diff --git a/CHANGELOG.md b/CHANGELOG.md index 6feaed180f..e4d221010d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -174,6 +174,10 @@ Up requirements to Django 4.0+, Twisted 22+, Python 3.9 or 3.10 - Simplified `EvMenu.options_formatter` hook to use `EvColumn` and f-strings (inspectorcaracal) - Allow `# CODE`, `# HEADER` etc as well as `#CODE`/`#HEADER` in batchcode files - this works better with black linting. +- Added `move_type` str kwarg to `move_to()` calls, optionally identifying the type of + move being done ('teleport', 'disembark', 'give' etc). (volund) +- Made RPSystem contrib msg calls pass `pose` or `say` as msg-`type` for use in + e.g. webclient pane filtering where desired. (volund) ## Evennia 0.9.5 diff --git a/docs/source/Howtos/NPC-shop-Tutorial.md b/docs/source/Howtos/NPC-shop-Tutorial.md index db37508f95..209f2d17f3 100644 --- a/docs/source/Howtos/NPC-shop-Tutorial.md +++ b/docs/source/Howtos/NPC-shop-Tutorial.md @@ -130,7 +130,7 @@ def menunode_inspect_and_buy(caller, raw_string): if wealth >= value: rtext = f"You pay {value} gold and purchase {ware.key}!" caller.db.gold -= value - ware.move_to(caller, quiet=True) + ware.move_to(caller, quiet=True, move_type="buy") else: rtext = f"You cannot afford {value} gold for {ware.key}!" caller.msg(rtext) diff --git a/docs/source/Howtos/Tutorial-Vehicles.md b/docs/source/Howtos/Tutorial-Vehicles.md index a62aa485f3..acb074de6a 100644 --- a/docs/source/Howtos/Tutorial-Vehicles.md +++ b/docs/source/Howtos/Tutorial-Vehicles.md @@ -86,7 +86,7 @@ class CmdEnterTrain(Command): def func(self): train = self.obj self.caller.msg("You board the train.") - self.caller.move_to(train) + self.caller.move_to(train, move_type="board") class CmdLeaveTrain(Command): @@ -107,7 +107,7 @@ class CmdLeaveTrain(Command): def func(self): train = self.obj parent = train.location - self.caller.move_to(parent) + self.caller.move_to(parent, move_type="disembark") class CmdSetTrain(CmdSet): diff --git a/evennia/commands/default/building.py b/evennia/commands/default/building.py index c81c320b3e..feac9de984 100644 --- a/evennia/commands/default/building.py +++ b/evennia/commands/default/building.py @@ -603,7 +603,7 @@ class CmdCreate(ObjManipCommand): if "drop" in self.switches: if caller.location: obj.home = caller.location - obj.move_to(caller.location, quiet=True) + obj.move_to(caller.location, quiet=True, move_type="drop") if string: caller.msg(string) @@ -993,7 +993,7 @@ class CmdDig(ObjManipCommand): ) caller.msg("%s%s%s" % (room_string, exit_to_string, exit_back_string)) if new_room and "teleport" in self.switches: - caller.move_to(new_room) + caller.move_to(new_room, move_type="teleport") class CmdTunnel(COMMAND_DEFAULT_CLASS): @@ -3709,6 +3709,7 @@ class CmdTeleport(COMMAND_DEFAULT_CLASS): quiet="quiet" in self.switches, emit_to_obj=caller, use_destination="intoexit" not in self.switches, + move_type="teleport" ): if obj_to_teleport == caller: diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index ca66727d94..98ad671e7a 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -49,7 +49,7 @@ class CmdHome(COMMAND_DEFAULT_CLASS): caller.msg("You are already home!") else: caller.msg("There's no place like home ...") - caller.move_to(home) + caller.move_to(home, move_type="teleport") class CmdLook(COMMAND_DEFAULT_CLASS): @@ -434,7 +434,7 @@ class CmdGet(COMMAND_DEFAULT_CLASS): if not obj.at_pre_get(caller): return - success = obj.move_to(caller, quiet=True) + success = obj.move_to(caller, quiet=True, move_type="get") if not success: caller.msg("This can't be picked up.") else: @@ -484,7 +484,7 @@ class CmdDrop(COMMAND_DEFAULT_CLASS): if not obj.at_pre_drop(caller): return - success = obj.move_to(caller.location, quiet=True) + success = obj.move_to(caller.location, quiet=True, move_type="drop") if not success: caller.msg("This couldn't be dropped.") else: @@ -538,7 +538,7 @@ class CmdGive(COMMAND_DEFAULT_CLASS): return # give object - success = to_give.move_to(target, quiet=True) + success = to_give.move_to(target, quiet=True, move_type="get") if not success: caller.msg("This could not be given.") else: diff --git a/evennia/contrib/base_systems/ingame_python/typeclasses.py b/evennia/contrib/base_systems/ingame_python/typeclasses.py index de70631218..804b4c4a93 100644 --- a/evennia/contrib/base_systems/ingame_python/typeclasses.py +++ b/evennia/contrib/base_systems/ingame_python/typeclasses.py @@ -189,7 +189,7 @@ class EventCharacter(DefaultCharacter): """Return the CallbackHandler.""" return CallbackHandler(self) - def announce_move_from(self, destination, msg=None, mapping=None): + def announce_move_from(self, destination, msg=None, move_type="move", mapping=None, **kwargs): """ Called if the move is to be announced. This is called while we are still standing in the old @@ -234,9 +234,9 @@ class EventCharacter(DefaultCharacter): if not string: return - super().announce_move_from(destination, msg=string, mapping=mapping) + super().announce_move_from(destination, msg=string, move_type=move_type, mapping=mapping, **kwargs) - def announce_move_to(self, source_location, msg=None, mapping=None): + def announce_move_to(self, source_location, msg=None, move_type="move", mapping=None, **kwargs): """ Called after the move if the move was not quiet. At this point we are standing in the new location. @@ -292,9 +292,9 @@ class EventCharacter(DefaultCharacter): if not string: return - super().announce_move_to(source_location, msg=string, mapping=mapping) + super().announce_move_to(source_location, msg=string, move_type=move_type, mapping=mapping, **kwargs) - def at_pre_move(self, destination): + def at_pre_move(self, destination, move_type="move", **kwargs): """ Called just before starting to move this object to destination. @@ -334,7 +334,7 @@ class EventCharacter(DefaultCharacter): return True - def at_post_move(self, source_location): + def at_post_move(self, source_location, move_type="move", **kwargs): """ Called after move has completed, regardless of quiet mode or not. Allows changes to the object due to the location it is @@ -644,7 +644,7 @@ class EventExit(DefaultExit): """Return the CallbackHandler.""" return CallbackHandler(self) - def at_traverse(self, traversing_object, target_location): + def at_traverse(self, traversing_object, target_location, **kwargs): """ This hook is responsible for handling the actual traversal, normally by calling @@ -665,7 +665,7 @@ class EventExit(DefaultExit): if not allow: return - super().at_traverse(traversing_object, target_location) + super().at_traverse(traversing_object, target_location, **kwargs) # After traversing if is_character: @@ -732,7 +732,7 @@ class EventObject(DefaultObject): """Return the CallbackHandler.""" return CallbackHandler(self) - def at_get(self, getter): + def at_get(self, getter, **kwargs): """ Called by the default `get` command when this object has been picked up. @@ -745,10 +745,10 @@ class EventObject(DefaultObject): permissions for that. """ - super().at_get(getter) + super().at_get(getter, **kwargs) self.callbacks.call("get", getter, self) - def at_drop(self, dropper): + def at_drop(self, dropper, **kwargs): """ Called by the default `drop` command when this object has been dropped. @@ -761,7 +761,7 @@ class EventObject(DefaultObject): permissions from that. """ - super().at_drop(dropper) + super().at_drop(dropper, **kwargs) self.callbacks.call("drop", dropper, self) diff --git a/evennia/contrib/full_systems/evscaperoom/commands.py b/evennia/contrib/full_systems/evscaperoom/commands.py index 15695e7caa..16a5706ecf 100644 --- a/evennia/contrib/full_systems/evscaperoom/commands.py +++ b/evennia/contrib/full_systems/evscaperoom/commands.py @@ -236,7 +236,7 @@ class CmdGiveUp(CmdEvscapeRoom): # manually call move hooks self.room.msg_room(self.caller, f"|r{self.caller.key} gave up and was whisked away!|n") self.room.at_object_leave(self.caller, self.caller.home) - self.caller.move_to(self.caller.home, quiet=True, move_hooks=False) + self.caller.move_to(self.caller.home, quiet=True, move_hooks=False, move_type="teleport") # back to menu run_evscaperoom_menu(self.caller) diff --git a/evennia/contrib/full_systems/evscaperoom/room.py b/evennia/contrib/full_systems/evscaperoom/room.py index 9b4a8205e8..934f363ecc 100644 --- a/evennia/contrib/full_systems/evscaperoom/room.py +++ b/evennia/contrib/full_systems/evscaperoom/room.py @@ -185,7 +185,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom): # Evennia hooks - def at_object_receive(self, moved_obj, source_location): + def at_object_receive(self, moved_obj, source_location, move_type="move", **kwargs): """ Called when an object arrives in the room. This can be used to sum up the situation, set tags etc. @@ -195,7 +195,7 @@ class EvscapeRoom(EvscaperoomObject, DefaultRoom): self.log(f"JOIN: {moved_obj} joined room") self.state.character_enters(moved_obj) - def at_object_leave(self, moved_obj, target_location, **kwargs): + def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs): """ Called when an object leaves the room; if this is a Character we need to clean them up and move them to the menu state. diff --git a/evennia/contrib/game_systems/clothing/clothing.py b/evennia/contrib/game_systems/clothing/clothing.py index 04554f7580..8c11f27d4e 100644 --- a/evennia/contrib/game_systems/clothing/clothing.py +++ b/evennia/contrib/game_systems/clothing/clothing.py @@ -611,7 +611,7 @@ class CmdDrop(MuxCommand): if obj.db.worn: obj.remove(caller, quiet=True) - obj.move_to(caller.location, quiet=True) + obj.move_to(caller.location, quiet=True, move_type="drop") caller.msg("You drop %s." % (obj.name,)) caller.location.msg_contents("%s drops %s." % (caller.name, obj.name), exclude=caller) # Call the object script's at_drop() method. @@ -664,10 +664,10 @@ class CmdGive(MuxCommand): # Remove clothes if they're given. if to_give.db.worn: to_give.remove(caller) - to_give.move_to(caller.location, quiet=True) + to_give.move_to(caller.location, quiet=True, move_type="remove") # give object caller.msg("You give %s to %s." % (to_give.key, target.key)) - to_give.move_to(target, quiet=True) + to_give.move_to(target, quiet=True, move_type="give") target.msg("%s gives you %s." % (caller.key, to_give.key)) # Call the object script's at_give() method. to_give.at_give(caller, target) diff --git a/evennia/contrib/grid/slow_exit/slow_exit.py b/evennia/contrib/grid/slow_exit/slow_exit.py index 2b4ff0f003..e5050e3f76 100644 --- a/evennia/contrib/grid/slow_exit/slow_exit.py +++ b/evennia/contrib/grid/slow_exit/slow_exit.py @@ -82,7 +82,7 @@ class SlowExit(DefaultExit): def move_callback(): "This callback will be called by utils.delay after move_delay seconds." source_location = traversing_object.location - if traversing_object.move_to(target_location): + if traversing_object.move_to(target_location, move_type="traverse"): self.at_post_traverse(traversing_object, source_location) else: if self.db.err_traverse: diff --git a/evennia/contrib/grid/wilderness/wilderness.py b/evennia/contrib/grid/wilderness/wilderness.py index 7ad043240d..418ad4253d 100644 --- a/evennia/contrib/grid/wilderness/wilderness.py +++ b/evennia/contrib/grid/wilderness/wilderness.py @@ -538,7 +538,7 @@ class WildernessRoom(DefaultRoom): # This object wasn't in the wilderness yet. Let's add it. itemcoords[moved_obj] = self.coordinates - def at_object_leave(self, moved_obj, target_location): + def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs): """ Called just before an object leaves from inside this object. This is a default Evennia hook. diff --git a/evennia/contrib/rpg/rpsystem/rpsystem.py b/evennia/contrib/rpg/rpsystem/rpsystem.py index f50ad88326..ea884f6365 100644 --- a/evennia/contrib/rpg/rpsystem/rpsystem.py +++ b/evennia/contrib/rpg/rpsystem/rpsystem.py @@ -202,7 +202,7 @@ _RE_RIGHT_BRACKETS = re.compile(r"\}+", _RE_FLAGS) _RE_REF = re.compile(r"\{+\#([0-9]+[\^\~tv]{0,1})\}+") # This regex is used to quickly reference one self in an emote. -_RE_SELF_REF = re.compile(r"/me|@", _RE_FLAGS) +_RE_SELF_REF = re.compile(r"(/me|@)(?=\W+)", _RE_FLAGS) # regex for non-alphanumberic end of a string _RE_CHAREND = re.compile(r"\W+$", _RE_FLAGS) @@ -213,6 +213,7 @@ _RE_REF_LANG = re.compile(r"\{+\##([0-9]+)\}+") # this regex returns in groups (langname, say), where langname can be empty. _RE_LANGUAGE = re.compile(r"(?:\((\w+)\))*(\".+?\")") + # the emote parser works in two steps: # 1) convert the incoming emote into an intermediary # form with all object references mapped to ids. @@ -235,6 +236,26 @@ class RecogError(Exception): class LanguageError(Exception): pass +def _get_case_ref(string): + """ + Helper function which parses capitalization and + returns the appropriate case-ref character for emotes. + """ + # default to retaining the original case + case = "~" + # internal flags for the case used for the original /query + # - t for titled input (like /Name) + # - ^ for all upercase input (like /NAME) + # - v for lower-case input (like /name) + # - ~ for mixed case input (like /nAmE) + if string.istitle(): + case = "t" + elif string.isupper(): + case = "^" + elif string.islower(): + case = "v" + + return case # emoting mechanisms def parse_language(speaker, emote): @@ -339,7 +360,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ """ # build a list of candidates with all possible referrable names # include 'me' keyword for self-ref - candidate_map = [(sender, "me")] + candidate_map = [] for obj in candidates: # check if sender has any recogs for obj and add if hasattr(sender, "recog"): @@ -365,6 +386,15 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ errors = [] obj = None nmatches = 0 + # first, find and replace any self-refs + for self_match in list(_RE_SELF_REF.finditer(string)): + matched = self_match.group() + case = _get_case_ref(matched.lstrip(_PREFIX)) if case_sensitive else "" + key = f"#{sender.id}{case}" + # replaced with ref + string = _RE_SELF_REF.sub(f"{{{key}}}", string, count=1) + mapping[key] = sender + for marker_match in reversed(list(_RE_OBJ_REF_START.finditer(string))): # we scan backwards so we can replace in-situ without messing # up later occurrences. Given a marker match, query from @@ -375,7 +405,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ 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 :] + tail = string[match_index + 1:] if search_mode: # match the candidates against the whole search string after the marker @@ -421,7 +451,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ # save search string matched_text = "".join(tail[1:iend]) # recombine remainder of emote back into a string - tail = "".join(tail[iend + 1 :]) + tail = "".join(tail[iend + 1:]) nmatches = len(bestmatches) @@ -456,24 +486,9 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ errors.append(_EMOTE_NOMATCH_ERROR.format(ref=marker_match.group())) elif nmatches == 1: # a unique match - parse into intermediary representation - case = "~" # retain original case of sdesc - if case_sensitive: - # case sensitive mode - # internal flags for the case used for the original /query - # - t for titled input (like /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) - if matchtext.istitle(): - case = "t" - elif matchtext.isupper(): - case = "^" - elif matchtext.islower(): - case = "v" - - key = f"#{obj.id}{case}" + case = _get_case_ref(marker_match.group()) if case_sensitive else "" # recombine emote with matched text replaced by ref + key = f"#{obj.id}{case}" string = f"{head}{{{key}}}{tail}" mapping[key] = obj @@ -513,7 +528,7 @@ def parse_sdescs_and_recogs(sender, candidates, string, search_mode=False, case_ return string, mapping -def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): +def send_emote(sender, receivers, emote, msg_type="pose", anonymous_add="first", **kwargs): """ Main access function for distribute an emote. @@ -523,6 +538,9 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): will also form the basis for which sdescs are 'valid' to use in the emote. emote (str): The raw emote string as input by emoter. + msg_type (str): The type of emote this is. "say" or "pose" + for example. This is arbitrary and used for generating + extra data for .msg(text) tuple. anonymous_add (str or None, optional): If `sender` is not self-referencing in the emote, this will auto-add `sender`'s data to the emote. Possible values are @@ -599,7 +617,7 @@ def send_emote(sender, receivers, emote, anonymous_add="first", **kwargs): ) # do the template replacement of the sdesc/recog {#num} markers - receiver.msg(sendemote.format(**receiver_sdesc_mapping), from_obj=sender, **kwargs) + receiver.msg(text=(sendemote.format(**receiver_sdesc_mapping), {"type": msg_type}), from_obj=sender, **kwargs) # ------------------------------------------------------------ @@ -910,7 +928,7 @@ class CmdSay(RPCommand): # replaces standard say # calling the speech modifying hook speech = caller.at_pre_say(self.args) targets = self.caller.location.contents - send_emote(self.caller, targets, speech, anonymous_add=None) + send_emote(self.caller, targets, speech, msg_type="say", anonymous_add=None) class CmdSdesc(RPCommand): # set/look at own sdesc @@ -1253,19 +1271,19 @@ class ContribRPObject(DefaultObject): self.sdesc.add("Something") def search( - self, - searchdata, - global_search=False, - use_nicks=True, - typeclass=None, - location=None, - attribute_name=None, - quiet=False, - exact=False, - candidates=None, - nofound_string=None, - multimatch_string=None, - use_dbref=None, + self, + searchdata, + global_search=False, + use_nicks=True, + typeclass=None, + location=None, + attribute_name=None, + quiet=False, + exact=False, + candidates=None, + nofound_string=None, + multimatch_string=None, + use_dbref=None, ): """ Returns an Object matching a search string/condition, taking @@ -1349,10 +1367,10 @@ class ContribRPObject(DefaultObject): ) if global_search or ( - is_string - and searchdata.startswith("#") - and len(searchdata) > 1 - and searchdata[1:].isdigit() + is_string + and searchdata.startswith("#") + and len(searchdata) > 1 + and searchdata[1:].isdigit() ): # only allow exact matching if searching the entire database # or unique #dbrefs diff --git a/evennia/contrib/rpg/rpsystem/tests.py b/evennia/contrib/rpg/rpsystem/tests.py index bd2ea32896..a46a9509c8 100644 --- a/evennia/contrib/rpg/rpsystem/tests.py +++ b/evennia/contrib/rpg/rpsystem/tests.py @@ -97,6 +97,7 @@ 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, /me looks at /FIRST, /First and /Colliding twice." +poss_emote = "/Me frowns at /first for trying to steal /me's test." class TestRPSystem(BaseEvenniaTest): @@ -140,18 +141,21 @@ class TestRPSystem(BaseEvenniaTest): ), ) - def parse_sdescs_and_recogs(self): + def test_parse_sdescs_and_recogs(self): speaker = self.speaker speaker.sdesc.add(sdesc0) self.receiver1.sdesc.add(sdesc1) self.receiver2.sdesc.add(sdesc2) + id0 = f"#{speaker.id}" + id1 = f"#{self.receiver1.id}" + id2 = f"#{self.receiver2.id}" candidates = (self.receiver1, self.receiver2) result = ( - 'With a flair, {#9} looks at {#10} and {#11}. She says "This is a test."', + 'With a flair, {'+id0+'} looks at {'+id1+'} and {'+id2+'}. She says "This is a test."', { - "#11": "Another nice colliding sdesc-guy for tests", - "#10": "The first receiver of emotes.", - "#9": "A nice sender of emotes", + id2: self.receiver2, + id1: self.receiver1, + id0: speaker, }, ) self.assertEqual( @@ -164,6 +168,27 @@ class TestRPSystem(BaseEvenniaTest): result, ) + def test_possessive_selfref(self): + speaker = self.speaker + speaker.sdesc.add(sdesc0) + self.receiver1.sdesc.add(sdesc1) + self.receiver2.sdesc.add(sdesc2) + id0 = f"#{speaker.id}" + id1 = f"#{self.receiver1.id}" + id2 = f"#{self.receiver2.id}" + candidates = (self.receiver1, self.receiver2) + result = ( + "{"+id0+"} frowns at {"+id1+"} for trying to steal {"+id0+"}'s test.", + { + id1: self.receiver1, + id0: speaker, + }, + ) + self.assertEqual( + rpsystem.parse_sdescs_and_recogs(speaker, candidates, poss_emote, case_sensitive=False), + result, + ) + def test_get_sdesc(self): looker = self.speaker # Sender target = self.receiver1 # Receiver1 @@ -197,17 +222,17 @@ class TestRPSystem(BaseEvenniaTest): receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text) rpsystem.send_emote(speaker, receivers, emote, case_sensitive=False) self.assertEqual( - self.out0, + self.out0[0], "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, + self.out1[0], "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, + self.out2[0], "With a flair, |bA nice sender of emotes|n looks at |bThe first " 'receiver of emotes.|n and |mReceiver2|n. She says |w"This is a test."|n', ) @@ -226,19 +251,19 @@ class TestRPSystem(BaseEvenniaTest): receiver2.msg = lambda text, **kwargs: setattr(self, "out2", text) rpsystem.send_emote(speaker, receivers, case_emote) self.assertEqual( - self.out0, + self.out0[0], "|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, + self.out1[0], "|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, + self.out2[0], "|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.", diff --git a/evennia/contrib/tutorials/tutorial_world/rooms.py b/evennia/contrib/tutorials/tutorial_world/rooms.py index 8f23363134..54a46c3fea 100644 --- a/evennia/contrib/tutorials/tutorial_world/rooms.py +++ b/evennia/contrib/tutorials/tutorial_world/rooms.py @@ -225,7 +225,7 @@ class CmdTutorialGiveUp(default_cmds.MuxCommand): ) return - self.caller.move_to(outro_room) + self.caller.move_to(outro_room, move_type="teleport") class TutorialRoomCmdSet(CmdSet): @@ -259,7 +259,7 @@ class TutorialRoom(DefaultRoom): ) self.cmdset.add_default(TutorialRoomCmdSet) - def at_object_receive(self, new_arrival, source_location): + def at_object_receive(self, new_arrival, source_location, move_type="move", **kwargs): """ When an object enter a tutorial room we tell other objects in the room about it by trying to call a hook on them. The Mob object @@ -451,7 +451,7 @@ class IntroRoom(TutorialRoom): "the account." ) - def at_object_receive(self, character, source_location): + def at_object_receive(self, character, source_location, move_type="move", **kwargs): """ Assign properties on characters """ @@ -523,7 +523,7 @@ class CmdEast(Command): # Move to the east room. eexit = search_object(self.obj.db.east_exit) if eexit: - caller.move_to(eexit[0]) + caller.move_to(eexit[0], move_type="traverse") else: caller.msg("No east exit was found for this room. Contact an admin.") return @@ -570,7 +570,7 @@ class CmdWest(Command): # Move to the west room. wexit = search_object(self.obj.db.west_exit) if wexit: - caller.move_to(wexit[0]) + caller.move_to(wexit[0], move_type="traverse") else: caller.msg("No west exit was found for this room. Contact an admin.") return @@ -658,7 +658,7 @@ class CmdLookBridge(Command): fall_exit = search_object(self.obj.db.fall_exit) if fall_exit: self.caller.msg("|r%s|n" % FALL_MESSAGE) - self.caller.move_to(fall_exit[0], quiet=True) + self.caller.move_to(fall_exit[0], quiet=True, move_type="fall") # inform others on the bridge self.obj.msg_contents( "A plank gives way under %s's feet and " @@ -770,7 +770,7 @@ class BridgeRoom(WeatherRoom): # send a message most of the time self.msg_contents("|w%s|n" % random.choice(BRIDGE_WEATHER)) - def at_object_receive(self, character, source_location): + def at_object_receive(self, character, source_location, move_type="move", **kwargs): """ This hook is called by the engine whenever the player is moved into this room. @@ -796,7 +796,7 @@ class BridgeRoom(WeatherRoom): character.db.tutorial_bridge_position = 0 character.execute_cmd("look") - def at_object_leave(self, character, target_location): + def at_object_leave(self, character, target_location, move_type="move", **kwargs): """ This is triggered when the player leaves the bridge room. """ @@ -1038,7 +1038,7 @@ class DarkRoom(TutorialRoom): # put players in darkness char.msg("The room is completely dark.") - def at_object_receive(self, obj, source_location): + def at_object_receive(self, obj, source_location, move_type="move", **kwargs): """ Called when an object enters the room. """ @@ -1048,7 +1048,7 @@ class DarkRoom(TutorialRoom): # in case the new guy carries light with them self.check_light_state() - def at_object_leave(self, obj, target_location): + def at_object_leave(self, obj, target_location, move_type="move", **kwargs): """ In case people leave with the light, we make sure to clear the DarkCmdSet if necessary. This also works if they are @@ -1103,7 +1103,7 @@ class TeleportRoom(TutorialRoom): self.db.failure_teleport_msg = "You fail!" self.db.failure_teleport_to = "dark cell" - def at_object_receive(self, character, source_location): + def at_object_receive(self, character, source_location, move_type="move", **kwargs): """ This hook is called by the engine whenever the player is moved into this room. @@ -1130,7 +1130,7 @@ class TeleportRoom(TutorialRoom): else: character.msg(self.db.failure_teleport_msg) # teleport quietly to the new place - character.move_to(results[0], quiet=True, move_hooks=False) + character.move_to(results[0], quiet=True, move_hooks=False, move_type="teleport") # we have to call this manually since we turn off move_hooks # - this is necessary to make the target dark room aware of an # already carried light. @@ -1167,7 +1167,7 @@ class OutroRoom(TutorialRoom): "character." ) - def at_object_receive(self, character, source_location): + def at_object_receive(self, character, source_location, move_type="move", **kwargs): """ Do cleanup. """ @@ -1183,6 +1183,6 @@ class OutroRoom(TutorialRoom): obj.delete() character.tags.clear(category="tutorial_world") - def at_object_leave(self, character, destination): + def at_object_leave(self, character, destination, move_type="move", **kwargs): if character.account: character.account.execute_cmd("unquell") diff --git a/evennia/contrib/tutorials/tutorial_world/tests.py b/evennia/contrib/tutorials/tutorial_world/tests.py index 07f4391a72..8a0b722cf9 100644 --- a/evennia/contrib/tutorials/tutorial_world/tests.py +++ b/evennia/contrib/tutorials/tutorial_world/tests.py @@ -160,7 +160,7 @@ class TestTutorialWorldRooms(BaseEvenniaCommandTest): def test_bridgeroom(self): room = create_object(tutrooms.BridgeRoom, key="bridgeroom") room.update_weather() - self.char1.move_to(room) + self.char1.move_to(room, move_type="teleport") self.call( tutrooms.CmdBridgeHelp(), "", @@ -181,7 +181,7 @@ class TestTutorialWorldRooms(BaseEvenniaCommandTest): def test_darkroom(self): room = create_object(tutrooms.DarkRoom, key="darkroom") - self.char1.move_to(room) + self.char1.move_to(room, move_type="teleport") self.call(tutrooms.CmdDarkHelp(), "", "Can't help you until") def test_teleportroom(self): diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index 21853e42e0..edc36ed912 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -847,6 +847,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): use_destination=True, to_none=False, move_hooks=True, + move_type="move", **kwargs, ): """ @@ -868,6 +869,11 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): move_hooks (bool): If False, turn off the calling of move-related hooks (at_pre/post_move etc) with quiet=True, this is as quiet a move as can be done. + move_type (str): The "kind of move" being performed, such as "teleport", "traverse", + "get", "give", or "drop". The value can be arbitrary. By default, it only affects + the text message generated by announce_move_to and announce_move_from by defining + their {"type": move_type} for outgoing text. This can be used for altering + messages and/or overloaded hook behaviors. Keyword Args: Passed on to announce_move_to and announce_move_from hooks. @@ -924,7 +930,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if move_hooks: # check if we are okay to move try: - if not self.at_pre_move(destination, **kwargs): + if not self.at_pre_move(destination, move_type=move_type, **kwargs): return False except Exception as err: logerr(errtxt.format(err="at_pre_move()"), err) @@ -947,7 +953,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # Call hook on source location if move_hooks and source_location: try: - source_location.at_object_leave(self, destination, **kwargs) + source_location.at_object_leave(self, destination, move_type=move_type, **kwargs) except Exception as err: logerr(errtxt.format(err="at_object_leave()"), err) return False @@ -955,7 +961,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if not quiet: # tell the old room we are leaving try: - self.announce_move_from(destination, **kwargs) + self.announce_move_from(destination, move_type=move_type, **kwargs) except Exception as err: logerr(errtxt.format(err="announce_move_from()"), err) return False @@ -970,7 +976,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): if not quiet: # Tell the new room we are there. try: - self.announce_move_to(source_location, **kwargs) + self.announce_move_to(source_location, move_type=move_type, **kwargs) except Exception as err: logerr(errtxt.format(err="announce_move_to()"), err) return False @@ -979,7 +985,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # Perform eventual extra commands on the receiving location # (the object has already arrived at this point) try: - destination.at_object_receive(self, source_location, **kwargs) + destination.at_object_receive(self, source_location, move_type=move_type, **kwargs) except Exception as err: logerr(errtxt.format(err="at_object_receive()"), err) return False @@ -988,7 +994,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # (usually calling 'look') if move_hooks: try: - self.at_post_move(source_location, **kwargs) + self.at_post_move(source_location, move_type=move_type, **kwargs) except Exception as err: logerr(errtxt.format(err="at_post_move"), err) return False @@ -1049,7 +1055,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # Famous last words: The account should never see this. string = "This place should not exist ... contact an admin." obj.msg(_(string)) - obj.move_to(home) + obj.move_to(home, move_type="teleport") @classmethod def create(cls, key, account=None, **kwargs): @@ -1501,13 +1507,17 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # hooks called when moving the object - def at_pre_move(self, destination, **kwargs): + def at_pre_move(self, destination, move_type="move", **kwargs): """ Called just before starting to move this object to destination. Return False to abort move. Args: destination (Object): The object we are moving to + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -1565,7 +1575,7 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # deprecated alias at_before_move = at_pre_move - def announce_move_from(self, destination, msg=None, mapping=None, **kwargs): + def announce_move_from(self, destination, msg=None, mapping=None, move_type="move", **kwargs): """ Called if the move is to be announced. This is called while we are still standing in the old @@ -1575,6 +1585,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): destination (Object): The place we are going to. msg (str, optional): a replacement message. mapping (dict, optional): additional mapping objects. + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -1610,9 +1624,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): } ) - location.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping) + location.msg_contents((string, {"type": move_type}), exclude=(self,), from_obj=self, mapping=mapping) - def announce_move_to(self, source_location, msg=None, mapping=None, **kwargs): + def announce_move_to(self, source_location, msg=None, mapping=None, move_type="move", **kwargs): """ Called after the move if the move was not quiet. At this point we are standing in the new location. @@ -1621,6 +1635,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): source_location (Object): The place we came from msg (str, optional): the replacement message if location. mapping (dict, optional): additional mapping objects. + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -1674,9 +1692,9 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): } ) - destination.msg_contents(string, exclude=(self,), from_obj=self, mapping=mapping) + destination.msg_contents((string, {"type": move_type}), exclude=(self,), from_obj=self, mapping=mapping) - def at_post_move(self, source_location, **kwargs): + def at_post_move(self, source_location, move_type="move", **kwargs): """ Called after move has completed, regardless of quiet mode or not. Allows changes to the object due to the location it is @@ -1684,6 +1702,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): Args: source_location (Object): Wwhere we came from. This may be `None`. + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -1693,20 +1715,24 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): # deprecated at_after_move = at_post_move - def at_object_leave(self, moved_obj, target_location, **kwargs): + def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs): """ Called just before an object leaves from inside this object Args: moved_obj (Object): The object leaving target_location (Object): Where `moved_obj` is going. + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). """ pass - def at_object_receive(self, moved_obj, source_location, **kwargs): + def at_object_receive(self, moved_obj, source_location, move_type="move", **kwargs): """ Called after an object has been moved into this object. @@ -1714,6 +1740,10 @@ class DefaultObject(ObjectDB, metaclass=TypeclassBase): moved_obj (Object): The object moved into this one source_location (Object): Where `moved_object` came from. Note that this could be `None`. + move_type (str): The type of move. "give", "traverse", etc. + This is an arbitrary string provided to obj.move_to(). + Useful for altering messages or altering logic depending + on the kind of movement. **kwargs (dict): Arbitrary, optional arguments for users overriding the call (unused by default). @@ -2450,7 +2480,7 @@ class DefaultCharacter(DefaultObject): # add the default cmdset self.cmdset.add_default(settings.CMDSET_CHARACTER, persistent=True) - def at_post_move(self, source_location, **kwargs): + def at_post_move(self, source_location, move_type="move", **kwargs): """ We make sure to look around after a move. @@ -2717,9 +2747,9 @@ class ExitCommand(_COMMAND_DEFAULT_CLASS): """ if self.obj.destination: - return " (exit to %s)" % self.obj.destination.get_display_name(caller) + return " (exit to %s)" % self.obj.destination.get_display_name(caller, **kwargs) else: - return " (%s)" % self.obj.get_display_name(caller) + return " (%s)" % self.obj.get_display_name(caller, **kwargs) # @@ -2928,7 +2958,7 @@ class DefaultExit(DefaultObject): """ source_location = traversing_object.location - if traversing_object.move_to(target_location): + if traversing_object.move_to(target_location, move_type="traverse"): self.at_post_traverse(traversing_object, source_location) else: if self.db.err_traverse: