From 543d5f03dbc7acecb4004dcf2abdc7eca3f5d246 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 12:51:05 -0600 Subject: [PATCH 01/28] initial patch --- evennia/contrib/rpg/rpsystem/rpsystem.py | 109 ++++++++++++----------- 1 file changed, 58 insertions(+), 51 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 2c431280c0..7a8caf05f8 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -442,7 +442,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ + [ regex_tuple_from_key_alias(obj) # handle objects without sdescs for obj in candidates - if not (hasattr(obj, "recog") and hasattr(obj, "sdesc")) + if not hasattr(obj, "recog") and not hasattr(obj, "sdesc") ] ) @@ -643,42 +643,13 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # the form {{#num}} to {#num} markers ready to sdesc-map in the next step. sendemote = emote.format(**receiver_lang_mapping) - # handle sdesc mappings. we make a temporary copy that we can modify - try: - process_sdesc = receiver.process_sdesc - except AttributeError: - process_sdesc = _dummy_process - - try: - process_recog = receiver.process_recog - except AttributeError: - process_recog = _dummy_process - - try: - recog_get = receiver.recog.get - receiver_sdesc_mapping = dict( - (ref, process_recog(recog_get(obj), obj, ref=ref, **kwargs)) - for ref, obj in obj_mapping.items() - ) - except AttributeError: - receiver_sdesc_mapping = dict( - ( - ref, - process_sdesc(obj.sdesc.get(), obj, ref=ref) - if hasattr(obj, "sdesc") - else process_sdesc(obj.key, obj, ref=ref), - ) - for ref, obj in obj_mapping.items() - ) - # make sure receiver always sees their real name - rkey_start = "#%i" % receiver.id - rkey_keep_case = rkey_start + "~" # signifies keeping the case - for rkey in (key for key in receiver_sdesc_mapping if key.startswith(rkey_start)): - # we could have #%i^, #%it etc depending on input case - we want the - # self-reference to retain case. - receiver_sdesc_mapping[rkey] = process_sdesc( - receiver.key, receiver, ref=rkey_keep_case, **kwargs + receiver_sdesc_mapping = dict( + ( + ref, + obj.get_display_name(receiver, ref=ref), ) + for ref, obj in obj_mapping.items() + ) # do the template replacement of the sdesc/recog {#num} markers receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs) @@ -719,11 +690,15 @@ class SdescHandler: def _cache(self): """ Cache data from storage - """ self.sdesc = self.obj.attributes.get("_sdesc", default="") sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) + if self.sdesc: + self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) + else: + permutation_string = " ".join([self.key] + self.aliases.all()) + self.sdesc_regex = re.compile(ordered_permutation_regex(permutation_string), _RE_FLAGS) + def add(self, sdesc, max_length=60): """ @@ -1346,6 +1321,9 @@ class ContribRPObject(DefaultObject): This class is meant as a mix-in or parent for objects in an rp-heavy game. It implements the base functionality for poses. """ + @lazy_property + def sdesc(self): + return SdescHandler(self) def at_object_creation(self): """ @@ -1539,6 +1517,7 @@ class ContribRPObject(DefaultObject): Keyword Args: pose (bool): Include the pose (if available) in the return. + ref (str): Specifies the capitalization for the displayed name. Returns: name (str): A string of the sdesc containing the name of the object, @@ -1546,20 +1525,18 @@ class ContribRPObject(DefaultObject): including the DBREF if this user is privileged to control said object. - Notes: - The RPObject version doesn't add color to its display. - """ idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" + ref = kwargs.get("ref","~") + if looker == self: sdesc = self.key else: try: - recog = looker.recog.get(self) + sdesc = looker.get_sdesc(self, process=True, ref=ref) except AttributeError: - recog = None - sdesc = recog or (hasattr(self, "sdesc") and self.sdesc.get()) or self.key - pose = " %s" % (self.db.pose or "") if kwargs.get("pose", False) else "" + sdesc = self.sdesc.get() + pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" return "%s%s%s" % (sdesc, idstr, pose) def return_appearance(self, looker): @@ -1635,21 +1612,23 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): said object. Notes: - The RPCharacter version of this method colors its display to make + The RPCharacter version adds additional processing to sdescs to make characters stand out from other objects. """ idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" + ref = kwargs.get("ref","~") + if looker == self: - sdesc = self.key + sdesc = self.process_recog(self.key,self) else: try: - recog = looker.recog.get(self) + sdesc = looker.get_sdesc(self, process=True, ref=ref) except AttributeError: - recog = None - sdesc = recog or (hasattr(self, "sdesc") and self.sdesc.get()) or self.key + sdesc = self.sdesc.get() pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" - return "|c%s|n%s%s" % (sdesc, idstr, pose) + return "%s%s%s" % (sdesc, idstr, pose) + def at_object_creation(self): """ @@ -1682,6 +1661,34 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): return f'/me whispers "{message}"' return f'/me says, "{message}"' + def get_sdesc(self, obj, process=False, **kwargs): + """ + Single hook method to handle getting recogs with sdesc fallback in an + aware manner, to allow separate processing of recogs from sdescs. + Gets the sdesc or recog for obj from the view of self. + + Args: + obj (Object): the object whose sdesc or recog is being gotten + Keyword Args: + process (bool): If True, the sdesc/recog is run through the + appropriate process_X method. + """ + if obj == self: + recog = self.key + sdesc = self.key + else: + try: + recog = self.recog.get(obj) + except AttributeError: + recog = None + sdesc = recog or (hasattr(obj, "sdesc") and obj.sdesc.get()) or obj.key + + if process: + sdesc = (self.process_recog if recog else self.process_sdesc)(sdesc, obj, **kwargs) + + return sdesc + + def process_sdesc(self, sdesc, obj, **kwargs): """ Allows to customize how your sdesc is displayed (primarily by From e0a9310b1bef6eb571fa871cfd53386c02a27667 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:00:09 -0600 Subject: [PATCH 02/28] split recog and sdesc colors --- evennia/contrib/rpg/rpsystem/rpsystem.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 7a8caf05f8..33a070b30a 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -1585,11 +1585,6 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): This is a character class that has poses, sdesc and recog. """ - # Handlers - @lazy_property - def sdesc(self): - return SdescHandler(self) - @lazy_property def recog(self): return RecogHandler(self) @@ -1739,14 +1734,15 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): translated from the original sdesc at this point. obj (Object): The object the recog:ed string belongs to. This is not used by default. - Kwargs: - ref (str): See process_sdesc. Returns: recog (str): The modified recog string. """ - return self.process_sdesc(recog, obj, **kwargs) + if not sdesc: + return "" + + return "|m%s|n" % sdesc def process_language(self, text, speaker, language, **kwargs): """ From e7292955efee234065535c07251290396da0c5f1 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 13:54:41 -0600 Subject: [PATCH 03/28] fix a couple typos/tweaks --- evennia/contrib/rpg/rpsystem/rpsystem.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 33a070b30a..a49e9a17cd 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -644,8 +644,8 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): sendemote = emote.format(**receiver_lang_mapping) receiver_sdesc_mapping = dict( - ( - ref, + ( + ref, obj.get_display_name(receiver, ref=ref), ) for ref, obj in obj_mapping.items() @@ -693,12 +693,10 @@ class SdescHandler: """ self.sdesc = self.obj.attributes.get("_sdesc", default="") sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") - if self.sdesc: - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) - else: + if not sdesc_regex: permutation_string = " ".join([self.key] + self.aliases.all()) - self.sdesc_regex = re.compile(ordered_permutation_regex(permutation_string), _RE_FLAGS) - + sdesc_regex = ordered_permutation_regex(permutation_string) + self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) def add(self, sdesc, max_length=60): """ @@ -1739,10 +1737,10 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): recog (str): The modified recog string. """ - if not sdesc: + if not recog: return "" - return "|m%s|n" % sdesc + return "|m%s|n" % recog def process_language(self, text, speaker, language, **kwargs): """ From 2418594742d78760b9c1af3d398c0d1a88388e5b Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:08:48 -0600 Subject: [PATCH 04/28] don't process non-character names --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index a49e9a17cd..901d731882 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -1531,7 +1531,7 @@ class ContribRPObject(DefaultObject): sdesc = self.key else: try: - sdesc = looker.get_sdesc(self, process=True, ref=ref) + sdesc = looker.get_sdesc(self, ref=ref) except AttributeError: sdesc = self.sdesc.get() pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" From f1b329c1becd7038be9d4eecee9ed727469d4c50 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:56:59 -0600 Subject: [PATCH 05/28] default sdesc is key --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 901d731882..fa3a5f6097 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -691,7 +691,7 @@ class SdescHandler: """ Cache data from storage """ - self.sdesc = self.obj.attributes.get("_sdesc", default="") + self.sdesc = self.obj.attributes.get("_sdesc", default=obj.key) sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") if not sdesc_regex: permutation_string = " ".join([self.key] + self.aliases.all()) From 08257e54416fb5be5eae36db812a5a8a83083633 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 15 Apr 2022 14:58:12 -0600 Subject: [PATCH 06/28] Update rpsystem.py --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index fa3a5f6097..31e4adbb01 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -691,7 +691,7 @@ class SdescHandler: """ Cache data from storage """ - self.sdesc = self.obj.attributes.get("_sdesc", default=obj.key) + self.sdesc = self.obj.attributes.get("_sdesc", default=self.obj.key) sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") if not sdesc_regex: permutation_string = " ".join([self.key] + self.aliases.all()) From 4127297a3a88fa3bea14297728dc430853e11a40 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 10:37:30 -0600 Subject: [PATCH 07/28] fix typo --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 31e4adbb01..2edd82b148 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -694,7 +694,7 @@ class SdescHandler: self.sdesc = self.obj.attributes.get("_sdesc", default=self.obj.key) sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") if not sdesc_regex: - permutation_string = " ".join([self.key] + self.aliases.all()) + permutation_string = " ".join([self.obj.key] + self.obj.aliases.all()) sdesc_regex = ordered_permutation_regex(permutation_string) self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) From 969e9d9ae5d7cc83b65fe40b0b958a5221c0ca01 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 18:44:48 -0600 Subject: [PATCH 08/28] change how regex is used also a couple other fixes i found; still needs some cleanup --- evennia/contrib/rpg/rpsystem/rpsystem.py | 106 ++++++++++------------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 2edd82b148..0cc9f66258 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -151,12 +151,13 @@ Extra Installation Instructions: import re from re import escape as re_escape import itertools +from string import punctuation from django.conf import settings from evennia.objects.objects import DefaultObject, DefaultCharacter from evennia.objects.models import ObjectDB from evennia.commands.command import Command from evennia.commands.cmdset import CmdSet -from evennia.utils import ansi +from evennia.utils import ansi, logger from evennia.utils.utils import lazy_property, make_iter, variable_from_module _REGEX_TUPLE_CACHE = {} @@ -430,24 +431,11 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ - says, "..." are """ - # Load all candidate regex tuples [(regex, obj, sdesc/recog),...] - candidate_regexes = ( - ([(_RE_SELF_REF, sender, sender.sdesc.get())] if hasattr(sender, "sdesc") else []) - + ( - [sender.recog.get_regex_tuple(obj) for obj in candidates] - if hasattr(sender, "recog") - else [] - ) - + [obj.sdesc.get_regex_tuple() for obj in candidates if hasattr(obj, "sdesc")] - + [ - regex_tuple_from_key_alias(obj) # handle objects without sdescs - for obj in candidates - if not hasattr(obj, "recog") and not hasattr(obj, "sdesc") - ] - ) - - # filter out non-found data - candidate_regexes = [tup for tup in candidate_regexes if tup] + candidate_map = [(sender, 'me')] + candidate_map += [ (obj, sender.recog.get(obj)) for obj in candidates if sender.recog.get(obj)] if hasattr(sender, "recog") else [] + candidate_map += [ (obj, obj.sdesc.get()) for obj in candidates if hasattr(obj, "sdesc") ] + for obj in candidates: + candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] # escape mapping syntax on the form {#id} if it exists already in emote, # if so it is replaced with just "id". @@ -469,17 +457,29 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # first see if there is a number given (e.g. 1-tall) num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None istart0 = marker_match.start() - istart = istart0 + istart = istart0 + 1 - # loop over all candidate regexes and match against the string following the match - matches = ((reg.match(string[istart:]), obj, text) for reg, obj, text in candidate_regexes) - - # score matches by how long part of the string was matched - matches = [(match.end() if match else -1, obj, text) for match, obj, text in matches] - maxscore = max(score for score, obj, text in matches) + if search_mode: + rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(string[istart:].split())]) + rquery = re.compile(rquery, _RE_FLAGS) + matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) + bestmatches = [(obj, match.group()) for match, obj, text in matches if match] + else: + word_list = [] + bestmatches = [] + for next_word in iter(string[istart:].split()): + word_list.append(next_word.strip(punctuation)) + rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list]) + rquery = re.compile(rquery, _RE_FLAGS) + matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) + matches = [(obj, match.group()) for match, obj, text in matches if match] + if len(matches) == 0: + # no matches at this length, keep previous iteration + break + # set latest match set as best matches + bestmatches = matches # we have a valid maxscore, extract all matches with this value - bestmatches = [(obj, text) for score, obj, text in matches if maxscore == score != -1] nmatches = len(bestmatches) if not nmatches: @@ -488,12 +488,11 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ nmatches = 0 elif nmatches == 1: # an exact match. - obj = bestmatches[0][0] - nmatches = 1 + obj, match_str = bestmatches[0] elif all(bestmatches[0][0].id == obj.id for obj, text in bestmatches): # multi-match but all matches actually reference the same # obj (could happen with clashing recogs + sdescs) - obj = bestmatches[0][0] + obj, match_str = bestmatches[0] nmatches = 1 else: # multi-match. @@ -501,7 +500,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ inum = min(max(0, int(num_identifier) - 1), nmatches - 1) if num_identifier else None if inum is not None: # A valid inum is given. Use this to separate data. - obj = bestmatches[inum][0] + obj, match_str = bestmatches[inum] nmatches = 1 else: # no identifier given - a real multimatch. @@ -522,19 +521,16 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # - ^ for all upercase input (likle /NAME) # - v for lower-case input (like /name) # - ~ for mixed case input (like /nAmE) - matchtext = marker_match.group() - if not _RE_SELF_REF.match(matchtext): - # self-refs are kept as-is, others are parsed by case - matchtext = marker_match.group().lstrip(_PREFIX) - if matchtext.istitle(): - case = "t" - elif matchtext.isupper(): - case = "^" - elif matchtext.islower(): - case = "v" + matchtext = marker_match.group().lstrip(_PREFIX) + if matchtext.istitle(): + case = "t" + elif matchtext.isupper(): + case = "^" + elif matchtext.islower(): + case = "v" key = "#%i%s" % (obj.id, case) - string = string[:istart0] + "{%s}" % key + string[istart + maxscore :] + string = string[:istart0] + "{%s}" % key + string[istart + len(match_str) :] mapping[key] = obj else: @@ -619,14 +615,16 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # if anonymous_add is passed as a kwarg, collect and remove it from kwargs if "anonymous_add" in kwargs: anonymous_add = kwargs.pop("anonymous_add") - if anonymous_add and not any(1 for tag in obj_mapping if tag.startswith(skey)): + self_refs = (f"{skey}{ref}" for ref in ('t','^','v','~','')) + if anonymous_add and not any(1 for tag in obj_mapping if tag in self_refs): # no self-reference in the emote - add to the end - obj_mapping[skey] = sender if anonymous_add == "first": + skey = skey + 't' possessive = "" if emote.startswith("'") else " " emote = "%s%s%s" % ("{{%s}}" % skey, possessive, emote) else: emote = "%s [%s]" % (emote, "{{%s}}" % skey) + obj_mapping[skey] = sender # broadcast emote to everyone for receiver in receivers: @@ -684,7 +682,6 @@ class SdescHandler: """ self.obj = obj self.sdesc = "" - self.sdesc_regex = "" self._cache() def _cache(self): @@ -692,11 +689,6 @@ class SdescHandler: Cache data from storage """ self.sdesc = self.obj.attributes.get("_sdesc", default=self.obj.key) - sdesc_regex = self.obj.attributes.get("_sdesc_regex", default="") - if not sdesc_regex: - permutation_string = " ".join([self.obj.key] + self.obj.aliases.all()) - sdesc_regex = ordered_permutation_regex(permutation_string) - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) def add(self, sdesc, max_length=60): """ @@ -737,12 +729,9 @@ class SdescHandler: ) # store to attributes - sdesc_regex = ordered_permutation_regex(cleaned_sdesc) self.obj.attributes.add("_sdesc", sdesc) - self.obj.attributes.add("_sdesc_regex", sdesc_regex) # local caching self.sdesc = sdesc - self.sdesc_regex = re.compile(sdesc_regex, _RE_FLAGS) return sdesc @@ -881,10 +870,10 @@ class RecogHandler: # check an eventual recog_masked lock on the object # to avoid revealing masked characters. If lock # does not exist, pass automatically. - return self.obj2recog.get(obj, obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key) + return self.obj2recog.get(obj, None) else: - # recog_mask log not passed, disable recog - return obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key + # recog_mask lock not passed, disable recog + return None def all(self): """ @@ -998,7 +987,6 @@ class CmdSay(RPCommand): # replaces standard say # calling the speech modifying hook speech = caller.at_pre_say(self.args) - # preparing the speech with sdesc/speech parsing. targets = self.caller.location.contents send_emote(self.caller, targets, speech, anonymous_add=None) @@ -1651,8 +1639,8 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): """ if kwargs.get("whisper"): - return f'/me whispers "{message}"' - return f'/me says, "{message}"' + return f'/Me whispers "{message}"' + return f'/Me says, "{message}"' def get_sdesc(self, obj, process=False, **kwargs): """ From b5ec52a51acf0ac52cbe01ddb620d7c2c407a01b Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:09:46 -0600 Subject: [PATCH 09/28] strip obj id; other cleanup --- evennia/contrib/rpg/rpsystem/rpsystem.py | 152 +++-------------------- 1 file changed, 19 insertions(+), 133 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 0cc9f66258..2589d67798 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -150,7 +150,6 @@ Extra Installation Instructions: """ import re from re import escape as re_escape -import itertools from string import punctuation from django.conf import settings from evennia.objects.objects import DefaultObject, DefaultCharacter @@ -160,8 +159,6 @@ from evennia.commands.cmdset import CmdSet from evennia.utils import ansi, logger from evennia.utils.utils import lazy_property, make_iter, variable_from_module -_REGEX_TUPLE_CACHE = {} - _AT_SEARCH_RESULT = variable_from_module(*settings.SEARCH_AT_RESULT.rsplit(".", 1)) # ------------------------------------------------------------ # Emote parser @@ -240,97 +237,7 @@ class LanguageError(Exception): pass -def _dummy_process(text, *args, **kwargs): - "Pass-through processor" - return text - - # emoting mechanisms - - -def ordered_permutation_regex(sentence): - """ - Builds a regex that matches 'ordered permutations' of a sentence's - words. - - Args: - sentence (str): The sentence to build a match pattern to - - Returns: - regex (re object): Compiled regex object represented the - possible ordered permutations of the sentence, from longest to - shortest. - Example: - The sdesc_regex for an sdesc of " very tall man" will - result in the following allowed permutations, - regex-matched in inverse order of length (case-insensitive): - "the very tall man", "the very tall", "very tall man", - "very tall", "the very", "tall man", "the", "very", "tall", - and "man". - We also add regex to make sure it also accepts num-specifiers, - like /2-tall. - - """ - # escape {#nnn} markers from sentence, replace with nnn - sentence = _RE_REF.sub(r"\1", sentence) - # escape {##nnn} markers, replace with nnn - sentence = _RE_REF_LANG.sub(r"\1", sentence) - # escape self-ref marker from sentence - sentence = _RE_SELF_REF.sub(r"", sentence) - - # ordered permutation algorithm - words = sentence.split() - combinations = itertools.product((True, False), repeat=len(words)) - solution = [] - for combination in combinations: - comb = [] - for iword, word in enumerate(words): - if combination[iword]: - comb.append(word) - elif comb: - break - if comb: - solution.append( - _PREFIX - + r"[0-9]*%s*%s(?=\W|$)+" % (_NUM_SEP, re_escape(" ".join(comb)).rstrip("\\")) - ) - - # combine into a match regex, first matching the longest down to the shortest components - regex = r"|".join(sorted(set(solution), key=lambda item: (-len(item), item))) - return regex - - -def regex_tuple_from_key_alias(obj): - """ - This will build a regex tuple for any object, not just from those - with sdesc/recog handlers. It's used as a legacy mechanism for - being able to mix this contrib with objects not using sdescs, but - note that creating the ordered permutation regex dynamically for - every object will add computational overhead. - - Args: - obj (Object): This object's key and eventual aliases will - be used to build the tuple. - - Returns: - regex_tuple (tuple): A tuple - (ordered_permutation_regex, obj, key/alias) - - - """ - global _REGEX_TUPLE_CACHE - permutation_string = " ".join([obj.key] + obj.aliases.all()) - cache_key = f"{obj.id} {permutation_string}" - - if cache_key not in _REGEX_TUPLE_CACHE: - _REGEX_TUPLE_CACHE[cache_key] = ( - re.compile(ordered_permutation_regex(permutation_string), _RE_FLAGS), - obj, - obj.key, - ) - return _REGEX_TUPLE_CACHE[cache_key] - - def parse_language(speaker, emote): """ Parse the emote for language. This is @@ -432,10 +339,14 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ """ candidate_map = [(sender, 'me')] - candidate_map += [ (obj, sender.recog.get(obj)) for obj in candidates if sender.recog.get(obj)] if hasattr(sender, "recog") else [] - candidate_map += [ (obj, obj.sdesc.get()) for obj in candidates if hasattr(obj, "sdesc") ] for obj in candidates: - candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] + if hasattr(sender, "recog"): + if recog := sender.recog.get(obj): + candidate_map.append((obj, recog)) + if hasattr(obj, "sdesc"): + candidate_map.append((obj, obj.sdesc.get())) + else: + candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] # escape mapping syntax on the form {#id} if it exists already in emote, # if so it is replaced with just "id". @@ -630,13 +541,15 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): for receiver in receivers: # first handle the language mapping, which always produce different keys ##nn receiver_lang_mapping = {} - try: - process_language = receiver.process_language - except AttributeError: - process_language = _dummy_process - for key, (langname, saytext) in language_mapping.items(): - # color says - receiver_lang_mapping[key] = process_language(saytext, sender, langname) + if hasattr(receiver, "process_language") and callable(receiver.process_language): + receiver_lang_mapping = { + key: receiver.process_language(saytext, sender, langname) + for key, (langname, saytext) in language_mapping.items() + } + else: + receiver_lang_mapping = { + key: saytext for key, (langname, saytext) in language_mapping.items() + } # map the language {##num} markers. This will convert the escaped sdesc markers on # the form {{#num}} to {#num} markers ready to sdesc-map in the next step. sendemote = emote.format(**receiver_lang_mapping) @@ -644,7 +557,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): receiver_sdesc_mapping = dict( ( ref, - obj.get_display_name(receiver, ref=ref), + obj.get_display_name(receiver, ref=ref, no_id=True), ) for ref, obj in obj_mapping.items() ) @@ -743,15 +656,6 @@ class SdescHandler: """ return self.sdesc or self.obj.key - def get_regex_tuple(self): - """ - Return data for sdesc/recog handling - - Returns: - tup (tuple): tuple (sdesc_regex, obj, sdesc) - - """ - return self.sdesc_regex, self.obj, self.sdesc class RecogHandler: @@ -779,7 +683,6 @@ class RecogHandler: self.obj = obj # mappings self.ref2recog = {} - self.obj2regex = {} self.obj2recog = {} self._cache() @@ -788,11 +691,7 @@ class RecogHandler: Load data to handler cache """ self.ref2recog = self.obj.attributes.get("_recog_ref2recog", default={}) - obj2regex = self.obj.attributes.get("_recog_obj2regex", default={}) obj2recog = self.obj.attributes.get("_recog_obj2recog", default={}) - self.obj2regex = dict( - (obj, re.compile(regex, _RE_FLAGS)) for obj, regex in obj2regex.items() if obj - ) self.obj2recog = dict((obj, recog) for obj, recog in obj2recog.items() if obj) def add(self, obj, recog, max_length=60): @@ -843,12 +742,9 @@ class RecogHandler: key = "#%i" % obj.id self.obj.attributes.get("_recog_ref2recog", default={})[key] = recog self.obj.attributes.get("_recog_obj2recog", default={})[obj] = recog - regex = ordered_permutation_regex(cleaned_recog) - self.obj.attributes.get("_recog_obj2regex", default={})[obj] = regex # local caching self.ref2recog[key] = recog self.obj2recog[obj] = recog - self.obj2regex[obj] = re.compile(regex, _RE_FLAGS) return recog def get(self, obj): @@ -895,18 +791,8 @@ class RecogHandler: if obj in self.obj2recog: del self.obj.db._recog_obj2recog[obj] del self.obj.db._recog_obj2regex[obj] - del self.obj.db._recog_ref2recog["#%i" % obj.id] self._cache() - def get_regex_tuple(self, obj): - """ - Returns: - rec (tuple): Tuple (recog_regex, obj, recog) - """ - if obj in self.obj2recog and obj.access(self.obj, "enable_recog", default=True): - return self.obj2regex[obj], obj, self.obj2regex[obj] - return None - # ------------------------------------------------------------ # RP Commands @@ -1512,7 +1398,7 @@ class ContribRPObject(DefaultObject): said object. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" + idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("no_id", False) else "" ref = kwargs.get("ref","~") if looker == self: @@ -1597,7 +1483,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): characters stand out from other objects. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") else "" + idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("no_id",False) else "" ref = kwargs.get("ref","~") if looker == self: From a49caf9f93fe6f878751e1f7cceece68a3e345aa Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:10:12 -0600 Subject: [PATCH 10/28] don't need those any more --- evennia/contrib/rpg/rpsystem/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/__init__.py b/evennia/contrib/rpg/rpsystem/__init__.py index 9affd32487..395aef660b 100644 --- a/evennia/contrib/rpg/rpsystem/__init__.py +++ b/evennia/contrib/rpg/rpsystem/__init__.py @@ -4,7 +4,6 @@ Roleplaying emotes and language - Griatch, 2015 """ from .rpsystem import EmoteError, SdescError, RecogError, LanguageError # noqa -from .rpsystem import ordered_permutation_regex, regex_tuple_from_key_alias # noqa from .rpsystem import parse_language, parse_sdescs_and_recogs, send_emote # noqa from .rpsystem import SdescHandler, RecogHandler # noqa from .rpsystem import RPCommand, CmdEmote, CmdSay, CmdSdesc, CmdPose, CmdRecog, CmdMask # noqa From 180b3f222fe7ce3001807934e86f86050f8a6af1 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:14:08 -0600 Subject: [PATCH 11/28] Update tests.py --- evennia/contrib/rpg/rpsystem/tests.py | 48 +-------------------------- 1 file changed, 1 insertion(+), 47 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index cd96ac43b1..d603b6fbff 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -113,41 +113,11 @@ class TestRPSystem(BaseEvenniaTest): rpsystem.ContribRPCharacter, key="Receiver2", location=self.room ) - def test_ordered_permutation_regex(self): - self.assertEqual( - rpsystem.ordered_permutation_regex(sdesc0), - "/[0-9]*-*A\\ nice\\ sender\\ of\\ emotes(?=\\W|$)+|" - "/[0-9]*-*nice\\ sender\\ of\\ emotes(?=\\W|$)+|" - "/[0-9]*-*A\\ nice\\ sender\\ of(?=\\W|$)+|" - "/[0-9]*-*sender\\ of\\ emotes(?=\\W|$)+|" - "/[0-9]*-*nice\\ sender\\ of(?=\\W|$)+|" - "/[0-9]*-*A\\ nice\\ sender(?=\\W|$)+|" - "/[0-9]*-*nice\\ sender(?=\\W|$)+|" - "/[0-9]*-*of\\ emotes(?=\\W|$)+|" - "/[0-9]*-*sender\\ of(?=\\W|$)+|" - "/[0-9]*-*A\\ nice(?=\\W|$)+|" - "/[0-9]*-*emotes(?=\\W|$)+|" - "/[0-9]*-*sender(?=\\W|$)+|" - "/[0-9]*-*nice(?=\\W|$)+|" - "/[0-9]*-*of(?=\\W|$)+|" - "/[0-9]*-*A(?=\\W|$)+", - ) - def test_sdesc_handler(self): self.speaker.sdesc.add(sdesc0) self.assertEqual(self.speaker.sdesc.get(), sdesc0) self.speaker.sdesc.add("This is {#324} ignored") self.assertEqual(self.speaker.sdesc.get(), "This is 324 ignored") - self.speaker.sdesc.add("Testing three words") - self.assertEqual( - self.speaker.sdesc.get_regex_tuple()[0].pattern, - "/[0-9]*-*Testing\ three\ words(?=\W|$)+|" - "/[0-9]*-*Testing\ three(?=\W|$)+|" - "/[0-9]*-*three\ words(?=\W|$)+|" - "/[0-9]*-*Testing(?=\W|$)+|" - "/[0-9]*-*three(?=\W|$)+|" - "/[0-9]*-*words(?=\W|$)+", - ) def test_recog_handler(self): self.speaker.sdesc.add(sdesc0) @@ -156,12 +126,8 @@ class TestRPSystem(BaseEvenniaTest): self.speaker.recog.add(self.receiver2, recog02) self.assertEqual(self.speaker.recog.get(self.receiver1), recog01) self.assertEqual(self.speaker.recog.get(self.receiver2), recog02) - self.assertEqual( - self.speaker.recog.get_regex_tuple(self.receiver1)[0].pattern, - "/[0-9]*-*Mr\\ Receiver(?=\\W|$)+|/[0-9]*-*Receiver(?=\\W|$)+|/[0-9]*-*Mr(?=\\W|$)+", - ) self.speaker.recog.remove(self.receiver1) - self.assertEqual(self.speaker.recog.get(self.receiver1), sdesc1) + self.assertEqual(self.speaker.recog.get(self.receiver1), None) self.assertEqual(self.speaker.recog.all(), {"Mr Receiver2": self.receiver2}) @@ -265,18 +231,6 @@ class TestRPSystem(BaseEvenniaTest): self.assertEqual(self.speaker.search("receiver of emotes"), self.receiver1) self.assertEqual(self.speaker.search("colliding"), self.receiver2) - def test_regex_tuple_from_key_alias(self): - self.speaker.aliases.add("foo bar") - self.speaker.aliases.add("this thing is a long thing") - t0 = time.time() - result = rpsystem.regex_tuple_from_key_alias(self.speaker) - t1 = time.time() - result = rpsystem.regex_tuple_from_key_alias(self.speaker) - t2 = time.time() - # print(f"t1: {t1 - t0}, t2: {t2 - t1}") - self.assertLess(t2 - t1, 10**-4) - self.assertEqual(result, (Anything, self.speaker, self.speaker.key)) - class TestRPSystemCommands(BaseEvenniaCommandTest): def setUp(self): From 2047c64e9db29c82daf243d582e0d54ed22b1bf5 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:46:06 -0600 Subject: [PATCH 12/28] Update rpsystem.py --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 2589d67798..cc10b0b99d 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -526,7 +526,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # if anonymous_add is passed as a kwarg, collect and remove it from kwargs if "anonymous_add" in kwargs: anonymous_add = kwargs.pop("anonymous_add") - self_refs = (f"{skey}{ref}" for ref in ('t','^','v','~','')) + self_refs = [f"{skey}{ref}" for ref in ('t','^','v','~','')] if anonymous_add and not any(1 for tag in obj_mapping if tag in self_refs): # no self-reference in the emote - add to the end if anonymous_add == "first": From 55e7b3c93df80b5939ec7ed4da9a39dd6ddeeab8 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 21 Apr 2022 21:57:51 -0600 Subject: [PATCH 13/28] deleted the wrong line --- evennia/contrib/rpg/rpsystem/rpsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index cc10b0b99d..b79a1974ec 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -790,7 +790,7 @@ class RecogHandler: """ if obj in self.obj2recog: del self.obj.db._recog_obj2recog[obj] - del self.obj.db._recog_obj2regex[obj] + del self.obj.db._recog_ref2recog["#%i" % obj.id] self._cache() From 7568cb29b96c5978705b839aa748cb2ede04f811 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 22 Apr 2022 11:39:49 -0600 Subject: [PATCH 14/28] update format color for recog vs sdesc --- evennia/contrib/rpg/rpsystem/tests.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index d603b6fbff..2c45e9a8a0 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -96,7 +96,7 @@ recog01 = "Mr Receiver" recog02 = "Mr Receiver2" recog10 = "Mr Sender" emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."' -case_emote = "/me looks at /first, then /FIRST, /First and /Colliding twice." +case_emote = "/Me looks at /first, then /FIRST, /First and /Colliding twice." class TestRPSystem(BaseEvenniaTest): @@ -178,18 +178,18 @@ class TestRPSystem(BaseEvenniaTest): rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False) self.assertEqual( self.out0, - "With a flair, |bSender|n looks at |bThe first receiver of emotes.|n " + "With a flair, |mSender|n looks at |bThe first receiver of emotes.|n " 'and |bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n', ) self.assertEqual( self.out1, - "With a flair, |bA nice sender of emotes|n looks at |bReceiver1|n and " + "With a flair, |bA nice sender of emotes|n looks at |mReceiver1|n and " '|bAnother nice colliding sdesc-guy for tests|n. She says |w"This is a test."|n', ) self.assertEqual( self.out2, "With a flair, |bA nice sender of emotes|n looks at |bThe first " - 'receiver of emotes.|n and |bReceiver2|n. She says |w"This is a test."|n', + 'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n', ) def test_send_case_sensitive_emote(self): @@ -207,20 +207,20 @@ class TestRPSystem(BaseEvenniaTest): rpsystem.send_emote(speaker, receivers, case_emote) self.assertEqual( self.out0, - "|bSender|n looks at |bthe first receiver of emotes.|n, then " + "|mSender|n looks at |bthe first receiver of emotes.|n, then " "|bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n and " "|bAnother nice colliding sdesc-guy for tests|n twice.", ) self.assertEqual( self.out1, - "|bA nice sender of emotes|n looks at |bReceiver1|n, then |bReceiver1|n, " - "|bReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.", + "|bA nice sender of emotes|n looks at |mReceiver1|n, then |mReceiver1|n, " + "|mReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.", ) self.assertEqual( self.out2, "|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n, " "then |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of " - "emotes.|n and |bReceiver2|n twice.", + "emotes.|n and |mReceiver2|n twice.", ) def test_rpsearch(self): From 0769ffbef1fbcbf820ba6f2acae9de016c96813a Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 22 Apr 2022 11:40:47 -0600 Subject: [PATCH 15/28] Update rpsystem.py --- evennia/contrib/rpg/rpsystem/rpsystem.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index b79a1974ec..836e7ea4d9 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -367,11 +367,12 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # first see if there is a number given (e.g. 1-tall) num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None - istart0 = marker_match.start() - istart = istart0 + 1 + match_index = marker_match.start() + head = string[:match_index] + tail = string[match_index+1:] if search_mode: - rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(string[istart:].split())]) + rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(tail.split())]) rquery = re.compile(rquery, _RE_FLAGS) matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) bestmatches = [(obj, match.group()) for match, obj, text in matches if match] @@ -379,8 +380,12 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ else: word_list = [] bestmatches = [] - for next_word in iter(string[istart:].split()): - word_list.append(next_word.strip(punctuation)) + tail = re.split('(\W)', tail) + istart = 0 + for i, item in enumerate(tail): + if not item.isalpha(): + continue + word_list.append(item) rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list]) rquery = re.compile(rquery, _RE_FLAGS) matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) @@ -390,7 +395,10 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ break # set latest match set as best matches bestmatches = matches - # we have a valid maxscore, extract all matches with this value + istart = i + + tail = "".join(tail[istart+1:]) + nmatches = len(bestmatches) if not nmatches: @@ -441,7 +449,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ case = "v" key = "#%i%s" % (obj.id, case) - string = string[:istart0] + "{%s}" % key + string[istart + len(match_str) :] + string = f"{head}{{{key}}}{tail}" mapping[key] = obj else: @@ -1109,7 +1117,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room if forget_mode: # remove existing recog caller.recog.remove(obj) - caller.msg("%s will now know them only as '%s'." % (caller.key, obj.recog.get(obj))) + caller.msg("%s will now know them only as '%s'." % (caller.key, obj.get_display_name(caller, no_id=True))) else: # set recog sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key From e86b81917bc915a7c0a5668f56402273b642b72b Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:43:07 -0600 Subject: [PATCH 16/28] added more comments, cleaned up a couple lines --- evennia/contrib/rpg/rpsystem/rpsystem.py | 104 ++++++++++++++++------- 1 file changed, 71 insertions(+), 33 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 836e7ea4d9..295aa2a55b 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -149,7 +149,6 @@ Extra Installation Instructions: """ import re -from re import escape as re_escape from string import punctuation from django.conf import settings from evennia.objects.objects import DefaultObject, DefaultCharacter @@ -338,13 +337,18 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ - says, "..." are """ + # build a list of candidates with all possible referrable names + # include 'me' keyword for self-ref candidate_map = [(sender, 'me')] for obj in candidates: + # check if sender has any recogs for obj and add if hasattr(sender, "recog"): if recog := sender.recog.get(obj): candidate_map.append((obj, recog)) + # check if obj has an sdesc and add if hasattr(obj, "sdesc"): candidate_map.append((obj, obj.sdesc.get())) + # if no sdesc, include key plus aliases instead else: candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] @@ -370,34 +374,41 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ match_index = marker_match.start() head = string[:match_index] tail = string[match_index+1:] - + if search_mode: + # match the candidates against the whole search string rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(tail.split())]) - rquery = re.compile(rquery, _RE_FLAGS) - matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) + matches = ((re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) + # filter out any non-matching candidates bestmatches = [(obj, match.group()) for match, obj, text in matches if match] else: + # to find the longest match, we start from the marker and lengthen the + # match query one word at a time. word_list = [] bestmatches = [] + # preserve punctuation when splitting tail = re.split('(\W)', tail) - istart = 0 + iend = 0 for i, item in enumerate(tail): + # don't add non-word characters to the search query if not item.isalpha(): continue word_list.append(item) rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list]) - rquery = re.compile(rquery, _RE_FLAGS) - matches = ((rquery.search(text), obj, text) for obj, text in candidate_map) + # match candidates against the current set of words + matches = ((re.search(re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) matches = [(obj, match.group()) for match, obj, text in matches if match] if len(matches) == 0: - # no matches at this length, keep previous iteration + # no matches at this length, keep previous iteration as best break - # set latest match set as best matches + # since this is the longest match so far, set latest match set as best matches bestmatches = matches - istart = i + # save current index as end point of matched text + iend = i - tail = "".join(tail[istart+1:]) + # recombine remainder of emote back into a string + tail = "".join(tail[iend+1:]) nmatches = len(bestmatches) @@ -448,7 +459,8 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ elif matchtext.islower(): case = "v" - key = "#%i%s" % (obj.id, case) + key = f"#{obj.id}{case}" + # recombine emote with matched text replaced by ref string = f"{head}{{{key}}}{tail}" mapping[key] = obj @@ -534,14 +546,17 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # if anonymous_add is passed as a kwarg, collect and remove it from kwargs if "anonymous_add" in kwargs: anonymous_add = kwargs.pop("anonymous_add") + # make sure to catch all possible self-refs self_refs = [f"{skey}{ref}" for ref in ('t','^','v','~','')] if anonymous_add and not any(1 for tag in obj_mapping if tag in self_refs): - # no self-reference in the emote - add to the end + # no self-reference in the emote - add it if anonymous_add == "first": - skey = skey + 't' + # add case flag for initial caps + skey += 't' possessive = "" if emote.startswith("'") else " " emote = "%s%s%s" % ("{{%s}}" % skey, possessive, emote) else: + # add it to the end emote = "%s [%s]" % (emote, "{{%s}}" % skey) obj_mapping[skey] = sender @@ -562,10 +577,11 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # the form {{#num}} to {#num} markers ready to sdesc-map in the next step. sendemote = emote.format(**receiver_lang_mapping) + # map the ref keys to sdescs receiver_sdesc_mapping = dict( ( ref, - obj.get_display_name(receiver, ref=ref, no_id=True), + obj.get_display_name(receiver, noid=True), ) for ref, obj in obj_mapping.items() ) @@ -1117,7 +1133,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room if forget_mode: # remove existing recog caller.recog.remove(obj) - caller.msg("%s will now know them only as '%s'." % (caller.key, obj.get_display_name(caller, no_id=True))) + caller.msg("%s will now know them only as '%s'." % (caller.key, obj.get_display_name(caller, noid=True))) else: # set recog sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key @@ -1397,24 +1413,34 @@ class ContribRPObject(DefaultObject): Keyword Args: pose (bool): Include the pose (if available) in the return. - ref (str): Specifies the capitalization for the displayed name. + ref (str): The reference marker found in string to replace. + This is on the form #{num}{case}, like '#12^', where + the number is a processing location in the string and the + case symbol indicates the case of the original tag input + - `t` - input was Titled, like /Tall + - `^` - input was all uppercase, like /TALL + - `v` - input was all lowercase, like /tall + - `~` - input case should be kept, or was mixed-case + noid (bool): Don't show DBREF even if viewer has control access. Returns: name (str): A string of the sdesc containing the name of the object, - if this is defined. - including the DBREF if this user is privileged to control - said object. + if this is defined. By default, included the DBREF if this user + is privileged to control said object. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("no_id", False) else "" + idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("noid",False) else "" ref = kwargs.get("ref","~") if looker == self: + # always show your own key sdesc = self.key else: try: + # get the sdesc looker should see sdesc = looker.get_sdesc(self, ref=ref) except AttributeError: + # use own sdesc as a fallback sdesc = self.sdesc.get() pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" return "%s%s%s" % (sdesc, idstr, pose) @@ -1426,6 +1452,10 @@ class ContribRPObject(DefaultObject): Args: looker (Object): Object doing the looking. + + Returns: + string (str): A string containing the name, appearance and contents + of the object. """ if not looker: return "" @@ -1449,6 +1479,7 @@ class ContribRPObject(DefaultObject): string += "\n|wExits:|n " + ", ".join(exits) if users or things: string += "\n " + "\n ".join(users + things) + return string @@ -1479,19 +1510,27 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): Keyword Args: pose (bool): Include the pose (if available) in the return. + ref (str): The reference marker found in string to replace. + This is on the form #{num}{case}, like '#12^', where + the number is a processing location in the string and the + case symbol indicates the case of the original tag input + - `t` - input was Titled, like /Tall + - `^` - input was all uppercase, like /TALL + - `v` - input was all lowercase, like /tall + - `~` - input case should be kept, or was mixed-case + noid (bool): Don't show DBREF even if viewer has control access. Returns: name (str): A string of the sdesc containing the name of the object, - if this is defined. - including the DBREF if this user is privileged to control - said object. + if this is defined. By default, included the DBREF if this user + is privileged to control said object. Notes: The RPCharacter version adds additional processing to sdescs to make characters stand out from other objects. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("no_id",False) else "" + idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("noid",False) else "" ref = kwargs.get("ref","~") if looker == self: @@ -1512,10 +1551,8 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): super().at_object_creation() self.db._sdesc = "" - self.db._sdesc_regex = "" self.db._recog_ref2recog = {} - self.db._recog_obj2regex = {} self.db._recog_obj2recog = {} self.cmdset.add(RPSystemCmdSet, persistent=True) @@ -1538,7 +1575,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): def get_sdesc(self, obj, process=False, **kwargs): """ - Single hook method to handle getting recogs with sdesc fallback in an + Single method to handle getting recogs with sdesc fallback in an aware manner, to allow separate processing of recogs from sdescs. Gets the sdesc or recog for obj from the view of self. @@ -1546,19 +1583,20 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): obj (Object): the object whose sdesc or recog is being gotten Keyword Args: process (bool): If True, the sdesc/recog is run through the - appropriate process_X method. + appropriate process method (process_sdesc or process_recog) """ + # always see own key if obj == self: recog = self.key sdesc = self.key else: - try: - recog = self.recog.get(obj) - except AttributeError: - recog = None + # first check if we have a recog for this object + recog = self.recog.get(obj) + # set sdesc to recog, using sdesc as a fallback, or the object's key if no sdesc sdesc = recog or (hasattr(obj, "sdesc") and obj.sdesc.get()) or obj.key if process: + # process the sdesc as a recog if a recog was found, else as an sdesc sdesc = (self.process_recog if recog else self.process_sdesc)(sdesc, obj, **kwargs) return sdesc From 97b83ef2410435ef318d1cedb729335322bf92ac Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:52:13 -0600 Subject: [PATCH 17/28] update RecogHandler.get docstring --- evennia/contrib/rpg/rpsystem/rpsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 295aa2a55b..bfa638af64 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -773,13 +773,13 @@ class RecogHandler: def get(self, obj): """ - Get recog replacement string, if one exists, otherwise - get sdesc and as a last resort, the object's key. + Get recog replacement string, if one exists. Args: obj (Object): The object, whose sdesc to replace Returns: - recog (str): The replacement string to use. + recog (str or None): The replacement string to use, or + None if there is no recog for this object. Notes: This method will respect a "enable_recog" lock set on From 052714f82b7edc20d680347fabc45f59ded49dfb Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:56:08 -0600 Subject: [PATCH 18/28] correct .get_sdesc docstring --- evennia/contrib/rpg/rpsystem/rpsystem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index bfa638af64..5227e73dc0 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -1583,7 +1583,8 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): obj (Object): the object whose sdesc or recog is being gotten Keyword Args: process (bool): If True, the sdesc/recog is run through the - appropriate process method (process_sdesc or process_recog) + appropriate process method for self - .process_sdesc or + .process_recog """ # always see own key if obj == self: From 15be72489af441dd3d2fc0bf75be17534ac51248 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:58:49 -0600 Subject: [PATCH 19/28] fix typo, add comment --- evennia/contrib/rpg/rpsystem/rpsystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 5227e73dc0..a14fc29d0a 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -372,11 +372,12 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # first see if there is a number given (e.g. 1-tall) num_identifier, _ = marker_match.groups("") # return "" if no match, rather than None match_index = marker_match.start() + # split the emote string at the reference marker, to process everything after it head = string[:match_index] tail = string[match_index+1:] if search_mode: - # match the candidates against the whole search string + # match the candidates against the whole search string after the marker rquery = "".join([r"\b(" + re.escape(word.strip(punctuation)) + r").*" for word in iter(tail.split())]) matches = ((re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) # filter out any non-matching candidates @@ -397,7 +398,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ word_list.append(item) rquery = "".join([r"\b(" + re.escape(word) + r").*" for word in word_list]) # match candidates against the current set of words - matches = ((re.search(re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) + matches = ((re.search(rquery, text, _RE_FLAGS), obj, text) for obj, text in candidate_map) matches = [(obj, match.group()) for match, obj, text in matches if match] if len(matches) == 0: # no matches at this length, keep previous iteration as best From 9b114b0bea7c0d26d66ebb4cb1821d470d9867ac Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:07:44 -0600 Subject: [PATCH 20/28] implement `get_posed_sdesc` --- evennia/contrib/rpg/rpsystem/rpsystem.py | 30 +++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index a14fc29d0a..2b0265d165 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -1232,6 +1232,10 @@ class ContribRPObject(DefaultObject): self.db.pose = "" self.db.pose_default = "is here." + # initializing sdesc + self.db._sdesc = "" + self.sdesc.add("Something") + def search( self, searchdata, @@ -1404,6 +1408,22 @@ class ContribRPObject(DefaultObject): multimatch_string=multimatch_string, ) + def get_posed_sdesc(self, sdesc, **kwargs): + """ + Displays the object with its current pose string. + + Returns: + pose (str): A string containing the object's sdesc and + current or default pose. + """ + + # get the current pose, or default if no pose is set + pose = self.db.pose or self.db.pose_default + + # return formatted string, or sdesc as fallback + return f"{sdesc} {pose}" if pose else sdesc + + def get_display_name(self, looker, **kwargs): """ Displays the name of the object in a viewer-aware manner. @@ -1430,7 +1450,6 @@ class ContribRPObject(DefaultObject): is privileged to control said object. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("noid",False) else "" ref = kwargs.get("ref","~") if looker == self: @@ -1443,8 +1462,13 @@ class ContribRPObject(DefaultObject): except AttributeError: # use own sdesc as a fallback sdesc = self.sdesc.get() - pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" - return "%s%s%s" % (sdesc, idstr, pose) + + # add dbref is looker has control access and `noid` is not set + if self.access(looker, access_type="control") and not kwargs.get("noid",False): + sdesc = f"{sdesc}{self.id}" + + return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc + def return_appearance(self, looker): """ From 865fc14ef82cae8ed476d6d22976f14b83ee392a Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:12:53 -0600 Subject: [PATCH 21/28] update character display name too --- evennia/contrib/rpg/rpsystem/rpsystem.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 2b0265d165..777e4507e1 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -1555,18 +1555,24 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): characters stand out from other objects. """ - idstr = "(#%s)" % self.id if self.access(looker, access_type="control") and not kwargs.get("noid",False) else "" ref = kwargs.get("ref","~") if looker == self: + # process your key as recog since you recognize yourself sdesc = self.process_recog(self.key,self) else: try: + # get the sdesc looker should see, with formatting sdesc = looker.get_sdesc(self, process=True, ref=ref) except AttributeError: + # use own sdesc as a fallback sdesc = self.sdesc.get() - pose = " %s" % (self.db.pose or "is here.") if kwargs.get("pose", False) else "" - return "%s%s%s" % (sdesc, idstr, pose) + + # add dbref is looker has control access and `noid` is not set + if self.access(looker, access_type="control") and not kwargs.get("noid",False): + sdesc = f"{sdesc}{self.id}" + + return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc def at_object_creation(self): From 344cde81e18d62324b843a43c2aa0d2f8fb7a484 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Tue, 26 Apr 2022 16:20:42 -0600 Subject: [PATCH 22/28] remove unnecessary declaration --- evennia/contrib/rpg/rpsystem/rpsystem.py | 1 - 1 file changed, 1 deletion(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 777e4507e1..1e311260aa 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -564,7 +564,6 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): # broadcast emote to everyone for receiver in receivers: # first handle the language mapping, which always produce different keys ##nn - receiver_lang_mapping = {} if hasattr(receiver, "process_language") and callable(receiver.process_language): receiver_lang_mapping = { key: receiver.process_language(saytext, sender, langname) From 8b612a9e9cba13ef9ad71972b00920521a70eaed Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 28 Apr 2022 13:58:05 -0600 Subject: [PATCH 23/28] first pass on switching to format/fstring --- evennia/contrib/rpg/rpsystem/rpsystem.py | 90 ++++++++++++------------ 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 1e311260aa..069f1196a4 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -282,9 +282,9 @@ def parse_language(speaker, emote): langname, saytext = say_match.groups() istart, iend = say_match.start(), say_match.end() # the key is simply the running match in the emote - key = "##%i" % imatch + key = f"##{imatch}" # replace say with ref markers in emote - emote = emote[:istart] + "{%s}" % key + emote[iend:] + emote = "{start}{key}{end}".format( start=emote[:istart], key=key, end=emote[iend:] ) mapping[key] = (langname, saytext) if errors: @@ -449,7 +449,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # case sensitive mode # internal flags for the case used for the original /query # - t for titled input (like /Name) - # - ^ for all upercase input (likle /NAME) + # - ^ for all upercase input (like /NAME) # - v for lower-case input (like /name) # - ~ for mixed case input (like /nAmE) matchtext = marker_match.group().lstrip(_PREFIX) @@ -469,13 +469,12 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # multimatch error refname = marker_match.group() reflist = [ - "%s%s%s (%s%s)" - % ( - inum + 1, - _NUM_SEP, - _RE_PREFIX.sub("", refname), - text, - " (%s)" % sender.key if sender == ob else "", + "{name}{sep}{num} ({text}{key})".format( + num=inum + 1, + sep=_NUM_SEP, + name=_RE_PREFIX.sub("", refname), + text=text, + key=f" ({sender.key})" if sender == ob else "", ) for inum, (ob, text) in enumerate(obj) ] @@ -539,7 +538,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): sender.msg(str(err)) return - skey = "#%i" % sender.id + skey = f"#{sender.id}" # we escape the object mappings since we'll do the language ones first # (the text could have nested object mappings). @@ -554,11 +553,12 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): if anonymous_add == "first": # add case flag for initial caps skey += 't' - possessive = "" if emote.startswith("'") else " " - emote = "%s%s%s" % ("{{%s}}" % skey, possessive, emote) + # don't put a space after the self-ref if it's a possessive emote + femote = "{key}{emote}" if emote.startswith("'") else "{key} {emote}" else: # add it to the end - emote = "%s [%s]" % (emote, "{{%s}}" % skey) + femote = "{emote} [{key}]" + emote = femote.format( key="{{"+ skey +"}}", emote=emote ) obj_mapping[skey] = sender # broadcast emote to everyone @@ -661,8 +661,7 @@ class SdescHandler: if len(cleaned_sdesc) > max_length: raise SdescError( - "Short desc can max be %i chars long (was %i chars)." - % (max_length, len(cleaned_sdesc)) + "Short desc can max be {} chars long (was {} chars).".format(max_length, len(cleaned_sdesc)) ) # store to attributes @@ -692,7 +691,6 @@ class RecogHandler: _recog_ref2recog _recog_obj2recog - _recog_obj2regex """ @@ -758,12 +756,11 @@ class RecogHandler: if len(cleaned_recog) > max_length: raise RecogError( - "Recog string cannot be longer than %i chars (was %i chars)" - % (max_length, len(cleaned_recog)) + "Recog string cannot be longer than {} chars (was {} chars)".format(max_length, len(cleaned_recog)) ) # mapping #dbref:obj - key = "#%i" % obj.id + key = f"#{obj.id}" self.obj.attributes.get("_recog_ref2recog", default={})[key] = recog self.obj.attributes.get("_recog_obj2recog", default={})[obj] = recog # local caching @@ -814,7 +811,7 @@ class RecogHandler: """ if obj in self.obj2recog: del self.obj.db._recog_obj2recog[obj] - del self.obj.db._recog_ref2recog["#%i" % obj.id] + del self.obj.db._recog_ref2recog[f"#{obj.id}"] self._cache() @@ -866,8 +863,8 @@ class CmdEmote(RPCommand): # replaces the main emote # we also include ourselves here. emote = self.args targets = self.caller.location.contents - if not emote.endswith((".", "?", "!")): # If emote is not punctuated, - emote = "%s." % emote # add a full-stop for good measure. + if not emote.endswith((".", "?", "!", '"')): # If emote is not punctuated or speech, + emote += "." # add a full-stop for good measure. send_emote(self.caller, targets, emote, anonymous_add="first") @@ -932,7 +929,7 @@ class CmdSdesc(RPCommand): # set/look at own sdesc except AttributeError: caller.msg(f"Cannot set sdesc on {caller.key}.") return - caller.msg("%s's sdesc was set to '%s'." % (caller.key, sdesc)) + caller.msg(f"{caller.key}'s sdesc was set to '{sdesc}'.") class CmdPose(RPCommand): # set current pose and default pose @@ -992,8 +989,8 @@ class CmdPose(RPCommand): # set current pose and default pose caller.msg("Usage: pose OR pose obj = ") return - if not pose.endswith("."): - pose = "%s." % pose + if not pose.endswith((".", "?", "!", '"')): + pose += "." if target: # affect something else target = caller.search(target) @@ -1005,18 +1002,18 @@ class CmdPose(RPCommand): # set current pose and default pose else: target = caller + target_name = target.sdesc.get() if hasattr(target, "sdesc") else target.key if not target.attributes.has("pose"): - caller.msg("%s cannot be posed." % target.key) + caller.msg(f"{target_name} cannot be posed.") return - target_name = target.sdesc.get() if hasattr(target, "sdesc") else target.key # set the pose if self.reset: pose = target.db.pose_default target.db.pose = pose elif self.default: target.db.pose_default = pose - caller.msg("Default pose is now '%s %s'." % (target_name, pose)) + caller.msg(f"Default pose is now '{target_name} {pose}'.") return else: # set the pose. We do one-time ref->sdesc mapping here. @@ -1028,12 +1025,12 @@ class CmdPose(RPCommand): # set current pose and default pose pose = parsed.format(**mapping) if len(target_name) + len(pose) > 60: - caller.msg("Your pose '%s' is too long." % pose) + caller.msg(f"'{pose}' is too long.") return target.db.pose = pose - caller.msg("Pose will read '%s %s'." % (target_name, pose)) + caller.msg(f"Pose will read '{target_name} {pose}'.") class CmdRecog(RPCommand): # assign personal alias to object in room @@ -1112,12 +1109,12 @@ class CmdRecog(RPCommand): # assign personal alias to object in room caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc)) elif nmatches > 1: reflist = [ - "{}{}{} ({}{})".format( - inum + 1, - _NUM_SEP, - _RE_PREFIX.sub("", sdesc), - caller.recog.get(obj), - " (%s)" % caller.key if caller == obj else "", + "{sdesc}{sep}{num} ({recog}{key})".format( + num=inum + 1, + sep=_NUM_SEP, + sdesc=_RE_PREFIX.sub("", sdesc), + recog=caller.recog.get(obj) or "no recog", + key=f" ({caller.key})" if caller == obj else "", ) for inum, obj in enumerate(matches) ] @@ -1133,7 +1130,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room if forget_mode: # remove existing recog caller.recog.remove(obj) - caller.msg("%s will now know them only as '%s'." % (caller.key, obj.get_display_name(caller, noid=True))) + caller.msg("You will now know them only as '{}'.".format( obj.get_display_name(caller, noid=True) )) else: # set recog sdesc = obj.sdesc.get() if hasattr(obj, "sdesc") else obj.key @@ -1142,7 +1139,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room except RecogError as err: caller.msg(err) return - caller.msg("%s will now remember |w%s|n as |w%s|n." % (caller.key, sdesc, alias)) + caller.msg("You will now remember |w{}|n as |w{}|n.".format(sdesc, alias)) class CmdMask(RPCommand): @@ -1173,14 +1170,14 @@ class CmdMask(RPCommand): caller.msg("You are already wearing a mask.") return sdesc = _RE_CHAREND.sub("", self.args) - sdesc = "%s |H[masked]|n" % sdesc + sdesc = f"{sdesc} |H[masked]|n" if len(sdesc) > 60: caller.msg("Your masked sdesc is too long.") return caller.db.unmasked_sdesc = caller.sdesc.get() caller.locks.add("enable_recog:false()") caller.sdesc.add(sdesc) - caller.msg("You wear a mask as '%s'." % sdesc) + caller.msg(f"You wear a mask as '{sdesc}'.") else: # unmask old_sdesc = caller.db.unmasked_sdesc @@ -1190,7 +1187,7 @@ class CmdMask(RPCommand): del caller.db.unmasked_sdesc caller.locks.remove("enable_recog") caller.sdesc.add(old_sdesc) - caller.msg("You remove your mask and are again '%s'." % old_sdesc) + caller.msg(f"You remove your mask and are again '{old_sdesc}'.") class RPSystemCmdSet(CmdSet): @@ -1672,7 +1669,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): sdesc = sdesc.upper() elif "v" in ref: sdesc = sdesc.lower() - return "|b%s|n" % sdesc + return f"|b{sdesc}|n" def process_recog(self, recog, obj, **kwargs): """ @@ -1691,7 +1688,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): if not recog: return "" - return "|m%s|n" % recog + return f"|m{recog}|n" def process_language(self, text, speaker, language, **kwargs): """ @@ -1714,4 +1711,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): the evennia.contrib.rpg.rplanguage module. """ - return "%s|w%s|n" % ("|W(%s)" % language if language else "", text) + return "{label}|w{text}|n".format( + label=f"|W({language})" if language else "", + text=text + ) From 369eab1963872f249e0620a6299b2a1e801c7235 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Thu, 28 Apr 2022 18:50:09 -0600 Subject: [PATCH 24/28] update docstring module paths --- evennia/contrib/rpg/rpsystem/rpsystem.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 069f1196a4..6520c6a9f1 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -53,7 +53,7 @@ Add `RPSystemCmdSet` from this module to your CharacterCmdSet: # ... -from evennia.contrib.rpg.rpsystem import RPSystemCmdSet <--- +from evennia.contrib.rpg.rpsystem.rpsystem import RPSystemCmdSet <--- class CharacterCmdSet(default_cmds.CharacterCmdset): # ... @@ -69,7 +69,7 @@ the typeclasses in this module: ```python # in mygame/typeclasses/characters.py -from evennia.contrib.rpg import ContribRPCharacter +from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPCharacter class Character(ContribRPCharacter): # ... @@ -79,7 +79,7 @@ class Character(ContribRPCharacter): ```python # in mygame/typeclasses/objects.py -from evennia.contrib.rpg import ContribRPObject +from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPObject class Object(ContribRPObject): # ... @@ -89,7 +89,7 @@ class Object(ContribRPObject): ```python # in mygame/typeclasses/rooms.py -from evennia.contrib.rpg import ContribRPRoom +from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPRoom class Room(ContribRPRoom): # ... @@ -125,7 +125,7 @@ Extra Installation Instructions: 1. In typeclasses/character.py: Import the `ContribRPCharacter` class: - `from evennia.contrib.rpg.rpsystem import ContribRPCharacter` + `from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPCharacter` Inherit ContribRPCharacter: Change "class Character(DefaultCharacter):" to `class Character(ContribRPCharacter):` @@ -133,13 +133,13 @@ Extra Installation Instructions: Add `super().at_object_creation()` as the top line. 2. In `typeclasses/rooms.py`: Import the `ContribRPRoom` class: - `from evennia.contrib.rpg.rpsystem import ContribRPRoom` + `from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPRoom` Inherit `ContribRPRoom`: Change `class Room(DefaultRoom):` to `class Room(ContribRPRoom):` 3. In `typeclasses/objects.py` Import the `ContribRPObject` class: - `from evennia.contrib.rpg.rpsystem import ContribRPObject` + `from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPObject` Inherit `ContribRPObject`: Change `class Object(DefaultObject):` to `class Object(ContribRPObject):` From b3aa869ac68c3ffa4bd20d52be92846ab05a7210 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:11:29 -0600 Subject: [PATCH 25/28] fixes --- evennia/contrib/rpg/rpsystem/rpsystem.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 6520c6a9f1..1296da6f62 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -284,7 +284,7 @@ def parse_language(speaker, emote): # the key is simply the running match in the emote key = f"##{imatch}" # replace say with ref markers in emote - emote = "{start}{key}{end}".format( start=emote[:istart], key=key, end=emote[iend:] ) + emote = "{start}{{{key}}}{end}".format( start=emote[:istart], key=key, end=emote[iend:] ) mapping[key] = (langname, saytext) if errors: @@ -408,6 +408,8 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # save current index as end point of matched text iend = i + # save search string + matched_text = "".join(tail[1:iend]) # recombine remainder of emote back into a string tail = "".join(tail[iend+1:]) @@ -469,7 +471,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # multimatch error refname = marker_match.group() reflist = [ - "{name}{sep}{num} ({text}{key})".format( + "{num}{sep}{name} ({text}{key})".format( num=inum + 1, sep=_NUM_SEP, name=_RE_PREFIX.sub("", refname), @@ -581,7 +583,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): receiver_sdesc_mapping = dict( ( ref, - obj.get_display_name(receiver, noid=True), + obj.get_display_name(receiver, ref=ref, noid=True), ) for ref, obj in obj_mapping.items() ) @@ -1109,7 +1111,7 @@ class CmdRecog(RPCommand): # assign personal alias to object in room caller.msg(_EMOTE_NOMATCH_ERROR.format(ref=sdesc)) elif nmatches > 1: reflist = [ - "{sdesc}{sep}{num} ({recog}{key})".format( + "{num}{sep}{sdesc} ({recog}{key})".format( num=inum + 1, sep=_NUM_SEP, sdesc=_RE_PREFIX.sub("", sdesc), @@ -1461,7 +1463,7 @@ class ContribRPObject(DefaultObject): # add dbref is looker has control access and `noid` is not set if self.access(looker, access_type="control") and not kwargs.get("noid",False): - sdesc = f"{sdesc}{self.id}" + sdesc = f"{sdesc}(#{self.id})" return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc From deb7d9f69a8025f632c55f73af76c56ffe84ef34 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:26:00 -0600 Subject: [PATCH 26/28] add test for `.get_sdesc` --- evennia/contrib/rpg/rpsystem/tests.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index 2c45e9a8a0..45f5a669a4 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -164,6 +164,24 @@ class TestRPSystem(BaseEvenniaTest): result, ) + def test_get_sdesc(self): + looker = self.speaker # Sender + target = self.receiver1 # Receiver1 + looker.sdesc.add(sdesc0) # A nice sender of emotes + target.sdesc.add(sdesc1) # The first receiver of emotes. + + # sdesc with no processing + self.assertEqual(looker.get_sdesc(target), "The first receiver of emotes.") + # sdesc with processing + self.assertEqual(looker.get_sdesc(target, process=True), "|bThe first receiver of emotes.|n") + + looker.recog.add(target, recog01) # Mr Receiver + + # recog with no processing + self.assertEqual(looker.get_sdesc(target), "Mr Receiver") + # recog with processing + self.assertEqual(looker.get_sdesc(target, process=True), "|mMr Receiver|n") + def test_send_emote(self): speaker = self.speaker receiver1 = self.receiver1 @@ -259,7 +277,7 @@ class TestRPSystemCommands(BaseEvenniaCommandTest): self.call( rpsystem.CmdRecog(), "barfoo as friend", - "Char will now remember BarFoo Character as friend.", + "You will now remember BarFoo Character as friend.", ) self.call( rpsystem.CmdRecog(), @@ -270,6 +288,6 @@ class TestRPSystemCommands(BaseEvenniaCommandTest): self.call( rpsystem.CmdRecog(), "friend", - "Char will now know them only as 'BarFoo Character'", + "You will now know them only as 'BarFoo Character'", cmdstring="forget", ) From a76ba1d92c24c14c800e8300177a67cbca3fc6cf Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:33:27 -0600 Subject: [PATCH 27/28] add /Me vs /me to case sensitive test --- evennia/contrib/rpg/rpsystem/tests.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index 45f5a669a4..f0d040d6f7 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -96,7 +96,7 @@ recog01 = "Mr Receiver" recog02 = "Mr Receiver2" recog10 = "Mr Sender" emote = 'With a flair, /me looks at /first and /colliding sdesc-guy. She says "This is a test."' -case_emote = "/Me looks at /first, then /FIRST, /First and /Colliding twice." +case_emote = "/Me looks at /first. Then, /me looks at /FIRST, /First and /Colliding twice." class TestRPSystem(BaseEvenniaTest): @@ -225,20 +225,21 @@ class TestRPSystem(BaseEvenniaTest): rpsystem.send_emote(speaker, receivers, case_emote) self.assertEqual( self.out0, - "|mSender|n looks at |bthe first receiver of emotes.|n, then " - "|bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n and " - "|bAnother nice colliding sdesc-guy for tests|n twice.", + "|mSender|n looks at |bthe first receiver of emotes.|n. Then, |mSender|n " + "looks at |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of emotes.|n " + "and |bAnother nice colliding sdesc-guy for tests|n twice.", ) self.assertEqual( self.out1, - "|bA nice sender of emotes|n looks at |mReceiver1|n, then |mReceiver1|n, " - "|mReceiver1|n and |bAnother nice colliding sdesc-guy for tests|n twice.", + "|bA nice sender of emotes|n looks at |mReceiver1|n. Then, " + "|ba nice sender of emotes|n looks at |mReceiver1|n, |mReceiver1|n " + "and |bAnother nice colliding sdesc-guy for tests|n twice." ) self.assertEqual( self.out2, - "|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n, " - "then |bTHE FIRST RECEIVER OF EMOTES.|n, |bThe first receiver of " - "emotes.|n and |mReceiver2|n twice.", + "|bA nice sender of emotes|n looks at |bthe first receiver of emotes.|n. " + "Then, |ba nice sender of emotes|n looks at |bTHE FIRST RECEIVER OF EMOTES.|n, " + "|bThe first receiver of emotes.|n and |mReceiver2|n twice.", ) def test_rpsearch(self): From bbee58c8541353fd3c06a44447874450432e5320 Mon Sep 17 00:00:00 2001 From: InspectorCaracal <51038201+InspectorCaracal@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:43:17 -0600 Subject: [PATCH 28/28] replace % subs on regex strings --- evennia/contrib/rpg/rpsystem/rpsystem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index 1296da6f62..c191d72d93 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -186,13 +186,13 @@ _EMOTE_MULTIMATCH_ERROR = """|RMultiple possibilities for {ref}: _RE_FLAGS = re.MULTILINE + re.IGNORECASE + re.UNICODE -_RE_PREFIX = re.compile(r"^%s" % _PREFIX, re.UNICODE) +_RE_PREFIX = re.compile(rf"^{_PREFIX}", re.UNICODE) # This regex will return groups (num, word), where num is an optional counter to # separate multimatches from one another and word is the first word in the # marker. So entering "/tall man" will return groups ("", "tall") # and "/2-tall man" will return groups ("2", "tall"). -_RE_OBJ_REF_START = re.compile(r"%s(?:([0-9]+)%s)*(\w+)" % (_PREFIX, _NUM_SEP), _RE_FLAGS) +_RE_OBJ_REF_START = re.compile(rf"{_PREFIX}(?:([0-9]+){_NUM_SEP})*(\w+)", _RE_FLAGS) _RE_LEFT_BRACKETS = re.compile(r"\{+", _RE_FLAGS) _RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS) @@ -350,7 +350,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ candidate_map.append((obj, obj.sdesc.get())) # if no sdesc, include key plus aliases instead else: - candidate_map += [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] + candidate_map.extend( [(obj, obj.key)] + [(obj, alias) for alias in obj.aliases.all()] ) # escape mapping syntax on the form {#id} if it exists already in emote, # if so it is replaced with just "id". @@ -1568,7 +1568,7 @@ class ContribRPCharacter(DefaultCharacter, ContribRPObject): # add dbref is looker has control access and `noid` is not set if self.access(looker, access_type="control") and not kwargs.get("noid",False): - sdesc = f"{sdesc}{self.id}" + sdesc = f"{sdesc}(#{self.id})" return self.get_posed_sdesc(sdesc) if kwargs.get("pose", False) else sdesc