Vehicle Armour and Damage - AntonIT99/Flans-Mod-Ultimate-2.0 GitHub Wiki

Vehicle Armour and Damage

Flan's Mod Ultimate 2.0 supports optional physical armour, velocity-dependent penetration, mass-normalized vehicle health, and armour-aware explosion damage. These systems are server-authoritative and are separate opt-ins: adding armour does not automatically normalize health, and normalizing health does not require armour.

Legacy definitions remain compatible. A vehicle with none of the keys on this page keeps its authored hitbox health and has no physical armour gate. Existing penetrationResistance and penetratingPower values keep their legacy abstract meaning and are never interpreted as millimetres; they belong to the separate Penetration System.

Contents

Armour Parameters

All thicknesses are nominal millimetres. Every semantic key accepts:

<thicknessMm> [slopeDeg]

The slope is optional and defaults to 0. An explicit thickness of 0 means intentionally unarmoured; it is different from an absent value because it stops armour-precedence fallback.

Hull

Key Applies to
ArmorFrontMm Hull front
ArmorRearMm Hull rear
ArmorSideMm Both hull sides
ArmorTopMm Hull top
ArmorBottomMm Hull bottom

Turret

Key Applies to
TurretArmorFrontMm Turret front
TurretArmorRearMm Turret rear
TurretArmorSideMm Both turret sides
TurretArmorTopMm Turret top
TurretArmorBottomMm Turret bottom

Turret-facing follows the turret's current yaw. A turret front plate therefore rotates with the turret rather than remaining attached to the hull's forward direction. Damageable barrel collision boxes also follow barrel pitch.

Warfare 44 tanks and turreted armoured vehicles define a separate SetupPart turret above the hull roof. Their turret armour keys therefore protect the rotating structure. Fixed casemates, including the Churchill Gun Carrier, SU-85, and SU-100, remain hull-facing and use the hull armour keys. Where a Warfare 44 turret has no authored turret thickness, the hull thickness is the fallback for that facing.

Naval

Warship armour is published per structure rather than per hull facing, and one structure is normally spread across several parts of a definition. Each naval key covers a whole family of parts and takes the same <thicknessMm> [slopeDeg] format as the hull keys.

Key Applies to
ArmorBeltMm belt, beltl, beltr, port, starboard, left, right, leftsideArmor, rightsideArmor
ArmorDeckMm deck, deck2, deck3
ArmorCitadelMm citadel
ArmorBulkheadMm bulkhead, bulkhead2
ArmorConningTowerMm conningTower, aftTower, bridge
ArmorTorpedoBulgeMm torpedoBulge, torpedoBulge2, torpedoBulge3, torpedoBulge4, bulge, bulgel, bulger
ArmorMachineryMm engineRoom1-8, engine, engine2-6, boilerRoom1-8, steering
ArmorSuperstructureMm superstructure
ArmorBowMm bow
ArmorSternMm stern
ArmorFlightDeckMm flightDeck, flightDeck2, hangar, hangarDeck, hangarDeck2, hangarDeck3

A naval key resolves as a part override, so it beats the turret and hull keys. An explicit PartArmorMm line for one of those parts is more specific still and wins, whichever order the two are declared in.

Main-battery mounts need no naval key. turret1 through turret16, turretarmor, moreturretarmor, turretside, turretSkirt, weakTrt, and weakTrt2 are recognised as turret-mounted. Their hitboxes rotate in the turret frame and TurretArmorFrontMm and its siblings cover them unless a PartArmorMm override wins.

ArmorBeltMm 310 19
ArmorDeckMm 121
ArmorCitadelMm 439
ArmorConningTowerMm 406
TurretArmorFrontMm 457
PartArmorMm deck2 37

Naval keys naming a part the definition does not declare are simply unused.

Per-Part Override

PartArmorMm <part> <thicknessMm>

This is a uniform, unsloped override for the named driveable part. For example:

ArmorSideMm 40
PartArmorMm leftTrack 15
PartArmorMm additionalArmor 0

The left track uses 15 mm on every face. The additional-armour part is explicitly unarmoured and does not fall back to turret or hull armour.

