"""Thing template loading, registry, and spawning.""" import functools import importlib import logging import tomllib from collections.abc import Awaitable, Callable from dataclasses import dataclass, field from pathlib import Path from mudlib.container import Container from mudlib.object import Object from mudlib.thing import Thing logger = logging.getLogger(__name__) @dataclass class ThingTemplate: """Definition loaded from TOML — used to spawn Thing instances.""" name: str description: str portable: bool = True aliases: list[str] = field(default_factory=list) # Container fields (presence of capacity indicates a container template) capacity: int | None = None closed: bool = False locked: bool = False # Verb handlers (verb_name -> module:function reference) verbs: dict[str, str] = field(default_factory=dict) readable_text: str = "" tags: list[str] = field(default_factory=list) # Module-level registry thing_templates: dict[str, ThingTemplate] = {} def _resolve_handler(ref: str) -> Callable[..., Awaitable[None]] | None: """Resolve a 'module:function' reference to a callable.""" module_path, _, func_name = ref.rpartition(":") try: module = importlib.import_module(module_path) return getattr(module, func_name) except (ImportError, AttributeError) as e: logger.warning("Failed to resolve verb handler '%s': %s", ref, e) return None def load_thing_template(path: Path) -> ThingTemplate: """Parse a thing TOML file into a ThingTemplate.""" with open(path, "rb") as f: data = tomllib.load(f) return ThingTemplate( name=data["name"], description=data["description"], portable=data.get("portable", True), aliases=data.get("aliases", []), capacity=data.get("capacity"), closed=data.get("closed", False), locked=data.get("locked", False), verbs=data.get("verbs", {}), readable_text=data.get("readable_text", ""), tags=data.get("tags", []), ) def load_thing_templates(directory: Path) -> dict[str, ThingTemplate]: """Load all .toml files in a directory into a dict keyed by name.""" templates: dict[str, ThingTemplate] = {} for path in sorted(directory.glob("*.toml")): template = load_thing_template(path) templates[template.name] = template return templates def spawn_thing( template: ThingTemplate, location: Object | None, *, x: int | None = None, y: int | None = None, ) -> Thing: """Create a Thing instance from a template at the given location.""" # If template has capacity, spawn a Container instead of a Thing if template.capacity is not None: obj = Container( name=template.name, description=template.description, portable=template.portable, aliases=list(template.aliases), readable_text=template.readable_text, tags=list(template.tags), capacity=template.capacity, closed=template.closed, locked=template.locked, location=location, x=x, y=y, ) else: obj = Thing( name=template.name, description=template.description, portable=template.portable, aliases=list(template.aliases), readable_text=template.readable_text, tags=list(template.tags), location=location, x=x, y=y, ) # Register verbs from the template for verb_name, handler_ref in template.verbs.items(): handler = _resolve_handler(handler_ref) if handler is None: continue # Bind the object as first argument using partial bound_handler = functools.partial(handler, obj) obj.register_verb(verb_name, bound_handler) return obj