BattleSessionActionExecutionSupport.kt

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

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

/**
 * session action 执行辅助组件。
 *
 * 设计意图:
 * - 作为 session action 执行相关 helper 的稳定门面;
 * - 对外继续维持一个 Bean/构造入口,避免 Spring 配置和测试装配反复跟着改签名;
 * - 具体职责拆给独立的小组件,让代码按“一个执行职责一个文件”继续收口。
 */
class BattleSessionActionExecutionSupport(
    private val effectExecutionCoordinator: BattleSessionEffectExecutionCoordinator = BattleSessionEffectExecutionCoordinator(),
    private val directDamageApplier: BattleSessionDirectDamageApplier = BattleSessionDirectDamageApplier(),
    private val switchActionExecutor: BattleSessionSwitchActionExecutor = BattleSessionSwitchActionExecutor(),
    private val runActionExecutor: BattleSessionRunActionExecutor = BattleSessionRunActionExecutor(),
) {
    /**
     * 执行一个已经具备目标信息的 effect。
     *
     * @param session 当前 battle session。
     * @param effectId 当前执行的 effect 标识。
     * @param actorUnitId 行动发起者单位标识。
     * @param targetUnitId 调用方提供的目标单位标识。
     * @param accuracy 命中值输入。
     * @param evasion 回避值输入。
     * @param basePower 基础威力输入。
     * @param damage 基础伤害输入。
     * @param attributes 透传给 battle flow 的扩展属性。
     * @return effect 结算完成后的聚合结果。
     */
    fun executeResolvedEffect(
        session: BattleSession,
        effectId: String,
        actorUnitId: String,
        targetUnitId: String,
        accuracy: Int?,
        evasion: Int?,
        basePower: Int,
        damage: Int,
        attributes: Map<String, Any?>,
    ): MoveResolutionResult =
        effectExecutionCoordinator.executeResolvedEffect(
            session = session,
            effectId = effectId,
            actorUnitId = actorUnitId,
            targetUnitId = targetUnitId,
            accuracy = accuracy,
            evasion = evasion,
            basePower = basePower,
            damage = damage,
            attributes = attributes,
        )

    /**
     * 将直接伤害 mutation 应用到当前快照。
     *
     * 设计意图:
     * - move resolution 主流程当前只把“最终伤害数字”算出来,还没有把这一步自动接进 mutation interceptor 链;
     * - 因此这里在真正扣血前,手动为目标派发一次 `on_damage`;
     * - 与 `DamageBattleMutationInterceptor` 不同,这里是单目标直接扣血路径,可以安全支持数值 relay 覆盖。
     */
    fun applyDirectDamage(
        session: BattleSession,
        sourceId: String,
        targetId: String,
        damage: Int,
    ): BattleRuntimeSnapshot = directDamageApplier.apply(session, sourceId, targetId, damage)

    /**
     * 记录一次 move 或 item 的最终结算结果。
     */
    fun recordMoveExecution(
        session: BattleSession,
        moveId: String,
        attackerId: String,
        targetId: String,
        result: MoveResolutionResult,
    ) {
        session.recordLog(
            "Executed move $moveId from $attackerId to $targetId. " +
                "hit=${result.hitSuccessful}, critical=${result.criticalHit}, " +
                "basePower=${result.basePower}, " +
                (result.damageRoll?.let { "damageRoll=$it, " } ?: "") +
                "damage=${result.damage}.",
        )
        session.recordEvent(
            BattleSessionMoveExecutedPayload(
                moveId = moveId,
                attackerId = attackerId,
                targetId = targetId,
                hitSuccessful = result.hitSuccessful,
                criticalHit = result.criticalHit,
                basePower = result.basePower,
                damageRoll = result.damageRoll,
                damage = result.damage,
            ),
        )
    }

    /**
     * 执行 switch 动作并更新当前 active 列表。
     */
    fun applySwitchAction(
        session: BattleSession,
        action: BattleSessionSwitchAction,
    ): BattleRuntimeSnapshot = switchActionExecutor.apply(session, action)

    /**
     * 执行逃跑动作。
     */
    fun applyRunAction(
        session: BattleSession,
        action: BattleSessionRunAction,
    ): BattleRuntimeSnapshot = runActionExecutor.apply(session, action)
}