Add the full EvscapeRoom game engine as a contrib
This commit is contained in:
parent
aa6b403cd1
commit
addaacf5d0
13 changed files with 3509 additions and 0 deletions
237
evennia/contrib/evscaperoom/room.py
Normal file
237
evennia/contrib/evscaperoom/room.py
Normal file
|
|
@ -0,0 +1,237 @@
|
|||
"""
|
||||
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 DefaultRoom, DefaultCharacter, DefaultObject
|
||||
from evennia import utils
|
||||
from evennia.utils.ansi import strip_ansi
|
||||
from evennia import logger
|
||||
from evennia.locks.lockhandler import check_lockstring
|
||||
from evennia.utils.utils import lazy_property, list_to_string
|
||||
from .objects import EvscaperoomObject
|
||||
from .commands import CmdSetEvScapeRoom
|
||||
from .state import StateHandler
|
||||
|
||||
|
||||
class EvscapeRoom(EvscaperoomObject, DefaultRoom):
|
||||
"""
|
||||
The room to escape from.
|
||||
|
||||
"""
|
||||
|
||||
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, permanent=True)
|
||||
|
||||
self.log("Room created and log started.")
|
||||
|
||||
@lazy_property
|
||||
def statehandler(self):
|
||||
return StateHandler(self)
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
return self.statehandler.current_state
|
||||
|
||||
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")
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
def set_flag(self, flagname):
|
||||
self.db.flags[flagname] = True
|
||||
|
||||
def unset_flag(self, flagname):
|
||||
if flagname in self.db.flags:
|
||||
del self.db.flags[flagname]
|
||||
|
||||
def check_flag(self, flagname):
|
||||
return self.db.flags.get(flagname, False)
|
||||
|
||||
def check_perm(self, caller, permission):
|
||||
return check_lockstring(caller, f"dummy:perm({permission})")
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
|
||||
def at_object_receive(self, moved_obj, source_location):
|
||||
"""
|
||||
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)
|
||||
|
||||
def at_object_leave(self, moved_obj, target_location, **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")
|
||||
|
||||
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()
|
||||
|
||||
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}"
|
||||
Loading…
Add table
Add a link
Reference in a new issue