command system ============== commands are registered as CommandDefinition objects with metadata. from mudlib.commands import CommandDefinition, register async def my_command(player: Player, args: str) -> None: player.writer.write("hello\r\n") await player.writer.drain() register(CommandDefinition("mycommand", my_command, aliases=["mc"])) CommandDefinition fields: name primary command name handler async function(player, args) aliases alternative names (default: []) mode required player mode (default: "normal", "*" = any mode) help help text (default: "") dispatch parses input, looks up the definition, calls its handler: await dispatch(player, "mycommand some args") if the command isn't found, the player gets "Unknown command: ..." movement -------- 8 directions, each with short and long aliases: n/north s/south e/east w/west ne/northeast nw/northwest se/southeast sw/southwest movement checks passability before updating position. impassable terrain gives "You can't go that way." nearby players see arrival/departure messages: jared leaves east. jared arrives from the west. "nearby" = within viewport range (10 tiles) of old or new position. look ---- look/l renders the viewport. player is always @ at center. other players show as *. output is ANSI-colored. adding commands --------------- 1. create src/mudlib/commands/yourcommand.py 2. import CommandDefinition and register from mudlib.commands 3. define async handler(player, args) 4. call register(CommandDefinition(...)) 5. import the module in server.py so registration runs at startup combat commands --------------- combat moves are defined in TOML files in content/combat/. each file describes a move (attack, defend, counter) with metadata: name type (attack/defend) stamina_cost telegraph (what the opponent sees) timing_window_ms (how long to react) damage_pct (base damage as % of attacker PL) counters (list of valid defensive moves) the combat system loads these at startup and registers them as commands via combat/commands.py. when combat mode is active, these commands become available. players use the move name to execute it during their turn. content-defined commands ------------------------ commands can be defined in TOML files in content/commands/. these are loaded at startup by content.py and registered alongside python-defined commands. format: name = "example" aliases = ["ex"] help = "example command help text" mode = "normal" content commands currently require a python handler (body field pointing to a callable). when the DSL arrives, this will be replaced with inline scripting. editor mode ----------- the edit command pushes editor mode onto the mode stack. while editor mode is active, all input bypasses the command dispatcher and goes to the editor buffer instead. the editor provides: line editing with insert/append/delete/replace search and replace (regex supported) syntax highlighting for TOML/Python save/discard changes dirty flag tracking quit (:q) pops editor mode and returns to normal. the editor is implemented in editor.py and uses the mode stack to capture input. code ---- src/mudlib/commands/__init__.py registry + dispatch + CommandDefinition src/mudlib/commands/movement.py direction commands src/mudlib/commands/look.py look/l src/mudlib/commands/fly.py fly src/mudlib/commands/quit.py quit/q (mode="*") src/mudlib/combat/commands.py combat move commands (loaded from TOML) src/mudlib/content.py content loading (commands and combat moves) src/mudlib/editor.py editor mode and buffer management src/mudlib/player.py Player dataclass + registry