BattleSessionMutationProcessor.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.BattleMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.apply.MutationApplicationContext
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleAttachedEffectProcessor
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleTriggeredHookDispatcher
/**
* session 内部通用 mutation 提交器。
*
* 设计意图:
* - 让 session 内部的“非 move phase mutation”也统一经过 interceptor、applier 和 triggered hook;
* - 收口 end-turn duration 到期移除、直接伤害补扣血这类原本绕过完整生命周期的路径;
* - 避免在多个 session 协调类里重复拼装 mutation filter/apply 上下文。
*/
internal class BattleSessionMutationProcessor(
private val session: BattleSession,
) {
private val triggeredHookDispatcher =
BattleTriggeredHookDispatcher(
attachedEffectProcessor =
BattleAttachedEffectProcessor { snapshot, unitId, hookName, targetId, sourceId, relay, attributes ->
session.battleFlowPhaseProcessor.processAttachedEffects(
snapshot = snapshot,
unitId = unitId,
hookName = hookName,
targetId = targetId,
sourceId = sourceId,
relay = relay,
attributes = attributes,
)
},
)
/**
* 以 session 语义提交一批 mutation。
*
* @param snapshot 当前提交起点快照
* @param selfId 当前 mutation 上下文的 self 单位
* @param targetId 当前 mutation 上下文的目标单位
* @param sourceId 当前 mutation 上下文的来源单位
* @param mutations 待提交的 mutation 列表
*/
fun apply(
snapshot: BattleRuntimeSnapshot,
selfId: String?,
targetId: String?,
sourceId: String?,
mutations: List<BattleMutation>,
): BattleRuntimeSnapshot {
if (mutations.isEmpty()) {
return snapshot
}
val filteredResult =
session.mutationInterceptorChain.filter(
snapshot = snapshot,
selfId = selfId,
targetId = targetId,
sourceId = sourceId,
mutations = mutations,
attachedEffectProcessor =
BattleAttachedEffectProcessor { attachedSnapshot, unitId, hookName, attachedTargetId, attachedSourceId, relay, attributes ->
session.battleFlowPhaseProcessor.processAttachedEffects(
snapshot = attachedSnapshot,
unitId = unitId,
hookName = hookName,
targetId = attachedTargetId,
sourceId = attachedSourceId,
relay = relay,
attributes = attributes,
)
},
)
if (filteredResult.mutations.isEmpty()) {
return filteredResult.snapshot
}
val applyResult =
session.mutationApplier.apply(
mutations = filteredResult.mutations,
context =
MutationApplicationContext(
battle = filteredResult.snapshot.battle,
field = filteredResult.snapshot.field,
units = filteredResult.snapshot.units,
sides = filteredResult.snapshot.sides,
selfId = selfId,
targetId = targetId,
sourceId = sourceId,
side = selfId?.let(session::sideIdOfUnit)?.let(filteredResult.snapshot.sides::get),
foeSide = targetId?.let(session::sideIdOfUnit)?.let(filteredResult.snapshot.sides::get),
),
)
val appliedSnapshot =
filteredResult.snapshot.copy(
battle = applyResult.battle,
field = applyResult.field,
units = applyResult.units,
sides = applyResult.sides,
)
return triggeredHookDispatcher.dispatch(
snapshot = appliedSnapshot,
triggeredHooks = applyResult.triggeredHooks,
targetId = targetId,
sourceId = sourceId,
participantUnitIds = listOfNotNull(selfId, targetId, sourceId).distinct(),
)
}
}