Bannerlord mechanics

Weapon stats

Bannerlord v1.4.8 · checked 2026-11-10 · updated 2026-11-12

How a crafted weapon's per-usage stats follow from its pieces, at full precision for game v1.4.8: geometry, grip, handling, speed, damage and the stored stats of each usage. Tier, value and trade price are in Value and price; difficulty, quality and materials in Smithing mechanics; the 12 templates and their example recipes in Weapon templates.

The formulas were recovered from v1.4.6 IL of TaleWorlds.Core.dll and TaleWorlds.CampaignSystem.dll and compared against v1.4.8 IL: no compared method differs, but 38 were compared by header only and 22 are not in the v1.4.8 dumps (see Value and price). The stat code is Crafting.CraftedItemGenerationHelper.CraftingStats in TaleWorlds.Core, a helper rather than a game model, so there is no model for SandBox, StoryMode, NavalDLC or BirthAndDeath to replace. NavalDLC changes only the data: it adds the pieces of naval_crafting_pieces.xml and patches two Native files through XSLT (see Loading). The snippet tables are computed by the Python model in src/bannerlord/crafting/.

The game computes in 32-bit floats wherever it uses float, and integer stats are rounded or floored from those float results, so an exact reproduction needs 32-bit arithmetic. The speed simulation uses doubles, as the game does.

Inputs

Stats, tier, value, difficulty and material costs need only game XML and the formulas on these pages. The constants live in the game DLLs, so a patch can change results without changing XML. Save state enters as parameters:

Pipeline

Six steps left to right for the one-handed sword example. Step 1: choose one piece for each slot, piece tiers 1, 5, 2, 1. Step 2: the pieces set weight and length, weight 0.9, length 97. Step 3: weight, length and balance set the stats, handling 99, swing speed 106, swing damage 21, thrust speed 97, thrust damage 12. Step 4: stats and piece tiers set the tier score, 1.4704. Step 5: the tier score sets the value, 398. Step 6: value and Trade skill set the price, 130 at Trade 86.

The data flow, file by file:

  1. crafting_pieces.xml gives the piece values, crafting_templates.xml the build_order of each slot, and weapon_descriptions.xml each usage's weapon_class, WeaponFlags and AvailablePieces.
  2. Piece values and build_order give weight, weapon_length, center_of_mass and inertia (Geometry).
  3. WeaponFlags select the grip profile (Grip profile).
  4. Geometry and grip profile give handling and swing and thrust speed; the speeds give damage, by weapon_class (Handling, Speed, Damage).
  5. The per-usage stats give tierf, and tierf gives value (Value and price).

An item has one WeaponComponentData per available usage. All usages share pieces and geometry. They differ by weapon_class and WeaponFlags, which select the formula constants.

How a crafted weapon gets its numbers, with the one-handed sword example at each step.

Step What happens Example
1 Choose one piece for each slot Piece tiers 1, 5, 2, 1
2 The pieces set weight and length Weight 0.9, length 97
3 Weight, length and balance set the stats Handling 99, swing speed 106, swing damage 21, thrust speed 97, thrust damage 12
4 Stats and piece tiers set the tier score 1.4704
5 The tier score sets the value 398
6 Value and Trade skill set the price 130 at Trade 86
Slot Piece Tier
Blade wood_blade_1 1
Guard vlandian_noble_guard_3 5
Handle khuzait_grip_7 2
Pommel battania_pommel_6 1

The tier score 1.4704 rounds to tier 1. It mixes 0.8207 from the stats and 2.445 from the pieces.

Variables

Names follow the XML. Lengths are stored in centimeters and loaded in meters.

crafting_pieces.xml, <CraftingPiece>:

XML Loaded value Default
@piece_type Blade, Guard, Handle, Pommel required
@tier int required
@weight kg 0
@length 0.01·length sum of the two distances below
@distance_to_next_piece 0.01·value length/2
@distance_to_previous_piece 0.01·value length/2
@center_of_mass length·value (fraction of length) length·0.5
@full_scale value == "true"; weight scales with scale³ instead of scale true for Guard, Pommel; otherwise false
@appearance float 0.5
BuildData/@piece_offset 0.01·value 0
BuildData/@previous_piece_offset 0.01·value 0
BuildData/@next_piece_offset 0.01·value 0
BladeData/@stack_amount int 1
BladeData/Thrust/@damage_factor float 0 (no <Thrust>: 0 damage)
BladeData/Thrust/@damage_type Cut, Pierce, Blunt Invalid
BladeData/Swing/@damage_factor float 0 (no <Swing>: 0 damage)
BladeData/Swing/@damage_type Cut, Pierce, Blunt Invalid
StatContributions/@armor_bonus int 0
Materials/Material/@id, @count IronOre, Iron1..Iron6, Wood, Charcoal none

