BattleMoveAccuracyEvasionPhaseStep.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleAttributeKeys
import io.github.lishangbu.avalon.game.battle.engine.core.constant.BattleStatIds
import io.github.lishangbu.avalon.game.battle.engine.core.event.StandardHookNames
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleFieldConditionSupport
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleMoveDataReader
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleRelayReader
import io.github.lishangbu.avalon.game.battle.engine.core.runtime.support.BattleStatStageSupport
/**
* 命中与回避修正 phase step。
*
* @property phaseProcessor battle hook phase 处理器。
*/
class BattleMoveAccuracyEvasionPhaseStep(
private val phaseProcessor: BattleFlowPhaseProcessor,
) : BattleMoveResolutionStep {
/**
* 当前步骤在 pipeline 中的执行顺序。
*/
override val order: Int = 100
/**
* 执行命中与回避修正阶段。
*/
override fun execute(context: BattleMoveResolutionContext) {
if (context.forcedHitSuccessful != null || context.skipAccuracyEvasionModifiers) {
return
}
val attacker = context.snapshot.units[context.attackerId]
val target = context.snapshot.units[context.targetId]
val attackerSide = sideOfUnit(context.snapshot, context.attackerId)
val accuracyPhaseAttributes =
context.attributes +
mapOf(
BattleAttributeKeys.MOVE_TYPE to BattleMoveDataReader.readType(context.moveEffect.data),
BattleAttributeKeys.DAMAGE_CLASS to BattleMoveDataReader.readDamageClass(context.moveEffect.data),
)
val attackerAccuracyStage = BattleStatStageSupport.readStage(attacker?.boosts.orEmpty(), BattleStatIds.ACCURACY)
val targetEvasionStage =
if (attacker?.abilityId == KEEN_EYE_ABILITY_ID) {
0
} else {
BattleStatStageSupport.readStage(target?.boosts.orEmpty(), BattleStatIds.EVASION)
}
val hasVictoryStarAura =
attackerSide
?.activeUnitIds
.orEmpty()
.mapNotNull { unitId -> context.snapshot.units[unitId]?.abilityId }
.any { abilityId -> abilityId == VICTORY_STAR_ABILITY_ID }
context.accuracy =
context.accuracy
?.let { accuracy ->
var adjustedAccuracy = accuracy * BattleStatStageSupport.accuracyEvasionMultiplier(attackerAccuracyStage)
if (hasVictoryStarAura) {
adjustedAccuracy *= VICTORY_STAR_MULTIPLIER
}
if (BattleFieldConditionSupport.hasGravity(context.snapshot)) {
adjustedAccuracy *= GRAVITY_ACCURACY_MULTIPLIER
}
adjustedAccuracy.toInt()
}
context.evasion =
context.evasion
?.let { evasion ->
(evasion * BattleStatStageSupport.accuracyEvasionMultiplier(targetEvasionStage)).toInt()
}
val accuracyResult =
phaseProcessor.processPhase(
snapshot = context.snapshot,
hookName = StandardHookNames.ON_MODIFY_ACCURACY.value,
moveEffect = context.moveEffect,
selfId = context.attackerId,
targetId = context.targetId,
sourceId = context.sourceId,
relay = context.accuracy?.toDouble(),
attributes = accuracyPhaseAttributes,
)
context.snapshot = accuracyResult.snapshot
context.accuracy = BattleRelayReader.readInt(accuracyResult.relay) ?: context.accuracy
val evasionResult =
phaseProcessor.processPhase(
snapshot = context.snapshot,
hookName = StandardHookNames.ON_MODIFY_EVASION.value,
moveEffect = context.moveEffect,
selfId = context.targetId,
targetId = context.targetId,
sourceId = context.sourceId,
relay = context.evasion?.toDouble(),
attributes = accuracyPhaseAttributes,
)
context.snapshot = evasionResult.snapshot
context.evasion = BattleRelayReader.readInt(evasionResult.relay) ?: context.evasion
}
/**
* 根据单位标识反查其所属 side。
*
* 命中阶段只需要读当前 active 队友能力,因此在本步骤内保留一份轻量查询即可,
* 避免把整套 session 依赖引进到 flow 层。
*/
private fun sideOfUnit(
snapshot: BattleRuntimeSnapshot,
unitId: String,
) = snapshot.sides.values.firstOrNull { side -> unitId in side.activeUnitIds || unitId in side.unitIds }
private companion object {
private const val KEEN_EYE_ABILITY_ID: String = "keen-eye"
private const val VICTORY_STAR_ABILITY_ID: String = "victory-star"
private const val VICTORY_STAR_MULTIPLIER: Double = 1.1
private const val GRAVITY_ACCURACY_MULTIPLIER: Double = 5.0 / 3.0
}
}