SnapshotCaptureFormulaInputResolver.kt

package io.github.lishangbu.avalon.game.battle.engine.core.capture

import io.github.lishangbu.avalon.game.battle.engine.core.capture.CaptureContext
import io.github.lishangbu.avalon.game.battle.engine.core.capture.CaptureFormulaInput
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow.BattleRuntimeSnapshot
import io.github.lishangbu.avalon.game.battle.engine.core.session.BattleSessionCaptureAction
import io.github.lishangbu.avalon.game.battle.engine.spi.capture.CaptureFormulaInputResolver

/**
 * 基于当前 battle snapshot 直接组装捕捉公式输入的默认 resolver。
 *
 * 设计意图:
 * - 让 battle-engine 在不额外依赖业务仓储的前提下,直接使用当前会话快照里的结构化单位元数据;
 * - 把 `hp / status / type / metadata.captureRate / metadata.captureContext` 这一组输入统一收口;
 * - 作为引擎内置的正式默认实现,替代测试和装配中到处回退到 `UnsupportedCaptureActionResolver` 的状态。
 *
 * 当前约定:
 * - `ballItemInternalName` 直接沿用 capture action 传入的 `ballItemId`;
 * - `alreadyCaught` 暂时仍由业务层扩展 resolver 覆盖,内置实现默认 `false`;
 * - 若目标单位缺失、或没有 `captureRate`,会立即抛出清晰错误,避免静默使用错误默认值。
 */
class SnapshotCaptureFormulaInputResolver : CaptureFormulaInputResolver {
    override fun resolve(
        sessionId: String,
        snapshot: BattleRuntimeSnapshot,
        action: BattleSessionCaptureAction,
    ): CaptureFormulaInput {
        val targetUnit =
            requireNotNull(snapshot.units[action.targetId]) {
                "Capture target '${action.targetId}' was not found in battle '$sessionId'."
            }
        val captureRate =
            requireNotNull(targetUnit.metadata.captureRate) {
                "Capture target '${action.targetId}' is missing metadata.captureRate in battle '$sessionId'."
            }
        val captureContext = targetUnit.metadata.captureContext
        return CaptureFormulaInput(
            currentHp = targetUnit.currentHp,
            maxHp = targetUnit.maxHp,
            captureRate = captureRate,
            statusState = targetUnit.statusState,
            ballItemInternalName = action.ballItemId,
            turn = snapshot.battle.turn,
            battleContext =
                CaptureContext(
                    alreadyCaught = false,
                    isFishingEncounter = captureContext.isFishingEncounter,
                    isSurfEncounter = captureContext.isSurfEncounter,
                    isNight = captureContext.isNight,
                    isCave = captureContext.isCave,
                    isUltraBeast = captureContext.isUltraBeast,
                    targetLevel = targetUnit.metadata.level,
                    targetWeight = captureContext.targetWeight ?: targetUnit.metadata.weight,
                    targetTypes = targetUnit.typeIds,
                ),
        )
    }
}