Command System Overview¶
The command system in MAID handles player input, routing text commands to appropriate handlers. It supports aliases, layered overrides, and content pack integration.
What is a Command?¶
A command is a text instruction from a player, like:
Commands consist of a name and optional arguments.
Command Registry¶
The LayeredCommandRegistry manages all commands:
from maid_engine.commands import LayeredCommandRegistry, CommandHandler
registry = LayeredCommandRegistry()
# Register a command
registry.register(
name="look",
handler=look_handler,
aliases=["l", "examine"],
help_text="Look at your surroundings",
)
# Execute a command
result = await registry.execute("look", player_context)
Command Structure¶
Command Handler¶
A command handler is an async function:
from maid_engine.commands import CommandContext, CommandResult
async def look_handler(ctx: CommandContext) -> CommandResult:
"""Look at surroundings or a specific target."""
if ctx.args:
# look <target>
return await look_at(ctx.player, ctx.args[0])
else:
# look (room description)
return await look_room(ctx.player)
CommandContext¶
Contains all information about the command invocation:
@dataclass
class CommandContext:
player_id: UUID # Player executing command
command: str # Command name ("look")
args: list[str] # Arguments (["sword"])
raw_input: str # Full input ("look at sword")
world: World # Game world reference
session: Session | None # Network session (if any)
CommandResult¶
Describes the outcome of a command:
Built-in Commands¶
MAID provides core commands in maid-stdlib:
Movement Commands¶
north, south, east, west, up, down # Move in a direction
n, s, e, w, u, d # Direction aliases
go <direction> # Explicit movement
Look Commands¶
look # Look at current room
look <target> # Look at entity or item
examine <target> # Alias for look <target>
Inventory Commands¶
inventory # List carried items
i # Alias for inventory
get <item> # Pick up an item
drop <item> # Drop an item
give <item> <target> # Give item to someone
Communication Commands¶
say <message> # Speak to the room
tell <player> <message> # Private message
shout <message> # Broadcast to area
System Commands¶
Command Layers¶
Commands can be registered at different layers, allowing content packs to override base functionality:
from maid_engine.commands import CommandLayer
# Engine layer - lowest priority, can be overridden
registry.register(
name="attack",
handler=basic_attack,
layer=CommandLayer.ENGINE,
)
# Stdlib layer - standard library commands
registry.register(
name="attack",
handler=stdlib_attack,
layer=CommandLayer.STDLIB,
)
# Content pack layer - game-specific commands
registry.register(
name="attack",
handler=rpg_attack,
layer=CommandLayer.CONTENT,
)
# Override layer - highest priority
registry.register(
name="attack",
handler=custom_attack,
layer=CommandLayer.OVERRIDE,
)
When attack is executed, custom_attack runs (highest priority).
Command Aliases¶
Commands can have multiple names:
registry.register(
name="inventory",
handler=inventory_handler,
aliases=["i", "inv", "items"],
)
# All of these work:
# inventory
# i
# inv
# items
Command Categories¶
Organize commands by category:
from maid_engine.commands import CommandCategory
registry.register(
name="attack",
handler=attack_handler,
category=CommandCategory.COMBAT,
)
registry.register(
name="cast",
handler=cast_handler,
category=CommandCategory.MAGIC,
)
# List commands by category
combat_commands = registry.get_by_category(CommandCategory.COMBAT)
Content Pack Integration¶
Content packs register commands through their interface:
class MyCombatPack:
def register_commands(self, registry: CommandRegistry) -> None:
registry.register(
name="attack",
handler=self.attack_handler,
aliases=["a", "hit"],
help_text="Attack a target",
category=CommandCategory.COMBAT,
source_pack="my-combat-pack",
)
registry.register(
name="flee",
handler=self.flee_handler,
help_text="Attempt to flee from combat",
category=CommandCategory.COMBAT,
source_pack="my-combat-pack",
)
Command Discovery¶
List All Commands¶
# Get all registered commands
for cmd in registry.all_commands():
print(f"{cmd.name}: {cmd.help_text}")
# Get command by name
cmd = registry.get_command("attack")
if cmd:
print(f"Found: {cmd.name} (aliases: {cmd.aliases})")
Help System¶
# Generate help text
help_text = registry.get_help("attack")
# Get all commands a player can use
available = registry.get_available_commands(player_context)
Command Execution Flow¶
1. Player types: "attack goblin"
2. Parse input -> command="attack", args=["goblin"]
3. Find handler via LayeredCommandRegistry
4. Create CommandContext
5. Execute handler
6. Return CommandResult
7. Send response to player
async def handle_player_input(input_text: str, player: Entity) -> None:
# Parse command and args
parts = input_text.strip().split()
if not parts:
return
command = parts[0].lower()
args = parts[1:]
# Create context
ctx = CommandContext(
player_id=player.id,
command=command,
args=args,
raw_input=input_text,
world=world,
)
# Execute
result = await registry.execute(command, ctx)
# Handle result
if result.success:
await send_to_player(player, result.message)
else:
await send_to_player(player, f"Error: {result.message}")
Error Handling¶
Unknown Commands¶
result = await registry.execute("xyzzy", ctx)
# result.success = False
# result.message = "Unknown command: xyzzy"
Command Errors¶
async def attack_handler(ctx: CommandContext) -> CommandResult:
if not ctx.args:
return CommandResult(
success=False,
message="Attack what? Usage: attack <target>",
)
target = find_target(ctx.args[0], ctx.player_id)
if not target:
return CommandResult(
success=False,
message=f"You don't see '{ctx.args[0]}' here.",
)
# Perform attack
await do_attack(ctx.player_id, target.id)
return CommandResult(
success=True,
message=f"You attack {target.name}!",
)
Best Practices¶
1. Validate Early¶
async def handler(ctx: CommandContext) -> CommandResult:
# Validate arguments first
if len(ctx.args) < 2:
return CommandResult(False, "Usage: give <item> <target>")
# Then process
...
2. Provide Clear Feedback¶
async def handler(ctx: CommandContext) -> CommandResult:
item = find_item(ctx.args[0])
if not item:
return CommandResult(
False,
f"You don't have '{ctx.args[0]}'.\n"
f"Check your inventory with 'inventory'.",
)
...
3. Use Categories¶
# Makes help more organized
registry.register(
name="sneak",
handler=sneak_handler,
category=CommandCategory.MOVEMENT, # Not COMBAT
)
4. Document Commands¶
registry.register(
name="cast",
handler=cast_handler,
help_text="Cast a spell.\nUsage: cast <spell> [target]",
aliases=["c"],
)
Next Steps¶
- Implementing Command Handlers - Write effective handlers
- Command Priority and Layering - Override commands properly