The user set the goal: complete retail parity for the chat system AND the chat
GUI, not just the green clickable name that started the review. Plan restated
around that bar, with a definition of done — every retail behaviour either
implemented or carrying a divergence-register row, every user-visible surface
covered by a test, and the digest/ISSUES describing reality.
Slices regrouped into A (the tagged-text capability, a strict chain where
nothing is visible until A4), B (system behaviours), C (GUI), D (hygiene), plus
the research still owed before specific slices and what is deliberately out of
scope.
Four items joined the plan that the original six lanes did not own, because
they fell between lanes:
- the /r, /t, /tell text-replacement macro (the commands work; retail's
VISIBLE expansion to "@tell {LastTeller}, " does not exist)
- FilterLanguage, which is a decorative toggle: we store it, ship the bit and
show it in Options, and never actually filter anything
- the plain-text session chat log retail writes and we do not
- the option-gated timestamp prefix
Also corrects the CH3 command-registry research note. Its "acdream status"
columns are from before slice CH4 and list 13 verbs as MISSING that have all
since been added — cg, soc, o, co-vassals, fellows, group, party, vassal, ab,
guild, ct, clfg, crp — and its DIVERGENT row for /g is likewise stale: acdream
maps /g to Fellowship, matching retail, confirmed against the live client
today. The retail side of that document is still the authority; only the
columns describing us were wrong. They misled this session's investigation,
which is exactly why the banner says to verify against ChatInputParser.cs.
Nothing implemented.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
414 lines
29 KiB
Markdown
414 lines
29 KiB
Markdown
# Retail client slash-command registry — complete enumeration + acdream audit
|
|
|
|
> **CORRECTION 2026-08-21 — the "acdream status" columns below are STALE.**
|
|
> They record the state on 2026-08-09, BEFORE slice CH4 landed. Every verb this
|
|
> document calls MISSING has since been added and verified present in
|
|
> `ChatInputParser.ChannelVerbs`: `cg`, `soc`, `o`, `co-vassals`, `covassal`,
|
|
> `c`, `fellows`, `group`, `party`, `vassal`, `ab`, `guild`, `gu`, `ct`,
|
|
> `clfg`, `crp`. The DIVERGENT row for `g` was also corrected — acdream now
|
|
> maps `/g` to Fellowship, matching retail, confirmed against the live retail
|
|
> client on 2026-08-21.
|
|
>
|
|
> The RETAIL side of this document (the registered verbs, handler addresses and
|
|
> channel ids) remains accurate and is still the authority. Only the columns
|
|
> describing what acdream does are out of date; verify against
|
|
> `ChatInputParser.cs` before trusting them.
|
|
|
|
|
|
Date: 2026-08-09
|
|
Status: RESEARCH ONLY. No production code was changed.
|
|
|
|
Oracle: `docs/research/named-retail/acclient_2013_pseudo_c.txt` (Sept 2013 EoR
|
|
build) plus byte-level decode of the PDB-paired binary
|
|
`C:\Users\erikn\Downloads\acclient.exe` (v11.4186, CodeView GUID
|
|
`9e847e2f-777c-4bd9-886c-22256bb87f32`). Every verb string in this document was
|
|
read out of `.rdata` at the exact `push imm32` operand feeding
|
|
`PStringBase<char>::PStringBase<char>` inside the registration loop — none were
|
|
inferred from Binary Ninja's symbolic rendering, which mislabels several of the
|
|
pooled single-character strings as wide-string slices
|
|
(`&*U"fvpca"[4]`) and two as vtable fields.
|
|
|
|
Extends `docs/research/2026-07-13-retail-client-command-families-pseudocode.md`,
|
|
which already proved out the families acdream ported (lifestone/marketplace/
|
|
arena/age/birth/framerate/lockui/die/loc/corpse/clear/UI-layout/friends/afk/
|
|
consent/squelch/filter/emote/fillcomps). Those pseudocode blocks are not
|
|
repeated here.
|
|
|
|
---
|
|
|
|
## 1. Registry mechanism
|
|
|
|
There is **one** command table and **two** functions that populate it:
|
|
`ClientCommunicationSystem::m_hashCommands`, an
|
|
`IntrusiveHashTable<CaseInsensitiveStringBase<PStringBase<char>>, CmdHashData*>`
|
|
sized to 100 buckets in the ctor at `0x0058555D`.
|
|
`ClientCommunicationSystem::InitializeCommands @ 0x00581970` (spanning to
|
|
`0x00585520`) registers **116** entries; `CmdHashData::CmdHashData @ 0x0056ED30`
|
|
stores `+0x00` verb, `+0x08` `func` (`uint8_t (*)(this, int argc, char** argv)`),
|
|
`+0x0C` unused-in-practice, `+0x10` `help`
|
|
(`uint8_t (*)(this, HelpType, char const*, PStringBase<char>*)`), `+0x14` unused.
|
|
`ClientCommunicationSystem::StartupTurbineChatSystem @ 0x0057EFB0` runs only when
|
|
the server enables Turbine Chat; it **removes** the `a` entry and adds 15
|
|
entries (`a`, `guild`, `gu`, `general`, `cg`, `trade`, `ct`, `lfg`, `clfg`,
|
|
`roleplay`, `crp`, `society`, `soc`, `olthoi`, `o`), i.e. 14 net-new verbs.
|
|
Nothing else ever calls `IntrusiveHashTable<…CmdHashData*>::add` — verified by
|
|
grepping every call site (18 in `0x0057xxxx`, all inside
|
|
`StartupTurbineChatSystem` plus the two hash-table helpers themselves; 116 in
|
|
`0x0058xxxx`, all inside `InitializeCommands`).
|
|
|
|
Dispatch is `ClientCommunicationSystem::OnChatCommand @ 0x00581320` →
|
|
`ClientCommunicationSystem::DoCommand @ 0x0057E2E0`. `OnChatCommand` switches on
|
|
`firstChar - 0x2F`: `'/'` (case 0) is **rewritten in place to `'@'`** and falls
|
|
into `DoCommand`; `'@'` (case 0x11) enters `DoCommand` directly; `':'` and `';'`
|
|
(cases 0x0B/0x0C) have their first character replaced with a space and the whole
|
|
line prefixed with the literal `"@emote"` before `DoCommand` — that is retail's
|
|
emote shorthand. Anything else routes to `PublicChat` / talk-focus.
|
|
`DoCommand` splits the line on `" \t"` (`PSUtils::FindAllWords`, delimiter set at
|
|
`0x007E0F5C`), takes `words[0].substring(1)` as the verb, **right-trims `','`**
|
|
from it (trim char set `0x0079452C`, `trim(left=0, right=1)`) so `@f, hello`
|
|
works, then looks the verb up. If the entry is absent **or its `func` is NULL**,
|
|
it calls `ClientCommunicationSystem::DoChannelCommand @ 0x005774A0`, which tries
|
|
`ChannelSystem::GetChannelID @ 0x005CF1F0` on the verb and, on a hit, sends
|
|
`CM_Communication::Event_ChannelBroadcast(id, joinedArgs)`. Only if *that* also
|
|
fails does the client fall back to `CM_Communication::Event_Talk` with the
|
|
**original `@`-prefixed line** — which is exactly the passthrough ACE relies on
|
|
(`references/ACE/Source/ACE.Server/Network/GameAction/Actions/GameActionTalk.cs:21`,
|
|
`if (message.StartsWith("@"))` → `CommandManager`). A registered handler that
|
|
returns 0 raises `HandleFailureEvent(0x26, "")`.
|
|
|
|
---
|
|
|
|
## 2. Complete retail command table
|
|
|
|
One row per handler. `A` in the Turbine column = the row exists only after
|
|
`StartupTurbineChatSystem`. acdream paths are relative to the repo root.
|
|
|
|
### 2.1 Help / group nodes
|
|
|
|
| Verb | Aliases | Handler @addr | Args | Behavior | acdream status |
|
|
|---|---|---|---|---|---|
|
|
| `help` | `?` | `DoHelp @ 0x0057F9E0` | `[command\|group]` | No args: prints the group index + "Note: You may substitute a forward slash (/) for the at symbol (@)." With an arg: looks the verb up and calls its `help` fn. | **PARTIAL** — `ChatCommandRouter.TryHandleLocalPresentationCommand` (`src/AcDream.UI.Abstractions/Panels/Chat/ChatCommandRouter.cs:96`) prints one flat blob for `/help`, `/?`, `/h`. No `@help <command>`, no group topics. `/h` is an acdream invention (not a retail verb). |
|
|
| `commands` | — | *(NULL func)*, help `HelpAllGroup @ 0x0057E7F0` | — | Help-topic node only. Typing it alone falls through to channel lookup then server. | MISSING |
|
|
| `allegiances` | — | *(NULL func)*, `HelpAllegiancesGroup @ 0x0057D4C0` | — | Help topic. | MISSING |
|
|
| `channels` | — | *(NULL func)*, `HelpChannelsGroup @ 0x005773E0` | — | Help topic. | MISSING |
|
|
| `chatting` | — | *(NULL func)*, `HelpChattingGroup @ 0x0057B1A0` | — | Help topic. | MISSING |
|
|
| `death` | — | *(NULL func)*, `HelpDeathGroup @ 0x0057B8B0` | — | Help topic. | MISSING |
|
|
| `status` | — | *(NULL func)*, `HelpStatusGroup @ 0x0057C410` | — | Help topic. | MISSING |
|
|
| `text` | — | *(NULL func)*, `HelpTextGroup @ 0x0057C6C0` | — | Help topic. | MISSING |
|
|
|
|
### 2.2 Local chat routing
|
|
|
|
| Verb | Aliases | Handler @addr | Args | Behavior | acdream status |
|
|
|---|---|---|---|---|---|
|
|
| `say` | `s` | `DoSay @ 0x00581240` | `<text>` | Joins + trims args, `PublicChat`. Empty → "You must specify the text you wish to say." | IMPLEMENTED-EQUIVALENT — `ChatInputParser` `SayAliases = {"/say","/s"}` (`ChatInputParser.cs:41`). Not a catalog command; routed as `SendChatCmd(Say)`. |
|
|
| `tell` | `t`, `send`, `whisper`, `w` | `DoTell @ 0x00577E40` | `<name>, <text>` | Splits on the **first comma**; name = left, text = right. Sets `SetLastTelleeName`, sends `CM_Communication::Event_TalkDirectByName`. Retail help is explicit: "you must put a comma after the character's name." | **PARTIAL** — `ChatInputParser.TellAliases = {"/tell","/t"}` only; `send`/`whisper`/`w` MISSING. acdream splits on **whitespace first** and merely trims a trailing comma off the target (`ChatInputParser.cs:208-220`), so a multi-word name (`/t Lord Gnarly, hi`) resolves the wrong target where retail resolves it correctly. |
|
|
| `reply` | `r`, `rp` | `DoReply @ 0x00577910` | `<text>` | Sends `Event_TalkDirect` to `gmCCommunicationSystem::GetLastTeller()` (a **guid**, not a name). No teller → "Someone must @tell you first!" | **PARTIAL/DIVERGENT** — `ReplyAliases = {"/reply","/r"}`; `rp` is MISSING **and is bound to Roleplay in acdream** (`ChatInputParser.cs:78`). acdream replies by *name*, retail by guid. |
|
|
| `mr` | — | **NULL func**, help `HelpReply @ 0x00577A50` | `<text>` | Documented as "reply to the last person who @m'd you (monarchs only)" but the 2013 build registers it with a **null function pointer** (verified at `0x00583041`: `xor edi,edi; xor ebp,ebp` before the ctor call — arg3 is 0). It therefore falls through to channel lookup (miss) and is sent to the server as text. A retail defect, not an acdream one. | MISSING (and **do not** implement client-side — retail does not) |
|
|
| `pr` | — | **NULL func**, help `HelpReply` | `<text>` | Same as `mr` (verified at `0x005830C1`). | MISSING (same note) |
|
|
| `retell` | `rt` | `DoReTell @ 0x00577BD0` | `<text>` | Re-sends to the last person **you** tell'd. | **PARTIAL** — `RetellAliases = {"/retell"}`; `rt` MISSING. |
|
|
| `chat` | — | `DoChatToggle @ 0x0056FAD0` | `on\|off` | `on` → `Event_ModifyGlobalSquelch(remove, 2)`; `off` → `Event_ModifyGlobalSquelch(add, 2)` — i.e. it is a global **Speech** filter, not a separate flag. | MISSING (cheap: acdream already has `ModifyGlobalSquelch`) |
|
|
| `notell` | — | `DoNoTell @ 0x0056FBD0` | `on\|off` | Same mechanism with message type **3 (Tell)**. | MISSING (same) |
|
|
| `join` | — | `DoJoinChat @ 0x0056F510` | `<Allegiance\|General\|Trade\|LFG\|Roleplay\|Society\|Soc>` | Sets the matching `PlayerModule::SetHear*Chat(true)` Turbine-room flag. | MISSING |
|
|
| `leave` | — | `DoLeaveChat @ 0x0056F7F0` | same tags | Clears the matching flag. | MISSING |
|
|
| `index` | — | `DoChannelIndex @ 0x0056E640` | — | `CM_Communication::Event_ChannelIndex()`. ACE: `GameActionType.IndexChannels`. | MISSING |
|
|
| `clist` | — | `DoChannelList @ 0x0057A9B0` | `<channel>` | `GetChannelID` then `Event_ChannelList(id)`; bad tag → WeenieError `0x422`; no arg → "Please specify the channel name." ACE: `ListChannels`. | MISSING |
|
|
| `on` | — | `DoChannelOn @ 0x0057AA80` | `<channel>` | `Event_AddToChannel(id)`. ACE: `AddChannel`. | MISSING |
|
|
| `off` | — | `DoChannelOff @ 0x0057AB50` | `<channel>` | `Event_RemoveFromChannel(id)`. ACE: `RemoveChannel`. | MISSING |
|
|
| `title` | — | `DoTitle @ 0x0057A640` | `<text>` | Sets the popup chat window's title. Pure local UI. | MISSING |
|
|
| `log` | — | `DoSetOutput @ 0x0057E4F0` | `[filename]` | Toggles chat-to-file logging; appends `.txt`; "Copying chat to %s. Run command again with no arguments to turn off logging." | MISSING |
|
|
| `clear` | — | `DoClear @ 0x0056E600` | `[all]` | IMPLEMENTED | **IMPLEMENTED** — `RetailClientCommandCatalog.cs:115`, `ClientCommandController.cs:165`. |
|
|
| `filter` / `unfilter` | — | `DoFilter @ 0x0057C860` / `DoUnFilter @ 0x0057C880` | `-<type>` | IMPLEMENTED | **IMPLEMENTED** — `ClientCommandController.ExecuteGlobalFilter`. |
|
|
| `messagetypes` | `message_types`, `msgtypes`, `msg_types` | `DoMessageTypes @ 0x0057A010` | — | Lists squelch/filter categories. | **PARTIAL** — only `messagetypes` registered (`RetailClientCommandCatalog.cs:251`); the three underscore/short aliases are MISSING. |
|
|
| `loadfile` | — | `DoLoadFile @ 0x00581870` / `LoadFile @ 0x00581710` | `<file>` | `fopen` the file, then feed every line back through `OnChatCommand` after `MakeLoadFileVariableSubstitutions`. A script player. No arg → "You must provide a file name."; open failure → "Cannot open file %hs". | MISSING (see §3 risk note) |
|
|
|
|
### 2.3 Chat channels (`DoStupidChannelHack @ 0x0057B130` → `DoChannelCommand`)
|
|
|
|
All of these are one handler: no args → "You must specify the text you wish to
|
|
say."; otherwise `ChannelSystem::GetChannelID(verb)` then
|
|
`CM_Communication::Event_ChannelBroadcast(id, text)`.
|
|
|
|
| Channel | Registered verbs | Channel id | acdream status |
|
|
|---|---|---|---|
|
|
| Allegiance | `a`, `ab` (also `allegiance`/`all` **as a command**, see §2.4) | `0x02000000` | `/a` IMPLEMENTED-EQUIVALENT (`ChatInputParser.cs:63`). `ab` MISSING. **`/allegiance` is bound to the Allegiance channel in acdream but is a COMMAND in retail** — divergence. |
|
|
| Co-vassals | `co-vassals`, `covassals`, `covassal`, `c` | `0x01000000` | `covassals` OK; `c`, `covassal`, `co-vassals` MISSING. acdream's `/cv` is an invention. |
|
|
| Monarch | `monarch`, `m` | `0x4000` | IMPLEMENTED-EQUIVALENT |
|
|
| Patron | `patron`, `p` | `0x2000` | IMPLEMENTED-EQUIVALENT |
|
|
| Vassals | `vassals`, `vassal`, `v` | `0x1000` | `vassals`, `v` OK; `vassal` MISSING. |
|
|
| Fellowship | `fellowship`, `fellows`, `fellow`, `f`, `group`, `g`, `party` | `0x800` | **DIVERGENT** — retail's `g` and `group` and `party` are **Fellowship**. acdream maps `/g` to **General** (`ChatInputParser.cs:57`). `fellows`, `group`, `party` MISSING. |
|
|
|
|
`ChannelSystem::GetChannelID` also resolves 22 tags that are **not** in the
|
|
command table. Because `DoCommand` falls through to `DoChannelCommand` for any
|
|
unregistered verb, these still work as channel broadcasts:
|
|
|
|
| Tags | Channel id |
|
|
|---|---|
|
|
| `av`, `av1`, `advocate`, `advocate1` | `0x08` |
|
|
| `av2`, `advocate2` | `0x10` |
|
|
| `av3`, `advocate3` | `0x20` |
|
|
| `abuse` | `0x01` |
|
|
| `ad`, `admin` | `0x02` |
|
|
| `au`, `audit` | `0x04` |
|
|
| `sent`, `sentinel` | `0x200` |
|
|
| `celestialhand`, `celhan` | `0x08000000` |
|
|
| `eldrytchweb`, `eldweb` | `0x10000000` |
|
|
| `radiantblood`, `radblo` | `0x20000000` |
|
|
| `ol` (and `olthoi` pre-Turbine) | `0x40000000` |
|
|
| `help` | `0x400` — **explicitly rejected** by `DoChannelCommand` at `0x005774FD` (`id == 0 || id == 0x400` → return 0) |
|
|
|
|
All 22 are SERVER-PASSTHROUGH in acdream today (they reach ACE as `@abuse …`
|
|
text rather than a channel broadcast).
|
|
|
|
### 2.4 Turbine Chat (registered only by `StartupTurbineChatSystem @ 0x0057EFB0`)
|
|
|
|
Each sends `ClientCommunicationSystem::SendTurbineChat @ 0x0057DB10` with the
|
|
matching `ChatTypeEnum` and the player's `PlayerModule::Hear*Chat` gate.
|
|
|
|
| Verb | Aliases | Handler @addr | acdream status |
|
|
|---|---|---|---|
|
|
| `a` | `guild`, `gu` | `DoTurbineChat_Allegiance @ 0x0057EBA0` | `/a` routed as a plain Allegiance channel message; `guild`/`gu` MISSING |
|
|
| `general` | `cg` | `DoTurbineChat_General @ 0x0057EC50` | `general` OK; `cg` MISSING. acdream's `/gen` is an invention. |
|
|
| `trade` | `ct` | `DoTurbineChat_Trade @ 0x0057ECE0` | `trade` OK; `ct` MISSING. `/tr` is an invention. |
|
|
| `lfg` | `clfg` | `DoTurbineChat_LFG @ 0x0057ED70` | `lfg` OK; `clfg` MISSING. `/lookingforgroup` is an invention. |
|
|
| `roleplay` | `crp` | `DoTurbineChat_Roleplay @ 0x0057EE00` | `roleplay` OK; `crp` MISSING. `/role` and `/rp` are inventions (`/rp` collides with retail's reply alias). |
|
|
| `society` | `soc` | `DoTurbineChat_Society @ 0x0057EF20` | `society` OK; `soc` MISSING |
|
|
| `olthoi` | `o` | `DoTurbineChat_Olthoi @ 0x0057EE90` | `olthoi` OK; `o` MISSING |
|
|
|
|
### 2.5 Allegiance management
|
|
|
|
| Verb | Aliases | Handler @addr | Args | Behavior | acdream status |
|
|
|---|---|---|---|---|---|
|
|
| `allegiance` | `all` | `DoAllegiance @ 0x0057D5A0` | `<sub> [args]` | Subcommand dispatcher. Verbs read out of the handler: `boot` (`DoAllegianceBoot @ 0x0057AEF0`), `info` (`DoAllegianceInfo @ 0x00576560`), `chat`/`ch` (`DoAllegianceChat @ 0x00575CB0`), `broadcast`/`br` (`DoAllegianceBroadcast @ 0x005761F0`), `ban` (`DoAllegianceBan @ 0x005762A0`), `officer` (`DoAllegianceOfficer @ 0x00576650`), `title` (`DoAllegianceOfficerTitle @ 0x00576A10`), `hometown`/`ho` (`DoAllegianceHometown @ 0x0056EF10`), `motd` (`DoMotd @ 0x00577150`), `name` (`DoAllegianceName @ 0x00576C80`), `lock` (`DoAllegianceLock @ 0x00576E70`), `house` (`DoAllegianceHouse @ 0x0056EF70`). | MISSING (and `/allegiance` currently mis-bound as a chat channel) |
|
|
| `ab` | — | `DoAllegianceBroadcast @ 0x005761F0` | `<text>` | Monarch broadcast to the whole allegiance. | MISSING |
|
|
| `alh` | `ah` | `DoAllegianceHometown @ 0x0056EF10` | — | ACE: `GameActionType.RecallAllegianceHometown`. | MISSING |
|
|
| `motd` | — | `DoMotd @ 0x00577150` | `[set <text>\|clear]` | Displays, sets (monarch-only), or clears the allegiance MOTD. | MISSING |
|
|
| `speaker` | — | `DoSpeaker @ 0x0057DAB0` | — | Prints exactly "This command is no longer in use, please see @allegiance officer.\n". Pure local. | MISSING (trivial) |
|
|
|
|
### 2.5b Housing
|
|
|
|
| Verb | Aliases | Handler @addr | Args | Behavior | acdream status |
|
|
|---|---|---|---|---|---|
|
|
| `house` | `hou` | `DoHouse @ 0x00580860` | `<sub> [args]` | Subcommand verbs read out of the handler: `open`, `close`, `recall`/`re`, `mansion_recall`/`alleg_recall`/`ma`, `storage` (`DoHouseStorage @ 0x00579560`), `remove`, `boot` (`DoHouseBoot @ 0x00579970`), `boot_all`, `remove_all`, `guest` (`DoHouseGuests @ 0x005791A0`), `abandon`, `available`, `hooks`, `on`, `off`. | **PARTIAL** — `RetailClientCommandCatalog.TryMatch` (`RetailClientCommandCatalog.cs:279-301`) recognizes only `recall`, `mansion_recall`, `alleg_recall`. `hou`, `re`, `ma` and all 12 other subcommands are MISSING, **and the catalog swallows them locally** with an invalid-args message instead of letting them reach ACE. |
|
|
| `hor` | `hr` | `DoHouseRecall @ 0x00570450` | — | IMPLEMENTED | **IMPLEMENTED** |
|
|
| `hom` | `hoa` | `DoMansionRecall @ 0x005704B0` | — | IMPLEMENTED | **IMPLEMENTED** |
|
|
| `hslist` | — | `DoHouseAvailableList @ 0x00570510` | `<apartment\|cottage\|villa\|mansion>` | Lists available houses. ACE: `GameActionType.ListAvailableHouses`. | MISSING |
|
|
|
|
### 2.6 Death / recall / PK
|
|
|
|
| Verb | Aliases | Handler @addr | acdream status |
|
|
|---|---|---|---|
|
|
| `lifestone` | `lif`, `ls` | `DoLifestone @ 0x0056FC70` | **IMPLEMENTED** |
|
|
| `marketplace` | `mar`, `mp` | `DoMarketplace @ 0x0056FCE0` | **IMPLEMENTED** |
|
|
| `pkarena` | `pka` | `DoPKArena @ 0x005788D0` | **IMPLEMENTED** |
|
|
| `pklarena` | `pla` | `DoPKLArena @ 0x005789D0` | **IMPLEMENTED** |
|
|
| `pklite` | `pkl` | `DoPKLite @ 0x0057A490` | **PARTIAL** — `pkl` alias MISSING (`RetailClientCommandCatalog.cs:218` registers only `pklite`) |
|
|
| `die` | — | `DoDie @ 0x00580050` | **IMPLEMENTED** |
|
|
| `corpse` | `cor` | `DoCorpse @ 0x00578220` | **IMPLEMENTED** |
|
|
| `consent` | — | `DoConsent @ 0x0057DDA0` | **IMPLEMENTED** |
|
|
| `permit` | — | `DoPermit @ 0x005785A0` | `add <name>` / `remove <name>` — grants/revokes corpse-loot permission. ACE: `AddPlayerPermission` / `RemovePlayerPermission`. **MISSING** |
|
|
|
|
### 2.7 Status / display
|
|
|
|
| Verb | Aliases | Handler @addr | Args | Behavior | acdream status |
|
|
|---|---|---|---|---|---|
|
|
| `age` | — | `DoAge @ 0x0057C5A0` | — | **IMPLEMENTED** |
|
|
| `birth` | — | `DoBirth @ 0x0056E5F0` | — | **IMPLEMENTED** |
|
|
| `day` | — | `DoDay @ 0x005706F0` | — | Toggles `LScape::m_fAlwaysDaylight` via `LScape::SetDay`, persists with `PlayerModule::SetPersistentAtDay`, echoes "Let there be light!" when enabling. Pure local. | MISSING |
|
|
| `endurance` | — | `DoEndurance @ 0x0057C5F0` | — | Prints a fixed paragraph beginning "The endurance attribute has a nu…". Pure local text. | MISSING (trivial) |
|
|
| `framerate` | — | `DoFrameRate @ 0x005707D0` | — | **IMPLEMENTED** |
|
|
| `loc` | — | `DoLoc @ 0x0057A250` | — | **IMPLEMENTED** |
|
|
| `version` | — | `DoVersion @ 0x0057E1B0` | — | Prints "Client version %s\n"; additionally prints "Using Turbine Chat.\n" when `IsUsingTurbineChat()`, and extra lines when `PlayerIsPSR()`. | **PARTIAL** — `ClientCommandController.cs:136` prints the version line only. |
|
|
| `render` | — | `DoRenderOption @ 0x0057E120` | `[options]` | Forwards to `SmartBox::HandleRenderOption`, prints its two out-strings. Pure local dev/render toggle. | MISSING |
|
|
|
|
### 2.8 Interface layout, emotes, social, components
|
|
|
|
| Verb | Aliases | Handler @addr | acdream status |
|
|
|---|---|---|---|
|
|
| `saveui` / `loadui` / `saveautoui` / `loadautoui` / `lockui` | — | `0x0056FFF0` / `0x00570150` / `0x005702B0` / `0x00570330` / `0x005703B0` | **IMPLEMENTED** |
|
|
| `emote` | `e`, `em`, `me` | `DoEmote @ 0x00578AD0` | **IMPLEMENTED** |
|
|
| `emotes` | — | `DoEmoteList @ 0x0057BB30` | **IMPLEMENTED** |
|
|
| `afk` | — | `DoAFK @ 0x0057B3F0` | **IMPLEMENTED** |
|
|
| `friends` / `friends_add` / `friends_remove` | — | `0x0057BC00` / `0x00578FB0` / `0x00579080` | **IMPLEMENTED** |
|
|
| `squelch` / `unsquelch` | — | `0x0057BF50` / `0x0057C070` | **IMPLEMENTED** |
|
|
| `fillcomps` | — | `DoFillComponents @ 0x0056FD50` | **IMPLEMENTED** |
|
|
|
|
### 2.9 Prefixes (not verbs)
|
|
|
|
| Prefix | Site | Behavior | acdream status |
|
|
|---|---|---|---|
|
|
| `/` | `OnChatCommand @ 0x00581431` | Rewritten to `@`, then dispatched. | **IMPLEMENTED** — `RetailClientCommandCatalog.TryMatch` accepts both (`RetailClientCommandCatalog.cs:267`). |
|
|
| `@` | `OnChatCommand @ 0x00581444` | Dispatched directly. | **IMPLEMENTED** |
|
|
| `:` , `;` | `OnChatCommand @ 0x0058144D` | First char replaced with a space, line prefixed with `"@emote"`, then dispatched — `:waves` ≡ `@emote waves`. | **MISSING** |
|
|
| trailing `,` on the verb | `DoCommand @ 0x0057E3CD` | Right-trimmed, so `@f, hi` ≡ `@f hi`. | **MISSING** — acdream's verb token keeps the comma, so `/f, hi` does not match any verb and is shipped to ACE as `@f,`. |
|
|
|
|
---
|
|
|
|
## 3. Prioritized missing-command list
|
|
|
|
Effort key: **L** = pure-local (no wire message); **W-have** = wire path already
|
|
exists in acdream; **W-new** = needs a new game action.
|
|
|
|
### Tier 1 — correctness bugs in what already ships (do these first)
|
|
|
|
1. **`/g` is bound to General; retail binds it to Fellowship** (`0x800`).
|
|
`ChatInputParser.cs:57`. One-line fix, but it silently sends fellowship
|
|
chatter to a global channel today. **L**
|
|
2. **`/rp` is bound to Roleplay; retail binds it to `reply`.** `ChatInputParser.cs:78`.
|
|
Same class of bug (a private reply becomes a global broadcast). **L**
|
|
3. **`/allegiance <text>` is bound to the Allegiance channel; retail's
|
|
`allegiance`/`all` is the allegiance *management command*.** Channel verbs are
|
|
`a`/`ab`/`guild`/`gu`. **L**
|
|
4. **`/house <anything-but-recall>` is swallowed locally.** `RetailClientCommandCatalog.cs:288-296`
|
|
returns a match with `HasValidArguments:false` for every unrecognized
|
|
subcommand, so `@house open`, `@house guest add X`, `@house abandon` never
|
|
reach ACE. Until `DoHouse` is ported, unrecognized `house` subcommands must
|
|
fall through to `SendServerCommandCmd`. **L**
|
|
5. **Verb-trailing-comma trim.** `@f, hi` / `@t Bob, hi` — retail right-trims `,`
|
|
off the verb in `DoCommand`. **L**
|
|
6. **`/tell` splits on the first comma, not the first space.** `ChatInputParser.cs:208`.
|
|
Multi-word names break today. **L**
|
|
|
|
### Tier 2 — pure-local commands, no wire work
|
|
|
|
7. `:` / `;` emote prefixes (`OnChatCommand` case 0x0B/0x0C). **L**
|
|
8. `@day` — daylight toggle + `SetPersistentAtDay`. **L**
|
|
9. `@endurance` — fixed help paragraph. **L**
|
|
10. `@speaker` — fixed deprecation line. **L**
|
|
11. `@title <text>` — chat window title. **L**
|
|
12. `@log [file]` — chat-to-file logging. **L**
|
|
13. `@version` — add the "Using Turbine Chat." line. **L**
|
|
14. `@render` — `SmartBox::HandleRenderOption` equivalent (acdream has no SmartBox;
|
|
map to the existing quality/debug toggles or leave as a documented divergence). **L**
|
|
15. `@help <command>` / `@help <group>` + the 7 group nodes (`commands`,
|
|
`allegiances`, `channels`, `chatting`, `death`, `status`, `text`). The exact
|
|
retail help strings for all 57 `Help*` functions are recoverable from the
|
|
binary — see §4 for the extraction recipe. **L**
|
|
16. **Missing aliases on already-implemented commands** (one dictionary edit
|
|
each): `pkl`, `hou`, `message_types`, `msgtypes`, `msg_types`, `rt`, `send`,
|
|
`whisper`, `w`, `vassal`, `covassal`, `co-vassals`, `c`, `fellows`, `group`,
|
|
`party`, `guild`, `gu`, `cg`, `ct`, `clfg`, `crp`, `soc`, `o`, `ab`.
|
|
Also delete the non-retail inventions `gen`, `cv`, `lookingforgroup`, `tr`,
|
|
`role`, `h`. **L**
|
|
|
|
### Tier 3 — wire messages acdream already has
|
|
|
|
17. `@chat on|off` and `@notell on|off` — both are `ModifyGlobalSquelch` with
|
|
message types 2 and 3. acdream already wires `ModifyGlobalSquelch` for
|
|
`/filter`. **W-have**
|
|
18. `@join` / `@leave <room>` — set `PlayerModule::SetHear*Chat`; these ride the
|
|
existing character-options path. **W-have**
|
|
|
|
### Tier 4 — new game actions (ACE-side handlers all exist)
|
|
|
|
19. `@permit add|remove <name>` — ACE `AddPlayerPermission` / `RemovePlayerPermission`. **W-new**
|
|
20. `@hslist <type>` — ACE `ListAvailableHouses`. **W-new**
|
|
21. `@index` / `@clist` / `@on` / `@off` — ACE `IndexChannels`, `ListChannels`,
|
|
`AddChannel`, `RemoveChannel`. **W-new** (four small parameterless/one-dword actions)
|
|
22. `@allegiance <sub>` — the 12-subcommand dispatcher. ACE handlers exist for
|
|
every one (`AllegianceInfoRequest`, `AllegianceChatBoot`, `AddAllegianceBan`,
|
|
`SetAllegianceOfficer`, `SetAllegianceOfficerTitle`, `RecallAllegianceHometown`,
|
|
`SetAllegianceName`, `DoAllegianceLockAction`, `DoAllegianceHouseAction`, …).
|
|
Largest single item; deserves its own slice. **W-new**
|
|
23. `@house <sub>` — 15 subcommands; ACE handlers exist (`SetOpenHouseStatus`,
|
|
`ChangeStoragePermission`, `BootSpecificHouseGuest`, `HouseBootAll`,
|
|
`AbandonHouse`, `HouseQuery`, `ModifyAllegianceGuestPermission`, …). **W-new**
|
|
24. `@motd [set|clear]` — allegiance MOTD. **W-new**
|
|
25. `@ab` — allegiance broadcast. **W-new**
|
|
26. `@alh` / `@ah` — `RecallAllegianceHometown`. **W-new**
|
|
27. Turbine-chat verbs as *Turbine* sends (`SendTurbineChat`) rather than plain
|
|
`ChannelBroadcast`. acdream already ships the 0xF7DE TurbineChat path, so
|
|
this is a routing decision, not new wire work. **W-have**
|
|
|
|
### Explicitly do NOT implement
|
|
|
|
- `@mr`, `@pr` — registered with a **null function pointer** in the 2013 build.
|
|
Retail ships them as help text only; the command itself falls through to the
|
|
server. Implementing them client-side would be a divergence.
|
|
- The 22 fallback-only channel tags (`abuse`, `admin`, `audit`, `advocate*`,
|
|
`sentinel`, `celhan`, `eldweb`, `radblo`, `ol`, …) are GM/faction channels.
|
|
They are reachable in retail only through `DoChannelCommand`'s fallback; the
|
|
cheapest faithful port is to add the `GetChannelID` table and let the existing
|
|
unknown-verb path consult it before falling back to `SendServerCommandCmd`.
|
|
- `@loadfile` — a client-side script player that re-enters `OnChatCommand` for
|
|
every line of an arbitrary file. Faithful, but it is a scripting surface;
|
|
acdream already has a designed plugin API for that, so porting `@loadfile`
|
|
should be an explicit product decision, not a parity checkbox.
|
|
|
|
---
|
|
|
|
## 4. Counts
|
|
|
|
| Measure | Count |
|
|
|---|---|
|
|
| Verbs registered by `InitializeCommands @ 0x00581970` | **116** |
|
|
| Verbs added by `StartupTurbineChatSystem @ 0x0057EFB0` | 15 (14 net-new; `a` is replaced) |
|
|
| **Total registered verbs** | **130** |
|
|
| …of which have a **null** `func` (help-only nodes) | 9 (`commands`, `allegiances`, `channels`, `chatting`, `death`, `status`, `text`, `mr`, `pr`) |
|
|
| Distinct handler functions behind those verbs | 68 (61 base + 7 Turbine) |
|
|
| Additional verbs reachable via the `GetChannelID` fallback (unregistered) | 22 |
|
|
| **Total client-parsed verbs** | **152** |
|
|
| Prefixes with special parsing | 4 (`/`, `@`, `:`, `;`) |
|
|
|
|
acdream, against the 130 registered verbs. Every verb is in exactly one row, so
|
|
the column sums to 130.
|
|
|
|
| Status | Verbs | Where |
|
|
|---|---|---|
|
|
| IMPLEMENTED — typed `ExecuteClientCommandCmd` | **45** (33 `ClientCommandId` values) | `RetailClientCommandCatalog.cs` + `ClientCommandController.cs` |
|
|
| PARTIAL via typed catalog | **1** — `house` (3 of 15 subcommands, and it swallows the rest) | `RetailClientCommandCatalog.cs:279` |
|
|
| IMPLEMENTED-EQUIVALENT — chat alias → `SendChatCmd` | **21** | `ChatInputParser.cs` |
|
|
| PARTIAL via chat alias (argument shape wrong) | **3** — `tell` (space-split, not comma-split), `reply` (by name, not guid), `retell` | `ChatInputParser.cs` |
|
|
| PARTIAL via local presentation | **2** — `help`, `?` (flat blob; no per-command or group help) | `ChatCommandRouter.cs:96` |
|
|
| DIVERGENT — bound, wrong target | **3** — `g`→General (retail Fellowship), `rp`→Roleplay (retail reply), `allegiance`→channel (retail command) | `ChatInputParser.cs` |
|
|
| **MISSING** | **55** | — |
|
|
| **Total registered** | **130** | |
|
|
|
|
Plus **22** unregistered `GetChannelID` fallback tags, all MISSING → **77**
|
|
client-parsed verbs unimplemented out of 152.
|
|
|
|
Reading note: the §2 tables mark `messagetypes`, `pklite` and `version` PARTIAL
|
|
because an *alias* or an *output line* is missing. The counts above are
|
|
per-verb, so those three verbs sit in IMPLEMENTED while their missing aliases
|
|
(`message_types`, `msgtypes`, `msg_types`, `pkl`) are counted as four separate
|
|
MISSING verbs.
|
|
|
|
Everything not in the table above reaches ACE correctly as
|
|
`SendServerCommandCmd` → Talk (`ChatCommandRouter.cs:62`), which matches retail's
|
|
own final fallback (`DoCommand` → `Event_Talk`) except that retail consults the
|
|
channel-tag table first.
|
|
|
|
acdream verbs with **no retail counterpart** (candidates for removal):
|
|
`/h`, `/gen`, `/cv`, `/lookingforgroup`, `/tr`, `/role`.
|
|
|
|
---
|
|
|
|
## 5. Reproduction recipes
|
|
|
|
Verb table (authoritative, resolves BN's mislabeled pooled strings):
|
|
|
|
```
|
|
# scan InitializeCommands for `push imm32` operands that point at short
|
|
# ASCII strings in .rdata — one per registered verb, in registration order
|
|
python pushes.py 581970 585520 # 116 verbs
|
|
python pushes.py 57efb0 57f9e0 # 15 Turbine verbs
|
|
```
|
|
|
|
Handler pairing: `sed -n '396604,399712p' acclient_2013_pseudo_c.txt` and read
|
|
the `CmdHashData::CmdHashData(ptr, &name, <func>, nullptr)` call (later blocks)
|
|
or the `*(esi + 8) = <func>` / `*(esi + 0x10) = <help>` stores (first ~22
|
|
blocks — BN renders the same code two different ways).
|
|
|
|
Exact retail help text for all 57 `Help*` functions: scan each
|
|
`ClientCommunicationSystem::Help*` symbol's byte extent for `push imm32` into
|
|
`.rdata` and read the C string. This recovered, verbatim, every usage line and
|
|
group listing quoted in §2 (e.g. the `@tell` comma requirement, the `@afk msg`
|
|
192-character limit, the `@hslist` house types).
|
|
|
|
`ChannelSystem::GetChannelID @ 0x005CF1F0`: alternate `push imm32` (tag) and
|
|
`mov eax, imm32` (channel id) down the function; the id immediately precedes the
|
|
next tag group.
|
|
|
|
Cross-checks performed: ACE `GameActionTalk.cs:21` (the `@`-passthrough
|
|
contract) and the `GameActionType` enumeration (confirms server-side handlers
|
|
exist for every Tier-4 item). holtburger's `commands.rs` was consulted and
|
|
**rejected as an oracle for aliases** — it binds `/g` to Allegiance and `/p` to
|
|
Fellowship, which matches neither retail nor acdream.
|