Add Thing/verb system documentation
This commit is contained in:
parent
db8b395257
commit
a350b3e1f6
1 changed files with 134 additions and 0 deletions
134
docs/how/things-and-verbs.rst
Normal file
134
docs/how/things-and-verbs.rst
Normal file
|
|
@ -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
|
||||||
Loading…
Reference in a new issue