DefaultCapturePreparationService.kt
package io.github.lishangbu.avalon.game.capture
import io.github.lishangbu.avalon.dataset.api.model.SpeciesInfo
import io.github.lishangbu.avalon.dataset.api.service.ItemReader
import io.github.lishangbu.avalon.dataset.api.service.SpeciesReader
import io.github.lishangbu.avalon.game.battle.engine.core.capture.CaptureContext
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.repository.OwnedCreatureRepository
import io.github.lishangbu.avalon.game.repository.PlayerRepository
import org.springframework.stereotype.Service
@Service
open class DefaultCapturePreparationService(
private val itemReader: ItemReader,
private val speciesReader: SpeciesReader,
private val playerRepository: PlayerRepository,
private val ownedCreatureRepository: OwnedCreatureRepository,
) {
/**
* 解析捕捉扣库存所需的最小上下文。
*
* 这里不读取目标精灵或物种信息,只负责把外部字符串标识收敛成真实主键。
*/
open fun resolveInventoryContext(
playerId: String,
ballItemId: String,
): CaptureInventoryContext {
val resolvedPlayerId = requirePlayer(playerId)
val item = loadItem(ballItemId)
return CaptureInventoryContext(
playerId = resolvedPlayerId,
ballItemId = item.id,
ballItemInternalName = item.internalName,
)
}
open fun prepare(
sessionId: String,
snapshot: BattleRuntimeSnapshot,
command: CaptureCommand,
inventoryContext: CaptureInventoryContext = resolveInventoryContext(command.playerId, command.ballItemId),
): PreparedCaptureContext {
val targetUnit =
requireNotNull(snapshot.units[command.targetUnitId]) {
"Target unit '${command.targetUnitId}' was not found."
}
val sourceUnit =
command.sourceUnitId?.let { sourceUnitId ->
requireNotNull(snapshot.units[sourceUnitId]) {
"Source unit '$sourceUnitId' was not found."
}
}
val targetMetadata = readBattleUnitMetadata(targetUnit)
val species = loadSpecies(targetMetadata.creatureSpeciesId)
val captureRate = species.captureRate ?: targetMetadata.captureRate ?: 0
val alreadyCaught =
ownedCreatureRepository
.findAll()
.any { ownedCreature ->
ownedCreature.playerId == inventoryContext.playerId &&
ownedCreature.creatureSpeciesId == targetMetadata.creatureSpeciesId
}
return PreparedCaptureContext(
sessionId = sessionId,
playerId = inventoryContext.playerId,
ballItemId = inventoryContext.ballItemId,
ballItemInternalName = inventoryContext.ballItemInternalName,
targetUnitId = command.targetUnitId,
sourceUnitId = command.sourceUnitId,
snapshot = snapshot,
targetUnit = targetUnit,
sourceUnit = sourceUnit,
targetMetadata = targetMetadata.copy(captureRate = captureRate),
battleContext =
CaptureContext(
alreadyCaught = alreadyCaught,
isFishingEncounter = targetUnit.metadata.captureContext.isFishingEncounter,
isSurfEncounter = targetUnit.metadata.captureContext.isSurfEncounter,
isNight = targetUnit.metadata.captureContext.isNight,
isCave = targetUnit.metadata.captureContext.isCave,
isUltraBeast = targetUnit.metadata.captureContext.isUltraBeast,
targetLevel = targetMetadata.level,
targetWeight = targetUnit.metadata.captureContext.targetWeight ?: targetUnit.metadata.weight,
targetTypes = targetUnit.typeIds,
),
)
}
private fun loadItem(internalName: String) =
itemReader.findItemByInternalName(internalName)
?: error("Capture ball '$internalName' was not found.")
private fun requirePlayer(playerId: String): Long {
val resolvedPlayerId = playerId.toLongOrNull() ?: error("playerId must be a valid long value.")
requireNotNull(playerRepository.findNullable(resolvedPlayerId)) {
"Player '$resolvedPlayerId' was not found."
}
return resolvedPlayerId
}
private fun loadSpecies(speciesId: Long): SpeciesInfo =
requireNotNull(speciesReader.findSpeciesById(speciesId)) {
"Creature species '$speciesId' was not found."
}
private fun readBattleUnitMetadata(unit: UnitState): BattleUnitMetadata =
BattleUnitMetadata(
creatureId = requireNotNull(unit.metadata.creatureId) { "Unit '${unit.id}' is missing required metadata 'creatureId'." },
creatureSpeciesId = requireNotNull(unit.metadata.creatureSpeciesId) { "Unit '${unit.id}' is missing required metadata 'creatureSpeciesId'." },
creatureInternalName = requireNotNull(unit.metadata.creatureInternalName) { "Unit '${unit.id}' is missing required metadata 'creatureInternalName'." },
creatureName = requireNotNull(unit.metadata.creatureName) { "Unit '${unit.id}' is missing required metadata 'creatureName'." },
level = requireNotNull(unit.metadata.level) { "Unit '${unit.id}' is missing required metadata 'level'." },
requiredExperience = unit.metadata.requiredExperience ?: 0,
natureId = unit.metadata.natureId,
captureRate = unit.metadata.captureRate,
ivs = unit.metadata.ivs,
evs = unit.metadata.evs,
calculatedStats = unit.stats,
)
}