Negative, non-finite, malformed, or out-of-range armour values are reported as content-definition warnings and ignored. Valid slope angles are 0 through 89 degrees.

Protection Against HEAT

Composite and spaced arrays stop a shaped-charge jet far better than their equivalent against kinetic rounds. A face can therefore declare a second thickness that only HEAT ammunition is tested against. Each key is the face's armour key with Mm replaced by VsHeatMm:

ArmorFrontMm 600
ArmorFrontVsHeatMm 950
TurretArmorFrontMm 650 10
TurretArmorFrontVsHeatMm 1100
PartArmorMm rightSkirt 8
PartArmorVsHeatMm rightSkirt 0
Key Refines
ArmorFrontVsHeatMm, ArmorRearVsHeatMm, ArmorSideVsHeatMm, ArmorTopVsHeatMm, ArmorBottomVsHeatMm The hull face of the same name
TurretArmorFrontVsHeatMm, TurretArmorRearVsHeatMm, TurretArmorSideVsHeatMm, TurretArmorTopVsHeatMm, TurretArmorBottomVsHeatMm The turret face of the same name
PartArmorVsHeatMm <part> <thicknessMm> A part given a PartArmorMm or naval armour line

A HEAT key takes only a thickness. It shares the slope of the plate it refines and resolves through the same precedence, so the line-of-sight geometry is the same for both kinds of round. A HEAT key without a plate of its own level to refine is reported as a warning and ignored. A face with no HEAT key resists HEAT with its ordinary thickness, so existing definitions need no change.

Slope Convention

The authored slope describes a virtual armour plate, not the visible model or the physical cuboid hitbox. Collision boxes may remain simple cuboids.

The slope is mathematically an angle away from the nominal face normal. For a geometrically head-on projectile:

effectiveArmorMm = nominalArmorMm / cos(slopeDeg)

Therefore:

ArmorFrontMm 80 55

resolves head-on as:

80 / cos(55°) = 139.47 mm

At runtime the mod rotates the nominal collision-face normal by the authored slope, then compares the incoming projectile direction with that virtual normal. Authored slope and tactical shot obliquity are combined in this single angle; the mod does not apply two independent cosine multipliers.

Front/rear slopes rotate around local X (the lateral axis), left/right slopes rotate around local Z (the longitudinal axis), and top/bottom slopes rotate around local X. Impact angles are capped by maxArmorImpactAngleDeg so grazing hits remain finite.

Static tooltips display the authored 80 mm @ 55°, not 139.47 mm, because effective armour depends on the incoming shot.

Armour Precedence

For the impacted part and collision face, resolution is:

explicit PartArmorMm override
    > naval structure key covering that part
    > turret semantic armour for turret-mounted parts
    > hull semantic armour
    > 0 mm / unarmoured

All definition-level tables and virtual plate normals are resolved once when the vehicle type is finalized. They are not parsed or reconstructed for every hit.

Projectile Penetration

Use the existing bullet parameter:

PenetrationAt100m <millimetres>

It is the projectile's range-independent penetration rating against the perpendicular plate. The historical key name is retained for content-pack compatibility. Per-round values remain available through AddRound:

AddRound <name> <count> <massG> [explosiveMassG] [muzzleVelocityMps] [penetrationAt100m]

The authored value is used directly at every range. Projectile velocity and air drag do not modify the armour gate.

The direct-fire gate is:

Impact Result
Nominal armour is 0 mm Penetration gate is skipped.
Armour is greater than 0, but P100 is absent or invalid Direct damage is blocked.
Authored penetration is below effective armour Direct damage is blocked.
Authored penetration equals or exceeds effective armour Penetration succeeds.

This prevents non-penetrating projectiles from repeatedly chipping armoured-part HP. It does not stop an explosive projectile from detonating.

Direct Projectile Damage

Armour and the HP scale are independent.

Legacy-Health Vehicles

After a successful armour gate, the existing fixed vehicle damage magnitude is used. The physical kinetic formula and overmatch multiplier are not applied to legacy 10,000–20,000 HP scales.

Normalized-Health Vehicles

When the projectile has a valid positive Mass in grams, it uses the same canonical kinetic formula as other kinetic projectile damage:

