Skip to content

Command System

MAID uses a layered command system with priority-based resolution.

Overview

The command system allows multiple content packs to provide commands, with higher-priority packs able to override or extend lower-priority commands.

from maid_engine.commands.registry import LayeredCommandRegistry

registry = LayeredCommandRegistry()

# Register at default priority
registry.register("look", look_handler)

# Register at higher priority (overrides default)
registry.register("look", enhanced_look_handler, priority=100)

Command Structure

When a player types a command, MAID:

  1. Parses the input into command name and arguments
  2. Finds registered handlers for that command
  3. Runs handlers in priority order (highest first)
  4. Stops when a handler returns True (handled)
Player Input: "attack goblin with sword"
                    |
                    v
+----------------------------------+
|     LayeredCommandRegistry       |
|  +---------+  +---------+        |
|  |priority |  |priority |        |
|  |  100    |  |   50    |        |
|  +---------+  +---------+        |
|       |            |             |
|       v            v             |
|  [handler1]   [handler2]         |
+----------------------------------+
                    |
                    v
         CommandContext(
             command="attack",
             args=["goblin", "with", "sword"],
             raw_input="attack goblin with sword",
             session=player_session,
             player_id=player.id,
             world=game_world
         )

Registration

Commands are registered during content pack loading:

class MyContentPack(ContentPack):
    def register_commands(self, registry: CommandRegistry) -> None:
        # Simple registration
        registry.register("greet", cmd_greet, pack=self.manifest.name)

        # With priority (higher = runs first)
        registry.register("look", enhanced_look, pack=self.manifest.name, priority=50)

        # With aliases
        registry.register(
            "attack",
            cmd_attack,
            pack=self.manifest.name,
            aliases=["kill", "hit", "strike"]
        )

Command Handlers

Command handlers are async functions that process player input:

from maid_engine.commands import CommandContext

async def cmd_greet(ctx: CommandContext) -> bool:
    """Greet other players in the room."""
    target = ctx.args[0] if ctx.args else "everyone"
    await ctx.session.send(f"You greet {target}!")
    return True  # Command was handled

The handler returns: - True - Command handled, stop processing - False - Not handled, try next handler

Advanced Features

Argument Parsing

from maid_engine.commands.arguments import arguments, ArgumentSpec, ArgumentType

@arguments(
    ArgumentSpec("target", ArgumentType.ENTITY, required=True),
    ArgumentSpec("weapon", ArgumentType.ITEM, required=False),
)
async def cmd_attack(ctx: CommandContext, args: ParsedArguments) -> bool:
    target = args["target"]
    weapon = args.get("weapon")
    # ...

Lock Expressions

from maid_engine.commands.decorators import command

@command(name="admin_cmd", locks="perm(admin)")
async def admin_command(ctx: CommandContext) -> bool:
    # Only admins can use this
    pass

@command(name="guild_skill", locks="level(10) AND in_guild(warriors)")
async def guild_skill(ctx: CommandContext) -> bool:
    # Requires level 10 AND membership in warriors guild
    pass

Pre/Post Hooks

from maid_engine.commands.hooks import PreCommandHook, HookPriority

class LoggingHook(PreCommandHook):
    async def execute(self, ctx: CommandContext) -> bool:
        logger.info(f"Command: {ctx.command} by {ctx.session.player_id}")
        return True  # Continue execution

registry.register_pre_hook("logging", LoggingHook(), priority=HookPriority.FIRST)

Further Reading