""" Room Rooms are simple containers that has no location of their own. """ from collections import defaultdict from datetime import datetime from re import match from django.utils.translation import gettext as _ from evennia import utils from evennia.utils import gametime, logger, delay from evennia.utils.utils import iter_to_str from evennia.contrib.grid.extended_room import ExtendedRoom from evennia.contrib.rpg.rpsystem import ContribRPRoom from django.conf import settings from typeclasses.pets import Hunger from typeclasses.objects import ObjectParent, Listener from utils.word_list import fix_msg _SEARCH_AT_RESULT = utils.object_from_module(settings.SEARCH_AT_RESULT) def articlize_character(character, capitalize=False): """ Return a simple character with article prefix. """ if capitalize: article = "A" else: article = "a" # Trim off any trailing periods that show up here. if match(r".*\.$", character): character = character[:-1] # logger.info(f"rooms: articlize_character({character})") if character == "" or match(r"^\|[A-z][A-Z]", character): return character elif match(r"^\|[A-z][aeiou]", character): return f"{article}n " + character else: return f"{article} " + character def articlize_characters(characters): return ", ".join([articlize_character(c) for c in characters]) def captialize_characters(characters): """ Capitalizes and prefixes an article on string of characters. Assuming one per line. """ char_list = characters.split("\n") updated = [articlize_character(ch, True) for ch in char_list] return " " + ", ".join(updated) class Room(ObjectParent, ExtendedRoom, ContribRPRoom, Listener): """ Rooms are like any Object, except their location is None (which is default). They also use basetype_setup() to add locks so they cannot be puppeted or picked up. (to change that, use at_object_creation instead) See mygame/typeclasses/objects.py for a list of properties and methods available on all Objects. """ is_dark = False has_weather = False # Used to keep track of the most recent events: short_history = [] appearance_template = """ {header} |c{name}{extra_name_info}|n {desc} {exits} {things} {characters} {footer}""" def puppets_here(self, func_name=None): """ Return a list of puppets in the current location. Only used for calling hooks on the animatronic dolls. """ if func_name: return [puppet for puppet in self.contents if hasattr(puppet, func_name) and callable(getattr(puppet, func_name))] return [puppet for puppet in self.contents if puppet.is_typeclass("typeclasses.puppets.Puppet") or puppet.is_typeclass("typeclasses.puzzles.StoryCube") or puppet.is_typeclass("typeclasses.pets.FriendlyPet") ] def get_time(self): """ Return tuple of current time of this location. (Minute, Hour, Time of Day, Season) """ timestamp = gametime.gametime(absolute=True) datestamp = datetime.fromtimestamp(timestamp) return (datestamp.minute, datestamp.hour, self.get_time_of_day(), self.get_season()) def get_full_appearance(self, looker, **kwargs): """ Return a string containing the full appearance of the room, but also the most recent events that have happened. """ logger.info(f"HISTORY: {self.short_history}") return self.return_appearance(looker, **kwargs) + "\n\n" + \ "\n".join(self.short_history) def get_display_header(self, looker, **kwargs): """ Possibly send an image to webclients. """ image = None # self.db.title_image if image: timed = self.attributes.get("title_time", None) if timed: # This makes: mellow-marsh-evening image = f"{image}-{self.get_time_of_day()}" season = self.attributes.get("title_season", None) if season: # This makes: frog-meadow-winter image = f"{image}-{self.get_season()}" level = self.attributes.get("title_level", None) if level: # This makes: cozy-house-2 image = f"{image}-{level}" looker.msg(image=( f"https://www.howardabrams.com/cozy-players-guide/{image}.jpg", {"type": "background-pane"} )) return "|n" def get_display_characters(self, looker, *args, **kwargs): chars = [c for c in self.contents_get(content_type="character") if not c.attributes.get('transformed')] vchars = self.filter_visible(chars, looker, **kwargs) num_chars = len(vchars) char_list = iter_to_str( [articlize_character(char.get_display_name(looker, pose=True, **kwargs)) for char in vchars]) # We add the word 'also' in case we showed there were objects: objs = self.contents_get(content_type="object") if len(objs) > 0: also = 'also ' else: also = '' if num_chars > 1: return f"You {also}see the following characters: {char_list}." if num_chars > 0: character = char_list.strip() if character.startswith('|'): comp_char = character[2:].lower() else: comp_char = character.lower() if match(r"an? |the ", comp_char): return f"You {also}see {character}." elif match(r"[aeiou]", comp_char): return f"You {also}see an {character}." else: return f"You {also}see a {character}." return '' def get_display_things(self, looker, *args, **kwargs): """ Replace """ things = self.filter_visible(self.contents_get(content_type="object"), looker, **kwargs) tchars = [c for c in self.contents_get(content_type="character") if c.attributes.get('transformed')] grouped_things = defaultdict(list) for thing in things + tchars: grouped_things[thing.get_display_name(looker, **kwargs)].append(thing) thing_names = [] for thingname, thinglist in sorted(grouped_things.items()): nthings = len(thinglist) thing = thinglist[0] singular, plural = thing.get_numbered_name(nthings, looker, key=thingname) if nthings > 1: thing_names.append(plural) elif thing.db.plural: thing_names.append(thing.get_display_name(looker)) else: thing_names.append(singular) thing_names = iter_to_str(thing_names, endsep=_(", and")) return _(f"You notice {thing_names}." if thing_names else "") def msg_contents(self, text, exclude=None, from_obj=None, mapping=None, raise_funcparse_errors=False, **kwargs): self.store_event(fix_msg(text)) super().msg_contents(text, exclude, from_obj, mapping, raise_funcparse_errors) def store_event(self, text, speaker=None): """ Store a text message in the short_history. """ if text and isinstance(text, tuple): text = text[0] if speaker: text = text.replace("/me", speaker.get_name()) self.short_history.append(text.replace(" /", " ")) self.short_history = self.short_history[-10:] class DabblersRoom(Room): """ Change the description based on the state of the fire. """ def get_display_desc(self, looker): fire = self.search("fire") full_desc = self.db.initial_desc if fire.hunger() == Hunger.RAVENOUS: self.db.title_level = 0 full_desc += " " + self.db.fire_out elif fire.hunger() == Hunger.HUNGRY: self.db.title_level = 1 full_desc += " " + self.db.fire_dim elif fire.hunger() == Hunger.FULL: self.db.title_level = 2 full_desc += " " + self.db.fire_full else: self.db.title_level = 3 full_desc += " " + self.db.fire_on full_desc += " " + self.db.final_desc return full_desc class OpenableRoom(Room): """ Rooms that accept the "open" and "close" command. This will move an exit in and out of this location. Set the follow properties: @set here/open_exit = $search(red door) @set here/open_exit_msg = "$You() $conj(open) the door!" @set here/close_exit_msg = "$You() $conj(close) the door!" @set here/close_automatic = 240 """ def do_open(self, opener, item): """ Move the 'open_exit' to this location. """ the_exit = self.db.open_exit the_item = self.db.open_item if the_exit and ((the_item and item.lower() ==the_item.lower()) or the_exit.key.lower().endswith(item)): if the_exit.location ==self: opener.msg(f"The {item} is already open.") return True the_exit.location = self opener.announce_action(self.db.open_exit_msg or f"$You() $conj(open) the {the_exit.name}") if self.db.close_automatic: delay(self.db.close_automatic, self.do_close) return True def do_close(self, closer=None, item=None): """ Move the 'open_exit' exit to Limbo. """ the_exit = self.db.open_exit # If a user is trying to close an item (an automatic close, # item will be None), so we just put in this check here: the_item = self.db.open_item if item and ((the_item and item.lower() ==the_item.lower()) or the_exit.key.lower().endswith(item)): pass else: return None if the_exit: if the_exit.location == self: # It is opened and can be closed: the_exit.location = None if closer: closer.announce_action(self.db.close_exit_msg or f"$You() $conj(close) the {the_exit.name}") else: self.msg_contents(self.db.close_exit_msg) elif closer: closer.msg(f"The {item} is already closed.") return True