kineticDamage = newDamageSystemDamageReference
              * 0.001
              * sqrt(massGrams)
              * (massGrams / 9)^(1/6)
              * velocityMetresPerSecond

The extra mass progression leaves a 9 g infantry projectile unchanged and makes damage grow with mass^(2/3) at a fixed velocity. This matches the geometric exponent used by normalized vehicle HP, preventing cannon shells from falling behind the mass-derived health curve while avoiding a global damage multiplier.

velocityMetresPerSecond is the fixed firing velocity, not the projectile entity's current movement at impact. It is resolved from the selected AddRound velocity first, then the ammunition's MuzzleVelocity / BulletSpeed, then the firing weapon, and finally the deterministic default. This keeps kinetic damage stable across drag, guidance, collision transforms, and raytraced shots.

After penetration, mass-based damage receives the hybrid overmatch multiplier:

overmatch  = penetrationMm / effectiveArmorMm
multiplier = min(overmatch, 2.5)

This is 1.0x at the penetration boundary, scales directly with surplus penetration, and caps at 2.5x. Unarmoured parts receive ordinary kinetic damage without treating zero armour as infinite overmatch.

If Mass is absent, the bullet's existing fixed Damage value is used unchanged after a successful gate. No fake mass is inferred, and legacy ammunition does not become harmless against normalized-health vehicles.

HEAT Ammunition

A shaped charge defeats armour with its jet, not with the speed of the round that carries it. Under the kinetic formula a slow rocket such as the Panzerfaust 60 (2.9 kg at 45 m/s) would deal about 32 damage, 79 at most with overmatch, against a Panzer IV hull of about 1,200 HP. Mark such a round with the bullet key:

HEAT true

It is the same key the Labjac edition uses, so its packs arrive with their HEAT rounds already marked. The built-in categories mark every HEAT round they cover.

A HEAT round changes three things.

Its armour gate. PenetrationAt100m is tested against each face's protection against HEAT, which defaults to the ordinary thickness.

Where it stops. The jet forms at the first surface the round strikes, so a HEAT round never carries on past the first vehicle part it hits. A skirt, an ERA block or a track hit first takes the round in place of the hull behind it, and a destroyed skirt leaves the hull exposed to the next shot.

Its damage behind armour, on vehicles using UseRealisticVehicleHealth. When the round defeats an armoured face, it deals damage from its explosive charge:

residual    = clamp(0.6 + 0.4 * (overmatch - 1), 0.6, 1)
heatDamage  = heatDamageReference * sqrt(chargeKgTnt) * residual
damage      = max(heatDamage, kineticDamage)

The charge is the round's ExplosiveMassTNTg / ExplosiveMassTNTKg, including per-weapon overrides, or the equivalent recovered from a legacy explosion radius. The damage does not depend on velocity. A jet that only just penetrates delivers 60% of it, rising to the full value at twice the needed penetration. Unlike the kinetic overmatch multiplier, surplus penetration never adds more than that. A fast HEAT tank shell keeps its kinetic damage when that is higher, so the direct hit never does less than the kinetic formula gives.

With the default reference of 1100, against a penetration with at least twice the needed margin:

Round Charge Damage on full penetration Hits on a Panzer IV hull (1,211 HP)
Panzerfaust 60 0.95 kg 1,072 2
Panzerschreck 0.86 kg 1,020 2
Bazooka M6A3 0.42 kg 713 2
PIAT 0.40 kg 696 2
FGM-148 Javelin 6 kg 2,694 1

Across the official Second World War tanks this gives:

  • A Panzerfaust or Panzerschreck knocks out a Panzer IV, Sherman or T-34-85 in two hits from any angle. The first takes at least two thirds of the hull. Light vehicles go down in one.
  • Heavy tanks (Tiger I, IS-2 side, Churchill, Tiger II side) take two or three.
  • The Tiger II glacis stops both. A Bazooka or PIAT cannot get through the front of a Tiger I, IS-2 or Churchill.
  • A Bazooka or PIAT barely penetrates a medium tank's front and needs three or four hits there, against two or three on the flank.

These targets are covered by the unit tests in WorldWarTwoAntiTankBalanceTest.

