mud/src/mudlib/zones.py

158 lines
4.1 KiB
Python

"""Zone registry and loading."""
from __future__ import annotations
import logging
import tomllib
from pathlib import Path
from mudlib.portal import Portal
from mudlib.zone import SpawnRule, Zone
log = logging.getLogger(__name__)
# Module-level zone registry
zone_registry: dict[str, Zone] = {}
def register_zone(name: str, zone: Zone) -> None:
"""Register a zone by name.
Args:
name: Unique name for the zone
zone: Zone instance to register
"""
zone_registry[name] = zone
def get_zone(name: str) -> Zone | None:
"""Look up a zone by name.
Args:
name: Zone name to look up
Returns:
Zone instance if found, None otherwise
"""
return zone_registry.get(name)
def load_zone(path: Path) -> Zone:
"""Load a zone from a TOML file.
Args:
path: Path to TOML file
Returns:
Zone instance loaded from file
"""
with open(path, "rb") as f:
data = tomllib.load(f)
# Extract basic properties
name = data["name"]
description = data.get("description", "")
width = data["width"]
height = data["height"]
toroidal = data.get("toroidal", True)
spawn_x = data.get("spawn_x", 0)
spawn_y = data.get("spawn_y", 0)
# Parse terrain rows into 2D list
terrain_rows = data.get("terrain", {}).get("rows", [])
terrain = []
for row in terrain_rows:
terrain.append(list(row))
# Parse impassable tiles
impassable_list = data.get("terrain", {}).get("impassable", {}).get("tiles", [])
impassable = set(impassable_list) if impassable_list else {"^", "~"}
# Parse ambient messages
ambient_data = data.get("ambient", {})
ambient_messages = ambient_data.get("messages", [])
ambient_interval = ambient_data.get("interval", 120)
# Parse spawn rules
spawns_data = data.get("spawns", [])
spawn_rules = [
SpawnRule(
mob=spawn["mob"],
max_count=spawn.get("max_count", 1),
respawn_seconds=spawn.get("respawn_seconds", 300),
)
for spawn in spawns_data
]
zone = Zone(
name=name,
description=description,
width=width,
height=height,
toroidal=toroidal,
terrain=terrain,
impassable=impassable,
spawn_x=spawn_x,
spawn_y=spawn_y,
ambient_messages=ambient_messages,
ambient_interval=ambient_interval,
spawn_rules=spawn_rules,
)
# Load portals
portals_data = data.get("portals", [])
for portal_dict in portals_data:
# Parse target string "zone_name:x,y"
target = portal_dict["target"]
try:
target_zone, coords = target.split(":")
target_x, target_y = map(int, coords.split(","))
except ValueError:
log.warning(
"skipping portal '%s' at (%d, %d): malformed target '%s'",
portal_dict["label"],
portal_dict["x"],
portal_dict["y"],
target,
)
continue
# Create portal (automatically added to zone._contents via Object.__post_init__)
Portal(
name=portal_dict["label"],
aliases=portal_dict.get("aliases", []),
target_zone=target_zone,
target_x=target_x,
target_y=target_y,
location=zone,
x=portal_dict["x"],
y=portal_dict["y"],
)
return zone
def load_zones(directory: Path) -> dict[str, Zone]:
"""Load all zones from a directory.
Args:
directory: Path to directory containing zone TOML files
Returns:
Dict mapping zone names to Zone instances
"""
zones = {}
if not directory.exists():
log.warning("zones directory does not exist: %s", directory)
return zones
for toml_file in directory.glob("*.toml"):
try:
zone = load_zone(toml_file)
zones[zone.name] = zone
log.debug("loaded zone '%s' from %s", zone.name, toml_file)
except Exception as e:
log.error("failed to load zone from %s: %s", toml_file, e, exc_info=True)
return zones