BattleBoostContextSupport.kt

package io.github.lishangbu.avalon.game.battle.engine.core.runtime.support

import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleAttributeKeys
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleBoostDirectionValues

/**
 * boost / stage 变化上下文的统一 support。
 *
 * 设计意图:
 * - 收口 boost relay、boost attributes 的规范化与构造逻辑;
 * - 避免 `BoostBattleMutationInterceptor`、`ClearBoostsBattleMutationInterceptor`、
 *   `boost_from_relay`、`invert_boost_relay` 各自重复实现一套 Map 解析规则;
 * - 让能力变化主链对外暴露的上下文字段保持稳定、可预测。
 */
object BattleBoostContextSupport {
    /**
     * 把任意运行期对象按 boost map 语义规范化成 `Map<String, Int>`。
     *
     * 合法输入:
     * - `Map<String, Number>`
     * - 使用 `atk/attack`、`spe/speed` 等别名的 map
     *
     * 非法输入返回 `null`,由上层决定是否回退或忽略。
     */
    fun normalizeBoostMap(value: Any?): Map<String, Int>? {
        val rawMap = value as? Map<*, *> ?: return null
        val rawBoosts = linkedMapOf<String, Int>()
        rawMap.forEach { (key, entryValue) ->
            val statId = key as? String ?: return null
            val delta = entryValue as? Number ?: return null
            rawBoosts[statId] = delta.toInt()
        }
        return BattleStatStageSupport.normalizeBoostPayload(rawBoosts)
    }

    /**
     * 构造 `on_boost` 标准 attributes。
     *
     * 这些字段会同时服务于:
     * - 条件判断,如 `negativeBoostChanged`
     * - relay 改写,如 `requestedBoosts` / `appliedBoosts`
     * - 调试探针与测试断言
     */
    fun createOnBoostAttributes(
        currentBoosts: Map<String, Int>,
        requestedBoosts: Map<String, Int>,
        targetRelation: String? = null,
    ): Map<String, Any?> {
        val normalizedCurrentBoosts = BattleStatStageSupport.normalizeStoredBoosts(currentBoosts)
        val normalizedRequestedBoosts = BattleStatStageSupport.normalizeBoostPayload(requestedBoosts)
        val appliedBoosts =
            BattleStatStageSupport.resolveAppliedBoostDeltas(
                currentBoosts = normalizedCurrentBoosts,
                requestedBoosts = normalizedRequestedBoosts,
            )
        val positiveBoostChanged = BattleStatStageSupport.hasPositiveBoostChange(appliedBoosts)
        val negativeBoostChanged = BattleStatStageSupport.hasNegativeBoostChange(appliedBoosts)
        return buildMap {
            put(BattleAttributeKeys.BOOSTS, normalizedRequestedBoosts)
            put(BattleAttributeKeys.REQUESTED_BOOSTS, normalizedRequestedBoosts)
            put(BattleAttributeKeys.CURRENT_BOOSTS, normalizedCurrentBoosts)
            put(BattleAttributeKeys.APPLIED_BOOSTS, appliedBoosts)
            put(BattleAttributeKeys.BOOST_CHANGED, appliedBoosts.isNotEmpty())
            put(BattleAttributeKeys.POSITIVE_BOOST_CHANGED, positiveBoostChanged)
            put(BattleAttributeKeys.NEGATIVE_BOOST_CHANGED, negativeBoostChanged)
            put(
                BattleAttributeKeys.BOOST_DIRECTION,
                BattleStatStageSupport.resolveBoostDirection(appliedBoosts),
            )
            targetRelation?.let { relation ->
                put(BattleAttributeKeys.TARGET_RELATION, relation)
            }
        }
    }

    /**
     * 构造 `on_clear_boosts` 标准 attributes。
     */
    fun createOnClearBoostsAttributes(
        currentBoosts: Map<String, Int>,
        targetRelation: String? = null,
    ): Map<String, Any?> {
        val normalizedCurrentBoosts = BattleStatStageSupport.normalizeStoredBoosts(currentBoosts)
        val boostChanged = normalizedCurrentBoosts.isNotEmpty()
        return buildMap {
            put(BattleAttributeKeys.BOOSTS, normalizedCurrentBoosts)
            put(BattleAttributeKeys.CURRENT_BOOSTS, normalizedCurrentBoosts)
            put(BattleAttributeKeys.CLEARED_BOOSTS, normalizedCurrentBoosts)
            put(BattleAttributeKeys.BOOST_CHANGED, boostChanged)
            put(BattleAttributeKeys.BOOST_DIRECTION, BattleBoostDirectionValues.NONE)
            targetRelation?.let { relation ->
                put(BattleAttributeKeys.TARGET_RELATION, relation)
            }
        }
    }
}