On a penetration the jet also sprays spall through the part. Every occupant of a seat on that part takes heatCrewSpallDamage, scaled by the same residual. The warhead's own blast then skips the penetrated part, since the jet has already done the warhead's work there; other parts still take blast under the normal pressure gate.

Soft parts with no armour, and vehicles on legacy health, keep their existing damage. Legacy Damage values already stand for the whole warhead. A HEAT round that fails to penetrate does no direct damage, and its blast faces the normal pressure gate.

Only bullets read HEAT. Grenades and mines do not.

Normalized Vehicle Health

Enable the system explicitly on a driveable:

UseRealisticVehicleHealth true
RealMassKg 45000

Total hitbox health is:

totalHP = realisticVehicleHealthScale * RealMassKg^(2/3)

With the default scale of 5.0:

Mass Approximate total HP
1,000 kg 500
3,000 kg 1,040
10,000 kg 2,321
20,000 kg 3,684
60,000 kg 7,663

Existing positive hitbox HP values become relative weights. For example:

core        70  -> 70%
turret      14  -> 14%
leftTrack    8  -> 8%
rightTrack   8  -> 8%

Warfare 44 vehicle definitions use the core as the main hull mass share. Separate nose armour boxes usually receive 10% of authored HP weight; armour thickness is handled by the armour values above rather than extra HP. Turreted vehicles allocate roughly 9–19% to the turret, varying with the vehicle and modeled gun. These are approximate part weights, informed by the Soviet archival turret-mass comparison, not a direct conversion from measured tonnes to hit points.

Hitboxes with authored health less than or equal to zero remain zero and are not given normalized HP. If RealMassKg is absent or invalid, or no positive weights exist, the mod logs a warning and retains all authored health. Re-finalizing a definition is idempotent and never scales already-scaled HP a second time.

AA Gun Mass-Scaled Health

AA guns can opt into the same curve with UseRealisticVehicleHealth true and a valid RealMassKg. For an AA gun, RealMassKg is the complete firing/operational mass of the represented gun and integral mount, carriage, or shield; it excludes crew, towing vehicles, and non-integral ammunition reserves.

An AA gun has one entity health pool rather than driveable hitbox weights, so the calculated total is rounded to the nearest integer and applied directly. A valid result has a minimum of one HP. If the mass is absent or invalid, the mod logs a warning and retains the legacy Health value.

Explosion Damage

Direct impact, fragmentation, and blast are separate channels. An explosive projectile that fails direct penetration still detonates and runs both explosion channels. A HEAT round that penetrates is the exception for the part it went through: that part takes the jet instead of the blast.

For each explosion, the driveable, wheel, and seat entities are grouped as one vehicle. Each active part inside the blast or fragmentation radius is evaluated from the detonation to its nearest collision surface. Block cover is measured with several nearby rays to that part's exposed face, so partial cover reduces damage gradually. This matters for large aircraft whose wings and tail can be far from the entity origin.

The nearest part receives its full resolved damage. Other exposed parts receive half their resolved damage, sharing a vehicle-wide budget capped at twice the strongest single-part hit. The cap prevents a vehicle with many hitboxes from multiplying one explosion into unbounded HP damage.

Destroyed parts are excluded from target selection and do not block the cover rays, which trace terrain blocks only. If a part is destroyed while the same explosion is being applied, its unused share of the damage budget is available to the surviving exposed parts. Repairing it makes its hitbox eligible again.

Fragmentation

Fragment damage uses the fragmentation model: casing mass and fragment size determine effective count, and spread and distance determine expected hits. Charge-to-metal ratio and projectile speed determine how quickly fragments lose wounding energy. Directional patterns are checked from the actual detonation point to each part's nearest surface.

Nominal armour Fragmentation result
0 mm Expected fragment damage after cover, direction, distance and energy loss.
Greater than 0 mm Fragmentation HP damage is zero.

Slope and effective ballistic armour are not used for this check.

Blast and Overpressure

Vehicle parts receive an additional structural blast effect when a large charge bursts close to an exposed part. It multiplies only blast HP damage, after cover and distance falloff; fragmentation, other entities and the blast radius are unchanged. The charge is the authored TNT equivalent (ExplosiveMassTNTg in grams or ExplosiveMassTNTKg in kilograms), or the equivalent recovered from a legacy crater radius and explosion power. With charge m, distance d from the detonation to the part, and blast radius R:

