mud/src/mudlib/object.py
Jared Miller 9534df8f9c
Add examine command for object inspection
Implements a global examine/ex command that shows detailed descriptions
of objects. Searches inventory first, then ground at player position.
Works with Things, Containers, and Mobs.
2026-02-11 21:47:33 -05:00

89 lines
2.9 KiB
Python

"""Base class for everything in the world."""
from __future__ import annotations
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field
@dataclass
class Object:
"""Base class for everything in the world.
Every object has a location (another Object, or None for top-level things
like zones). The location pointer is the entire containment system —
contents is just the reverse lookup.
"""
name: str
location: Object | None = None
x: int | None = None
y: int | None = None
_contents: list[Object] = field(
default_factory=list, init=False, repr=False, compare=False
)
_verbs: dict[str, Callable[..., Awaitable[None]]] = field(
default_factory=dict, init=False, repr=False, compare=False
)
def __post_init__(self) -> None:
if self.location is not None:
self.location._contents.append(self)
# Auto-register verbs from @verb decorated methods
for attr_name in dir(self):
# Skip private/magic attributes
if attr_name.startswith("_"):
continue
attr = getattr(self, attr_name)
# Check if this is a verb-decorated method
if hasattr(attr, "_verb_name"):
self.register_verb(attr._verb_name, attr)
@property
def contents(self) -> list[Object]:
"""Everything whose location is this object."""
return list(self._contents)
def move_to(
self,
destination: Object | None,
*,
x: int | None = None,
y: int | None = None,
) -> None:
"""Move this object to a new location.
Removes from old location's contents, updates the location pointer,
and adds to new location's contents. Coordinates are set from the
keyword arguments (cleared to None if not provided).
"""
# Remove from old location
if self.location is not None and self in self.location._contents:
self.location._contents.remove(self)
# Update location and coordinates
self.location = destination
self.x = x
self.y = y
# Add to new location
if destination is not None:
destination._contents.append(self)
def can_accept(self, obj: Object) -> bool:
"""Whether this object accepts obj as contents. Default: no."""
return False
def register_verb(self, name: str, handler: Callable[..., Awaitable[None]]) -> None:
"""Register a verb handler on this object."""
self._verbs[name] = handler
def get_verb(self, name: str) -> Callable[..., Awaitable[None]] | None:
"""Get a verb handler by name, or None if not found."""
return self._verbs.get(name)
def has_verb(self, name: str) -> bool:
"""Check if this object has a verb registered."""
return name in self._verbs