Cliloc-style message catalog: server sends id + args, client owns strings #59

Closed
opened 2026-07-19 06:28:51 +02:00 by marco · 0 comments
Owner

Problem. User-facing strings are scattered across the server: spell cues ("Not enough mana", "No line of sight", "Out of range", "Invalid target", "You can't cast while dead") in SpellSystem/SpellCastResult, the skill-gain line ("{Skill} increased … — now …") in WorldTick and GameSessionHandler, GM-command feedback in the command classes. As the spell system grows (invalid-combo, timeout, disrupted, fizzle variants…) this surface explodes, and presentation strings live inside authoritative game logic.

Proposal (UO cliloc-style). The server sends a message id + parameters; the client owns the string table and formats it. Decouples wire from presentation, keeps gameplay logic free of copy, and is localization-ready. Aligns with "server = authoritative logic, client = presentation" and with ModernUO/UO (our reference).

Our variant (type-safe, not magic numbers). UO uses numeric cliloc ids because its client is closed + the table huge. We should instead use a typed SystemMessageId enum in IsoMmo.Shared/Protocol (compile-time safe, both sides reference it), a new SystemNotice(SystemMessageId Id, IReadOnlyList<string> Args) protocol message, and a client-side formatter mapping id → template (a single file, e.g. en.json/a static table). Call sites become new SystemNotice(SystemMessageId.NotEnoughMana) / SystemNotice(SystemMessageId.SkillIncreased, [skill, gained, now]).

Keep freeform SystemMessage(string) only where the text is genuinely dynamic and not worth cataloguing (or drop it entirely). Migrate incrementally.

Why now-ish (not urgent). Do it as its own pass before the spell-system string surface grows much further (slices 2–4 add many cues). Owner flagged it as "later".

Scope

  • Add SystemMessageId enum + SystemNotice message to Shared/Protocol.
  • Client: a single message-catalog/formatter mapping id → template, rendered into the journal.
  • Migrate existing SystemMessage gameplay cues (spell cues, skill-gain, GM feedback) to SystemNotice.

Definition of Done

  • No user-facing gameplay string literal remains inside server logic (SpellSystem, WorldTick, command classes) — all go through SystemMessageId.
  • The client renders every cue from one catalog file; adding/altering a string touches only that file + the enum.
  • Skill-gain, spell reject/fizzle cues, and GM feedback all arrive as SystemNotice(id, args) and render correctly (integration-tested for at least one of each).
  • Localization-ready: a second language would be a second catalog file, no server change.
**Problem.** User-facing strings are scattered across the server: spell cues ("Not enough mana", "No line of sight", "Out of range", "Invalid target", "You can't cast while dead") in `SpellSystem`/`SpellCastResult`, the skill-gain line (`"{Skill} increased … — now …"`) in `WorldTick` **and** `GameSessionHandler`, GM-command feedback in the command classes. As the spell system grows (invalid-combo, timeout, disrupted, fizzle variants…) this surface explodes, and presentation strings live inside authoritative game logic. **Proposal (UO cliloc-style).** The server sends a **message id + parameters**; the **client** owns the string table and formats it. Decouples wire from presentation, keeps gameplay logic free of copy, and is localization-ready. Aligns with "server = authoritative logic, client = presentation" and with ModernUO/UO (our reference). **Our variant (type-safe, not magic numbers).** UO uses numeric cliloc ids because its client is closed + the table huge. We should instead use a **typed `SystemMessageId` enum in `IsoMmo.Shared/Protocol`** (compile-time safe, both sides reference it), a new **`SystemNotice(SystemMessageId Id, IReadOnlyList<string> Args)`** protocol message, and a **client-side formatter** mapping id → template (a single file, e.g. `en.json`/a static table). Call sites become `new SystemNotice(SystemMessageId.NotEnoughMana)` / `SystemNotice(SystemMessageId.SkillIncreased, [skill, gained, now])`. Keep freeform `SystemMessage(string)` only where the text is genuinely dynamic and not worth cataloguing (or drop it entirely). Migrate incrementally. **Why now-ish (not urgent).** Do it as its own pass **before** the spell-system string surface grows much further (slices 2–4 add many cues). Owner flagged it as "later". ## Scope - Add `SystemMessageId` enum + `SystemNotice` message to `Shared/Protocol`. - Client: a single message-catalog/formatter mapping id → template, rendered into the journal. - Migrate existing `SystemMessage` gameplay cues (spell cues, skill-gain, GM feedback) to `SystemNotice`. ## Definition of Done - No user-facing **gameplay** string literal remains inside server logic (`SpellSystem`, `WorldTick`, command classes) — all go through `SystemMessageId`. - The client renders every cue from **one** catalog file; adding/altering a string touches only that file + the enum. - Skill-gain, spell reject/fizzle cues, and GM feedback all arrive as `SystemNotice(id, args)` and render correctly (integration-tested for at least one of each). - Localization-ready: a second language would be a second catalog file, no server change.
marco closed this issue 2026-07-19 08:25:58 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
marco/IsoMmo#59
No description provided.