BoostBattleMutationInterceptor.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.mutation.BattleMutation
import io.github.lishangbu.avalon.game.battle.engine.core.mutation.BoostMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleBoostContextSupport
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleStatStageSupport
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.MutationTargetSelectorResolver

/**
 * 能力阶级变更 mutation 的生命周期拦截器。
 *
 * 设计意图:
 * - 把 `BoostMutation` 接到 `on_boost`,让能力 / 道具 / 状态能够在 boost 落盘前响应;
 * - 允许 hook 直接把 relay object 回写成新的 `boosts` 负载。
 *
 * 说明:
 * - `on_boost` 的标准 relay 语义是 object;
 * - 默认 DSL 已经提供 `set_relay`,可以直接把 relay 改成新的 boost map;
 * - 这里会把任意 object relay 规范化成 `Map<String, Int>` 后再决定是否重写 mutation。
 */
class BoostBattleMutationInterceptor : BattleMutationInterceptor {
    override val order: Int = 400

    override fun supports(mutation: BattleMutation): Boolean = mutation is BoostMutation

    override fun intercept(
        context: BattleMutationInterceptionContext,
        attachedEffectProcessor: BattleAttachedEffectProcessor,
    ): BattleMutationInterceptionResult {
        val mutation =
            context.mutation as? BoostMutation
                ?: return BattleMutationInterceptionResult(context.snapshot, true, context.mutation)
        val targetUnitIds =
            MutationTargetSelectorResolver.resolve(
                mutation.target,
                BattleMutationInterceptorSupport.mutationApplicationContext(context),
            )
        var currentSnapshot = context.snapshot
        var blocked = false
        var rewrittenBoosts: Map<String, Int>? = null
        var rewriteConflicted = false
        val requestedBoosts = BattleBoostContextSupport.normalizeBoostMap(mutation.boosts).orEmpty()

        targetUnitIds.forEach { affectedTargetId ->
            val affectedUnit = currentSnapshot.units[affectedTargetId] ?: return@forEach
            val currentBoosts = BattleStatStageSupport.normalizeStoredBoosts(affectedUnit.boosts)
            val result =
                attachedEffectProcessor.process(
                    snapshot = currentSnapshot,
                    unitId = affectedTargetId,
                    hookName = StandardHookNames.ON_BOOST.value,
                    targetId = affectedTargetId,
                    sourceId = context.sourceId,
                    relay = requestedBoosts,
                    attributes =
                        BattleBoostContextSupport.createOnBoostAttributes(
                            currentBoosts = currentBoosts,
                            requestedBoosts = requestedBoosts,
                            targetRelation =
                                BattleMutationInterceptorSupport.resolveTargetRelation(
                                    currentSnapshot,
                                    context.sourceId,
                                    affectedTargetId,
                                ),
                        ),
                )
            currentSnapshot = result.snapshot
            if (result.cancelled || result.relay == false) {
                blocked = true
                return@forEach
            }

            val relayBoosts = BattleBoostContextSupport.normalizeBoostMap(result.relay) ?: return@forEach
            if (targetUnitIds.size == 1) {
                rewrittenBoosts = relayBoosts
                return@forEach
            }
            if (rewrittenBoosts == null) {
                rewrittenBoosts = relayBoosts
            } else if (rewrittenBoosts != relayBoosts) {
                rewriteConflicted = true
            }
        }

        val nextMutation =
            if (!blocked && !rewriteConflicted && rewrittenBoosts != null) {
                mutation.copy(boosts = requireNotNull(rewrittenBoosts))
            } else if (!blocked && requestedBoosts != mutation.boosts) {
                mutation.copy(boosts = requestedBoosts)
            } else {
                mutation
            }

        return BattleMutationInterceptionResult(
            snapshot = currentSnapshot,
            allowed = !blocked,
            mutation = nextMutation,
        )
    }
}