Penetration System - AntonIT99/Flans-Mod-Ultimate-2.0 GitHub Wiki
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.
PenetrationAt100mis compared againstArmorFrontMmand 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.
- The penetrating-power budget
- Where the initial value comes from
- Kinetic penetrating power
- What spends the budget
- Player armour
- Penetration loss and damage
- Hitmarkers
- Global settings
- What each setting actually gates
- Tuning guide
- Compatibility summary
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.
The initial value is resolved once, when the shot is fired, in this order:
-
Penetrates falseforces the legacy default of0.7and disables every derivation below. This is how a definition declares "this projectile does not penetrate". -
A projectile
Massgreater than0, meaning the ammunition uses the kinetic damage system, derives the value from muzzle kinetic energy. See Kinetic penetrating power. This supersedesPenetration/PenetratingPower. -
Otherwise, the authored
Penetration/PenetratingPowervalue is used, defaulting to1.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.
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.
The velocity used is the one the projectile is actually fired at, resolved in this order:
- The
muzzleVelocityMpsfield of the round selected from anAddRoundbelt. - The ammunition's own
BulletSpeedorMuzzleVelocity, multiplied byBulletSpeedMultiplier. - The speed the firing weapon gives the projectile, including grip secondary fire and attachment multipliers.
- The default bullet speed of
3.0blocks per tick, that is60 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.
| 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.
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.
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.
A bullet striking an armoured player passes through four independent stages, in order:
-
PenetrationResistancescales damage as above, then the hitbox multiplier applies. - Vanilla Minecraft mitigation from
ArmorPointsandToughness. -
Defenceis summed across the four worn pieces and removed as a flat fraction of all damage. -
BulletDefencerenormalizes the result so that bullet damage ends at exactly1 - BulletDefenceof 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".
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.
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.
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.
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 |
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.
- A pack with no
Masson its ammunition and noPenetrationResistanceon its armour behaves exactly as before. - Ammunition that declares a
Massderives its penetrating power and ignores any authoredPenetration/PenetratingPower. AddPenetrates falseto opt a specific round out and pin it to0.7. - Armour that declares no
PenetrationResistanceis treated as bare skin on that slot rather than as zero resistance, so enablinguseNewPenetrationSystemon an existing world never makes existing armour harmful. -
PenetrationAt100mand the millimetre armour gate are a separate system and are not affected by anything on this page.