diff --git a/docs/how/things-and-verbs.rst b/docs/how/things-and-verbs.rst new file mode 100644 index 0000000..b9a095c --- /dev/null +++ b/docs/how/things-and-verbs.rst @@ -0,0 +1,134 @@ +===================================== +things and verbs: how objects interact +===================================== + +the thing/verb system lets world objects respond to player actions. instead of hardcoding behavior in commands, objects carry their own verb handlers. two ways to register verbs: decorator-based (python classes) or toml-based (content files). both converge to the same dispatch mechanism. + +object base class +================= + +``Object`` (object.py) is the foundation for all world entities. it has: + +- ``name`` - what the object is called +- ``location`` - another Object or None (the entire containment system) +- ``x``, ``y`` - coordinates in the world +- ``_contents`` - list of objects inside this one +- ``_verbs`` - dict mapping verb names to handlers + +``__post_init__`` scans the instance for methods decorated with ``@verb`` and auto-registers them in ``_verbs``. + +thing and container +=================== + +``Thing`` (thing.py) extends Object for portable items:: + + name: str + description: str + portable: bool = True + aliases: list[str] = [] + readable_text: str | None = None + tags: set[str] = field(default_factory=set) + +``Container`` (container.py) extends Thing with containment rules:: + + capacity: int + closed: bool = False + locked: bool = False + +``can_accept()`` checks object type, open state, and capacity before allowing insertions. + +verb registration: decorator path +================================== + +the ``@verb`` decorator (verbs.py) marks methods as verb handlers. it sets ``_verb_name`` on the function. ``Object.__post_init__`` discovers these and calls ``register_verb()``:: + + class Fountain(Thing): + @verb("drink") + async def drink(self, player, args): + await player.send("You drink from the fountain.\r\n") + +when you instantiate the fountain, the drink handler automatically registers in ``_verbs["drink"]``. + +verb registration: toml path +============================= + +``ThingTemplate`` (things.py) defines objects in toml:: + + name = "chest" + description = "a sturdy wooden chest with iron bindings" + portable = false + capacity = 5 + closed = true + locked = false + aliases = ["box"] + + [verbs] + unlock = "verb_handlers:unlock_handler" + +``spawn_thing()`` (things.py) creates a Thing or Container from the template. if ``capacity`` is set, you get a Container. if ``verbs`` dict is present, it resolves "module:function" strings via importlib and binds them with functools.partial. + +``verb_handlers.py`` contains standalone functions referenced by toml. example:: + + async def unlock_handler(obj, player, args): + if not isinstance(obj, Container): + await player.send("That's not lockable.\r\n") + return + if not obj.locked: + await player.send("It's already unlocked.\r\n") + return + # check for key in inventory... + obj.locked = False + await player.send(f"You unlock the {obj.name}.\r\n") + +two paths, one dispatch +======================= + +decorator-based and toml-based verbs both populate ``Object._verbs``. the command dispatcher doesn't care which path was used. + +finding objects +=============== + +``find_object()`` (verbs.py) searches for targets: + +1. player inventory first +2. ground at player position second +3. matches by name or aliases (case-insensitive) + +returns the object or None. + +command dispatch fallback +========================= + +the command dispatcher (commands/__init__.py) tries registered commands first. if no match, it tries verb dispatch: + +1. parse input as "verb target" (e.g., "drink fountain") +2. call ``find_object()`` for target +3. check ``obj.has_verb(verb)`` +4. call ``obj.call_verb(verb, player, args)`` + +this lets content authors add new interactions without modifying command code. + +use command +=========== + +``use`` (commands/use.py) provides explicit verb access:: + + use fountain # calls fountain's "use" verb + use key on chest # calls chest's "use" verb with args="key on chest" + +parses input, finds object, checks for "use" verb, calls handler. + +code +==== + +relevant files:: + + src/mudlib/object.py # Object base class, verb registration + src/mudlib/thing.py # Thing class + src/mudlib/container.py # Container class + src/mudlib/verbs.py # @verb decorator, find_object() + src/mudlib/things.py # ThingTemplate, spawn_thing() + src/mudlib/verb_handlers.py # standalone verb functions for toml + src/mudlib/commands/__init__.py # command dispatcher with verb fallback + src/mudlib/commands/use.py # explicit verb command + content/things/ # toml thing definitions