BattleSessionRunActionExecutor.kt
package io.github.lishangbu.avalon.game.battle.engine.core.session
import io.github.lishangbu.avalon.game.battle.engine.core.model.BattleLifecycle
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
/**
* session run action 执行器。
*
* 设计意图:
* - 收敛逃跑 action 的成功/失败分支编排;
* - 让逃跑失败计数、battle ended 写回和结构化事件记录
* 不再混在通用 action support 中;
* - 保持 run 尝试细节仍由 `BattleSessionRunAttemptResolver` 负责。
*/
class BattleSessionRunActionExecutor {
/**
* 执行逃跑动作。
*/
fun apply(
session: BattleSession,
action: BattleSessionRunAction,
): BattleRuntimeSnapshot {
val resolution = BattleSessionRunAttemptResolver.resolve(session, action)
if (!resolution.success) {
val failedAttempts = resolution.failedAttempts ?: session.currentSnapshot.battle.failedRunAttempts[action.sideId] ?: 0
session.currentSnapshot =
session.currentSnapshot.copy(
battle =
session.currentSnapshot.battle.copy(
failedRunAttempts = session.currentSnapshot.battle.failedRunAttempts + (action.sideId to failedAttempts),
),
)
session.recordLog(
"Run failed for side ${action.sideId}. " +
"reason=${resolution.reason} runner=${resolution.runnerUnitId} " +
"failedAttempts=$failedAttempts" +
(resolution.escapeValue?.let { " escapeValue=$it" } ?: "") +
(resolution.roll?.let { " roll=$it" } ?: "") +
".",
)
session.recordEvent(
BattleSessionRunFailedPayload(
sideId = action.sideId,
runnerUnitId = resolution.runnerUnitId,
reason = resolution.reason,
failedAttempts = failedAttempts,
escapeValue = resolution.escapeValue,
roll = resolution.roll,
),
)
return session.currentSnapshot
}
val survivingOpponent =
session.currentSnapshot.sides.keys
.firstOrNull { sideId -> sideId != action.sideId }
// 逃跑成功时,胜负已经确定,但战后副作用仍然交给统一 settlement 处理。
session.currentSnapshot =
session.currentSnapshot.copy(
battle =
session.currentSnapshot.battle.copy(
lifecycle = BattleLifecycle.ENDED_UNSETTLED,
winner = survivingOpponent,
endedReason = "run",
),
)
session.recordLog(
"Executed run action for side ${action.sideId}. " +
"runner=${resolution.runnerUnitId} reason=${resolution.reason}. Winner: $survivingOpponent.",
)
session.recordEvent(
BattleSessionBattleEndedPayload(
winner = survivingOpponent,
actionType = BattleSessionActionEventKind.RUN,
runner = action.sideId,
),
)
return session.currentSnapshot
}
}