BattleFieldEffectDurationManager.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.FieldState
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearTerrainMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearWeatherMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveFieldConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
/**
* 天气 / 地形的持续回合推进器。
*
* 设计意图:
* - 在回合末统一推进 field-attached effect 的持续时间;
* - 只以 `weatherState / terrainState / conditionStates` 作为场地挂载效果的唯一真实来源。
*/
internal class BattleFieldEffectDurationManager {
/**
* 推进当前快照上的天气与地形持续时间。
*/
fun advance(snapshot: BattleRuntimeSnapshot): BattleEffectDurationAdvanceResult {
val expirationMutations = mutableListOf<BattleSessionScopedMutation>()
val nextField = advanceField(snapshot.field, expirationMutations)
val nextSnapshot =
if (nextField == snapshot.field) {
snapshot
} else {
snapshot.copy(field = nextField)
}
return BattleEffectDurationAdvanceResult(
snapshot = nextSnapshot,
expirationMutations = expirationMutations,
)
}
/**
* 推进当前 field 上的天气、地形与 field condition 持续时间。
*/
private fun advanceField(
field: FieldState,
expirationMutations: MutableList<BattleSessionScopedMutation>,
): FieldState {
val nextWeatherState =
advanceState(field.weatherState) { state ->
expirationMutations +=
BattleSessionScopedMutation(
selfId = null,
targetId = null,
sourceId = state.sourceId,
mutation = ClearWeatherMutation(),
)
}
val nextTerrainState =
advanceState(field.terrainState) { state ->
expirationMutations +=
BattleSessionScopedMutation(
selfId = null,
targetId = null,
sourceId = state.sourceId,
mutation = ClearTerrainMutation(),
)
}
val nextConditionStates = linkedMapOf<String, AttachedEffectState>()
field.conditionStates.values
.sortedBy(AttachedEffectState::effectOrder)
.forEach { state ->
val nextState =
advanceState(state) { expiredState ->
expirationMutations +=
BattleSessionScopedMutation(
selfId = null,
targetId = null,
sourceId = expiredState.sourceId,
mutation = RemoveFieldConditionMutation(expiredState.effectId),
)
}
if (nextState != null) {
nextConditionStates[nextState.effectId] = nextState
}
}
return field.copy(
weatherState = nextWeatherState,
terrainState = nextTerrainState,
conditionStates = nextConditionStates,
)
}
/**
* 推进单个 field effect 的剩余回合。
*
* 返回空值表示该 effect 已在本次回合结束时自然到期。
*/
private fun advanceState(
state: AttachedEffectState?,
onExpired: (AttachedEffectState) -> Unit = {},
): AttachedEffectState? {
val currentState = state ?: return null
val duration = currentState.duration ?: return currentState
val nextDuration = duration - 1
return if (nextDuration > 0) {
currentState.copy(duration = nextDuration)
} else {
onExpired(currentState)
currentState
}
}
}