1.1 认识 BallisticsFramework¶
BallisticsFramework 是一个 NeoForge 1.21.1 的 lib 库模组——它不直接在游戏中添加物品、方块或生物,而是为其他模组提供一套标准化的终点弹道伤害协议层。枪械模组、载具模组和护甲模组可以通过共同依赖此 lib 来互通穿深、入射角、装甲等效厚度等高维弹道信息,而不必各自发明私有的 ad-hoc 方案。
协议解决了什么问题¶
Minecraft 原版的伤害系统围绕 DamageSource 设计。DamageSource 的本质是一个不可变的"伤害类型标签",用来描述伤害的种类和来源实体。对于普通的生物攻击、摔落、火焰、爆炸,这套机制完全够用——只要知道"谁打了谁、怎么打的",原版的护甲和附魔就能完成剩余的工作。
但弹道伤害是完全不同的场景。当一枚穿甲弹以某个角度命中一辆坦克的侧面装甲时,需要传递的信息远不止"谁打了谁":弹丸的穿深是多少毫米、命中角度是多少、打在哪个面上、装甲的等效厚度是多少——这些都是一次命中过程中临时产生的高维上下文。它们不适合塞入 DamageSource(那是类型描述,不是数据容器),也不适合存储在实体的 Capability 中(它们只在该次伤害的调用链内有意义)。
BallisticsFramework 协议层的核心定位就是在伤害调用链内携带这些上下文,并提供一套标准的协商接口让武器侧和护甲侧一致地处理弹道伤害。它拆开了"发起伤害"和"响应伤害"两个角色——武器模组负责构造命中的完整上下文(BFDamageContext),护甲模组负责基于上下文做出穿甲判定和伤害计算(通过实现 BFHurtTarget 接口)。
设计原则¶
协议的设计有一条根本原则:不替代原版,包裹原版。协议绝不绕开 Entity#hurt、绝不直接 setHealth、绝不重新实现完整的战斗管线。如果一个实体没有声明协议感知(即没有实现 BFHurtTarget),那么所有伤害完全走原版流程,协议层透明不参与。
正是这条原则保证了协议的兼容性——依赖本 lib 的武器模组和护甲模组不会有任何冲突,因为它们通过同一个协议协商伤害,而未参与协议的 mod 完全不受到影响。
为了实现这一点,协议做了三件事。首先,在武器侧提供 BFDamageApi.hurt(target, context) 作为唯一入口,将命中的所有高维信息打包在一个不可变的上下文对象中;其次,在护甲侧定义 BFHurtTarget 接口,让实体声明"我自行处理穿甲判定和伤害后果";最后,通过 Mixin 注入 Entity#hurt 和 LivingEntity#hurt,使得原版生物攻击、TNT 爆炸等非协议来源的伤害在命中一个协议感知目标时,也能自动转换为低信息量的协议上下文并走完整穿甲管线。
两种角色:武器侧与护甲侧¶
BallisticsFramework 的使用者天然分为两类,它们关心的问题完全不同。
武器侧开发者——通常是枪械模组或载具模组的作者——需要知道如何构造一个 BFDamageContext,在其中填写穿深、命中速度、命中点、命中面法线等信息,然后调用协议入口发起伤害。此外,武器侧还可以通过注入 BFDamageHandler 来接收击穿、未击穿、跳弹等回调事件,从而触发命中音效、弹坑粒子等后续效果。
护甲侧开发者——通常是护甲模组或实体模组的作者——需要让自己的实体实现 BFHurtTarget 接口。简单模式下,只需要提供 getArmorLevel() 返回一个离散护甲等级,协议就会自动完成穿甲判定和伤害计算。精密模式下,可以覆写全部管线方法,精细控制 RHA 数值、斜穿修正、跳弹判定和最终伤害公式。
这两种角色的工作通过协议完全解耦。武器模组不需要知道目标穿了什么装甲、装甲是用什么机制计算的;护甲模组也不需要知道是谁开的枪、子弹是从什么武器射出的。它们只需要各自对齐协议的 API 约定,就能在运行时通过 BFDamageContext 完成一次完整的弹道伤害协商。
协议的核心流程¶
当一次协议伤害被发起时,幕后的处理流程是一个固定的五步管线。了解这个流程有助于理解各个 API 类的职责分工。
第一步,获取目标的 RHA 等效厚度:协议调用 target.getRHA(ctx),获取命中部位的装甲厚度。默认实现取 getArmorLevel() 的中位值,精密模组可以覆写为精确的 float 值。
第二步,修正穿深:协议调用 target.modifyPenetration(ctx)。默认实现直接返回上下文中的原始穿深;护甲模组可以覆写此方法来模拟爆反拦截、间隙衰减等减效逻辑。需要注意,基本的角度效应(斜穿导致等效厚度增加)应该由武器侧在构造上下文前自行计算,协议不默认修正。
第三步,穿甲判定:协议调用 target.resolvePenetration(ctx),返回 PENETRATED(击穿)、BLOCKED(未击穿)或 RICOCHET(跳弹)三选一。默认实现基于离散的 ArmorLevel 等级比较(等于算击穿),精密模组可以覆写为基于 modifyPenetration > getRHA 的精确 float 判定
第四步,计算最终伤害:协议调用 target.calculateFinalDamage(ctx, result)。默认实现是三级模型——刚好击穿(同等级)为标称伤害的 65%,越级击穿为 100%,未击穿或跳弹为 0。护甲侧可以覆写此方法来实现非零的未击穿钝伤、跳弹贯穿或超匹配加成。
第五步,执行伤害:协议调用 target.hurt(source, finalDmg)。对于 LivingEntity 子类,通常委托给 super.hurt(source, finalDmg) 走原版管线。
在这五个步骤全部完成后,如果武器侧注入了 BFDamageHandler,协议还会触发相应的回调——onPenetrated、onBlocked、onRicochet,以及自动判定的超匹配(onOvermatch)和破片(onSpall)。
装甲等级体系¶
协议内置了一套从"无防护"到"现代主战坦克正面"的 13 级离散装甲等级(ArmorLevel,含元等级 UNPENETRABLE),参考了 Helldivers 2 的离散护甲理念。每个等级对应一个 RHA 厚度区间,大约以 ×2 的步幅非线性增长。
对于简单的护甲模组而言,这套等级体系足以覆盖大多数场景——装甲目标只需要返回一个枚举值,协议就自动完成等级比较和伤害计算。如果只需要"这把枪能打穿三级甲吗"这样的离散判定,甚至根本不需要关心 RHA 数值。
对于需要精确判定的模组,协议同样支持完全绕过等级体系,直接使用 float 精度的 RHA 值进行比较。这种情况下,穿深的角度效应等内容由武器侧在构造上下文前自行计算,再将修正好穿深写入 penetration 字段。护甲侧如需额外的减效逻辑(爆反、间隙衰减等),可在 modifyPenetration 中覆写。
软件包结构¶
协议的所有公开 API 集中在 io.github.sweetzonzi.ballistics_framework.api 包中。这是依赖本 lib 的模组唯一需要 import 的包,包中所有类和接口均保证版本兼容性。
api 包同级存在一个 internal 包(TBContextStack、TBExtensionKeyRegistry),它负责 ThreadLocal 栈管理和扩展 key 注册等底层工作,但不构成公开 API,不保证跨版本兼容性。此外还有 mixin 包用于注入原版伤害入口。开发者在正常使用协议时不需要接触这两部分。
核心的 API 类包括:BFDamageApi(唯一对外入口)、BFDamageContext(命中上下文 record)、BFDamageContextBuilder(Builder 构造器)、BFHurtTarget(协议伤害目标接口)、BFDamageHandler(伤害发起方回调接口)、ArmorLevel(离散装甲等级枚举)、PenetrationResult(穿甲结果枚举)、BFDamageExtensions(类型安全扩展容器)和 BFDamageExtensionKey(扩展键)。