BattleSessionSwitchActionExecutor.kt

package io.github.lishangbu.avalon.game.battle.engine.core.session

import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot

/**
 * session switch action 执行器。
 *
 * 设计意图:
 * - 收敛换人 action 的前置校验、switch out/in 生命周期触发、
 *   active 槽位替换以及 force switch 标记清理;
 * - 让换人编排从通用 action support 中拆出去,后续补复杂换位规则时
 *   可以在这里独立演进。
 */
class BattleSessionSwitchActionExecutor {
    /**
     * 执行 switch action 并更新当前 active 列表。
     */
    fun apply(
        session: BattleSession,
        action: BattleSessionSwitchAction,
    ): BattleRuntimeSnapshot {
        val side = requireNotNull(session.currentSnapshot.sides[action.sideId]) { "Side '${action.sideId}' was not found." }
        require(action.outgoingUnitId in side.activeUnitIds) {
            "Outgoing unit '${action.outgoingUnitId}' is not active on side '${action.sideId}'."
        }
        require(action.incomingUnitId in side.unitIds) {
            "Incoming unit '${action.incomingUnitId}' is not registered on side '${action.sideId}'."
        }
        require((session.currentSnapshot.units[action.incomingUnitId]?.currentHp ?: 0) > 0) {
            "Incoming unit '${action.incomingUnitId}' is not able to battle."
        }

        session.currentSnapshot = session.processSwitchOut(action.outgoingUnitId, session.currentSnapshot)
        session.currentSnapshot = BattleSessionSwitchBoostStateSupport.clearOutgoingBoosts(session.currentSnapshot, action.outgoingUnitId)
        val nextActiveIds =
            side.activeUnitIds.map { unitId ->
                if (unitId == action.outgoingUnitId) action.incomingUnitId else unitId
            }
        val switchedSnapshot =
            session.currentSnapshot.copy(
                sides = session.currentSnapshot.sides + (action.sideId to side.copy(activeUnitIds = nextActiveIds)),
            )
        session.currentSnapshot =
            session.clearForceSwitchRequests(
                snapshot = switchedSnapshot,
                unitIds = listOf(action.outgoingUnitId, action.incomingUnitId),
            )
        session.currentSnapshot =
            BattleSessionSwitchBoostStateSupport.applyIncomingBoostCarry(
                snapshot = session.currentSnapshot,
                outgoingUnitId = action.outgoingUnitId,
                incomingUnitId = action.incomingUnitId,
            )
        session.currentSnapshot = session.processSwitchIn(action.incomingUnitId, session.currentSnapshot)
        session.recordLog("Executed switch for side ${action.sideId}: ${action.outgoingUnitId} -> ${action.incomingUnitId}.")
        session.recordEvent(
            BattleSessionSwitchExecutedPayload(
                sideId = action.sideId,
                outgoingUnitId = action.outgoingUnitId,
                incomingUnitId = action.incomingUnitId,
            ),
        )
        return session.currentSnapshot
    }
}