Skip to content

伤害系统

EmakiAttribute 的伤害计算是阶段式的:一次攻击会经过多个计算阶段(基础伤害 → 暴击 → 防御减免),每个阶段的输出作为下一阶段的输入,最终得到实际伤害值。伤害类型、计算阶段、公式全部通过 YAML 配置,不需要改代码就能调整。

伤害类型定义

伤害类型文件放在 damage_types/ 目录下,每个 YAML 文件定义一个伤害类型。

YAML 格式

yaml
id: physical
display_name: "物理伤害"
aliases:
  - "melee"
  - "physical_damage"
allowed_events:
  - entity_attack
  - entity_sweep_attack
hard_lock: false
stages:
  - id: base_attack
    kind: FLAT_PERCENT
    source: ATTACKER
    mode: ADD
    flat_attributes:
      - physical_attack
    percent_attributes:
      - physical_damage_bonus
  - id: crit
    kind: FLAT_PERCENT
    source: ATTACKER
    mode: ADD
    chance_attributes:
      - physical_crit_rate
    flat_attributes:
      - physical_crit_damage
    multiplier_attributes: []
    min_chance: 0.0
    max_chance: 100.0
  - id: target_defense
    kind: FLAT_PERCENT
    source: TARGET
    mode: SUBTRACT
    flat_attributes:
      - physical_defense
    percent_attributes: []
recovery:
  source: ATTACKER
  resistance_source: TARGET
  flat_attributes:
    - lifesteal
  percent_attributes:
    - percentage_lifesteal
  resistance_attributes:
    - lifesteal_resistance
  min_result: 0.0

字段说明

字段类型必填默认值说明
idString伤害类型唯一标识
display_nameStringid显示名称
aliasesList[]别名列表,在 MythicMobs 等地方可以用别名引用
allowed_eventsList[]允许触发此伤害类型的 Bukkit DamageCause
hard_lockbooleanfalse硬锁定到指定事件。开启后,只有 allowed_events 中的事件才能触发这个伤害类型
stagesList[]计算阶段列表,按顺序执行
recoveryObjectnull吸血/回复配置
descriptionString""描述文本
attacker_messageStringnull攻击者看到的伤害消息(MiniMessage 格式)
target_messageStringnull受击者看到的伤害消息(MiniMessage 格式)

内置伤害类型

插件默认带了三个伤害类型,覆盖了最常见的战斗场景:

ID显示名称主要事件说明
physical物理伤害entity_attackentity_sweep_attack近战物理伤害,玩家挥剑砍怪时触发
projectile射击伤害projectile弓箭、弩等投射物命中时触发
spell法术伤害没有绑定原版事件,通常由 MythicMobs 的 emaki_damage 机制触发

阶段(Stage)定义

每个伤害类型包含一个有序的阶段列表。伤害值从第一个阶段开始,逐步经过每个阶段的计算,最终输出结果。

阶段字段

字段类型默认值说明
idString"stage"阶段标识,某些特殊 ID 有额外行为(见下方)
kindEnumFLAT_PERCENT阶段类型
sourceEnumATTACKER属性从谁身上读:ATTACKER(攻击者)、TARGET(目标)、CONTEXT(上下文)
modeEnumADD运算模式:ADD(加)或 SUBTRACT(减)
flat_attributesList[]固定值属性列表
percent_attributesList[]百分比属性列表
chance_attributesList[]概率属性列表(用于暴击判定等)
multiplier_attributesList[]乘数属性列表
expressionString""自定义表达式(仅 CUSTOM 类型使用)
min_resultDoublenull阶段结果下限
max_resultDoublenull阶段结果上限
min_chanceDoublenull概率下限
max_chanceDoublenull概率上限
min_multiplierDoublenull乘数下限
max_multiplierDoublenull乘数上限

FLAT_PERCENT 类型

最常用的阶段类型,计算逻辑是"固定值 + 百分比加成"。

mode: ADD 时:

阶段结果 = flat + input × (percent / 100)
最终值 = input + 阶段结果

mode: SUBTRACT 时:

阶段结果 = flat + input × (percent / 100)
最终值 = input - 阶段结果

这里 flatflat_attributes 所有属性值的合计,percentpercent_attributes 的合计,input 是上一阶段的输出。

举个例子:攻击者有 100 物理攻击、20% 物理伤害加成,那 base_attack 阶段的结果就是 100 + 0 × (20 / 100) = 100(因为初始 input 是 0),最终值 = 0 + 100 = 100。等等——这里 input 初始值是事件的 baseDamage,通常是原版伤害值或 MythicMobs 传入的基础值。

CUSTOM 类型

如果 FLAT_PERCENT 满足不了你的需求,可以用 CUSTOM 类型写自定义表达式。表达式中可以用这些变量:

变量说明
{input}当前输入值(上一阶段的输出)
{flat}flat_attributes 合计值
{percent}percent_attributes 合计值
{chance}chance_attributes 合计值
{multiplier}multiplier_attributes 合计值
{crit}是否暴击(1 或 0)
{roll}暴击随机数(0–100)
yaml
# 自定义阶段示例
- id: custom_stage
  kind: CUSTOM
  source: ATTACKER
  expression: "{input} * (1 + {percent} / 100) + {flat} * {crit}"

特殊阶段 ID

有两个阶段 ID 会触发额外的内置逻辑:

