feat(chat): Campaign CH slice CH4 — command registry completion
Brings acdream's / and @ command parsing to parity with the complete
retail registry (130 registered verbs + 22 unregistered GetChannelID
fallback tags = 152 client-parsed verbs), per
docs/research/2026-08-09-chat-retail-command-registry.md.
Parser semantics (retail OnChatCommand/DoCommand):
- : and ; rewrite to "@emote <rest>" before dispatch.
- Verb trailing-comma trim ("@f, hi" == "@f hi") applied at every
verb-lookup site in the catalog and the parser.
- @tell/aliases split the target on the FIRST COMMA, not the first
whitespace token, so multi-word names work ("@tell Aunt Agatha, hi").
- The 22 unregistered GM/faction channel tags (admin, sentinel,
celestialhand, ...) now broadcast for real via a new
RetailChannelTagTable + SendRawChannelCmd bypass, reusing the existing
BuildChatChannel wire builder.
Binding corrections:
- /g, /group, /party -> Fellowship (0x800), not General.
- /rp -> reply alias (retail's own help text confirms "@r or @rp"), not
Roleplay; /role (an acdream invention) deleted.
- /allegiance, /all -> the allegiance management command
(RetailClientCommandCatalog), not a channel verb.
- /house no longer swallows unrecognized subcommands with a local usage
error; they now correctly fall through to ACE.
- @mr/@pr pinned as permanently non-executable (retail registers them
with a null function pointer).
New verbs with real local execution: endurance, speaker, title (silent,
AP-182), chat, notell, join, leave, permit, hslist, index, clist, on,
off, alh/ah (+ "@allegiance hometown"/"ho"), "@allegiance info",
"@house abandon"; a missing-alias sweep across 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; the non-retail
inventions gen/cv/lookingforgroup/tr/role/h are deleted. New Core.Net
wire builders (IndexChannels, ListChannels, AddChannel, RemoveChannel,
RecallAllegianceHometown, AllegianceInfoRequest, ListAvailableHouses,
AddPlayerPermission, RemovePlayerPermission, AbandonHouse) are all
parameterless or single-field payloads cross-checked against ACE's
GameAction readers, not guessed.
Deferred (filed as #360/#361/#362, register rows TS-68/TS-69/TS-70):
the ~22 remaining allegiance/house subcommands + standalone @motd
(largest single item, needs its own slice per the doc), the three
still-inert pure-local commands (day/log/render), and the inbound
GameEvent responses for the new outbound requests. All correctly fall
through to ACE server-passthrough rather than being silently swallowed
or faking success.
RetailCommandRegistryConformanceTests pins the complete 152-verb
registry against production: every verb resolves through exactly one
production surface if Implemented, through none if HelpOnly/
ServerPassthrough, and two reverse-direction tests fail the build if
RetailClientCommandCatalog or ChatInputParser ever claims a verb
outside this registry again. Final tally: 138 Implemented / 5
ServerPassthrough / 9 HelpOnly = 152.
Release suite: 12,190 passed / 4 skipped / 0 failed (up from CH3's
11,964/4/0).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9247d5d5b5
commit
090825e703
23 changed files with 2069 additions and 97 deletions
|
|
@ -51,12 +51,13 @@ public static class RetailClientCommandCatalog
|
|||
"/pklarena (/pla) - Teleports a PKLite player to the PKLite Arena.");
|
||||
|
||||
// ClientCommunicationSystem::DoPKLite/HelpPKLite @ 0x0057A490/0x0057A540.
|
||||
// Retail registers exactly one verb string ("pklite" @ 0x007E16B0) — no
|
||||
// alias, unlike most of this catalog.
|
||||
// Retail registers exactly one verb string ("pklite" @ 0x007E16B0) —
|
||||
// Campaign CH slice CH4 (2026-08-09) added the "pkl" alias per the
|
||||
// command-registry doc §2.6.
|
||||
private static readonly Definition PkLite = NoArguments(
|
||||
ClientCommandId.EnterPkLite,
|
||||
"/pklite",
|
||||
"@pklite - Sets your status to Player Killer Lite. Type @help pklite for more details.");
|
||||
"@pklite (@pkl) - Sets your status to Player Killer Lite. Type @help pklite for more details.");
|
||||
|
||||
private static readonly Definition HouseRecall = NoArguments(
|
||||
ClientCommandId.HouseRecall,
|
||||
|
|
@ -68,6 +69,13 @@ public static class RetailClientCommandCatalog
|
|||
"/house mansion_recall",
|
||||
"/house mansion_recall (/hom, /hoa) - Teleports you to your allegiance mansion.");
|
||||
|
||||
// GameActionHouseAbandon.Handle / retail help data_7dd3d0:
|
||||
// "@house abandon - Abandons your house.\n"
|
||||
private static readonly Definition HouseAbandon = NoArguments(
|
||||
ClientCommandId.HouseAbandon,
|
||||
"/house abandon",
|
||||
"@house abandon - Abandons your house.");
|
||||
|
||||
private static readonly Definition QueryAge = NoArguments(
|
||||
ClientCommandId.QueryAge,
|
||||
"/age",
|
||||
|
|
@ -192,16 +200,198 @@ public static class RetailClientCommandCatalog
|
|||
"/unfilter -<message type>",
|
||||
"/unfilter -<message type> - Shows a globally hidden message category.");
|
||||
|
||||
// Campaign CH slice CH4 (2026-08-09): retail registers exactly one
|
||||
// handler (DoMessageTypes @ 0x0057A010) under four verb strings —
|
||||
// "messagetypes", "message_types", "msgtypes", "msg_types" — all
|
||||
// aliases of the SAME definition, not separate commands.
|
||||
private static readonly Definition MessageTypes = NoArguments(
|
||||
ClientCommandId.ListMessageTypes,
|
||||
"/messagetypes",
|
||||
"/messagetypes - Lists valid filter and squelch message types.");
|
||||
"/messagetypes (/message_types, /msgtypes, /msg_types) - Lists valid filter and squelch message types.");
|
||||
|
||||
private static readonly Definition FillComponents = AnyArguments(
|
||||
ClientCommandId.FillComponents,
|
||||
"/fillcomps [component type] [pyreal value]",
|
||||
"/fillcomps - Helps you buy components in bulk.");
|
||||
|
||||
// ── Campaign CH slice CH4 (2026-08-09) additions ────────────────────
|
||||
|
||||
// ClientCommunicationSystem::DoEndurance @ 0x0057C5F0. Exact retail
|
||||
// text extracted from acclient_2013_pseudo_c.txt:1031097
|
||||
// (data_7de2f8) — the first paragraph only; the full multi-paragraph
|
||||
// block is reproduced verbatim by DoEndurance itself and is long
|
||||
// enough that only the opening line is duplicated here as a teaser —
|
||||
// BuildHelpText below uses the SAME text callers already see through
|
||||
// ClientCommandController.
|
||||
private static readonly Definition Endurance = NoArguments(
|
||||
ClientCommandId.Endurance,
|
||||
"/endurance",
|
||||
"The endurance attribute has a number of abilities tied to it. Type @help endurance for the full description.");
|
||||
|
||||
// ClientCommunicationSystem::DoSpeaker @ 0x0057DAB0. Exact retail text:
|
||||
// acclient_2013_pseudo_c.txt:393309 / 1031426 (data_7e0cd8).
|
||||
private static readonly Definition Speaker = NoArguments(
|
||||
ClientCommandId.Speaker,
|
||||
"/speaker",
|
||||
"This command is no longer in use, please see @allegiance officer.");
|
||||
|
||||
// ClientCommunicationSystem::DoTitle @ 0x0057A640. Exact retail help:
|
||||
// acclient_2013_pseudo_c.txt:1031162 (data_7df2c4) — "@title <new
|
||||
// title> - Sets the title of the popup chat window.\n". No confirmation
|
||||
// text was found at the success site; acdream stores the title but has
|
||||
// no title-bar chrome to render it yet (AP-182).
|
||||
private static readonly Definition SetTitle = AnyArguments(
|
||||
ClientCommandId.SetChatTitle,
|
||||
"/title <new title>",
|
||||
"@title <new title> - Sets the title of the popup chat window.");
|
||||
|
||||
// ClientCommunicationSystem::DoChatToggle @ 0x0056FAD0 — Event_
|
||||
// ModifyGlobalSquelch(remove, 2) for "on", (add, 2) for "off". Exact
|
||||
// retail help: acclient_2013_pseudo_c.txt:1030716/1030720.
|
||||
private static readonly Definition ChatToggle = new(
|
||||
ClientCommandId.ChatToggle,
|
||||
Usage: "/chat <on|off>",
|
||||
HelpText: "@chat <on/off> - Sets whether or not you receive normal chat. When set to \"off\", you will no longer receive any spoken speech (normal chat). However, you will still receive tells.",
|
||||
ValidateArguments: static arguments =>
|
||||
arguments.Equals("on", StringComparison.OrdinalIgnoreCase)
|
||||
|| arguments.Equals("off", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
// ClientCommunicationSystem::DoNoTell @ 0x0056FBD0 — same mechanism,
|
||||
// message type 3 (Tell). Exact retail help:
|
||||
// acclient_2013_pseudo_c.txt:1030724/1030728.
|
||||
private static readonly Definition NoTellToggle = new(
|
||||
ClientCommandId.NoTellToggle,
|
||||
Usage: "/notell <on|off>",
|
||||
HelpText: "@notell <on/off> - Sets whether or not you receive @tells. When set to \"on\", you will not receive any tells.",
|
||||
ValidateArguments: static arguments =>
|
||||
arguments.Equals("on", StringComparison.OrdinalIgnoreCase)
|
||||
|| arguments.Equals("off", StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
/// <summary>
|
||||
/// Tags accepted by <c>@join</c>/<c>@leave</c> mapped to the linear
|
||||
/// <c>SetSingleCharacterOption (0x0005)</c> option id — retail
|
||||
/// PlayerModule::SetHear*Chat flags, per
|
||||
/// <c>AcDream.Core.Net.Messages.CharacterOptionId</c>.
|
||||
/// </summary>
|
||||
public static bool TryResolveJoinLeaveOption(string tag, out uint optionId) =>
|
||||
JoinLeaveTags.TryGetValue(tag.Trim(), out optionId);
|
||||
|
||||
private static readonly FrozenDictionary<string, uint> JoinLeaveTags =
|
||||
new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["allegiance"] = 0x1Bu, // CharacterOptionId.ListenToAllegianceChat
|
||||
["general"] = 0x23u, // CharacterOptionId.ListenToGeneralChat
|
||||
["trade"] = 0x24u, // CharacterOptionId.ListenToTradeChat
|
||||
["lfg"] = 0x25u, // CharacterOptionId.ListenToLFGChat
|
||||
["roleplay"] = 0x26u, // CharacterOptionId.ListenToRoleplayChat
|
||||
["society"] = 0x2Eu, // CharacterOptionId.ListenToSocietyChat
|
||||
["soc"] = 0x2Eu,
|
||||
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// ClientCommunicationSystem::DoJoinChat/DoLeaveChat @ 0x0056F510/
|
||||
// 0x0056F7F0. Tags per the registry doc §2.2: Allegiance, General,
|
||||
// Trade, LFG, Roleplay, Society, Soc. Exact retail help:
|
||||
// acclient_2013_pseudo_c.txt:1030689/1030693.
|
||||
private static readonly Definition JoinChannel = new(
|
||||
ClientCommandId.JoinChannel,
|
||||
Usage: "/join <channel tag>",
|
||||
HelpText: "@join <channel tag> - Allows you to hear and speak on the given channel.",
|
||||
ValidateArguments: static arguments => JoinLeaveTags.ContainsKey(arguments.Trim()));
|
||||
|
||||
private static readonly Definition LeaveChannel = new(
|
||||
ClientCommandId.LeaveChannel,
|
||||
Usage: "/leave <channel tag>",
|
||||
HelpText: "@leave <channel tag> - Prevents you from hearing or speaking on the given channel.",
|
||||
ValidateArguments: static arguments => JoinLeaveTags.ContainsKey(arguments.Trim()));
|
||||
|
||||
// ClientCommunicationSystem::DoPermit @ 0x005785A0. Exact retail help:
|
||||
// acclient_2013_pseudo_c.txt:1030850-1030852 (data_7dbac8).
|
||||
private static readonly Definition Permit = new(
|
||||
ClientCommandId.Permit,
|
||||
Usage: "/permit <add|remove> <name>",
|
||||
HelpText: "@permit add <name> - Allows another player to loot your corpse. @permit remove <name> - Removes permission to access your corpse from the named character.",
|
||||
ValidateArguments: static arguments =>
|
||||
{
|
||||
string[] parts = arguments.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries);
|
||||
return parts.Length == 2
|
||||
&& (parts[0].Equals("add", StringComparison.OrdinalIgnoreCase)
|
||||
|| parts[0].Equals("remove", StringComparison.OrdinalIgnoreCase));
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Retail house-type spellings mapped to ACE's <c>HouseType</c> enum
|
||||
/// value (the <c>@hslist</c>/ListAvailableHouses payload).
|
||||
/// </summary>
|
||||
public static bool TryResolveHouseType(string type, out uint houseType) =>
|
||||
HouseTypes.TryGetValue(type.Trim(), out houseType);
|
||||
|
||||
private static readonly FrozenDictionary<string, uint> HouseTypes =
|
||||
new Dictionary<string, uint>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
["cottage"] = 1u,
|
||||
["villa"] = 2u,
|
||||
["mansion"] = 3u,
|
||||
["apartment"] = 4u,
|
||||
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// ClientCommunicationSystem::DoHouseAvailableList @ 0x00570510. Exact
|
||||
// retail help: acclient_2013_pseudo_c.txt:1031049 (data_7dd9d0).
|
||||
private static readonly Definition HouseAvailableList = new(
|
||||
ClientCommandId.HouseAvailableList,
|
||||
Usage: "/hslist <house type>",
|
||||
HelpText: "@hslist <house type> - Lists the number and, if appropriate, positions of houses currently available for purchase. Types include: Apartment, Cottage, Villa, Mansion",
|
||||
ValidateArguments: static arguments => HouseTypes.ContainsKey(arguments.Trim()));
|
||||
|
||||
// ClientCommunicationSystem::DoChannelIndex @ 0x0056E640. No help
|
||||
// string was extracted for the bare form; the verb is admin/advocate/
|
||||
// PSR gated server-side (GameActionChannelIndex.Handle).
|
||||
private static readonly Definition IndexChannels = NoArguments(
|
||||
ClientCommandId.IndexChannels,
|
||||
"/index",
|
||||
"@index - Requests the channel index (restricted).");
|
||||
|
||||
// ClientCommunicationSystem::DoChannelList @ 0x0057A9B0. Exact retail
|
||||
// no-arg text: acclient_2013_pseudo_c.txt:1031202 (data_7dfaf8)
|
||||
// "Please specify the channel name."
|
||||
private static readonly Definition ListChannel = new(
|
||||
ClientCommandId.ListChannel,
|
||||
Usage: "/clist <channel>",
|
||||
HelpText: "@clist <channel> - Requests the member list of a channel (restricted).",
|
||||
ValidateArguments: static arguments => RetailChannelTagTable.TryResolve(arguments.Trim(), out _),
|
||||
InvalidArgumentsText: "Please specify the channel name.");
|
||||
|
||||
private static readonly Definition OnChannel = new(
|
||||
ClientCommandId.OnChannel,
|
||||
Usage: "/on <channel>",
|
||||
HelpText: "@on <channel> - Joins a channel (restricted).",
|
||||
ValidateArguments: static arguments => RetailChannelTagTable.TryResolve(arguments.Trim(), out _),
|
||||
InvalidArgumentsText: "Please specify the channel name.");
|
||||
|
||||
private static readonly Definition OffChannel = new(
|
||||
ClientCommandId.OffChannel,
|
||||
Usage: "/off <channel>",
|
||||
HelpText: "@off <channel> - Leaves a channel (restricted).",
|
||||
ValidateArguments: static arguments => RetailChannelTagTable.TryResolve(arguments.Trim(), out _),
|
||||
InvalidArgumentsText: "Please specify the channel name.");
|
||||
|
||||
// GameActionRecallAllegianceHometown.Handle. Exact retail help:
|
||||
// acclient_2013_pseudo_c.txt:1031230 — "@allegiance hometown -
|
||||
// Recalls you to your allegiance bindstone, if your allegiance has
|
||||
// tied to one.\n"
|
||||
private static readonly Definition AllegianceHometown = NoArguments(
|
||||
ClientCommandId.AllegianceHometown,
|
||||
"/alh",
|
||||
"@allegiance hometown (@alh, @ah) - Recalls you to your allegiance bindstone, if your allegiance has tied to one.");
|
||||
|
||||
// GameActionAllegianceInfoRequest.Handle — String16L name, empty = self.
|
||||
// Exact retail help: acclient_2013_pseudo_c.txt:1031214 —
|
||||
// "@allegiance info <name> - Requests information on a member of your
|
||||
// allegiance.\n"
|
||||
private static readonly Definition AllegianceInfo = AnyArguments(
|
||||
ClientCommandId.AllegianceInfo,
|
||||
"/allegiance info [name]",
|
||||
"@allegiance info <name> - Requests information on a member of your allegiance.");
|
||||
|
||||
private static readonly FrozenDictionary<string, Definition> ByVerb =
|
||||
new Dictionary<string, Definition>(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
|
|
@ -216,6 +406,7 @@ public static class RetailClientCommandCatalog
|
|||
["pklarena"] = PkLiteArena,
|
||||
["pla"] = PkLiteArena,
|
||||
["pklite"] = PkLite,
|
||||
["pkl"] = PkLite,
|
||||
["hor"] = HouseRecall,
|
||||
["hr"] = HouseRecall,
|
||||
["hom"] = MansionRecall,
|
||||
|
|
@ -249,7 +440,25 @@ public static class RetailClientCommandCatalog
|
|||
["filter"] = Filter,
|
||||
["unfilter"] = Unfilter,
|
||||
["messagetypes"] = MessageTypes,
|
||||
["message_types"] = MessageTypes,
|
||||
["msgtypes"] = MessageTypes,
|
||||
["msg_types"] = MessageTypes,
|
||||
["fillcomps"] = FillComponents,
|
||||
["endurance"] = Endurance,
|
||||
["speaker"] = Speaker,
|
||||
["title"] = SetTitle,
|
||||
["chat"] = ChatToggle,
|
||||
["notell"] = NoTellToggle,
|
||||
["join"] = JoinChannel,
|
||||
["leave"] = LeaveChannel,
|
||||
["permit"] = Permit,
|
||||
["hslist"] = HouseAvailableList,
|
||||
["index"] = IndexChannels,
|
||||
["clist"] = ListChannel,
|
||||
["on"] = OnChannel,
|
||||
["off"] = OffChannel,
|
||||
["alh"] = AllegianceHometown,
|
||||
["ah"] = AllegianceHometown,
|
||||
}.ToFrozenDictionary(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -271,35 +480,28 @@ public static class RetailClientCommandCatalog
|
|||
string verb = separator < 0
|
||||
? trimmed[1..]
|
||||
: trimmed.Substring(1, separator - 1);
|
||||
// Retail DoCommand @ 0x0057E2E0 right-trims ',' off the verb token
|
||||
// before lookup (trim char set 0x0079452C) — "@f, hi" resolves the
|
||||
// SAME as "@f hi". Campaign CH slice CH4 (2026-08-09).
|
||||
verb = verb.TrimEnd(',');
|
||||
string arguments = separator < 0
|
||||
? string.Empty
|
||||
: trimmed[(separator + 1)..].Trim();
|
||||
|
||||
Definition? definition;
|
||||
if (verb.Equals("house", StringComparison.OrdinalIgnoreCase))
|
||||
if (verb.Equals("house", StringComparison.OrdinalIgnoreCase)
|
||||
|| verb.Equals("hou", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
definition = arguments.ToLowerInvariant() switch
|
||||
{
|
||||
"recall" => HouseRecall,
|
||||
"mansion_recall" or "alleg_recall" => MansionRecall,
|
||||
_ => null,
|
||||
};
|
||||
if (definition is null)
|
||||
{
|
||||
match = new Match(
|
||||
ClientCommandId.HouseRecall,
|
||||
arguments,
|
||||
"/house recall | /house mansion_recall",
|
||||
HasValidArguments: false,
|
||||
InvalidArgumentsText: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The subcommand selected the operation; its own handler has no
|
||||
// additional arguments.
|
||||
arguments = string.Empty;
|
||||
return TryMatchHouse(arguments, out match);
|
||||
}
|
||||
else if (!ByVerb.TryGetValue(verb, out definition))
|
||||
|
||||
if (verb.Equals("allegiance", StringComparison.OrdinalIgnoreCase)
|
||||
|| verb.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return TryMatchAllegiance(arguments, out match);
|
||||
}
|
||||
|
||||
if (!ByVerb.TryGetValue(verb, out definition))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -313,6 +515,85 @@ public static class RetailClientCommandCatalog
|
|||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>@house <sub></c> / <c>@hou <sub></c> dispatcher.
|
||||
/// Retail's real <c>DoHouse @ 0x00580860</c> handles 15 subcommands
|
||||
/// (see the registry doc §2.5b) locally; acdream Campaign CH slice CH4
|
||||
/// (2026-08-09) ports 4 of them (recall/re, mansion_recall/alleg_recall/
|
||||
/// ma, abandon) plus the pre-existing HasValidArguments==false swallow
|
||||
/// for a MISSPELLED recall variant. Every OTHER subcommand — open,
|
||||
/// close, storage, remove, boot, boot_all, remove_all, guest, available,
|
||||
/// hooks, on, off — is NOT yet ported (TS-68) and must reach ACE
|
||||
/// (which replies "Unknown command") rather than being swallowed
|
||||
/// locally with a wrong usage message — the Tier-1 #4 fix from the
|
||||
/// command-registry doc. Returning <c>false</c> here lets
|
||||
/// <see cref="ChatCommandRouter"/> fall through to server passthrough.
|
||||
/// </summary>
|
||||
private static bool TryMatchHouse(string arguments, out Match match)
|
||||
{
|
||||
match = default;
|
||||
string subcommand = arguments.ToLowerInvariant();
|
||||
Definition? definition = subcommand switch
|
||||
{
|
||||
"recall" or "re" => HouseRecall,
|
||||
"mansion_recall" or "alleg_recall" or "ma" => MansionRecall,
|
||||
"abandon" => HouseAbandon,
|
||||
_ => null,
|
||||
};
|
||||
if (definition is null)
|
||||
return false;
|
||||
|
||||
match = new Match(
|
||||
definition.Command,
|
||||
Arguments: string.Empty,
|
||||
definition.Usage,
|
||||
HasValidArguments: true,
|
||||
InvalidArgumentsText: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <c>@allegiance <sub></c> / <c>@all <sub></c> dispatcher.
|
||||
/// Retail's real <c>DoAllegiance @ 0x0057D5A0</c> handles 12
|
||||
/// subcommands (see the registry doc §2.5) locally; acdream Campaign CH
|
||||
/// slice CH4 (2026-08-09) ports 2 of them (info, hometown/ho). Every
|
||||
/// OTHER subcommand — boot, ban, officer, title, name, lock, house,
|
||||
/// motd, chat, broadcast — is NOT yet ported (TS-68) and falls through
|
||||
/// to server passthrough, same reasoning as <see cref="TryMatchHouse"/>.
|
||||
/// </summary>
|
||||
private static bool TryMatchAllegiance(string arguments, out Match match)
|
||||
{
|
||||
match = default;
|
||||
int separator = IndexOfWhitespace(arguments);
|
||||
string subcommand = separator < 0 ? arguments : arguments[..separator];
|
||||
string rest = separator < 0 ? string.Empty : arguments[(separator + 1)..].Trim();
|
||||
|
||||
if (subcommand.Equals("hometown", StringComparison.OrdinalIgnoreCase)
|
||||
|| subcommand.Equals("ho", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
match = new Match(
|
||||
AllegianceHometown.Command,
|
||||
Arguments: string.Empty,
|
||||
AllegianceHometown.Usage,
|
||||
HasValidArguments: true,
|
||||
InvalidArgumentsText: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subcommand.Equals("info", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
match = new Match(
|
||||
AllegianceInfo.Command,
|
||||
rest,
|
||||
AllegianceInfo.Usage,
|
||||
HasValidArguments: true,
|
||||
InvalidArgumentsText: null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>Help line generated from the same definition routing uses.</summary>
|
||||
public static string BuildHelpText() => string.Join("\n ",
|
||||
Lifestone.HelpText,
|
||||
|
|
@ -322,6 +603,7 @@ public static class RetailClientCommandCatalog
|
|||
PkLite.HelpText,
|
||||
HouseRecall.HelpText,
|
||||
MansionRecall.HelpText,
|
||||
HouseAbandon.HelpText,
|
||||
QueryAge.HelpText,
|
||||
QueryBirth.HelpText,
|
||||
FrameRate.HelpText,
|
||||
|
|
@ -345,7 +627,65 @@ public static class RetailClientCommandCatalog
|
|||
Filter.HelpText,
|
||||
Unfilter.HelpText,
|
||||
MessageTypes.HelpText,
|
||||
FillComponents.HelpText);
|
||||
FillComponents.HelpText,
|
||||
Endurance.HelpText,
|
||||
Speaker.HelpText,
|
||||
SetTitle.HelpText,
|
||||
ChatToggle.HelpText,
|
||||
NoTellToggle.HelpText,
|
||||
JoinChannel.HelpText,
|
||||
LeaveChannel.HelpText,
|
||||
Permit.HelpText,
|
||||
HouseAvailableList.HelpText,
|
||||
AllegianceHometown.HelpText,
|
||||
AllegianceInfo.HelpText);
|
||||
|
||||
/// <summary>
|
||||
/// Every verb string this catalog dispatches, INCLUDING the
|
||||
/// specially-parsed "house"/"hou"/"allegiance"/"all" verbs (which are
|
||||
/// not literal keys of the backing dictionary because their dispatch
|
||||
/// depends on the subcommand). Used by the CH4 conformance test to
|
||||
/// enforce the ownership rule in both directions: every retail-registry
|
||||
/// verb this catalog claims must actually be in the registry, and vice
|
||||
/// versa.
|
||||
/// </summary>
|
||||
public static IReadOnlyCollection<string> KnownVerbs { get; } =
|
||||
ByVerb.Keys.Concat(["house", "hou", "allegiance", "all"]).ToArray();
|
||||
|
||||
/// <summary>
|
||||
/// <c>/help <verb></c> lookup for a catalog-dispatched command —
|
||||
/// retail's <c>DoHelp @ 0x0057F9E0</c> looking up the verb's registered
|
||||
/// help callback. Does not cover chat-alias or channel verbs (see
|
||||
/// <see cref="RetailCommandHelpTable"/> for those) nor the deferred
|
||||
/// allegiance/house subcommand overviews (also
|
||||
/// <see cref="RetailCommandHelpTable"/>).
|
||||
/// </summary>
|
||||
public static bool TryGetHelpText(string verb, out string helpText)
|
||||
{
|
||||
string trimmedVerb = verb.TrimEnd(',');
|
||||
if (trimmedVerb.Equals("house", StringComparison.OrdinalIgnoreCase)
|
||||
|| trimmedVerb.Equals("hou", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
helpText = RetailCommandHelpTable.HouseOverview;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (trimmedVerb.Equals("allegiance", StringComparison.OrdinalIgnoreCase)
|
||||
|| trimmedVerb.Equals("all", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
helpText = RetailCommandHelpTable.AllegianceOverview;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ByVerb.TryGetValue(trimmedVerb, out Definition? definition))
|
||||
{
|
||||
helpText = definition.HelpText;
|
||||
return true;
|
||||
}
|
||||
|
||||
helpText = string.Empty;
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Definition NoArguments(
|
||||
ClientCommandId command, string usage, string helpText) =>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue