feat(chat): port retail client command families
Expand the typed client-command boundary across travel, character queries, local UI and layout controls, AFK and consent, emotes, friends, squelch and filters, and fill-components. Preserve retail packet layouts and queue ownership, import the confirmation dialog, and keep authoritative social state in Core. Co-Authored-By: Codex <codex@openai.com>
This commit is contained in:
parent
5a45a7ac7f
commit
9ea579bdd0
47 changed files with 6659 additions and 99 deletions
|
|
@ -48,7 +48,7 @@ Copy this block when adding a new issue:
|
|||
|
||||
## #213 — Retail client commands were sent to ACE as chat text
|
||||
|
||||
**Status:** IN-PROGRESS — implementation complete 2026-07-13, pending live gate
|
||||
**Status:** IN-PROGRESS — full named-retail command-family port complete 2026-07-13, pending live gate
|
||||
**Severity:** MEDIUM
|
||||
**Component:** retained UI / chat commands / net
|
||||
|
||||
|
|
@ -63,23 +63,29 @@ as Talk text.
|
|||
|
||||
**Resolution:** Added an immutable named-retail client-command catalog and
|
||||
three explicit routes: typed retail client action, ACE server command, and
|
||||
ordinary chat. `/lifestone`, `/lif`, and `/ls` now publish
|
||||
`ExecuteClientCommandCmd`; the App-layer `ClientCommandController` invokes
|
||||
`WorldSession.SendTeleportToLifestone`, which sends retail game action `0x0063`.
|
||||
Unknown `/` commands publish `SendServerCommandCmd` in canonical `@` form, so
|
||||
commands such as `/ci` continue to reach ACE. Both chat backends use the same
|
||||
router.
|
||||
ordinary chat. The catalog now owns recall/house/PK travel, age/birth,
|
||||
framerate/lock/version/location/corpse/die, clear and named/automatic UI
|
||||
layouts, AFK/consent, emotes, friends, squelch/filter/message types, and
|
||||
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
|
||||
canonical `@` form so ACE administrator commands continue to work. Both chat
|
||||
backends use the same router.
|
||||
|
||||
**Research:**
|
||||
`docs/research/2026-07-13-retail-client-command-routing-pseudocode.md`;
|
||||
`docs/research/2026-07-13-retail-client-command-families-pseudocode.md`;
|
||||
`ClientCommunicationSystem::OnChatCommand @ 0x00581320`;
|
||||
`ClientCommunicationSystem::DoLifestone @ 0x0056FC70`;
|
||||
`CM_Character::Event_TeleToLifestone @ 0x006A1B90`.
|
||||
|
||||
**Acceptance:** In the connected Release client, `/ls`, `/lif`, and
|
||||
`/lifestone` begin recall to the bound lifestone without appearing as speech or
|
||||
an ACE unknown-command error. `/ls now` shows local usage and sends nothing.
|
||||
An ACE command such as `/ci 629 1` still works.
|
||||
**Acceptance:** In the connected Release client, representative local and
|
||||
server-backed commands execute without appearing as speech: `/loc`,
|
||||
`/framerate`, `/saveui test`, `/loadui test`, `/age`, `/friends`, `/afk on`,
|
||||
and `/marketplace`. `/ls now` shows local usage and sends nothing. An ACE
|
||||
command such as `/ci 629 1` still works.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -5132,18 +5138,20 @@ rendering.
|
|||
|
||||
---
|
||||
|
||||
## #L.6 — UI layout save/load (saveui / loadui / lockui)
|
||||
## #L.6 — [DONE 2026-07-13] UI layout save/load (saveui / loadui / lockui)
|
||||
|
||||
**Status:** OPEN
|
||||
**Status:** DONE
|
||||
**Severity:** LOW
|
||||
**Filed:** 2026-04-26 (deferred from Phase K)
|
||||
**Component:** ui
|
||||
|
||||
**Description:** Retail had `@saveui <name>`, `@loadui <name>`,
|
||||
`@lockui` commands for persisting ImGui-style window layouts. ImGui
|
||||
has built-in `LoadIniSettingsFromMemory` /
|
||||
`SaveIniSettingsToMemory` — wire these to per-named-layout files,
|
||||
plus chat-command parsing for the `@` prefixes.
|
||||
**Resolution:** The retained production UI now implements `@saveui [name]`,
|
||||
`@loadui [name]`, `@saveautoui`, `@loadautoui`, and `@lockui`. Named profiles
|
||||
persist all mounted retail windows independently of character/resolution;
|
||||
automatic profiles continue to use the existing character-and-resolution
|
||||
layout store. Both `/` and `@` prefixes route through the shared typed retail
|
||||
command catalog. The JSON storage remains the documented IA-15 modern
|
||||
adaptation of retail's `UIElement_Position` serialization.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -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, and schema-v2 per-character/per-resolution layout persistence; `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`, 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/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`, 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-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` |
|
||||
|
|
@ -207,7 +207,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| ~~AP-99~~ | **RETIRED 2026-07-11 (Wave 3.2)** — Core `ItemInteractionPolicy` ports the complete ordered `DetermineUseResult`/`UseObject`/`AttemptPlaceIn3D` matrix; PWD flags and `CombatUse` survive CreateObject; component-pack membership comes from portal.dat; App owns throttle, UseDone-balanced busy state, target mode, wire/optimistic dispatch, and typed confirmation/auxiliary seams. The two corrupt decompiler operands were pinned from matching x86 as `BF_REQUIRES_PACKSLOT` and `BF_VENDOR`. | `src/AcDream.Core/Items/ItemInteractionPolicy.cs`; `src/AcDream.App/UI/ItemInteractionController.cs` | — | — | `ItemHolder::DetermineUseResult @ 0x00588460`; `UseObject @ 0x00588A80`; `AttemptPlaceIn3D @ 0x00588600` |
|
||||
| ~~AP-101~~ | **RETIRED 2026-07-11 (Wave 4.4e)** — toolbar control coverage is complete: DAT panel launchers, combat, Use/Examine, selected-object strip, shortcuts, and exact thrown-weapon/separate-ammo count resolution on authored element `0x10000194`. | `src/AcDream.Core/Items/ToolbarAmmoPolicy.cs`; `src/AcDream.App/UI/Layout/ToolbarController.cs` | — | — | `gmToolbarUI::UpdateAmmoID @ 0x004BF210`; `UpdateAmmoNumber @ 0x004BE9E0` |
|
||||
| AP-104 | Vitals detail element `0x100004A9` and root `HideDetail`/`ShowDetail` transitions are not wired | `src/AcDream.App/UI/Layout/VitalsController.cs` | Compact vitals values/bars are correct | Detail click does nothing and expanded retail state is unreachable | `gmVitalsUI::ListenToElementMessage @ 0x004BFC00`; `PostInit @ 0x004BFCE0` |
|
||||
| AP-105 | **PARTIAL 2026-07-10 (Wave 2.2)** — inherited scrollbar media/roles now come from DAT (decrement/top `0x06004C69`, increment/bottom `0x06004C6C`). Retained chat still lacks complete tab/filter/unread, social availability, squelch, and focus-opacity behavior; a second ChatVM still loses FPS/position providers. | `src/AcDream.App/UI/Layout/DatWidgetFactory.cs`; `ChatWindowController.cs`; chat mount in `GameWindow.cs` | Shared log/send path, wrapping, scrollbar roles, and outer maximize geometry work; Wave 5 consolidates remaining chat state | Commands degrade, tabs are no-ops, moderation/channel state is wrong, and focus visuals diverge | `gmMainChatUI @ 0x004CCCC0..0x004CE2A0`; `UIElement_Scrollbar::OnSetAttribute @ 0x004714D0`; `ChatInterface` methods |
|
||||
| AP-105 | **PARTIAL 2026-07-13** — inherited scrollbar media/roles now come from DAT (decrement/top `0x06004C69`, increment/bottom `0x06004C6C`), and both chat backends share typed client-command routing plus one retained `ChatVM` for reply state. Retained chat still lacks complete tab/filter/unread, social availability, incoming squelch enforcement, and focus-opacity behavior. | `src/AcDream.App/UI/Layout/DatWidgetFactory.cs`; `ChatWindowController.cs`; `src/AcDream.App/UI/ClientCommandController.cs`; chat mount in `GameWindow.cs` | Shared log/send path, wrapping, scrollbar roles, command ownership, and outer maximize geometry work; later chat work consolidates the remaining presentation/filter state | Tabs are no-ops, squelched lines can still render, contextual social actions are absent, and focus visuals diverge | `gmMainChatUI @ 0x004CCCC0..0x004CE2A0`; `UIElement_Scrollbar::OnSetAttribute @ 0x004714D0`; `ChatInterface` methods |
|
||||
| AP-106 | The retained UI has no external/ground-container window lifecycle, while the original owned-side-bag `NoLongerViewingContents` premise was incorrect (#196) | `src/AcDream.App/UI`; window runtime; absent external-container controller | Owned inventory navigation remains usable and must not gain an unproven `0x0195` send; Wave 2/6 adds lifecycle ownership and the separate external surface | External container replacement/close cannot notify the server exactly once; adding the packet to owned bag close would itself diverge from the named retail call graph | `ClientUISystem.groundObject`; `CM_Inventory::Event_NoLongerViewingContents @ 0x006ABC50`; `docs/research/2026-07-10-retail-panel-behavior-pseudocode.md` |
|
||||
| ~~AP-107~~ | **RETIRED 2026-07-11 (Wave 3.3 / #197)** — typed `OfferPrimaryClick` returns `NotActive`, `ConsumedSuccess`, or `ConsumedRejected`; every retained item surface plus radar/world offers active target mode before local selection/open/use fallback. Rejections are consumed and cannot drift selection. | `src/AcDream.App/UI/ItemInteractionController.cs`; inventory/paperdoll/toolbar/radar/world call sites | — | — | `UIElement_ItemList::HandleTargetedUseLeftClick @ 0x004E24D0` |
|
||||
| AP-108 | Paperdoll/AutoWield still omit full `AutoWieldIsLegal`/dual-wield rules, double-click examine/drag from the doll, and body-part selection lighting. **Aetheria retired from this row 2026-07-13:** all three sigil slots use exact backgrounds/equip masks and live `PropertyInt.AetheriaBitfield` visibility. | `src/AcDream.App/UI/Layout/PaperdollController.cs`; `src/AcDream.App/UI/AutoWieldController.cs` | Basic equip slots, Aetheria, and live doll work; inventory double-click faithfully sequences the primary weapon, incompatible shield, and mismatched ammo blockers through server-confirmed dequip→wield in both peace and war | Remaining illegal/off-hand cases, doll examine/drag, and selection lighting still differ functionally | `CPlayerSystem::AutoWield @ 0x00560A60`; `ACCWeenieObject::BlocksUseOfShield @ 0x0055D3E0`; `gmPaperDollUI @ 0x004A3590..0x004A5F90` |
|
||||
|
|
@ -249,7 +249,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| TS-28 | LoginComplete sent on PlayerCreate (0xF746) arrival; retail sends it after the portal-space transition animation finishes (no such animation exists yet) | `src/AcDream.Core.Net/Messages/GameActionLoginComplete.cs:30` | acdream has no portal-space animation; "InWorld" phrasing in the file is slightly stale (trigger is PlayerCreate) | Server flips the character out of the loading state and pushes initial updates while the client may still be streaming — server logic assuming retail's load-screen duration fires against a half-initialized client | retail post-EnterWorld flow (holtburger messages.rs:391-422) |
|
||||
| TS-29 | Background music (MIDI) + ambient loops not ported: PlayMusic/StopMusic no-op; StartAmbient reserves a handle that never plays | `src/AcDream.App/Audio/OpenAlAudioEngine.cs:331` | Explicitly outside R5 audio-phase scope; a landblock-attached ambient system is planned separately | Silent world where retail has music/atmosphere; code trusting StartAmbient's handle to mean "playing" is already subtly wrong (StopAmbient looks up a never-created source) | retail MIDI + ambient system (r05) |
|
||||
| TS-30 | Chat DAT elements `0x10000522`–`0x10000525` render but have no controller semantics; the older claim that they are numbered in-window filter tabs is **unproven** | `src/AcDream.App/UI/Layout/ChatWindowController.cs` | Named retail proves separately filtered main/floaty chat windows, not an in-window numbered-tab model. Wave 5 must live/DAT-confirm these element roles before assigning behavior | The controls may be inert today, but inventing tab switching could be a larger divergence than leaving an unconfirmed role inactive | `gmMainChatUI @ 0x004CCCC0..0x004CE2A0`; correction in `docs/research/2026-07-10-retail-panel-behavior-pseudocode.md` |
|
||||
| TS-31 | Squelch toggle absent (no `/squelch` slash command, no clickable name-tags to silence); retail's squelch list filters incoming chat lines | `src/AcDream.Core/Chat/ChatLog.cs` | Squelch is a social / moderation feature deferred to post-M1.5; the data structure (`ChatLog`) has no squelch set today | Any player can spam all clients; clickable-name-tag contextual menu (used in retail to squelch, tell, add-to-friends) is absent | `ChatFilter::IsSquelched`; retail right-click player name → Squelch menu |
|
||||
| TS-31 | **NARROWED 2026-07-13** — `/squelch`, `/unsquelch`, `/filter`, `/unfilter`, and `/messagetypes` send the exact modification events and consume the authoritative retail `SquelchDB`; incoming `ChatLog` lines are not yet filtered through that database, and clickable name-tag social actions remain absent | `src/AcDream.Core/Social/SquelchState.cs`; `src/AcDream.Core.Net/Messages/SocialStateMessages.cs`; `src/AcDream.App/UI/ClientCommandController.cs`; `src/AcDream.Core/Chat/ChatLog.cs` | Command/state transport is complete; enforcement belongs at the shared inbound-chat boundary so both backends remain identical | A squelch appears in the list and persists server-side but matching incoming lines can still render; contextual name actions remain unavailable | `SquelchDB::UnPack @ 0x006B1900`; `ChatFilter::IsSquelched`; retail right-click player name → Squelch menu |
|
||||
| TS-32 | `ClientObjectTable` has no pre-queue for a child `CreateObject` that arrives before its parent (out-of-order PARENTED create); such objects are ingested as root objects and their `ContainerId` links a not-yet-known container. Retail's `null_object_table` + `null_weenie_object_table` hold unresolvable objects until the parent arrives | `src/AcDream.Core/Items/ClientObjectTable.cs` (`Ingest`) | PD↔`CreateObject` ordering is handled (upsert semantics); out-of-order PARENTED creates are observed only at high packet loss or in vendor/corpse multi-object bursts on non-loopback links; deferred to D.5.5+ | A container's child object arriving before the container is ingested as a root item — it won't appear in `GetContents` until the next `RecordMembership` or a move event corrects the parent link | `CObjectMaint::null_object_table` / `null_weenie_object_table` (acclient.h / named-retail pc) |
|
||||
| TS-33 | Outbound position-send tracker over-stamped after MoveToState: `NotePositionSent` writes last-sent position + cell + contact-plane after BOTH the MTS and AP sends, but retail's `SendMovementEvent` (0x006b4680) stamps ONLY `last_sent_position_time` after an MTS — only `SendPositionEvent` (0x006b4770, the AP path) stamps all three. Broader: acdream gates APs on a plain interval (`HeartbeatDue`) where retail uses `ShouldSendPositionEvent` (0x006b45e0 — interval OR cell-change OR contact-plane-change), AND acdream's frame-changed diff compares POSITION only (`ApproxPositionEqual`) where retail's `Frame::is_equal` compares the full frame incl. ORIENTATION — a stationary heading change (R4-V5: the MoveToManager's `HandleTurnToHeading` arrival snap, `set_heading(send:true)`) never triggers an AP, so the server keeps the stale facing until the player next moves. Masked against ACE (ACE rotates server-side on its own mt-8/9 / close-range-use paths and broadcasts the result) | `src/AcDream.App/Rendering/GameWindow.cs:8331` (MTS `NotePositionSent`); `src/AcDream.App/Input/PlayerMovementController.cs` (`ApproxPositionEqual` + the heartbeat diff); the player MoveToManager `setHeading` seam in `EnterPlayerModeNow` (the `send` flag's would-be consumer) | D5 audit-only in the L.2b wire-parity slice; the full cadence port (`ShouldSendPositionEvent` gate + split MTS/AP stamping + full-frame `Frame::is_equal` diff) is a dedicated follow-up slice (R7 outbound) | AP heartbeat cadence diverges from retail — acdream may suppress or reorder autonomous-position sends differently after movement-state sends, and a stationary server-commanded turn leaves observers with stale facing until the next movement; on a real network this shifts the position-correction rhythm | `CommandInterpreter::SendMovementEvent` 0x006b4680, `SendPositionEvent` 0x006b4770, `ShouldSendPositionEvent` 0x006b45e0, `Frame::is_equal` (pc:700263) |
|
||||
| TS-40 | Retail's `physics_obj->cell` ("placed in the world") is proxied by the explicit `PhysicsBody.InWorld` flag — set by `SnapToCell` (local player placement) and `RemoteMotion` construction (remotes exist only for world entities); consumed by `CMotionInterp`'s detached-object link-strip guards (`if (cell == 0) RemoveLinkAnimations`, raw @305627). Replaces the UNREGISTERED `CellPosition.ObjCellId == 0` proxy, which only the local player ever seeded (#145 `SnapToCell`), so every REMOTE body read "detached" and every dispatched transition link (door swings, remote walk↔run links) was stripped the same tick it was appended — the 2026-07-03 door-snap bug | `src/AcDream.Core/Physics/PhysicsBody.cs` (`InWorld`); `src/AcDream.Core/Physics/MotionInterpreter.cs` (3 guard sites) | acdream has no per-body CObjCell pointer; a boolean placement flag carries exactly the guard's retail meaning until cell-pointer plumbing exists | A body used without either placement path (a future entity class constructing bodies directly) reads detached and loses transition links until its creation site sets the flag | `CMotionInterp::DoInterpretedMotion` 0x00528360 tail @305627; `CPhysicsObj::RemoveLinkAnimations` |
|
||||
|
|
@ -262,7 +262,7 @@ AP-94..AP-112 for the confirmed retail-UI completion gaps.
|
|||
| TS-46 | Player/remote collision spheres are passed as TWO SCALARS (radius, capsule-top height) and reconstructed by `SpherePath.InitPath` (foot center at `radius`, head center at `height − radius`) — retail passes the Setup's SPHERE LIST verbatim (`CPhysicsObj::transition` 0x00512dc0 → `init_sphere(GetNumSphere, GetSphere, m_scale)`, ≤2 spheres, each origin AND radius × m_scale). With the corrected callers (0.48, 1.835 = Setup.Height) the reconstruction sits 5 mm off the dat: foot center 0.480 vs dat 0.475, head center 1.355 vs dat 1.350 (human Setup 0x02000001). **#184 Slice 3 (2026-07-07) NARROWED this: the remote de-overlap sweep now derives its scalars from the creature's OWN Setup (`GetSetupCylinder` = `setup.Radius`/`setup.Height` × ObjScale) — remotes NO LONGER use human dims regardless of Setup/scale.** RESIDUAL: (a) it is still the two-SCALAR reconstruction, not retail's ≤2-sphere LIST (lossy for creatures whose foot/head spheres differ), for both player and remotes; (b) the remote sweep's `stepUpHeight`/`stepDownHeight` stay a hardcoded 0.4 m, where retail derives them from `setup->step_up_height`/`step_down_height` (0x005180d0/0x005180f0, 0.04 m fallback, `radius×0.5` clamp) — an adjacent non-Setup divergence left for a later slice. (The pre-2026-07-06 value 1.2f put the head TOP at 1.2 m — the #137 window climb; fixed same day.) | `src/AcDream.Core/Physics/TransitionTypes.cs` (`InitPath`); `src/AcDream.App/Input/PlayerMovementController.cs` (player, human — correct as-is); `src/AcDream.App/Rendering/GameWindow.cs` (remote sweep now `GetSetupCylinder`-fed with a shapeless→human fallback) | The scalar API predates the Setup ingestion; 5 mm is below the visual/feel threshold; the remote scalars are now the creature's real (radius,height)×ObjScale, consistent with the shadow registration's entScale and the moveto/sticky radii | Marginal r−ε/r+ε grazes still flip on the 5 mm scalar offset; a creature whose head sphere is wider than its foot de-overlaps by the single (radius) approximation, not the true 2-sphere profile; the 0.4 m step heights are non-Setup for all movers | `CPhysicsObj::transition` 0x00512dc0; `SPHEREPATH::init_sphere` 0x0050c670 (≤2, ×m_scale); `set_description` 0x00514f40 (m_scale from wire ObjScale); retire by plumbing the full Setup sphere list into `InitPath` |
|
||||
| ~~TS-45~~ | **RETIRED 2026-07-07** — the hand-rolled `SphereCollision` (forced `combinedR+1 cm` radial de-penetration + leaked `SetSlidingNormal` + always-Slid, head-sphere ignored) is REPLACED by the faithful `CSphere::intersects_sphere` family port (branch dispatcher 0x00537A80 + `step_sphere_up`/`slide_sphere`/`land_on_sphere`/`collide_with_point`/`step_sphere_down`), routing the grounded slide through the shared crease `SlideSphere` (0x00537440). Humanoid creatures collide via body Spheres, so this was the player-vs-monster crowd path; the radial de-penetration was the "can't wiggle free in a packed crowd" wedge. `SphereCollisionFamilyTests` (slide-around, block, ethereal) + `docs/research/2026-07-07-csphere-collision-family-pseudocode.md`. Residual `AP-84` (PerfectClip TOI dead in M1.5). | — | — | — | `CSphere::intersects_sphere` 0x00537A80 (pc:321678) |
|
||||
| TS-43 | Remote teleport has no `teleport_hook` equivalent: retail tears down the position managers on every teleport (`CPhysicsObj::teleport_hook` 0x00514ed0 — `CancelMoveTo(0x3c)` @0x00514edf, `PositionManager::UnStick` @0x00514eee, `StopInterpolating`/`UnConstrain`); acdream's remote teleport is a bare UP hard-snap, so a stuck/chasing remote that the server teleports keeps its stick/moveto for up to the 1 s sticky lease / next UM. The LOCAL player side IS wired (R5-V3: `PlayerMovementController.SetPosition` → `PositionManager.UnStick`; the moveto cancel was already there via `StopCompletely`; the teleport-arrival site also fires the hook's tail — `EntityPhysicsHost.NotifyTeleported` = `TargetManager::ClearTarget` + `NotifyVoyeurOfEvent(Teleported)` @0x00514f1b-0x00514f28, which is what makes mobs stuck to the player drop their sticks on a recall) | `src/AcDream.App/Rendering/GameWindow.cs` (`OnLivePositionUpdated` — no teleport-flag manager teardown for remotes) | Remote teleports are rare (recalls/summons); the sticky 1 s lease + UP hard-snaps self-correct within a second; wiring it properly wants the UP teleport-stamp plumbing (TS-26's stamp work) | A teleported-away attacker briefly steers toward its pre-teleport target from the new location (≤1 s) before the lease/next-UM corrects it | `CPhysicsObj::teleport_hook` 0x00514ed0; retire with the TS-26 UP-stamp port |
|
||||
| TS-47 | The typed retail client-command catalog currently ports only `lifestone`/`lif`/`ls`; all other retail-owned command verbs fall through to ACE as server commands until ported one family at a time | `src/AcDream.UI.Abstractions/Panels/Chat/RetailClientCommandCatalog.cs` | The catalog/router/controller architecture is the first vertical slice; each remaining family needs its own named-decomp pseudocode, typed action, and conformance tests rather than a guessed bulk alias list | Retail commands such as `/marketplace`, `/house`, or `/friends` may produce ACE unknown-command output or server-specific behavior instead of the retail client action | `ClientCommunicationSystem` command-table construction around `0x00581A40..0x005850A0`; `docs/research/2026-07-13-retail-client-command-routing-pseudocode.md` |
|
||||
| TS-47 | **NARROWED 2026-07-13** — typed routing now ports the named-retail recall/house/PK travel, age/birth, local display/location, UI persistence, AFK/consent, emote, friends, squelch/filter, and fill-components families. Retail-owned verbs outside the researched family set still fall through to ACE until individually verified. | `src/AcDream.UI.Abstractions/Panels/Chat/RetailClientCommandCatalog.cs`; `src/AcDream.App/UI/ClientCommandController.cs`; `src/AcDream.Core.Net/Messages/ClientCommandRequests.cs` | The high-use researched families have decomp pseudocode, typed actions, and conformance tests; unresearched registry entries must follow the same evidence-first path | An unported retail-owned verb can still produce ACE unknown-command output or server-specific behavior instead of its client action | `ClientCommunicationSystem` command-table construction around `0x00581A40..0x005850A0`; `docs/research/2026-07-13-retail-client-command-routing-pseudocode.md`; `docs/research/2026-07-13-retail-client-command-families-pseudocode.md` |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -501,6 +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; 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`. #L.6 is closed; TS-31/TS-47 are narrowed. Research: `docs/research/2026-07-13-retail-client-command-families-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.
|
||||
|
|
|
|||
|
|
@ -484,6 +484,14 @@ include dungeons.
|
|||
automatic candidates to hostile non-player monsters; live gate passed
|
||||
2026-07-12. See
|
||||
`docs/research/2026-07-12-death-and-auto-target-pseudocode.md`.
|
||||
- **Retail client command families (implemented 2026-07-13; live gate pending)** —
|
||||
both chat surfaces now route a named-decomp command catalog into an App-layer
|
||||
controller, keeping ACE administrator verbs on the explicit server-command
|
||||
path. The implemented set covers travel, character/time queries, local UI and
|
||||
location commands, named/automatic layout persistence, AFK/consent, emotes,
|
||||
friends, squelch/filter, and fill-components, with exact Core.Net messages and
|
||||
authoritative social-state parsing. See
|
||||
`docs/research/2026-07-13-retail-client-command-families-pseudocode.md`.
|
||||
- **L.1b** — Command router + motion-state cleanup (prereq for L.1c).
|
||||
|
||||
**Freeze on landing:**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,343 @@
|
|||
# Retail client command families
|
||||
|
||||
Date: 2026-07-13
|
||||
|
||||
## Scope
|
||||
|
||||
This note extends the first lifestone vertical slice with the exact command
|
||||
families approved for porting. The named September 2013 retail client is the
|
||||
behavioral oracle; ACClientLib is used as an independent wire-name and payload
|
||||
cross-check.
|
||||
|
||||
## Sources
|
||||
|
||||
- `ClientCommunicationSystem::DoMarketplace @ 0x0056FCE0`
|
||||
- `ClientCommunicationSystem::DoLockUI @ 0x005703B0`
|
||||
- `ClientCommunicationSystem::DoFrameRate @ 0x005707D0`
|
||||
- `ClientCommunicationSystem::DoPKArena @ 0x005788D0`
|
||||
- `ClientCommunicationSystem::DoPKLArena @ 0x005789D0`
|
||||
- `ClientCommunicationSystem::DoAge @ 0x0057C5A0`
|
||||
- `ClientCommunicationSystem::DoBirth @ 0x0056E5F0`
|
||||
- `CM_Character::Event_QueryAge @ 0x006A1620`
|
||||
- `CM_Character::Event_QueryBirth @ 0x006A16F0`
|
||||
- `CM_Character::Event_TeleToMarketplace @ 0x006A1C20`
|
||||
- `CM_Character::Event_TeleToPKArena @ 0x006A1CB0`
|
||||
- `CM_Character::Event_TeleToPKLArena @ 0x006A1D40`
|
||||
- `CM_House::Event_TeleToHouse_Event @ 0x006AAFA0`
|
||||
- `CM_House::Event_TeleToMansion_Event @ 0x006AB030`
|
||||
- `CM_Character::DispatchUI_QueryAgeResponse @ 0x006A2E40`
|
||||
- `ClientCommunicationSystem::Handle_Character__QueryAgeResponse @ 0x005711D0`
|
||||
- retail command-table construction at `0x00581900..0x00585100`
|
||||
|
||||
Independent protocol cross-check:
|
||||
|
||||
- `references/acclientlib/UtilityBelt.Common/MessageTypes.cs`
|
||||
- `references/acclientlib/UtilityBelt.Common/Enums/Enums.cs`
|
||||
- `references/acclientlib/UtilityBelt.Scripting/Enums/BusyAction.cs`
|
||||
- ACE `Source/ACE.Server/Network/GameAction/Actions/` handlers for
|
||||
`SetAFKMode`, `ModifyCharacterSquelch`, `QueryAge`, `ConfirmationResponse`,
|
||||
`SetDesiredComponentLevel`, `AddFriend`, and the teleport families
|
||||
- ACE `GameEventConfirmationRequest`, `GameEventFriendsListUpdate`, and
|
||||
`Network/Structure/SquelchDB`/`SquelchInfo` writers
|
||||
|
||||
ACE's self age/birth handlers describe the trailing four zero bytes as an
|
||||
empty aligned `String16L`; named retail describes the same self-command bytes
|
||||
as object id zero. The wire bytes are identical for this path, and the named
|
||||
retail interpretation remains authoritative.
|
||||
|
||||
## Packet pseudocode
|
||||
|
||||
All actions use the normal `0xF7B1` game-action envelope and the next UI
|
||||
sequence number.
|
||||
|
||||
```text
|
||||
send_parameterless_action(opcode):
|
||||
packet = [0xF7B1, next_sequence(), opcode]
|
||||
send packet to the weenie server
|
||||
|
||||
marketplace(): send_parameterless_action(0x028D)
|
||||
pk_arena(): send_parameterless_action(0x0027)
|
||||
pkl_arena(): send_parameterless_action(0x0026)
|
||||
house_recall(): send_parameterless_action(0x0262)
|
||||
mansion_recall(): send_parameterless_action(0x0278)
|
||||
```
|
||||
|
||||
```text
|
||||
send_character_query(opcode, object_id):
|
||||
packet = [0xF7B1, next_sequence(), opcode, object_id]
|
||||
send packet to the weenie server
|
||||
|
||||
query_age(): send_character_query(0x01C2, selected_player_or_zero)
|
||||
query_birth(): send_character_query(0x01C4, selected_player_or_zero)
|
||||
```
|
||||
|
||||
For the ordinary self-command, `selected_player_or_zero` is zero. Retail's
|
||||
privileged PSR path may use the currently selected player; acdream does not
|
||||
claim that privileged mode.
|
||||
|
||||
## Command pseudocode
|
||||
|
||||
```text
|
||||
marketplace(arguments):
|
||||
if no arguments: send marketplace action
|
||||
else: display local usage
|
||||
```
|
||||
|
||||
```text
|
||||
pk_arena(arguments):
|
||||
if arguments: display local usage
|
||||
else if local player is not full PK: display retail failure 0x055F
|
||||
else: send PK-arena action
|
||||
|
||||
pkl_arena(arguments):
|
||||
if arguments: display local usage
|
||||
else if local player is not PKLite: display retail failure 0x0560
|
||||
else: send PKLite-arena action
|
||||
```
|
||||
|
||||
Retail derives both eligibility checks from the local player's
|
||||
`PublicWeenieDesc` bits (`IsPK` / `IsPKLite`), not from a guessed server
|
||||
capability.
|
||||
|
||||
```text
|
||||
framerate(arguments):
|
||||
if arguments: display local usage
|
||||
else:
|
||||
show_framerate = !show_framerate
|
||||
notify UI so the display changes immediately
|
||||
|
||||
lockui(arguments):
|
||||
if arguments: display local usage
|
||||
else:
|
||||
ui_locked = !ui_locked
|
||||
broadcast the UI-lock state change
|
||||
```
|
||||
|
||||
```text
|
||||
query_age_response(name, duration):
|
||||
if name is empty: display "You have played for {duration}."
|
||||
else: display "{name} has played for {duration}."
|
||||
```
|
||||
|
||||
The age-response payload is two aligned CP-1252 `String16L` values: target
|
||||
name (empty for self), then the already-formatted duration. Birth has no
|
||||
dedicated response event in the retail registry; the server supplies its
|
||||
result through the existing communication/system-text path.
|
||||
|
||||
## Location and corpse pseudocode
|
||||
|
||||
Sources:
|
||||
|
||||
- `ClientCommunicationSystem::DoLoc @ 0x0057A250`
|
||||
- `Position::ToString @ 0x005A9330`
|
||||
- `ClientCommunicationSystem::DoCorpse @ 0x00578220`
|
||||
- `LandDefs::CellidToCoordinateString @ 0x005A9CB0`
|
||||
|
||||
```text
|
||||
loc(arguments):
|
||||
if arguments: display local usage
|
||||
else if player position has no cell: display "Not in valid cell!"
|
||||
else:
|
||||
position_text = format(
|
||||
"0x%08X [%f %f %f] %f %f %f %f",
|
||||
cell, x, y, z, quaternion_w, quaternion_x,
|
||||
quaternion_y, quaternion_z)
|
||||
display "Your location is: {position_text}"
|
||||
```
|
||||
|
||||
```text
|
||||
corpse(arguments):
|
||||
// Retail ignores arguments for this command.
|
||||
last_outside = player quality PositionType.LastOutsideDeath (0x0E)
|
||||
if absent:
|
||||
display "We're sorry, but we have no record of your last outside corpse location."
|
||||
else:
|
||||
coordinates = cell_id_to_coordinate_string(last_outside.cell)
|
||||
display "The last time you died outside, your corpse was located at ({coordinates})."
|
||||
```
|
||||
|
||||
The corpse command reads the position already carried by PlayerDescription;
|
||||
it does not infer the corpse location from a nearby corpse object or the most
|
||||
recent death chat line.
|
||||
|
||||
## Chat-local and UI-layout pseudocode
|
||||
|
||||
Sources:
|
||||
|
||||
- `ClientCommunicationSystem::DoClear @ 0x0056E600`
|
||||
- `ClientCommunicationSystem::DoSaveUI @ 0x0056FFF0`
|
||||
- `ClientCommunicationSystem::DoLoadUI @ 0x00570150`
|
||||
- `ClientCommunicationSystem::DoSaveAutoUI @ 0x005702B0`
|
||||
- `ClientCommunicationSystem::DoLoadAutoUI @ 0x00570330`
|
||||
|
||||
```text
|
||||
clear(arguments):
|
||||
source = current_chat_source
|
||||
if first argument equals "all", ignoring case: source = all_sources
|
||||
clear(source)
|
||||
```
|
||||
|
||||
```text
|
||||
save_ui(arguments):
|
||||
reject more than one argument with the retail usage message
|
||||
name = first argument, or the empty/default profile
|
||||
reject names longer than 16 characters
|
||||
save every attached window's current placement and visibility to name
|
||||
|
||||
load_ui(arguments):
|
||||
apply the same argument rules as save_ui
|
||||
restore every attached window from name
|
||||
|
||||
save_auto_ui(arguments):
|
||||
reject arguments
|
||||
save every attached window under the current character and resolution
|
||||
|
||||
load_auto_ui(arguments):
|
||||
reject arguments
|
||||
restore every attached window for the current character and resolution
|
||||
```
|
||||
|
||||
The original serializes `UIElement_Position` records. acdream stores the same
|
||||
observable placement/visibility values in its typed layout store; the storage
|
||||
format is deliberately modern and remains tracked as divergence IA-15.
|
||||
|
||||
## Friends pseudocode
|
||||
|
||||
Sources:
|
||||
|
||||
- `ClientCommunicationSystem::DoFriends @ 0x0057BC00`
|
||||
- `ClientCommunicationSystem::DoFriendsAdd @ 0x00578FB0`
|
||||
- `ClientCommunicationSystem::DoFriendsRemove @ 0x00579080`
|
||||
- `gmFriendsUI::Request_AddFriend @ 0x0048D240`
|
||||
- `CM_Login::AddFriend @ 0x006A3020`
|
||||
- `CM_Login::RemoveFriend @ 0x006A30A0`
|
||||
- `CM_Login::ClearFriends @ 0x006A3110`
|
||||
|
||||
```text
|
||||
friends(arguments):
|
||||
no arguments: display all friends
|
||||
"online": display only online friends
|
||||
"add <name>": add_friend(name)
|
||||
"remove <name>": remove_friend(name)
|
||||
"old": request the legacy server friends listing
|
||||
otherwise: display "Invalid friends command specified."
|
||||
|
||||
add_friend(name):
|
||||
require a non-empty name
|
||||
if local list already has 50 entries: display WeenieError 0x0561
|
||||
else send opcode 0x0018 followed by String16L name
|
||||
|
||||
remove_friend(name):
|
||||
require a non-empty name
|
||||
if name equals "-all":
|
||||
send opcode 0x0025 and clear the local list
|
||||
else if a case-insensitive local match exists:
|
||||
send opcode 0x0017 followed by the friend's object id
|
||||
else display WeenieError 0x0563
|
||||
```
|
||||
|
||||
The server's `FriendsListUpdate` event is authoritative. Full updates replace
|
||||
the list; add/remove/login-state updates mutate it by object id. Display order
|
||||
is the received retail list order.
|
||||
|
||||
## Away, consent, and message-filter pseudocode
|
||||
|
||||
Sources:
|
||||
|
||||
- `ClientCommunicationSystem::DoAFK @ 0x0057B3F0`
|
||||
- `ClientCommunicationSystem::DoConsent @ 0x0057DDA0`
|
||||
- `ClientCommunicationSystem::ProcessSquelchArgs @ 0x00579C30`
|
||||
- `ClientCommunicationSystem::DoSquelch @ 0x0057BF50`
|
||||
- `ClientCommunicationSystem::DoUnSquelch @ 0x0057C070`
|
||||
- `ClientCommunicationSystem::DoFilter @ 0x0057C860`
|
||||
- `ClientCommunicationSystem::DoUnFilter @ 0x0057C880`
|
||||
- `ClientCommunicationSystem::PerformGlobalSquelchMod @ 0x0057C2D0`
|
||||
- `LogTextTypeEnumMapper::IsLegalChannel @ 0x006AFF40`
|
||||
|
||||
```text
|
||||
afk(arguments):
|
||||
no arguments or "on": send opcode 0x000F with true, unless already away
|
||||
"off": send opcode 0x000F with false, unless already present
|
||||
"msg <text>": trim/join text, enforce retail's 191-character limit,
|
||||
send opcode 0x0010 with String16L text, and echo the new text
|
||||
otherwise: display AFK usage
|
||||
```
|
||||
|
||||
```text
|
||||
consent(arguments):
|
||||
"on" / "off": toggle the local accept-loot-permits option and echo it
|
||||
"who": send opcode 0x0217
|
||||
"clear": send opcode 0x0216
|
||||
"remove <name>": send opcode 0x0218 followed by String16L name
|
||||
otherwise: display the retail invalid-command text
|
||||
```
|
||||
|
||||
The option is CharacterOption bit `0x00080000`; retail's default mask
|
||||
`0x50C4A54A` leaves it clear.
|
||||
|
||||
```text
|
||||
parse_squelch(arguments):
|
||||
default category = All
|
||||
consume options before the target:
|
||||
-reply uses the most recent teller
|
||||
-account chooses account-wide storage
|
||||
-<message type> chooses a legal LogTextType
|
||||
remaining joined text is the target name
|
||||
|
||||
squelch / unsquelch:
|
||||
with no arguments: display the authoritative local squelch database
|
||||
account target: send opcode 0x0059, add/remove flag, String16L name
|
||||
character target: send opcode 0x0058, add/remove flag, zero guid,
|
||||
String16L name, category
|
||||
|
||||
filter / unfilter:
|
||||
with no arguments: display global filters
|
||||
require exactly a legal -<message type> option
|
||||
send opcode 0x005B, add/remove flag, category
|
||||
```
|
||||
|
||||
Legal retail filter types are Speech, Tell, Combat, Magic, Emote, Appraisal,
|
||||
Spellcasting, Allegiance, Fellowship, Combat_Enemy, Combat_Self, Recall,
|
||||
Craft, and Salvaging. The inbound `SetSquelchDB` packet replaces the local
|
||||
database; command output reads that state rather than inventing client-only
|
||||
state.
|
||||
|
||||
## Emote and spell-component pseudocode
|
||||
|
||||
Sources:
|
||||
|
||||
- `ClientCommunicationSystem::DoEmote @ 0x00578AD0`
|
||||
- `CM_Communication::Event_Emote @ 0x006A4120`
|
||||
- `ClientCommunicationSystem::DoFillComponents @ 0x0056FD50`
|
||||
- `gmVendorUI::FillComponentList @ 0x004C4540`
|
||||
- `MagicEnumMapper::SpellComponentCategoryFromString @ 0x0056E290`
|
||||
|
||||
```text
|
||||
emote(arguments):
|
||||
text = join all arguments
|
||||
if text is non-empty: send opcode 0x01DF followed by String16L text
|
||||
|
||||
emotes(arguments):
|
||||
if arguments: display usage
|
||||
else: display the retail standard-emote help list
|
||||
```
|
||||
|
||||
```text
|
||||
fillcomps(arguments):
|
||||
reject more than two arguments
|
||||
if first argument is "clear":
|
||||
send opcode 0x0224, component id 0, amount 0xFFFFFFFF
|
||||
clear desired component levels and echo "Component list cleared."
|
||||
return
|
||||
parse optional component category and optional positive maximum price
|
||||
if no vendor is open: display "You need an open vendor."
|
||||
else for each desired component in the chosen category:
|
||||
needed = desired level - currently held count
|
||||
find the component in vendor stock and add min(needed, stock) to buy list
|
||||
stop before exceeding the maximum price and report the limit
|
||||
```
|
||||
|
||||
Accepted category spellings are Scarab(s), Herb(s), PowderedGem(s) or
|
||||
Powder(s), AlchemicalSubstance(s) or Potion(s), Talisman(s), Taper(s), and
|
||||
Pea(s). Component buying remains an operation on the vendor controller; the
|
||||
command parser does not duplicate vendor inventory or price logic.
|
||||
Loading…
Add table
Add a link
Reference in a new issue