inertia has no XML attribute. It is derived at load:

inertia = 0.0833333 × weight × length²

crafting_templates.xml, <CraftingTemplate>: PieceDatas/PieceData/@piece_type, @build_order, WeaponDescriptions/WeaponDescription/@id.

weapon_descriptions.xml, <WeaponDescription>: @weapon_class, WeaponFlags/WeaponFlag/@value, AvailablePieces/AvailablePiece/@id.

Recipes (<CraftedItem>): Pieces/Piece/@id, @Type, @scale_factor (percent, default 100).

Outputs use <Weapon> attribute names from item XMLs: weapon_length (cm), speed_rating (swing speed), thrust_speed, swing_damage, thrust_damage, missile_speed, weapon_balance, accuracy, stack_amount, body_armor. Item XML stores weapon_balance multiplied by 100; here it is 0..1. Thrown weapons store missile damage in thrust_damage. handling, inertia, center_of_mass and sweet_spot are in-game fields without an equivalent crafted-item XML attribute.

Loading

Piece attributes load as follows (CraftingPiece.Deserialize, BladeData.Deserialize):

  1. weight is @weight, or 0.
  2. With @length set: length = 0.01·@length, and both distances are length/2; the distance attributes are not read. Without it: each distance is 0.01· its attribute, and length is their sum.
  3. center_of_mass = length × @center_of_mass, default fraction 0.5.
  4. inertia = 0.0833333 × weight × length², from the loaded weight and length.
  5. full_scale is @full_scale == "true" when the attribute is present; otherwise true for Guard and Pommel.
  6. The three offsets are 0.01· their BuildData attribute, or 0.
  7. stack_amount is BladeData/@stack_amount, or 1, also for a piece without BladeData.
  8. Each of Thrust and Swing gives a damage_factor and a damage_type; a missing element gives factor 0 and type Invalid.
  9. armor_bonus is StatContributions/@armor_bonus, or 0.
  10. Materials are every Material element's @id and @count.

WeaponFlag values used here: MeleeWeapon (0x1), RangedWeapon (0x2), NotUsableWithOneHand (0x10) and WideGrip (0x40). Other values do not enter these formulas.

Files read, with NavalDLC active:

File Content
Native/ModuleData/crafting_pieces.xml Native pieces
NavalDLC/ModuleData/naval_crafting_pieces.xml NavalDLC pieces, added to the Native ones
Native/ModuleData/weapon_descriptions.xml usages
NavalDLC/ModuleData/XSLT/NavalDLC_Native_WeaponDescriptions.xslt adds AvailablePiece ids to Native usages (AvailablePieces)
Native/ModuleData/crafting_templates.xml templates
NavalDLC/ModuleData/XSLT/NavalDLC_Native_CraftingTemplates.xslt adds UsablePiece ids to Native templates (UsablePieces)

NavalDLC data can redefine a Native piece: a piece id in naval_crafting_pieces.xml that is already in crafting_pieces.xml replaces the Native definition. In v1.4.8 the only such piece is sickle_handle_1, whose BuildData/@piece_offset is 13.7 in Native and 0 in NavalDLC (piece_offset 0.137 and 0), so recipes with that handle get different stats with NavalDLC.

Without NavalDLC only the Native files are read. mp_crafting_pieces.xml replaces crafting_pieces.xml in multiplayer.

Scale

scale = scale_factor × 0.01

length, center_of_mass, both distances and all three offsets multiply by scale. weight multiplies by scale³ when full_scale is true, which is the default for Guard and Pommel, and by scale otherwise. Only 38 pieces, all blades, set the attribute explicitly. The game skips scaling when scale_factor == 100. A piece's inertia is not scaled (see Quirks).

