Extended Room Features Guide¶
Overview¶
The MAID Extended Room System provides dynamic, context-aware room descriptions that change based on time of day, season, weather, and other game state. This creates immersive environments where rooms feel alive and responsive to the game world.
Key features: - Time-Based Descriptions: Different descriptions for day, night, dawn, dusk - Season Variations: Descriptions that change with the seasons - Weather Effects: Overlay text during rain, snow, fog, storms - Conditional Details: Show details based on player state or game conditions - Random Details: Weighted random selection from detail pools - Dynamic Exits: Exit descriptions that change based on state - Caching: Efficient description caching for performance
Quick Start¶
from maid_stdlib.components.extended_room import (
ExtendedRoomComponent,
ExtendedDescriptions,
TimeOfDay,
Season,
Weather,
RoomDetail,
)
# Create dynamic room descriptions
descriptions = ExtendedDescriptions(
base_description="A cozy tavern with wooden beams and a crackling fireplace.",
time_variants={
TimeOfDay.NIGHT: "The tavern is quiet, with only a few patrons nursing drinks.",
TimeOfDay.NOON: "The tavern bustles with the lunch crowd.",
},
season_variants={
Season.WINTER: "Frost clings to the windows.",
},
weather_effects={
Weather.RAIN: "Rain drums steadily on the roof above.",
Weather.STORM: "Thunder rattles the windowpanes.",
},
)
# Create component
room_component = ExtendedRoomComponent(descriptions=descriptions)
# Render description with context
context = {
"time": TimeOfDay.NIGHT,
"season": Season.WINTER,
"weather": Weather.RAIN,
}
description = room_component.get_description(context)
# "A cozy tavern... The tavern is quiet... Frost clings... Rain drums..."
Time of Day¶
TimeOfDay Enum¶
from maid_stdlib.components.extended_room import TimeOfDay
# Time periods and their hour ranges
TimeOfDay.MIDNIGHT # 0:00 - 0:59
TimeOfDay.NIGHT # 1:00 - 4:59, 22:00 - 23:59
TimeOfDay.DAWN # 5:00 - 6:59
TimeOfDay.MORNING # 7:00 - 11:59
TimeOfDay.NOON # 12:00 - 12:59
TimeOfDay.AFTERNOON # 13:00 - 16:59
TimeOfDay.DUSK # 17:00 - 18:59
TimeOfDay.EVENING # 19:00 - 21:59
# Get time from hour
time = TimeOfDay.from_hour(14) # AFTERNOON
# Check if dark
if TimeOfDay.NIGHT.is_dark:
print("It's dark outside")
# Get light level (0.0 - 1.0)
light = TimeOfDay.NOON.light_level # 1.0
light = TimeOfDay.NIGHT.light_level # 0.1
Light Levels¶
| Time | Light Level |
|---|---|
| MIDNIGHT | 0.0 |
| NIGHT | 0.1 |
| DAWN | 0.4 |
| MORNING | 0.8 |
| NOON | 1.0 |
| AFTERNOON | 0.9 |
| DUSK | 0.4 |
| EVENING | 0.3 |
Seasons¶
Season Enum¶
from maid_stdlib.components.extended_room import Season
# Seasons
Season.SPRING
Season.SUMMER
Season.AUTUMN
Season.WINTER
# Get season from month (Northern Hemisphere)
season = Season.from_month(7) # SUMMER
# Temperature modifier
modifier = Season.WINTER.temperature_modifier # 0.5
# Cycle through seasons
next_season = Season.SPRING.next() # SUMMER
Temperature Modifiers¶
| Season | Modifier |
|---|---|
| WINTER | 0.5 |
| SPRING | 0.8 |
| SUMMER | 1.2 |
| AUTUMN | 0.9 |
Weather¶
Weather Enum¶
from maid_stdlib.components.extended_room import Weather
# Weather types
Weather.CLEAR
Weather.CLOUDY
Weather.RAIN
Weather.STORM
Weather.SNOW
Weather.FOG
Weather.WIND
# Visibility modifier (0.0 - 1.0)
visibility = Weather.FOG.visibility_modifier # 0.2
# Movement modifier
movement = Weather.STORM.movement_modifier # 0.7
# Check if outdoor-only
if Weather.RAIN.outdoor_only:
print("Only affects outdoor rooms")
# Check valid transitions
if Weather.CLOUDY.can_transition_to(Weather.RAIN):
print("Weather can change to rain")
Weather Effects¶
| Weather | Visibility | Movement |
|---|---|---|
| CLEAR | 1.0 | 1.0 |
| CLOUDY | 0.9 | 1.0 |
| RAIN | 0.6 | 0.9 |
| STORM | 0.3 | 0.7 |
| SNOW | 0.5 | 0.6 |
| FOG | 0.2 | 0.8 |
| WIND | 0.8 | 0.9 |
Valid Weather Transitions¶
CLEAR -> CLOUDY, WIND, FOG
CLOUDY -> CLEAR, RAIN, SNOW, FOG
RAIN -> STORM, CLOUDY, CLEAR
STORM -> RAIN
SNOW -> CLOUDY, CLEAR, STORM
FOG -> CLEAR, CLOUDY, RAIN
WIND -> CLEAR, CLOUDY, STORM, RAIN
Extended Descriptions¶
Basic Configuration¶
from maid_stdlib.components.extended_room import (
ExtendedDescriptions,
TimeOfDay,
Season,
Weather,
)
descriptions = ExtendedDescriptions(
base_description="The town square is paved with worn cobblestones.",
# Time variants
time_variants={
TimeOfDay.NIGHT: "The square is empty under the starlight.",
TimeOfDay.MORNING: "Merchants are setting up their stalls.",
TimeOfDay.NOON: "The square is crowded with shoppers.",
TimeOfDay.EVENING: "The last merchants pack up their wares.",
},
time_mode="append", # or "replace"
# Season variants
season_variants={
Season.SPRING: "Cherry blossoms scatter across the stones.",
Season.SUMMER: "Heat shimmers rise from the cobblestones.",
Season.AUTUMN: "Fallen leaves crunch underfoot.",
Season.WINTER: "Snow blankets the square in white.",
},
season_mode="append", # or "replace"
# Weather effects
weather_effects={
Weather.RAIN: "Puddles form between the cobblestones.",
Weather.SNOW: "Fresh snow accumulates on every surface.",
Weather.FOG: "Mist obscures the far end of the square.",
},
# Mood and atmosphere
mood="bustling",
atmosphere_text="The sounds of commerce fill the air.",
)
Time and Season Modes¶
Append Mode (default): Variants are added to the base description.
Replace Mode: Variants replace the base description.
Room Details¶
RoomDetail¶
Individual details that can be shown conditionally.
from maid_stdlib.components.extended_room import RoomDetail, TimeOfDay
detail = RoomDetail(
text="A cat dozes on the windowsill.",
weight=1.0, # Selection weight
conditions={
"time": TimeOfDay.AFTERNOON, # Only show in afternoon
},
)
# Check if conditions pass
context = {"time": TimeOfDay.AFTERNOON}
if detail.check_conditions(context):
print(detail.text)
Condition Types¶
# Equality
RoomDetail(text="...", conditions={"time": TimeOfDay.NIGHT})
# List membership
RoomDetail(text="...", conditions={
"weather": [Weather.RAIN, Weather.STORM],
})
# Comparison tuple
RoomDetail(text="...", conditions={
"player_level": (">=", 10), # >, >=, <, <=, ==, !=
})
# Callable (receives context value)
RoomDetail(text="...", conditions={
"perception": lambda x: x >= 15,
})
# Callable (receives full context if key missing)
RoomDetail(text="...", conditions={
"custom": lambda ctx: ctx.get("has_lantern", False),
})
Conditional Details¶
Details shown when their conditions match:
descriptions = ExtendedDescriptions(
base_description="A quiet library.",
conditional_details=[
RoomDetail(
text="Moonlight streams through the windows.",
conditions={"time": TimeOfDay.NIGHT},
),
RoomDetail(
text="You notice a hidden book behind the shelf.",
conditions={"perception": (">=", 15)},
),
RoomDetail(
text="Your guild insignia glows faintly.",
conditions={"guild": "mages"},
),
],
)
Random Details¶
Details selected randomly with weights:
descriptions = ExtendedDescriptions(
base_description="A forest clearing.",
random_details=[
RoomDetail(text="A deer grazes nearby.", weight=2.0),
RoomDetail(text="Birds sing in the trees.", weight=3.0),
RoomDetail(text="A butterfly flutters past.", weight=1.0),
RoomDetail(text="You hear wolves in the distance.", weight=0.5),
],
max_random_details=2, # Select up to 2
)
Higher weights mean more likely selection. Details can also have conditions:
random_details=[
RoomDetail(
text="Fireflies dance in the darkness.",
weight=2.0,
conditions={"time": TimeOfDay.NIGHT},
),
RoomDetail(
text="Sunbeams pierce the canopy.",
weight=2.0,
conditions={"time": [TimeOfDay.MORNING, TimeOfDay.AFTERNOON]},
),
]
Detail Collections¶
DetailCollection¶
For managing groups of details:
from maid_stdlib.components.extended_room import DetailCollection, RoomDetail
collection = DetailCollection(max_details=3)
# Add details
collection.add(RoomDetail(text="A painting hangs on the wall.", weight=1.0))
collection.add(RoomDetail(text="Candles flicker on the table.", weight=1.5))
# Remove detail
collection.remove("A painting hangs on the wall.")
# or
collection.remove(detail_object)
# Get all matching details
context = {"time": TimeOfDay.EVENING}
matching = collection.get_matching(context)
# Weighted random selection
from random import Random
rng = Random(42)
selected = collection.select_weighted(context, count=2, rng=rng)
# Convenience method (uses max_details)
selected = collection.select(context, rng=rng)
Extended Exit Descriptions¶
ExtendedExitDescription¶
Dynamic exit descriptions based on state:
from maid_stdlib.components.extended_room import (
ExtendedExitDescription,
ExitState,
TimeOfDay,
)
exit_desc = ExtendedExitDescription(
direction="north",
base_description="A sturdy oak door leads north.",
state_variants={
ExitState.OPEN: "The oak door stands open.",
ExitState.CLOSED: "A sturdy oak door blocks the way north.",
ExitState.LOCKED: "The oak door is locked tight.",
ExitState.HIDDEN: "There appears to be a door hidden behind the bookshelf.",
},
time_variants={
TimeOfDay.NIGHT: "Moonlight seeps through cracks in the door.",
},
hidden_until={"perception": (">=", 12)}, # Reveal conditions
)
# Render with state and context
context = {"time": TimeOfDay.NIGHT, "perception": 15}
text = exit_desc.render(state=ExitState.CLOSED, context=context)
Exit States¶
from maid_stdlib.components.extended_room import ExitState
ExitState.OPEN # Exit is open and passable
ExitState.CLOSED # Exit is closed but visible
ExitState.LOCKED # Exit is locked, requires key
ExitState.HIDDEN # Exit not visible until conditions met
Hidden Exit Conditions¶
# Hidden until perception check passes
hidden_until={"perception": (">=", 15)}
# Hidden until player has specific item
hidden_until={"has_key": True}
# Hidden until quest complete
hidden_until={"quest_complete": "find_secret_door"}
# Multiple conditions (all must pass)
hidden_until={
"perception": (">=", 12),
"class": ["rogue", "ranger"],
}
ExtendedRoomComponent¶
Basic Usage¶
from maid_stdlib.components.extended_room import (
ExtendedRoomComponent,
ExtendedDescriptions,
ExtendedExitDescription,
ExitState,
)
component = ExtendedRoomComponent(
descriptions=ExtendedDescriptions(
base_description="A stone chamber.",
# ...
),
exit_descriptions={
"north": ExtendedExitDescription(
direction="north",
base_description="A passage leads north.",
),
"east": ExtendedExitDescription(
direction="east",
base_description="A locked iron gate blocks the east.",
state_variants={
ExitState.LOCKED: "A locked iron gate blocks the east.",
ExitState.OPEN: "The iron gate stands open.",
},
),
},
)
Getting Descriptions¶
# Get room description
context = {
"time": TimeOfDay.NIGHT,
"season": Season.WINTER,
"weather": Weather.CLEAR,
"player_level": 15,
}
description = component.get_description(context)
# Get exit description
exit_text = component.get_exit_description(
direction="east",
state=ExitState.LOCKED,
context=context,
)
# Invalidate cache when room changes
component.invalidate_cache()
Caching¶
The component caches descriptions for performance:
# First call generates and caches
desc1 = component.get_description(context)
# Second call returns cached result
desc2 = component.get_description(context)
# Force regeneration
desc3 = component.get_description(context, use_cache=False)
# Cache is keyed on time, season, weather
# Changing these invalidates the cache
Complete Example¶
from maid_stdlib.components.extended_room import (
ExtendedRoomComponent,
ExtendedDescriptions,
ExtendedExitDescription,
RoomDetail,
TimeOfDay,
Season,
Weather,
ExitState,
)
# Create a tavern with full dynamic descriptions
tavern = ExtendedRoomComponent(
descriptions=ExtendedDescriptions(
base_description=(
"The Rusty Tankard is a warm and welcoming tavern. "
"Wooden tables fill the common room, and a large fireplace "
"dominates the north wall."
),
time_variants={
TimeOfDay.MORNING: "Early morning light streams through dusty windows. The tavern is nearly empty.",
TimeOfDay.NOON: "The lunch crowd fills every table. Servers weave between patrons.",
TimeOfDay.EVENING: "The tavern is alive with chatter and laughter as regulars gather.",
TimeOfDay.NIGHT: "Only a few night owls remain, nursing their drinks in the dim light.",
TimeOfDay.MIDNIGHT: "The tavern is silent and dark, chairs upturned on tables.",
},
time_mode="append",
season_variants={
Season.WINTER: "A roaring fire in the hearth fights back the winter chill.",
Season.SUMMER: "Windows stand open to catch any hint of breeze.",
},
weather_effects={
Weather.RAIN: "Rain patters against the windows, making the inside feel even cozier.",
Weather.STORM: "Thunder rumbles outside, and patrons glance nervously at the door.",
},
conditional_details=[
RoomDetail(
text="The bartender gives you a knowing nod.",
conditions={"reputation_tavern": (">=", 50)},
),
RoomDetail(
text="You notice a suspicious figure in the corner booth.",
conditions={"perception": (">=", 12)},
),
],
random_details=[
RoomDetail(text="A bard tunes a lute by the fireplace.", weight=2.0),
RoomDetail(text="Two dwarves argue loudly about mining rights.", weight=1.5),
RoomDetail(text="A serving girl drops a tray of mugs with a crash.", weight=0.5),
RoomDetail(text="The smell of fresh bread wafts from the kitchen.", weight=3.0),
],
max_random_details=2,
mood="cozy",
atmosphere_text="The warmth of good company fills the air.",
),
exit_descriptions={
"north": ExtendedExitDescription(
direction="north",
base_description="A sturdy door leads to the street.",
time_variants={
TimeOfDay.NIGHT: "The door is barred for the night.",
},
),
"up": ExtendedExitDescription(
direction="up",
base_description="Stairs lead up to the guest rooms.",
),
"down": ExtendedExitDescription(
direction="down",
base_description="A trapdoor leads to the cellar.",
state_variants={
ExitState.HIDDEN: "", # Invisible until found
ExitState.LOCKED: "The cellar door is padlocked.",
},
hidden_until={"perception": (">=", 15)},
),
},
)
# Render for a specific context
context = {
"time": TimeOfDay.EVENING,
"season": Season.WINTER,
"weather": Weather.RAIN,
"perception": 16,
"reputation_tavern": 60,
}
description = tavern.get_description(context)
print(description)
Builder Commands¶
MAID provides in-game commands for extended room management (BUILDER access level):
@room time <target> <period> <description> - Set time variant
@room season <target> <season> <description> - Set season variant
@room weather <target> <weather> <description> - Set weather effect
@room detail <target> add <text> [weight] - Add random detail
@room detail <target> remove <text> - Remove detail
@room detail <target> list - List all details
@room exit <target> <direction> <description> - Set exit description
@room mood <target> <mood> - Set room mood
@room atmosphere <target> <text> - Set atmosphere text
Integration with Game Systems¶
Time System Integration¶
# In your game tick or time system
def update_world_time():
hour = get_game_hour()
time_of_day = TimeOfDay.from_hour(hour)
# Update context for room rendering
context["time"] = time_of_day
# Invalidate caches if time period changed
if time_of_day != previous_time:
for room in rooms_with_extended_descriptions:
room.invalidate_cache()
Weather System Integration¶
# In your weather system
def change_weather(new_weather: Weather, region):
old_weather = region.current_weather
if old_weather.can_transition_to(new_weather):
region.current_weather = new_weather
# Update context
for room in region.outdoor_rooms:
context = {"weather": new_weather}
room.invalidate_cache()
# Apply weather effects
if new_weather.outdoor_only and not room.is_outdoor:
continue # Skip indoor rooms
# Modify visibility/movement
visibility = new_weather.visibility_modifier
movement = new_weather.movement_modifier