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,
        )
}