Complete Game Tutorial¶
This tutorial shows how to combine multiple content packs to create a complete MUD game. You will learn how different systems integrate, how to make architecture decisions, and best practices for building a full game.
Overview¶
A complete MAID game typically consists of:
- Core Systems: Combat, magic, movement
- World Content: Rooms, NPCs, items
- Progression: Levels, skills, quests
- Economy: Currency, shops, trading
- Social: Chat, groups, guilds
Architecture Overview¶
┌─────────────────────────────────────────────────────────────┐
│ Your Game │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Combat │ │ Magic │ │ Economy │ │
│ │ System │ │ System │ │ System │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ └────────────────┼────────────────┘ │
│ │ │
│ ┌───────▼────────┐ │
│ │ maid-classic-rpg│ │
│ └───────┬────────┘ │
├──────────────────────────┼──────────────────────────────────┤
│ ┌───────▼────────┐ │
│ │ maid-stdlib │ │
│ └───────┬────────┘ │
├──────────────────────────┼──────────────────────────────────┤
│ ┌───────▼────────┐ │
│ │ maid-engine │ │
│ └────────────────┘ │
└─────────────────────────────────────────────────────────────┘
System Integration¶
How Systems Communicate¶
Systems communicate through the EventBus, not direct calls:
# Combat system emits damage event
await self.events.emit(DamageDealtEvent(
source_id=attacker.id,
target_id=target.id,
damage=25,
))
# XP system listens for kills
async def _on_entity_death(self, event: EntityDeathEvent) -> None:
if event.killer_id:
await self._award_xp(event.killer_id, event.entity_id)
# Loot system also listens for kills
async def _on_entity_death(self, event: EntityDeathEvent) -> None:
await self._spawn_loot(event.entity_id)
# Quest system tracks kills too
async def _on_entity_death(self, event: EntityDeathEvent) -> None:
await self._update_kill_quests(event.killer_id, event.entity_id)
Shared Components¶
Systems share components to avoid duplication:
# Many systems use HealthComponent
player.add(HealthComponent(current=100, maximum=100))
# Combat reads it
health = player.get(HealthComponent)
health.damage(25)
# Magic reads it
health.heal(30)
# Regeneration system updates it
if health.current < health.maximum:
health.heal(int(health.regeneration_rate * delta))
Example: Complete Game Structure¶
Project Layout¶
my-mud-game/
src/
my_mud_game/
__init__.py
main.py # Entry point
config.py # Game configuration
packs/
__init__.py
world/ # World content pack
pack.py
rooms/
npcs/
items/
gameplay/ # Gameplay systems pack
pack.py
systems/
commands/
progression/ # Progression pack
pack.py
xp_system.py
skills.py
data/
world.yaml # World definition
npcs.yaml # NPC definitions
items.yaml # Item definitions
tests/
pyproject.toml
Main Entry Point¶
# src/my_mud_game/main.py
"""Main entry point for the MUD game."""
import asyncio
from maid_engine.core.engine import GameEngine
from maid_engine.config.settings import Settings
from maid_stdlib.pack import StdlibContentPack
from maid_classic_rpg.pack import ClassicRPGContentPack
from .packs.world import WorldContentPack
from .packs.gameplay import GameplayContentPack
from .packs.progression import ProgressionContentPack
async def main():
"""Start the game server."""
# Load configuration
settings = Settings(
game=GameSettings(
name="My MUD Game",
tick_rate=4.0,
),
telnet=TelnetSettings(
port=4000,
),
web=WebSettings(
port=8080,
),
)
# Create engine
engine = GameEngine(settings)
# Load content packs in dependency order
# Core library
engine.load_content_pack(StdlibContentPack())
# Classic RPG features (combat, magic, etc.)
engine.load_content_pack(ClassicRPGContentPack())
# Game-specific packs
engine.load_content_pack(WorldContentPack())
engine.load_content_pack(GameplayContentPack())
engine.load_content_pack(ProgressionContentPack())
# Start the server
print("Starting My MUD Game...")
await engine.start()
# Keep running until shutdown
try:
while True:
await asyncio.sleep(1)
except KeyboardInterrupt:
print("Shutting down...")
await engine.stop()
if __name__ == "__main__":
asyncio.run(main())
World Content Pack¶
# src/my_mud_game/packs/world/pack.py
"""World content pack - defines rooms, NPCs, items."""
from maid_engine.plugins.protocol import BaseContentPack
class WorldContentPack(BaseContentPack):
"""Content pack containing world definitions."""
@property
def manifest(self):
from maid_engine.plugins.manifest import ContentPackManifest
return ContentPackManifest(
name="my-game-world",
version="1.0.0",
display_name="My Game World",
dependencies={"stdlib": ">=0.1.0", "classic-rpg": ">=0.1.0"},
)
def get_dependencies(self):
return ["stdlib", "classic-rpg"]
async def on_load(self, engine):
"""Load world data and spawn initial entities."""
await self._load_rooms(engine)
await self._load_npcs(engine)
await self._load_items(engine)
async def _load_rooms(self, engine):
"""Create rooms from data files."""
# Load from YAML/JSON or create programmatically
world = engine.world
# Create starting room
start_room = world.rooms.create(
name="Town Square",
description="The bustling center of town.",
)
# Create connected rooms
tavern = world.rooms.create(
name="The Rusty Dagger Tavern",
description="A cozy tavern with a roaring fireplace.",
)
# Connect rooms
start_room.add_exit("north", tavern.id)
tavern.add_exit("south", start_room.id)
async def _load_npcs(self, engine):
"""Spawn NPCs in rooms."""
# Create NPCs with AI and dialogue
pass
async def _load_items(self, engine):
"""Place items in the world."""
pass
Gameplay Content Pack¶
# src/my_mud_game/packs/gameplay/pack.py
"""Custom gameplay systems."""
from maid_engine.plugins.protocol import BaseContentPack
from .systems import RestingSystem, HungerSystem
class GameplayContentPack(BaseContentPack):
"""Custom gameplay mechanics."""
@property
def manifest(self):
from maid_engine.plugins.manifest import ContentPackManifest
return ContentPackManifest(
name="my-game-gameplay",
version="1.0.0",
dependencies={"stdlib": ">=0.1.0"},
)
def get_systems(self, world):
return [
RestingSystem(world),
HungerSystem(world),
]
def register_commands(self, registry):
from .commands import register_commands
register_commands(registry, self.manifest.name)
Best Practices¶
1. Use Events for Cross-System Communication¶
# Good: Events for loose coupling
await self.events.emit(PlayerLeveledUpEvent(player_id=player.id, new_level=10))
# Bad: Direct system calls
skill_system = self.world.systems.get(SkillSystem)
skill_system.unlock_skills_for_level(player_id, 10)
2. Keep Components Focused¶
# Good: Focused components
class HealthComponent(Component):
current: int
maximum: int
class ManaComponent(Component):
current: int
maximum: int
# Bad: Mega-component
class CharacterStatsComponent(Component):
health: int
max_health: int
mana: int
max_mana: int
stamina: int
# ... 50 more fields
3. Design for Extensibility¶
# Good: Can be extended by other packs
class CombatSystem(System):
async def calculate_damage(self, attacker, defender, attack_type):
base_damage = self._get_base_damage(attacker)
# Emit event so other systems can modify
event = DamageCalculationEvent(
attacker_id=attacker.id,
defender_id=defender.id,
base_damage=base_damage,
modifiers=[],
)
await self.events.emit(event)
# Apply modifiers from other systems
final_damage = base_damage
for modifier in event.modifiers:
final_damage = int(final_damage * modifier)
return final_damage
4. Test Systems in Isolation¶
# Test combat system without magic system
@pytest.fixture
def combat_only_world(world):
world.systems.register(CombatSystem(world))
return world
async def test_basic_attack(combat_only_world):
# Test combat in isolation
pass
5. Document System Interactions¶
class QuestSystem(System):
"""Quest tracking and completion system.
Dependencies:
- StdlibContentPack: DescriptionComponent for quest givers
- CombatSystem: Listens to EntityDeathEvent for kill quests
- LootSystem: Listens to ItemPickedUpEvent for collection quests
Events Emitted:
- QuestAcceptedEvent: When player accepts a quest
- QuestCompletedEvent: When quest objectives are met
- QuestRewardEvent: When rewards are granted
Events Listened:
- EntityDeathEvent: Track kills for kill quests
- ItemPickedUpEvent: Track items for collection quests
- RoomEnterEvent: Track exploration for discovery quests
"""
Common Patterns¶
Loading World Data¶
# Load from YAML
import yaml
async def _load_rooms(self, engine):
with open("data/rooms.yaml") as f:
data = yaml.safe_load(f)
for room_data in data["rooms"]:
room = engine.world.rooms.create(
id=room_data["id"],
name=room_data["name"],
description=room_data["description"],
)
for exit_dir, target in room_data.get("exits", {}).items():
room.add_exit(exit_dir, target)
Spawning NPCs¶
async def spawn_npc(self, world, template_id: str, room_id: UUID):
"""Spawn an NPC from a template."""
template = self.npc_templates[template_id]
entity = world.entities.create()
entity.add(PositionComponent(room_id=room_id))
entity.add(DescriptionComponent(
name=template["name"],
short_desc=template["description"],
))
entity.add(HealthComponent(
current=template["health"],
maximum=template["health"],
))
entity.add(NPCComponent(
template_id=template_id,
behavior_type=template.get("behavior", "passive"),
))
if template.get("hostile"):
entity.add(AttackComponent(**template["attack"]))
entity.add_tag("hostile")
return entity
Saving Game State¶
async def save_player(self, player_id: UUID):
"""Save player state to document store."""
player = self.world.entities.get(player_id)
if not player:
return
# Serialize entity
data = player.to_dict()
# Save to document store
players = self.document_store.get_collection("players")
await players.upsert(player_id, data)
Summary¶
Building a complete game requires:
- Layered content packs that build on each other
- Event-driven communication between systems
- Shared components for common data
- Clear documentation of system interactions
- Comprehensive testing at all levels
Next Steps¶
- Review the Combat System Tutorial for detailed system implementation
- Review the Magic System Tutorial for spell mechanics
- Check the Content Pack Guides for detailed API documentation
Additional Resources¶
- ECS Overview - Entity Component System patterns
- Events Reference - Built-in event types
- Components Reference - Built-in components
- Publishing Guide - Share your game