Building Game Worlds Guide¶
This guide covers everything you need to know about creating and managing game worlds in MAID, from basic room creation to procedural wilderness generation.
Table of Contents¶
- Overview
- Getting Started
- Creating Rooms
- Connecting Rooms with Exits
- Using the Grid System
- Procedural Wilderness Generation
- Zone Management
- Best Practices
Overview¶
MAID provides multiple approaches to world building:
- Manual Building - Create rooms one at a time using in-game builder commands
- Grid-Based Building - Use coordinates for structured layouts like towns or dungeons
- Procedural Generation - Generate wilderness areas on-demand as players explore
- Batch Creation - Script large areas using batch files
Choose the approach that fits your needs:
| Approach | Best For | Complexity |
|---|---|---|
| Manual | Small areas, unique locations | Low |
| Grid-Based | Towns, dungeons, structured areas | Medium |
| Procedural | Large wilderness, infinite exploration | Medium |
| Batch | Mass creation, world seeding | High |
Getting Started¶
Access Requirements¶
Building commands require the BUILDER access level. Contact your server administrator to get building permissions.
Basic Workflow¶
- Navigate to where you want to build (or create a starting room)
- Create rooms using
@digor@create - Move into the new room with
@goto(@digcreates the room but does not move you there) - Describe rooms with
@describe - Connect rooms with exits using
@dig,@tunnel, or@link - Organize rooms into zones with
@zone
@digdoes not move you. It creates the room and the exits from your current room, then leaves you where you are. To work inside the new room (describe it, tag it, dig further), move into it first with@goto <room name>(or@goto #<room-id>).
Essential Commands Quick Reference¶
@dig <direction> [= room_name] - Create room and exit (stays put)
@goto <room name|#id|x,y> - Move the builder into a room
@describe here <text> - Set room description
@examine here - View room details
@tunnel <direction> <room> - Connect to existing room
@zone assign here <zone> - Add room to zone
Creating Rooms¶
Using @dig¶
The @dig command creates a new room connected to your current location:
This creates: - A new room named "The Grand Hall" to the north - A bidirectional exit (north from here, south from there)
Without a name, the room gets a default name:
Using @create¶
For rooms not connected to anything (isolated areas, limbo rooms):
Then connect it later with @link or @tunnel.
Setting Room Descriptions¶
Use @describe for single-line descriptions:
For multi-line descriptions, use @edit:
This opens the in-game VI-like editor. Type your description, then use :wq to save and quit.
Room Attributes¶
@set on a room updates fields that already exist on the room object. Rooms
created with @dig/@create room are RoomData records that expose name,
description, exits, and area_id, so you can rename or re-describe them:
Note:
@set here/<field>cannot create new fields on a builder-created room. Custom attributes such assector_type,light_level, or terrain metadata are not part ofRoomDataand must be defined either in the room's YAML through the content loader, or by a content pack that supplies a richer room class/component. Attempting@set here/sector_type = ...on aRoomDataroom returns a "Cannot set" error.
Room-level tags/flags (for example a safe_zone or no_teleport marker) are
not set with @attribute. The @attribute command is entity-only and
rejects room targets ("Target must be an entity (not a room)"). Define such
flags in the room's YAML tags: list via the content loader, or have a content
pack read them from a room component it registers.
Dynamic Room Descriptions¶
For rooms that change based on time, weather, or season, use the @room.desc.*
builder commands. They resolve the current room and create/attach an
ExtendedRoomComponent automatically — you do not run @component here add
(components are entity-only and reject room targets):
@room.desc.time night = The tavern is quiet, with only a few patrons.
@room.desc.time noon = The lunch crowd fills every table.
@room.desc.season winter = Frost clings to the windows.
@room.desc.weather rain = Rain drums steadily on the roof.
See the Extended Room Features Guide for complete documentation.
Connecting Rooms with Exits¶
Standard Directions¶
MAID supports these directions:
- Cardinal: north, south, east, west
- Diagonal: northeast, northwest, southeast, southwest
- Vertical: up, down
- Special: in, out
Abbreviations work too: n, s, e, w, ne, nw, se, sw, u, d
Creating Exits with @dig¶
@dig creates bidirectional exits by default:
Creates: - Exit "north" from here to Bedroom - Exit "south" from Bedroom to here
Connecting to Existing Rooms¶
Use @tunnel to connect to a room that already exists. The destination can be a
room name (partial match), #<room_id>, or a raw UUID:
One-Way Exits¶
For trapdoors, slides, or magical portals:
Removing Exits¶
Remove an exit from your current room:
Exit Descriptions¶
@describe targets entities and rooms only (here, me, a name, or
#<id>) — it does not accept a direction, so there is no
@describe north ... form for exit text.
Rich, state-aware exit descriptions are a data model, not a builder command. In
code (or a content pack), build an ExtendedExitDescription and attach it to the
room's ExtendedRoomComponent.exit_descriptions:
from maid_stdlib.components.extended_room import (
ExtendedExitDescription,
ExitState,
)
exit_desc = ExtendedExitDescription(
direction="north",
base_description="A passage leads north.",
state_variants={
ExitState.LOCKED: "A locked iron gate blocks the way.",
ExitState.HIDDEN: "", # Invisible until found
},
hidden_until={"perception": (">=", 15)},
)
# extended_room.exit_descriptions["north"] = exit_desc
Using the Grid System¶
The grid system provides coordinate-based room management with automatic exit creation and pathfinding. Use it for structured areas like towns, dungeons, or buildings.
Enabling Grid Mode¶
In a batch file or content pack:
from maid_engine.core.grid import GridManager, GridCoord
# Create grid manager with auto-exits
grid = GridManager(
world=engine.world,
auto_create_exits=True,
allow_diagonals=True,
)
Creating a Grid Area¶
In-Game Command¶
Creates a 10x10 grid of 100 rooms with coordinates (0,0) to (9,9). The bounds
are inclusive, so @grid.create 0 0 10 10 would create an 11x11 grid of 121
rooms — use 0 0 9 9 for exactly 100.
Programmatically¶
from maid_engine.core.grid import GridManager, GridCoord
def create_room(coord: GridCoord):
room_id = uuid4()
# Create room entity with the coordinate...
return room_id
# Create 10x10 grid
rooms = grid.create_grid_area(
GridCoord(0, 0),
GridCoord(9, 9),
room_factory=create_room,
)
Coordinate System¶
- X: East (+) / West (-)
- Y: North (+) / South (-)
- Z: Up (+) / Down (-)
Finding Paths¶
result = grid.find_path(start_room_id, end_room_id)
if result.found:
print(f"Directions: {result.directions}") # ['n', 'ne', 'e']
print(f"Distance: {result.length}")
print(f"Cost: {result.total_cost}")
Terrain Costs¶
Different terrain types affect pathfinding. movement_cost must be >= 1.0
— GridRoom raises ValueError for anything lower, because A* requires the
heuristic to never overestimate (values below 1.0 would break admissibility):
# Easy terrain (1.0 is the minimum / easiest)
grid.register_room(room_id, GridCoord(0, 0), movement_cost=1.0)
# Difficult terrain
grid.register_room(room_id, GridCoord(1, 0), movement_cost=2.0)
# Impassable
grid.block_coord(GridCoord(2, 0))
Grid Commands Reference¶
@grid.create <min_x> <min_y> <max_x> <max_y> [--terrain <type>] [--connect] - Create grid area
@grid.path <from> <to> - Find path between rooms
@grid.map [radius] [--center <x> <y>] - Render ASCII map
See the Grid System Guide for complete documentation.
Procedural Wilderness Generation¶
The wilderness system generates rooms on-demand as players explore, creating consistent terrain based on noise algorithms.
Enabling Wilderness¶
from maid_engine.world import WildernessManager, WildernessConfig, Landmark
from maid_stdlib.components.core import DescriptionComponent
config = WildernessConfig(
seed=42, # Same seed = same terrain
min_x=-1000, max_x=1000,
min_y=-1000, max_y=1000,
cleanup_interval=300.0, # Clean up empty rooms
room_max_age=600.0,
)
# A room_factory is required to build real, enterable rooms. Without one the
# manager only allocates deterministic UUIDs (no entity, no room-index entry).
def room_factory(x, y, biome_name, biome_def, rng):
# Generate name/description once — each call advances the RNG.
name = biome_def.generate_name(rng)
description = biome_def.generate_description(rng)
room = engine.world.create_entity()
room.add(DescriptionComponent(name=name, long_desc=description))
engine.world.register_room(room.id, {"name": name, "exits": {}})
return room.id
wilderness = WildernessManager(
world=engine.world, config=config, room_factory=room_factory
)
Biomes¶
The terrain generator creates these biomes:
| Biome | Conditions | Movement Cost |
|---|---|---|
| Plains | Default moderate elevation | 1.0 |
| Forest | Moderate moisture | 1.0 |
| Hills | Rolling elevation | 1.3 |
| Desert | Hot and dry | 1.5 |
| Tundra | Cold | 1.5 |
| Mountains | High elevation | 2.0 |
| Swamp | High moisture near water | 2.0 |
| Water | Low elevation | 3.0 |
Values are the movement_cost of each entry in maid_stdlib.world.biomes.DEFAULT_BIOMES
and are all >= 1.0 (the BiomeDefinition validator rejects anything lower).
Adding Landmarks¶
Landmarks bias procedural terrain around a fixed coordinate (forcing a biome or
naming a notable spot). Generation keys off coord, radius, and
biome_override:
# Bias the terrain around (0, 0) toward plains and name it
wilderness.add_landmark(Landmark(
coord=(0, 0),
name="Starting Town",
radius=5,
biome_override="plains",
))
# Dungeon entrance
wilderness.add_landmark(Landmark(
coord=(500, 300),
name="Dragon's Lair",
radius=3,
description="A dark cave entrance looms ahead.",
metadata={"dungeon_id": "dragons_lair"},
))
Landmark.room_idis stored but not used during generation. The wilderness manager always creates a fresh room for a generated coordinate; it does not substitute a pre-existing room referenced byroom_id. To place a hand-built room at a spot, register that room separately (e.g. on the grid) rather than expecting a landmark to "reuse" it.
Wilderness Commands¶
@wilderness.stats - Show wilderness statistics
@wilderness.preview [<x> <y>] - Preview terrain at coordinate
@wilderness.landmark add <x> <y> <name> [--radius <n>] [--biome <type>] - Add landmark
@wilderness.landmark remove <x> <y> - Remove landmark
@wilderness.landmark list [--near <radius>] - List all landmarks
See the Wilderness System Guide for complete documentation.
Zone Management¶
Zones (or areas) group related rooms for organization, access control, and events.
Creating Zones¶
Assigning Rooms to Zones¶
@zone assign takes the room as here, a room name, or #<room_id> (there is
no room: prefix):
@zone assign here Darkwood Forest
@zone assign Inn "Town"
@zone assign Market "Town"
@zone assign Tavern "Town"
Zone Information¶
Zone Properties¶
Set zone-wide properties in batch files or code:
from uuid import uuid4
zone = ZoneData(
id=uuid4(),
name="Darkwood Forest",
level_min=5,
level_max=15,
metadata={"respawn_rate": 300.0, "outdoor": True, "wilderness": True},
)
Zone-Based Features¶
Zones enable:
- Level-appropriate spawns: Monsters scale to zone level range
- PvP control: Enable/disable PvP per zone
- Weather: Zones can have independent weather
- Events: Zone-wide events (invasions, festivals)
- Access control: Restrict entry by level or faction
Best Practices¶
Naming Conventions¶
| Type | Convention | Example |
|---|---|---|
| Rooms | Title Case | "The Grand Hall" |
| Zones | Title Case | "Darkwood Forest" |
| Exits | lowercase | "north", "secret_door" |
| Tags | snake_case | "safe_zone", "no_teleport" |
Organization Tips¶
- Plan before building: Sketch your area layout first
- Use zones early: Assign rooms to zones as you create them
- Consistent descriptions: Maintain a consistent tone and style
- Test navigation: Walk through your area as a player would
- Document landmarks: Keep notes on important locations
Performance Considerations¶
- Batch creation: Use batch files for large areas
- Wilderness limits: Set reasonable boundaries
- Room cleanup: Enable wilderness cleanup for procedural areas
- Grid size: Consider memory when creating large grids
Testing Your World¶
@goto #room-uuid - Teleport to test locations
@find room name:*test* - Find test rooms (filters use key:value)
@purge type:item --global - Clean up test items
Example: Creating a Small Town¶
Here's a complete example of creating a town. Because @dig creates a room but
leaves you where you are, each new room is entered with @goto before it is
described or tagged:
# Start at the center
@create room Town Square
@goto Town Square
@describe here The central square of the town, with a well at its center.
@zone create Riverside Town
@zone assign here "Riverside Town"
# Tavern to the north (dig, then walk into it)
@dig north = The Rusty Tankard
@goto The Rusty Tankard
@describe here A cozy tavern with a roaring fireplace.
@zone assign here "Riverside Town"
# Guest rooms above the tavern
@dig up = Guest Rooms
@goto Guest Rooms
@describe here A narrow hallway with doors to small guest rooms.
@zone assign here "Riverside Town"
# Back to the square to branch off again
@goto Town Square
# General store to the east
@dig east = General Store
@goto General Store
@describe here Shelves lined with various goods and supplies.
@zone assign here "Riverside Town"
@goto Town Square
# Town gate to the south, then the road out
@dig south = Town Gate
@goto Town Gate
@describe here The main entrance to the town, flanked by two guards.
@zone assign here "Riverside Town"
@dig south = Forest Road
@goto Forest Road
@describe here A dirt road leading into the dark forest.
# Don't add to town zone - it's wilderness
@goto Town Square
# Temple to the west
@dig west = Temple of Light
@goto Temple of Light
@describe here A serene temple with stained glass windows.
@zone assign here "Riverside Town"
Note on room tags: Room-level flags such as
safe_zone,no_combat, orshopare not applied with@attribute— that command is entity-only and rejects room targets. Define such flags in the room's YAMLtags:field via the content loader, or have a content pack read them from a room component it registers.RoomDatarooms created by@dig/@create roomdo not carry arbitrary tags on their own.
Related Documentation¶
- Building Commands Reference - Complete command reference
- Grid System Guide - Detailed grid documentation
- Wilderness System Guide - Procedural generation details
- Extended Room Features - Dynamic room descriptions
- Advanced Command System - Building command customization