Files
sacred_k/docs/skill-slots.md
T
khallaandClaude Opus 5.5 dbc632750b Taskbar: 7 weapon sets and 7 combat-art slots
cUI_Taskbar2's slot arrays move behind the (enlarged) object with room
for 7 each; all instructions addressing them, the relative accesses
between the two arrays, the slot loops and the count clamp are patched
from tables in game/addresses.h (belt loops untouched). Slots 6 / 7
unlock at levels 45 / 65; AssignArtToSlot accepts slots up to 6. Slot
keys call the taskbar's selection directly (honouring COMBINE_SLOTS).

Shared code: re::PatchAll / PatchInstructionByte for patch tables,
game/engine/objects.h for object, RTTI and kernel calls, slot-hook
templates in skillslots.cpp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-15 00:38:34 +02:00

7.0 KiB

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.