feat(ui): port retail dialog factory lifecycle
Replace the single mutable confirmation service with retail's property-backed DialogFactory model: fresh DAT roots, context ids, queue groups, priority preemption, callback/close-notice ordering, and context cancellation. Route /die, server confirmation aborts, and guarded item use through focused semantic owners. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
43f7c7807c
commit
66bdae7a83
22 changed files with 1442 additions and 220 deletions
|
|
@ -70,7 +70,8 @@ fill-components. `ClientCommandController` keeps the panel layer independent
|
|||
of App/network services; `WorldSession` sends the exact game-action or control
|
||||
message for server-backed families. Friends and squelch databases are parsed
|
||||
into authoritative Core state, confirmation requests use the imported retail
|
||||
dialog, and unknown `/` commands still publish `SendServerCommandCmd` in
|
||||
dialog catalog through the ported context/queue/callback factory, and unknown
|
||||
`/` commands still publish `SendServerCommandCmd` in
|
||||
canonical `@` form so ACE administrator commands continue to work. Both chat
|
||||
backends use the same router.
|
||||
|
||||
|
|
@ -83,9 +84,16 @@ showing the live `FPS` and `DEG` values with the authored font and two-decimal
|
|||
format. `/framrate` remains unknown because retail registers no misspelled
|
||||
alias.
|
||||
|
||||
The reusable-dialog follow-up ports `DialogFactory @ 0x004773C0..0x00478470`
|
||||
and type-1 `ConfirmationDialog` rather than sharing one mutable widget. Every
|
||||
display gets a fresh LayoutDesc `0x2100003C` root; `/die`, server confirmation
|
||||
tuples (including `CharacterConfirmationDone` aborts), and PK/NPK/volatile-rare
|
||||
item use now share its property-backed result and lifecycle.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-13-retail-client-command-routing-pseudocode.md`;
|
||||
`docs/research/2026-07-13-retail-client-command-families-pseudocode.md`;
|
||||
`docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`;
|
||||
`ClientCommunicationSystem::OnChatCommand @ 0x00581320`;
|
||||
`ClientCommunicationSystem::DoLifestone @ 0x0056FC70`;
|
||||
`CM_Character::Event_TeleToLifestone @ 0x006A1B90`.
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ accepted-divergence entries (#96, #49, #50).
|
|||
| IA-12 | UI toolkit mirrors retail behavior from research docs, not a byte-port — keystone.dll is outside decomp coverage; observed constants embedded (drag 3 px, tooltip 1000 ms) | `src/AcDream.App/UI/README.md:3` (window mgr: `UiRoot.cs` RegisterWindow/Show/Hide/Toggle/BringToFront — F12 inventory toggle IS retail-faithful) | keystone.dll has no PDB/decomp; semantics reconstructed from the six `docs/research/retail-ui/` deep-dives, keeping retail's event-type constants so panel switch-cases transplant cleanly | Edge-case input semantics the research under-specified (drag threshold, tooltip timing, focus hand-off, capture corners) differ silently with no oracle to diff against | keystone.dll Device DAT_00837ff4; docs/research/retail-ui/04-input-events.md |
|
||||
| IA-13 | GameEventType registry deliberately omits event types retail ignores; unknown events fall through unhandled | `src/AcDream.Core.Net/Messages/GameEventType.cs:11` | Retail also ignores them — dropping matches retail by construction | If the "retail ignores X" judgment is wrong for any opcode (or a server mod uses one), the event is silently dropped with no diagnostic pointing at the omission | retail GameEvent dispatch (ignored-event set) |
|
||||
| IA-14 | Rendering + dat-handling base is WorldBuilder's tested port, not a fresh retail-decomp port (Phase N.4/O design stance) | `docs/architecture/worldbuilder-inventory.md` (code at `src/AcDream.{Core,App}/Rendering/Wb/`) | WB visually verified on the AC world, MIT, same stack; known WB↔retail deltas resolved case-by-case — terrain split kept retail `FSplitNESW` (**#51**, pinned by `SplitFormulaDivergenceTest`), scenery drift accepted (AP-31) | A WB-upstream divergence not yet caught ships silently as "our" behavior; guard = the inventory doc's 🟢/🔴 split + per-formula divergence tests | retail decomp per algorithm; `tests/.../SplitFormulaDivergenceTest.cs` |
|
||||
| IA-15 | D.2b gameplay UI is our own `UiHost`/`UiRoot` retained tree, not a byte-port of Keystone. `RetailUiRuntime` owns the production import/mount graph; `RetailWindowManager`/typed handles centralize registry, raise, focus/capture cleanup, lifecycle events, reverse-order grouped controller teardown, removable Silk input subscriptions, schema-v2 per-character/per-resolution automatic layouts, and portable named `saveui/loadui` profiles; `RetailWindowFrame` is the single production/Studio mount contract for imported-chrome and shared-wrapper windows. Production LayoutDesc imports include vitals `0x2100006C`, chat `0x21000006`, toolbar `0x21000016`, character `0x2100002E`, inventory `0x21000023` plus mounted `0x21000024/22/21`, confirmation dialogs `0x2100003C`, and radar `0x21000074`. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/RetailWindowManager.cs`; `src/AcDream.App/UI/RetainedPanelControllerGroup.cs`; `src/AcDream.App/UI/UiHost.cs`; `src/AcDream.App/UI/RetailWindowLayoutPersistence.cs`; `src/AcDream.App/UI/Layout/RetailWindowFrame.cs`; `src/AcDream.App/UI/Layout/RetailConfirmationDialogService.cs`; `src/AcDream.App/UI/Layout/LayoutImporter.cs`; binding supply in `GameWindow.cs` | Keystone has no matching PDB/decomp, so we preserve its observable ElementDesc/state/input behavior from DAT, named client call sites, and live evidence while using modern retained ownership. Real RenderSurfaces and imported element geometry remain the visual oracle. | Persistence is behaviorally reconstructed from `saveui/loadui` semantics rather than Keystone internals; lifecycle edge cases remain constrained by conformance tests instead of a byte-port | Production LayoutDesc objects; `docs/research/2026-06-15-layoutdesc-format.md`; Keystone behavior notes in `docs/research/retail-ui/` |
|
||||
| IA-15 | D.2b gameplay UI is our own `UiHost`/`UiRoot` retained tree, not a byte-port of Keystone. `RetailUiRuntime` owns the production import/mount graph; `RetailWindowManager`/typed handles centralize registry, raise, focus/capture cleanup, lifecycle events, reverse-order grouped controller teardown, removable Silk input subscriptions, schema-v2 per-character/per-resolution automatic layouts, and portable named `saveui/loadui` profiles; `RetailWindowFrame` is the single production/Studio mount contract for imported-chrome and shared-wrapper windows. Production LayoutDesc imports include vitals `0x2100006C`, chat `0x21000006`, toolbar `0x21000016`, character `0x2100002E`, inventory `0x21000023` plus mounted `0x21000024/22/21`, dialog catalog `0x2100003C`, and radar `0x21000074`. The dialog context/queue/callback lifecycle is now a named-client port; only its retained rendering remains under this Keystone adaptation. | `src/AcDream.App/UI/RetailUiRuntime.cs`; `src/AcDream.App/UI/RetailWindowManager.cs`; `src/AcDream.App/UI/RetainedPanelControllerGroup.cs`; `src/AcDream.App/UI/UiHost.cs`; `src/AcDream.App/UI/RetailWindowLayoutPersistence.cs`; `src/AcDream.App/UI/Layout/RetailWindowFrame.cs`; `src/AcDream.App/UI/Layout/RetailDialogFactory.cs`; `src/AcDream.App/UI/Layout/RetailConfirmationDialogView.cs`; `src/AcDream.App/UI/Layout/LayoutImporter.cs`; binding supply in `GameWindow.cs` | Keystone has no matching PDB/decomp, so we preserve its observable ElementDesc/state/input behavior from DAT, named client call sites, and live evidence while using modern retained ownership. Real RenderSurfaces and imported element geometry remain the visual oracle. | Persistence and low-level widget rendering are behaviorally reconstructed from retail semantics rather than a Keystone byte-port; lifecycle edge cases remain constrained by conformance tests | Production LayoutDesc objects; `DialogFactory @ 0x004773C0..0x00478470`; `docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`; Keystone behavior notes in `docs/research/retail-ui/` |
|
||||
| IA-17 | Toolbar chrome is toolkit-supplied through the central `RetailWindowFrame` mount (`UiCollapsibleFrame` 8-piece bevel) because LayoutDesc `0x21000016` carries no baked frame. It also supports a toolkit-defined collapse-to-one-row (bottom-edge resize snapping between a row-1-only and a two-row height, row-2 visibility tied to the stop) — retail's real collapse is keystone.dll (no decomp) and the DAT stacks both rows always. | `src/AcDream.App/UI/Layout/RetailWindowFrame.cs`; `src/AcDream.App/UI/UiCollapsibleFrame.cs`; toolbar policy in `GameWindow.cs`; spec: `docs/superpowers/specs/2026-06-20-d2b-toolbar-collapse-design.md` | The central mount now owns wrapper geometry/registration uniformly; border-over-content prevents the row-2 right cap from poking through | The collapse stops remain a toolkit reconstruction rather than a byte-port of Keystone behavior | gmToolbarUI WM chrome (keystone.dll, no PDB); no bevel ids in LayoutDesc 0x21000016 (toolbar dump) |
|
||||
| IA-18 | Effect overlay tile (enum 0x10000005) is a `ReplaceColor` SURFACE SOURCE — pure-white pixels in the composited drag icon are replaced PER-PIXEL with the same (x,y) pixel of the effect tile (the SURFACE overload `SurfaceWindow::ReplaceColor` 0x004415b0), preserving the tile's texture/gradient; the tile itself is NOT blitted as an additional layer. This IS faithful retail behavior. **Anti-regression: do NOT re-implement this as a blit layer NOR as a flat-color replace (it is a per-pixel surface copy).** | `src/AcDream.App/UI/IconComposer.cs` (`ReplaceWhiteFromSurface`) | Faithful port of `IconData::RenderIcons` @407614 → the SURFACE overload `ReplaceColor` 0x004415b0 (`dst[x,y]=src[x,y]` where `dst==white`); confirmed via clean Ghidra decompile + named decomp + visual (the Energy Crystal's blue is a gradient, 2026-06-17). | A blit-layer or flat-color re-implementation would show the wrong effect look (no gradient) — the visual-verification regression that retired the mean-color approximation | `IconData::RenderIcons` acclient_2013_pseudo_c.txt:407524; `ReplaceColor` SURFACE overload 0x004415b0:71656; `docs/research/2026-06-17-stateful-icon-RESOLVED.md` |
|
||||
| IA-19 | Automatic combat acquisition is narrowed to attackable non-player monsters. Retail `AutoTarget` falls back to `SelectNext(SELECTION_TYPE_COMPASS_ITEM)`, whose combat filter can also admit attackable enemy players in compatible PK states. | `src/AcDream.Core/Combat/CombatTargetPolicy.cs`; consumer `src/AcDream.App/Rendering/GameWindow.cs` | Explicit product direction: Auto Target must never select NPCs, players, pets, or other objects; manual player-selection commands remain available | In PK play, Auto Target will not acquire an otherwise valid hostile player as retail would; the player must be selected manually | `ClientCombatSystem::AutoTarget @ 0x0056BC80`; `CPlayerSystem::SelectNext @ 0x0055F9A0`; `ClientCombatSystem::ObjectIsAttackable @ 0x0056A600` |
|
||||
|
|
|
|||
|
|
@ -501,7 +501,7 @@ behavior. Estimated 17–26 days focused work, 3–5 weeks calendar.
|
|||
- **M2 held-object parenting shipped and live-gated 2026-07-11.** The combat toggle now ports `GetDefaultCombatMode` over ordered equipped contents, so a bow requests Missile instead of the old hardcoded Melee. CreateObject preserves parent/placement/timestamp fields, `0xF749` ParentEvent is handled, and `EquippedChildRenderController` renders the weapon as a separate child composed from the animated hand part + holding frame + child placement frame. Live gate passed: bow selected missile stance, rendered in-hand, followed animation, unequipped cleanly, and melee remained correct. App Release builds with zero warnings; the full 4,765-pass / 5-skip suite is green. AP-111 is retired; research: `docs/research/2026-07-11-combat-default-and-parent-event-pseudocode.md`.
|
||||
- **M2 local attack receive funnel implemented 2026-07-11; live gate pending.** Retail `ExecuteAttack` only sends the request; ACE chooses the concrete melee/missile action and returns it in a non-autonomous mt-0 `UpdateMotion`. The local branch now runs that state through the same constructor-defaulted `MoveToInterpretedState` funnel and 15-bit action-stamp gate as remotes, then applies sticky/long-jump tails. The old local-only direct `Commands[]` replay is deleted. Shared conversion lives in `InboundInterpretedMotionFactory`; research: `docs/research/2026-07-11-local-combat-motion-pseudocode.md`.
|
||||
- **M2 basic retail combat bar implemented 2026-07-11; corrective live visual gate pending.** Production mounts authored `gmCombatUI` LayoutDesc `0x21000073`, shows it only for Melee/Missile, and routes mouse plus keyboard through one `CombatAttackController`. Corrections include the authored wider centered dark-red child range (accepted IA-20), full-width left-to-right bright live attack-charge feedback, exact Speed-left/Power-right justification, silent control-only `AttackDone(ActionCancelled)`, target-frame Keep in View with manual orbit, and persistent corpse motion: the AP-80 velocity-only NPC adaptation can now replace only Ready/Walk/Run, never authoritative Dead/actions. `CombatTargetController` ports the selection-cleared AutoTarget consumer, so a selected creature's authoritative Dead motion clears it and selects the nearest eligible creature when enabled. The 2026-07-12 replacement-corpse correction unifies both multi-frame and static/reactive spawns behind retail's CreateObject lifecycle: apply the wire's Dead state while detached, then `MotionTableManager::HandleEnterWorld` strips Ready→Dead links before the first in-world tick; multiple corpses remaining fallen passed the live user gate that day. Follow-up #205 makes the toolbar read the final canonical selection after a reentrant Auto Target notice and restricts automatic candidates to hostile non-player monsters (intentional PK-edge divergence IA-19); that live gate also passed 2026-07-12. Research: `docs/research/2026-07-11-retail-combat-bar-pseudocode.md`, `docs/research/2026-07-11-combat-target-camera-pseudocode.md`, `docs/research/2026-07-12-death-and-auto-target-pseudocode.md`; AP-24/AP-95 retired, AP-80 narrowed, AP-110 narrowed, AP-112 records the remaining attack-start and exact trained-Recklessness seams.
|
||||
- **Retail client command families implemented 2026-07-13; corrective live gate pending.** One shared typed catalog now separates retail client actions from ACE administrator commands for both chat backends. Named-decomp ports cover recall/house/PK travel; age/birth; framerate, lock, version, location, corpse, and die confirmation; clear plus named/automatic UI layouts; AFK/consent; emotes; friends; squelch/filter/message types; and fill-components. App owns execution, Core owns authoritative friends/squelch state, Core.Net owns exact UIQueue/ControlQueue packets, and confirmation requests mount imported LayoutDesc `0x2100003C`. The first live gate passed the family except for raw suicide-success code `0x004A` and the title-bar-only FPS presentation; the correction maps retail's text and mounts SmartBox element `0x10000047` with live two-decimal `FPS`/`DEG`. #L.6 is closed; TS-31/TS-47 are narrowed. Research: `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`.
|
||||
- **Retail client command families implemented 2026-07-13; corrective live gate pending.** One shared typed catalog now separates retail client actions from ACE administrator commands for both chat backends. Named-decomp ports cover recall/house/PK travel; age/birth; framerate, lock, version, location, corpse, and die confirmation; clear plus named/automatic UI layouts; AFK/consent; emotes; friends; squelch/filter/message types; and fill-components. App owns execution, Core owns authoritative friends/squelch state, Core.Net owns exact UIQueue/ControlQueue packets. Confirmation reuse now ports retail `DialogFactory` contexts, queue groups/priority, fresh DAT roots, property results, callbacks/close notices, and server abort handling; `/die`, gameplay request tuples, and guarded item-use prompts share its type-1 LayoutDesc `0x2100003C` presenter. The first live gate passed the family except for raw suicide-success code `0x004A` and the title-bar-only FPS presentation; the correction maps retail's text and mounts SmartBox element `0x10000047` with live two-decimal `FPS`/`DEG`. #L.6 is closed; TS-31/TS-47 are narrowed. Research: `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`, `docs/research/2026-07-13-retail-dialog-factory-pseudocode.md`.
|
||||
- **✓ SHIPPED — Character window** (`LayoutDesc 0x2100002E`, `CharacterStatController`, 2026-06-26, same branch). **Visually user-confirmed 2026-06-26 — Attributes tab reads as retail.** Three tabs, header (name/heritage/PK), large-gold level number (dat font, `largeDatFont` 18px), "Total Experience (XP):" + "XP for next level:" captions, 9-row attribute list (icons + right-aligned values + Health/Stamina/Mana vitals), click-to-select (top/bottom selection bars + footer State-B "{Attr}: {value}" / "Experience To Raise: Infinity!" + affordability-gated raise triangles), centered footer. User noted "still needs some polish for later" — deferred to Issue #158.
|
||||
- **✓ SHIPPED — D.5.4 — Client object/item data model (foundation).** Shipped 2026-06-18 (`b506f53`..`a33e897`, 11 commits). Renamed `ItemRepository`→`ClientObjectTable` / `ItemInstance`→`ClientObject`; broadened the table to hold EVERY server object (retail `weenie_object_table` shape). `CreateObject` is now the canonical merge-upsert (`ClientObjectTable.Ingest`, retail `SetWeenieDesc` semantics) via a new Core.Net `ObjectTableWiring` (off GameWindow); `DeleteObject` evicts; `PlayerDescription` is a membership manifest (`RecordMembership`); live container-membership index (`GetContents`, retail `object_inventory_table`). `_liveEntityInfoByGuid` retired (selection/describe resolve from the one table). Root fix: the old enrich-existing-only `EnrichItem` dropped `CreateObject`s for items with no `PlayerDescription` stub — live-Coldeve 4/6 hotbar slots blank; items are now created, not dropped. **Crux resolved:** retail is TWO tables (`object_table` + `weenie_object_table`), NOT one — acdream's `WorldEntity` (3D system) + `ClientObjectTable` (data/UI) split was already architecturally faithful; the fix was the ingestion path, not a table unification. 2671 tests green.
|
||||
- **Roadmap correction (2026-07-10):** the completion order is now the architecture-first campaign in `docs/superpowers/plans/2026-07-10-retail-ui-fidelity-completion.md`. Retail `gmToolbarUI` is object-only: preserve `ShortCutData.index_`, `objectID_`, and `spellID_`, but do not invent spell glyphs on this bar. `PlayerModule::favorite_spells_[8]` feeds separate spell bars.
|
||||
|
|
|
|||
|
|
@ -495,6 +495,10 @@ include dungeons.
|
|||
The first connected gate passed the family except for raw suicide success
|
||||
code `0x004A` and the incomplete title-bar-only FPS toggle; the corrective
|
||||
pass maps retail's success text and mounts the authored SmartBox FPS element.
|
||||
Confirmation reuse now follows retail's `DialogFactory`: fresh DAT roots,
|
||||
property-backed results, context ids, independent queues/priority, callbacks,
|
||||
close notices, and server abort handling; `/die`, gameplay requests, and
|
||||
guarded item uses share the type-1 presenter.
|
||||
- **L.1b** — Command router + motion-state cleanup (prereq for L.1c).
|
||||
|
||||
**Freeze on landing:**
|
||||
|
|
|
|||
190
docs/research/2026-07-13-retail-dialog-factory-pseudocode.md
Normal file
190
docs/research/2026-07-13-retail-dialog-factory-pseudocode.md
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
# Retail dialog factory and confirmation lifecycle
|
||||
|
||||
Date: 2026-07-13
|
||||
|
||||
## Scope
|
||||
|
||||
This note pins the reusable retail dialog architecture needed by acdream's
|
||||
confirmation call sites. It ports the factory/context/queue lifecycle and the
|
||||
type-1 `ConfirmationDialog`. The other six catalog types are identified here
|
||||
so the factory can accept their presenters later, but their type-specific data
|
||||
contracts are outside this slice.
|
||||
|
||||
## Named retail sources
|
||||
|
||||
- `Dialog::UpdatePendingDialogDisplay @ 0x00476930`
|
||||
- `Dialog::UpdateDialogText @ 0x00476A50`
|
||||
- `Dialog::SetData @ 0x00476BE0`
|
||||
- `DialogFactory::DialogDone @ 0x004773C0`
|
||||
- `DialogFactory::UpdatePendingDialogDisplay_ @ 0x00477590`
|
||||
- `DialogFactory::Reset @ 0x00477950`
|
||||
- `DialogFactory::CreateDialog_ @ 0x00477AD0`
|
||||
- `DialogFactory::OpenNextDialog @ 0x00477D30`
|
||||
- `DialogFactory::MakeDialog @ 0x00477E90`
|
||||
- `DialogFactory::CloseDialog @ 0x00478160`
|
||||
- `DialogFactory::MakeCallbackDialogInCurrentUI @ 0x00478430`
|
||||
- `ConfirmationDialog::SetData @ 0x004764D0`
|
||||
- `ConfirmationDialog::ListenToElementMessage @ 0x00476670`
|
||||
- `ConfirmationDialog::CancelDialog @ 0x00476770`
|
||||
- `ClientCommunicationSystem::DoDie @ 0x00580050`
|
||||
- `ClientCommunicationSystem::DieDialogCallback @ 0x0057BA70`
|
||||
- `ClientUISystem::UsageCallback @ 0x00565B20`
|
||||
- `ClientUISystem::UsageConfirmation_PKAltar @ 0x00566420`
|
||||
- `ClientUISystem::UsageConfirmation_NPKAltar @ 0x00566610`
|
||||
- `ClientUISystem::UsageConfirmation_VolatileRare @ 0x00566800`
|
||||
- `ClientUISystem::Handle_Character__ConfirmationRequest @ 0x005640A0`
|
||||
- `gmGamePlayUI::MakeGameplayConfirmationDialog @ 0x004EB890`
|
||||
- `gmGamePlayUI::RecvNotice_CloseDialog @ 0x004EA4F0`
|
||||
- `gmGamePlayUI::RecvNotice_AbortConfirmationRequest @ 0x004E9D70`
|
||||
|
||||
Verbatim `DialogInfo` layout: `docs/research/named-retail/acclient.h`, struct
|
||||
5922. Protocol payloads were independently cross-checked against ACE and
|
||||
ACClientLib during the command-family port; see
|
||||
`docs/research/2026-07-13-retail-client-command-families-pseudocode.md`.
|
||||
|
||||
## Property contract
|
||||
|
||||
```text
|
||||
0x8D boolean priority/preempt-current flag
|
||||
0x8E integer dialog type
|
||||
0x90 StringInfo optional accept caption
|
||||
0x91 StringInfo optional reject caption
|
||||
0x92 boolean confirmation result
|
||||
0xAC boolean when true, Dialog::SetData sets UIElement attribute 0x40
|
||||
0xC3 integer queue key; default 2, value 1 is nonqueued
|
||||
0xC5 StringInfo main dialog text
|
||||
0x1000003D instance item/object id carried by item confirmations
|
||||
```
|
||||
|
||||
Dialog type to catalog-root/type mapping in `CreateDialog_`:
|
||||
|
||||
```text
|
||||
1 -> root 0x15, element type 0x13, ConfirmationDialog
|
||||
2 -> root 0x31, element type 0x19, WaitDialog
|
||||
3 -> root 0x24, element type 0x17, MessageDialog
|
||||
4 -> root 0x28, element type 0x18, TextInputDialog
|
||||
5 -> root 0x2C, element type 0x15, ConfirmationTextInputDialog
|
||||
6 -> root 0x1B, element type 0x16, MenuDialog
|
||||
7 -> root 0x1F, element type 0x14, ConfirmationMenuDialog
|
||||
```
|
||||
|
||||
## Factory pseudocode
|
||||
|
||||
```text
|
||||
make_dialog(parent, input_data):
|
||||
context = ++global_context
|
||||
info = DialogInfo(copy(input_data), pointer=null, parent, context)
|
||||
priority = input_data.bool(0x8D, false)
|
||||
queue_key = input_data.uint(0xC3, 2)
|
||||
|
||||
if queue_key == 1:
|
||||
append info to active_nonqueued
|
||||
create_dialog(info, queue_key)
|
||||
return context
|
||||
|
||||
if no active dialog for queue_key:
|
||||
create_dialog(info, queue_key)
|
||||
else if not priority:
|
||||
pending[queue_key].push_back(info)
|
||||
update_pending_displays()
|
||||
else:
|
||||
current = active[queue_key]
|
||||
remove current root from parent without completing it
|
||||
pending[queue_key].push_front(current)
|
||||
create_dialog(info, queue_key)
|
||||
|
||||
return context
|
||||
|
||||
create_dialog(info, queue_key):
|
||||
store info as active nonqueued or active[queue_key]
|
||||
read type from property 0x8E
|
||||
create a FRESH root element from shared catalog enum (2, 5)
|
||||
assign context to the Dialog
|
||||
dialog.SetData(info.data)
|
||||
bring dialog to front
|
||||
update_pending_displays()
|
||||
broadcast OpenDialog(context)
|
||||
|
||||
update_pending_display(dialog, pending_count):
|
||||
resolve child 0x33 and its text child 0x34 once
|
||||
child_0x33.SetState(pending_count == 0 ? 0x19 : 0x18)
|
||||
// retail does not synthesize numeric count text
|
||||
|
||||
close_dialog(context):
|
||||
if context is active nonqueued:
|
||||
dialog_done(info)
|
||||
remove it
|
||||
return
|
||||
|
||||
if context is an active queued dialog:
|
||||
remove active[queue_key]
|
||||
dialog_done(info)
|
||||
open_next_dialog(queue_key)
|
||||
return
|
||||
|
||||
if context is pending:
|
||||
dialog_done(info)
|
||||
remove it from pending
|
||||
update_pending_displays()
|
||||
|
||||
dialog_done(info):
|
||||
if callback exists for info.context:
|
||||
callback(dialog.data)
|
||||
remove callback
|
||||
broadcast CloseDialog(info.context, dialog.data)
|
||||
remove the root element from its parent
|
||||
dialog.context = 0
|
||||
|
||||
open_next_dialog(queue_key):
|
||||
if pending[queue_key] has an entry:
|
||||
info = pop_front()
|
||||
create_dialog(info, queue_key)
|
||||
delete an empty pending queue
|
||||
```
|
||||
|
||||
`Reset` runs `dialog_done` for active and pending dialogs before clearing all
|
||||
factory collections and callbacks.
|
||||
|
||||
## Confirmation presenter pseudocode
|
||||
|
||||
```text
|
||||
confirmation.SetData(data):
|
||||
base.SetData(data) // copies collection; 0xC5 updates child 0x3E
|
||||
if data has 0x90: set child 0x17 caption
|
||||
if data has 0x91: set child 0x19 caption
|
||||
|
||||
on button message:
|
||||
if child is not 0x17 or 0x19: delegate to base
|
||||
data[0x92] = (child == 0x17)
|
||||
DialogFactory.CloseDialog(context)
|
||||
|
||||
cancel:
|
||||
data[0x92] = false
|
||||
DialogFactory.CloseDialog(context)
|
||||
```
|
||||
|
||||
The catalog/root/class are reused; the live widget instance is not. Retail
|
||||
creates a fresh root for every displayed `DialogInfo`, which is required for
|
||||
simultaneous nonqueued dialogs and independent queue groups.
|
||||
|
||||
## Integration ownership
|
||||
|
||||
```text
|
||||
/die:
|
||||
data = { 0x8E=1, 0xC5=retail message }
|
||||
MakeCallbackDialogInCurrentUI(data, DieDialogCallback)
|
||||
callback sends suicide only when 0x92 is true
|
||||
|
||||
item confirmation:
|
||||
data = { 0x8E=1, 0xC5=message, 0x1000003D=item id }
|
||||
MakeCallbackDialogInCurrentUI(data, UsageCallback)
|
||||
callback sends Use only when 0x92 is true
|
||||
|
||||
server gameplay confirmation:
|
||||
gameplay UI stores server type/context
|
||||
data = { 0x8E=1, 0xC5=message }
|
||||
context = MakeDialog(gameplay UI, data) // no direct callback
|
||||
DialogFactory's CloseDialog notice reaches gmGamePlayUI
|
||||
gmGamePlayUI reads 0x92 and sends ConfirmationResponse(type, server context, result)
|
||||
matching ConfirmationDone closes the context through DialogFactory
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue