Penetration System - AntonIT99/Flans-Mod-Ultimate-2.0 GitHub Wiki

Penetration System

Flan's Mod Ultimate 2.0 has two unrelated mechanics that are both called "penetration". They never interact and are never converted into each other.

  • Penetrating power, documented on this page. An abstract budget carried by every projectile, spent as it passes through players, mobs, shields, blocks, and vehicle parts. It decides how far a projectile travels through matter and, optionally, how much damage it still does when it gets there.
  • Physical armour penetration in millimetres, documented in Vehicle Armour and Damage. PenetrationAt100m is compared against ArmorFrontMm and friends to decide whether a shell defeats a vehicle plate.

The PenetrationResistance key of ArmorType and the internal penetration resistance of a driveable collision box both belong to the abstract system on this page, not to the millimetre system.

Contents

The Penetrating-Power Budget

Every shot starts with a single scalar, its penetrating power. The projectile spends that budget on everything it passes through and dies when the budget reaches zero. Nothing else terminates a penetrating projectile except its fuse, maximum range, or an explode-on-impact detonation.

The scale is abstract, not physical. A useful mental anchor is that one point of penetrating power is worth roughly one unarmoured player, because an unarmoured torso costs 1.0.

The budget is server-authoritative and is tracked per projectile, so two shots from the same magazine can end up with different remaining power.

Where The Initial Value Comes From

The initial value is resolved once, when the shot is fired, in this order:

  1. Penetrates false forces the legacy default of 0.7 and disables every derivation below. This is how a definition declares "this projectile does not penetrate".
  2. A projectile Mass greater than 0, meaning the ammunition uses the kinetic damage system, derives the value from muzzle kinetic energy. See Kinetic penetrating power. This supersedes Penetration / PenetratingPower.
  3. Otherwise, the authored Penetration / PenetratingPower value is used, defaulting to 1.0.

Because the derivation is keyed on Mass, a pack that has already converted its ammunition to physical mass and muzzle velocity gets consistent penetration for free, and a legacy pack that has not is untouched.

Kinetic Penetrating Power

For ammunition that declares a mass, penetrating power is derived from the muzzle kinetic energy of the projectile:

energyJoules = 0.5 * (massGrams / 1000) * muzzleVelocityMetresPerSecond^2

penetratingPower = kineticPenetrationReference * cbrt(energyJoules)

The cube root is what makes the system usable. Muzzle energy spans about four orders of magnitude between a pistol round and a tank shell; used directly it would give a cannon shell a budget of tens of thousands of players. The cube root compresses that span into a range where the budget stays readable in units of targets penetrated.

With the default kineticPenetrationReference of 0.087:

Ammunition Mass Muzzle velocity Muzzle energy Penetrating power
Nerf dart 1 g 20 m/s 0.2 J 0.05
.45 ACP 14.9 g 250 m/s 466 J 0.68
9x19mm Parabellum 8.0 g 360 m/s 518 J 0.70
5.56x45mm M855 4.0 g 920 m/s 1693 J 1.04
7.62x39mm M43 7.9 g 715 m/s 2019 J 1.10
.303 British 11.3 g 744 m/s 3127 J 1.27
7.62x51mm NATO M80 9.5 g 838 m/s 3336 J 1.30
7.92x57mm Mauser 12.8 g 760 m/s 3697 J 1.35
.50 BMG 42 g 890 m/s 16.6 kJ 2.21
20x110mm HS.404 130 g 840 m/s 45.9 kJ 3.11
37mm M3 APC 870 g 884 m/s 340 kJ 6.07
88mm PzGr 39 10.2 kg 773 m/s 3.05 MJ 12.61
128mm PzGr 43 28.3 kg 920 m/s 12.0 MJ 19.91

The default reference is chosen so that a 9x19mm service pistol round lands on 0.7, the legacy Penetrates false value and the exact threshold of an unarmoured torso. A pistol therefore does full damage to an unprotected target and is blunted by body armour, which is the intended shape of the curve.

Velocity Resolution

The velocity used is the one the projectile is actually fired at, resolved in this order:

  1. The muzzleVelocityMps field of the round selected from an AddRound belt.
  2. The ammunition's own BulletSpeed or MuzzleVelocity, multiplied by BulletSpeedMultiplier.
  3. The speed the firing weapon gives the projectile, including grip secondary fire and attachment multipliers.
  4. The default bullet speed of 3.0 blocks per tick, that is 60 m/s.

