BattleSessionDirectDamageApplier.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleAttributeKeys
import io.github.lishangbu.avalon.game.battle.engine.core.event.StandardHookNames
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.DamageMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.apply.MutationApplicationContext
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.type.StandardTargetSelectorIds
/**
* session 直接伤害写回器。
*
* 设计意图:
* - 承接“move pipeline 已经算出最终伤害数值,但还没自动落盘”的单目标扣血路径;
* - 在真正写回前手动派发一次 `on_damage`,让 attached effect 有机会 veto
* 或覆盖最终伤害;
* - 与通用 mutation interceptor 链分开,避免重复触发 `on_damage` 生命周期。
*/
class BattleSessionDirectDamageApplier {
/**
* 将直接伤害应用到当前快照。
*/
fun apply(
session: BattleSession,
sourceId: String,
targetId: String,
damage: Int,
): BattleRuntimeSnapshot {
val hookResult =
session.battleFlowPhaseProcessor.processAttachedEffects(
snapshot = session.currentSnapshot,
unitId = targetId,
hookName = StandardHookNames.ON_DAMAGE.value,
targetId = targetId,
sourceId = sourceId,
relay = damage,
attributes =
buildMap {
put("amount", damage)
BattleSessionUnitRelationResolver
.resolveTargetRelation(session.currentSnapshot, sourceId, targetId)
?.let { relation -> put(BattleAttributeKeys.TARGET_RELATION, relation) }
},
)
val resolvedDamage =
when {
hookResult.cancelled || hookResult.relay == false -> 0
hookResult.relay is Number -> hookResult.relay.toInt().coerceAtLeast(0)
else -> damage
}
if (resolvedDamage <= 0) {
return hookResult.snapshot
}
val applyResult =
session.mutationApplier.apply(
mutations =
listOf(
DamageMutation(
target = StandardTargetSelectorIds.TARGET,
mode = null,
value = resolvedDamage.toDouble(),
),
),
context =
MutationApplicationContext(
battle = hookResult.snapshot.battle,
field = hookResult.snapshot.field,
units = hookResult.snapshot.units,
sides = hookResult.snapshot.sides,
selfId = sourceId,
targetId = targetId,
sourceId = sourceId,
side = BattleSessionUnitRelationResolver.sideOfUnit(hookResult.snapshot, sourceId),
foeSide = BattleSessionUnitRelationResolver.sideOfUnit(hookResult.snapshot, targetId),
),
)
return hookResult.snapshot.copy(
battle = applyResult.battle,
field = applyResult.field,
units = applyResult.units,
sides = applyResult.sides,
)
}
}