1.3 武器模组:发起协议伤害¶
武器侧开发者的职责是构造命中的完整上下文,然后调用协议入口。协议层接管后续的穿甲判定、伤害计算、伤害执行和回调触发。本节展示从零开始的最简流程,后续章节将逐一展开每个步骤的细节。
最简示例¶
以下是最简的武器侧代码——当一颗子弹命中某个实体时,构造上下文并发起协议伤害。
// 假设你已经从 raycast 中获取了命中结果
EntityHitResult hitResult = ...; // 射线命中的结果
Vec3 bulletVelocity = ...; // 弹丸速度矢量(m/s)
float bulletPenetration = 120f; // 弹丸理论穿深(mm RHA),已由武器侧自行计算角度修正
DamageSource damageSource = ...; // 原版 DamageSource,指定伤害类型和来源
BFDamageContext ctx = BFDamageContext.builder()
.source(damageSource)
.baseDamage(35f)
.hitVelocity(bulletVelocity)
.hitPoint(hitResult.getLocation())
.hitNormal(new Vec3(hitResult.getDirection().getNormal().getX(),
hitResult.getDirection().getNormal().getY(),
hitResult.getDirection().getNormal().getZ()))
.penetration(bulletPenetration)
.build();
float dealt = BFDamageApi.hurt(hitResult.getEntity(), ctx);
这就是发起一次协议伤害所需的全部代码。BFDamageApi.hurt() 返回实际造成的伤害量——如果目标是普通实体(未实现 BFHurtTarget),协议直接走原版 entity.hurt(source, baseDamage) 并返回结果;如果目标实现了 BFHurtTarget,则走完整的穿甲判定管线。
Builder 的默认值¶
BFDamageContext.builder() 只要求 source 必须设置,其他字段都有安全的默认值。hitVelocity、hitPoint、hitNormal 默认均为 Vec3.ZERO,baseDamage 默认 0,penetration 默认 0,extensions 默认一个空容器。因此即使你只设置了 source 和 baseDamage,也能得到一个合法的上下文——尽管这样的上下文缺少弹道信息,目标实体在判定时只能依赖于离散的 ArmorLevel 等级比较。
注册全局扩展 key¶
如果你的模组需要在命中上下文中携带自定义的结构化数据(例如弹药类型、炸药装药量等),可以使用类型安全的扩展机制。扩展 key 建议在模组初始化时注册,并存为 public static final 常量:
// 在你的 @Mod 类或 config 类中
public static final BFDamageExtensionKey<Float> EXPLOSIVE_MASS =
BFDamageExtensions.register(
ResourceLocation.fromNamespaceAndPath("your_mod_id", "explosive_mass"),
Float.class,
() -> 0f // 默认值工厂——未设置时返回此值
);
构造上下文时,通过 set() 写入扩展值:
护甲侧在管线方法内通过 ctx.extensions().get(YourMod.EXPLOSIVE_MASS) 即可读取——如果武器侧未设置,将退回注册时指定的默认值。协议自身已预定义了三个标准 key:BFDamageExtensions.FUSE_DELAY(引信延迟)、BFDamageExtensions.CALIBER(口径)和 BFDamageExtensions.MASS(弹体质量),可以直接使用而不必额外注册。
添加回调处理¶
武器侧往往需要在伤害完成后执行后续效果——击穿时播放火花粒子、未击穿时播放跳弹音效、产生破片时生成二次弹丸。通过实现 BFDamageHandler 接口并注入上下文,可以在管线的末尾接收到这些事件:
BFDamageContext ctx = BFDamageContext.builder()
.source(damageSource)
.baseDamage(35f)
.hitVelocity(bulletVelocity)
.hitPoint(hitResult.getLocation())
.hitNormal(hitNormal)
.penetration(bulletPenetration)
.handler(myHandler) // 注入 handler
.build();
float dealt = BFDamageApi.hurt(hitResult.getEntity(), ctx);
BFDamageHandler 提供了两个层面的钩子。事件回调层面,有 onPenetrated、onBlocked、onRicochet、onOvermatch、onSpall 五个方法,均为 default 空实现,按需覆写即可。前三个主结果回调由穿甲结果决定;onOvermatch 和 onSpall 则只由 isOvermatch / isSpall 的返回值决定。默认判定逻辑中,超匹配定义为"穿深 > 装甲厚度 × 1.5",破片定义为"未击穿"或"击穿但非超匹配"。
此外,BFDamageHandler 还提供了便捷方法 dealDamage(target, ctx),它会自动将自身注入上下文后再调用 BFDamageApi.hurt(),使调用方不必手动调用 withHandler:
// 等价于 BFDamageApi.hurt(target, ctx.withHandler(this))
float dealt = myHandler.dealDamage(target, ctx);
角度修正由武器侧负责¶
一个重要的约定是:穿深的角度效应应由武器侧在构造上下文前自行计算。协议不默认进行斜穿修正——modifyPenetration 方法的默认实现直接返回原始穿深,它存在的意义是供护甲侧实现爆反拦截、间隙衰减等额外的减效逻辑。
因此在调用协议之前,武器侧应该根据入射角对理论穿深做修正。例如,如果弹丸速度矢量与命中面法线反方向之间的夹角为 θ,则等效穿深近似为 penetration / cosθ。将修正后的值填入 penetration 字段即可。
下一步¶
你已经掌握了武器侧发起协议伤害的基本流程。下一节将从护甲侧的角度,展示如何让一个实体响应协议伤害。如果你希望深入了解上下文的构造细节——每个字段的含义、命中几何信息的计算方式、扩展注册的生命周期——可以跳转到第二章。