Custom Creature: big creatures in battle
What changes when a creature or race is much bigger than a human: its size, collision and weapons, then the code behind its attacks, formation spacing and banners, and why an area attack needs a cap.
Part of the Custom Creatures guide. Read the XML first, or for a race, the race page.
Version
Measured against Bannerlord v1.5.3. The engine-code findings come from TAOM's reverse engineering of the v1.5.3 game and Modding Kit DLLs, and native crash offsets move with every engine update. Older measurements (v1.4.5 to v1.4.7) are marked where they appear.
The last four sections need a C# module
Size, collision and weapons are data you author in XML, Blender and the Modding Kit. Scripted attacks, area caps, formation spacing and the banner gate each change what engine code does, so XML alone cannot do them: they take a mission logic, a game model override or, for formation spacing and one banner path, Harmony patches. Each of those sections says what you get if you skip it.
Size
The engine has no size on a Monster. A mount takes its size from its Horse item: at build the engine
calls SetInitialAgentScale(0.01 x body_length) (v1.5.3 managed code), which scales the skeleton,
capsules and ragdoll together. 100 is the authored size; TAOM's mumakil, its war elephant at 3x, is
300. So a mount's Monster capsule is written at 1x: the mumakil's, radius 1.1 in its XML, is about
3.3 in game.
body_length does not scale the rider
The managed code reads as if it would (the Horse slot and ArmorItemEndSlot share an index, and
the scale block has no mount check), but the trace ends in native code, and in game the rider is
not scaled: a 3x mumakil's rider stands at 1x beside its 1x crew. Older advice says the
opposite.
Anything your own code places against the creature (an attack's reach, a seat offset) does not scale
by itself: read Agent.AgentScale and multiply. The exception is a prefab with a physics body or a
navmesh: TAOM builds its mumakil tower at final size, having found no vanilla one scaled at runtime.
A race is scaled by its skin in skins.xml: TAOM's cave troll, on human_skeleton with
min_scale="1.9", spawns at about 1.9x. Whether that scale grows the Monster's capsule as
body_length does is not established; the cave troll kept Native's 0.37 capsule and fought.
A race on its own skeleton carries its size in the skeleton, so AgentScale alone under-reads
it. TAOM takes body size as AgentScale times the Monster's standing_eye_height over the
human's 1.70. TAOM's hill troll, a race on its own skeleton, has an eye height of 3.58;
AgentScale alone would have given it about half the cave troll's reach.
Items.xsd requires body_length on <Horse>, and a module cannot extend engine schemas. TAOM's
per-Monster size is its own attribute, copied into each Horse item by its code: one approach, not an
engine feature, untested in game.
Three collision layers
A creature collides in three places. Two live in its skeleton, one record per bone, inside the tpac (the binary package the Modding Kit, TaleWorlds' asset editor, builds from your FBX):
| Layer | Where it lives | What it does |
|---|---|---|
| Body capsule | <Capsules><body_capsule> in the Monster XML |
agents bump into it and path around it |
| Hit capsule, per bone | CollisionPosition1/2, CollisionRadius, a body zone |
weapons and missiles strike it |
| Ragdoll capsule, per bone | RagdollPosition1/2, RagdollRadius, masses, joints |
the corpse only |
When someone says the collision is too small, ask first: do units walk into it, or do blows pass through it? Walking in is the body capsule; blows passing through are the hit capsules.
Not verified in game
That weapons meet the hit capsule, not the ragdoll one, is inferred from the field names and the
body zones, which match the BoneBodyPartType a blow reads
for its armour zone. No in-game hit test has confirmed it on any TAOM creature.
The Kit's default hit capsule is almost nothing
A skeleton nobody authored gets a rod along each bone's local x from 0.1 to 1.0 of its length, radius a tenth of that length (the tell: a radius about a ninth of the capsule's length). Nothing looks broken. The share of the skin inside any hit capsule:
| Skeleton | Default hit capsules | Skin covered | After fitting to the mesh |
|---|---|---|---|
| TAOM's war elephant | 41 of 60 | 48% | 98.2% |
| TAOM's giant spider | 40 of 62 | 49.9% | 97.2% |
| TAOM's chariot (two horses and a cart on one skeleton) | 57 of 60 | 8.8% | 98.0% |
| the warg (Byak0's Alliance.Wargs) | 2 of 49 | not measured | not refitted |
The warg's other 47 capsules were already authored, so TAOM did not refit it and has no coverage figure for it.
The fit rules that mattered: let a capsule stand at most 20 cm past the skin (90th percentile) and leave the rest to its neighbours; try its ends at the skin's extent; take its axis from the bone, since a fixed local x lies across every limb of a rig exported along +Y.
Optional helpers: export_skin_for_capsules.py
exports the skin from headless Blender, and
skeleton_hit_capsules.py
fits capsules to it and patches the tpac (the Kit's skeleton editor does it by hand). After an
offline patch, save the module once in the Kit so it re-cooks the package's
RuntimeDataCache
entry; whether the game reads the capsules from the package or from that entry is not established.
Size the body capsule from the mesh
This is for a creature or race not built like a human, such as a race on its own skeleton: measure
the mesh's shoulders and hips rather than scaling a human capsule. On human_skeleton, TAOM's cave
troll kept the human's and fought (size). The hill troll got the human capsule scaled by
height (radius 0.82), but it is about twice as broad for its height (2.43 m shoulders) and
neighbours stood inside each other. TAOM's monsters.xml now gives it half the shoulders:
The mumakil first shipped the elephant's capsule; enemies ran into the visible mesh before meeting it. A platform needs the opposite care: the war elephant's capsule stays under its howdah floor (3.2 m at 1x), since a floor inside it is the leading, unconfirmed cause of its sliding.
Check it in a Custom Battle: swing at and shoot the neck, legs and haunches, and walk infantry
into it. For a scripted sweep, Mission.RayCastForClosestAgentsLimbs (v1.5.3) returns the agent
and bone a ray hits; TAOM has not used it yet.
Weapons for a large race
Crafted weapons store no damage. The engine simulates swing speed and damage from each piece's
geometry and weight (TaleWorlds.Core.Crafting). TAOM's hill troll hammer, a longer, heavier clone
of the cave troll's two-handed mace, came out at 23 Blunt and swing speed 12 against the mace's 86
and 28; with the head's weight cut from 1.23 to 0.875 it came out at 86 and 28. Those figures come
from TAOM's Python copy of the engine's crafting simulation, not from the in-game tooltip. See
Weapons.
- When a crafting piece has a
length, the engine sets both its piece distances to half of it, sodistance_to_previous_pieceis dead data. - A
body_namethat no package ships hangs the preload: save the weapon's meshes andbo_physics body in the Kit before any item names them.
A race on its own skeleton holds items on grip bones: TAOM's hill troll Monster names r_finger0 in
main_hand_item_bone and l_finger0 in off_hand_item_bone. Clips do not need them; weapons do
(the hammer sits in the troll's hand in game). A grip bone carried from a donor's proportions can miss
the fist (see the race's skeleton).
Armour follows the skeleton too: armour rigged to human_skeleton floats on another rig.
Optional helper: melee_catalogue.py
runs that simulation over every crafted weapon in a Modules folder without launching the game,
read-only.
Scripted creature attacks
Needs C#, no Harmony. The engine gives a mount no attack beyond charge damage and its usage
set's kick_action, which it fires by itself: vanilla's horse set fires act_horse_kick at
whatever stands behind the mount, and the kick clip's combat parameter, horse_kick_params, drives
the engine's own hit detection. A new creature's usage set can name its own kick action, but no TAOM
test records one firing or landing: treat that route as unverified. An authored attack clip does
not attack by itself: a bite or trample is code that plays the action and applies a blow. Skip
this section and a mount fights by charge damage and, at most, its kick. A race's swings use the
engine's melee instead: see
melee attack clips.
The pattern behind every TAOM creature:
- Attach per agent, by Monster. A mission logic attaches a behaviour
tree to every agent whose
Monster.StringIdmatches, on the first tick and again fromOnAgentBuild, because Custom Battle deployment spawns agents later. A mount agent has no character to key on. The tree layers on the engine's mount AI and needs no Monster flag (neverCanAttack). - Derive it from
MissionLogic. The engine code (read on v1.4.5, unchanged on v1.5.3) files a behaviour reportingMissionBehaviorType.Logicunder its logics with anas MissionLogiccast; one that is not aMissionLogicbecomes a null there, andCheckMissionEndedthrows. - Resolve actions once at mission start and log any that come back
act_none. On channel 0, the full-body channel locomotion uses,act_nonefreezes the legs while the agent slides. Never touchActionIndexCachebefore the engine has loadedaction_types.xml: on v1.4.7 an early touch made its static constructor bake every value to -1 for good. - Request with a priority that wins, and hit on the measured frame. The Kit's default priority 0
can lose to locomotion in battle (TAOM's reading;
why);
SetActionChannelcan replace it per request. Deliver the blow at the clip progress where the weapon lands (0.58 on TAOM's troll smash). TAOM's war ram, a horse-skeleton reskin that dwarves ride, with one authored head-butt clip, logged 1,300 head-butts whose clip never showed: a damage log proves nothing about the animation. - Damage written as already calculated skips armour. TAOM's creature blows set
InflictedDamagewithDamageCalculated = true: 976 ram head-butts on armoured trolls averaged 23.1, the middle of the raw 18 to 28 roll. A Blunt killing blow needsCanKillEvenIfBluntor it only wounds. Give the blow to the rider while there is one. - Take strike bones from the creature's own skeleton. The warg's bone indices reused on the spider connected 2 of 75 attacks.
- Agent slots are reused and some callbacks run off-thread. Never key on
Agent.Index, recheck a held agent's identity, and write engine state on the main thread. - Mount lock. Override
AgentStatCalculateModel.CanAgentRideMountto refuse the creature and set itsMountDifficultyto 999; the assigned rider's spawn ignores both. Pattern from Artem's ADOD_Beasts; no in-game "cannot steal it" test is recorded. - The rider's
default_groupdecides whether it closes. AsHorseArcherTAOM's elephant skirmished and never trampled; asCavalryit charges. - Riderless is possible, unsupported.
Mission.SpawnMonsterbuilds one, andAgent.SetTeamand theFormationsetter field it (ADOD_Beasts' riderless wolves work this way), with no engine AI or roster support. If your creature carries a rider, make it a mount: see the index's warning against detached non-mount agents.
Cap area attacks
Needs C#. The engine has no area melee (one swing, one target), so the attack and its cap are both code. Skip the cap and a scripted area hit multiplies across a stack: before its cap, one troll smash cleared a whole infantry block. The war ram first swept a 3.5 m knockdown on a 6 s cooldown; fifteen to a lord's party, it played as overpowered. The radius went to 2 m and the cooldown to 10 s, a battle log still showed several victims per butt, and the butt now hits one enemy. Budget an area attack by how many of the creature a battle fields.
| Attack | Shape | Status |
|---|---|---|
| War ram head-butt | the enemy it faces most squarely within 2 m (a rider, never his horse) | single-target version not yet seen in game |
| Elephant and mumakil trample | radial, a fixed radius set by hand for each size: 4 m on the elephant, 12 m on the 3x mumakil | fought in battles |
| Troll smash | capped: rolls 1 to 5, hits that many nearest the impact | current tuning, not seen in game |
The capped pattern, as TAOM's troll smash does it:
- Gather the enemies within 3.5 m of an impact point 1.8 m ahead (
Mission.GetNearbyEnemyAgents), skipping mounts (or take the rider, as the ram does). - Roll this smash's cap from
MBRandom.RandomFloat, evenly from 1 to 5. - Sort by distance from the impact and hit the nearest N; the rest are untouched.
- Full damage inside 1.5 m, falling to about a ninth at the edge; a shield-blocking victim
(
GetCurrentActionType(1)isDefendShield) takes half and stays up. - Multiply every distance by the attacker's body size, capped at 3 (three times the human's size).
Check it by logging each smash's rolled cap and victims in a battle.
Not verified in game
The troll smash cap and its numbers are TAOM's current tuning: unit-tested, not yet seen in a battle.
Formation spacing
Needs C# and Harmony. The engine spaces every foot unit for a 0.76 m human:
Formation.UnitDiameter is BipedalRadius times two, whatever the Monster, so no Monster or race
XML widens it. Skip this section and broad units stand inside each other, as TAOM's trolls did. One
approach that worked, as Harmony patches:
- A postfix on the
Formation.UnitDiametergetter returns a wider width once broad units are a tenth of the formation, the share at which the engine code spaces a formation for horses. Past it every unit is spaced wide, humans beside the trolls included. - The width comes from the mesh: LOD0 shoulder width times
AgentScale, capped at 4 m. TAOM's cave troll gets about 1.43 m, the hill troll about 2.7 m. - Compute on the main thread, read anywhere. The getter runs on other threads too, so the
postfix only reads a thread-safe table. Key it by reference:
Formation.GetHashCodereadsTeam, null on the layout copies of step 5. - Rebuild the cached slots when the width changes (
Formation.OnUnitAddedOrRemoved(), thenArrangement.OnFormationFrameChanged(updateCachedOrderedLocalPositions: true)), and during deployment replay the tail ofFormation.OnMassUnitTransferEndso placed units jump to the new slots. - Let layout copies borrow the width. The order preview, deployment placement and spawn frames
lay a formation out on a team-less copy through
GetUnitPositionWithIndexAccordingToNewOrderandGetUnitSpawnFrameWithIndex; a prefix and finalizer there name the real formation for the call.
Check it on the deployment screen: broad units should stand apart, as in TAOM's final battle
test. Known gaps: the custom-width order preview reads one width outside that scope (not verified in
game), and the deployment plan sizes spawn areas with the static Formation.GetDefaultUnitDiameter.
See Formations.
Banners, and other jobs meant for humans
Needs C#, and one Harmony patch. Agent.IsHuman tests the humanoid flag, not the human race,
so every IsHumanoid="true" race can bear a banner and do every other job gated on IsHuman. Gate
the job, not IsHuman: here, with banner-bearer model overrides, since a kit
can keep only one door shut. Skip it and the race carries standards: TAOM's hill trolls raised them
in a Custom Battle while only the campaign's model was gated.
The campaign and Custom Battle use different banner-bearer models: Custom Battle builds
CustomBattleBannerBearersModel on a BasicGameStarter, so an override of the campaign's
SandboxBattleBannerBearersModel never runs there. A race gate has four doors to close:
CanAgentBecomeBannerBearer, which picks bearers at deployment.CanAgentPickUpAnyBanner, for a dropped banner. The fetch never asks door 1, and vanilla's version checksIsHuman, not the race.- The item-pickup tick.
DefaultItemPickupModelaccepts the agent's own formation banner into an empty slot without asking door 2. The tick runs for an agent that lost its spawn shield or holds a consumable stack at half or less, so a big race's kit with a shield or any consumable, arrows included, reopens it. - The reinforcement spawn.
MissionBattleSideSpawnContext.SpawnTroopschecks only!IsHerobeforeSpawnBannerBearer; closing it takes a Harmony patch.
Check it in a battle with reinforcements. In TAOM's final battle test no troll carried a banner; kits with no shield or consumable keep door 3 shut.
Next
A humanoid race on its own skeleton for the trolls' skeleton and grip bones, and troubleshooting when something fails.