RestorePpBattleMutationInterceptor.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleAttributeKeys
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.RestorePpMutation
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.MutationTargetSelectorResolver
/**
* PP 回复 mutation 的生命周期拦截器。
*
* 当前职责:
* - `on_restore_pp` 派发;
* - veto 能力;
* - 在 hook 返回整数 relay 时,把新的 PP 回复量真正回写到 mutation。
*
* 注意:
* - 一条 `RestorePpMutation` 可能对应多个目标;
* - 由于当前 mutation 仍是“共享负载”,只有当所有目标都给出一致的新数值时,
* 这里才会执行 rewrite,避免把最后一个目标的 relay 静默覆盖到前面目标。
*/
class RestorePpBattleMutationInterceptor : BattleMutationInterceptor {
override val order: Int = 550
override fun supports(mutation: BattleMutation): Boolean = mutation is RestorePpMutation
override fun intercept(
context: BattleMutationInterceptionContext,
attachedEffectProcessor: BattleAttachedEffectProcessor,
): BattleMutationInterceptionResult {
val mutation =
context.mutation as? RestorePpMutation
?: return BattleMutationInterceptionResult(context.snapshot, true, context.mutation)
val targetUnitIds =
MutationTargetSelectorResolver.resolve(
mutation.target,
BattleMutationInterceptorSupport.mutationApplicationContext(context),
)
var currentSnapshot = context.snapshot
var blocked = false
var rewrittenValue: Int? = null
var rewriteConflicted = false
targetUnitIds.forEach { affectedTargetId ->
val result =
attachedEffectProcessor.process(
snapshot = currentSnapshot,
unitId = affectedTargetId,
hookName = StandardHookNames.ON_RESTORE_PP.value,
targetId = affectedTargetId,
sourceId = context.sourceId,
relay = mutation.value,
attributes =
buildMap {
put("value", mutation.value)
mutation.moveId?.let { moveId -> put("moveId", moveId) }
BattleMutationInterceptorSupport
.resolveTargetRelation(currentSnapshot, context.sourceId, affectedTargetId)
?.let { relation -> put(BattleAttributeKeys.TARGET_RELATION, relation) }
},
)
currentSnapshot = result.snapshot
if (result.cancelled || result.relay == false) {
blocked = true
return@forEach
}
val relayValue = BattleMutationInterceptorSupport.normalizeIntegerRelay(result.relay) ?: return@forEach
if (targetUnitIds.size == 1) {
rewrittenValue = relayValue.coerceAtLeast(0)
return@forEach
}
if (rewrittenValue == null) {
rewrittenValue = relayValue.coerceAtLeast(0)
} else if (rewrittenValue != relayValue.coerceAtLeast(0)) {
rewriteConflicted = true
}
}
val nextMutation =
if (!blocked && !rewriteConflicted && rewrittenValue != null) {
mutation.copy(value = requireNotNull(rewrittenValue))
} else {
mutation
}
return BattleMutationInterceptionResult(
snapshot = currentSnapshot,
allowed = !blocked,
mutation = nextMutation,
)
}
}