fix(chat): CH4 review fixes — allegiance ownership guard, house-abandon confirmation
Blocker 1: an unrecognized "@allegiance <sub>" subcommand escaped TryMatchAllegiance (which only claimed "info"/"hometown") and fell through the unregistered-tag channel fallback, broadcasting the raw subcommand text to the Allegiance chat channel (0x02000000). Retail's own DoAllegiance never reaches DoChannelCommand for an unrecognized subcommand — it claims the whole verb and prints its own client-local refusal. TryMatchAllegiance now claims "allegiance"/"all" unconditionally and shows retail's "Please see @help Allegiance..." text; ChatCommandRouter also gained a blanket RetailClientCommandCatalog.KnownVerbs ownership guard in TryDispatchChannelFallback as defense in depth. Blocker 2: "@house abandon" sent 0x021F immediately with no confirmation. Retail runs a real two-stage dialog before Event_AbandonHouse(); ported both verbatim strings and chained two ShowConfirmation calls. Should-fixes: a bare unregistered tag with no text now passes through silently instead of showing a refusal that belongs to a different retail function; @join/@leave update RuntimeCharacterOptionsState locally (new SetOptionBit) before the wire push so the Turbine membership gate stops refusing a just-joined room; @permit accepts multi-word names; @clist/ @on/@off validate shape only and raise WeenieError 0x422 for an unknown tag; @mr/@pr help text is now the verbatim retail strings; corrected issue #360, register row TS-68, the campaign doc's B.7 note, and a stale RetailChannelTagTable comment; filed issue #363 + register row AP-183 for the deferred error-typing debt. Nits: fixed TryMatchHouse's stale doc comment, the AP-182/@title "stores the value" comments (the binding is a no-op), IsUnregisteredFallbackTag's olthoi false-positive, added /g and /rp binding-level conformance pins, made @index ignore extra arguments, and noted the six removed invented verbs in ISSUES.md. Suite: 12,216 passed / 4 skipped / 0 failed (Release), up from CH4's 12,190/4/0 — net +26 tests, no removals. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
090825e703
commit
724ef2d389
17 changed files with 853 additions and 85 deletions
|
|
@ -256,9 +256,13 @@ public sealed class ClientCommandController
|
|||
"This command is no longer in use, please see @allegiance officer.");
|
||||
break;
|
||||
// ClientCommunicationSystem::DoTitle @ 0x0057A640. No local
|
||||
// chat-window title chrome exists yet (AP-182) — the value is
|
||||
// stored for a future consumer, matching retail's silent
|
||||
// success (no confirmation text was found at the success site).
|
||||
// chat-window title chrome exists yet (AP-182) — the binding is
|
||||
// a pure no-op, matching retail's silent success (no
|
||||
// confirmation text was found at the success site). Corrected
|
||||
// 2026-08-09 at the CH4 REJECT-review nit 11: the earlier
|
||||
// wording here claimed the value "is stored for a future
|
||||
// consumer", which was false — see LiveSessionRuntimeFactory's
|
||||
// SetChatTitle binding (`_ => { }`).
|
||||
case ClientCommandId.SetChatTitle:
|
||||
_bindings.SetChatTitle(command.Arguments.Trim());
|
||||
break;
|
||||
|
|
@ -300,20 +304,32 @@ public sealed class ClientCommandController
|
|||
case ClientCommandId.IndexChannels:
|
||||
_bindings.RequestChannelIndex();
|
||||
break;
|
||||
// ClientCommunicationSystem::DoChannelList @ 0x0057A9B0.
|
||||
// ClientCommunicationSystem::DoChannelList @ 0x0057A9B0. CH4
|
||||
// REJECT-review SHOULD-FIX 6 (2026-08-09): the catalog only
|
||||
// validated argument SHAPE (exactly one token); an unresolved
|
||||
// tag reaches here and raises retail's own
|
||||
// HandleFailureEvent(0x422) ("That channel doesn't exist.").
|
||||
case ClientCommandId.ListChannel:
|
||||
if (RetailChannelTagTable.TryResolve(command.Arguments.Trim(), out uint listChannelId))
|
||||
_bindings.RequestChannelList(listChannelId);
|
||||
else
|
||||
_bindings.ShowWeenieError(0x0422u);
|
||||
break;
|
||||
// ClientCommunicationSystem::DoChannelOn @ 0x0057AA80.
|
||||
// ClientCommunicationSystem::DoChannelOn @ 0x0057AA80. Same
|
||||
// 0x422 shape as ListChannel above.
|
||||
case ClientCommandId.OnChannel:
|
||||
if (RetailChannelTagTable.TryResolve(command.Arguments.Trim(), out uint onChannelId))
|
||||
_bindings.JoinGmChannel(onChannelId);
|
||||
else
|
||||
_bindings.ShowWeenieError(0x0422u);
|
||||
break;
|
||||
// ClientCommunicationSystem::DoChannelOff @ 0x0057AB50.
|
||||
// ClientCommunicationSystem::DoChannelOff @ 0x0057AB50. Same
|
||||
// 0x422 shape as ListChannel above.
|
||||
case ClientCommandId.OffChannel:
|
||||
if (RetailChannelTagTable.TryResolve(command.Arguments.Trim(), out uint offChannelId))
|
||||
_bindings.LeaveGmChannel(offChannelId);
|
||||
else
|
||||
_bindings.ShowWeenieError(0x0422u);
|
||||
break;
|
||||
// GameActionRecallAllegianceHometown — @alh/@ah/"@allegiance hometown".
|
||||
case ClientCommandId.AllegianceHometown:
|
||||
|
|
@ -323,9 +339,34 @@ public sealed class ClientCommandController
|
|||
case ClientCommandId.AllegianceInfo:
|
||||
_bindings.RequestAllegianceInfo(command.Arguments.Trim());
|
||||
break;
|
||||
// GameActionHouseAbandon — "@house abandon".
|
||||
// GameActionHouseAbandon — "@house abandon". Retail's abandon
|
||||
// branch (DoHouse @ 0x00580D58) opens a FIRST confirmation
|
||||
// dialog (DialogFactory::MakeCallbackDialogInCurrentUI →
|
||||
// HouseAbandonDialogCallback_First @0x00580E1A); only on
|
||||
// accept does that callback open a SECOND dialog
|
||||
// (HouseAbandonDialogCallback_Second @0x0057BE90), and only
|
||||
// THAT callback's accept calls Event_AbandonHouse()
|
||||
// (0x0057BF01 — the ONLY call site). Both strings recovered
|
||||
// verbatim from acclient_2013_pseudo_c.txt (data_7e1460 /
|
||||
// data_7e1370). CH4 REJECT-review Blocker 2 (2026-08-09):
|
||||
// acdream previously sent 0x021F immediately with NO
|
||||
// confirmation at all.
|
||||
case ClientCommandId.HouseAbandon:
|
||||
_bindings.AbandonHouse();
|
||||
_bindings.ShowConfirmation(
|
||||
"Do you really want to abandon your house? Any items in the house (on hooks or in storage) will stay with the house, and you will lose access to them.",
|
||||
firstAccepted =>
|
||||
{
|
||||
if (!firstAccepted)
|
||||
return;
|
||||
|
||||
_bindings.ShowConfirmation(
|
||||
"Are you absolutely certain you wish to abandon your house? Click yes only if you are sure!",
|
||||
secondAccepted =>
|
||||
{
|
||||
if (secondAccepted)
|
||||
_bindings.AbandonHouse();
|
||||
});
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -645,12 +686,15 @@ public sealed class ClientCommandController
|
|||
}
|
||||
|
||||
// ClientCommunicationSystem::DoPermit @ 0x005785A0. Argument shape
|
||||
// already validated by RetailClientCommandCatalog (exactly "add <name>"
|
||||
// or "remove <name>").
|
||||
// already validated by RetailClientCommandCatalog (a mode word plus at
|
||||
// least one more token). CH4 REJECT-review SHOULD-FIX 5 (2026-08-09):
|
||||
// retail's DoPermit joins every token after the mode word into the
|
||||
// name (JoinArgsAsName), so a multi-word character name — "@permit add
|
||||
// Aunt Agatha" — must resolve to "Aunt Agatha", not just "Aunt".
|
||||
private void ExecutePermit(string arguments)
|
||||
{
|
||||
string[] parts = SplitArguments(arguments);
|
||||
string name = parts[1];
|
||||
string name = string.Join(' ', parts, 1, parts.Length - 1);
|
||||
if (parts[0].Equals("add", StringComparison.OrdinalIgnoreCase))
|
||||
_bindings.AddPlayerPermission(name);
|
||||
else
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue