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.

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:

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.

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

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.