Below, scaled x means x × scale of the piece's own scale, and scaled_weight the scaled weight.

Geometry

build_order: 0 is the root (handle), positive pieces stack toward the tip, negative toward the pommel end. Slots without a piece are skipped everywhere.

weight = round2(Σ scaled_weight)

The sum runs in double and is rounded to 2 decimals (.NET Math.Round: ties to even). All later formulas use the rounded weight.

weapon_length = trunc(100 × reach)

reach = round2(max(blade_pivot + scaled blade.distance_to_next_piece, widest_piece))

blade_pivot (WeaponDesign.CalculatePivotDistances): start with top = 0, bottom = 0, pivot = 0, then for each slot in the template's PieceDatas order:

  1. If build_order = 0: top += piece_offset, bottom −= piece_offset.
  2. If build_order < 0: bottom += distance_to_next_piece + piece_offset − next_piece_offset.
  3. If build_order > 0: top += distance_to_previous_piece + piece_offset − previous_piece_offset.
  4. If the piece is the blade: pivot = top, plus the blade's piece_offset when its build_order is 0.
  5. If build_order ≤ 0: bottom += distance_to_previous_piece − previous_piece_offset.
  6. If build_order ≥ 0: top += distance_to_next_piece − next_piece_offset.

All values are scaled. blade_pivot is the final pivot.

widest_piece (WeaponDesign.CalculateWeaponLength): start with widest = 0, then for each piece in the order Blade, Guard, Handle, Pommel: if its scaled distance_to_next_piece is greater than widest, set widest to its scaled distance_to_next_piece + piece_offset. The comparison leaves out piece_offset; the stored value includes it.

center_of_mass = moment ÷ weight − (scaled handle.distance_to_previous_piece − scaled handle.piece_offset)

moment (CraftingStats.CalculateCenterOfMass): start with moment = 0, length_up = 0, length_down = 0, then for each slot in PieceDatas order, with L its scaled length, c its scaled center_of_mass:

  1. If build_order < 0: moment −= (length_down + L − c) × scaled_weight, then length_down += L.
  2. Otherwise: moment += (length_up + c) × scaled_weight, then length_up += L.

inertia = Σ [piece.inertia + scaled_weight × (x + piece.center_of_mass)²]

CraftingStats.CalculateWeaponInertia: x starts at −center_of_mass and, after each piece, advances by the piece's scaled length. The sum walks PieceDatas in XML order, with Pommel last. piece.inertia and piece.center_of_mass are the unscaled loaded values.

inertia_shoulder = inertia + weight × (0.5 + center_of_mass)²

inertia_grip = inertia + weight × center_of_mass²

Grip profile

Only WeaponFlags selects constants; weapon_class does not.

Profile Required flags
OneHanded (One-handed) none of the below (also all thrown usages, which lack MeleeWeapon)
OneHandedWideGrip (One-handed, wide grip) MeleeWeapon, WideGrip
TwoHanded (Two-handed) MeleeWeapon, NotUsableWithOneHand
TwoHandedWideGrip (Two-handed, wide grip) MeleeWeapon, NotUsableWithOneHand, WideGrip

Constants per profile. Two-handed stage values are the base value times the game's multiplier, shown in brackets:

Grip constants for each grip. Swing drag is 3.9 times the grip's drag factor, per unit of reach. Two-handed stage values are the one-handed value times a multiplier.

Constant One-handed One-handed, wide grip Two-handed Two-handed, wide grip
Handling inputs a and b 1, 0.7 0.4, 1 0.5, 0.9 0.5, 0.9
Swing drag per unit of reach 1.17 (3.9 × 0.3) 3.9 (3.9 × 1) 1.17 (3.9 × 0.3) 3.9 (3.9 × 1)
Extra swing load 0 0 1 1.5
Swing stage 2: power and torque 170, 15 170, 15 195.5, 19.5 (170 × 1.15, 15 × 1.3) 195.5, 25.5 (170 × 1.15, 15 × 1.7)
Swing stage 3: power and torque 90, 7 90, 7 121.5, 16.8 (90 × 1.35, 7 × 2.4) 117, 28 (90 × 1.3, 7 × 4)
Extra thrust load 0 0 0.6 0.9
Thrust stage 2: power and torque 170, 24 170, 24 178.5, 26.4 (170 × 1.05, 24 × 1.1) 178.5, 28.8 (170 × 1.05, 24 × 1.2)
Thrust stage 3: power and torque 90, 15 90, 15 108, 28.5 (90 × 1.2, 15 × 1.9) 108, 31.5 (90 × 1.2, 15 × 2.1)
Grip Usages
One-handed Dagger, Dagger (alternative), Javelin, Mace, One-handed axe, One-handed axe (bastard grip), One-handed axe (bastard grip, alternative), One-handed sword, One-handed sword (bastard grip), One-handed sword (bastard grip, alternative), Throwing axe, Throwing knife, Thrown polearm
One-handed, wide grip One-handed polearm, One-handed polearm (javelin alternative), Two-handed polearm (couchable)
Two-handed Two-handed axe, Two-handed mace, Two-handed sword
Two-handed, wide grip Pike, Two-handed polearm, Two-handed polearm (bracing)

