BattleSideConditionDurationManager.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.model.AttachedEffectState
import io.github.lishangbu.avalon.game.battle.engine.core.model.SideState
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveSideConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.type.StandardTargetSelectorIds
/**
* side condition 持续回合推进器。
*
* 设计意图:
* - 在回合末统一衰减并清理带固定持续时间的 side condition。
* - 把 duration 语义从 `BattleSessionLifecycleCoordinator` 中拆出来,避免回合流程代码继续膨胀。
*
* 当前约定:
* - `duration = null` 视为无固定回合数,不会在这里被自动移除;
* - 每次 `endTurn()` 完成 residual 后,所有 side condition 的剩余回合数减 1;
* - 当剩余回合数降到 0 时,该 side condition 会被从 side 上移除。
*/
internal class BattleSideConditionDurationManager {
/**
* 推进当前快照中全部 side condition 的剩余持续时间。
*/
fun advance(snapshot: BattleRuntimeSnapshot): BattleEffectDurationAdvanceResult {
if (snapshot.sides.isEmpty()) {
return BattleEffectDurationAdvanceResult(snapshot)
}
val expirationMutations = mutableListOf<BattleSessionScopedMutation>()
val nextSides =
snapshot.sides.mapValues { (_, side) ->
advanceSide(side, expirationMutations)
}
val nextSnapshot =
if (nextSides == snapshot.sides) {
snapshot
} else {
snapshot.copy(sides = nextSides)
}
return BattleEffectDurationAdvanceResult(
snapshot = nextSnapshot,
expirationMutations = expirationMutations,
)
}
/**
* 推进单个 side 上全部 side condition 的剩余持续时间。
*/
private fun advanceSide(
side: SideState,
expirationMutations: MutableList<BattleSessionScopedMutation>,
): SideState {
if (side.conditionStates.isEmpty()) {
return side
}
val contextUnitId = (side.activeUnitIds + side.unitIds).distinct().firstOrNull()
val nextStates = linkedMapOf<String, AttachedEffectState>()
side.conditionStates.values
.sortedBy(AttachedEffectState::effectOrder)
.forEach { state ->
val nextState =
advanceState(state) { expiredState ->
if (contextUnitId != null) {
expirationMutations +=
BattleSessionScopedMutation(
selfId = contextUnitId,
targetId = contextUnitId,
sourceId = expiredState.sourceId,
mutation =
RemoveSideConditionMutation(
target = StandardTargetSelectorIds.SIDE,
conditionEffectId = expiredState.effectId,
),
)
}
}
if (nextState != null) {
nextStates[nextState.effectId] = nextState
}
}
return if (nextStates == side.conditionStates) {
side
} else {
side.copy(
conditionStates = nextStates,
)
}
}
/**
* 推进单个 side condition 的剩余回合。
*
* 返回空值表示该 condition 已在本次回合结束时自然到期。
*/
private fun advanceState(
state: AttachedEffectState,
onExpired: (AttachedEffectState) -> Unit = {},
): AttachedEffectState? {
val duration = state.duration ?: return state
val nextDuration = duration - 1
return if (nextDuration > 0) {
state.copy(duration = nextDuration)
} else {
onExpired(state)
state
}
}
}