feat(ui): port retail spellbook interactions
Resolve the authored spell shortcut row prototype so the spellbook presents the retail icon, name, separator, selected overlay, and scrollbar geometry. Port exact school and level filters, stable display ordering, selection exposure, and learned-spell drags into the open favorite bar. Route deletion through the shared retail confirmation dialog and send CM_Magic::Event_RemoveSpell only after an affirmative answer, leaving the inbound server notice authoritative for list state. Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
parent
ad30c37a48
commit
ac2ca8f965
17 changed files with 727 additions and 47 deletions
|
|
@ -240,6 +240,105 @@ sorts by `CSpellBase::_display_order`; school and level buttons apply the
|
|||
server-persisted filter bitfield. Component rows use the installed component
|
||||
table and the server-persisted desired purchase amounts.
|
||||
|
||||
### Spellbook row, selection, filtering, dragging, and deletion
|
||||
|
||||
Named-retail anchors:
|
||||
|
||||
- `gmSpellbookUI::IsFilteredOut @ 0x0048AEB0`
|
||||
- `gmSpellbookUI::GetSortedInsertionPlace @ 0x0048B440`
|
||||
- `gmSpellbookUI::SetSelected @ 0x0048B540`
|
||||
- `gmSpellbookUI::UpdateFilter @ 0x0048B5E0`
|
||||
- `gmSpellbookUI::AddSpell @ 0x0048B7A0`
|
||||
- `gmSpellbookUI::DeleteSpellDialogCallback @ 0x0048BC10`
|
||||
- `gmSpellbookUI::DeleteSpell @ 0x0048BD50`
|
||||
- `gmSpellbookUI::ListenToElementMessage @ 0x0048BFD0`
|
||||
- `UIElement_UIItem::Init_UIItem_Spell_Shortcut @ 0x004E1140`
|
||||
- `UIElement_ItemList::ItemList_AddSpellShortcut @ 0x004E40A0`
|
||||
- `UIElement_ItemList::ItemList_InsertSpellShortcut @ 0x004E41B0`
|
||||
- `CM_Magic::Event_RemoveSpell @ 0x006A3220`
|
||||
|
||||
DAT structure:
|
||||
|
||||
```text
|
||||
spell list 0x10000295 (280 x 224)
|
||||
spell scrollbar 0x10000296 (16 x 224)
|
||||
spell-row prototype property 0x1000000E -> 0x10000343
|
||||
shared UIItem catalog 0x21000037
|
||||
row prototype 0x10000343 (280 x 32)
|
||||
row background 0x06001396
|
||||
selected overlay child 0x10000342 -> 0x06001397
|
||||
label child 0x10000344 (x=42, width=230)
|
||||
icon child 0x1000033B (x=0, 32 x 32)
|
||||
delete button 0x100002A5
|
||||
```
|
||||
|
||||
The row's authored background contains the narrow horizontal separator visible
|
||||
between entries. It is not an invented procedural line. The selection surface
|
||||
is a separate overlay, so selecting a spell does not replace its icon or label.
|
||||
|
||||
```text
|
||||
AddSpell(spellID):
|
||||
if spell metadata is missing: return
|
||||
if IsFilteredOut(spell): return
|
||||
insertion = first existing row whose displayOrder > spell.displayOrder
|
||||
list.InsertSpellShortcut(spellID, insertion)
|
||||
|
||||
SetSelected(spellID):
|
||||
for each spell shortcut row:
|
||||
row.selectedOverlay = (row.spellID == spellID)
|
||||
if selected: list.ScrollItemIntoView(row)
|
||||
selectedSpellID = spellID
|
||||
|
||||
ListenToElementMessage(message):
|
||||
if message came from a spell shortcut row:
|
||||
if message kind is click or double-click: SetSelected(row.spellID)
|
||||
if message kind is AddSpellShortcut/drag notice:
|
||||
CM_Magic.SendNotice_AddSpellShortcut(row.spellID)
|
||||
if message is click on a school/level filter:
|
||||
UpdateFilter(clickedFilter)
|
||||
if message is click on 0x100002A5:
|
||||
DeleteSpell()
|
||||
|
||||
UpdateFilter(button):
|
||||
mask = exact school/level bit assigned to button
|
||||
if button is currently Highlight: filters |= mask
|
||||
else: filters &= ~mask
|
||||
if filters changed:
|
||||
CM_Character.Event_SpellbookFilterEvent(filters)
|
||||
rebuild list from authoritative learned spells
|
||||
scroll list to first row
|
||||
|
||||
IsFilteredOut(spell):
|
||||
school bits: Creature=0x0001, Item=0x0002, Life=0x0004,
|
||||
War=0x0008, Void=0x2000
|
||||
level bits: I=0x0010 through VIII=0x0800
|
||||
return school bit is disabled OR level bit is disabled
|
||||
|
||||
DeleteSpell():
|
||||
if selectedSpellID == 0: return false
|
||||
name = ClientMagicSystem.GetSpellName(selectedSpellID)
|
||||
message = "Are you sure you want to remove {name} from your spellbook? "
|
||||
"You will no longer be able to cast this spell unless you learn it again!"
|
||||
dialog properties include selectedSpellID under 0x1000003F
|
||||
DialogFactory.MakeCallbackDialogInCurrentUI(message, callback)
|
||||
|
||||
DeleteSpellDialogCallback(dialogProperties):
|
||||
if confirmation result property 0x92 is true:
|
||||
spellID = property 0x1000003F
|
||||
CM_Magic.Event_RemoveSpell(spellID)
|
||||
|
||||
CM_Magic.Event_RemoveSpell(spellID):
|
||||
send GameAction opcode 0x01A8 followed by spellID
|
||||
|
||||
RecvNotice_SpellRemoved(spellID):
|
||||
rebuild from the authoritative player description
|
||||
```
|
||||
|
||||
The client does not optimistically erase the row when Yes is clicked. It waits
|
||||
for the server's `MagicRemoveSpell` notice. Dragging a row similarly carries a
|
||||
spell-shortcut identity into the open spellcasting favorite list; it never
|
||||
removes the spell from the learned-spell set.
|
||||
|
||||
## Active enchantment panels
|
||||
|
||||
Retail: `gmEffectsUI::PostInit` `0x004B7560` and
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue