Commit graph

4 commits

Author SHA1 Message Date
Erik
579cbfb48b fix(chat): block / unknown commands from broadcasting as speech
User reported typing /ls (a command-style request, not chat) gets
echoed by the server as "You say, \"/ls\"". Slash-prefix is a
COMMAND surface, never a chat surface. Filed after the same flow
that produced @help and the welcome-message work.

Behavior change at the ChatPanel submit layer:
- Any /-prefixed input whose verb isn't in our alias tables now
  renders a local "[System] Unknown command: /foo. Type /help for
  the list." line and is NEVER published to the bus. No SendChatCmd,
  no Talk packet. The server never sees /foo.
- Known /-verbs (/say /tell /reply /retell /general /allegiance
  /patron /vassals /monarch /covassals /fellowship /lookingforgroup
  /trade /roleplay /society /olthoi /help /clear /framerate /loc
  and friends) still flow through ChatInputParser.Parse → SendChatCmd
  exactly as before.
- @-prefix unchanged: ACE's CommandManager handles unknown @ verbs
  server-side and replies via SystemChat ("Unknown command: foo")
  per ACE GameActionTalk.cs:21. Our @ -> / normalization for known
  verbs (Phase J Tier 1) and the @-passthrough fallthrough for
  unknown verbs both still apply.

ChatInputParser now exposes:
- IsKnownVerb(string verb): query against the union of every alias
  table. Used by ChatPanel to discriminate "unknown verb" from
  "known verb with bad args".
- GetVerbToken(string command): public alias of the existing
  ExtractVerb so callers can pull the first whitespace token without
  reproducing the helper.

Parse itself is unchanged — its existing fall-through (Say with
literal text) still applies for unknown /-verbs called directly via
the parser, but ChatPanel intercepts before reaching that path so
the fall-through never fires through the live submit pipeline. Tests
that directly call Parse continue to pass; the new ChatPanel-level
tests pin the unknown-command rejection.

19 new tests:
- ChatInputParserTests: 10 IsKnownVerb Theory cases + 4 GetVerbToken
  Theory cases.
- ChatPanelInputTests: 5 Theory cases for Submit_UnknownSlashCommand
  covering /foo, /ls, /mp <path>, /genio, and bare /.

Solution total: 1086 green (243 Core.Net + 183 UI + 660 Core),
0 warnings.

Acceptance: type /ls, /mp /path, /anything-not-known — see local
"[System] Unknown command: /xxx. Type /help for the list of
supported commands." Nothing reaches the wire.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:49:56 +02:00
Erik
a316d6359c feat(chat): Phase J Tier 1+2 - @ verb prefix, /retell, /framerate, /loc
Three-tier rollout per the 2026-04-25 retail @help dump showing the
full ACE command surface. Tier 1 + most of Tier 2 in one commit.

TIER 1 - @ as / equivalent

ACE accepts both / and @ as verb prefixes (per its own help text:
"Note: You may substitute a forward slash (/) for the at symbol
(@)."). ChatInputParser now normalises @ to / for the verb-match
phase and re-enters parsing. Critical: for verbs we don't recognise
(@acehelp, @tele, @die, @version, @loc-on-server, @nonsense, ...),
the original @ is kept in the message text so ACE's CommandManager
intercepts the message server-side. If we substituted / there too,
ACE would treat it as plain Talk and broadcast it.

Result: @a hi / @tell Bob hi / @help / @clear / @reply / @retell
all route exactly like their / counterparts. @acehelp / @tele /
@version / @die etc. pass through to the server intact.

TIER 2 - client-only commands

- /retell <msg> (also @retell): resend to the last person you
  tell'd. Mirrors retail @retell. ChatVM tracks
  LastOutgoingTellTarget on each OnSelfSent(Tell, ...) entry —
  SenderGuid==0 distinguishes outgoing echo from inbound whispers,
  same way LastIncomingTellSender already worked. ChatInputParser
  takes a new optional lastOutgoingTellTarget param.

- /framerate (also @framerate): prints "Framerate: 144.2 FPS"
  into chat. Wired via a new ChatVM.FpsProvider Func<float>
  callback set by GameWindow at construction (closes over
  _lastFps). Falls back to "(provider unavailable)" if no
  callback is wired (tests / pre-live).

- /loc (also @loc): prints "Location: (123.4, 567.8, 60.0)" into
  chat. Wired via ChatVM.PositionProvider Func<Vector3> closing
  over GetDebugPlayerPosition() in GameWindow. ACE has a server-
  side @loc too; client wins here (instantaneous + uses the local
  interpolated position).

ChatPanel.TryHandleClientCommand grew @ aliases for /help /clear
/framerate /loc and the new EqAny helper for case-insensitive
multi-string matching. Help text rewritten to reference the
/ <-> @ equivalence and point at @acehelp / @acecommands for ACE's
full command list.

TIER 3 - automatic (no code)

Most retail @-commands (@allegiance motd, @afk, @die, @lifestone,
@corpse, @marketplace, @pkarena, @emote/@emotes, @fillcomps,
@permit, @consent, @squelch, @unsquelch, @messagetypes, @age,
@birth, @day, @endurance, @pklite, @version, @filter, @unfilter,
@loadfile, @log, @marketplace, ...) are server-side ACE commands.
Tier 1's passthrough takes care of them automatically — they
arrive via Talk, ACE recognises the @ and intercepts, replies via
SystemChat (which our 0xF7E0 wiring renders as [System] lines).

DEFERRED

- @saveui / @loadui / @lockui: ImGui layout save/load, ~1 hr
  standalone task. Filed for follow-up.
- @title <text>: rename chat window. ImGui window-id complications.
- Toggle-style @framerate (FPS overlay on/off): print-once is
  simpler and matches retail's most-common usage.

30 new tests:
- ChatInputParserAtPrefixTests: 11 covering @-prefix recognition,
  unknown-@ passthrough, /retell and @retell.
- ChatVMRetellAndProvidersTests: 8 covering LastOutgoingTellTarget
  tracking, FpsProvider/PositionProvider callbacks, no-provider
  fallback.
- ChatPanelInputTests: +3 (/framerate, @loc, @acehelp passthrough).

Solution total: 1063 green (243 Core.Net + 160 UI + 660 Core),
0 warnings.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:34:13 +02:00
Erik
3501194083 fix(chat): /help client-side handler + System dedup + ThatIsNotAValidCommand template
Phase J follow-up after a 2026-04-25 trace where typing /help
produced two identical "Unknown command: help" lines (ACE fires the
text via both GameMessageSystemChat 0xF7E0 and a paired
CommunicationTransientString 0x02EB), and the server's WeenieError
0x0026 trailer rendered cryptically as "WeenieError 0x0026".

Three small changes:

1. WeenieErrorMessages: add 0x0026 ThatIsNotAValidCommand ->
   "That is not a valid command." Plus 0x0414 / 0x050F that Phase J
   already added are now covered by tests too.

2. ChatLog.OnSystemMessage dedup. Track last system text + arrival
   time; if a second identical text shows up within 1 second,
   suppress. ACE's two-path send (gag warnings, command errors,
   etc.) collapses to a single chat line. Long bursts of repeated
   text still skip the duplicates without resetting the timer.

3. Client-side /help and /clear in ChatPanel. Intercepted BEFORE
   the parser passes to the server bus:
   - /help, /?, /h (case-insensitive) -> render local cheat-sheet
     listing acdream's slash prefixes via ChatLog.OnSystemMessage.
     Avoids the round-trip to ACE that produced the duplicate
     "Unknown command: help" lines AND gives users discoverability.
   - /clear, /cls -> drains the chat log so the panel starts empty.

   New ChatVM.ShowSystemMessage() + ChatVM.Clear() expose the
   minimum surface the panel needs to dispatch client-only feedback
   without coupling the panel to ChatLog directly.

12 new tests:
- 3 WeenieErrorMessages template adds (0x0026 / 0x0414 / 0x050F).
- 4 ChatLog dedup cases (immediate dup, different text, triplet,
  bookended-by-different-text).
- 5 ChatPanel client-command cases (/help, 3 alias variants,
  /clear).

Solution total: 1033 green (243 Core.Net + 130 UI + 660 Core),
0 warnings.

Acceptance: type /help in chat -> local help banner appears, no
server round-trip, no "Unknown command: help" duplicates. Type
/clear -> chat tail empty. Welcome banner + WeenieError-templated
"You are not in an allegiance!" / "You do not belong to a
Fellowship." continue rendering once each.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 21:22:07 +02:00
Erik
f14296c75f feat(ui): #17 ChatPanel input field + slash commands + reply-to-last-tell
ChatPanel gains an Enter-to-submit input field via the I.1
InputTextSubmit widget. Submitted text routes through ChatInputParser
to a SendChatCmd published on ctx.Commands; LiveCommandBus (I.3)
handles the wire send + ChatLog echo.

Recognised prefixes (ported from holtburger commands.rs):

  /say msg or no prefix  -> Say
  /t Name msg or /tell   -> Tell  (first whitespace token = target)
  /r msg                 -> Tell  (target = LastIncomingTellSender)
  /g msg                 -> General
  /f msg                 -> Fellowship
  /a msg                 -> Allegiance
  /m msg                 -> Monarch
  /p msg                 -> Patron
  /v msg                 -> Vassals
  /cv msg                -> CoVassals
  /lfg msg               -> Lfg
  /trade msg             -> Trade
  /role msg              -> Roleplay
  /society msg           -> Society
  /olthoi msg            -> Olthoi

Edge cases: empty / whitespace / cmd-without-message / /r without
prior tell -> null (no-op). Unknown /xyz prefix -> Say with literal
text (matches holtburger's Talk(command) default arm).

ChatVM.LastIncomingTellSender populated only on incoming Tell entries;
discriminated by SenderGuid != 0 (OnSelfSent echoes always carry 0).

32 new tests:
- ChatInputParserTests: 22 covering every prefix + edge case
- ChatVMLastTellSenderTests: 6 covering capture + skip rules
- ChatPanelInputTests: 6 using FakePanelRenderer + recording
  ICommandBus to assert publish behaviour

UI.Abstractions.Tests: 60 -> 92. Solution total: 934 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:44:04 +02:00