BattleSessionEffectExecutionCoordinator.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.MoveResolutionResult
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleMoveDataReader
/**
* session effect 执行协调器。
*
* 设计意图:
* - 承载 move/item 这类“指定 effectId + actor + target”的统一结算编排;
* - 把目标展开、attributes 补全、逐目标聚合、直接伤害写回和日志事件记录
* 从 `BattleSessionActionExecutionSupport` 中剥离出来;
* - 让 action support 退化成一层薄分发器。
*/
class BattleSessionEffectExecutionCoordinator(
private val directDamageApplier: BattleSessionDirectDamageApplier = BattleSessionDirectDamageApplier(),
) {
/**
* 执行一个已经具备目标信息的 effect。
*/
fun executeResolvedEffect(
session: BattleSession,
effectId: String,
actorUnitId: String,
targetUnitId: String,
accuracy: Int?,
evasion: Int?,
basePower: Int,
damage: Int,
attributes: Map<String, Any?>,
): MoveResolutionResult {
val targetQuery = session.queryTargets(effectId, actorUnitId)
val effect = session.effectRepository.get(effectId)
val resolvedAccuracy = accuracy ?: BattleMoveDataReader.readAccuracy(effect.data)
val resolvedTargetIds =
when {
targetQuery.requiresExplicitTarget -> listOf(targetUnitId)
targetQuery.availableTargetUnitIds.isEmpty() -> listOf(targetUnitId)
else -> targetQuery.availableTargetUnitIds
}
val resolvedAttributes =
BattleSessionEffectAttributeResolver
.completeRandomAttributes(session, resolvedAccuracy, attributes)
.let { candidate ->
BattleSessionEffectAttributeResolver.withResolvedTargetCount(candidate, resolvedTargetIds.size)
}
var aggregatedResult: MoveResolutionResult? = null
resolvedTargetIds.forEach { resolvedTargetId ->
val targetAwareAttributes =
BattleSessionEffectAttributeResolver.withDerivedTargetRelation(
attributes = resolvedAttributes,
session = session,
actorUnitId = actorUnitId,
targetUnitId = resolvedTargetId,
)
val result =
session.battleFlowEngine.resolveMoveAction(
snapshot = session.currentSnapshot,
moveId = effectId,
attackerId = actorUnitId,
targetId = resolvedTargetId,
accuracy = resolvedAccuracy,
evasion = evasion,
basePower = basePower,
damage = damage,
attributes = targetAwareAttributes,
)
session.currentSnapshot = result.snapshot
if (result.hitSuccessful && result.damage > 0) {
session.currentSnapshot = directDamageApplier.apply(session, actorUnitId, resolvedTargetId, result.damage)
}
session.currentSnapshot = session.resolveFaintAndReplacement()
val finalResult = result.copy(snapshot = session.currentSnapshot)
session.recordMoveExecution(effectId, actorUnitId, resolvedTargetId, finalResult)
aggregatedResult = aggregateResult(aggregatedResult, finalResult, session.currentSnapshot)
if (session.currentSnapshot.battle.lifecycle
.isEnded()
) {
return requireNotNull(aggregatedResult)
}
}
return requireNotNull(aggregatedResult) {
"No targets were resolved for effect '$effectId'."
}
}
/**
* 聚合同一 effect 多目标结算结果。
*
* 当前策略保持与原实现一致:
* - `hitSuccessful / criticalHit` 取任一目标命中即可;
* - 其余数值字段保留最后一个目标的结果;
* - `snapshot` 始终返回最新 session snapshot。
*/
private fun aggregateResult(
current: MoveResolutionResult?,
next: MoveResolutionResult,
latestSnapshot: io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot,
): MoveResolutionResult =
if (current == null) {
next
} else {
current.copy(
snapshot = latestSnapshot,
hitSuccessful = current.hitSuccessful || next.hitSuccessful,
criticalHit = current.criticalHit || next.criticalHit,
accuracy = next.accuracy,
evasion = next.evasion,
basePower = next.basePower,
damageRoll = next.damageRoll,
damage = next.damage,
)
}
}