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
    }
}