跳转至

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(兜底)。UNPENETRABLEfromRha 显式跳过。
  • fromRha 永远不会返回 UNPENETRABLE——它只能通过 ArmorLevel.UNPENETRABLE 显式引用获得。

向上映射的设计决策是基于"防护需求"的视角:如果我有一块刚好 5mm 的装甲板,它应该被归类为 LIGHT_1(覆盖 3\~5mm)而非 LIGHT_2(覆盖 5\~10mm),因为后者的下限是 5mm(不包含)。5mm 刚好等于 LIGHT_1 的上限,因此归入 LIGHT_1

UNPENETRABLE:绝对不可击穿

UNPENETRABLE 是枚举末尾的一个元等级(meta level),与前面 12 级物理等级不同,它表达的是一个纯语义概念——"免疫一切弹道伤害"。

它的核心规则如下:

  1. 只能显式引用fromRha 会跳过 UNPENETRABLE,因此任何有限(甚至无穷大)的穿深值都不会映射到这个等级。普通武器永远无法"自然达到"绝对防御。
  2. 任何等级都无法击穿它canDefeat(UNPENETRABLE) 永远返回 false。即使武器的穿甲等级也是 UNPENETRABLE(作为穿甲等级使用时),判定结果仍是 false——这个等级被设计为仅在护甲侧使用。
  3. 中位值为 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 的离散等级对等物。简易模式的模组作者无需覆写 getRHAresolvePenetration,只需在 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 的核心设计空间——你有权限拿到完整的命中几何信息(hitPointhitNormal等),可以根据这些信息做出任意复杂的部位判定。判定的粒度完全由你控制——可以三个部位(前/侧/后),也可以六个面分别处理。

护甲物品的差异:如果你是护甲物品开发者,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 的步幅),就应在精密模式下覆写 getRHAresolvePenetration,完全绕过等级比较,直接使用 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 个条目。如果需要修改显示文字,可以通过资源包覆写这些语言键,而不必修改代码。