Content Pack System¶
Content packs are the modular building blocks of MAID games.
Overview¶
A content pack is a self-contained module that provides:
- Systems - Game logic (combat, crafting, etc.)
- Commands - Player commands (attack, craft, etc.)
- Events - Custom events for the pack
- Components - Data types for game objects
- Document Schemas - Persistence definitions
What Content Packs Provide¶
| Feature | Description | Example |
|---|---|---|
| Systems | ECS systems that process entities | CombatSystem, QuestSystem |
| Commands | Player-facing commands | attack, craft, quest |
| Events | Custom event types | CombatStartedEvent |
| Components | Entity data types | WeaponComponent |
| Schemas | Database collections | Character save format |
| Connection Handler | Login/gameplay flow | Character selection |
The ContentPack Protocol¶
from maid_engine.plugins.protocol import ContentPack, ContentPackManifest
from maid_engine.core.world import World
from maid_engine.core.ecs import System
from maid_engine.commands.registry import CommandRegistry
from maid_engine.storage.document_store import DocumentStore
class MyContentPack(ContentPack):
@property
def manifest(self) -> ContentPackManifest:
return ContentPackManifest(
name="my-pack",
version="1.0.0",
description="My custom content pack",
author="Your Name",
)
def get_dependencies(self) -> list[str]:
"""Declare which packs must be loaded before this one."""
return ["maid-stdlib"]
def get_systems(self, world: World) -> list[System]:
"""Return systems to register with the world."""
return [
MyCustomSystem(world),
AnotherSystem(world),
]
def get_events(self) -> list[type[Event]]:
"""Return custom event types for documentation."""
return [MyCustomEvent, AnotherEvent]
def register_commands(self, registry: CommandRegistry) -> None:
"""Register commands with the command registry."""
registry.register("mycommand", my_command_handler, pack="my-pack")
def register_document_schemas(self, store: DocumentStore) -> None:
"""Register database collections and schemas."""
store.register_collection("my_data", MyDataSchema)
async def on_load(self, engine: GameEngine) -> None:
"""Called when the pack is loaded. Initialize resources here."""
self._engine = engine
# Load data, start background tasks, etc.
async def on_unload(self, engine: GameEngine) -> None:
"""Called when the pack is unloaded. Clean up here."""
# Stop background tasks, release resources
pass
Dependencies and Loading Order¶
Content packs declare dependencies, and MAID loads them in the correct order:
# maid-stdlib has no dependencies
class StdlibContentPack(ContentPack):
def get_dependencies(self) -> list[str]:
return []
# maid-classic-rpg depends on maid-stdlib
class ClassicRPGContentPack(ContentPack):
def get_dependencies(self) -> list[str]:
return ["maid-stdlib"]
# Your pack can depend on both
class MyContentPack(ContentPack):
def get_dependencies(self) -> list[str]:
return ["maid-stdlib", "maid-classic-rpg"]
Loading order matters - dependencies must be loaded first:
engine = GameEngine(settings)
# Correct order
engine.load_content_pack(StdlibContentPack()) # First - no deps
engine.load_content_pack(ClassicRPGContentPack()) # Second - needs stdlib
engine.load_content_pack(MyContentPack()) # Third - needs both
# Wrong order raises ValueError
engine.load_content_pack(MyContentPack()) # Error: dependency not loaded
Command Priority¶
Later-loaded packs can override commands from earlier packs:
# maid-stdlib registers "look" at default priority
registry.register("look", basic_look_handler)
# maid-classic-rpg can override with enhanced version
registry.register("look", enhanced_look_handler, priority=10)
# Your pack can override again
registry.register("look", custom_look_handler, priority=20)
Higher priority commands are executed first. If a handler returns True, the command is considered handled and lower-priority handlers don't run.
Creating a Content Pack¶
1. Define the Manifest¶
@property
def manifest(self) -> ContentPackManifest:
return ContentPackManifest(
name="my-awesome-pack",
version="1.0.0",
description="Adds awesome features to MAID",
author="Your Name",
homepage="https://github.com/you/my-awesome-pack",
license="MIT",
)
2. Implement Systems¶
from maid_engine.core.ecs import System
class AwesomeSystem(System):
priority = 50
async def update(self, delta: float) -> None:
# Process entities each tick
for entity in self.entities.with_components(AwesomeComponent):
component = entity.get(AwesomeComponent)
# Do awesome things
3. Register Commands¶
def register_commands(self, registry: CommandRegistry) -> None:
from .commands import cmd_awesome, cmd_super_awesome
registry.register("awesome", cmd_awesome, pack=self.manifest.name)
registry.register("superawesome", cmd_super_awesome, pack=self.manifest.name)
4. Package for Distribution¶
my-awesome-pack/
pyproject.toml
src/
my_awesome_pack/
__init__.py
pack.py # ContentPack class
systems/
__init__.py
awesome.py
commands/
__init__.py
awesome.py
components/
__init__.py
awesome.py
Hot Reloading¶
Content packs can be reloaded without restarting the server:
The hot reload system: 1. Pauses the game loop 2. Unregisters the pack's systems, commands, and events 3. Reloads Python modules 4. Re-registers everything 5. Resumes the game loop
Further Reading¶
- Architecture Overview - How content packs fit into MAID
- Command System - Registering and handling commands
- ECS Concepts - Building systems and components