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:

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

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)

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

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

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.