# 7 weapon sets and 7 combat-art slots Goal (user request 2026-10-14): 7 weapon sets and 7 combat-art slots instead of 5 each; skills on 1-7, weapon sets on F1-F7. Inventories are saved as JSON in place of the binary block; vanilla saves are migrated on load. Saves do not need to stay loadable by the original game. Status: inventory data for slots 6-7 (`src/mods/skillslots.cpp`) and the JSON save (`src/mods/inventory_save.cpp`) done and tested (vanilla save migrated, JSON save reloads unchanged); taskbar with 7 + 7 slots (`src/mods/skillbar.cpp`), unlock levels 45 / 65 and keys 1-7 / F1-F7 done; tested in-game 2026-10-15 (drag & drop, casting, equipping set 6 changes the stats, save / reload). ## Where the slots live ### `cInventoryEntry` (one per player, no vtable) All 32 inventories are one block from `cInventoryManager::Instance` (`0x5deb80`, 32 x 0x1180 + 8 bytes at `*0xad23c8`); `GetInventory` (`0x5dec50`) hardcodes the stride 0x1180. An inventory's index is therefore `(inventory - block) / 0x1180`. | Offset | Size | Content | |---|---|---| | +0x18 | 0x100 x 0xc | item grid | | +0xc18 | 5 x 0x18 | weapon sets: 2 hands x (item id, 2 dwords) | | +0xc90 | 5 x 2 x 0x14 | art slots: 2 variants (bit 2 of +0x1162, maybe mounted) x {u16 type 2/3/4, u16, id, 3 dwords} | | +0xd58 | 2 x u16 | selected slot: [0] weapon set, [1] art slot | | +0xd5c | 0x400 | (next field, unrelated) | | +0x115c / +0x115e | u16 | grid width 0x14 / height 8 | | +0x1160 | u16 | player index | | +0x1162 | u16 | flags | The three slot arrays are packed together, so there is no room for slots 6 and 7 in the object. Methods (names in `tools/names.txt`): `GetWeaponSetItem` `0x5d86c0`, `SetWeaponSetItem` `0x5d86f0`, `SetWeaponSet` `0x5d8930`, `TakeWeaponSetItem` `0x5d8be0`, `GetWeaponSetItemPos` `0x5d8d00`, `GetArtSlotType` `0x5d93e0`, `GetArtSlotRef` `0x5d9420`, `SetArtSlot` `0x5d9480`, `ClearArtSlot` `0x5d9680`, `SelectSlot` `0x5d9820`, `GetSelectedSlot` `0x5d9b60`, `SetArtVariant` `0x5d9200`; loops over the 5 weapon sets in `Clear` `0x5d7d30`, `ReplaceItemId` `0x5d80e0`, `FixInventory` `0x5de8a0` and the load path of `Serialize`. Accesses outside the class found so far: none to these offsets (searched `+0xc18..+0xca0`, `+0xd58/+0xd5a`, `(x+0x102)*0xc`, `(x+0xa1)*0x14`, `(x*3+0x183)*8`). The compiler also spells addresses in other ways, so the list is a lower bound until the callers of the accessors are checked. ### Save format (`cInventoryEntry::Serialize` `0x5dd9f0`) `(stream, write)`; stream vf04 read, vf08 write, vf14 position. Raw blocks in this order, no version field: flags +0x1162 (2), +0x1164 (2), +0x1166 (2), width (2), height (2), +0xd5c (0x400), grid (0xc00), **weapon sets (0x78)**, **art slots (0xc8)**, **selected (4)**, +0x1168 (4), quest items (count + 8 each), scrolls (count + 4 each). Returns the number of bytes processed. Callers: - hero save / load: `ItemDataMgr::saveHero` (`0x6046f0`: `0x604a26`, `0x604a7d`) and `ItemDataMgr::loadHero` (`0x605900`: `0x605cee`); - multiplayer: `0x5db670` and `0x5df1f0` serialize into a `cFile_ram` and send it as a network event; must keep the binary format; - save games (GAME*.PAK, "AMS"): `cInventoryManager::Serialize` (`0x5def60`, thiscall (stream, write, x)), all 32 inventories behind 0xdeadc0de markers; called from `0x61ae26` / `0x61c0a8`. ### Hero file chunks (`saveHero` / `loadHero`) 0x100-byte header (magic "AMH" at +0x?, chunk count), then a table of (type, offset, size) entries, 12 bytes each, 0xc0 bytes = 16 entries, written last (`saveHero` seeks back to 0x100). `loadHero` seeks to each chunk and switches on its type; unknown types appear to be skipped (to confirm). | Type | Content | |---|---| | 0xc0 | hero record (0x5ac) | | 0xc1 | the hero's inventory (`Serialize`, inventory index = hero id) | | 0xc6 | inventory #0x11 (`Serialize`; what it is: not checked) | | 0xc2, 0xc3 (0x22c), 0xc4 (0x40), 0xcc, 0xce, 199, 200 | other hero data | | 0xca / 0xcb / 0xcd | variants of 0xc1 / 0xc6 / 0xcc with a preceding `0x6076a0` step | Decision (2026-10-14): the inventory chunks are written as JSON (new chunk type) instead of 0xc1/0xc6; reading 0xc1/0xc6 remains as the migration from vanilla saves. The multiplayer `cFile_ram` path keeps the binary format. ### Slot count by level `SlotCountForLevel` (`0x57f750`): table at `0x95503c`, (level, count) u16 pairs: 0 -> 1, 2 -> 2, 8 -> 3, 16 -> 4, 30 -> 5. Used by the taskbar (`+0x15c`). ### Taskbar (`cUI_Taskbar2`) Slot controls (0x7c-byte `UI_STATIC` controls, created in the layout function `0x6e4ba0`): weapon sets at +0x164 (5), art slots at +0x178 (5); selection highlight written into the controls (+0x54..+0x60, +0x78) from `0x9ed058`. The object's fields are packed behind the arrays (+0x18c ...). Layout in the 1024 frame, slot width 0x42 (66 px), y = 0xf: - weapon sets on the left, growing leftwards from the centre: x = 0x18a + (i - n) * 0x42 (n = active slots); - art slots on the right, growing rightwards: x = 0x280 + i * 0x42; - `SetSlotCount` (`0x6dffa0`, clamps n to 1..5): shows the first n controls of both rows, moves the weapon row, re-selects if the selection is >= n, and rebuilds the decorative fill tiles (`UI_STATIC`, random variants from `0x9eb0b0` / `0x9eb104` / `0x9eb1ac` / `0x9eb200`) between the rows and the centre (left from 0x16a - n * 0x42, right from 0x23a + n * 0x40). With 7 slots the rows reach x = -68 on the left and 1102 on the right, past the 1024 frame (66-78 px each side). In the widened view the centred bar has room for that (16:9: 1365 wide, 170 px each side). Decision 2026-10-14: keep the original spacing; 4:3 is not a target (cut off there). The two 5 x 0xbc blocks at +0x258 / +0x604 are the belt (3D item views), not slots. Still to map: rendering of the slot contents (icons, cooldowns, `Render` `0x6e3ac0`), drag & drop onto slots (`HandleEvent` mouse branch, `0x5d9480` / `0x5d9680` calls), and whether the UI manager routes mouse events outside 0..1024 to the bar. ### Network Slot changes send network events 299 (`SetWeaponSetItem`), 300 (`SetWeaponSet`), 0x129 (`SetArtSlot`), 0x12a (`ClearArtSlot`), 0x130 (`SelectSlot`), 0x123 (`SetArtVariant`) with the slot index. A server or client running the original only knows 5 slots: slots 6-7 are single player only (or disabled in multiplayer). ## Plan 1. Slots 6-7 in a DLL table per inventory index (2 weapon sets, 2 x 2 art slot variants); the original object keeps slots 1-5 unchanged. 2. Hook the accessors above: index >= 5 goes to the table. Extend the 5-slot loops (`Clear`, `ReplaceItemId`, `FixInventory`, load fix-up). 3. Save: `saveHero` writes the inventories as a JSON chunk (versioned) instead of 0xc1 / 0xc6; `loadHero` reads the JSON chunk, or migrates a vanilla 0xc1 / 0xc6 chunk through the original reader (slots 6-7 empty). 4. Slot count table: 7 entries; slot 6 at level 45, slot 7 at level 65 (agreed 2026-10-14). 5. Taskbar: 7 controls each (arrays inside the object are fixed at 5: own table for the extra controls), layout, rendering, hit tests, drag & drop. 6. Keys: 1-7 select and cast art slots, F1-F7 select weapon sets.