diff --git a/evennia/commands/default/general.py b/evennia/commands/default/general.py index 18bd5d0dde..02a877a0ea 100644 --- a/evennia/commands/default/general.py +++ b/evennia/commands/default/general.py @@ -409,17 +409,15 @@ class CmdSay(COMMAND_DEFAULT_CLASS): speech = self.args - # calling the speech hook on the location - speech = caller.location.at_say(caller, speech) + # Calling the at_before_say hook on the character + speech = caller.at_before_say(speech) - # Feedback for the object doing the talking. - caller.msg('You say, "%s|n"' % speech) - - # Build the string to emit to neighbors. - emit_string = '%s says, "%s|n"' % (caller.name, speech) - caller.location.msg_contents(text=(emit_string, {"type": "say"}), - exclude=caller, from_obj=caller) + # If speech is empty, stop here + if not speech: + return + # Call the at_after_say hook on the character + caller.at_after_say(speech) class CmdWhisper(COMMAND_DEFAULT_CLASS): """ @@ -454,13 +452,15 @@ class CmdWhisper(COMMAND_DEFAULT_CLASS): return speech = self.rhs + # Call a hook to change the speech before whispering + speech = caller.at_before_whisper(receiver, speech) - # Feedback for the object doing the talking. - caller.msg('You whisper to %s, "%s|n"' % (receiver.key, speech)) + # If the speech is empty, abort the command + if not speech: + return - # Build the string to emit to receiver. - emit_string = '%s whispers, "%s|n"' % (caller.name, speech) - receiver.msg(text=(emit_string, {"type": "whisper"}), from_obj=caller) + # Call the at_after_whisper hook for feedback + caller.at_after_whisper(receiver, speech) class CmdPose(COMMAND_DEFAULT_CLASS): diff --git a/evennia/commands/default/tests.py b/evennia/commands/default/tests.py index 55896db1b2..fd56f7a799 100644 --- a/evennia/commands/default/tests.py +++ b/evennia/commands/default/tests.py @@ -131,7 +131,7 @@ class TestGeneral(CommandTest): self.call(general.CmdSay(), "Testing", "You say, \"Testing\"") def test_whisper(self): - self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"") + self.call(general.CmdWhisper(), "Obj = Testing", "You whisper to Obj, \"Testing\"", caller=self.char2) def test_access(self): self.call(general.CmdAccess(), "", "Permission Hierarchy (climbing):") diff --git a/evennia/objects/objects.py b/evennia/objects/objects.py index cffb2e7cdf..c67a55c3d6 100644 --- a/evennia/objects/objects.py +++ b/evennia/objects/objects.py @@ -1511,23 +1511,149 @@ class DefaultObject(with_metaclass(TypeclassBase, ObjectDB)): """ pass - def at_say(self, speaker, message): + def at_before_say(self, speech): """ - Called on this object if an object inside this object speaks. - The string returned from this method is the final form of the - speech. + Before the object says something. + + This hook is called by the 'say' command on the object itself + (probably a character). It is called before the actual say, + and can be used to control the content of the text to be said, + prevent saying altogether or perform some alternative checks. + This hook should return the modified speech. If this return + value is empty (like "" or None), the command is aborted. Args: - speaker (Object): The object speaking. - message (str): The words spoken. + speech (str): the text to be said by self. - Notes: - You should not need to add things like 'you say: ' or - similar here, that should be handled by the say command before - this. + Returns: + speech (str): the text to be said (can be modified). """ - return message + return speech + + def at_after_say(self, speech, msg_self=None, msg_location=None, + mapping=None): + """ + Display the actual say of self. + + This hook should display the actual say of the object in its + location. It should both alert the object (self) and its + location that some text is spoken. The overriding of messages or + `mapping` allows for simple customization of the hook without + re-writing it completely. + + Args: + speech (str): the text to be said by self. + msg_self (str, optional): the replacement message to say to self. + msg_location (str, optional): the replacement message to say + to the location. + mapping (dict, optional): Additional mapping in messages. + + Both `msg_self` and `msg_location` should contain references + to other objects between braces, the way `locaiton.msg_contents` + would allow. For instance: + msg_self = 'You say: "{speech}"' + msg_location = '{object} says: "{speech}"' + + The following mappings can be used in both messages: + object: the object speaking. + location: the location where object is. + speech: the text spoken by self. + + You can use additional mappings if you want to add other + information in your messages. + + """ + if self.location is None: + self.msg("You can't utter a sound in the void.") + return + + msg_self = msg_self or 'You say, "{speech}"|n' + msg_location = msg_location or '{object} says, "{speech}"|n' + mapping = mapping or {} + mapping.update({ + "object": self, + "location": self.location, + "speech": speech, + }) + self_mapping = {k: v.get_display_name(self) if hasattr( + v, "get_display_name") else str(v) for k, v in mapping.items()} + self.msg(msg_self.format(**self_mapping)) + self.location.msg_contents(msg_location, exclude=(self, ), + mapping=mapping) + + def at_before_whisper(self, receiver, speech): + """ + Before the object whispers something to receiver. + + This hook is called by the 'whisper' command on the object itself + (probably a character). It is called before the actual whisper, + and can be used to control the content of the text to be whispered, + prevent whispering altogether or perform some alternative checks. + This hook should return the modified speech. If this return + value is empty (like "" or None), the command is aborted. + + Args: + receiver (Object): the object to whisper to. + speech (str): the text to be whispered by self. + + Returns: + speech (str): the text to be whispered (can be modified). + + """ + return speech + + def at_after_whisper(self, receiver, speech, msg_self=None, + msg_receiver=None, mapping=None): + """ + Display the actual whisper of self. + + This hook should display the actual whisper of the object to + receiver. It should both alert the object (self) and the + receiver. You can also notify the location if you want to, + to indicate to others that a message was whispered but you + can't hear it. The overriding of messages or + `mapping` allows for simple customization of the hook without + re-writing it completely. + + Args: + receiver (Objecvt): the object to whisper to. + speech (str): the text to be said by self. + msg_self (str, optional): the replacement message to say to self. + msg_receiver (str, optional): the replacement message to say + to receiver. + mapping (dict, optional): Additional mapping in messages. + + Both `msg_self` and `msg_receiver` should contain references + to other objects between braces, the way `locaiton.msg_contents` + would allow. For instance: + msg_self = 'You whisper to {receiver}, "{speech}"|n' + msg_receiver = '{object} whispers: "{speech}"|n' + + The following mappings can be used in both messages: + object: the object whispering. + receiver: the object whispered to. + speech: the text spoken by self. + + You can use additional mappings if you want to add other + information in your messages. + + """ + msg_self = msg_self or 'You whisper to {receiver}, "{speech}"|n' + msg_receiver = msg_receiver or '{object} whispers: "{speech}"|n' + mapping = mapping or {} + mapping.update({ + "object": self, + "receiver": receiver, + "speech": speech, + }) + self_mapping = {k: v.get_display_name(self) if hasattr( + v, "get_display_name") else str(v) for k, v in mapping.items()} + receiver_mapping = {k: v.get_display_name(receiver) if hasattr( + v, "get_display_name") else str(v) for k, v in mapping.items()} + self.msg(msg_self.format(**self_mapping)) + receiver.msg(msg_receiver.format(**receiver_mapping)) + #