跳转至

3.3 穿深修正与斜穿效应

协议的管线中,modifyPenetration 是护甲侧唯一可以降低有效穿深的方法。它的默认实现直接返回 ctx.penetration()——不做任何事情。这并非疏忽:角度效应导致的等效厚度增加由武器侧在构造上下文前完成,modifyPenetration 专门留给护甲侧实现主动减效——爆反拦截、间隙衰减、多层装甲的逐层扣除等。

默认行为:穿深原样传递

default float modifyPenetration(BFDamageContext ctx) {
    return ctx.penetration();
}

只有在不覆写 modifyPenetration 的情况下,穿甲判定才等价于简单的 penetration > getRHA(精密模式)或等级比较(简易模式)。此时武器侧的原始穿深直接与护甲厚度对抗,中间没有任何衰减——这是最"干净"的对抗模型,适合不想在判定中引入额外复杂性的简单护甲模组。

斜穿修正的职责分工

理解 modifyPenetration 的定位,关键在于分清两个概念——角度修正减效

角度修正是武器侧的职责。当一颗弹丸以倾斜角命中装甲时,等效需要穿透的厚度增加——公式近似为 有效穿深 = 垂直穿深 / cosθ。武器模组在构造上下文前将此修正计入 penetration 字段,因此护甲侧拿到的 penetration 已经是"考虑了角度后的有效穿深"。

减效是护甲侧的职责。爆炸反应装甲(ERA)的拦截、间隙装甲(spaced armor)的衰减、复合材料对特定弹种的额外防护——这些都减少已修正的穿深值。modifyPenetration 正是为此而设。

将这两个职责分开的理由至关重要:如果协议默认做了角度修正,护甲侧覆写的 modifyPenetration 就会在"已被协议修正过"的值上再做操作,导致修正叠加或顺序矛盾。而将角度修正放在武器侧、减效放在护甲侧,职责清晰,不会冲突。

爆反拦截:概率性或确定性衰减

爆炸反应装甲对化学能射流(HEAT)的拦截是最典型的减效场景。一块 ERA 模块被命中时,它会引爆并向外喷射金属板,切断正在穿透的射流。

最简单的 ERA 模型是概率性的——一定概率减少固定穿深:

@Override
public float modifyPenetration(BFDamageContext ctx) {
    float base = ctx.penetration();

    // ERA 仅在正面生效
    Vec3 local = ctx.hitPoint().subtract(this.position());
    if (!isFrontalHit(local)) return base;

    // 70% 概率拦截,减少 150mm 穿深
    if (this.level().random.nextFloat() < 0.7f) {
        return Math.max(0, base - 150f);
    }
    return base;
}

更精致的模型可以考虑弹种差异。化学能弹药(HEAT)的射流速度相对较慢且连续,更容易被 ERA 拦截;动能穿甲弹(APFSDS)速度极高,ERA 的拦截效果有限:

@Override
public float modifyPenetration(BFDamageContext ctx) {
    float base = ctx.penetration();
    if (!isFrontalHit(ctx)) return base;

    // 判断弹种——通过扩展字段
    String ammoType = ctx.extensions().get(MyAmmoKeys.AMMO_TYPE);

    if ("HEAT".equals(ammoType)) {
        // 化学能弹药:ERA 强力拦截
        if (this.level().random.nextFloat() < 0.8f) {
            return Math.max(0, base - 200f);
        }
        return base;
    } else if ("HESH".equals(ammoType)) {
        // 碎甲弹:ERA 完全无效——通过冲击波而非射流工作
        return base;
    } else {
        // 动能弹药(APFSDS、AP 等):ERA 轻度衰减
        return Math.max(0, base - 30f);
    }
}

此例展示了扩展字段(AMMO_TYPE)在护甲侧的典型用法——你通过读取武器侧写入的扩展 key 来区分弹种,做出不同的减效决策。协议核心完全不知情,只看到了 modifyPenetration 返回的值。

间隙装甲与多层衰减

间隙装甲(spaced armor)由多层薄板和板间空隙组成。弹丸在穿透第一层薄板后发生偏转和能量损失,再撞击第二层时穿深已降低。这个效果在 modifyPenetration 中可以建模为逐层扣除:

@Override
public float modifyPenetration(BFDamageContext ctx) {
    float remaining = ctx.penetration();

    // 第一层:外层装甲板(50mm RHA)
    float outerPlate = 50f;
    remaining -= outerPlate;

    // 间隙衰减:弹丸在空气中飞行一段距离,偏转导致额外损失
    remaining -= 20f;  // 固定衰减

    // 如果穿深已耗尽,直接返回 0——弹丸被外层板捕获
    if (remaining <= 0) return 0f;

    // 第二层:内层装甲板(30mm RHA),但弹丸已偏转,等效厚度更高
    float innerPlate = 30f;
    remaining -= innerPlate * 1.2f;  // 偏转导致等效厚度增加 20%

    // 如果穿深仍为正,弹丸穿透全部间隙装甲并仍有剩余穿深
    return Math.max(0, remaining);
}

这个模型按照物理穿透顺序处理装甲层——外层板→间隙→内层板,每一步扣除穿深。如果 remaining 在中途变为负数或零,说明弹丸被当前层挡住。

间隙装甲的衰减还可以取决于弹丸口径。小口径弹丸在穿透间隙时更容易偏转和翻滚,因此衰减更明显:

float caliber = ctx.extensions().get(BFDamageExtensions.CALIBER);
float gapAttenuation;
if (caliber < 0.03f) {
    gapAttenuation = 50f;   // 小口径(<30mm)严重衰减
} else if (caliber < 0.075f) {
    gapAttenuation = 30f;   // 中口径
} else {
    gapAttenuation = 10f;   // 大口径(>75mm)几乎不受影响
}

modifyPenetration 与 getRHA 的关系

modifyPenetrationgetRHA 是两个独立的管线步骤,但它们在逻辑上有微妙的区别。getRHA 描述的是装甲自身的防护能力——它的值取决于装甲的材料和结构,与弹丸无关。modifyPenetration 描述的是弹丸在穿透此装甲时的能力衰减——它的值取决于弹丸的类型、速度和装甲的防御机制。

当你在覆写 modifyPenetration 时,一个常见的误区是试图在这里修正角度——但角度修正已经在武器侧完成,你应该关心的是"弹丸穿透这片装甲过程中损失了多少穿深"。

另一个常见误区是在 modifyPenetration 中重复 getRHA 的计算。如果你已经在 getRHA 中精确计算了装甲等效厚度,那么 resolvePenetration 中的 modifiedPenetration > getRHA 比较已经是精确的——你不需要在 modifyPenetration 中再去"反映装甲有多厚"。

换句话说,modifyPenetration 关心的不是"装甲有多厚",而是"装甲对弹丸做了什么"。这两者通常是相关的(厚装甲=更强的衰减),但在覆写中应保持职责分开。