3.2 装甲等级体系¶
ArmorLevel 是协议内置的离散装甲等级枚举,将连续的 RHA 等效厚度映射为 13 个语义化的等级(12 级实体等级 + 1 级元等级)。它参考了 Helldivers 2 的离散护甲理念——等级按防护强度升序排列,大约以 ×2 的步幅非线性增长,覆盖从血肉到现代主战坦克正面防护的完整范围。
十三级一览¶
下表列出了全部 13 个等级及其 RHA 区间和现实参考对象。区间是左开右闭——不包含下限,包含上限。第一个等级的区间是 (0, 1]。UNPENETRABLE 为元等级,无上限,只能显式引用获取。
| 等级 | RHA 区间 (mm) | 参考对象 |
|---|---|---|
UNARMORED_1 |
(0, 1] | 血肉、无保护裸露表面 |
UNARMORED_2 |
(1, 3] | 几丁质甲壳、木板、薄塑料壳 |
LIGHT_1 |
(3, 5] | 轻型防弹衣、铁皮、铝合金薄板 |
LIGHT_2 |
(5, 10] | 重型防弹衣、装甲车门 |
MEDIUM |
(10, 20] | 一般车辆车架、步战车侧后方 |
HEAVY |
(20, 40] | 步战车正面、坦克侧后/顶部 |
SUPER_HEAVY_1 |
(40, 80] | 二战早期中型坦克正面(T-34、谢尔曼) |
SUPER_HEAVY_2 |
(80, 150] | 二战晚期重型坦克正面(虎王、IS-2) |
SUPER_HEAVY_3 |
(150, 300] | 冷战早期主战坦克(T-55、M48) |
SUPER_HEAVY_4 |
(300, 600] | 冷战中期+爆反(T-72、M60A3 ERA) |
SUPER_HEAVY_5 |
(600, 1200] | 冷战晚期现代 MBT(豹2A4、M1A1 HA) |
SUPER_HEAVY_6 |
(1200, 2000] | 最高常规防护等级,超出此范围视为该等级 |
UNPENETRABLE |
(2000, ∞) | 绝对不可击穿——元等级,仅显式引用获取 |
每个枚举常量记录了三个数值:lowerRha(下限,第一个等级为 0)、upperRha(上限)、medianRha(中位值,即 (lower + upper) / 2)。默认的 getRHA 实现使用 medianRha 作为该等级的单一数值代表——在离散视角下,"三级甲"对应的精确厚度就是 15mm(MEDIUM 的中位值)。
fromRha:RHA 到等级的映射¶
ArmorLevel.fromRha(float) 是等级体系的核心映射方法。它采用向上映射规则——取第一个 upperRha >= rha 的等级。这意味着:
- 穿深 120mm 落在
SUPER_HEAVY_2(上限 150mm > 120mm,且上一级SUPER_HEAVY_1上限仅 80mm < 120mm)。 - 穿深 20mm 落在
MEDIUM(上限 20mm = 20mm,命中边界值——等于上限算该等级)。 - 穿深 0mm 落在
UNARMORED_1(上限 1mm >= 0mm)。 - 任何大于 2000mm 的值落在
SUPER_HEAVY_6(兜底)。UNPENETRABLE被fromRha显式跳过。 fromRha永远不会返回UNPENETRABLE——它只能通过ArmorLevel.UNPENETRABLE显式引用获得。
向上映射的设计决策是基于"防护需求"的视角:如果我有一块刚好 5mm 的装甲板,它应该被归类为 LIGHT_1(覆盖 3\~5mm)而非 LIGHT_2(覆盖 5\~10mm),因为后者的下限是 5mm(不包含)。5mm 刚好等于 LIGHT_1 的上限,因此归入 LIGHT_1。
UNPENETRABLE:绝对不可击穿¶
UNPENETRABLE 是枚举末尾的一个元等级(meta level),与前面 12 级物理等级不同,它表达的是一个纯语义概念——"免疫一切弹道伤害"。
它的核心规则如下:
- 只能显式引用:
fromRha会跳过UNPENETRABLE,因此任何有限(甚至无穷大)的穿深值都不会映射到这个等级。普通武器永远无法"自然达到"绝对防御。 - 任何等级都无法击穿它:
canDefeat(UNPENETRABLE)永远返回false。即使武器的穿甲等级也是UNPENETRABLE(作为穿甲等级使用时),判定结果仍是false——这个等级被设计为仅在护甲侧使用。 - 中位值为
Float.MAX_VALUE:这意味着即使精密模式下的resolvePenetration使用modifyPenetration > getRHA的严格浮点比较,由于Float.MAX_VALUE是 Java 浮点数的最大值,任何有限的modifyPenetration返回值都无法超过它,判定结果仍是未击穿。
适用场景包括:魔法护盾(护盾激活时绝对防御)、创造模式装备、管理员/剧情保护目标(任务关键 NPC)、调试方块等。
@Override
public ArmorLevel getArmorLevel(BFDamageContext ctx) {
if (this.isShieldActive()) {
return ArmorLevel.UNPENETRABLE; // 护盾激活时免疫一切伤害
}
return ArmorLevel.HEAVY;
}
UNPENETRABLE 是对精密模式下 getRHA 返回 Float.MAX_VALUE 的离散等级对等物。简易模式的模组作者无需覆写 getRHA 或 resolvePenetration,只需在 getArmorLevel 中返回 UNPENETRABLE 即可获得绝对防御行为。
canDefeat:等级间判定¶
canDefeat(armorLevel) 判定当前等级(作为武器穿深等级)是否能击穿目标护甲等级。规则是 ordinal() >= armorLevel.ordinal()——使用 >= 比较,等于算击穿。唯一的例外是当目标等级为 UNPENETRABLE 时,永远返回 false。
例如,一把穿深为 70mm 的枪(映射到 SUPER_HEAVY_1,40\~80mm)命中一个护甲为 HEAVY(20\~40mm)的目标时,SUPER_HEAVY_1.canDefeat(HEAVY) 返回 true——越级击穿。同一把枪命中一个护甲同样为 SUPER_HEAVY_1 的目标时,canDefeat 返回 true——同级击穿,此时伤害打六五折。
canDefeat 的 >= 规则意味着等级体系的击穿判定偏向于"乐观"——刚好相等时算击穿。如果你需要更保守的判定,在精密模式下覆写 resolvePenetration,使用 modifiedPenetration > getRHA 的严格大于比较即可。但需要注意,即使是严格大于比较,也无法击穿 UNPENETRABLE——因为其 medianRha() 返回 Float.MAX_VALUE。
在 getArmorLevel 中使用等级¶
对于简易模式的护甲模组,getArmorLevel 是你最重要的实现。你需要根据命中上下文返回正确的等级。最简单的情况是全身统一等级:
@Override
public ArmorLevel getArmorLevel(BFDamageContext ctx) {
return ArmorLevel.HEAVY; // 40~80mm,相当于步战车正面
}
更常见的情况是根据命中部位返回不同等级。下面的代码展示了一个典型的坦克装甲分布——正面最强,侧面次之,后方最弱,顶部和底部也有区分:
@Override
public ArmorLevel getArmorLevel(BFDamageContext ctx) {
Vec3 local = ctx.hitPoint().subtract(this.position());
double yRot = Math.toRadians(this.getYRot());
// 前后方向分量
double forwardDist = local.x * Math.sin(-yRot) + local.z * Math.cos(-yRot);
// 左右方向分量
double sideDist = Math.abs(local.x * Math.cos(-yRot) - local.z * Math.sin(-yRot));
// 高度
double heightFrac = local.y / this.getBbHeight();
// 顶部攻击(高角度)
if (heightFrac > 0.75f) {
return ArmorLevel.SUPER_HEAVY_1; // 顶部通常较薄
}
// 底部攻击
if (heightFrac < 0.1f) {
return ArmorLevel.MEDIUM; // 底部最薄
}
// 正面进攻
if (forwardDist > 0) {
return sideDist < 1.0
? ArmorLevel.SUPER_HEAVY_4 // 正前方:300~600mm(含爆反)
: ArmorLevel.SUPER_HEAVY_2; // 前侧方:80~150mm
}
// 侧面进攻
if (sideDist < 1.5) {
return ArmorLevel.HEAVY; // 纯侧面:20~40mm
}
// 后方进攻
return ArmorLevel.SUPER_HEAVY_1; // 后方:40~80mm
}
这个示例展示了 getArmorLevel 的核心设计空间——你有权限拿到完整的命中几何信息(hitPoint、hitNormal等),可以根据这些信息做出任意复杂的部位判定。判定的粒度完全由你控制——可以三个部位(前/侧/后),也可以六个面分别处理。
护甲物品的差异:如果你是护甲物品开发者,BFArmorMaterial 版本的 getArmorLevel(EquipmentSlot slot, BFDamageContext ctx) 多了一个 slot 参数——适配器已根据命中点高度帮你确定了命中槽位。通常你只需按 slot 返回等级而不必手动做命中部位计算。如果需要自定义命中槽位映射规则,覆写 mapHitToSlot(wearer, ctx) 即可。详见 3.6 BFArmorMaterial 接口。
等级作为设计辅助而非枷锁¶
ArmorLevel 的十二级体系是一个设计辅助工具,不是一个必须遵守的标准。你可以选择任何一个等级来描述你的装甲——即使它的参考对象和你的实际设计意图不完全匹配。一个"混凝土碉堡"的装甲可能用 SUPER_HEAVY_3 来表达——不是因为它有 150\~300mm 的轧制钢板,而是因为这个等级在游戏性上恰当地表达了"冷战中早期反装甲武器能打穿,但二战武器够呛"的防护水平。
同样,你可以定义完全脱离 RHA 物理参照的等级语义。例如一个魔法护盾模组可以将 LIGHT_1 定义为"学徒级护盾",SUPER_HEAVY_3 定义为"大法师级护盾"——等级枚举的值本身不带有物理含义,物理含义是你通过 getArmorLevel 的返回值赋予的。
当你的设计超出十二级的表达能力时(例如你需要 0.5mm 的间隔而不是 ×2 的步幅),就应在精密模式下覆写 getRHA 和 resolvePenetration,完全绕过等级比较,直接使用 float 精度。
如果你需要的是"不可击穿"而非"更高数值",则不应通过增加数值来追求——UNPENETRABLE 元等级为此而生。不要在 SUPER_HEAVY_6 之后尝试映射 Float.MAX_VALUE 来"自然达到"绝对防御,因为 fromRha(Float.MAX_VALUE) 仍然返回 SUPER_HEAVY_6,它在离散等级下是可以被同级击穿的。正确的做法是在 getArmorLevel 中显式返回 ArmorLevel.UNPENETRABLE。
显示名与本地化¶
每个 ArmorLevel 枚举常量都注册了本地化键,分别供护甲显示和穿甲显示两种场景使用。调用 getArmorDisplayName() 和 getPenetrationDisplayName() 返回 Component,可在 HUD 或物品提示中直接使用:
// 显示目标的防护等级
ArmorLevel armor = target.getArmorLevel(ctx);
player.displayClientMessage(armor.getArmorDisplayName(), true);
// 输出类似:重型防护 [HEAVY]
// 显示武器的穿甲等级
ArmorLevel pen = ctx.getPenetrationLevel();
tooltip.add(pen.getPenetrationDisplayName());
// 输出类似:重型穿透 [HEAVY PEN]
语言文件位于 assets/ballistics_framework/lang/ 下,共 13 个等级 × 2 种显示名 = 26 个条目。如果需要修改显示文字,可以通过资源包覆写这些语言键,而不必修改代码。