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,
)
}
}