BattleStoredBoostSupport.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.support
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleBoostRelaySelectionValues
/**
* 既有 stored boosts 的读取、筛选与迁移支持组件。
*
* 设计意图:
* - 把 `psych-up / power-swap / guard-swap / heart-swap / topsy-turvy / spectral-thief`
* 这类基于“当前已持有能力阶级”的规则收口到统一 helper;
* - 避免多个 action executor 各自重复实现 boost map 归一、过滤、交换和偷取逻辑;
* - 保持所有 stored boost 改写都共享同一套别名、上下限和 0 值清理规则。
*/
object BattleStoredBoostSupport {
/**
* 一次 boost 转移的结果。
*
* @property sourceBoosts 转移后来源单位的 boosts。
* @property targetBoosts 转移后目标单位的 boosts。
* @property transferredBoosts 本次真正被转移的 boost 子集。
*/
data class TransferResult(
val sourceBoosts: Map<String, Int>,
val targetBoosts: Map<String, Int>,
val transferredBoosts: Map<String, Int>,
)
/**
* 读取并规范化一份 stored boosts。
*/
fun normalize(boosts: Map<String, Int>): Map<String, Int> = BattleStatStageSupport.normalizeStoredBoosts(boosts)
/**
* 按 selection 与 stats 子集筛选 stored boosts。
*/
fun select(
boosts: Map<String, Int>,
selection: String = BattleBoostRelaySelectionValues.ALL,
stats: Set<String> = emptySet(),
): Map<String, Int> {
val normalizedBoosts = normalize(boosts)
val canonicalStats =
stats
.map(BattleStatAliasResolver::canonicalBoostId)
.toSet()
return normalizedBoosts.filter { (statId, stage) ->
(canonicalStats.isEmpty() || statId in canonicalStats) &&
matchesSelection(stage, selection)
}
}
/**
* 用 replacement 替换 current 中指定 stat 子集的 boosts。
*/
fun replaceSelected(
current: Map<String, Int>,
replacement: Map<String, Int>,
selectedStats: Set<String>,
): Map<String, Int> {
val normalizedCurrent = normalize(current)
val normalizedReplacement = normalize(replacement)
val canonicalStats =
selectedStats
.map(BattleStatAliasResolver::canonicalBoostId)
.toSet()
if (canonicalStats.isEmpty()) {
return normalizedCurrent
}
val nextBoosts = normalizedCurrent.toMutableMap()
canonicalStats.forEach(nextBoosts::remove)
normalizedReplacement
.filterKeys { statId -> statId in canonicalStats }
.forEach { (statId, stage) ->
nextBoosts[statId] = stage
}
return normalize(nextBoosts)
}
/**
* 把指定 stat 子集取反。
*/
fun invert(
boosts: Map<String, Int>,
stats: Set<String> = emptySet(),
): Map<String, Int> {
val normalizedBoosts = normalize(boosts)
val selectedStats = resolveSelectedStats(normalizedBoosts, emptyMap(), stats)
val invertedSelection =
selectedStats.associateWith { statId ->
-(normalizedBoosts[statId] ?: 0)
}
return replaceSelected(normalizedBoosts, invertedSelection, selectedStats)
}
/**
* 交换两份 boosts 的指定 stat 子集。
*/
fun swap(
left: Map<String, Int>,
right: Map<String, Int>,
stats: Set<String> = emptySet(),
): Pair<Map<String, Int>, Map<String, Int>> {
val normalizedLeft = normalize(left)
val normalizedRight = normalize(right)
val selectedStats = resolveSelectedStats(normalizedLeft, normalizedRight, stats)
val leftSelected = normalizedLeft.filterKeys { statId -> statId in selectedStats }
val rightSelected = normalizedRight.filterKeys { statId -> statId in selectedStats }
return replaceSelected(normalizedLeft, rightSelected, selectedStats) to
replaceSelected(normalizedRight, leftSelected, selectedStats)
}
/**
* 从 source 中取出指定 boosts,并把它们加到 target 身上。
*
* 当前主要用于 Spectral Thief 这类“偷取对方正向强化”的规则。
*/
fun steal(
source: Map<String, Int>,
target: Map<String, Int>,
selection: String = BattleBoostRelaySelectionValues.POSITIVE,
stats: Set<String> = emptySet(),
): TransferResult {
val normalizedSource = normalize(source)
val transferredBoosts = select(normalizedSource, selection, stats)
val sourceNext =
normalizedSource.filterKeys { statId ->
statId !in transferredBoosts.keys
}
val targetNext =
BattleStatStageSupport.applyBoostDeltas(
currentBoosts = normalize(target),
deltas = transferredBoosts,
)
return TransferResult(
sourceBoosts = normalize(sourceNext),
targetBoosts = targetNext,
transferredBoosts = transferredBoosts,
)
}
/**
* 统计当前 boosts 中全部正向 stage 的总和。
*/
fun sumPositiveStages(boosts: Map<String, Int>): Int = normalize(boosts).values.filter { stage -> stage > 0 }.sum()
private fun resolveSelectedStats(
left: Map<String, Int>,
right: Map<String, Int>,
stats: Set<String>,
): Set<String> =
if (stats.isNotEmpty()) {
stats
.map(BattleStatAliasResolver::canonicalBoostId)
.toSet()
} else {
(left.keys + right.keys).toSet()
}
private fun matchesSelection(
stage: Int,
selection: String,
): Boolean =
when (selection) {
BattleBoostRelaySelectionValues.ALL -> true
BattleBoostRelaySelectionValues.POSITIVE -> stage > 0
BattleBoostRelaySelectionValues.NEGATIVE -> stage < 0
else -> false
}
}