阶段 ID说明
crit / critical暴击阶段。系统会根据 chance_attributes(暴击率)做随机判定,只有暴击成功时 flat_attributes(暴击伤害)和 multiplier_attributes 才会生效。目标的暴击抵抗属性(如 physical_crit_evasion)会降低暴击概率
defense / target_defense防御阶段。通常配合 source: TARGETmode: SUBTRACT 使用,从目标身上读取防御属性来减免伤害

暴击阶段的工作方式

暴击判定的流程是:先把攻击者的暴击率减去目标的暴击抵抗,得到实际暴击概率,然后掷骰子。暴击成功后,暴击伤害值才会加到当前伤害上。目标的暴伤削减属性(如 physical_crit_multiplier_resistance)会进一步降低暴击伤害的加成幅度。

回复/吸血定义

伤害类型可以配置 recovery 块,让攻击者在造成伤害后回复生命值。

yaml
recovery:
  source: ATTACKER              # 吸血属性从攻击者身上读
  resistance_source: TARGET     # 吸血抵抗从目标身上读
  flat_attributes:              # 固定吸血值
    - lifesteal
  percent_attributes:           # 百分比吸血(按最终伤害的百分比回复)
    - percentage_lifesteal
  resistance_attributes:        # 目标的吸血抵抗
    - lifesteal_resistance
  expression: ""                # 可选自定义表达式
  min_result: 0.0               # 回复下限(不会出现负数回复)
  max_result: null              # 回复上限

吸血计算公式:

基础回复 = flat + finalDamage × (percent / 100)
抵抗系数 = 1 - clamp(resistance / 100, 0, 1)
实际回复 = 基础回复 × 抵抗系数

也就是说,如果攻击者有 10 点固定吸血和 20% 百分比吸血,打出 200 点伤害,基础回复就是 10 + 200 × 0.2 = 50。如果目标有 30% 吸血抵抗,实际回复就是 50 × 0.7 = 35

完整伤害计算流程

从一次攻击事件触发到最终伤害生效,整个流程如下:

┌─────────────────────────────────────────────┐
│  1. 事件触发(EntityDamageByEntityEvent)     │
│     取消原版伤害事件,接管伤害计算             │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  2. 投射物快照                                │
│     弓箭等投射物在发射时会快照攻击者属性       │
│     命中时用发射时的快照,而非当前属性         │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  3. 确定伤害类型                               │
│     根据 allowed_damage_causes 匹配           │
│     匹配不到就用 default_damage_type          │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  4. 闪避判定                                   │
│     根据目标的 dodge_chance 掷骰子             │
│     闪避成功 → 跳过后续所有计算                │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  5. 阶段式计算                                 │
│     按 stages 列表顺序依次执行                 │
│     每个阶段的输出作为下一阶段的输入            │
│     ┌─ base_attack (ADD)                     │
│     ├─ crit (暴击判定 + 暴击伤害)              │
│     └─ target_defense (SUBTRACT)             │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  6. 触发 EmakiAttributeDamageEvent            │
│     其他插件可以监听这个事件,修改最终伤害     │
│     也可以取消这次伤害                         │
└──────────────────┬──────────────────────────┘

┌─────────────────────────────────────────────┐
│  7. 应用伤害                                   │
│     a. 击退 + 受伤音效                        │
│     b. 扣减目标生命值                          │
│     c. 仇恨更新                                │
│     d. 攻击冷却                                │
│     e. 吸血回复                                │
│     f. 伤害消息发送                            │
│     g. 生命值同步到 Bukkit                     │
└─────────────────────────────────────────────┘

投射物快照

投射物(弓箭等)在发射瞬间会快照攻击者当前的属性状态。命中时使用的是发射时的快照,而不是命中时的实时属性。这意味着如果玩家射出箭后脱掉武器,箭的伤害不会受影响——这是有意为之的设计,避免了"射出去的箭因为属性变化而伤害突变"的问题。

DamageContext 变量

DamageContext 是贯穿整个伤害计算流程的上下文对象,包含了这次伤害的所有信息。

字段类型说明
attackerLivingEntity攻击者实体
targetLivingEntity目标实体
projectileProjectile投射物(近战时为 null)
causeDamageCauseBukkit 伤害原因
damageTypeIdString伤害类型 ID
sourceDamagedouble原始伤害值(事件原始值)
baseDamagedouble基础伤害值(进入阶段计算的初始值)
attackerSnapshotAttributeSnapshot攻击者属性快照
targetSnapshotAttributeSnapshot目标属性快照
variablesDamageContextVariables扩展变量表

DamageContextVariables

扩展变量表里存放了一些控制伤害行为的开关和额外信息:

说明
cause / damage_cause伤害原因
allow_critical是否允许暴击
allow_target_dodge是否允许目标闪避
calculate_target_defense是否计算目标防御
trigger_mythic_on_damaged是否触发 MythicMobs 的 onDamaged 事件
mythic_skillMythicMobs 技能名称
mythic_powerMythicMobs 技能威力
damage_type伤害类型覆盖(可以在运行时强制指定伤害类型)

DamageResult

伤害计算完成后会生成 DamageResult 对象,包含 damageTypeIdfinalDamagecritical(是否暴击)、roll(暴击随机数)、stageValues(各阶段的输出值列表)以及完整的 DamageContext 引用。如果你在监听 EmakiAttributeDamageEvent,可以通过 getDamageResult() 拿到这些数据。

Released under the GPL-3.0 License