BattleTriggeredHookDispatcher.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow
import io.github.lishangbu.avalon.game.battle.engine.core.event.StandardHookNames
import io.github.lishangbu.avalon.game.battle.engine.core.model.SideState
import io.github.lishangbu.avalon.game.battle.engine.core.type.HookName
/**
* triggered hook 广播器。
*
* 设计意图:
* - 把 mutation apply 之后的 triggered hook 回放逻辑从具体流程类中拆出来;
* - 统一 `trigger_event` 与天气/地形这类全局广播 hook 的派发语义;
* - 让 session 内部 mutation 提交与 move phase mutation 提交复用同一套后续 hook 逻辑。
*
* 当前约定:
* - `on_weather_change / on_terrain_change` 会广播给当前全部 active 单位;
* - 其他 triggered hook 只会回放给本次上下文中的参与单位;
* - 当局部 triggered hook 没有参与单位时,当前不会退化成全场广播。
*/
internal class BattleTriggeredHookDispatcher(
private val attachedEffectProcessor: BattleAttachedEffectProcessor,
) {
/**
* 回放一批 mutation apply 请求触发的后续 hook。
*/
fun dispatch(
snapshot: BattleRuntimeSnapshot,
triggeredHooks: List<HookName>,
targetId: String?,
sourceId: String?,
participantUnitIds: List<String>,
): BattleRuntimeSnapshot {
if (triggeredHooks.isEmpty()) {
return snapshot
}
var currentSnapshot = snapshot
triggeredHooks.forEach { hookName ->
val targetUnitIds =
when (hookName.value) {
StandardHookNames.ON_WEATHER_CHANGE.value,
StandardHookNames.ON_TERRAIN_CHANGE.value,
-> {
currentSnapshot.sides.values
.flatMap(SideState::activeUnitIds)
.distinct()
}
else -> {
participantUnitIds.filter { unitId -> unitId in currentSnapshot.units }
}
}
if (targetUnitIds.isEmpty()) {
return@forEach
}
targetUnitIds.forEach { unitId ->
currentSnapshot =
attachedEffectProcessor
.process(
snapshot = currentSnapshot,
unitId = unitId,
hookName = hookName.value,
targetId = targetId,
sourceId = sourceId,
relay = null,
attributes = emptyMap(),
).snapshot
}
}
return currentSnapshot
}
}