BattleSessionReplacementPlanResolver.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.model.SideState
import io.github.lishangbu.avalon.game.battle.engine.core.model.UnitState
/**
* 替补需求与自动替补计划解析器。
*
* 设计意图:
* - 收敛“哪些 active 需要被替下”“当前有哪些候补”“自动替补策略选中了谁”
* 这一组纯解析逻辑;
* - 让 `BattleSessionReplacementResolver` 不再自己维护大段集合运算;
* - 为后续更复杂的 replacement policy 保留稳定的计划模型。
*/
internal class BattleSessionReplacementPlanResolver(
private val replacementStrategy: ReplacementStrategy,
) {
/**
* 为单个 side 解析当前替补计划。
*/
fun resolve(
sideId: String,
side: SideState,
units: Map<String, UnitState>,
): BattleSessionReplacementPlan {
val faintedActiveIds = side.activeUnitIds.filter { unitId -> (units[unitId]?.currentHp ?: 0) <= 0 }
val forcedActiveIds = side.activeUnitIds.filter { unitId -> units[unitId]?.forceSwitchRequested == true }
val requiredOutgoingUnitIds = (faintedActiveIds + forcedActiveIds).distinct()
val nextActiveUnitIds = replacementStrategy.selectActiveUnitIds(side, units)
val candidateUnitIds =
side.unitIds
.filterNot { unitId -> unitId in side.activeUnitIds }
.filter { unitId -> (units[unitId]?.currentHp ?: 0) > 0 }
return BattleSessionReplacementPlan(
sideId = sideId,
beforeActiveUnitIds = side.activeUnitIds,
nextActiveUnitIds = nextActiveUnitIds,
requiredOutgoingUnitIds = requiredOutgoingUnitIds,
candidateUnitIds = candidateUnitIds,
slotTransitions = slotTransitions(side.activeUnitIds, nextActiveUnitIds),
)
}
/**
* 以槽位维度比较换人前后 active 列表,提取出真正发生变化的“出场 / 入场”对。
*/
private fun slotTransitions(
before: List<String>,
after: List<String>,
): List<BattleSessionSlotTransition> =
(0 until maxOf(before.size, after.size))
.mapNotNull { index ->
val outgoingUnitId = before.getOrNull(index)
val incomingUnitId = after.getOrNull(index)
if (outgoingUnitId == incomingUnitId) {
null
} else {
BattleSessionSlotTransition(
outgoingUnitId = outgoingUnitId,
incomingUnitId = incomingUnitId,
)
}
}
}