Captain traits, classes and personalities
How a captain's traits are stored, rolled and re-rolled: trait slots and their gate word, the resolver and the rebuild loop, exclusive groups, what a save keeps, the trait card's rows and text markup, and how class, advanced class and personality are represented.
Captains are FactionMember objects (see The Nemesis system). Their strengths, weaknesses, class
bonus, advanced class, gangs and quirks are all traits, held in trait slots and resolved by the
engine from data.
Storage
| FactionMember offset | Meaning |
|---|---|
| +0x1E0 | the trait slot vector {begin, end, u32 capacity at +0x10, u8 owned at +0x14}; a slot is 0x20 bytes (below) |
| +0x198 | the active trait set, a vector of 8-byte Faction/Traits/Definitions pointers (it equals the set of slot traits in 528 of 531 captains) |
| +0x1B0 | the marker vector (Marker_* records) |
| +0x378 | a cache: the first active trait with GangLeader (0x1BC3CB4) |
| +0x508 | derived bits: 14 any CanBypassRequiredDataLevelToSpawn, 19 any NPC summon / gang, 43 a trait that forbids sim events with a boss, 34 the slot list needs sorting, 38 the engine refuses trait edits (0x114B55C) |
A slot is +0x00 the Faction/Traits/Picker, +0x08 the trait it resolved to (null = nothing),
+0x10 the trait a story mission unlocked for it, +0x18 a u16 flag word, +0x1A an i16 option
index (-1 = none). A picker's options are Picker.Traits -> _StructuresFactionTraitPickerEntry
records (.Trait). 493 pickers exist.
The flag word is the gate list and the re-roll mask
When a slot is added, its u16 at +0x18 is copied from the faction system's table FS+0xE0
(indexed by the picker's record index; 18,508 of 18,508 entries match). It is two things at once:
- the gate list - 0x1234E0(&slot+0x18, bit) (
bt) decides which tests the resolver runs: bit 1 minimum level, 2 required row rank, 3 player follower, 4 required other traits, 6 intel level, 7 a campaign predicate, 8 difficulty; - the re-roll mask - the resolver 0x3528C4 first ANDs the rebuild's reason mask with it and,
when the result is 0, returns
slot+0x08unchanged (0x352AB8:xor eax,eax; cmp word [rcx],ax; sete al). A slot is re-decided only by the events whose bit it carries.
Every picker carries bit 0 (493 of 493), so reason 0 re-rolls every slot. No live slot has a flag word of 0 (19,713 slots), so a zero word marks a slot that the engine will never re-roll: the resolver returns its trait for every reason.
The resolver returns the first option whose flagged gates all pass. Options with
MissionUnlock_MinXPLevel set additionally require the level to reach the remapped MissionUnlock -
1 (0x352DB8) and the option's trait to equal slot+0x10 (0x3529A3); +0x10 is 0 on every live
slot. Its option walk: 0x352BB8(&iterator, picker) over Picker.Traits (a 0x30-byte iterator:
+0x28 index, +0x2C count), 0x352B2C the entry at the index, 0x352AC4 the entry's trait, 0x2774A8
"at end".
Adding, removing and rebuilding
- UpdateTraits 0x3528A0(fm, reason) runs the rebuild 0xF88230:
mask = 1 << reason
if FM+0x508 bit 34: sort the slots (comparator 0x7127A8: Picker.SortPriority, higher first,
ties by address), clear bit 34
reason 0: active set emptied (FM+0x1A0 = FM+0x198), FM+0x378 = 0, FM+0x508 bits 14 and 19 cleared,
byte FM+0x51C = 1
per slot: new = resolver(slot, &mask, &ctx)
if new == old and reason != 0: skip
mask |= bit 4 (a trait changed: later slots' required-trait gates re-run)
deactivate old (0x353454), activate new (0x3566B0)
- ApplyReward 0x3511F4 -> 0x351AF8 and five other callers rebuild with reason 0, so every reward re-rolls every slot.
- Add = 0x7CD03C pushes
{picker}(constructors 0x7CD150 / 0x7CD184, push 0x7CD0CC, growth 0x1144628), then UpdateTraits. - Remove = find the slot by picker, 0x353454 (drop it from the active set and re-derive only the bits that trait set; unregister its spawner hijack or catalyst; remove its items from a live body), then erase it (0x7A7378).
- Activate 0x3566B0: gives the trait's items (0x355400 -> 0x355474 per item), registers a spawner
hijack (0x1A32D88) for captains of the current region, and starts a catalyst only when he has a
body. Item 0x355474 checks
RequiredCharacterTypeagainst the type of his combat tree (FM+0x188; 0x30CF30, exclusion 0x36686C) before giving a weapon item (olog flamethrower or harpoon, the power-shot crossbow, grenades). The first loop of 0x355400 writes flair colours into the list atFM+0x118({u8, u32 channel at +4, ARGB at +8}via 0x1C10E54 / 0x1C11A74, seeded byFM+0x168).
Exclusive groups. 51 PickerGroup_* records cover 207 of the 493 pickers
(Picker.ExclusiveGroup). 0x19F66C collects the groups of every slot a captain has, rolled or not,
and 0x7DABFC refuses a picker that shares one. They are of three kinds: design limits
(DLC4_Ultimate 10 pickers, DLC4_Opener 3, BodySlamThroatGrab 2, Mannerisms_Pool 23 audio
mannerisms that never appear on the card); one-of-a-kind features (GangOrPet 13, HasMount 2,
ElementalWeapon 5); and a strength paired with its own opposite (Resist_<element> - immune or
resistant against vulnerable or mortal to one element; every <X>_Fear_Hate; BeastSlayerOrDismount;
AggressiveUnAgressive; LastChanceModifiers; RepositionMoveAway; VaultBreaker;
BodyguardDefeat; HateBossDefeat; KillOtherCaptain; WeakByHate_PinningShot; Alone_Fear;
Blocks).
The first option he qualifies for wins. Picker_GangOfDoppelgangers (flags 0x83: level and a
campaign gate; group GangOrPet; excluded markers Marker_SpecialButterfly, Marker_Deranged)
lists Quirk_HasGang_Doppelgangers_Elite (minimum level 35) before
Quirk_HasGang_Doppelgangers (minimum level 15); both share the card name "Decoys". From level 35
that slot always rolls the Elite trait.
What a save keeps
A save keeps only the slots. The FactionMember loader 0x1BC159C (a keyed tree: Identity ...
Inventory, Traits, PickerLevelRemap) clears FM+0x1E0 (0x1BB9068), re-adds each saved picker
through 0x1BB255C -> 0x7CD03C with no trait, then calls UpdateTraits(fm, 0) unconditionally
(0x1BC3554). Every load therefore re-rolls every slot: only single-option, level-only slots come
back identical, and gated slots are decided again. The loader bypasses the exclusive-group test,
so a state that breaks a group persists. (0x1C12224 is the serializer of the online "searchable"
index, not the save.)
Class, advanced class and personality
Class = FM+0x278 (a Character/Models record) + FM+0x188 (that model's own combat tree) +
one class-bonus slot Picker_BaseStrength_<Infantry | Defender | Archer | Hunter | Savage | Olog>. Six
pairs exist (Orc_Infantry, Orc_Defender, Orc_Archer, Orc_Spearman, Orc_Berserker, OlogHai_Infantry,
each with its AI_<same> tree); the spearman's bonus is Hunter and the berserker's Savage (156 of 156
living row captains follow this).
Advanced class = one slot Picker_BaseStrength_<Assassin | BeastMaster | Berserker | CombatMaster |
Commander | Destroyer | Marksman | Tank | Tracker | Trickster> (155 of 156). All 16
BaseStrength pickers have table flags 1, one option and no exclusive group, so the slot's picker
alone decides it, and it survives a save. AdvancedClass_* and OriginDeathTaunt_* are
Faction/Rewards/Types weight records that no captain holds; *Champion<X> rewards are the
Legendary promotion (rarity picker, an epic trait, a kit, a level and a new role).
Personality is the voice. FM+0x270 is a Faction/Personalities record (120 records; their
ShortName is the voice set: pr, br, tw, wh, cv for orcs, ws, ze, bs for ologs, ea for Easterlings;
empty for nonverbal and undead characters). Base tables live in Faction/PersonalityTable (11 orc, 3
olog, 4 mercenary). The engine's setter 0x1BC60A0(fm, record) (used by the reward SetPersonality
0x3523EB and by role changes that force a personality) removes the old personality's marker traits
(0x79A1D4), writes FM+0x270, re-registers a captain in the world with the new voice (0x73D548:
CCharacter+0x2B28 and AudioHooks/List.CharacterSpawns on [char+0x2808]), then adds the new marker
traits (0x7D3EA4), skipping markers while bit 38 is set. 0x7D3EA4 refuses a marker whose exclusive
group is already held (every PersonalityMarker_* is in MarkerGroup_Personality), and the setter
removes only the markers of the personality it replaces.
The trait card
The card's rows are Interface/Menu/MenuTreeNode/TraitGroup records (.TraitGroups, the UI
groups; TraitType 0 / 1 = strengths / weaknesses): Immunities; Hates; Class traits = Classes;
Bonuses = Bonuses + Epics; Mortal weaknesses = MortalVulnerabilities + Terrors; Vulnerabilities;
"Dazed by" = WeakenedBy + Fears; and a hint row of the resistances (MaxVisTraits 3). Miscellaneous
passives and rarity are not drawn. The number under each row's icon is the captain's trait count in
that row.
Icons are Icon strings (interface\textures\hud\traits\enemy\...\x.tex); base icons ship in
interface\bundles\ingame_navmarkers.bndl. A trait's PreviewBundle holds only its preview movie.
43 Easterling trait icons of the fourth DLC live in a single patch bundle; when it is not loaded the
card's request is registered with no data and draws a placeholder.
Text. The localization strings carry the card's markup (63,493 strings):
- colour runs
{Color_<Alias>}text</font>(Enemy 593, Player 498, Objective 305, Broken 46, Intel 43, Epic 35, Follower 16 ...); among trait names only Epic, which wraps 35 names whole ({Color_Epic}Epic Trait: Slayer Counter</font>); - font runs
<font face="$INTHUD">...</font>, glyph and button tokens ({Poison},{CombatPrimary03},{EpicSkull}), format arguments ({1!s},{2!d}) and voice cues (<laugh>).
A colour alias is a Profile/ActionAlias record (Color_Epic; GamepadString, KBMString, FontColor)
whose FontColor links an Interface/Color record with an ARGB Color (Epic 0xFFE24E2C, Red
0xFFA42E06, Green 0xFF359737).
21 card names belong to two or three traits each; the record names' family words tell them apart (Easterling / East, D4RES / DLC, DEMO, Elite, Rare, Six, Archer, CMT for Combat Master, Perm, ET): "Decoys" / "Decoys (Elite)" / the DLC's six-decoy version, "Agile" and its Easterling twin, and so on.
Looks from traits. Of 334 addable traits, none sets ModelHide, BodyModelChange or
AppearanceTags. Only Quirk_Undead changes the body and colours: Body_Scar_BodyReplacement ->
IB_Body_Scar -> AD_Body_Scar.ModelPicker (the scarred orc and olog bodies) plus
I_Am_Undead_Appearance(_OLOG) (flair colours, cataract-eye material overrides, material-set variants
and the undead effect).
Open questions
- What a slot with a zero flag word does across every trait type in play (gang, summon and spawner hijack traits on a captain who is in the world).
- Traits gated on a marker (for example a curse marker) and whether they act when forced.
Shadow of War (Steam), game version 1.21. An unofficial fan research project, not affiliated with or endorsed by Warner Bros. Games, Monolith Productions or Middle-earth Enterprises. Middle-earth: Shadow of War belongs to its owners.