Table of the 8 grip constants for the 4 grips, one-handed, one-handed wide grip, two-handed, two-handed wide grip. Handling inputs a and b: 1, 0.7; 0.4, 1; 0.5, 0.9; 0.5, 0.9. Swing drag per unit of reach: 1.17; 3.9; 1.17; 3.9. Extra swing load: 0; 0; 1; 1.5. Swing stage 2 power and torque: 170, 15; 170, 15; 195.5, 19.5; 195.5, 25.5. Swing stage 3: 90, 7; 90, 7; 121.5, 16.8; 117, 28. Extra thrust load: 0; 0; 0.6; 0.9. Thrust stage 2: 170, 24; 170, 24; 178.5, 26.4; 178.5, 28.8. Thrust stage 3: 90, 15; 90, 15; 108, 28.5; 108, 31.5.

Handling

handling = round(100 × (1 ÷ (a × inertia_grip + b))^0.55)

Speed

Each raw speed is a constant divided by 0.33 × Σ t, with t the simulated time of three stages. Every stage accelerates a load through an angle in steps of dt = 0.01 s, starting from v = 0.01, angle = 0, t = 0, and repeats while angle is below the stage angle:

  1. torque = min(power ÷ v, max_torque); swing stages subtract drag × v.
  2. v += dt × torque ÷ load.
  3. angle += v × dt.
  4. t += dt.

Swing stages run to angle 1.5 with dt the float 0.01 widened to double (0.009999999776482582); thrust stages run to angle 0.6 with the double 0.01.

Stage Swing Thrust
Load L inertia_shoulder + 0.9 + extra swing load 1.8 + weight + 0.2·inertia_grip + extra thrust load
1 power 200, max torque max(1, 27 − L), load L + 2 power 250, max torque 48, load L + 4
2 stage 2 power, max torque max(1, stage 2 torque − L), load L + 1 stage 2 power, max torque stage 2 torque, load L + 2
3 stage 3 power, max torque max(1, stage 3 torque − L), load L + 0.5 stage 3 power, max torque stage 3 torque, load L + 0.5

raw swing speed = 20.8 ÷ (0.33 × Σ t)

raw thrust speed = 3.85 ÷ (0.33 × Σ t)

The thrust constant is the double 3.8500000000000005. Both results are stored as float.

speed_rating = floor(raw swing speed × 4.5454545)

thrust_speed = floor(raw thrust speed × 11.7647057)

Damage uses the raw speeds, not these integer ratings.

weapon_balance = clamp((raw swing speed × 4.5454545 − 70) ÷ 30, 0, 1), rounded to 2 decimals.

Damage

Swing magnitude is the energy transferred by an inelastic hit at impact point p along the blade, with ω the raw swing speed, then maximized over impact points (CombatStatCalculator):

  1. a = reach × p − center_of_mass
  2. v = ω × (0.5 + center_of_mass)
  3. J = (v + ω × a) ÷ (1/weight + a²/inertia)
  4. v' = v − J/weight
  5. ω' = ω − J × a/inertia
  6. strike(p) = 0.067 × (½·weight·v² + ½·inertia·ω² − ½·weight·v'² − ½·inertia·ω'² + 0.5)

Impact points: the step is clamp(0.4 ÷ reach, 0, 1). A start point begins at 0.93 and drops by 0.05 while above 0.5. Each start samples 5 points, p = start + i/4 × step for i = 0..4, and stops sampling at the first p ≥ 1. Swing magnitude is the largest strike(p) sampled.

