Skip to content

Weather Effects

Problem

You want room descriptions to change dynamically based on the current weather — rain makes streets slippery, fog hides exits, snow blankets the ground.

Solution

MAID's ExtendedRoomComponent has built-in support for weather-variant descriptions. No custom system is needed for the basic case.

Setting Up Weather Descriptions (Builder Commands)

@room weather here clear The marketplace bustles under a bright sky.
@room weather here rain Rain hammers the cobblestones. Merchants huddle under awnings.
@room weather here fog Thick fog rolls through the square. You can barely see your hand.
@room weather here snow A blanket of fresh snow covers the marketplace. Footprints criss-cross the ground.
@room mood here bustling
@room atmosphere here The smell of baking bread drifts from a nearby stall.

Programmatic Setup

from uuid import UUID
from maid_engine.core.world import World
from maid_stdlib.components import DescriptionComponent
from maid_stdlib.components.extended_room import (
    ExtendedRoomComponent,
    ExtendedDescriptions,
    Weather,
    TimeOfDay,
    Season,
    RoomDetail,
)


def create_weather_room(world: World) -> UUID:
    """Create a room with weather-reactive descriptions."""
    room = world.create_entity()
    room.add(DescriptionComponent(
        name="Town Square",
        keywords=["square", "town"],
    ))
    room.add(ExtendedRoomComponent(
        descriptions=ExtendedDescriptions(
            base_description=(
                "A wide cobblestone square surrounded by timber-framed shops."
            ),
            # Weather-specific overlays
            weather_effects={
                Weather.CLEAR: (
                    "Sunlight warms the stones. Street performers "
                    "entertain a small crowd."
                ),
                Weather.RAIN: (
                    "Rain hammers the cobblestones, pooling in the "
                    "gutters. Most shops have closed their shutters."
                ),
                Weather.FOG: (
                    "Dense fog swallows the square. The buildings are "
                    "dim shapes in the murk."
                ),
                Weather.SNOW: (
                    "Fresh snow blankets everything in white. A child "
                    "builds a snowman near the fountain."
                ),
                Weather.STORM: (
                    "Thunder cracks overhead. Lightning illuminates "
                    "the empty square in stark flashes."
                ),
            },
            # Time-of-day variants
            time_variants={
                TimeOfDay.DAWN: (
                    "The first light of dawn paints the rooftops gold."
                ),
                TimeOfDay.NIGHT: (
                    "Lanterns cast pools of warm light across the square."
                ),
                TimeOfDay.MIDNIGHT: (
                    "The square is deserted. Only a stray cat prowls "
                    "between the shuttered stalls."
                ),
            },
            time_mode="append",  # Appended after base description
            # Seasonal changes
            season_variants={
                Season.SPRING: "Flower boxes overflow with color.",
                Season.AUTUMN: "Fallen leaves skitter across the stones.",
                Season.WINTER: "Icicles hang from the eaves.",
            },
            season_mode="append",
            # Random ambient details
            random_details=[
                RoomDetail(text="A merchant argues loudly with a customer."),
                RoomDetail(text="A pigeon lands on the fountain rim."),
                RoomDetail(text="The smell of roasted chestnuts fills the air."),
                RoomDetail(
                    text="Snowflakes drift lazily down.",
                    conditions={"weather": Weather.SNOW},
                ),
            ],
            max_random_details=2,
            mood="lively",
            atmosphere_text=(
                "The constant murmur of commerce fills the air."
            ),
        ),
    ))
    room.add_tag("room")
    room.add_tag("outdoor")
    world.register_room(room.id, {"name": "Market Square"})
    return room.id

Rendering Descriptions

The ExtendedRoomComponent renders descriptions based on a context dict:

from maid_stdlib.components.extended_room import (
    ExtendedRoomComponent,
    Weather,
    TimeOfDay,
    Season,
)


def get_room_description(room_entity: Entity, weather: Weather) -> str:
    """Get the full room description for current conditions."""
    ext = room_entity.try_get(ExtendedRoomComponent)
    if not ext:
        # Fall back to basic description
        desc = room_entity.try_get(DescriptionComponent)
        return desc.long_desc if desc else "You see nothing special."

    context = {
        "weather": weather,
        "time": TimeOfDay.AFTERNOON,
        "season": Season.SUMMER,
    }
    return ext.get_description(context)

A Simple Weather System

If your content pack doesn't already have weather, here's a minimal system:

import random
from maid_engine.core.ecs import System
from maid_engine.core.world import World
from maid_stdlib.components.extended_room import Weather


class SimpleWeatherSystem(System):
    """Changes weather periodically."""

    priority = 10  # Run early so other systems see current weather

    def __init__(self, world: World, change_interval: float = 900.0) -> None:
        super().__init__(world)
        self.change_interval = change_interval
        self._timer: float = change_interval
        self.current_weather: Weather = Weather.CLEAR

    async def startup(self) -> None:
        self.world.set_data("weather", "current", self.current_weather)

    async def update(self, delta: float) -> None:
        self._timer -= delta
        if self._timer <= 0:
            self._timer = self.change_interval
            await self._change_weather()

    async def _change_weather(self) -> None:
        """Transition to a new weather state."""
        candidates = [
            w for w in Weather
            if self.current_weather.can_transition_to(w)
        ]
        if candidates:
            self.current_weather = random.choice(candidates)
            self.world.set_data("weather", "current", self.current_weather)

How It Works

  1. ExtendedRoomComponent stores weather/time/season description variants in ExtendedDescriptions
  2. get_description(context) merges base + weather + time + season + random details
  3. time_mode / season_mode controls whether variants replace or append to the base description
  4. Weather.can_transition_to() ensures realistic weather progression (e.g., CLEAR → STORM is blocked; CLEAR → CLOUDY → RAIN is valid)
  5. RoomDetail.conditions allows details that only appear under certain conditions
  6. world.set_data("weather", "current", ...) stores global state that any system can read

Variations

  • Indoor rooms: Skip weather entirely — don't add weather_effects to indoor rooms
  • Movement penalties: Check Weather.movement_modifier to slow travel in storms
  • Visibility: Use Weather.visibility_modifier to hide entities or shorten look range in fog
  • Regional weather: Store weather per-area instead of globally using area-namespaced data
  • Weather events: Emit a WeatherChangedEvent so other systems can react (NPCs seek shelter, etc.)

See Also