Skip to content

Chat command arguments: add value ranges and semantic reference kinds (item, monster, map, character, …) #849

Description

@sven-n

Is your feature request related to a problem? Please describe.

Chat command arguments are currently described only by their CLR type, their
short name, a required flag and — for small sets — [ValidValues]. That is
enough to render a text box, but not enough to render anything helpful.

Look at what a GM actually has to type today:

/item group=1 number=2 lvl=13 ex=8 anc=1
/createmonster number=417
/skin 235
/movemonster id=7382 x=120 y=125

Every one of those numbers is opaque. group=1 number=2 is a Blade, 417 is
some monster, 235 is some model — the admin has to look them up externally.
But the client already knows what these numbers mean, and so does the game
configuration. With a bit of metadata saying "this parameter is a monster
number"
, a UI can offer a searchable dropdown of monster names instead of an
empty number field.

[ValidValues] can't express this: it's meant for small, fixed, server-defined
sets (str|agi|vit|ene|cmd, PK levels 1|2|3, ancient discriminator 0|1|2),
and it would be absurd to enumerate thousands of item names into it and send
them over the wire.

Describe the solution you'd like

Two additions to the argument metadata, both purely descriptive — server-side
validation and parsing are unchanged
.

1. Value ranges — reuse [Range]

No new attribute needed: System.ComponentModel.DataAnnotations.RangeAttribute
is already used in this codebase (GameLogic/Bots/BotConfiguration.cs), and it
resolves the // todo: ranges in ParameterAttribute left in
CommandExtensions.GetParameters:

[Argument("lvl", false)]
[Range(0, 15)]
public byte Level { get; set; }

Min/max should be carried as values on the DTO, not read straight from the
attribute by every consumer, so a builder can override them from the game
configuration where the real limit isn't a compile-time constant (maximum item
level, map dimensions, …).

2. Reference kinds — a new attribute

public enum ChatCommandValueReference
{
    None,
    CharacterName,
    AccountName,
    GuildName,
    Map,
    MapCoordinateX,
    MapCoordinateY,
    ItemGroup,
    ItemNumber,
    MonsterNumber,
    ObjectId,        // an object currently in scope
    SkillNumber,
    LanguageIsoCode,
}
[Argument("number")]
[ValueReference(ChatCommandValueReference.MonsterNumber)]
public short MonsterNumber { get; set; }

Mapped onto what exists today in PlugIns/ChatCommands/Arguments:

Argument class Property Reference kind
ItemChatCommandArgs Group, Number ItemGroup + ItemNumber (composite, see below)
CreateMonsterChatCommandArgs MonsterNumber MonsterNumber
SkinChatCommandArgs SkinNumber MonsterNumber
MoveMonsterCommandArgs, IdCommandArgs Id ObjectId
MoveChatCommandArgs, GuildMoveChatCommandArgs MapIdOrName Map
CoordinatesCommandArgs and derived X, Y MapCoordinateX / MapCoordinateY
BanCharChatCommandArgs, CharInfoChatCommandArgs, ChatBanCharChatCommandArgs, DisconnectChatCommandArgs, PkChatCommandArgs, TraceChatCommandArgs, … CharacterName CharacterName (12+ occurrences)
BanAccChatCommandArgs, UnBanAccChatCommandArgs AccountName AccountName
GuildWarChatCommandArgs, GuildMoveChatCommandArgs, GuildDisconnectChatCommandArgs GuildName GuildName
ChangeLanguageChatCommandArgs IsoLanguageCode LanguageIsoCode

Composite references. /item identifies one item with two parameters, so
the attribute should be able to name its companion:

[Argument("number")]
[ValueReference(ChatCommandValueReference.ItemNumber, GroupWith = nameof(Group))]
public short Number { get; set; }

A UI then renders one item picker that fills both fields, instead of two
disconnected number boxes.

Why this works — the client really does know these numbers

Verified in the MuMain sources, so this isn't speculative:

  • Monsters: getMonsterName(int type)Engine/Object/ZzzInfomation.cpp.
    Covers both MonsterNumber and the /skin model number.
  • Items: GetItemName(int iType, int iLevel, wchar_t*) and
    GetItemDisplayNameEngine/Object/ZzzInventory.h. Crucially the client's
    item type encoding is ITEM_GROUP_x * MAX_ITEM_INDEX + index
    (Core/Globals/_define.h), i.e. exactly group and number — so
    group=1 number=2 maps to a client item name with no translation table.
  • Maps: gMapManager.GetMapName(...).
  • In-scope objects: the client holds CharactersClient, so an ObjectId
    parameter can offer the objects around the player — or even a "pick target on
    screen" button.
  • Character names: the client can't know every character on the server, but
    it can suggest party members, guild members and players in scope.

Two design rules follow from that last point:

  1. A reference kind is a hint, never a constraint. The UI must always allow
    raw entry. The client's data files can be out of sync with the server's
    configuration — custom items or monsters added server-side won't be in the
    client's list, and the command must still be usable.
  2. Nothing changes on the validation path. The server keeps parsing and
    validating exactly as before; this metadata only makes input easier.

How it interacts with the other issues

  • Provide the available chat commands in a machine-readable form to the client #847 (machine-readable command list for the client): two more fields per
    parameter — ReferenceKind (byte) and the companion parameter name — plus
    min/max. Purely additive; if the parameter strings are length-prefixed and
    may be empty as proposed there, this can land before or after the packet
    without a breaking change.
  • Admin panel: dedicated chat commands overview page with usage, parameters and activation toggle #848 (admin panel page): benefits just as much, and can actually do
    better than the game client, because the admin panel has the authoritative
    GameConfiguration — the dropdowns can be built from
    GameConfiguration.Items, .Monsters and .Maps rather than from client
    data files.
  • This issue is independent of both and is useful on its own: even /help
    output could read /createmonster number={MonsterNumber} instead of
    {Number:Int16}.

Describe alternatives you've considered

  • Send the full value lists from the server (e.g. as ValidValues). Fine
    for maps (~60) or languages, hopeless for items and monsters, and it would
    ship English server-side names to a client that already has them translated.
    The sensible split is: small server-defined sets → ValidValues inline;
    large sets the client already knows → reference kind.
  • Hardcode a parameter→picker mapping in the client, keyed on command name.
    Brittle, and it breaks for exactly the commands where discovery matters most:
    custom plugins the client has never heard of. That defeats the purpose of a
    dynamic command list.
  • Do nothing and rely on [Range] alone. Ranges help for coordinates and
    levels, but a range of 0–511 on an item number is still an empty box.

Additional context

Related: #847, #848, sven-n/MuMain#539

Open questions:

  • One [ValueReference] attribute with an enum, or separate marker attributes
    per kind?
  • Is GroupWith the right way to express the item group/number pair, or should
    there be a dedicated composite parameter concept?
  • Should configuration-dependent ranges (max item level, map size) be resolved
    into the DTO server-side, or left to the consumer?
  • Worth adding a per-parameter description at the same time, now that the
    attributes are being touched anyway?

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions