Skip to content

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

MAID provides multiple approaches to world building:

  1. Manual Building - Create rooms one at a time using in-game builder commands
  2. Grid-Based Building - Use coordinates for structured layouts like towns or dungeons
  3. Procedural Generation - Generate wilderness areas on-demand as players explore
  4. 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

  1. Navigate to where you want to build (or create a starting room)
  2. Create rooms using @dig or @create
  3. Move into the new room with @goto (@dig creates the room but does not move you there)
  4. Describe rooms with @describe
  5. Connect rooms with exits using @dig, @tunnel, or @link
  6. Organize rooms into zones with @zone

@dig does 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:

@dig north = The Grand Hall

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:

@dig east

Using @create

For rooms not connected to anything (isolated areas, limbo rooms):

@create room The Void

Then connect it later with @link or @tunnel.

Setting Room Descriptions

Use @describe for single-line descriptions:

@describe here A cozy tavern with worn wooden tables and a crackling fireplace.

For multi-line descriptions, use @edit:

@edit here/description

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:

@set here/name = The Rusty Tankard
@set here/description = A cozy tavern with worn wooden tables.

Note: @set here/<field> cannot create new fields on a builder-created room. Custom attributes such as sector_type, light_level, or terrain metadata are not part of RoomData and 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 a RoomData room 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:

@dig north = Bedroom

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:

@tunnel east #550e8400-e29b-41d4-a716-446655440000
@tunnel west Market Square

One-Way Exits

For trapdoors, slides, or magical portals:

@link down #destination-uuid --oneway

Removing Exits

Remove an exit from your current room:

@unlink north           # Remove exit, keep return exit
@unlink north --both    # Remove both directions

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

@grid.create 0 0 9 9

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

      Y+
      |
      |  (0,1)  (1,1)  (2,1)
      |  (0,0)  (1,0)  (2,0)
      +----------------> X+
  • 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.0GridRoom 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_id is 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 by room_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

@zone create Darkwood Forest
@zone create "The Haunted Mansion" A spooky mansion on the hill.

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 list                    - List all zones
@zone info Darkwood Forest    - Zone details (room count, players, etc.)

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

  1. Plan before building: Sketch your area layout first
  2. Use zones early: Assign rooms to zones as you create them
  3. Consistent descriptions: Maintain a consistent tone and style
  4. Test navigation: Walk through your area as a player would
  5. 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, or shop are not applied with @attribute — that command is entity-only and rejects room targets. Define such flags in the room's YAML tags: field via the content loader, or have a content pack read them from a room component it registers. RoomData rooms created by @dig/@create room do not carry arbitrary tags on their own.