"""
Base objects for the Evscaperoom contrib.
The object class itself provide the actions possible to use on that object.
This makes these objects suitable for use with multi-inheritance. For example,
to make an object both possible to smell and eat or drink, find the appropriate
parents in this module and make an object like this:
class Apple(Edible, Smellable):
def at_drink(self, caller):
# ...
def at_smell(self, caller):
# ...
Various object parents could be more complex, so read the class for more info.
Available parents:
- EvscapeRoomObject - parent class for all Evscaperoom entities (also the room itself)
- Feelable
- Listenable
- Smellable
- Rotatable
- Openable
- Readable
- IndexReadable (like a lexicon you have to give a search term in)
- Movable
- Edible
- Drinkable
- Usable
- Insertable (can be inserted into a target)
- Combinable (combines with another object to create a new one)
- Mixable (used for mixing potions into it)
- HasButtons (an object with buttons on it)
- CodeInput (code locks)
- Sittable (can be sat on)
- Liable (can be lied down on)
- Kneeable (can be kneed down on)
- Climbable (can be climbed on)
- Positionable (supports sit/lie/knee/climb at once)
"""
import inspect
import re
from evennia import DefaultObject
from evennia.utils.utils import list_to_string, wrap
from .utils import create_evscaperoom_object, parse_for_perspectives, parse_for_things
[docs]class EvscaperoomObject(DefaultObject):
"""
Default object base for all objects related to the contrib.
"""
# these will be automatically filtered out by self.parse for
# focus-commands using arguments like (`combine [with] object`)
# override this per-class as necessary.
action_prepositions = ("in", "with", "on", "into", "to")
# this mapping allows for prettier descriptions of our current
# position
position_prep_map = {"sit": "sitting", "kneel": "kneeling", "lie": "lying", "climb": "standing"}
[docs] def at_object_creation(self):
"""
Called once when object is first created.
"""
# state flags (setup/reset for each state).
self.db.tagcategory = None
self.db.flags = {}
self.db.desc = "Nothing of interest."
self.db.positions = {}
_tagcategory = None
@property
def tagcategory(self):
if not self._tagcategory:
self._tagcategory = (
self.location.db.tagcategory if self.location else self.db.tagcategory
)
return self._tagcategory
@property
def room(self):
return self.location or self
@property
def roomstate(self):
return self.room.statehandler.current_state
[docs] def next_state(self, statename=None):
"""
Helper to have the object switch the room to next state
Args:
statename (str, optional): If given, move to this
state next. Otherwise use the default next-state
of the current state.
"""
self.room.statehandler.next_state(next_state=statename)
[docs] def set_flag(self, flagname):
"Set flag on object"
self.db.flags[flagname] = True
[docs] def unset_flag(self, flagname):
"Unset flag on object"
if flagname in self.db.flags:
del self.db.flags[flagname]
[docs] def check_flag(self, flagname):
"Check if flag is set on this object"
return self.db.flags.get(flagname, False)
[docs] def set_character_flag(self, char, flagname, value=True):
"Set flag on character"
flags = char.attributes.get(flagname, category=self.tagcategory, default={})
flags[flagname] = value
char.attributes.add(flagname, flags, category=self.tagcategory)
[docs] def unset_character_flag(self, char, flagname):
"Set flag on character"
flags = char.attributes.get(flagname, category=self.tagcategory, default={})
if flagname in flags:
flags.pop(flagname, None)
char.attributes.add(flagname, flags, category=self.tagcategory)
[docs] def check_character_flag(self, char, flagname):
"Check if flag is set on character"
flags = char.attributes.get(flagname, category=self.tagcategory, default={})
return flags.get(flagname, False)
[docs] def msg_room(self, caller, string, skip_caller=False):
"""
Message everyone in the room with a message that is parsed for
~first/third person grammar, as well as for *thing markers.
Args:
caller (Object or None): Sender of the message. If None, there
is no sender.
string (str): Message to parse and send to the room.
skip_caller (bool): Send to everyone except caller.
Notes:
Messages sent by this method will be tagged with a type of
'your_action' and `others_action`. This is an experiment for
allowing users of e.g. the webclient to redirect messages to
differnt windows.
"""
you = caller.key if caller else "they"
first_person, third_person = parse_for_perspectives(string, you=you)
for char in self.room.get_all_characters():
options = char.attributes.get("options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2)
if char == caller:
if not skip_caller:
txt = parse_for_things(first_person, things_style=style)
char.msg((txt, {"type": "your_action"}))
else:
txt = parse_for_things(third_person, things_style=style)
char.msg((txt, {"type": "others_action"}))
[docs] def msg_char(self, caller, string, client_type="your_action"):
"""
Send message only to caller (not to the room at large)
"""
# we must clean away markers
first_person, _ = parse_for_perspectives(string)
options = caller.attributes.get("options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2)
txt = parse_for_things(first_person, things_style=style)
caller.msg((txt, {"type": client_type}))
[docs] def msg_system(self, message, target=None, borders=True):
"""
Send a 'system message' by using the State.msg function.
"""
self.room.state.msg(message, target=target, borders=borders)
[docs] def get_position(self, caller):
"""
Get position of caller on this object (like lying, sitting, kneeling,
standing). See the Positionable child class.
Args:
caller (Object): The one position we seek.
Returns:
obj, pos (Object, str): The object we have a position relative to,
as well as the name of that position (lying, sitting, kneeling,
standing). If these are None, it means we are standing on the
floor.
"""
pos = caller.attributes.get("position", category=self.tagcategory)
if pos:
obj, old_position = pos
return obj, old_position
return None, None
[docs] def set_position(self, caller, new_position):
"""
Set position of caller (like lying, sitting, kneeling, standing)
on this object. See Positionable child class.
Args:
caller (Object): The one positioning themselves on this object.
new_position (str, None): One of "lie", "kneel", "sit" or "stand".
If `None`, remove position (character stands normally on the
floor).
"""
if new_position is None:
# reset position
caller.attributes.remove("position", category=self.tagcategory)
if caller in self.db.positions:
del self.db.positions[caller]
else:
# set a new position on this object
position = (self, new_position)
caller.attributes.add("position", position, category=self.tagcategory)
self.db.positions[caller] = new_position
[docs] def at_focus(self, caller):
"""
Called when somone is focusing on this object.
Args:
caller (Character): The one doing the focusing.
"""
self.msg_char(caller, caller.at_look(self), client_type="look")
[docs] def at_unfocus(self, caller):
"""
Called when focus leaves this object. Note that more than one caller
may be focusing on the object at the same time, so we should not change
the state of the object itself here!
Args:
caller (Character): The one doing the unfocusing.
"""
pass
[docs] def at_speech(self, speaker, action):
"""
We don't use the default at_say hook since we handle the send logic in
the command. This is only meant to trigger eventual game-events when
speaking to an object or the room.
Args:
speaker (Character): The one speaking.
action (str): One of 'say', 'whisper' or 'shout'
"""
pass
[docs] def parse(self, args):
"""
Simple parser of focus arguments starting with a preposition,
like 'combine with <object>' <- we want to strip out the preposition
here.
"""
args = re.sub(
r"|".join(r"^{}\s".format(prep) for prep in self.action_prepositions), "", args
)
return args
[docs] def get_cmd_signatures(self):
"""
This allows the object to return more detailed call signs
for each of their at_focus_* methods. This is useful for
things like detailed arguments (only 'move' but 'move left/right')
Returns:
callsigns (list, None): List of strings to inject into the
available action list produced by `self.get_help`. If `None`,
automatically find actions based on the method names.
custom_helpstr (str): This should be the help text for
the command with a marker `{callsigns}` for where to
inject the list of callsigns.
"""
command_signatures = []
helpstr = ""
methods = inspect.getmembers(self, predicate=inspect.ismethod)
for name, method in methods:
if name.startswith("at_focus_"):
command_signatures.append(name[9:])
command_signatures = sorted(command_signatures)
if len(command_signatures) == 1:
helpstr = f"It looks like {self.key} may be " "suitable to {callsigns}."
else:
helpstr = (
f"At first glance, it looks like {self.key} might be " "suitable to {callsigns}."
)
return command_signatures, helpstr
[docs] def get_short_desc(self, full_desc):
"""
Extract the first sentence from the desc and use as the short desc.
"""
mat = re.match(r"(^.*?[.?!])", full_desc.strip(), re.M + re.U + re.I + re.S)
if mat:
return mat.group(0).strip()
return full_desc
[docs] def get_help(self, caller):
"""
Get help about this object. By default we return a
listing of all actions you can do on this object.
"""
# custom-created signatures. We don't sort these
command_signatures, helpstr = self.get_cmd_signatures()
callsigns = list_to_string(["*" + sig for sig in command_signatures], endsep="or")
# parse for *thing markers (use these as items)
options = caller.attributes.get("options", category=self.room.tagcategory, default={})
style = options.get("things_style", 2)
helpstr = helpstr.format(callsigns=callsigns)
helpstr = parse_for_things(helpstr, style, clr="|w")
return wrap(helpstr, width=80)
# Evennia hooks
[docs] def return_appearance(self, looker, **kwargs):
"""Could be modified per state. We generally don't worry about the
contents of the object by default.
"""
# accept a custom desc
desc = kwargs.get("desc", self.db.desc)
if kwargs.get("unfocused", False):
# use the shorter description
focused = ""
desc = self.get_short_desc(desc)
helptxt = ""
else:
focused = " |g(examining |G- use '|gex|G' again to look away. See also '|ghelp|G')|n"
helptxt = kwargs.get("helptxt", f"\n\n({self.get_help(looker)})")
obj, pos = self.get_position(looker)
pos = (
f" |w({self.position_prep_map[pos]} on " f"{obj.get_display_name(looker)})"
if obj
else ""
)
return f" ~~ |y{self.get_display_name(looker)}|n{focused}{pos}|n ~~\n\n{desc}{helptxt}"
[docs]class Feelable(EvscaperoomObject):
"""
Any object that you can feel the surface of.
"""
[docs] def at_focus_feel(self, caller, **kwargs):
self.msg_char(caller, f"You feel *{self.key}.")
[docs]class Listenable(EvscaperoomObject):
"""
Any object one can listen to.
"""
[docs] def at_focus_listen(self, caller, **kwargs):
self.msg_char(caller, f"You listen to *{self.key}")
[docs]class Smellable(EvscaperoomObject):
"""
Any object you can smell.
"""
[docs] def at_focus_smell(self, caller, **kwargs):
self.msg_char(caller, f"You smell *{self.key}.")
[docs]class Rotatable(EvscaperoomObject):
"""
Any object that you can lift up and look at from different angles
"""
rotate_flag = "rotatable"
start_rotatable = True
[docs] def at_object_creation(self):
super().at_object_creation()
if self.start_rotatable:
self.set_flag("rotatable")
[docs] def at_focus_rotate(self, caller, **kwargs):
if self.check_flag("rotatable"):
self.at_rotate(caller)
else:
self.at_cannot_rotate(caller)
at_focus_turn = at_focus_rotate
[docs] def at_rotate(self, caller):
self.msg_char(caller, f"You turn *{self.key} around.")
[docs] def at_cannot_rotate(self, caller):
self.msg_char(caller, f"You cannot rotate this.")
[docs]class Openable(EvscaperoomObject):
"""
Any object that you can open/close. It's lockable with
a flag.
"""
# this flag must be set for item to open. None for unlocked.
unlock_flag = "unlocked"
open_flag = "open"
# start this item in the opened/unlocked state
start_open = False
[docs] def at_object_creation(self):
super().at_object_creation()
if self.start_open:
self.set_flag(self.unlock_flag)
self.set_flag(self.open_flag)
[docs] def at_focus_open(self, caller, **kwargs):
if self.check_flag(self.open_flag):
self.at_already_open(caller)
elif self.unlock_flag is None or self.check_flag(self.unlock_flag):
self.set_flag(self.open_flag)
self.at_open(caller)
else:
self.at_locked(caller)
[docs] def at_focus_close(self, caller, **kwargs):
if not self.check_flag(self.open_flag):
self.at_already_closed(caller)
else:
self.unset_flag(self.open_flag)
self.at_close(caller)
[docs] def at_open(self, caller):
self.msg_char(caller, f"You open *{self.key}")
[docs] def at_already_open(self, caller):
self.msg_char(caller, f"{self.key.capitalize()} is already open.")
[docs] def at_locked(self, caller):
self.msg_char(caller, f"{self.key.capitalize()} won't open.")
[docs] def at_close(self, caller):
self.msg_char(caller, f"You close *{self.key}.")
[docs] def at_already_closed(self, caller):
self.msg_char(caller, f"{self.key.capitalize()} is already closed.")
[docs]class Readable(EvscaperoomObject):
"""
Any object that you can read from. This is controlled
from a flag.
"""
# this must be set to be able to read. None to
# always be able to read.
read_flag = "readable"
start_readable = True
[docs] def at_object_creation(self):
super().at_object_creation()
if self.start_readable:
self.set_flag(self.read_flag)
[docs] def at_focus_read(self, caller, **kwargs):
if self.read_flag is None or self.check_flag(self.read_flag):
self.at_read(caller)
else:
self.at_cannot_read(caller)
[docs] def at_read(self, caller, *args, **kwargs):
self.msg_char(caller, f"You read from *{self.key}.")
[docs] def at_cannot_read(self, caller, *args, **kwargs):
self.msg_char(caller, "You cannot understand a thing!")
[docs]class IndexReadable(Readable):
"""
Any object for which you need to specify a key/index to get a given result
back. For example a lexicon or book where you enter a topic or a page
number to see what's to be read on that page.
"""
# keys should be lower-key
index = {"page1": "This is page1", "page2": "This is page2", "page two": "page2"} # alias
[docs] def at_focus_read(self, caller, **kwargs):
topic = kwargs.get("args").strip().lower()
entry = self.index.get(topic, None)
if entry is None or not self.check_flag(self.read_flag):
self.at_cannot_read(caller, topic)
else:
if entry in self.index:
# an alias-reroute
entry = self.index[entry]
self.at_read(caller, topic, entry)
[docs] def get_cmd_signatures(self):
txt = (
f"You don't have the time to read this from beginning to end. "
"Use *read <topic> to look up something in particular."
)
return [], txt
[docs] def at_cannot_read(self, caller, topic, *args, **kwargs):
self.msg_char(caller, f"Cannot find an entry on '{topic}'.")
[docs] def at_read(self, caller, topic, entry, *args, **kwargs):
self.msg_char(caller, f"You read about '{topic}':\n{entry.strip()}")
[docs]class Movable(EvscaperoomObject):
"""
Any object that can be moved from one place to another
or in one direction or another.
Once moved to a given position, the object's state will
change.
"""
# these are the possible locations (or directions) to move to
# name: callable
move_positions = {"left": "at_left", "right": "at_right"}
start_position = "left"
[docs] def at_object_creation(self):
super().at_object_creation()
self.db.position = self.start_position
[docs] def get_cmd_signatures(self):
txt = "Looks like you can {callsigns}."
return ["move", "push", "shove left/right"], txt
[docs] def at_focus_move(self, caller, **kwargs):
pos = self.parse(kwargs["args"])
callfunc_name = self.move_positions.get(pos)
if callfunc_name:
if self.db.position == pos:
self.at_already_moved(caller, pos)
else:
self.db.position = pos
getattr(self, callfunc_name)(caller)
else:
self.at_cannot_move(caller)
at_focus_shove = at_focus_move
at_focus_push = at_focus_move
[docs] def at_cannot_move(self, caller):
self.msg_char(caller, "That does not work.")
[docs] def at_already_moved(self, caller, position):
self.msg_char(caller, f"You already moved *{self.key} to the {position}.")
[docs] def at_left(self, caller):
self.msg_char(caller, f"You move *{self.key} left")
[docs] def at_right(self, caller):
self.msg_char(caller, f"You move *{self.key} right")
[docs]class BaseConsumable(EvscaperoomObject):
"""
Any object that is consumable in some way. This acts as an
abstract parent.
This sets a flag that
is unique for each person consuming, allowing it to e.g. only
be consumed once (don't support multi-uses here, that's left for
a custom object if needed).
"""
consume_flag = "consume"
# may only consume once
one_consume_only = True
[docs] def handle_consume(self, caller, action, **kwargs):
"""
Wrap this by the at_focus method
"""
if self.one_consume_only and self.has_consumed(caller):
self.at_already_consumed(caller, action)
else:
self.has_consumed(caller, True)
self.at_consume(caller, action)
[docs] def has_consumed(self, caller, setflag=False):
"Check if caller already consumed at least once"
flag = f"{self.consume_flag}#{caller.id}"
if setflag:
self.set_flag(flag)
else:
return self.check_flag(flag)
[docs] def at_consume(self, caller, action):
if hasattr(self, f"at_{action}"):
getattr(self, f"at_{action}")(caller)
else:
self.msg_char(caller, f"You {action} *{self.key}.")
[docs] def at_already_consumed(self, caller, action):
self.msg_char(caller, f"You can't {action} any more.")
[docs]class Edible(BaseConsumable):
"""
Any object specifically possible to eat.
"""
consume_flag = "eat"
[docs] def at_focus_eat(self, caller, **kwargs):
super().handle_consume(caller, "eat", **kwargs)
[docs]class Drinkable(BaseConsumable):
"""
Any object specifically possible to drink.
"""
consume_flag = "drink"
[docs] def at_focus_drink(self, caller, **kwargs):
super().handle_consume(caller, "drink", **kwargs)
[docs] def at_focus_sip(self, caller, **kwargs):
super().handle_consume(caller, "sip", **kwargs)
[docs] def at_consume(self, caller, action):
self.msg_char(caller, f"You {action} from *{self.key}.")
[docs] def at_already_consumed(self, caller, action):
self.msg_char(caller, f"You can't drink any more.")
[docs]class BaseApplicable(EvscaperoomObject):
"""
Any object that can be applied/inserted/used on another object in some way.
This acts an an abstract base class.
"""
# the target object this is to be used with must
# have this flag. It'll likely be unique to this
# object combination.
target_flag = "applicable"
[docs] def handle_apply(self, caller, action, **kwargs):
"""
Wrap this with the at_focus methods in the child classes
"""
args = self.parse(kwargs["args"])
if not args:
self.msg_char(caller, "You need to specify a target.")
return
obj = caller.search(args)
if not obj:
return
try:
can_apply = obj.check_flag(self.target_flag)
except AttributeError:
can_apply = False
if can_apply:
self.at_apply(caller, action, obj)
else:
self.at_cannot_apply(caller, action, obj)
[docs] def at_apply(self, caller, action, obj):
self.msg_char(caller, f"You {action} *{self.key} to {obj.key}.")
[docs] def at_cannot_apply(self, caller, action, obj):
self.msg_char(caller, f"You cannot {action} *{self.key} to {obj.key}.")
[docs]class Usable(BaseApplicable):
"""
Any object that can be used with another object.
"""
target_flag = "usable"
[docs] def at_focus_use(self, caller, **kwargs):
super().handle_apply(caller, "use", **kwargs)
[docs] def at_apply(self, caller, action, obj):
self.msg_char(caller, f"You {action} *{self.key} with {obj.key}")
[docs] def at_cannot_apply(self, caller, action, obj):
self.msg_char(caller, f"You cannot {action} *{self.key} with {obj.key}.")
[docs]class Insertable(BaseApplicable):
"""
Any object that can be inserted into another object.
This would cover a key, for example.
"""
# this would likely be a custom name
target_flag = "insertable"
[docs] def at_focus_insert(self, caller, **kwargs):
super().handle_apply(caller, "insert", **kwargs)
[docs] def at_apply(self, caller, action, obj):
self.msg_char(caller, f"You {action} *{self.key} in {obj.key}.")
[docs] def get_cmd_signatures(self):
txt = "You can use this object to {callsigns}"
return ["insert in <object>"], txt
[docs] def at_cannot_apply(self, caller, action, obj):
self.msg_char(caller, f"You cannot {action} *{self.key} in {obj.key}.")
[docs]class Combinable(BaseApplicable):
"""
Any object that combines with another object to create
a new one.
"""
# the other object must have this flag to be able to be combined
# (this is likely unique for a given combination)
target_flag = "combinable"
# create-dict to pass into the create_object for the
# new "combined" object.
new_create_dict = {
"typeclass": "evscaperoom.objects.Combinable",
"key": "sword",
"aliases": ["combined"],
}
# if set, destroy the two components used to make the new one
destroy_components = True
[docs] def at_focus_combine(self, caller, **kwargs):
super().at_focus_apply(caller, **kwargs)
[docs] def get_cmd_signatures(self):
txt = "It looks like this should work: {callsigns}"
return ["combine <object>"], txt
[docs] def at_cannot_apply(self, caller, action, obj):
self.msg_char(caller, f"You cannot {action} *{self.key} with {obj.key}.")
[docs] def at_apply(self, caller, action, other_obj):
create_dict = self.new_create_dict
if "location" not in create_dict:
create_dict["location"] = self.location
new_obj = create_evscaperoom_object(**create_dict)
if new_obj and self.destroy_components:
self.msg_char(
caller, f"You combine *{self.key} with {other_obj.key} to make {new_obj.key}!"
)
other_obj.delete()
self.delete()
[docs]class Mixable(EvscaperoomObject):
"""
Any object into which you can mix ingredients (such as when
mixing a potion). This offers no actions on its own, instead
the ingredients should be 'used' with this object in order
mix, calling at_mix when they do.
"""
# ingredients can check for this before they allow to mix at all
mixer_flag = "mixer"
# ingredients must have these flags and this order
ingredient_recipe = ["ingredient1", "ingredient2", "ingredient3"]
[docs] def at_object_creation(self):
super().at_object_creation()
self.set_flag(self.mixer_flag)
# this holds the ingredients as they are added
self.db.ingredients = []
[docs] def check_mixture(self):
"check so mixture is correct, returning True/False."
ingredients = list(self.db.ingredients)
for iflag, flag in enumerate(self.ingredient_recipe):
try:
if not ingredients[iflag].check_flag(flag):
return False
except (IndexError, AttributeError):
return False
# we only get here if all ingredients have the right flags in the right
# order
return True
[docs] def handle_mix(self, caller, ingredient, **kwargs):
"""
Add ingredient object to mixture.
Called by the mixing ingredient. We assume the ingredient has already
checked to make sure they allow themselves to be mixed into an object
with this mixer_flag.
"""
self.db.ingredients.append(ingredient)
# normal mix
self.at_mix(caller, ingredient, **kwargs)
if len(self.db.ingredients) >= len(self.ingredient_recipe):
# we have enough, check if it matches recipe
if self.check_mixture():
self.at_mix_success(caller, ingredient, **kwargs)
else:
self.room.log(
f"{self.name} mix failure: Tried {' + '.join([ing.key for ing in self.db.ingredients if ing])}"
)
self.db.ingredients = []
self.at_mix_failure(caller, ingredient, **kwargs)
[docs] def at_mix(self, caller, ingredient, **kwargs):
self.msg_room(caller, f"~You ~mix {ingredient.key} into *{self.key}.")
[docs] def at_mix_failure(self, caller, ingredient, **kwargs):
self.msg_room(caller, f"This mix doesn't work. ~You ~clean and start over.")
[docs] def at_mix_success(self, caller, ingredient, **kwargs):
self.msg_room(caller, f"~You successfully ~complete the mix!")
[docs]class BasePositionable(EvscaperoomObject):
"""
Any object a character can be positioned on. This is meant as an
abstract parent.
This is a little special since a char can only have one position at a
time and must therefore be aware of the other 'positional' actions
any object may support (otherwise you may end up sitting/standing/etc on
more than one object at once!)
We set a Attribute (obj, position) on the caller to indicate that
they have a position on an object. This is necessary so as to not have
the caller sit on more than one sittable object at a time, for example. The
'positions' Attribute on this object holds a mapping of who is sitting
lying etc on this object. We don't add a limit to how many chars could
have a position on an object - it's not realistic, but this goes with the
philosophy that one character should not be able to block others if they go
inactive etc.
This state is also tied to the general 'stand' command, which should return
the player to the normal standing state regardless of if they focus on this
object or not.
"""
[docs] def at_object_creation(self):
super().at_object_creation()
# mapping {object: position}.
self.db.positions = {}
[docs] def handle_position(self, caller, new_pos, **kwargs):
"""
Wrap this with the at_focus_ method of the child class.
"""
old_obj, old_pos = self.get_position(caller)
if old_obj:
if old_obj is self:
if old_pos == new_pos:
self.at_again_position(caller, new_pos)
else:
self.set_position(caller, new_pos)
self.at_position(caller, new_pos)
else:
self.at_cannot_position(caller, new_pos, old_obj, old_pos)
else:
self.set_position(caller, new_pos)
self.at_position(caller, new_pos)
[docs] def at_cannot_position(self, caller, position, old_obj, old_pos):
self.msg_char(
caller,
f"You can't; you are currently {self.position_prep_map[old_pos]} on *{old_obj.key} "
"(better |wstand|n first).",
)
[docs] def at_again_position(self, caller, position):
self.msg_char(
caller, f"But you are already {self.position_prep_map[position]} on *{self.key}?"
)
[docs] def at_position(self, caller, position):
self.msg_room(caller, f"~You ~{position} on *{self.key}.")
[docs]class Sittable(BasePositionable):
"""
Any object you can sit on.
"""
[docs] def at_focus_sit(self, caller, **kwargs):
super().handle_position(caller, "sit", **kwargs)
[docs]class Liable(BasePositionable):
"""
Any object you can lie down on.
"""
[docs] def at_focus_lie(self, caller, **kwargs):
super().handle_position(caller, "lie", **kwargs)
[docs]class Kneelable(BasePositionable):
"""
Any object you can kneel on.
"""
[docs] def at_focus_kneel(self, caller, **kwargs):
super().handle_position(caller, "kneel", **kwargs)
[docs]class Climbable(BasePositionable):
"""
Any object you can climb up to stand on. We name this
'climb' so as to not collide with the general 'stand'
command, which resets your position.
"""
[docs] def at_focus_climb(self, caller, **kwargs):
super().handle_position(caller, "climb", **kwargs)
[docs]class Positionable(Sittable, Liable, Kneelable, Climbable):
"""
An object on which you can position yourself in one of the
supported ways (sit, lie, kneel or climb)
"""
[docs] def get_cmd_signatures(self):
txt = "It looks like you can {callsigns} on it."
return ["sit", "lie", "kneel", "climb"], txt