One block is one metre and twenty ticks are one second, so a speed in blocks per tick is twenty times smaller than the same speed in metres per second.

An AddRound belt derives penetrating power per round, so a belt that alternates armour-piercing and high-explosive rounds gets a different budget on each shot without any extra keys.

What Spends The Budget

Event Cost Always active
One tick of flight power *= (1 - PenetrationDecay) Yes
Head hitbox Helmet PenetrationResistance Yes, value differs (see below)
Body or arm hitbox Chestplate PenetrationResistance Yes, value differs
Leg hitbox Leggings + boots PenetrationResistance Yes, value differs
Any other living entity 1.0, flat Yes
Shield held by the target The gun's ShieldDamageAbsorption Yes
Block 2 * hardness * (1 / BlockPenetrationModifier) Only with enableBlockPenetration
Driveable part The part's collision-box penetration resistance Yes
Explode-on-impact detonation Whole remaining budget Yes

When useNewPenetrationSystem is disabled, a player hit costs a flat 1.0 instead of the armour-derived value. Every other row above is unaffected by that setting.

Block hardness is read from the blocks list of the penetration config when the block is listed there, and from the block's own destroy speed otherwise. With the default blockPenetrationModifier of 0.0 the multiplier term is 1, so a block costs twice its hardness: 3.0 for stone, 1.0 for dirt, 0.6 for glass. No small-arms round penetrates stone at default settings. Blocks whose effective hardness is negative, such as bedrock by default, stop projectiles regardless of their remaining penetration budget.

The raytracer spends block hardness to decide how far ahead to look for targets even when enableBlockPenetration is off. Only the projectile's real budget is gated by that setting.

Player Armour

The PenetrationResistance Key

ArmorType accepts:

PenetrationResistance <value>

It is the price, in penetrating power, of getting through that piece, and it is also the reference used to scale damage. It is not a percentage.

An unarmoured body part already has a resistance. An armour piece that declares no value defaults to the unarmoured value of its own slot, so equipping armour is never worse than wearing nothing:

Slot Unarmoured resistance Also the default for that slot
Helmet 1.00 Yes
Chestplate 1.00 Yes
Leggings 0.65 Yes
Boots 0.35 Yes

Which piece is charged depends on the hitbox that was struck:

Hitbox Resistance used
Head Helmet only
Body, left arm, right arm Chestplate only
Legs Leggings plus boots

Leggings and boots are therefore a matched pair whose unarmoured values sum back to 1.00. Splitting a class differently between the two slots changes how trousers behave when worn without boots.

Damage Scaling

The threshold for a hit is:

threshold = 0.7 * totalPenetrationResistance

If the projectile arrives with at least that much power it does full damage. If it arrives with less, damage falls off steeply:

damageMultiplier = (remainingPower / threshold)^2.5
remainingPower / threshold Damage
1.00 100%
0.90 77%
0.80 57%
0.70 41%
0.60 28%
0.50 18%
0.25 3%

The exponent means armour does not shave a flat percentage off every shot. It sets a threshold: rounds that overmatch it are barely affected, rounds that fall short are destroyed. That is what expresses "this vest defeats pistol rounds but not rifle rounds" without needing a separate calibre concept.

Damage scaling and armour-derived budget cost both require useNewPenetrationSystem. With it disabled, PenetrationResistance has no effect on damage and a player hit costs a flat 1.0.

Hitbox multipliers such as headshotDamageModifier are applied after this multiplier, not instead of it.

Relationship To The Other Armour Keys

A bullet striking an armoured player passes through four independent stages, in order:

  1. PenetrationResistance scales damage as above, then the hitbox multiplier applies.
  2. Vanilla Minecraft mitigation from ArmorPoints and Toughness.
  3. Defence is summed across the four worn pieces and removed as a flat fraction of all damage.
  4. BulletDefence renormalizes the result so that bullet damage ends at exactly 1 - BulletDefence of the pre-armour amount.

They are not redundant. Defence answers "how tough is this against everything", BulletDefence answers "how tough is this against gunfire specifically", and PenetrationResistance answers "which projectiles does this actually defeat, and does it stop them or let them carry on to the next target".

Penetration Loss And Damage

Every deduction is recorded with its cause. A projectile can optionally lose damage in proportion to the budget it has spent:

Key Format Default Applies to loss from
PlayerPenetrationDamageEffect float 0.0 Players
EntityPenetrationDamageEffect float 0.0 Other living entities
BlockPenetrationDamageEffect float 0.0 Blocks
PenetrationDecayDamageEffect float 0.0 In-flight decay

