UnitMutationStateApplier.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.apply
import io.github.lishangbu.avalon.game.battle.engine.core.model.AttachedEffectState
import io.github.lishangbu.avalon.game.battle.engine.core.model.UnitState
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.AddVolatileMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ApplyConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.BoostMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ChangeTypeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearBoostsMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ClearProbeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ConsumeItemMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.DamageMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.ForceSwitchMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.HealMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveConditionMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveStatusMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RemoveVolatileMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.RestorePpMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetBoostsMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetProbeMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetStatusMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.SetSwitchBoostCarryMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleStatStageSupport
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.MutationTargetSelectorResolver
/**
* unit 级 mutation 写回助手。
*
* 设计意图:
* - 把 `DefaultMutationApplier` 里所有单位状态写回逻辑收口到一个独立文件;
* - 保持 `DefaultMutationApplier` 只负责分发 mutation 类型,而不是继续堆所有具体状态变换细节。
*/
internal object UnitMutationStateApplier {
fun applyDamage(
mutation: DamageMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
val damage = calculateAmount(unit.maxHp, mutation.mode, mutation.value)
unit.copy(currentHp = (unit.currentHp - damage).coerceAtLeast(0))
}
fun applyHeal(
mutation: HealMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
val healing = calculateAmount(unit.maxHp, mutation.mode, mutation.value)
unit.copy(currentHp = (unit.currentHp + healing).coerceAtMost(unit.maxHp))
}
fun applySetStatus(
mutation: SetStatusMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(
statusState =
AttachedEffectState(
effectId = mutation.statusEffectId,
sourceId = mutation.sourceId,
duration = mutation.duration,
effectOrder = unit.statusState?.effectOrder ?: 1,
),
)
}
fun applyRemoveStatus(
mutation: RemoveStatusMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(statusState = null)
}
fun applyAddVolatile(
mutation: AddVolatileMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
val existingState = unit.volatileStates[mutation.volatileEffectId]
val nextOrder =
existingState?.effectOrder
?: ((unit.volatileStates.values.maxOfOrNull(AttachedEffectState::effectOrder) ?: 0) + 1)
unit.copy(
volatileStates =
unit.volatileStates +
(
mutation.volatileEffectId to
AttachedEffectState(
effectId = mutation.volatileEffectId,
sourceId = mutation.sourceId,
duration = mutation.duration,
effectOrder = nextOrder,
)
),
)
}
fun applyRemoveVolatile(
mutation: RemoveVolatileMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(volatileStates = unit.volatileStates - mutation.volatileEffectId)
}
fun applyBoost(
mutation: BoostMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(
boosts =
BattleStatStageSupport.applyBoostDeltas(
currentBoosts = unit.boosts,
deltas = mutation.boosts,
),
)
}
fun applyClearBoosts(
mutation: ClearBoostsMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(boosts = emptyMap())
}
fun applySetBoosts(
mutation: SetBoostsMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(
boosts = BattleStatStageSupport.normalizeStoredBoosts(mutation.boosts),
)
}
fun applyConsumeItem(
mutation: ConsumeItemMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(itemId = null)
}
fun applyRestorePp(
mutation: RestorePpMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
if (mutation.moveId == null) {
unit
} else {
val nextPp = unit.movePp.toMutableMap()
nextPp[mutation.moveId] = (nextPp[mutation.moveId] ?: 0) + mutation.value
unit.copy(movePp = nextPp)
}
}
fun applyChangeType(
mutation: ChangeTypeMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(typeIds = mutation.values.toSet())
}
fun applyForceSwitch(
mutation: ForceSwitchMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(forceSwitchRequested = true)
}
fun applySetSwitchBoostCarry(
mutation: SetSwitchBoostCarryMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(
sessionState =
unit.sessionState.copy(
pendingSwitchBoostCarry = BattleStatStageSupport.normalizeStoredBoosts(mutation.boosts),
),
)
}
fun applyAddCondition(
mutation: ApplyConditionMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
val existingState = unit.conditionStates[mutation.conditionEffectId]
val nextOrder =
existingState?.effectOrder
?: ((unit.conditionStates.values.maxOfOrNull(AttachedEffectState::effectOrder) ?: 0) + 1)
unit.copy(
conditionStates =
unit.conditionStates +
(
mutation.conditionEffectId to
AttachedEffectState(
effectId = mutation.conditionEffectId,
sourceId = mutation.sourceId,
duration = mutation.duration,
effectOrder = nextOrder,
)
),
)
}
fun applyRemoveCondition(
mutation: RemoveConditionMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(conditionStates = unit.conditionStates - mutation.conditionEffectId)
}
fun applySetProbe(
mutation: SetProbeMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(debugState = unit.debugState.copy(probes = unit.debugState.probes + (mutation.key to mutation.value)))
}
fun applyClearProbe(
mutation: ClearProbeMutation,
context: MutationApplicationContext,
units: Map<String, UnitState>,
): Map<String, UnitState> =
updateTargets(mutation.target, context, units) { unit ->
unit.copy(debugState = unit.debugState.copy(probes = unit.debugState.probes - mutation.key))
}
private fun updateTargets(
selector: io.github.lishangbu.avalon.game.battle.engine.core.type.TargetSelectorId,
context: MutationApplicationContext,
units: Map<String, UnitState>,
transform: (UnitState) -> UnitState,
): Map<String, UnitState> {
val targetIds = MutationTargetSelectorResolver.resolve(selector, context)
if (targetIds.isEmpty()) {
return units
}
val nextUnits = units.toMutableMap()
targetIds.forEach { unitId ->
val unit = requireNotNull(nextUnits[unitId]) { "Target unit '$unitId' was not found." }
nextUnits[unitId] = transform(unit)
}
return nextUnits
}
private fun calculateAmount(
maxHp: Int,
mode: String?,
value: Double,
): Int =
when (mode) {
"max_hp_ratio" -> (maxHp * value).toInt()
else -> value.toInt()
}
}