Locked Door¶
Problem¶
You want a door that only opens when the player carries a specific key item. The door should block movement until unlocked.
Solution¶
This uses MAID's built-in lock expressions and ExitMetadataComponent — no custom system needed for the basic case.
Setting Up the Locked Exit (Builder Commands)¶
The fastest way is to use in-game builder commands:
@dig north = Throne Room
@set here/exits/north/locked = true
@set here/exits/north/door = true
@set here/exits/north/key_id = iron_key
@set here/exits/north/door_name = iron-bound door
Creating the Key Item¶
@create item Iron Key
@set Iron Key/item_type = key
@set Iron Key/keywords = ["iron", "key", "iron key"]
@describe Iron Key = A heavy iron key with ornate teeth. It looks like it fits a large door.
Programmatic Setup¶
To create the locked door in code (e.g., during on_load):
from uuid import UUID
from maid_engine.core.world import World
from maid_stdlib.components import (
DescriptionComponent,
ExitMetadataComponent,
ExitInfo,
ItemComponent,
)
async def create_locked_door_area(world: World) -> None:
"""Create two rooms connected by a locked door."""
# Create the rooms
hallway = world.create_entity()
hallway.add(DescriptionComponent(
name="Grand Hallway",
long_desc="A long stone hallway stretches before you. "
"An iron-bound door blocks the way north.",
keywords=["hallway"],
))
hallway.add_tag("room")
world.register_room(hallway.id, {"name": "Grand Hallway"})
throne_room = world.create_entity()
throne_room.add(DescriptionComponent(
name="Throne Room",
long_desc="A magnificent throne sits atop a raised dais.",
keywords=["throne", "room"],
))
throne_room.add_tag("room")
world.register_room(throne_room.id, {"name": "Throne Room"})
# Lock the north exit from the hallway
hallway.add(ExitMetadataComponent(
exits={
"north": ExitInfo(
door=True,
locked=True,
key_id="iron_key",
door_name="iron-bound door",
),
},
))
# Wire the actual exit route so movement works once unlocked
hallway_data = world.get_room(hallway.id)
if hallway_data is not None:
if isinstance(hallway_data, dict):
hallway_data.setdefault("exits", {})["north"] = throne_room.id
elif hasattr(hallway_data, "exits"):
hallway_data.exits["north"] = throne_room.id
# Create the key item
key = world.create_entity()
key.add(DescriptionComponent(
name="Iron Key",
short_desc="a heavy iron key",
long_desc="A heavy iron key with ornate teeth.",
keywords=["iron", "key", "iron key"],
))
key.add(ItemComponent(
item_type="key",
weight=0.5,
value=0,
))
key.add_tag("item")
Gating a Command with a Lock Expression¶
If you want a command that only works when the player has the key:
from maid_engine.commands.decorators import command
from maid_engine.commands import CommandContext
@command(
name="unlock",
category="interaction",
help_text="Unlock a door with the right key",
locks="has_item(iron_key)",
)
async def cmd_unlock(ctx: CommandContext) -> bool:
"""Unlock a door if the player has the key."""
room_id = ctx.world.get_entity_room(ctx.player_id)
if not room_id:
return False
room = ctx.world.get_entity(room_id) if room_id else None
if not room:
return False
exit_meta = room.try_get(ExitMetadataComponent)
if not exit_meta:
await ctx.session.send("There's nothing to unlock here.\n")
return False
direction = ctx.args[0] if ctx.args else "north"
exit_info = exit_meta.exits.get(direction)
if not exit_info or not exit_info.locked:
await ctx.session.send(f"There's no locked door to the {direction}.\n")
return False
exit_info.locked = False
exit_info.door_open = True
exit_meta.notify_mutation()
await ctx.session.send(
f"You unlock the {exit_info.door_name or 'door'} with your iron key.\n"
)
return True
How It Works¶
- ExitMetadataComponent stores per-direction exit state:
door,locked,hidden,key_id - The movement system checks
ExitInfo.lockedbefore allowing passage - Lock expressions like
has_item(iron_key)are evaluated against the player's inventory at command time notify_mutation()marks the entity dirty so persistence picks up the change
Variations¶
- Multiple keys for one door: Use a custom lock function instead of the built-in
has_item - One-use key: Remove the key from inventory after unlocking with
inventory.remove_item(key.id, weight) - Lockpicking: Add a skill check —
locks="has_item(iron_key) OR has_skill(lockpick, 5)" - Timed relock: Subscribe to a timer event to set
locked = Trueagain after N seconds
See Also¶
- Command System Guide — Lock expressions reference
- Building Commands —
@set,@dig,@create - ECS Components — How components work