0 means that cause does not affect damage and 1 means damage falls by the same fraction of the initial budget that was spent on it. For each recorded loss:

lostFraction = loss / initialPenetratingPower
damage      *= 1 - lostFraction * effectOnDamage

initialPenetratingPower is the value this specific shot was fired with, so on kinetic ammunition it already reflects the round's mass and the firing weapon's velocity.

This channel is independent of useNewPenetrationSystem. Setting PenetrationDecayDamageEffect 1 together with a PenetrationDecay is the simplest way to give a weapon realistic damage falloff with range.

Hitmarkers

The shooter receives the largest damage multiplier produced by the shot. Against vehicles this is the fraction of budget that survived the part, and against players it is the armour damage multiplier, which is pinned at 1.0 while useNewPenetrationSystem is disabled.

Global Settings

File: config/flansmodultimate-common.toml

["Penetration System Settings"]
    useNewPenetrationSystem = false
    enableBlockPenetration = false
    blockPenetrationModifier = 0.0
    kineticPenetrationReference = 0.087
    blocks = []
Setting Default Purpose
useNewPenetrationSystem false Makes player armour participate: PenetrationResistance scales damage and sets the budget cost of a player hit.
enableBlockPenetration false Lets projectiles spend budget to pass through blocks.
blockPenetrationModifier 0.0 Default block penetration power. BlockPenetrationModifier on a bullet overrides it.
kineticPenetrationReference 0.087 Scaling coefficient of the kinetic penetrating-power formula.
blocks [] Per-block hardness and break-on-penetration overrides.

These settings are synchronized from the server to clients.

Raising kineticPenetrationReference makes every kinetic round punch through more targets and blocks and shrug off more armour. Lowering it makes armour dominate. It changes nothing for ammunition without a Mass.

What Each Setting Actually Gates

The budget itself is always live. The table below is the honest summary of what is and is not switched off by default.

Behaviour Requires a setting
Projectile stops when the budget runs out No
In-flight decay No
Kinetic derivation of the initial value No, only a projectile Mass
Flat 1.0 cost per non-player entity No
Shield absorption cost No
Damage loss from the *DamageEffect keys No
Vehicle-part resistance cost and legacy-health damage scaling No
Player armour scaling damage useNewPenetrationSystem
Player armour setting the budget cost of a hit useNewPenetrationSystem
Projectiles passing through blocks enableBlockPenetration

Tuning Guide

Suggested armour resistance values, expressed as a multiplier of the unarmoured value of the slot. Multiply the multiplier by the slot's unarmoured value from the table above to get the number to write.

Protection class Multiplier Helmet Chestplate Leggings Boots
Cloth garment, uniform, cap 1.00 1.00 1.00 0.65 0.35
Leather, padded, gambeson 1.05 1.05 1.05 0.68 0.37
Fragmentation shell: steel combat helmet, flak vest 1.15 1.15 1.15 0.75 0.40
Soft ballistic: aramid vest, composite helmet 1.35 1.35 1.35 0.88 0.47
Hard plate: rifle-rated plate, steel breastplate 1.90 1.90 1.90 1.24 0.67
Composite plate, powered armour 2.40 2.40 2.40 1.56 0.84

Cross-referenced against the kinetic table, a fragmentation shell at 1.15 has a threshold of 0.805: it takes a 9x19mm round down to 71% damage and leaves 5.56mm untouched. A hard plate at 1.90 has a threshold of 1.33: it takes 5.56mm down to 54% and 7.62x51mm to 94%, and is overmatched outright by .50 BMG. Composite plate at 2.40 has a threshold of 1.68 and takes 7.62x51mm down to 53%.

Never write a resistance below the unarmoured value of the slot. It would make that armour piece worse than wearing nothing.

Compatibility Summary

  • A pack with no Mass on its ammunition and no PenetrationResistance on its armour behaves exactly as before.
  • Ammunition that declares a Mass derives its penetrating power and ignores any authored Penetration / PenetratingPower. Add Penetrates false to opt a specific round out and pin it to 0.7.
  • Armour that declares no PenetrationResistance is treated as bare skin on that slot rather than as zero resistance, so enabling useNewPenetrationSystem on an existing world never makes existing armour harmful.
  • PenetrationAt100m and the millimetre armour gate are a separate system and are not affected by anything on this page.

See Also

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