3.3 穿深修正与斜穿效应¶
协议的管线中,modifyPenetration 是护甲侧唯一可以降低有效穿深的方法。它的默认实现直接返回 ctx.penetration()——不做任何事情。这并非疏忽:角度效应导致的等效厚度增加由武器侧在构造上下文前完成,modifyPenetration 专门留给护甲侧实现主动减效——爆反拦截、间隙衰减、多层装甲的逐层扣除等。
默认行为:穿深原样传递¶
只有在不覆写 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 的关系¶
modifyPenetration 和 getRHA 是两个独立的管线步骤,但它们在逻辑上有微妙的区别。getRHA 描述的是装甲自身的防护能力——它的值取决于装甲的材料和结构,与弹丸无关。modifyPenetration 描述的是弹丸在穿透此装甲时的能力衰减——它的值取决于弹丸的类型、速度和装甲的防御机制。
当你在覆写 modifyPenetration 时,一个常见的误区是试图在这里修正角度——但角度修正已经在武器侧完成,你应该关心的是"弹丸穿透这片装甲过程中损失了多少穿深"。
另一个常见误区是在 modifyPenetration 中重复 getRHA 的计算。如果你已经在 getRHA 中精确计算了装甲等效厚度,那么 resolvePenetration 中的 modifiedPenetration > getRHA 比较已经是精确的——你不需要在 modifyPenetration 中再去"反映装甲有多厚"。
换句话说,modifyPenetration 关心的不是"装甲有多厚",而是"装甲对弹丸做了什么"。这两者通常是相关的(厚装甲=更强的衰减),但在覆写中应保持职责分开。