Five report-only deep-dives + synthesis for the next D.2b UI panels, built on the shipped widget toolkit. Confirms LayoutDesc ids (toolbar 0x21000016, inventory 0x21000023, backpack 0x21000022, paperdoll 0x21000024, 3ditems 0x21000021), the shared item-slot/item-list spine (UIElement_UIItem 0x10000032 / UIElement_ItemList 0x10000031), the 5-layer icon composite (IconData::RenderIcons @407524), the cross-panel wire catalog with acdream parse-status, and the dependency-ordered build plan. Produced via a multi-agent research workflow; the spine agent died on a transient API error and was re-run as a focused follow-up with its decomp anchors verified against source. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
27 KiB
Action bar / quick slots (gmToolbarUI) — retail-faithful deep dive
Date: 2026-06-16
Panel: action bar / shortcut bar — retail class gmToolbarUI, element class 0x10000007, LayoutDesc 0x21000016 (root element 300×122).
Scope: handoff §3 Q1 (LayoutDesc/element map) + Q4 (shortcut slot model) + Q5 (wire + persistence) + Q6 (drag-drop / reorder). Report-only; no code written this phase.
Builds on: the D.2b importer/widget toolkit (src/AcDream.App/UI/ + …/UI/Layout/). The "spine" item-slot/icon doc referenced in the handoff prompt does NOT exist in this worktree (searched **/*spine*, **/*item-slot*, the named path — all NOT FOUND), so the UIElement_UIItem / UIElement_ItemList facts below are derived here directly from the decomp; a later synthesis should reconcile with the spine doc if it lands.
1. Summary + confidence legend
The retail toolbar is one gmToolbarUI window that contains 18 single-cell item slots (two rows of 9: top 0x100001A7..AF, bottom 0x100006B7..BF), each slot a UIElement_ItemList (element class 0x10000031) holding at most one UIElement_UIItem (class 0x10000032). A slot stores nothing but the item it currently holds; the persistent model is ShortCutManager::shortCuts_[18] (an array of ShortCutData{ index_, objectID_, spellID_ }) living on the CPlayerModule. Shortcuts are server-persisted as a character option — they arrive in the big PlayerDescription login message (the CharacterOptionDataFlag::SHORTCUT block, already parsed by acdream) and are mutated live by two C2S game actions: AddShortCut 0x019C and RemoveShortCut 0x019D (both already have outbound builders in acdream). Activation of a slot does not use a "use-shortcut" wire message — it routes through the ordinary use-item path (ItemHolder::UseObject), so it reuses acdream's existing B.4 interaction pipeline. Drag-from-inventory and drag-to-reorder are handled by gmToolbarUI being an ItemListDragHandler (multiple inheritance) whose HandleDropRelease resolves the target slot and calls CreateShortcutToItem / AddShortcut / GetFirstEmptyShortcutToTheRightOf.
The 2 Meters + 1 Scrollbar in the layout dump are NOT bar paging or extra vitals: they are the selected-object Health & Mana meters (0x100001A1/0x100001A2) and the stack-size split slider (0x100001A4), all inside the m_pSelObjectField sub-panel and hidden by default (SetVisible(0) in PostInit) — they appear only when you select an object / split a stack.
Confidence legend — CONFIRMED = quoted from named decomp or a reference file I opened; LIKELY = inferred from confirmed facts (source named); UNVERIFIED = educated guess, flagged.
2. LayoutDesc / element map (Q1) — CONFIRMED against .layout-dumps/toolbar-0x21000016.txt
LayoutDesc 0x21000016 (Id 553648150). The dump's Width=800 Height=600 is the LayoutDesc canvas; the root element 0x10000191 (ElementId 268435857, Type 0x10000463 = the registered gmToolbarUI class type) is 300×122 — that 300×122 matches the handoff's stated size and is the real window. The root's Type value 268435463 = 0x10000007… correction: dump shows Type = 268435463 which is 0x10000007 (the gmToolbarUI class id) — i.e. the root element registers as the panel class itself, exactly like gmToolbarUI::GetUIElementType returns 0x10000007 (decomp line 196707: return 0x10000007;). CONFIRMED.
2a. The 18 shortcut slots — element→slot-index map
gmToolbarUI::InitShortcutArray (decomp line 197051) wires the slots by walking GetChildRecursive(this, <id>) in order and DynamicCast(0x10000031) (= UIElement_ItemList), registering each with the drag handler and pushing into m_shortcutSlots. The push order is the slot index. The 18 ids extracted from the function body (decomp 197054–197560):
| Slot # | Element id | Row | Dump X,Y (W×H) | Hotkey msg (use / select) |
|---|---|---|---|---|
| 0 | 0x100001A7 |
top | 6,58 (32×32) | 0x10000042 / 0x1000004E |
| 1 | 0x100001A8 |
top | 38,58 | 0x10000043 / 0x1000004F |
| 2 | 0x100001A9 |
top | 70,58 | 0x10000044 / 0x10000050 |
| 3 | 0x100001AA |
top | 102,58 | 0x10000045 / 0x10000051 |
| 4 | 0x100001AB |
top | 134,58 | 0x10000046 / 0x10000052 |
| 5 | 0x100001AC |
top | 166,58 | 0x10000047 / 0x10000053 |
| 6 | 0x100001AD |
top | 198,58 | 0x10000048 / 0x10000054 |
| 7 | 0x100001AE |
top | 230,58 | 0x10000049 / 0x10000055 |
| 8 | 0x100001AF |
top | 262,58 | 0x1000004A / 0x10000056 |
| 9 | 0x100006B7 |
bottom | 6,90 | 0x1000004B / 0x10000057 |
| 10 | 0x100006B8 |
bottom | 38,90 | 0x1000004C / 0x10000058 |
| 11 | 0x100006B9 |
bottom | 70,90 | 0x1000004D / 0x10000059 |
| 12 | 0x100006BA |
bottom | 102,90 | 0x10000132 / 0x10000138 |
| 13 | 0x100006BB |
bottom | 134,90 | 0x10000133 / 0x10000139 |
| 14 | 0x100006BC |
bottom | 166,90 | 0x10000134 / 0x1000013A |
| 15 | 0x100006BD |
bottom | 198,90 | 0x10000135 / 0x1000013B |
| 16 | 0x100006BE |
bottom | 230,90 | 0x10000136 / 0x1000013C |
| 17 | 0x100006BF |
bottom | 262,90 | 0x10000137 / 0x1000013D |
CONFIRMED — slot ids from InitShortcutArray; X/Y from the dump; hotkey msg ids from gmToolbarUI::ListenToGlobalMessage (decomp 197564). The hotkey routing:
0x10000042..0x1000004D→UseShortcut(this, msg-0x10000042, 1)→ slots 0–11, use (arg3=1). (decomp 197576–197591)0x1000004E..0x10000059→UseShortcut(this, msg-0x1000004E, 0)→ slots 0–11, select (arg3=0). (decomp 197592–197606)0x10000132..0x10000137→UseShortcut(this, 0xC..0x11, 1)→ slots 12–17, use. (decomp 197616–197645)0x10000138..0x1000013D→UseShortcut(this, 0xC..0x11, 0)→ slots 12–17, select. (decomp 197646–197674)
The slot count 18 is independently confirmed by the header struct ShortCutManager::shortCuts_[18] (acclient.h line 36492–36494: struct __cppobj ShortCutManager : PackObj { ShortCutData *shortCuts_[18]; };) and the login-restore loop for (i=0; i<0x12; i++) in UpdateFromPlayerDesc (decomp 198879). ACE's comment corroborates the UX: "there are two rows. The top row is 1-9, the bottom row has no hotkeys" (Player_Character.cs:250).
Slot template: each slot's ElementDesc has BaseElement=0x100001B2 / BaseLayoutId=553648150 (the dump's last element, 0x100001B2, ElementId 268435890, which itself inherits BaseElement=268436281 BaseLayoutId=553648189). 0x100001B2 is the slot prototype (W=32 H=32) — i.e. the 18 slot elements are clones of one UIElement_ItemList prototype. LIKELY (from the dump's BaseElement chain; the resolved Type would surface 0x10000031 via ElementReader.Merge, exactly as the toolkit memory describes for Type-0 inheritance).
2b. The selected-object sub-panel + the "extra" widgets (resolves the prompt's "2 Meters / 1 Scrollbar = ?")
From gmToolbarUI::PostInit (decomp 198119) — all GetChildRecursive + DynamicCast:
| Element id | Field | DynamicCast Type | Dump location | Purpose |
|---|---|---|---|---|
0x1000019D |
m_pUseObjectButton |
(button) | 55,27 (23×31) | the Use button (sprite 0x06001129, Ghosted 0x0600120E) |
0x100001A5 |
m_pExamineObjectButton |
(button) | 218,27 (22×31) | the Examine/Appraise button (sprite 0x06001127) |
0x1000019E |
m_pSelObjectField |
(Type 3 container) | 78,27 (140×31) | the selected-object info sub-panel (dump 0x1000019E) |
0x1000019F |
m_pSelObjectName |
DynamicCast(0xC) Text |
child of A field | selected object's name |
0x100001A1 |
m_pSelObjectHealthMeter |
DynamicCast(7) Meter |
child | Meter #1 = target Health bar |
0x100001A2 |
m_pSelObjectManaMeter |
DynamicCast(7) Meter |
child | Meter #2 = target Mana bar |
0x100001A3 |
m_pStackSizeEntryBox |
DynamicCast(0xC) Text |
child | the stack-split number entry (gets NumberInputFilter) |
0x100001A4 |
m_pStackSizeSlider |
DynamicCast(0xB) Scrollbar |
50,13 (90×14), Type 11 | Scrollbar = the stack-split slider |
PostInit ends (decomp 198307–198310) by hiding all four: m_pSelObjectHealthMeter/ManaMeter/StackSizeEntryBox/StackSizeSlider → SetVisible(0). So the 2 Meters and the Scrollbar are NOT toolbar paging or persistent vitals — they are the on-demand "selected object" readout + the stack-split slider, hidden until needed. CONFIRMED.
Panel-launcher buttons (open inventory/spellbook/etc.) wired into m_buttonInfoArray with a panelID attribute (0x10000029): 0x10000197, 0x10000198, 0x10000199, 0x1000055A, 0x1000019A, 0x1000019B, 0x100001B1 (decomp 198179–198303). 0x100001B1 (X=238 W=63, sprite 0x06004CF7 Alphablend, with child 0x1000046C = m_pInventoryButtonDragOverlay) is the inventory button that also serves as a "drop item into your pack" target (see §5). The 0x1000019C/0x10000196 Type-3 elements (sprites 0x0600112B/0x0600112C) are decorative dividers; the 0x10000194 element drives UpdateAmmoNumber (the ammo-count readout, decomp 198081). Text0x34 in the pre-dump label = the 0x34 (52) text/field/image sub-elements across this whole tree (chrome + the above); they are NOT 52 slots.
3. Shortcut slot model (Q4) — CONFIRMED
A slot holds an item, the player module holds the model. Each m_shortcutSlots[i] is a UIElement_ItemList; UseShortcut/RemoveShortcutInSlotNum read the item via UIElement_ListBox::GetItem(slot, 0) then DynamicCast(0x10000032) (= UIElement_UIItem) and read the object id at field offset +0x5FC on the UIItem (decomp 196415, 196519, 196811: *(uint32_t*)((char*)eax_1 + 0x5fc)). That +0x5FC is the weenie/object id the slot points at. UNVERIFIED exact field name (offset only); LIKELY the UIItem's bound object id.
ShortCutData (the persistent unit) — verbatim header (acclient.h:36484):
struct __cppobj ShortCutData : PackObj {
int index_; // slot number (0..17)
unsigned int objectID_;// item guid (0 if spell shortcut)
unsigned int spellID_; // spell id (0 for item shortcut)
};
Constructed CShortCutData(&var_10, index, objectID, spellID) (decomp 489341: index_=arg2; objectID_=arg3; spellID_=arg4). For an item shortcut the toolbar always passes spellID=0 (CShortCutData(&var_10, i_1, arg2, 0) in AddShortcut, decomp 196874).
Number of slots / bars: 18 slots in 2 visible rows of 9 (top row = hotkeys 1-9, bottom = no hotkeys but addressable via UseShortcut(0xC..0x11)). There is no separate "bar paging" — all 18 are always present; the layout just stacks two rows. CONFIRMED (§2a).
Item vs spell shortcuts. The data model has a spellID_ slot, but in practice the toolbar holds only items. Confirmation from three angles:
- The toolbar's add paths only ever construct item shortcuts (
AddShortcut/CreateShortcutToItempassspellID=0). - Spell shortcuts live in a different list — the spellbook's
m_spellListviaUIElement_ItemList::ItemList_InsertSpellShortcut(decomp 232294) and the spell-bar hotbars (theSpellLists8/hotbar_spellsblock, separate fromSHORTCUT).CM_Magic::SendNotice_AddSpellShortcut(decomp 682275) is a local UI notice (dispatched viagmGlobalEventHandlerto notice handlers), not a wire send and not routed togmToolbarUI. - Chorizite's own comment on
ShortCutData.SpellId: "May not have been used in prod? … I don't think you could put spells in shortcut spot…" (ShortCutData.generated.cs:34). CONFIRMED — the toolbar is item-only; thespellID_/spell-bar machinery is a separate spellbook concern (out of scope for the action-bar widget).
IsShortcutEligible(ACCWeenieObject*) (decomp 196261, __stdcall): returns true unless the object is null, OR it's the player itself / a creature you don't own, OR it's currently inside the open vendor's container. Logic (decomp 196268–196300):
- if
(pwd._bitfield & 4) == 0(not "owned"?) and not a player → fall through; else requireIsPlayer(). - then
if ((InqType() & 0x10) != 0)(Creature type bit) requireIsPlayer()to continue; - then read
pwd._containerID; eligible (return 1) iff_containerID == 0OR_containerID != UISystem->vendorID— i.e. anything not sitting in the currently-open vendor window is eligible. CONFIRMED (paraphrase of the branch tree).
IsShortcutSlotAvailable(slot) (decomp 196575): slot in range AND UIElement_ItemList::GetNumUIItems(slot)==0 (empty). CONFIRMED.
Activation — UseShortcut(slot, useFlag) (decomp 196395):
- Get the
UIItemin the slot; read its object id from+0x5FC. - If a target mode is active (
UISystem->targetMode != TARGET_MODE_NONE, e.g. a spell awaiting a target):ClientUISystem::ExecuteTargetModeForItem(objId, targetMode)then clear target mode. (decomp 196412–196421) - Else if
useFlag != 0:ItemHolder::UseObject(objId, 0, 0)— the standard use-item action. (decomp 196429) - Else (
useFlag==0):ACCWeenieObject::SetSelectedObject(objId, 0)— just select it. (decomp 196433)
So toolbar activation is the ordinary use-item path, not a bespoke message. ItemHolder::UseObject (decomp 402923) has a 0.2 s throttle (m_timeLastUsed + 0.2, decomp 402933) and then dispatches the use via the inventory-request path (DetermineUseResult → 0x0036 "Use" or 0x0035 "UseWithTarget"). LIKELY (the exact 0x0035/0x0036 branch is deep in UseObject; the throttle + dispatch are CONFIRMED, the opcode selection is inferred from acdream's existing InteractRequests.cs opcodes 0x0035/0x0036).
4. Wire + persistence (Q5)
4a. Persistence = a character option in PlayerDescription (login restore)
Shortcuts are saved server-side (ACE: CharacterPropertiesShortcutBar, Player_Character.cs:235) and shipped to the client inside the PlayerDescription login message in the CharacterOptionDataFlag::SHORTCUT (0x1) block — count:u32 then count × ShortCutData. CONFIRMED in three refs:
- holtburger
events.rs:514-524(PlayerDescriptionEventData.shortcuts, "List of user-defined shortcuts for the action bar" line 124). - ACE
Player_Character.cs:238 GetShortcuts()readsCharacter.GetShortcuts(...)→List<Shortcut>for the description. - acdream already parses this:
PlayerDescriptionParser.cs:345-356readscountthenShortcutEntry(Index, ObjectGuid, SpellId, Layer)per entry, exposed onParsed.Shortcuts.
Client-side restore: gmToolbarUI::UpdateFromPlayerDesc (decomp 198838) → FlushShortcuts(), gets the CPlayerModule's ShortCutManager, then for (i=0; i<0x12; i++) { objId = shortCuts_[i]->objectID_ (+8); if (objId) AddShortcut(this, objId, i, 0); } (decomp 198879–198893). The 0 final arg = do NOT echo to server (it's already persisted). CONFIRMED.
4b. Live mutation — two C2S game actions
| Opcode | Name | Dir | Trigger | ACE handler | Chorizite type | acdream parse status |
|---|---|---|---|---|---|---|
0x019C |
AddShortCut | C→S | AddShortcut(…, send=1) builds CShortCutData(slot,objId,0) → CM_Character::Event_AddShortCut |
GameActionAddShortcut.Handle → Player.HandleActionAddShortcut(shortcut) → Character.AddOrUpdateShortcut(Index,ObjectId) |
Character_AddShortCut { ShortCutData Shortcut } |
builder present (outbound InventoryActions.BuildAddShortcut, see note) |
0x019D |
RemoveShortCut | C→S | RemoveShortcut(…, send=1) → CM_Character::Event_RemoveShortCut(slotIndex) |
GameActionRemoveShortcut.Handle → Player.HandleActionRemoveShortcut(index) → Character.TryRemoveShortcut(index) |
Character_RemoveShortCut { uint Index } |
builder present (InventoryActions.BuildRemoveShortcut) |
| (—) | shortcut list | S→C | login | part of PlayerDescription SHORTCUT block |
ShortCutData in description |
parsed (PlayerDescriptionParser.cs:345) |
Opcode values triple-confirmed: decomp Event_AddShortCut packs *(uint32_t*)var_c = 0x19c (decomp 679733) and Event_RemoveShortCut packs 0x19d (decomp 680332); ACE GameActionType.cs:77-78 (AddShortCut=0x019C, RemoveShortCut=0x019D); holtburger opcodes.rs:371-374 (commented, same values).
Wire field order — ShortCutData payload (16 bytes), CONFIRMED across 3 refs:
Index : u32 (slot 0..17)
ObjectId : u32 (item guid; 0 for spell)
SpellId : u16 (LayeredSpell.id; 0 for item)
Layer : u16 (LayeredSpell.layer; 0 for item)
- Chorizite
ShortCutData.generated.cs:41-46(Index,ObjectId, thenLayeredSpellId.Read= u16 id + u16 layer). - ACE
Shortcut.cs:33-42ReadShortcut(Index,ObjectId,ReadLayeredSpell). - holtburger
shortcuts.rs:13-34(index u32,object_id Guid,spell_id u16,layer u16). RemoveShortCut payload = justIndex:u32(ChoriziteCharacter_RemoveShortCut.generated.cs:33; ACEGameActionRemoveShortcut.cs:9; decomp packs*(uint32_t*)eax_3 = arg1at 680335).
⚠ acdream builder field-naming bug to fix at port time (not a wire bug). InventoryActions.BuildAddShortcut(seq, slotIndex, objectType, targetId) (InventoryActions.cs:99-110) writes 24 bytes = 8-byte envelope (0xF7B1 + seq) + slotIndex(u32) + objectType(u32) + targetId(u32). The byte layout is correct for item shortcuts (slot, then guid, then a final dword that for items is 0 = SpellId|Layer), but the parameter names are wrong/misleading: the 2nd field is Index, the 3rd is ObjectId, and the 4th dword is SpellId(u16)|Layer(u16) — there is no separate "objectType". A faithful builder should take (seq, uint index, uint objectGuid, ushort spellId, ushort layer) and pack the spell as two u16s. For the toolbar's item-only use, callers must pass objectGuid as the 3rd arg and 0 as the 4th. LIKELY a latent bug if anyone wired a "objectType" semantic; flag in the divergence register when the toolbar lands. (CONFIRMED file contents; the "bug" judgment is mine.)
ACE's reorder note (important UX contract): "When a shortcut is added on top of an existing item, the client automatically sends the RemoveShortcut command for that existing item first, then will add the new item, and re-add the existing item to the appropriate place." (Player_Character.cs:254). This is exactly the HandleDropRelease sequence in §5. CONFIRMED.
5. Drag-drop for the toolbar (Q6) — CONFIRMED
gmToolbarUI multiply-inherits ItemListDragHandler (constructor sets the ItemListDragHandler::vftable, decomp 196680) and registers itself as the drag handler on every slot's UIElement_ItemList in InitShortcutArray (RegisterItemListDragHandler(slot, &this->vtable), decomp 197069 etc.). Drops land in gmToolbarUI::HandleDropRelease (decomp 197971):
- Read source
UIItem(ebp = msg.dwParam1+8) and drop-target element (ebx = msg.dwParam1+0x10). (decomp 197974–197976) - If the target is the inventory button (
ebx->m_desc.m_elementID == 0x100001B1): this is "drop item into my pack."InqDropIconInfoextracts the dragged object id; then if owned by player →CPlayerSystem::PlaceInBackpack(objId, 0), else →ItemHolder::AttemptToPlaceInContainer(objId, playerId, …). (decomp 198031–198056) — i.e. dropping on the inventory button moves the real item into your pack, it does not create a shortcut. - Else (target is a shortcut slot): find which slot
iis the ancestor of the drop target (IsAncestorOfMe(ebx, m_shortcutSlots[i]), decomp 197991),InqDropIconInfo(ebp, &objId, &var_4, &flags). Then onobjId != 0:- drop flags
(flags & 0xE) == 0(a fresh drag from inventory, not a within-bar move):RemoveShortcutInSlotNum(i, 1)(evict whatever was there, returns its objIdeax_13),CreateShortcutToItem(objId, i, 1, 0)(place the dragged item in sloti, send=1). If the evictedeax_13was a different item,GetFirstEmptyShortcutToTheRightOf(i)andAddShortcut(eax_13, thatSlot, 1)to relocate it. (decomp 198007–198018) - else if
(flags & 4) != 0(a within-bar reorder,m_lastShortcutNumDraggedis the source slot):RemoveShortcutInSlotNum(i, 1)→AddShortcut(objId, i, 1); if an item was displaced andIsShortcutSlotAvailable(m_lastShortcutNumDragged), put the displaced item back into the vacated source slot (AddShortcut(eax_15, m_lastShortcutNumDragged, 1)). (decomp 198020–198027)
- drop flags
This is precisely ACE's "remove the existing one, add the new one, re-add the existing item to the appropriate place." CONFIRMED.
Slot-resolution helpers (Q6 core):
CreateShortcutToItem(objId, slotOrNeg1, send, fromServer)(decomp 196905): null-check; getACCWeenieObject; ifIsShortcutEligible. Ifslot != 0xFFFFFFFF→RemoveShortcut(objId,1); AddShortcut(objId, slot, 1)(decomp 196928–196930). Else (slot unspecified) it scans for a home (the loop at 196954+, with a "no empty slot"DisplayStringInfonotice when full, decomp 196945–196949). This is the entry called byRecvNotice_AddShortcutand the keyboard "add selected to toolbar" (0x1000010D→CreateShortcutToItem(selectedID, 0xFFFFFFFF, 1, 0), decomp 197613).AddShortcut(objId, slot, send)(decomp 196825): ifslotout of range, find the first empty slot (linear scan, decomp 196836–196848). ThenItemList_Flush(slot); ItemList_AddItem(slot, objId); SetShortcutNum(weenie, slot)(orSetDelayedShortcutNumif the weenie isn't loaded yet, decomp 196861–196867). Ifsend, buildCShortCutData(slot, objId, 0)→Event_AddShortCut(wire) +PlayerModule::AddShortCut(local model) (decomp 196873–196876).RemoveShortcut(objId, send)(decomp 196462): scan slots for the one containingobjId(ItemList_IsInList),ItemList_Flush,SetShortcutNum(weenie, 0xFFFFFFFF); ifsend,Event_RemoveShortCut(slotIndex)+PlayerModule::RemoveShortCut(slotIndex); returns the slot index (or0xFFFFFFFF). (decomp 196471–196496)RemoveShortcutInSlotNum(slot, send)(decomp 196502): read theUIItemobjId at+0x5FC,RemoveShortcut(objId, send), return the evicted objId. (decomp 196519–196524)GetFirstEmptyShortcutToTheRightOf(slot)(decomp 196536): scanslot+1 .. endfor an emptyItemList(GetNumUIItems==0); if none, wrap-scan0 .. slot; return0xFFFFFFFFif the bar is full. (decomp 196539–196569)FlushShortcuts()(decomp 196442):ItemList_Flushevery slot (visual clear; does NOT touch the server). Used by login restore. (decomp 196451–196457)
6. New toolkit widgets this introduces
The toolbar needs the same item-slot spine the inventory/paperdoll need; it adds the slot-grid + drag-handler concept on top.
| Widget | dat Type it registers at | leaf vs container | Purpose |
|---|---|---|---|
UiItemSlot (port of UIElement_UIItem, class 0x10000032) |
resolves to a class id, not a numeric toolkit Type (it's a UIElement subclass 0x10000032, registered via RegisterElementClass, not Types 1-0x12); in acdream's factory this is a new behavioral leaf widget |
leaf (ConsumesDatChildren=>true) |
the item-in-a-slot: icon from weenie IconId (+ underlay/overlay/highlight), stack-size + selection state, holds the bound object id (retail +0x5FC). Shared with inventory + paperdoll — build once. |
UiItemList (port of UIElement_ItemList / UIElement_ListBox, class 0x10000031) |
new behavioral widget at class 0x10000031 (the dump shows it as the slot prototype 0x100001B2's resolved class; Type-5 ListBox is the generic relative but item lists are the specialized 0x10000031) |
leaf wrt the importer (it manages its own UIItem children procedurally) |
a 1-cell (toolbar) or N-cell (inventory) container of UiItemSlots; exposes AddItem/Flush/IsInList/GetNumUIItems/GetItem. Shared. |
ToolbarController (the gmToolbarUI::PostInit-style binder) |
not a widget — a controller (like VitalsController/ChatWindowController) |
n/a | finds the 18 slots by id, the use/examine buttons, the selected-object meters/name, the stack slider; binds UseShortcut/AddShortcut/RemoveShortcut; restores from Parsed.Shortcuts; sends 0x019C/0x019D. |
| drag-handler seam | n/a (an interface on UiItemList + the controller) |
n/a | port of ItemListDragHandler — OnItemListDragOver / HandleDropRelease (slot resolution from §5). The toolkit's UiRoot already has drag-drop input plumbing (per the d2b memory: "UiRoot already has full input (focus/capture/drag-drop/tooltip/click)"), so this is a binding, not new infra. |
Reuses (no new widget needed): UiMeter (Type 7) for the two selected-object bars; UiText/UiField (Type 12 / the controller-placed editable) for the name + stack-size box; UiScrollbar (Type 11) for the stack slider; UiButton (Type 1) for Use/Examine/panel-launchers; UiDatElement for chrome. The window-manager (open/close/z-order/persist + grip/dragbar drag from D.2b Plan-2) is needed for show/hide + persisting position, same as inventory/paperdoll — it is not toolbar-specific.
7. Open questions / UNVERIFIED
UIElement_UIItem +0x5FCfield name — confirmed as the bound object id by offset only; the symbolic field name is UNVERIFIED. Cross-check against the spine doc'sUIItemport if/when it exists, or grepUIElement_UIItem::SetShortcutNum/UIItem_GetState.- Exact use-item opcode
UseObjectsends (0x0035 vs 0x0036) —ItemHolder::UseObjectthrottle + dispatch CONFIRMED; the precise opcode branch (DetermineUseResult) was not traced to the send. acdream'sInteractRequests.csalready has both (0x0035 UseWithTarget, 0x0036 Use); reconcile when wiring activation. UseShortcuttarget-mode path —ClientUISystem::ExecuteTargetModeForItem(for "use item on a target", e.g. a healing kit) is out of scope for the action-bar widget itself; it depends on the target-mode subsystem (cursor target picking). File as a follow-up.SetDelayedShortcutNum— the "weenie not loaded yet" deferral path (AddShortcutdecomp 196867) needs a small state machine on the slot to re-bind onceCreateObjectfor that guid arrives. Note for the controller port; not yet detailed here.- Root element Type value — the dump prints the root's
Type = 268435463(=0x10000007) for0x10000191but some other top-level dump fields printType = 268435463ambiguously; I read it as the panel class id, consistent withGetUIElementType. LIKELY; verify withElementReader.Mergewhen the importer runs over0x21000016. - Spell-on-toolbar — declared dead (Chorizite + the toolbar's item-only add paths). If a future server/ACE variant DOES persist a spell shortcut (
spellID_!=0), theUiItemSlotwould need a spell-icon branch. Low priority; the wire field exists so parsing already handles it.
8. MEMORY.md index line
- Action bar / quick slots (
gmToolbarUI) deep dive — 18 item slots (2 rows of 9, ids0x100001A7-AF+0x100006B7-BF) =UIElement_ItemList(0x10000031) of oneUIElement_UIItem(0x10000032); modelShortCutManager::shortCuts_[18]persisted inPlayerDescription's SHORTCUT block (acdream already parses it); live mutate viaAddShortCut 0x019C/RemoveShortCut 0x019D(acdream builders present — fixBuildAddShortcutfield naming); activation = ordinary use-item (ItemHolder::UseObject, no special wire); the 2 Meters + Scrollbar in0x21000016are the hidden selected-object Health/Mana bars + the stack-split slider, NOT paging; drag-drop viagmToolbarUI : ItemListDragHandler::HandleDropRelease(CreateShortcutToItem/GetFirstEmptyShortcutToTheRightOf). New toolkit widgets:UiItemSlot+UiItemList(shared spine) +ToolbarController.