DefaultBattleCaptureSettlementService.kt
package io.github.lishangbu.avalon.game.capture
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionCaptureResourceUsage
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionQuery
import org.springframework.stereotype.Service
/**
* 默认捕捉账本结算实现。
*
* 这里统一收口 battle engine 与玩家域的衔接:
* - engine 只负责记录“这回合发生了哪些捕捉尝试”;
* - player 域负责把这些尝试转成库存变更与已拥有精灵落库。
*/
@Service
open class DefaultBattleCaptureSettlementService(
private val capturePreparationService: DefaultCapturePreparationService,
private val playerInventoryService: DefaultPlayerInventoryService,
private val ownedCreatureService: DefaultOwnedCreatureService,
) : BattleCaptureSettlementService {
override fun settle(
sessionId: String,
session: BattleSessionQuery,
): CaptureBattleResult? {
val captureUsages = session.captureResourceLedger
if (captureUsages.isEmpty()) {
return null
}
var captureSummary: CapturedCreatureSummary? = null
val successfulUsage = resolveSuccessfulUsage(session)
captureUsages.forEach { usage ->
// 失败尝试只需要扣库存,不必重复装配完整的目标精灵上下文。
val inventoryContext =
capturePreparationService.resolveInventoryContext(
playerId = usage.playerId,
ballItemId = usage.ballItemId,
)
// 结算阶段再次校验库存,避免预校验与真实落库之间被并发消耗破坏一致性。
playerInventoryService.ensureAvailable(inventoryContext.playerId, inventoryContext.ballItemId, usage.quantity)
playerInventoryService.consume(inventoryContext.playerId, inventoryContext.ballItemId, usage.quantity)
if (usage == successfulUsage) {
// 只有最终成功那次捕捉,才需要装配完整上下文去落库存档。
captureSummary = ownedCreatureService.capture(prepareCaptureContext(sessionId, session, usage, inventoryContext))
}
}
return successfulUsage?.let { usage ->
CaptureBattleResult(
success = true,
sessionId = sessionId,
targetUnitId = usage.targetUnitId,
ballItemId = usage.ballItemId,
shakes = usage.shakes,
reason = usage.reason,
battleEnded = true,
finalRate = usage.finalRate,
session = session,
capturedCreature = captureSummary,
)
}
}
private fun resolveSuccessfulUsage(session: BattleSessionQuery): BattleSessionCaptureResourceUsage? =
session.captureResourceLedger.lastOrNull { usage ->
usage.success && usage.targetUnitId == session.snapshot.battle.capturedUnitId
}
private fun prepareCaptureContext(
sessionId: String,
session: BattleSessionQuery,
usage: BattleSessionCaptureResourceUsage,
inventoryContext: CaptureInventoryContext,
): PreparedCaptureContext =
capturePreparationService.prepare(
sessionId = sessionId,
snapshot = session.snapshot,
command =
CaptureCommand(
playerId = usage.playerId,
ballItemId = usage.ballItemId,
targetUnitId = usage.targetUnitId,
sourceUnitId = usage.sourceUnitId,
),
inventoryContext = inventoryContext,
)
}