chargeGain = clamp((m / 5)^0.75, 1, 5)
proximity  = clamp(1 - d / R, 0, 1)
vehicleBlastDamage = existingBlastDamage
                   * (1 + (chargeGain - 1) * proximity^4)

Charges of at most 5 kg TNT equivalent receive no boost, and the multiplier reaches one at the blast radius. This is a gameplay calibration for close-range structural damage, not a universal blast-physics law.

Unarmoured parts take this adjusted blast damage. Armoured parts require a valid positive explosive mass and still pass the pressure gate below.

The simplified scaled-distance model is:

d = max(actualDistance, minimumBlastDistanceMeters)
Z = d / cbrt(explosiveMassKg)

pressureKPa = 1772 / Z^3
            + 114 / Z^2
            + 108 / Z

requiredPressureKPa = armoredBlastResistanceKPaPerMm * nominalArmorMm

If pressure does not exceed the threshold, blast HP damage is zero. Above it:

ratio      = pressureKPa / requiredPressureKPa
multiplier = clamp((ratio - 1) / 2, 0, 1)
blastDamage = vehicleBlastDamage * multiplier

This threshold is a gameplay calibration, not a universal armour-physics law. Nominal thickness is used; armour slope does not increase blast resistance. Missing explosive mass means armoured blast damage is zero, while unarmoured blast remains compatible.

Destroyed Tracked Mobility

For vehicles declared with Tank true, losing either leftTrack or rightTrack removes all forward and reverse propulsion. The surviving track can still rotate the vehicle in place at reduced authority. The normal left/right controls keep their direction, while forward and reverse turn the vehicle in opposite directions according to which track survives. The destroyed track remains visually stationary. Destroying both tracks disables this emergency turning as well. The damaged track's own mesh or procedural links disappear when that track is destroyed. Part health and destroyed state are saved with the driveable and restored when the world is loaded again.

This is resolved by the server and requires no additional definition parameter.

Global Settings

File: config/flansmodultimate-common.toml

["Vehicle Damage Settings"]
    realisticVehicleHealthScale = 5.0
    maxArmorImpactAngleDeg = 80.0
    armoredBlastResistanceKPaPerMm = 150.0
    minimumBlastDistanceMeters = 0.5
    maxExplosionRadius = 128.0
    heatDamageReference = 1100.0
    heatCrewSpallDamage = 10.0

maxExplosionRadius clamps the explosion, blast and fragmentation radii of every detonation. It is a performance guard rather than a balance setting: the authored explosive mass is left untouched, so damage and every derived figure stay honest, and only the radii the server actually has to iterate are bounded. Conventional ordnance never reaches the default - the largest researched charge in the built-in categories, an 88 kg TNT-equivalent naval shell, works out at about 45 blocks - but a nuclear weapon's true yield would ask for a radius of tens of thousands of blocks. Raise it if your hardware can process a larger detonation, lower it if it cannot.

Setting Default Purpose
realisticVehicleHealthScale 5.0 Multiplier for mass-derived total HP.
maxArmorImpactAngleDeg 80.0 Caps effective armour at grazing impact angles.
armoredBlastResistanceKPaPerMm 150.0 Gameplay pressure threshold per nominal armour millimetre.
minimumBlastDistanceMeters 0.5 Prevents singular or infinite pressure at zero distance.
heatDamageReference 1100.0 Damage behind armour from a 1 kg TNT shaped charge that fully penetrates.
heatCrewSpallDamage 10.0 Damage to each occupant of a part a HEAT jet penetrates. 0 disables it.

These settings are synchronized from the server to clients.

Tooltips and Debugging

Hold Shift over a driveable item to see authored hull, turret, and per-part armour, RealMassKg, normalized-health status, derived total HP, and the per-part allocation. Hull and turret armour use a compact front / side / back summary. For example, the Tiger I H1 values 100 mm @ 9° / 80 mm / 80 mm @ 9° display as:

Armour       front / side / back
Hull               100 (9°) / 80 / 80 (9°) mm
Turret            100 / 80 / 80 mm

