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>
12 KiB
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 @ 0x0056FCE0ClientCommunicationSystem::DoLockUI @ 0x005703B0ClientCommunicationSystem::DoFrameRate @ 0x005707D0ClientCommunicationSystem::DoPKArena @ 0x005788D0ClientCommunicationSystem::DoPKLArena @ 0x005789D0ClientCommunicationSystem::DoAge @ 0x0057C5A0ClientCommunicationSystem::DoBirth @ 0x0056E5F0CM_Character::Event_QueryAge @ 0x006A1620CM_Character::Event_QueryBirth @ 0x006A16F0CM_Character::Event_TeleToMarketplace @ 0x006A1C20CM_Character::Event_TeleToPKArena @ 0x006A1CB0CM_Character::Event_TeleToPKLArena @ 0x006A1D40CM_House::Event_TeleToHouse_Event @ 0x006AAFA0CM_House::Event_TeleToMansion_Event @ 0x006AB030CM_Character::DispatchUI_QueryAgeResponse @ 0x006A2E40ClientCommunicationSystem::Handle_Character__QueryAgeResponse @ 0x005711D0- retail command-table construction at
0x00581900..0x00585100
Independent protocol cross-check:
references/acclientlib/UtilityBelt.Common/MessageTypes.csreferences/acclientlib/UtilityBelt.Common/Enums/Enums.csreferences/acclientlib/UtilityBelt.Scripting/Enums/BusyAction.cs- ACE
Source/ACE.Server/Network/GameAction/Actions/handlers forSetAFKMode,ModifyCharacterSquelch,QueryAge,ConfirmationResponse,SetDesiredComponentLevel,AddFriend, and the teleport families - ACE
GameEventConfirmationRequest,GameEventFriendsListUpdate, andNetwork/Structure/SquelchDB/SquelchInfowriters
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.
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)
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
marketplace(arguments):
if no arguments: send marketplace action
else: display local usage
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.
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
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 @ 0x0057A250Position::ToString @ 0x005A9330ClientCommunicationSystem::DoCorpse @ 0x00578220LandDefs::CellidToCoordinateString @ 0x005A9CB0
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}"
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 @ 0x0056E600ClientCommunicationSystem::DoSaveUI @ 0x0056FFF0ClientCommunicationSystem::DoLoadUI @ 0x00570150ClientCommunicationSystem::DoSaveAutoUI @ 0x005702B0ClientCommunicationSystem::DoLoadAutoUI @ 0x00570330
clear(arguments):
source = current_chat_source
if first argument equals "all", ignoring case: source = all_sources
clear(source)
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 @ 0x0057BC00ClientCommunicationSystem::DoFriendsAdd @ 0x00578FB0ClientCommunicationSystem::DoFriendsRemove @ 0x00579080gmFriendsUI::Request_AddFriend @ 0x0048D240CM_Login::AddFriend @ 0x006A3020CM_Login::RemoveFriend @ 0x006A30A0CM_Login::ClearFriends @ 0x006A3110
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 @ 0x0057B3F0ClientCommunicationSystem::DoConsent @ 0x0057DDA0ClientCommunicationSystem::ProcessSquelchArgs @ 0x00579C30ClientCommunicationSystem::DoSquelch @ 0x0057BF50ClientCommunicationSystem::DoUnSquelch @ 0x0057C070ClientCommunicationSystem::DoFilter @ 0x0057C860ClientCommunicationSystem::DoUnFilter @ 0x0057C880ClientCommunicationSystem::PerformGlobalSquelchMod @ 0x0057C2D0LogTextTypeEnumMapper::IsLegalChannel @ 0x006AFF40
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
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.
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 @ 0x00578AD0CM_Communication::Event_Emote @ 0x006A4120ClientCommunicationSystem::DoFillComponents @ 0x0056FD50gmVendorUI::FillComponentList @ 0x004C4540MagicEnumMapper::SpellComponentCategoryFromString @ 0x0056E290
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
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.