- Shared chest: tabs "Truhe"/"Gemeinsam" as a column left of the grid (game tab sheet), grids placed every tick, persistent across heroes - Inventory tab texts capitalized (RegisterText / CapitalizeText) - Combat-art runes stack like potions (itemstack, renamed from potionstack) - Rune master: any rune for any number of runes, price by rune count (4 runes = 1,000, x10 per missing rune), bought rune goes into the backpack, Buy disabled without runes, gold or room, last rune remembered per hero (saved), the chosen rune's name below its icon, dropping a stack gives up one rune - Keys: the old key handling no longer takes over after the save screen (text field focus only counts while its dialog is shown) - Esc crash: tooltips forgotten when the game destroys them - Pick up (Q) sends the pickup order directly; search reach doubled - Kettenblitz: tooltip shows hits per chain, chains, cast and jump range; cast range 900 (longer ray segments), jumps half of that Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
5.0 KiB
Item stacks (potions, combat-art runes)
Requests (2026-10-17)
- The potion selector on the bottom bar always shows how many potions of each kind the hero carries (only when there are any).
- Potions of the same type stack in one inventory cell, up to 20 per stack. Every potion entering the inventory tries to stack. Drag, sell and drop move a whole stack. The inventory's sort button stacks first, then sorts. A stack dropped on the ground splits into single potions (agreed for now).
Measured (diagnostic log, 2026-10-17)
| Action | Calls |
|---|---|
| Drink (potion key) | cEngine::UseActionSlot 0x613040 → cCreature::UseItem 0x54a8f0 → RemoveItem (from 0x54aa20) → DestroyObject (from 0x54aa30); then UseActionSlot calls RemoveItem again (0x6130d8, not found) |
| Drag in the inventory | RemoveAt from 0x6c06fb (cell → cursor), PlaceItem from 0x6c0583 (cursor → cell) |
| Buy | trader grid (inventory 22) RemoveItem 0x6d96f5 → hero grid PlaceItem 0x6d98e8 |
| Pick up from the ground | PlaceItem from 0x558db6 |
| Trader restock | RemoveItem 0x6d9973 + DestroyObject 0x6d99cc, new items PlaceItem 0x6d9bdf |
| Sort | cInventoryEntry::Sort 0x5db670: all items removed (0x5db8a5) and placed again (0x5dbb4c, notify 0) |
Not seen yet: a sale, a right-click drink.
Game side
- Potion kinds:
ItemPotionKind0x43f630(ecx = item type table*0xaa9564) maps an item type to kind 1..5 (the bottom bar's potion slots);GetPotionRef0x5da880picks the potion a slot drinks. - Bottom bar:
cUI_Taskbar2::RenderPotionSlots0x6e39e0draws the 5 slot controls (embedded at +0x258, stride 0xbc). - Inventory grid (
cInventoryEntry, docs/skill-slots.md): cells of 0xc bytes at +0x18; an item's id only in its first cell, covered cells flagged.CellRect0x5dd530gives a cell's screen rect (32x32 from the origin +0x1164 / +0x1166, which the window sets beforeDrawItems0x5dc3f0). - Drinking:
cPotion3D::vf180x5ca9d0applies the effect and sets object flag +0x14 bit0x8000000(used up);UseItemthen removes and destroys. - Sort button:
cUI_Inventory3::HandleEvent0x6c5f40, button control at +0x158, mode from bit 4 of +0x168. - Hero files map saved item ids to new objects with
ReplaceItemId0x5d80e0.
Mods
src/mods/potioncounts.cpp: counts per kind (stacks added up), drawn bottom-right on each potion slot afterRenderPotionSlots.src/mods/itemstack.cpp: stack counts by item id (with the item type), saved as"count"on the grid cell in the JSON inventories (inventory_save.cpp); entry pours into stacks with room on the next tick;Sortstacks the hero's potions first; a drink from a stack of more than one takes one off the count and keeps the object; the count is drawn on the cell afterDrawItems.
Open
- Selling a stack: the price is for one potion so far (sale path not measured).
- Dropping a stack on the ground: it should split into single potions; for now the count stays with the object while the game runs, but a save keeps only the object (one potion).
Chest and other inventories (2026-10-17)
Asked: potions stack in every kind of inventory; traders only sell single
potions. Measured: the chest is inventory 0x11 (cUI_Chest draws window
+0x158), saved with the hero as chunk 0xc6 and in save games, both through the
JSON path already (stack sizes survived a reload). Stacking now applies to all
inventories below 0x12 (players and the chest); trader stock (0x12 and up)
never stacks. Selling a stack to a trader still has to be handled (pay
count x price, the trader keeps singles).
Runes (request 2026-10-17: "also allow stacking for combat arts")
- Measured (sort button log of a Rucksack with runes): runes are item class 0x1c (type table +0x2e), 1x1. Two runes of type 0x1517 serialize the same past the object part (id, flags, world position differ), so the type alone identifies a rune.
- The game's own
ItemIsConsumable(0x43f5f0, class 9 or 0x1c) now decides what stacks (was:ItemPotionKind). The module was renamedsrc/mods/itemstack.cpp. - Reading a rune goes through
cCreature::UseItemlike a potion (class 0x1c is one of the three classes it uses), so a stack loses one per read.
Rune master (request 2026-10-17: one rune per drop)
(See docs/rune-master.md for the window.) cUI_MasterLotto::HandleEvent
(0x6d54e0, read): a left press over one of
its four slots (controls at +0x154 + i * 0x114) puts the slot's old rune on
the cursor and, if the cursor carries a rune (descriptor at cursor +0x68:
{u16 kind 1, u16 class 0x1c, u32 id, ...}), copies the descriptor into the
slot (+0x24c + i * 0x114) and clears the cursor. src/mods/runetrade.cpp
(owns the event hook; hud.cpp centres it through hud::HandleCentred): for a
stack, a single copy (game::CloneItem, serializer round trip) goes into the
slot, the stack keeps count - 1 and stays on the cursor; if the slot held a
rune (it comes onto the cursor), the rest goes to the backpack instead, and
without room the click does nothing.