Bannerlord mechanics

Weapon damage

Bannerlord v1.5.7 · checked 2026-11-12 · updated 2026-11-12

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):

Hit state, per hit (AttackCollisionData, AttackInformation):

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)

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

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:

  1. ω = S_swing ÷ 4.5454545 = 106 ÷ 4.5454545 = 23.3200.
  2. Best hit point p = 0.6762: a = L·p − c = 0.4383.
  3. v = ω·(0.5 + c) = 16.7341.
  4. J = (v + ω·a) ÷ (1/m + a²/I) = 26.9556 ÷ 1.7880 = 15.0762.
  5. ΔE = ½·(v + ω·a)·J = 203.1934.
  6. B = 0.067·(ΔE + 0.5) = 13.6475.
  7. M = B·k_swing = 20.4712.
  8. Against armor 40: 8.17 blunt damage.

Thrust:

  1. v = S_thrust ÷ 11.7647057 = 8.2450.
  2. B = 0.125·½·(m + 2.5)·v² = 14.4458.
  3. M = B·k_thrust = 11.5566.
  4. 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)

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:

Changelog