thrust magnitude = 0.125 × ½ × (weight + 2.5) × raw thrust speed²

thrown magnitude = 0.125 × ½ × weight × raw thrust speed²

Thrown thrust omits the 2.5; ThrowingKnife and Javelin damage use this thrown magnitude.

Damage by weapon_class, rounded half to even:

weapon_class swing_damage thrust_damage
Melee classes swing magnitude · Swing@damage_factor thrust magnitude · Thrust@damage_factor
ThrowingAxe 0 2 · swing magnitude · Swing@damage_factor
ThrowingKnife 0 3.3 · thrown magnitude · Thrust@damage_factor
Javelin 0 9 · thrown magnitude · Thrust@damage_factor

The damage factors and types are the blade's. swing_damage_type and thrust_damage_type are the blade's Swing and Thrust damage types.

sweet_spot: the impact point p = 0, 0.01, …, 0.99 with the largest strike(p); on a tie the first one counts (CraftingStats.CalculateSweetSpot).

Per-usage stats

Thrown constants, from CraftingStats.SetWeaponData and CalculateMissileSpeed:

weapon_class accuracy missile_speed
ThrowingAxe 93 floor(3.2 · raw thrust speed)
ThrowingKnife 95 floor(3.9 · raw thrust speed)
Javelin 92 floor(3.6 · raw thrust speed)

Melee usages have accuracy 0 and missile_speed 0.

A usage is available when every piece of the design is in its AvailablePieces. Available usages keep the template's WeaponDescriptions order.

Quirks

Validation

Compared with game-generated exports in data/data_exporter/ (craftable_item_usages*.csv, craftable_items*.csv), computing from current game XML. Exports predate the current build: recipes containing pieces whose XML changed since are skipped (battania_dagger_guard_9, dagger_blade_10, dagger_blade_11, dagger_blade_12, dagger_blade_13, dagger_blade_14, dagger_blade_6, dagger_blade_6_blunt, empire_dagger_guard_5, mace_head_24, mace_head_37, mace_head_38, sickle_handle_1, spear_blade_29). Blank export cells are not compared.

Export coverage:

Files Recipes Scale
craftable_items.csv, craftable_item_usages.csv 627,602, from up to 20 random pieces per slot per template 100 only
craftable_items_with_scale.csv, craftable_item_usages_with_scale.csv 1 per template 90..110 on every slot

Unscaled rows left after skips use 461 of 954 pieces. 11 of the 12 scaled recipes remain: Throwing Knife uses the changed dagger_blade_11. mace_head_10 and mace_head_34 cover explicit full_scale blades.

Random sample of up to 500 per template and usage:

Stat Unscaled Scaled
weight 10098/10098 8264/8264
weapon_length 10098/10098 8264/8264
center_of_mass 10098/10098 8264/8264
total_inertia 10098/10098 8264/8264
handling 10098/10098 8264/8264
weapon_balance 10098/10098 8264/8264
sweet_spot 10098/10098 8264/8264
speed_rating 5818/5818 4441/4441
thrust_speed 5961/5961 4882/4882
swing_damage 5818/5818 4441/4441
thrust_damage 5961/5961 4882/4882
thrust_damage (thrown) 2012/2012 941/941
missile_speed 2012/2012 941/941
accuracy 10098/10098 8264/8264
body_armor 10098/10098 8264/8264

Tolerances: weight, weapon_balance, sweet_spot ±0.0051; center_of_mass, total_inertia ±0.001; integers exact. Available usages, tier, value and price checks are in Value and price.

Game code

Formula Method
Piece loading TaleWorlds.Core.CraftingPiece.Deserialize, BladeData.Deserialize
Geometry WeaponDesign.CalculatePivotDistances, CalculateWeaponLength; CraftingStats.CalculateCenterOfMass, CalculateWeaponInertia
Speed, handling, damage Crafting.CraftedItemGenerationHelper.CraftingStats
Impact TaleWorlds.Core.CombatStatCalculator
Usage availability, stack CraftedItemGenerationHelper.GenerateCraftedItem, WeaponComponentData.Init

IL decoded from game v1.4.8:

Changelog