Teleport Portal¶
Problem¶
You want a portal object that teleports players from one room to another when they interact with it — optionally across different worlds.
Solution¶
The Portal Component¶
from uuid import UUID
from maid_engine.core.ecs import Component
class PortalComponent(Component):
"""Marks an entity as a teleportation portal."""
destination_room_id: UUID
destination_world_id: str | None = None # For cross-world portals
portal_name: str = "shimmering portal"
use_message: str = "You step through the portal..."
arrive_message: str = "{name} steps out of a portal."
cooldown: float = 0.0 # Seconds between uses
last_used: float = 0.0
one_way: bool = False # If True, no return portal
requires_key: str | None = None # Item keyword needed to activate
The Enter Command¶
from uuid import UUID
from maid_engine.commands.decorators import command, arguments
from maid_engine.commands.arguments import ArgumentSpec, ArgumentType, ParsedArguments
from maid_engine.commands import CommandContext
from maid_engine.core.ecs import Entity
from maid_stdlib.components import DescriptionComponent
def _find_portal(ctx: CommandContext, keyword: str) -> Entity | None:
"""Find a portal entity in the player's room."""
room_id = ctx.world.get_entity_room(ctx.player_id)
if not room_id:
return None
for entity in ctx.world.entities_in_room(room_id):
portal = entity.try_get(PortalComponent)
if not portal:
continue
desc = entity.try_get(DescriptionComponent)
if desc and desc.matches_keyword(keyword):
return entity
if keyword.lower() in portal.portal_name.lower():
return entity
return None
@command(name="enter", aliases=["portal"], category="movement",
help_text="Enter a portal to teleport")
@arguments(
ArgumentSpec("target", ArgumentType.STRING, required=False, default="portal"),
)
async def cmd_enter_portal(ctx: CommandContext, args: ParsedArguments) -> bool:
"""Step through a portal to teleport to another location."""
keyword: str = args["target"]
portal_entity = _find_portal(ctx, keyword)
if not portal_entity:
await ctx.session.send("There's no portal here.\n")
return False
portal = portal_entity.get(PortalComponent)
# Check key requirement
if portal.requires_key:
player = ctx.world.get_entity(ctx.player_id)
if not player:
return False
from maid_stdlib.components import InventoryComponent
inv = player.try_get(InventoryComponent)
has_key = False
if inv:
for item_id in inv.items:
item = ctx.world.get_entity(item_id)
if item:
desc = item.try_get(DescriptionComponent)
if desc and desc.matches_keyword(portal.requires_key):
has_key = True
break
if not has_key:
await ctx.session.send(
"The portal resists your touch. You seem to need something.\n"
)
return False
# Verify destination exists
dest_room = ctx.world.get_room(portal.destination_room_id)
if not dest_room:
await ctx.session.send("The portal flickers but leads nowhere.\n")
return False
# Announce departure
from_room_id = ctx.world.get_entity_room(ctx.player_id)
await ctx.session.send(f"{portal.use_message}\n")
# Teleport
ctx.world.move_entity(ctx.player_id, portal.destination_room_id)
# Show the new room (reuse look command pattern)
await ctx.session.send("You arrive in a new location.\n")
return True
Creating a Portal Pair¶
from uuid import UUID
from maid_engine.core.world import World
from maid_stdlib.components import DescriptionComponent
async def create_portal_pair(
world: World,
room_a: UUID,
room_b: UUID,
) -> None:
"""Create a bidirectional portal between two rooms."""
# Portal A → B
portal_ab = world.create_entity()
portal_ab.add(DescriptionComponent(
name="Blue Portal",
short_desc="a shimmering blue portal",
long_desc="A swirling vortex of blue energy hangs in the air.",
keywords=["blue", "portal"],
))
portal_ab.add(PortalComponent(
destination_room_id=room_b,
portal_name="blue portal",
use_message="You step into the blue vortex. The world spins...",
arrive_message="{name} tumbles out of a blue portal.",
))
portal_ab.add_tag("portal")
world.place_entity_in_room(portal_ab.id, room_a)
# Portal B → A (return portal)
portal_ba = world.create_entity()
portal_ba.add(DescriptionComponent(
name="Red Portal",
short_desc="a shimmering red portal",
long_desc="A swirling vortex of red energy hangs in the air.",
keywords=["red", "portal"],
))
portal_ba.add(PortalComponent(
destination_room_id=room_a,
portal_name="red portal",
use_message="You step into the red vortex. The world spins...",
arrive_message="{name} tumbles out of a red portal.",
))
portal_ba.add_tag("portal")
world.place_entity_in_room(portal_ba.id, room_b)
Builder Commands¶
You can also create portals in-game:
@create item Blue Portal
@describe Blue Portal = A swirling vortex of blue energy.
@component Blue Portal add PortalComponent {"destination_room_id": "<target-room-uuid>", "portal_name": "blue portal"}
@teleport Blue Portal here
How It Works¶
- PortalComponent stores the destination and activation rules
cmd_enter_portalfinds a matching portal in the room, checks requirements, then callsworld.move_entity()world.move_entity()handles updatingPositionComponentand room index automatically- For bidirectional travel, create two portal entities — one in each room
Variations¶
- Cross-world portals: Use
WorldManagerto look up a destination in a different world instance - Timed portals: Add an expiry timestamp — a system removes the portal after N minutes
- Random destination: Pick from a list of
destination_room_ids each time - Level-gated portal: Add
locks="level(10)"to restrict portal use by level - Portal network: Create a
PortalNetworkSystemthat links multiple portals like a fast-travel map - Visual effects: Subscribe to a custom
PortalActivatedEventto trigger room description changes
See Also¶
- Level Gating — Restricting access by character level
- Building Commands —
@create,@component - Multi-World Support — Cross-world teleportation