Weapon damage
How a melee swing, a melee thrust and a missile turn weapon stats into the strike magnitude that armor then reduces, at full precision. The IL was read from game v1.5.7 and compared with v1.4.8 (see Game version). Damage after armor is on Armor and damage.
The campaign uses SandboxStrikeMagnitudeModel (SandBox). NavalDLC's NavalStrikeMagnitudeModel delegates swing, thrust, missile and ComputeRawDamage to it and replaces only CalculateAdjustedArmorForBlow (armor perks). StoryMode and BirthAndDeath add no strike model. The magnitude code itself is MissionCombatMechanicsHelper and CombatStatCalculator, which no module replaces. The numbers in the examples are computed by the Python model in src/bannerlord/weapons/strike.py.
At a glance
| Step | Game code | Rule |
|---|---|---|
| Base magnitude, swing | CalculateBaseMeleeBlowMagnitude, CombatStatCalculator.CalculateStrikeMagnitudeForSwing |
kinetic energy lost when the blade stops at the hit point, best of 5 points over the next 0.4 m of blade |
| Base magnitude, thrust | same, CalculateStrikeMagnitudeForThrust |
0.125·½·(weight + 2.5)·speed² |
| Scaling | ComputeBlowMagnitudeMelee |
momentum, arm or non-tip penalty, weapon skill |
| Damage factor | ConvertBaseAttackMagnitude |
times SwingDamageFactor or ThrustDamageFactor, clamped to 0..500 |
| Missile | SandboxStrikeMagnitudeModel.CalculateStrikeMagnitudeForMissile |
(speed ÷ starting base speed)² · missile total damage |
| After armor | ComputeBlowDamage, ComputeRawDamage |
Armor and damage |
The stored swing_damage and thrust_damage of an item do not enter the melee magnitude. The magnitude is recomputed at every hit from the weapon's physics fields and its damage factors.
Symbols
Weapon fields, per usage (WeaponComponentData, ItemObject):
m:ItemObject.Weight, kg.L:GetRealWeaponLength()=weapon_length ÷ 100plus the weapon frame's origin along its blade axis, m. The frame is the identity for crafted usages unless the usage holds the weapon at its centre of mass.I:TotalInertia.c:CenterOfMass, m from the grip.S_swing,S_thrust:SwingSpeed(speed_rating) andThrustSpeed, after the item modifier (ItemModifier.ModifySpeed).k_swing,k_thrust:SwingDamageFactor,ThrustDamageFactor.
Hit state, per hit (AttackCollisionData, AttackInformation):
P: progress effect,SpeedGraphFunction(AttackProgress), 1 at full swing speed.p: hit point as a fraction of the weapon,clamp(CollisionDistanceOnWeapon, −0.2, L + z) ÷ (L + z), withzthe attacker's current weapon offset along the arm.u: extra linear speed from movement.μ:momentumRemaining, below 1 after the blow has already passed through something.h: hit multiplier, see Scaling.s: weapon skill multiplier,AgentStatCalculateModel.GetWeaponDamageMultiplier.
Where the factors come from
| Item | I |
c |
k_swing, k_thrust |
|---|---|---|---|
Crafted (<CraftedItem>) |
the design's inertia about its centre of mass | the design's centre of mass | the blade's Swing and Thrust damage_factor, rounded to 2 decimals |
<Item> with a melee <Weapon> |
0.05·m |
0.5·weapon_length ÷ 100 |
both 0.8·{0.5 cut, 0.4 pierce, 1.0 blunt} by the swing damage type |
Crafted values are set by CraftingStats.SetWeaponData through WeaponComponentData.Init; inertia, centre of mass and the blade factors are defined on Weapon stats. <Item> values are set by WeaponComponentData.Deserialize and SetDamageFactors; SetDamageFactors also computes a weight-and-length term that it never stores. Bow, Crossbow, Arrow, Bolt, ThrowingAxe, ThrowingKnife and Javelin usages get factors 1.0 instead.
Swing
ComputeBlowMagnitudeMelee → CalculateBaseMeleeBlowMagnitude → SandboxStrikeMagnitudeModel.CalculateStrikeMagnitudeForSwing → CombatStatCalculator.CalculateStrikeMagnitudeForSwing.
ω = S_swing ÷ 4.5454545 · √P
u_s = 0.7·u (perk-scaled, see below)
w = clamp(0.4 ÷ L, 0, 1)
q_i = a0 + i/4 · (a1 − a0), i = 0..4, a0 = min(0.93, p), a1 = min(0.93, p + w)
B = max(0, strike(q_0), …, strike(q_4))
strike(q), the energy the weapon loses when the hit point q stops dead:
a = L·q − c
v = ω·(0.5 + c) + u_s
E = ½·m·v² + ½·I·ω²
J = (v + ω·a) ÷ (1/m + a²/I)
v' = v − J/m
ω' = ω − J·a/I
E' = ½·m·v'² + ½·I·ω'²
strike = 0.067·(E − E' + 0.5)
ω: angular speed of the swing;S_swing ÷ 4.5454545is the raw swing speed of crafting.w: the 0.4 m sampling window as a fraction ofL.a: lever arm of the hit point from the centre of mass;0.5 + cis the centre of mass's distance from the shoulder pivot.J: the impulse;E − E' = ½·(v + ω·a)·J.B: base magnitude. Every sampled point is capped at 0.93 of the weapon.
When the attacker is a CharacterObject and u_s > 0, u_s passes through an ExplainedNumber with perk bonuses. Mounted: Riding Nomadic Traditions from the captain. On foot: Two Handed Reckless Charge (two-handed skill weapons), Roguery Dash and Slash, Athletics Surging Blow (own and captain's). Polearm skill weapons, either way: Polearm Lancer from the captain; mounted also own Lancer and the captain's Unstoppable Force. For an item crafted by the player, the result passes through Crafting Sharpened Edge.
Thrust
Same chain, CalculateStrikeMagnitudeForThrust:
v = S_thrust ÷ 11.7647057 · √P + 0.5·u
B = 0.125·½·(m + 2.5)·v² if v > 0, else 0
0.5·u takes the same perks as the swing; Crafting Sharpened Tip replaces Sharpened Edge. Thrown thrusts (isThrown) drop the 2.5; melee hits always pass isThrown = false.
Scaling
ComputeBlowMagnitudeMelee, then ComputeBlowMagnitude:
B' = B·μ·h·s
M = clamp(B'·k, 0, 500) k = k_swing or k_thrust
h:ManagedParametersSwingHitWithArmDamageMultiplier(19) orThrustHitWithArmDamageMultiplier(20) when the hit lands with the arm instead of the weapon;NonTipThrustHitDamageMultiplier(21) for a thrust that is not a tip hit and not blocked by a shield; otherwise 1.s: 1 plus the agent's weapon-skill damage bonus (DefaultSkillEffectsOneHandedDamage,TwoHandedDamage,PolearmDamage,BowDamage,ThrowingDamage) at its effective skill, not below 0. It depends on the skill only, so it does not reorder weapons of one skill.M: the magnitude passed toComputeRawDamage. Shield damage usesB'instead.
The combat log speed bonus is B ÷ B(u = 0) − 1, shown only when P > 0.7 (ComputeSpeedBonus).
Hit state
| Input | Source | From item stats |
|---|---|---|
m, L, I, c, k |
weapon fields | yes |
S_swing, S_thrust |
weapon fields and item modifier | yes |
P |
SpeedGraphFunction(AttackProgress, strike type, direction) |
no: time into the attack, from the engine |
p |
CollisionDistanceOnWeapon |
no: where the blade meets the target, from the engine |
u |
attacker and victim velocity | no |
μ, h |
collision | no |
s, perks |
character | no |
SpeedGraphFunction is piecewise linear in AttackProgress with four ManagedParameters per curve: swing (22..25), overswing, the overhead attack AttackUp (26..29), and thrust (30..33). Below the first maximum point it rises from the zero-progress value to 1; between the two maximum points it is 1; after the second it moves linearly to the one-progress value. The parameter values are in Native/ModuleData/managed_core_parameters.xml, which is not in the repository snapshot.
u is |v_attacker − v_victim| (horizontal) times d, with d the dot product of the blow direction and the normalised relative velocity, raised by 0.2 and capped at 1 when positive.
A reference hit removes the runtime inputs: P = 1, u = 0, μ = h = s = 1, no perks, and p the best point in 0, 0.01, …, 0.93. This reference is a modelling choice, not game behaviour.
Worked example
Crafting example recipe OneHandedSword (v1.5.7 crafting snapshot), usage OneHandedSword, reference hit.
| Input | Value |
|---|---|
m (weight) |
0.9000 |
L (length) |
0.9700 |
I (inertia) |
0.2838 |
c (centre of mass) |
0.2176 |
S_swing (swing speed) |
106 |
S_thrust (thrust speed) |
97 |
k_swing (Blunt) |
1.50 |
k_thrust (Blunt) |
0.80 |
stored swing_damage, thrust_damage |
21, 12 |
Swing:
ω = S_swing ÷ 4.5454545 = 106 ÷ 4.5454545 = 23.3200.- Best hit point
p = 0.6762:a = L·p − c = 0.4383. v = ω·(0.5 + c) = 16.7341.J = (v + ω·a) ÷ (1/m + a²/I) = 26.9556 ÷ 1.7880 = 15.0762.ΔE = ½·(v + ω·a)·J = 203.1934.B = 0.067·(ΔE + 0.5) = 13.6475.M = B·k_swing = 20.4712.- Against armor 40: 8.17 blunt damage.
Thrust:
v = S_thrust ÷ 11.7647057 = 8.2450.B = 0.125·½·(m + 2.5)·v² = 14.4458.M = B·k_thrust = 11.5566.- Against armor 40: 3.85 blunt damage.
Stored damage and the reference magnitude
The crafting swing_damage is the same energy formula at the unrounded raw swing speed, the crafting reach and hit points up to just below 1, rounded to an integer (Weapon stats). In combat the speed is the floored rating, the length is weapon_length ÷ 100 and every hit point is capped at 0.93. The reference magnitude therefore differs from the stored damage by rounding, and falls clearly below it when the best hit point would lie past 0.93, as on the rows below with best p 0.93.
Crafting example recipes (v1.5.7 crafting snapshot), melee usages: stored damage against the reference magnitude M.
| Template | Usage | Swing speed | swing_damage |
Swing M |
Best p |
Thrust speed | thrust_damage |
Thrust M |
|---|---|---|---|---|---|---|---|---|
| OneHandedSword | OneHandedSword | 106 | 21 | 20.5 | 0.68 | 97 | 12 | 11.6 |
| TwoHandedSword | OneHandedBastardSword | 95 | 56 | 55.0 | 0.69 | 92 | 31 | 31.1 |
| TwoHandedSword | TwoHandedSword | 108 | 71 | 71.1 | 0.69 | 99 | 36 | 36.1 |
| TwoHandedSword | OneHandedBastardSwordAlternative | 95 | 56 | 55.0 | 0.69 | 92 | 31 | 31.1 |
| Dagger | DaggerAlternative | 123 | 17 | 16.8 | 0.53 | 99 | 18 | 17.5 |
| Dagger | Dagger | 123 | 17 | 16.8 | 0.53 | 99 | 18 | 17.5 |
| ThrowingKnife | Dagger | 122 | 29 | 28.6 | 0.51 | 98 | 32 | 31.8 |
| OneHandedAxe | OneHandedAxe | 86 | 67 | 65.8 | 0.93 | 93 | 0 | 0.0 |
| TwoHandedAxe | TwoHandedAxe | 85 | 117 | 110.1 | 0.93 | 95 | 0 | 0.0 |
| ThrowingAxe | OneHandedAxe | 118 | 32 | 31.1 | 0.93 | 100 | 0 | 0.0 |
| TwoHandedPolearm | OneHandedPolearm | 19 | 0 | 0.0 | — | 82 | 30 | 30.1 |
| TwoHandedPolearm | TwoHandedPolearm | 66 | 0 | 0.0 | — | 89 | 36 | 35.5 |
| TwoHandedPolearm | TwoHandedPolearm_Couchable | 19 | 0 | 0.0 | — | 82 | 30 | 30.1 |
| TwoHandedPolearm | TwoHandedPolearm_Bracing | 66 | 0 | 0.0 | — | 89 | 36 | 35.5 |
| Pike | TwoHandedPolearm_Pike | 62 | 0 | 0.0 | — | 89 | 40 | 38.9 |
| Pike | TwoHandedPolearm_Bracing | 62 | 0 | 0.0 | — | 89 | 40 | 38.9 |
| Javelin | OneHandedPolearm_JavelinAlternative | 89 | 0 | 0.0 | — | 96 | 27 | 26.4 |
| Mace | Mace | 87 | 62 | 60.7 | 0.80 | 87 | 0 | 0.0 |
| TwoHandedMace | TwoHandedMace | 80 | 83 | 80.2 | 0.93 | 91 | 0 | 0.0 |
Best p is the sampled hit point that gives the swing M; — marks usages with k_swing = 0. <Item> melee weapons (bo_staff, push_fork, torch, horse_whip, flail, flail_b, shackle in the v1.5.7 SandBoxCore, SandBox, StoryMode and NavalDLC XML) use the fixed factors from Where the factors come from, so their stored damage does not predict M.
Item modifiers change S_swing and S_thrust through ModifySpeed. Their damage change (ModifyDamage) reaches only the stored damage sent to the engine in WeaponStatsData and the missile damage bonus; no managed melee magnitude code reads it.
Missile
ComputeBlowMagnitudeMissile → SandboxStrikeMagnitudeModel.CalculateStrikeMagnitudeForMissile:
V = |v_missile − v_victim| (|v_missile| when there is no victim agent)
V' = V + max(0, V − V0)·r
M = clamp((V' ÷ V0)²·D·μ·s, 0, 500)
D:AttackCollisionData.MissileTotalDamage.V0:AttackCollisionData.MissileStartingBaseSpeed.r: Throwing Running Throw bonus for a hero whose missile's ammo class isSling,Stone,ThrowingAxe,ThrowingKnifeorJavelin; otherwise 0.μ,s: as for melee (suses the missile's skill).
Missiles skip ConvertBaseAttackMagnitude: no damage factor. Mission.OnAgentShootMissile passes the launcher's modified thrust_damage as the damage bonus and its modified missile_speed as the base speed, with the ammunition's stats, to the engine; a thrown weapon is its own ammunition with damage bonus 0. The engine fills D and V0; how it combines ammunition damage and the bonus is not visible in managed code. At V = V0, M = D·μ·s.
Damage, shields
ComputeBlowDamage: ComputeRawDamage(type, M, armor, absorbed) (Armor and damage), with the result also multiplied by the victim's VictimAgentAbsorbedDamageRatio (absorbed); armor from CalculateAdjustedArmorForBlow, plus 10 when the hit lands on a shield worn on the back. Unless the blow was blocked by a shield or is fall damage, the result is multiplied by the bone's damage multiplier, the sneak attack multiplier when it applies, and the combat difficulty multiplier. Inflicted damage is ceil of that, clamped to 0..2000.
ComputeBlowDamageOnShield, for a shield with CanBlockRanged that can take damage:
raw = ComputeRawDamage(type, B', shield armor, 1)
| Blow | Multiplier |
|---|---|
| melee cut or blunt | 0.7 |
| melee pierce | 0.5 |
missile, ThrowingAxe |
0.3 |
missile, Javelin |
0.5 |
missile with CanPenetrateShield and MultiplePenetration |
0.5 |
| other missile | 0.15 |
weapon with BonusAgainstShield |
×2 on top |
Then ManagedParameters 54 (ShieldRightStanceBlockDamageMultiplier) when the defender is not in left stance, 55 (ShieldCorrectSideBlockDamageMultiplier) on a correct-side block, and AgentApplyDamageModel.CalculateShieldDamage. Crush-through is decided in GetDefendCollisionResults, not covered here.
Attack speed
S_swing and S_thrust enter the magnitude only through ω and v above; GetDefendCollisionResults reads them again for blocked attacks, not covered here. How long an attack takes is decided by the engine: MissionWeapon.GetWeaponStatsDataForUsage sends SwingSpeed, ThrustSpeed, Handling (as DefendSpeed), SweetSpotReach, length, balance, damage and frame to it in WeaponStatsData. No method in TaleWorlds.MountAndBlade, SandBox or NavalDLC turns them into an animation rate. The agent side is managed: SandboxAgentStatCalculateModel.UpdateHumanStats starts SwingSpeedMultiplier and ThrustOrRangedReadySpeedMultiplier at 0.93 and HandlingMultiplier at 1, then SetWeaponSkillEffectsOnAgent adds OneHandedSpeed, TwoHandedSpeed or PolearmSpeed by skill, and perks and the mounted penalty adjust them. These multipliers are per agent and skill, not per weapon.
The crafting raw swing speed is 20.8 ÷ (0.33·Σt) and the raw thrust speed 3.85 ÷ (0.33·Σt), with Σt the simulated swing or thrust time (Weapon stats), so the speed ratings are inversely proportional to a simulated attack duration. Whether the engine's attack duration follows the same proportion is not known from the IL.
Game version
The methods on this page were compared between v1.4.8 and v1.5.7 IL, ignoring metadata tokens, offsets and local slot numbers:
| Method | v1.4.8 → v1.5.7 |
|---|---|
CombatStatCalculator (all), CalculateBaseMeleeBlowMagnitude, SpeedGraphFunction, ConvertBaseAttackMagnitude, ComputeBlowMagnitude, ComputeBlowMagnitudeMissile, ComputeSpeedBonus, ComputeBlowDamage, ComputeBlowDamageOnShield, GetAttackCollisionResults |
unchanged |
WeaponComponentData.Init, SetDamageFactors, GetRealWeaponLength; WeaponComponentDataExtensions; MissionWeapon; CraftingStats |
unchanged |
SandboxAgentStatCalculateModel.GetWeaponDamageMultiplier, SetWeaponSkillEffectsOnAgent; NavalAgentStatCalculateModel.GetWeaponDamageMultiplier |
unchanged |
SandboxStrikeMagnitudeModel.ComputeRawDamage |
assert line number only |
SandboxStrikeMagnitudeModel.CalculateStrikeMagnitudeFor{Swing,Thrust,Missile} |
perk calls now pass the attacker's battle environment; physics unchanged |
ComputeBlowMagnitudeMelee |
the couched-lance branch passes the attack information to CalculatePassiveAttackDamage; swing and thrust path unchanged |
CalculateAdjustedArmorForBlow (SandBox, NavalDLC) |
Crossbow Piercer and Throwing Slinging Competitions perk checks reworked |
WeaponComponentData.Deserialize |
a WeaponFlags attribute set to false no longer sets the flag |
SandboxAgentStatCalculateModel.UpdateHumanStats |
new perk checks (Bow Ranger's Swiftness, Crossbow Loose and Move, One Handed Shield Bearer); the 0.93 and 1 starting multipliers unchanged |
Mission.OnAgentShootMissile, MeleeHitCallback, MissileHitCallback, GetAttackCollisionResults |
mission time source, renumbered multiplayer friendly-fire options, a new collision reaction for thrusts against non-enemies that land inside 0.8 of the weapon and the attacker's arm length; damage path unchanged |
Game code
IL decoded from game v1.5.7 with the steps in How the findings were reversed:
- CombatStatCalculator.il: swing and thrust energy.
- MissionCombatMechanicsHelper.il:
GetAttackCollisionResults,ComputeBlowMagnitude*,CalculateBaseMeleeBlowMagnitude,SpeedGraphFunction,ConvertBaseAttackMagnitude,ComputeSpeedBonus,ComputeBlowDamage,ComputeBlowDamageOnShield,DecideSweetSpotCollision. - SandboxStrikeMagnitudeModel.il: the campaign strike model.
- NavalStrikeMagnitudeModel.il: delegation.
- DefaultStrikeMagnitudeModel.il, StrikeMagnitudeCalculationModel.il: the Native model without perks, and the base class.
- WeaponComponentData.il:
Init,Deserialize,SetDamageFactors,GetRealWeaponLength, field getters. - WeaponComponentDataExtensions.il, MissionWeapon.il: item-modifier speeds and damage,
GetWeaponStatsDataForUsage. - CraftingStats.il:
SetWeaponData. - SandboxAgentStatCalculateModel.il, NavalAgentStatCalculateModel.il:
GetWeaponDamageMultiplier,UpdateHumanStats,SetWeaponSkillEffectsOnAgent. - Mission.il:
OnAgentShootMissile,MeleeHitCallback,MissileHitCallback,GetAttackCollisionResults.
Changelog
- v1.5.7 (2026-11-12): Examples recomputed on the v1.5.7 crafting snapshot.
- v1.4.8 (2026-11-11): New page; formulas read from v1.5.7 IL and compared with v1.4.8.