Verbs let any Object have interactive handlers players can trigger. Uses @verb decorator to mark methods that auto-register on instantiation. - Object._verbs dict stores verb name to async handler mapping - Object.register_verb(), get_verb(), has_verb() API - @verb decorator marks methods with _verb_name attribute - __post_init__ scans for decorated methods and registers them - find_object() helper searches inventory then ground by name/alias - Bound methods stored in _verbs (self already bound) - Works on Object and all subclasses (Thing, Entity, etc) - 18 tests covering registration, lookup, decoration, inheritance
62 lines
1.8 KiB
Python
62 lines
1.8 KiB
Python
"""Verb system for interactive objects."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Callable
|
|
from typing import TYPE_CHECKING
|
|
|
|
if TYPE_CHECKING:
|
|
from mudlib.object import Object
|
|
from mudlib.player import Player
|
|
|
|
|
|
def verb(name: str) -> Callable:
|
|
"""Decorator to mark a method as a verb handler.
|
|
|
|
Usage:
|
|
class Fountain(Thing):
|
|
@verb("drink")
|
|
async def drink(self, player, args):
|
|
await player.send("You drink from the fountain.\\r\\n")
|
|
"""
|
|
|
|
def decorator(func: Callable) -> Callable:
|
|
func._verb_name = name # type: ignore
|
|
return func
|
|
|
|
return decorator
|
|
|
|
|
|
def find_object(name: str, player: Player) -> Object | None:
|
|
"""Find an object by name or alias.
|
|
|
|
Searches inventory first, then ground at player position.
|
|
Works on all Objects that have a name and optional aliases attribute.
|
|
"""
|
|
name_lower = name.lower()
|
|
|
|
# Search inventory first
|
|
for obj in player.contents:
|
|
if obj.name.lower() == name_lower:
|
|
return obj
|
|
# Check aliases if the object has them
|
|
aliases = getattr(obj, "aliases", None)
|
|
if aliases and name_lower in (a.lower() for a in aliases):
|
|
return obj
|
|
|
|
# Search ground at player position
|
|
if player.location is not None:
|
|
from mudlib.zone import Zone
|
|
|
|
if isinstance(player.location, Zone):
|
|
for obj in player.location.contents_at(player.x, player.y):
|
|
if obj is player:
|
|
continue
|
|
if obj.name.lower() == name_lower:
|
|
return obj
|
|
# Check aliases if the object has them
|
|
aliases = getattr(obj, "aliases", None)
|
|
if aliases and name_lower in (a.lower() for a in aliases):
|
|
return obj
|
|
|
|
return None
|