GameBattleViewAssembler.kt
package io.github.lishangbu.avalon.game.battle.api
import io.github.lishangbu.avalon.game.battle.engine.core.model.AttachedEffectState
import io.github.lishangbu.avalon.game.battle.engine.core.model.BattleState
import io.github.lishangbu.avalon.game.battle.engine.core.model.FieldState
import io.github.lishangbu.avalon.game.battle.engine.core.model.SideState
import io.github.lishangbu.avalon.game.battle.engine.core.model.UnitState
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.MoveResolutionResult
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionActionExecutionResult
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionCaptureAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionCaptureResourceUsage
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionCaptureResult
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionChoiceStatus
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionEffectAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionEvent
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionQuery
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionReplacementRequest
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionResourceUsage
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionSideAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionSubmittingAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionSwitchingAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionTargetedAction
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionTurnResult
import io.github.lishangbu.avalon.game.battle.engine.core.session.target.BattleSessionTargetQuery
import io.github.lishangbu.avalon.game.battle.settlement.BattleSettlementResult
import io.github.lishangbu.avalon.game.capture.CaptureBattleResult
import io.github.lishangbu.avalon.game.capture.CapturedCreatureSummary
import org.springframework.stereotype.Component
/**
* battle 接口层视图组装器。
*
* 设计意图:
* - 把 engine DTO 映射为面向前端的稳定 API view。
* - 让 controller 与 battle 应用层之间保持清晰的接口边界,不直接暴露 engine 层结构。
*/
@Component
class GameBattleViewAssembler {
/**
* 把 battle session 查询结果映射为前端视图。
*/
fun toSessionView(query: BattleSessionQuery): GameBattleSessionView =
GameBattleSessionView(
snapshot = toSnapshotView(query.snapshot),
turnStatus = query.turnStatus,
pendingActions = query.pendingActions.map(::toActionView),
choiceStatuses = query.choiceStatuses.map(::toChoiceStatusView),
replacementRequests = query.replacementRequests.map(::toReplacementRequestView),
resourceLedger = query.resourceLedger.map(::toResourceUsageView),
captureResourceLedger = query.captureResourceLedger.map(::toCaptureResourceUsageView),
battleLogs = query.battleLogs,
events = query.eventLogs.map(::toEventView),
)
/**
* 把目标查询结果映射为前端视图。
*/
fun toTargetQueryView(query: BattleSessionTargetQuery): GameBattleTargetQueryView =
GameBattleTargetQueryView(
effectId = query.effectId,
actorUnitId = query.actorUnitId,
mode = query.mode,
availableTargetUnitIds = query.availableTargetUnitIds,
requiresExplicitTarget = query.requiresExplicitTarget,
)
/**
* 把整回合推进结果映射为前端视图。
*/
fun toTurnResultView(result: BattleSessionTurnResult): GameBattleTurnResultView =
GameBattleTurnResultView(
actionResults = result.actionResults.map(::toActionExecutionResultView),
snapshot = toSnapshotView(result.snapshot),
)
/**
* 把内部结算结果映射为前端视图。
*/
fun toSettlementView(result: BattleSettlementResult): GameBattleSettlementView =
GameBattleSettlementView(
sessionId = result.sessionId,
endedReason = result.endedReason,
settled = result.settled,
session = toSessionView(result.session),
captureResult = result.captureResult?.let(::toCaptureResultView),
)
/**
* 把内部捕捉结算结果映射为前端视图。
*/
fun toCaptureResultView(result: CaptureBattleResult): GameBattleCaptureResultView =
GameBattleCaptureResultView(
success = result.success,
sessionId = result.sessionId,
targetUnitId = result.targetUnitId,
ballItemId = result.ballItemId,
shakes = result.shakes,
reason = result.reason,
battleEnded = result.battleEnded,
finalRate = result.finalRate,
capturedCreature = result.capturedCreature?.let(::toCapturedCreatureView),
)
/**
* 把快照映射为前端视图。
*/
private fun toSnapshotView(snapshot: BattleRuntimeSnapshot): GameBattleSnapshotView =
GameBattleSnapshotView(
battle = toBattleStateView(snapshot.battle),
field = toFieldView(snapshot.field),
units =
snapshot.units.values
.sortedBy(UnitState::id)
.map(::toUnitView),
sides =
snapshot.sides.values
.sortedBy(SideState::id)
.map(::toSideView),
)
/**
* 把 battle state 映射为前端视图。
*/
private fun toBattleStateView(state: BattleState): GameBattleStateView =
GameBattleStateView(
id = state.id,
formatId = state.formatId,
battleKind = state.battleKind,
lifecycle = state.lifecycle,
turn = state.turn,
winner = state.winner,
endedReason = state.endedReason,
capturableSideId = state.capturableSideId,
capturedUnitId = state.capturedUnitId,
)
/**
* 把 field state 映射为前端视图。
*/
private fun toFieldView(field: FieldState): GameBattleFieldView =
GameBattleFieldView(
weatherState = field.weatherState,
terrainState = field.terrainState,
)
/**
* 把 unit state 映射为前端视图。
*/
private fun toUnitView(unit: UnitState): GameBattleUnitView =
GameBattleUnitView(
id = unit.id,
currentHp = unit.currentHp,
maxHp = unit.maxHp,
metadata = unit.metadata,
statusState = unit.statusState,
abilityId = unit.abilityId,
itemId = unit.itemId,
typeIds = unit.typeIds.toList().sorted(),
volatileStates =
unit.volatileStates.values
.sortedBy(AttachedEffectState::effectOrder)
.associateBy(AttachedEffectState::effectId),
conditionStates =
unit.conditionStates.values
.sortedBy(AttachedEffectState::effectOrder)
.associateBy(AttachedEffectState::effectId),
boosts = unit.boosts,
stats = unit.stats,
movePp = unit.movePp,
forceSwitchRequested = unit.forceSwitchRequested,
)
/**
* 把 side state 映射为前端视图。
*/
private fun toSideView(side: SideState): GameBattleSideView =
GameBattleSideView(
id = side.id,
unitIds = side.unitIds,
activeUnitIds = side.activeUnitIds,
conditionStates =
side.conditionStates.values
.sortedBy(AttachedEffectState::effectOrder)
.associateBy(AttachedEffectState::effectId),
)
/**
* 把当前待执行动作映射为前端视图。
*/
private fun toActionView(action: BattleSessionAction): GameBattleActionView =
GameBattleActionView(
kind = action.kind,
priority = action.priority,
speed = action.speed,
submittingUnitId = (action as? BattleSessionSubmittingAction)?.submittingUnitId,
sideId = (action as? BattleSessionSideAction)?.sideId,
effectId = (action as? BattleSessionEffectAction)?.effectId,
targetUnitId = (action as? BattleSessionTargetedAction)?.targetUnitId,
playerId = (action as? BattleSessionCaptureAction)?.playerId,
ballItemId = (action as? BattleSessionCaptureAction)?.ballItemId,
outgoingUnitId = (action as? BattleSessionSwitchingAction)?.outgoingUnitId,
incomingUnitId = (action as? BattleSessionSwitchingAction)?.incomingUnitId,
)
/**
* 把单个动作执行结果映射为前端视图。
*/
private fun toActionExecutionResultView(result: BattleSessionActionExecutionResult): GameBattleActionExecutionResultView =
GameBattleActionExecutionResultView(
action = toActionView(result.action),
snapshot = toSnapshotView(result.snapshot),
moveResult = result.moveResult?.let(::toMoveResolutionView),
captureResult = result.captureResult?.let(::toCaptureExecutionView),
skippedReason = result.skippedReason,
)
/**
* 把 move 结算结果映射为前端视图。
*/
private fun toMoveResolutionView(result: MoveResolutionResult): GameBattleMoveResolutionView =
GameBattleMoveResolutionView(
cancelled = result.cancelled,
hitSuccessful = result.hitSuccessful,
criticalHit = result.criticalHit,
accuracy = result.accuracy,
evasion = result.evasion,
basePower = result.basePower,
damageRoll = result.damageRoll,
damage = result.damage,
)
/**
* 把 capture action 执行结果映射为前端视图。
*/
private fun toCaptureExecutionView(result: BattleSessionCaptureResult): GameBattleCaptureExecutionView =
GameBattleCaptureExecutionView(
success = result.success,
playerId = result.playerId,
ballItemId = result.ballItemId,
sourceUnitId = result.sourceUnitId,
targetId = result.targetId,
shakes = result.shakes,
reason = result.reason,
finalRate = result.finalRate,
)
/**
* 把 choice status 映射为前端视图。
*/
private fun toChoiceStatusView(status: BattleSessionChoiceStatus): GameBattleChoiceStatusView =
GameBattleChoiceStatusView(
sideId = status.sideId,
activeUnitIds = status.activeUnitIds,
submittedUnitIds = status.submittedUnitIds,
missingUnitIds = status.missingUnitIds,
requiredActionCount = status.requiredActionCount,
submittedActionCount = status.submittedActionCount,
ready = status.ready,
)
/**
* 把替补请求映射为前端视图。
*/
private fun toReplacementRequestView(request: BattleSessionReplacementRequest): GameBattleReplacementRequestView =
GameBattleReplacementRequestView(
sideId = request.sideId,
outgoingUnitIds = request.outgoingUnitIds,
candidateUnitIds = request.candidateUnitIds,
)
/**
* 把 battle event 映射为前端视图。
*/
private fun toEventView(event: BattleSessionEvent): GameBattleEventView =
GameBattleEventView(
type = event.type,
turn = event.turn,
payload = event.payload,
)
/**
* 把资源账本条目映射为前端视图。
*/
private fun toResourceUsageView(usage: BattleSessionResourceUsage): GameBattleResourceUsageView =
GameBattleResourceUsageView(
kind = usage.kind,
payload = usage,
)
/**
* 把捕捉账本条目映射为前端视图。
*/
private fun toCaptureResourceUsageView(usage: BattleSessionCaptureResourceUsage): GameBattleCaptureResourceUsageView =
GameBattleCaptureResourceUsageView(
playerId = usage.playerId,
sourceUnitId = usage.sourceUnitId,
ballItemId = usage.ballItemId,
targetUnitId = usage.targetUnitId,
quantity = usage.quantity,
success = usage.success,
shakes = usage.shakes,
reason = usage.reason,
finalRate = usage.finalRate,
)
/**
* 把已捕捉生物摘要映射为前端视图。
*/
private fun toCapturedCreatureView(summary: CapturedCreatureSummary): GameBattleCapturedCreatureView =
GameBattleCapturedCreatureView(
ownedCreatureId = summary.ownedCreatureId,
creatureId = summary.creatureId,
creatureSpeciesId = summary.creatureSpeciesId,
creatureInternalName = summary.creatureInternalName,
creatureName = summary.creatureName,
)
}