The single mm suffix applies to all three values. A missing semantic face is shown as —. Top and bottom plates remain active in damage calculations but are omitted from this compact summary; /vehiclephysics can be used for the complete resolved definition. Explicit PartArmorMm overrides continue on separate lines.

The operator command /vehiclephysics reports the same resolved definition data for the ridden driveable or held driveable item, including whether normalized health was requested and successfully enabled.

Faces with a separate protection against HEAT show it beside the ordinary value in /vehiclephysics, the driveable inventory's part details and the armour debug overlay. A HEAT round's ammunition tooltip tags its penetration, for example 200mm HEAT.

While debug rendering (F10) is on, an operator's bullet hits on a driveable are reported in chat. A hit on an armoured face names the penetration it brought, the effective armour, the struck face, the nominal thickness (the HEAT value for a HEAT round) and the impact angle against the face's sloped normal:

[FMU Debug] Tiger I - Core: blocked (penetration 120.00 mm vs effective armor 139.47 mm: front face, 80.00 mm nominal at 55.0 deg)

A penetrating hit shows the same check after the damage it dealt; hits on unarmoured faces report only the damage.

Complete Examples

Armoured Vehicle Retaining Legacy HP

ArmorFrontMm 80 55
ArmorRearMm 40
ArmorSideMm 40
ArmorTopMm 16
ArmorBottomMm 20

TurretArmorFrontMm 100 12
TurretArmorRearMm 45
TurretArmorSideMm 45
TurretArmorTopMm 25

PartArmorMm leftTrack 15
PartArmorMm rightTrack 15

Only the penetration gate is new. Successful shots still use the vehicle's legacy fixed-damage and authored-HP scale.

Armour and Normalized HP

RealMassKg 44800
UseRealisticVehicleHealth true

ArmorFrontMm 80 55
ArmorRearMm 40
ArmorSideMm 40
ArmorTopMm 16
ArmorBottomMm 20

TurretArmorFrontMm 100 12
TurretArmorRearMm 45
TurretArmorSideMm 45
TurretArmorTopMm 25

The positive authored SetupPart health values determine only how the derived total HP is distributed.

Penetrating AP Round

Mass 6200
MuzzleVelocity 790
PenetrationAt100m 145
Damage 100

The mass enables kinetic damage against normalized-health targets. P100 controls whether armoured parts can be damaged. Damage remains the compatibility value for legacy-health vehicles.

Explosive Round

Mass 6800
ExplosiveMassTNTg 650
MuzzleVelocity 600
PenetrationAt100m 85

Direct penetration, fragments, and armoured blast pressure are resolved independently.

HEAT Round

Mass 2900
MuzzleVelocity 45
PenetrationAt100m 200
ExplosiveMassTNTg 950
ExplodeOnImpact True
HEAT True

The Panzerfaust 60. Against a realistic-health vehicle its 950 g charge, not its 45 m/s, sets the damage of a penetrating hit.

Compatibility Summary

Definition combination Behaviour
Old vehicle + old bullet Authored HP, fixed direct damage, no armour gate.
Old vehicle + P100 bullet No change unless the vehicle declares armour.
Armoured vehicle + bullet without P100 Armoured faces block direct damage; explicit/unresolved 0 mm faces remain damageable.
Normalized vehicle + mass bullet Mass-derived HP and kinetic direct damage.
Normalized vehicle + massless bullet Mass-derived HP and existing fixed-damage fallback.
Normalized-health opt-in without valid RealMassKg Warning and authored HP retained.
Armoured part + explosion without an explosive mass Fragments blocked and armoured blast HP damage is zero.
Normalized vehicle + HEAT round Charge-based damage behind armour it penetrates, crew spall, and no blast on that part.
Any vehicle + HEAT round Tested against the face's protection against HEAT; stops at the first part it hits.
Any vehicle + HESH round Stops at the first part it hits, using kinetic protection and existing direct damage. Its configured explosion remains separate; no HEAT jet is generated.

See Special ammunition for depth-charge and HESH definitions.

No existing legacy penetration or damage parameter changed meaning, and no new parameter is required on existing content.

⚠️ **GitHub.com Fallback** ⚠️