BattleMoveFixedDamageRuleResolver.kt
package io.github.lishangbu.avalon.game.battle.engine.core.runtime.flow
import io.github.lishangbu.avalon.game.battle.engine.core.model.UnitState
import kotlin.math.floor
/**
* 固定伤害类招式原生规则解析器。
*
* 设计意图:
* - 把“固定数值 / 按攻击方等级 / 按目标当前生命比例”这类不走 A/D 公式的规则,
* 从主伤害 phase step 中拆成独立组件,避免 `BattleMovePowerDamagePhaseStep` 继续膨胀。
* - 让 move fixture 可以直接声明固定伤害规则,而不是要求调用方始终预先把 `damage` 算好。
*
* 当前支持的静态元数据:
* - `fixedDamage`: 直接固定伤害值,例如 20、40。
* - `fixedDamageMode=attacker_level`: 以攻击方等级作为固定伤害。
* - `fixedDamageMode=target_current_hp_ratio` + `fixedDamageValue`: 以目标当前 HP 比例结算伤害。
*
* 约定:
* - 这里返回的是“固定伤害本体”;属性免疫仍由主伤害 phase 统一裁决。
* - 当前生命比例模式会对仍存活的目标至少造成 1 点伤害,避免出现命中后 0 伤害的空结算。
*/
class BattleMoveFixedDamageRuleResolver {
/**
* 判断当前招式是否声明了固定伤害规则。
*
* 该判定会同时被要害 phase 与伤害 phase 使用,
* 目的是让固定伤害在更早的阶段就跳过要害判定等不应参与的分支。
*/
fun hasFixedDamageRule(moveData: Map<String, Any?>): Boolean = moveData.containsKey(FIXED_DAMAGE_KEY) || moveData.containsKey(FIXED_DAMAGE_MODE_KEY)
/**
* 解析当前招式的固定伤害结果。
*
* @return 若当前招式不是固定伤害类,返回 `null`;否则返回已经原生计算好的固定伤害值。
*/
fun resolve(
moveData: Map<String, Any?>,
attacker: UnitState?,
target: UnitState?,
): Int? {
parseInt(moveData[FIXED_DAMAGE_KEY])
?.let { fixedDamage ->
return fixedDamage.coerceAtLeast(0)
}
return when (moveData[FIXED_DAMAGE_MODE_KEY]?.toString()) {
ATTACKER_LEVEL_MODE -> resolveAttackerLevelDamage(attacker)
TARGET_CURRENT_HP_RATIO_MODE -> resolveTargetCurrentHpRatioDamage(moveData, target)
else -> null
}
}
private fun resolveAttackerLevelDamage(attacker: UnitState?): Int? {
val level = attacker?.metadata?.level ?: DEFAULT_LEVEL
return level.coerceAtLeast(1)
}
private fun resolveTargetCurrentHpRatioDamage(
moveData: Map<String, Any?>,
target: UnitState?,
): Int? {
val ratio = parseDouble(moveData[FIXED_DAMAGE_VALUE_KEY]) ?: return null
val currentHp = target?.currentHp ?: return null
if (currentHp <= 0) {
return 0
}
return floor((currentHp * ratio).coerceAtLeast(1.0)).toInt()
}
private fun parseInt(value: Any?): Int? =
when (value) {
is Int -> value
is Number -> value.toInt()
else -> value?.toString()?.toIntOrNull()
}
private fun parseDouble(value: Any?): Double? =
when (value) {
is Double -> value
is Number -> value.toDouble()
else -> value?.toString()?.toDoubleOrNull()
}
private companion object {
private const val DEFAULT_LEVEL: Int = 50
private const val FIXED_DAMAGE_KEY: String = "fixedDamage"
private const val FIXED_DAMAGE_MODE_KEY: String = "fixedDamageMode"
private const val FIXED_DAMAGE_VALUE_KEY: String = "fixedDamageValue"
private const val ATTACKER_LEVEL_MODE: String = "attacker_level"
private const val TARGET_CURRENT_HP_RATIO_MODE: String = "target_current_hp_ratio"
}
}