Implements TDD feature for readable text on Things: - Added readable_text field to Thing dataclass - Extended ThingTemplate to parse readable_text from TOML - Created read command that finds objects by name/alias in inventory or on ground - Handles edge cases: no target, not found, not readable
120 lines
3.7 KiB
Python
120 lines
3.7 KiB
Python
"""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 = ""
|
|
|
|
|
|
# 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", ""),
|
|
)
|
|
|
|
|
|
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,
|
|
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,
|
|
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
|