"""
Room class and mechanics for the Evscaperoom.
This is a special room class that not only depicts the evscaperoom itself, it
also acts as a central store for the room state, score etc. When deleting this,
that particular escaperoom challenge should be gone.
"""
from evennia import DefaultCharacter, DefaultObject, DefaultRoom, logger, utils
from evennia.locks.lockhandler import check_lockstring
from evennia.utils.ansi import strip_ansi
from evennia.utils.utils import lazy_property, list_to_string
from .commands import CmdSetEvScapeRoom
from .objects import EvscaperoomObject
from .state import StateHandler
[docs]class EvscapeRoom(EvscaperoomObject, DefaultRoom):
"""
The room to escape from.
"""
[docs] def at_object_creation(self):
"""
Called once, when the room is first created.
"""
super().at_object_creation()
# starting state
self.db.state = None # name
self.db.prev_state = None
# this is used for tagging of all objects belonging to this
# particular room instance, so they can be cleaned up later
# this is accessed through the .tagcategory getter.
self.db.tagcategory = "evscaperoom_{}".format(self.key)
# room progress statistics
self.db.stats = {
"progress": 0, # in percent
"score": {}, # reason: score
"max_score": 100,
"hints_used": 0, # total across all states
"hints_total": 41,
"total_achievements": 14,
}
self.cmdset.add(CmdSetEvScapeRoom, persistent=True)
self.log("Room created and log started.")
[docs] @lazy_property
def statehandler(self):
return StateHandler(self)
@property
def state(self):
return self.statehandler.current_state
[docs] def log(self, message, caller=None):
"""
Log to a file specificially for this room.
"""
caller = f"[caller.key]: " if caller else ""
logger.log_file(
strip_ansi(f"{caller}{message.strip()}"), filename=self.tagcategory + ".log"
)
[docs] def score(self, new_score, reason):
"""
We don't score individually but for everyone in room together.
You can only be scored for a given reason once."""
if reason not in self.db.stats["score"]:
self.log(f"score: {reason} ({new_score}pts)")
self.db.stats["score"][reason] = new_score
[docs] def progress(self, new_progress):
"Progress is what we set it to be (0-100%)"
self.log(f"progress: {new_progress}%")
self.db.stats["progress"] = new_progress
[docs] def achievement(self, caller, achievement, subtext=""):
"""
Give the caller a personal achievment. You will only
ever get one of the same type
Args:
caller (Object): The receiver of the achievement.
achievement (str): The title/name of the achievement.
subtext (str, optional): Eventual subtext/explanation
of the achievement.
"""
achievements = caller.attributes.get("achievements", category=self.tagcategory)
if not achievements:
achievements = {}
if achievement not in achievements:
self.log(f"achievement: {caller} earned '{achievement}' - {subtext}")
achievements[achievement] = subtext
caller.attributes.add("achievements", achievements, category=self.tagcategory)
[docs] def get_all_characters(self):
"""
Get the player characters in the room.
Returns:
chars (Queryset): The characters.
"""
return DefaultCharacter.objects.filter_family(db_location=self)
[docs] def set_flag(self, flagname):
self.db.flags[flagname] = True
[docs] def unset_flag(self, flagname):
if flagname in self.db.flags:
del self.db.flags[flagname]
[docs] def check_flag(self, flagname):
return self.db.flags.get(flagname, False)
[docs] def check_perm(self, caller, permission):
return check_lockstring(caller, f"dummy:perm({permission})")
[docs] def tag_character(self, character, tag, category=None):
"""
Tag a given character in this room.
Args:
character (Character): Player character to tag.
tag (str): Tag to set.
category (str, optional): Tag-category. If unset, use room's
tagcategory.
"""
category = category if category else self.db.tagcategory
character.tags.add(tag, category=category)
[docs] def tag_all_characters(self, tag, category=None):
"""
Set a given tag on all players in the room.
Args:
room (EvscapeRoom): The room to escape from.
tag (str): The tag to set.
category (str, optional): If unset, will use the room's tagcategory.
"""
category = category if category else self.tagcategory
for char in self.get_all_characters():
char.tags.add(tag, category=category)
[docs] def character_cleanup(self, char):
"""
Clean all custom tags/attrs on a character.
"""
if self.tagcategory:
char.tags.remove(category=self.tagcategory)
char.attributes.remove(category=self.tagcategory)
[docs] def character_exit(self, char):
"""
Have a character exit the room - return them to the room menu.
"""
self.log(f"EXIT: {char} left room")
from .menu import run_evscaperoom_menu
self.character_cleanup(char)
char.location = char.home
# check if room should be deleted
if len(self.get_all_characters()) < 1:
self.delete()
# we must run menu after deletion so we don't include this room!
run_evscaperoom_menu(char)
# Evennia hooks
[docs] def at_object_receive(self, moved_obj, source_location, move_type="move", **kwargs):
"""
Called when an object arrives in the room. This can be used to
sum up the situation, set tags etc.
"""
if utils.inherits_from(moved_obj, "evennia.objects.objects.DefaultCharacter"):
self.log(f"JOIN: {moved_obj} joined room")
self.state.character_enters(moved_obj)
[docs] def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs):
"""
Called when an object leaves the room; if this is a Character we need
to clean them up and move them to the menu state.
"""
if utils.inherits_from(moved_obj, "evennia.objects.objects.DefaultCharacter"):
self.character_cleanup(moved_obj)
if len(self.get_all_characters()) <= 1:
# after this move there'll be no more characters in the room - delete the room!
self.delete()
# logger.log_info("DEBUG: Don't delete room when last player leaving")
[docs] def delete(self):
"""
Delete this room and all items related to it. Only move the players.
"""
self.db.deleting = True
for char in self.get_all_characters():
self.character_exit(char)
for obj in self.contents:
obj.delete()
self.log("END: Room cleaned up and deleted")
return super().delete()
[docs] def return_appearance(self, looker, **kwargs):
obj, pos = self.get_position(looker)
pos = (
f"\n|x[{self.position_prep_map[pos]} on " f"{obj.get_display_name(looker)}]|n"
if obj
else ""
)
admin_only = ""
if self.check_perm(looker, "Admin"):
# only for admins
objs = DefaultObject.objects.filter_family(db_location=self).exclude(id=looker.id)
admin_only = "\n|xAdmin only: " + list_to_string(
[obj.get_display_name(looker) for obj in objs]
)
return f"{self.db.desc}{pos}{admin_only}"