BattleSessionActionSpeedResolver.kt

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

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

/**
 * battle session 行动速度解析器。
 *
 * 设计意图:
 * - 把“动作原始 speed 字段”提升为可读取当前快照的有效速度解析;
 * - 让回合内排序能够感知 side condition 等运行时规则,而不把这些规则散落进排序器本体;
 * - 为后续继续接入更完整的速度修正体系保留单一扩展点。
 *
 * 当前已接入:
 * - `tailwind`:将所属 side 的有效速度翻倍;
 * - `trick-room-field`:当 field 上挂有该 room 标记时,反转速度排序方向;
 * - 当 action 自身未显式传入 speed 时,会从提交单位或所属 side 的 active 单位推导速度。
 */
class BattleSessionActionSpeedResolver {
    /**
     * 解析单个 action 在当前快照下的有效速度。
     */
    fun resolveEffectiveSpeed(
        snapshot: BattleRuntimeSnapshot,
        action: BattleSessionAction,
    ): Int {
        val baseSpeed = resolveBaseSpeed(snapshot, action)
        val side = resolveSide(snapshot, action) ?: return baseSpeed
        return if (TAILWIND_EFFECT_ID in side.conditionStates) {
            baseSpeed * 2
        } else {
            baseSpeed
        }
    }

    /**
     * 当前快照下是否应按 Trick Room 反转速度排序。
     *
     * `trick-room-field` 已收口为正式 field condition,
     * 后续同类 room 规则也应沿这一公共场地状态链路接入。
     */
    fun isSpeedOrderReversed(snapshot: BattleRuntimeSnapshot): Boolean = BattleFieldConditionIds.TRICK_ROOM in snapshot.field.conditionStates

    private fun resolveBaseSpeed(
        snapshot: BattleRuntimeSnapshot,
        action: BattleSessionAction,
    ): Int {
        if (action.speed != 0) {
            return action.speed
        }
        return when (action) {
            is BattleSessionSubmittingAction -> {
                resolveUnitSpeed(snapshot, action.submittingUnitId)
            }

            is BattleSessionSideAction -> {
                val side = snapshot.sides[action.sideId]
                side
                    ?.activeUnitIds
                    ?.map { unitId -> resolveUnitSpeed(snapshot, unitId) }
                    ?.maxOrNull()
                    ?: 0
            }

            else -> {
                0
            }
        }
    }

    private fun resolveSide(
        snapshot: BattleRuntimeSnapshot,
        action: BattleSessionAction,
    ) = when (action) {
        is BattleSessionSideAction -> {
            snapshot.sides[action.sideId]
        }

        is BattleSessionSubmittingAction -> {
            snapshot.sides.values.firstOrNull { side -> action.submittingUnitId in side.unitIds }
        }

        else -> {
            null
        }
    }

    private fun resolveUnitSpeed(
        snapshot: BattleRuntimeSnapshot,
        unitId: String,
    ): Int {
        val stats = snapshot.units[unitId]?.stats.orEmpty()
        return (stats["speed"] ?: stats["spe"]) ?: 0
    }

    private companion object {
        private const val TAILWIND_EFFECT_ID: String = "tailwind-boost"
    }
}