Weapon sets and combat-art slots 6-7 live in a shadow inventory per
inventory; the slot methods run the original code on it for slots >= 5,
so item references, icon resources and selection events stay the
game's own. Save games (cInventoryManager::Serialize) and hero files
(saveHero / loadHero) store inventories as JSON ("SRIJ" + length);
vanilla blocks are still read, which migrates old saves. Multiplayer
transfers keep the binary format. nlohmann/json fetched via git.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
6.9 KiB
7 weapon sets and 7 combat-art slots (planned)
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, unlock levels and equip test open.
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) andItemDataMgr::loadHero(0x605900:0x605cee); - multiplayer:
0x5db670and0x5df1f0serialize into acFile_ramand 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 from0x61ae26/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 from0x9eb0b0/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
- 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.
- Hook the accessors above: index >= 5 goes to the table. Extend the
5-slot loops (
Clear,ReplaceItemId,FixInventory, load fix-up). - Save:
saveHerowrites the inventories as a JSON chunk (versioned) instead of 0xc1 / 0xc6;loadHeroreads the JSON chunk, or migrates a vanilla 0xc1 / 0xc6 chunk through the original reader (slots 6-7 empty). - Slot count table: 7 entries; slot 6 at level 45, slot 7 at level 65 (agreed 2026-10-14).
- Taskbar: 7 controls each (arrays inside the object are fixed at 5: own table for the extra controls), layout, rendering, hit tests, drag & drop.
- Keys: 1-7 select and cast art slots, F1-F7 select weapon sets.