Items: inventory, gear, affixes, gems and skills
How items and gear work: the inventory inside the visual component, the item instance, affixes as the only source of gear stats and their level-indexed values, equip deltas on the player's stat table, gems and sockets, what a save keeps, the item lists combat requires, skill rows and the Skills screen, weapon looks and slots, and gear effects.
The inventory
The player's inventory lives in the visual component: vc = [[base+0x2797808]+0x2348] (vt_0x1FD63F0;
the same object the Skills screen uses, [UIManager+0xB0]). The inventory proper is embedded at
vc+0x3D8; 25 slot vectors sit at vc+0x4D0 + i * 0x18:
| Slot | Content |
|---|---|
| 0 | Melee (sword) |
| 1 | MeleeAux (dagger) |
| 2 | Ranged (bow) |
| 3 | RangedAux |
| 6 | Utility |
| 7 | Tribe |
| 9 | MeleeOffHand |
| 11 / 12 / 22 | cloak, armour, ring (by their contents) |
The slot names are Combat/Predicates/InventorySlot. Index 0 of a slot vector is not the equipped
item; the equipped one carries the equipped byte.
- Owned counts:
vc+0x740 / +0x748, records{+0x00 item, +0x20 u32 count}(714 stacks on a finished save). Count 0x509074, consume 0x4F163C(vc, item, n), give 0x1BCE0C0(vc, item); the general give is 0x1C01E0(vc, item, n).vc+0x3D8+0xF0is an append-only change log, not the counts. - Held test 0x24DF7C(vc, item): bit
recordIndex(item)(0x1898E8) of the held bit set[vc+0x490](0x24EB74); without a bit set it walks the stacks (0x36A17C). 102 callers rely on it (combat, quests, skills).
The item instance (vt_0x1FDABF8):
| Offset | Meaning |
|---|---|
| +0x20 | u32 id |
| +0x24 | u32 level |
| +0x2C | u32 slot / holder index (copied from the equipped-weapon data by 0x1BF3750 / 0x1BF3474 / 0x649790) |
| +0x38 | the Inventory/Items record |
| +0x40 | the Inventory/EquippedWeaponData record (the look) |
| +0x60 | socket objects (a per-item path no shipped item uses) |
| +0x78 | affix instances {+0x00 Affix/Definition, +0x08 f32 value, +0x0C f32 bonus from ModifyOtherAffix} |
| +0x88 / +0x8C | the ability list's capacity / owned byte |
| +0xA8 / +0xA9 | new / equipped |
| +0xB0 | nemesis origin |
| +0xB8 | the upgrade challenge (vt_0x1DC0F70; +0x78 progress / target, the card's "5 / 10") |
| +0xC8 | GUID |
The item destructor (0x1C7DB4 -> 0x114D828) always frees the ability objects and frees the list's buffer only when the owned byte is set. Captain traits that carry items are items of the same class.
Affixes are the stats
A weapon has no stats of its own: Inventory/WeaponData holds only sheath, effects and predicates.
Every number on gear is an affix (Inventory/Affix/Definition, 2,961 records). An affix's value is
FloatIndex.IndexedValues[ItemLevel - 1] of its VariableMin table (10 of 10 checked live). A sword's
"Melee Attack" is the affix 1Sword_Base_Prop_<rarity or set> -> Float_DmgMod_M_FLAT (level 80:
Common 160, Epic 166, Legendary 169); the player's own Float_DmgBase_M_BASE is 0, so the sword's roll
is the whole flat input to the player's melee damage formula (DmgForm_Plyr_Melee).
Equipping adds deltas. 0x40136C(item, apply, owner combat object) calls 0xFE68F0 per affix ->
0x1A105D8(store, variable, +/- (value + bonus)). Unequipping subtracts the current values, so an
affix value changed while the item is equipped leaves the stat table out of step. The engine's own
upgrade (0xFBAAC0, at 0xFBB2EC: strip, level = min(owner level + 1, 80), recompute) removes, changes
and re-applies.
The player's stat table is [[[player+0x26A8]+0x318]+0x60] + recordIndex * 0x50 = {variable
record, +0x08 f32 value}, 622 entries (the Combat/Stat/Variable/Float category; lookup 0x1E53B8).
A save keeps identity, not numbers: id, level, the item, look and affix hashes, the GUID and the nemesis origin; computed values (such as 157.0 or 169.0) occur nowhere in the decrypted save. See Saves for the inventory section's layout.
Gems
GemData.SocketAffixes gives one affix per socket type (red on a sword Float_DmgMod_M_PCT 0.10 -
0.30, green life steal 0.04 - 0.20, white Mirian 0.05 - 0.15, blade critical damage 0.20 - 1.00).
Sockets belong to slots, not items: six live socket objects (vt_0x1FDAED0) at [vc+0x3D8+0x80] =
{entries, 8}, entry {GemSocketType, socket*}; a socket is +0x18 gem, +0x20 type, +0x28 its
affix vector. Constructor 0x1BFB8AC(memory, type, gem), SocketGem 0x1BFBCB8, apply 0x1BFBC50(socket,
0 / 1, owner, GemItemLevel) over the socket's affix vector.
Items that combat requires
The player's combat accepts a held item only if the Combat/Predicates/Item lists name it:
PC_Sword (73 items), PC_SwordOrDagger, PC_ActivateEquip (378), PC_Sword_Or_GroundGlaive,
PC_SwordOrDaggerOrGroundGlaive and Trait_ItemBreaker_IgnoreSwordList. An item on none of these lists
renders and equips, but no attack, combo or dodge starts with it. Items.WeaponType is null on every
player sword and is not the gate for the player.
Skills
A skill row is an Inventory/ActiveItemGroup record (89): Entries[] ->
_StructuresInventoryActiveItemGroupEntry {RequiredItem = the variant's unlock item (held =
bought), ActiveItem[] = the *_SKILL / *_DUMMY items whose possession makes it active}. An unlock
finds its row through Inventory/Items.ActiveItemGroup. These fields have exactly two readers:
- The Skills screen's active test 0x1BCD6DC(inventory, unlock): unlock held -> its row -> the first
entry with
RequiredItem == unlock-> itsActiveItem[0]held. The node state 0x18A1770: 3 active (drawn white), 2 owned, 1 available, 0 locked; the effect pickers 0x19453D0 and 0x19480D4 ask the same test. - The click swap 0x1BC8C94(vc, unlock) (wrapper 0x1BC8C6C(vc, Skills record)): entries with
another
RequiredItemlose their active items, entries with this one gain theirs (inventory events atGM+0x6D18, types 8 and 7). Other callers: the loadout apply 0x1BCC1C4 (from the inventory re-init 0x4F0938), 0x17D9E4C and 0x1B56FA4.
Which move a button runs. The node runner 0x30FD80 tries the current node's links, then the root
list of the tree at comp+0xEF0 in order; each root is resolved to its keyboard variant, validated
(0x310C5C: type, cancel condition, transition; 0x310F10: the input) and PickAttack 0x275730 runs; the
first root that yields an attack wins. An attack's priority (attack cache entry +0x0C) ranks attacks
only inside one node. A skill variant whose moves sit in their own earlier root node therefore hides the
later nodes on the same input: of 49 multi-variant rows, only Quick Throw does this (Rapid Throw, root
107, before the shared node, root 108).
A multi-projectile volley is one data link: AttackData.Multishoot -> Combat/Multishoot (one
projectile per Entries wedge; FanOfKnives = 5), read at every shot by 0x2E8994 (0x6F6510;
ProjectileCluster wins when set).
HUD button prompts: Combat/PromptOverride/Setup -> Override (one per button, CommandID) ->
Prompts[]; the evaluator 0x277650 reads the compiled entry: +0xE8 IncludedItem (any of, 0x276E1C),
+0xF0 IncludedItemAll (all of, 0x6E42A4), +0xF8 ExcludedItem. The link is copied at load
(0x1C0DAE8); the list is walked live. _StructuresQuestPredicates.RequiredInventoryItemAll is the
only other all-of item field.
Weapons and their looks
An orc's weapons are items in its inventory (FM+0x050): the held weapon at inventory+0x38 (item)
and +0x40 (look), the thrown weapon through FM+0x060. The weapon's type is the item's
Inventory/Items.WeaponType; the look (EquippedWeaponData) decides only the model.
- The look's slot.
EquippedWeaponData.ItemSlotis the inventory slot a look belongs to: 0 Melee (581 looks), 1 MeleeAux (407), 2 Ranged (697), 3 RangedAux (8: the olog arquebus, flamethrower and harpoon), 6 Utility (34: everyOrc_Misc_*prop, drawn in the hand), 7 Tribe (15: exactly theOrc_Trb_*/Orc_TrbVis_*looks), out of 1,763. The game always pairs items and looks of the same slot (813 of 813 orc weapons in one census). A tribe weapon is drawn only while a tribal attack draws slot 7. - Where it is drawn and where it rests.
EquippedWeaponData.Model->_StructuresInventoryItemModelActiveNode/InactiveNode: every weapon a class holds is drawn inRightHandand rests in a sheath socket (spearsSocket_Sheath_Spr, archer crossbowsSheath_Archer, defendersSocket_Sheath_Def01, berserkersBer_R_sheath, infantry and ologsL_sheath). Throwables areLeftHandwith no rest socket: they exist only during the throw. Sauron's and the Nazgul's hand weapons rest nowhere. - Classes.
Combat/InventorySetup::Orc_Spearmangives one weapon,Orc_1HR_ThrowingSpear, in the ranged slot: the spear is both the throw and the melee weapon, so spearmen have an empty hand slot. Berserker dual wielding is in the look: looks with anX2suffix draw a model in each hand. - Every orc weapon look resolves the same way (
EquippedWeaponData.Model->_StructuresInventoryItemModel.Models->_StructuresInventoryModel.ModelRecord-> aModel); see Characters for holders and how a drawn weapon is rebuilt.
Gear effects
A legendary gear effect is data wiring around an affix:
Inventory/Affix/Definition
.Buff -> Combat/Buffs a held effect
.Proc -> Combat/Buffs/Procs/Definitions (223) an on-event effect
.Chance -> Combat/Buffs/ChanceTypes a bare tag (the key)
.ChanceValue -> Combat/Stat/Variable/FloatIndex "1" = 100%
Combat/Buffs/Procs/Definitions: Chance, Predicate, ResourceGain, Buff, MustBeDispatchSource,
MustBeDispatchTarget, AllowDuplicateProcs, TargetBuff, TargetAppliedState, RandomStatusEffect
Predicate -> Quest/AutoObjectives (the quest event vocabulary: PlayerHit_Sword, PlayerKill_AnyKill ...)
TargetBuff -> Combat/Buffs .OneTimeAOE / .LoopingAOE / .OneTimeAOE_Player / .DeathAOE
-> Combat/AOE -> .Tick -> Combat/AOE/Tick (Radius, Angle, MaxAIToHit, Attack, AOEFX)
_Base_Prop_ affixes are always on (_SetProp_ affixes need the set's pieces). Across the 752 area
ticks the radius ranges from 0 to 1,000,000 (median 550; 20,000 is the shipped "whole arena" value).
Combat/AOE.Alignment decides who is damaged (*_DamageAll includes followers).
How an area ticks. Combat/AOE.Tick is a list of 1 to 8 Combat/AOE/Tick records (1 on 344 of
627 areas, 2 on 132, up to 8 on 7), fired in order: the tick loop 0x4E9D28 keeps the next one's index
at +0x18C and stops after the last. It fires when the area's tick timer (+0x150, {clock, u32
end in ms}) has expired and re-arms it with TickDelay seconds (0x4EA90C: end = now + seconds x
1000); a timer without a clock counts as expired (0x4E9E98). The area's creation (0x15B994, "%s
created AOE: %s") leaves the tick timer unarmed, so the first tick fires on the area's first update,
and arms only the length timer +0x178, with MaxAOELength when that is above its threshold.
Timer is an integer (1 on 583 areas, 2 on 41, 3 on 2, 4 on 1), not a duration.
An affix's Buff and Proc are read only when its item is equipped or a gem is socketed: the field
bindings list three readers, all on those paths - 0x40136C (log tag [COMBAT|BUFF]; it also applies the
item's ActiveBuffData, ActiveVariableMod, ActiveMaterialOverrides and AppearanceTagsOnEquip),
called from the equip paths 0x4010D8 and 0x1BF2ED8 and reading the affix through 0x1BFBC50, the
gem-socketing path 0x1BFBCB8, and 0x1C7FCD8, reached from the same equip path (0x4010D8 -> 0x1C68B58 ->
0x1C7FC04). A change to an affix's Buff or Proc record therefore
takes effect at the next equip, not at the next event.
Open questions
- What the integer
Combat/AOE.Timercontrols (1 to 4; its relation to the length of theTicklist varies: equal on 337 areas, smaller on 271, larger on 19), and whether an area whoseTickDelayis longer than itsMaxAOELengthgets past its first tick. - What the four records of the inventory's last slot entries are on a finished save (ids one above the highest item).
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.