You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
It's not readable. The MuMain client only keeps the last few blue system
messages on screen and has no scrollback there, so with ~60 registered
commands the list is gone before the player can read it. This came up in the
Discord #help channel — a user asked how to find out how to start a Blood
Castle event on demand, /list was suggested, and the answer was "in the
case of MuMain /list makes no sense because there is no history scrolling
in the info window".
It's not discoverable. The player has to know that /list exists in the
first place, and afterwards has to type every command by hand with the exact
argument syntax.
The server already knows everything needed to do much better — it just
flattens it into a display string and throws the rest away.
Describe the solution you'd like
Expose the available chat commands to the client in a machine-readable
form, so a client can render a proper UI: browse the commands the player is
actually allowed to use, read a localized description, fill in the parameters
in generated input fields, and execute by clicking. The client-side counterpart
is sven-n/MuMain#539.
GetAvailableChatCommands(this Player) — already filters by CharacterStatus
.../ChatCommandTypeExtensions.cs
Localized plugin name/description via [Display(..., ResourceType = typeof(PlugInResources))]
on 62 of the 63 command plugins
Also worth noting: executing a command needs no new packet at all. ChatMessageAction routes every /-prefixed message to ChatMessageCommandProcessor and never broadcasts it to other players, so the
client can keep sending the composed command line as an ordinary chat message.
Only the listing direction is missing.
Gaps to close first
The good news after an audit of all 63 chat command plugins: the metadata is in
much better shape than the /list output suggests. All 63 have a [Display]
attribute, and 62 of them resolve name and description from PlugInResources, so descriptions are already there and already localizable.
The remaining work is small and mostly about picking one source of truth:
The description lives in two places, and one of them is thrown away.
This overload takes a description and never assigns it to a property:
17 commands pass a description this way that nobody can ever read — and for
most of them the [Display] description says roughly the same thing in a
different wording (e.g. ResetInfoChatCommandPlugIn: "…and gained points…"
vs. "…and granted points…"). Proposal: [Display] is the source of
truth (it's localizable), and the unused ctor parameter gets removed or
documented as a fallback.
The descriptions mix prose with usage text. 36 of them follow the
pattern "Handles the chat command '/item <group> <number> …'. Drops a
specific item next to the character.", while others inline a Usage: …
fragment ("Gets level of a player. Usage: /getlevel (optional:character)").
Since command and parameters are transmitted structurally, consumers want
only the second half. Worth normalizing so the UI doesn't have to show the
syntax twice — see the sequencing note below on when to do that.
Deactivated plugins are still listed.GetAvailableChatCommands uses PlugInManager.GetKnownPlugInsOf<IChatCommandPlugIn>(), which returns known
plugins regardless of their active state, while dispatch in ChatMessageCommandProcessor goes through the strategy provider. So a chat
command deactivated in the admin panel still shows up in /list and /help
but does nothing when used. This should be filtered with PlugInManager.IsPlugInActive. This one is a plain bug and independent of
everything else here.
One plugin isn't localizable: ResetInfoChatCommandPlugIn uses [Display(Name = "Reset Info Command", Description = "…")] with hardcoded
English instead of ResourceType = typeof(PlugInResources).
Parameter metadata is thin — no per-parameter description and no
min/max range (there is already a // todo: ranges in ParameterAttribute in CommandExtensions.GetParameters). Explicitly not a blocker: name,
short name, type, required flag and valid values are enough to generate a
usable input form, and both fields can be added later without breaking the
packet as long as the strings are length-prefixed and may be empty.
Sequencing
No big metadata project is needed up front. Suggested order:
The IsPlugInActive fix (3 above) — small, independent, a bug today.
The ChatCommandInfo DTO + builder, reading the description from [Display], plus fixing the one hardcoded [Display] (4 above).
The admin panel page (Admin panel: dedicated chat commands overview page with usage, parameters and activation toggle #848) as the first consumer — it's pure C#, needs
no protocol change, no client change and no NuGet release, and it renders
all 63 descriptions next to their generated usage strings on one screen.
That is the cheapest place to see which descriptions read badly and to do
the normalization pass from 2 above with immediate feedback.
Then the packet and the client UI, against a model that has already been
proven in the browser.
Proposed implementation
a) A DTO built from the existing metadata, e.g. ChatCommandInfo:
Command ("/item")
DisplayName, Description — localized, resolved for the player's culture
b) A view pluginIChatCommandListViewPlugIn in src/GameLogic/Views with ShowChatCommandListAsync(IReadOnlyCollection<ChatCommandInfo> commands), and
an implementation in src/GameServer/RemoteView annotated with [MinimumClient(106, 3, ClientLanguage.Invariant)], so only Extended-protocol
clients receive it (MuMain sends version 20404 = "Extended S6E3").
c) Packet definitions in src/Network/Packets:
ClientToServer ChatCommandListRequest — C1, code 0xF5, sub code 0x00.
ServerToClient ChatCommandList — C2, code 0xF5, sub code 0x01.
0xF5 is currently unused in ClientToServerPackets.xml, unused in ServerToClientPackets.xml, and unused in MuMain's ProcessPacket switch —
but the exact code is of course up for discussion.
Sketch of the payload:
Field
Type
Note
PageIndex / PageCount
Byte / Byte
so a long list can be split
CommandCount
Byte
commands in this packet
Commands
Structure[]
variable length, UseCustomIndexer
and per command entry: EntryLength (ushort), MinimumCharacterStatus
(byte), ParameterCount (byte), then length-prefixed UTF-8 Command, DisplayName, Description, followed by one entry per parameter with Flags (required), Type (byte enum), Name, ShortName, ValidValues.
Variable-length entries in a list are already handled in this codebase — AddCharactersToScope does exactly that with UseCustomIndexer plus a
hand-written partial class.
Why binary and not JSON: MuMain has no JSON dependency (its CMake only
pulls in CURL, OpenGL and Python), and the existing XSLT codegen generates
both the C# writers here and MuMain's C++ bindings straight from these XML
files. A JSON blob would mean a new third-party dependency and a hand-written
parser on the client for no real gain.
d) A handlerChatCommandListRequestHandlerPlugIn in src/GameServer/MessageHandler.
e) Optionally push the list unsolicited right after the character entered
the game world, so the client doesn't even have to ask.
f) A release of MUnique.OpenMU.Network.Packets — MuMain pins version 0.9.9, so the client can only pick this up after a new package is published.
Describe alternatives you've considered
Client-side only, parsing the /list output. Fragile (the usage string is
a display format, not an API), gives no descriptions, no valid values, no
required/optional distinction, and still depends on the blue-message
transport that is the actual problem.
Just fixing the display of /list (routing it into the scrollable chat
list box, as suggested in the Discord thread). Worth doing anyway as a small
independent fix, but it only makes the wall of text scrollable — the player
still has to know about /list and still has to type every command.
JSON over an existing packet. See above — new client dependency, and it
bypasses the packet definition/codegen infrastructure.
Is 0xF5 an acceptable head code, or should this hang off an existing
extension code with a new sub code?
Confirm [Display] as the single source of truth for the description, and
drop the unused description parameter of ChatCommandHelpAttribute?
Do we want per-parameter descriptions and value ranges right away, or in a
follow-up?
Paging vs. a single C2 packet — 63 commands with descriptions should still
fit into the 65535 byte limit, but paging is cheap insurance against servers
with many custom command plugins.
Is your feature request related to a problem? Please describe.
Chat commands are currently only discoverable by typing
/list, which printsone blue system message per command
(
ListCommand.cs):That has two problems:
messages on screen and has no scrollback there, so with ~60 registered
commands the list is gone before the player can read it. This came up in the
Discord
#helpchannel — a user asked how to find out how to start a BloodCastle event on demand,
/listwas suggested, and the answer was "in thecase of MuMain
/listmakes no sense because there is no history scrollingin the info window".
/listexists in thefirst place, and afterwards has to type every command by hand with the exact
argument syntax.
The server already knows everything needed to do much better — it just
flattens it into a display string and throws the rest away.
Describe the solution you'd like
Expose the available chat commands to the client in a machine-readable
form, so a client can render a proper UI: browse the commands the player is
actually allowed to use, read a localized description, fill in the parameters
in generated input fields, and execute by clicking. The client-side counterpart
is sven-n/MuMain#539.
What already exists and can be reused
IChatCommandPlugIn(Key,MinCharacterStatusRequirement)src/GameLogic/PlugIns/ChatCommands/IChatCommandPlugIn.csChatCommandHelpAttribute(Command,MinimumCharacterStatus,ArgumentsType,Usage).../ChatCommandHelpAttribute.csArgumentAttribute(ShortName,IsRequired).../ArgumentAttribute.csValidValuesAttribute.../ValidValuesAttribute.csCommandExtensions.GetParameters(Type)→(Name, Type, ValidValues).../CommandExtensions.csGetAvailableChatCommands(this Player)— already filters byCharacterStatus.../ChatCommandTypeExtensions.cs[Display(..., ResourceType = typeof(PlugInResources))]Also worth noting: executing a command needs no new packet at all.
ChatMessageActionroutes every/-prefixed message toChatMessageCommandProcessorand never broadcasts it to other players, so theclient can keep sending the composed command line as an ordinary chat message.
Only the listing direction is missing.
Gaps to close first
The good news after an audit of all 63 chat command plugins: the metadata is in
much better shape than the
/listoutput suggests. All 63 have a[Display]attribute, and 62 of them resolve name and description from
PlugInResources, so descriptions are already there and already localizable.The remaining work is small and mostly about picking one source of truth:
The description lives in two places, and one of them is thrown away.
This overload takes a description and never assigns it to a property:
17 commands pass a description this way that nobody can ever read — and for
most of them the
[Display]description says roughly the same thing in adifferent wording (e.g.
ResetInfoChatCommandPlugIn: "…and gained points…"vs. "…and granted points…"). Proposal:
[Display]is the source oftruth (it's localizable), and the unused ctor parameter gets removed or
documented as a fallback.
The descriptions mix prose with usage text. 36 of them follow the
pattern "Handles the chat command '/item <group> <number> …'. Drops a
specific item next to the character.", while others inline a
Usage: …fragment ("Gets level of a player. Usage: /getlevel (optional:character)").
Since command and parameters are transmitted structurally, consumers want
only the second half. Worth normalizing so the UI doesn't have to show the
syntax twice — see the sequencing note below on when to do that.
Deactivated plugins are still listed.
GetAvailableChatCommandsusesPlugInManager.GetKnownPlugInsOf<IChatCommandPlugIn>(), which returns knownplugins regardless of their active state, while dispatch in
ChatMessageCommandProcessorgoes through the strategy provider. So a chatcommand deactivated in the admin panel still shows up in
/listand/helpbut does nothing when used. This should be filtered with
PlugInManager.IsPlugInActive. This one is a plain bug and independent ofeverything else here.
One plugin isn't localizable:
ResetInfoChatCommandPlugInuses[Display(Name = "Reset Info Command", Description = "…")]with hardcodedEnglish instead of
ResourceType = typeof(PlugInResources).Parameter metadata is thin — no per-parameter description and no
min/max range (there is already a
// todo: ranges in ParameterAttributeinCommandExtensions.GetParameters). Explicitly not a blocker: name,short name, type, required flag and valid values are enough to generate a
usable input form, and both fields can be added later without breaking the
packet as long as the strings are length-prefixed and may be empty.
Sequencing
No big metadata project is needed up front. Suggested order:
IsPlugInActivefix (3 above) — small, independent, a bug today.ChatCommandInfoDTO + builder, reading the description from[Display], plus fixing the one hardcoded[Display](4 above).no protocol change, no client change and no NuGet release, and it renders
all 63 descriptions next to their generated usage strings on one screen.
That is the cheapest place to see which descriptions read badly and to do
the normalization pass from 2 above with immediate feedback.
proven in the browser.
Proposed implementation
a) A DTO built from the existing metadata, e.g.
ChatCommandInfo:Command("/item")DisplayName,Description— localized, resolved for the player's cultureMinimumCharacterStatusParameters:Name,ShortName,Type,IsRequired,ValidValues,DefaultValueb) A view plugin
IChatCommandListViewPlugIninsrc/GameLogic/ViewswithShowChatCommandListAsync(IReadOnlyCollection<ChatCommandInfo> commands), andan implementation in
src/GameServer/RemoteViewannotated with[MinimumClient(106, 3, ClientLanguage.Invariant)], so only Extended-protocolclients receive it (MuMain sends version
20404= "Extended S6E3").c) Packet definitions in
src/Network/Packets:ClientToServer
ChatCommandListRequest—C1, code0xF5, sub code0x00.ServerToClient
ChatCommandList—C2, code0xF5, sub code0x01.0xF5is currently unused inClientToServerPackets.xml, unused inServerToClientPackets.xml, and unused in MuMain'sProcessPacketswitch —but the exact code is of course up for discussion.
Sketch of the payload:
PageIndex/PageCountCommandCountCommandsStructure[]UseCustomIndexerand per command entry:
EntryLength(ushort),MinimumCharacterStatus(byte),
ParameterCount(byte), then length-prefixed UTF-8Command,DisplayName,Description, followed by one entry per parameter withFlags(required),Type(byte enum),Name,ShortName,ValidValues.Variable-length entries in a list are already handled in this codebase —
AddCharactersToScopedoes exactly that withUseCustomIndexerplus ahand-written partial class.
Why binary and not JSON: MuMain has no JSON dependency (its CMake only
pulls in CURL, OpenGL and Python), and the existing XSLT codegen generates
both the C# writers here and MuMain's C++ bindings straight from these XML
files. A JSON blob would mean a new third-party dependency and a hand-written
parser on the client for no real gain.
d) A handler
ChatCommandListRequestHandlerPlugIninsrc/GameServer/MessageHandler.e) Optionally push the list unsolicited right after the character entered
the game world, so the client doesn't even have to ask.
f) A release of
MUnique.OpenMU.Network.Packets— MuMain pins version0.9.9, so the client can only pick this up after a new package is published.Describe alternatives you've considered
/listoutput. Fragile (the usage string isa display format, not an API), gives no descriptions, no valid values, no
required/optional distinction, and still depends on the blue-message
transport that is the actual problem.
/list(routing it into the scrollable chatlist box, as suggested in the Discord thread). Worth doing anyway as a small
independent fix, but it only makes the wall of text scrollable — the player
still has to know about
/listand still has to type every command.bypasses the packet definition/codegen infrastructure.
Additional context
Client-side counterpart: sven-n/MuMain#539
Open questions for discussion:
0xF5an acceptable head code, or should this hang off an existingextension code with a new sub code?
[Display]as the single source of truth for the description, anddrop the unused
descriptionparameter ofChatCommandHelpAttribute?follow-up?
C2packet — 63 commands with descriptions should stillfit into the 65535 byte limit, but paging is cheap insurance against servers
with many custom command plugins.