From 2a0c6e5e10130612c08d564b9a4c6298d50fe256 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Sat, 14 Feb 2026 19:55:42 -0800 Subject: [PATCH] Fix the say command ... for real this time More flexible and intuitive, where the comma is no longer critical. --- commands/default_cmdsets.py | 8 +- commands/everyone.py | 211 -------------------------- commands/say.py | 289 ++++++++++++++++++++++++++++++++++++ 3 files changed, 293 insertions(+), 215 deletions(-) create mode 100755 commands/say.py diff --git a/commands/default_cmdsets.py b/commands/default_cmdsets.py index 0db60cb..664af1c 100644 --- a/commands/default_cmdsets.py +++ b/commands/default_cmdsets.py @@ -20,10 +20,10 @@ from evennia.contrib.game_systems.gendersub import SetGender from evennia.contrib.rpg.rpsystem import RPSystemCmdSet from evennia.contrib.rpg.character_creator.character_creator import ContribChargenCmdSet from commands.sittables import CmdNoSitStand -from commands.everyone import (CmdTake, CmdThink, CmdSay, - CmdWhisper, CmdRead, CmdEat, CmdDrink, - CmdUse, CmdPush, CmdPull, - CmdOpen, CmdClose, CmdFeed) +from commands.everyone import (CmdTake, CmdThink, CmdWhisper, CmdRead, + CmdEat, CmdDrink, CmdUse, CmdPush, + CmdPull, CmdOpen, CmdClose, CmdFeed) +from commands.say import CmdSay from commands.scoring import CmdScore from commands.hint import CmdHint from commands.misc import CmdLight diff --git a/commands/everyone.py b/commands/everyone.py index 5a45056..0283e80 100755 --- a/commands/everyone.py +++ b/commands/everyone.py @@ -16,31 +16,6 @@ from typeclasses.tutorial import TutorBird, TutorialState from utils.word_list import routput, paragraph, choices -def speech_effect(speech, verb, target, effects): - """ - Return speech after applying 'effects'. - - Return a tuple for what a user thinks he said, and what - others actually here. If no effect, just return speech twice. - - Effects: - - mute ... can't talk - - donkied ... target and others hear the value of effect - - sloshed ... slurred speech - - To administer: - - @set woman/donkied:effect = "Heehaw! ;; Heehaw, heehaw!" - """ - for effect in effects: - if effect: - if effect.db_key == 'donkied': - msg = choices(effect.value) - return (msg, msg, "bray") - - return (speech, speech, verb) - - class CmdUse(MuxCommand): """ Use an item. @@ -251,192 +226,6 @@ class CmdWhisper(MuxCommand): self.caller.msg(full_speech, from_obj=self.caller) -class CmdSay(Command): - """Say something to the characters in the same area. - - Usage: - - |gsay [[] [to] [],] |n - - This command has many optional strings. You see, the text within - brackets, [...] are optional, while text in <...> are to be - replaced, and this technical jargon probably means little, so - perhaps examples would be better: - - |gsay Good evening.|n - |wYou say, "Good evening."|n - - Or: - - |gsay loudly, Good evening!|n - |wYou loudly exclaim, "Good evening!"|n - - In the second example, the exclamation point changes the 'say' to - an 'exclaim'. A question mark changes 'say' to 'ask'. - - Also, you gave an |wadverb|n that ends in -ly, as well as a comma. - The comma is important, as it triggers |wprocessing|n. Without - the comma, you would see: - - |gs loudly Good evening! - |wYou exclaim, "loudly Good evening!"|n - - You can also direct your message to a particular person (or thing), - as in: - - |gsay to heron, How do you do? - |wYou ask the purple heron, "How do you do?"|n - "What can I do for you?" asks the purple heron. - - Again, the comma is important, otherwise, you will see: - - |gsay to heron How do you do? - |wYou ask, "to heron How do you do?"|n - The purple heron questions, "Uhm. I'm not sure how to respond." - - You can replace the 'say' command with any of the following: - - - shout - - scream - - respond - - reply - - yell - - ask - - Because this command is so common, you can also use ", as in: - - |g"What's going on?|n - - """ - - key = "say" - verb = "say" - aliases = ["says", "speak", "shout", "yell", "exclaim", "scream", "ask", - "reply", "respond", '"', "'"] - locks = "cmd:all()" - # don't require a space after `say/'/"` - arg_regex = None - adverb = None - target = None - - def parse(self): - """Parse the input into speech parts.""" - self.phrase = self.args.strip() - - m = match(r".+\?.*", self.phrase) - if m: - self.verb = "ask" - m = match(r".+!.*", self.phrase) - if m: - self.verb = "exclaim" - - # Final quotes can be trimmed: - self.phrase = self.phrase.strip('"').strip("'") - - m = match(r"(.*) *, *(.+)", self.phrase) - if m: - self.phrase = m.group(2) - for word in split(r" +", m.group(1)): - if word.endswith("ly") and not self.adverb: - self.adverb = word - elif word == "to": - pass - else: - self.target = self.caller.search(word) - - def func(self): - """Implement the new 'say' command with switches.""" - speaker = self.caller - if not self.phrase or self.phrase == "": - speaker.msg("Say what?") - return - - # If speech is empty, stop here - if not speaker.at_pre_say(self.phrase): - return - - if self.cmdstring == 'scream': - self.verb = 'scream' - elif self.cmdstring == 'shout': - self.verb = 'shout' - elif self.cmdstring == 'respond': - self.verb = 'respond' - elif self.cmdstring == 'reply': - self.verb = 'reply' - elif self.cmdstring == 'yell': - self.verb = 'yell' - elif self.cmdstring == 'ask': - self.verb = 'ask' - - for_me, for_others, verb = \ - speech_effect(self.phrase, self.verb, speaker, - speaker.attributes.get(category="effect", - return_obj=True, - return_list=True)) - self.to_puppets(speaker, for_others, self.target) - self.to_you(speaker, verb, for_me) - self.to_others(speaker, verb, for_others) - - def to_you(self, speaker, verb, phrase): - """Send 'say' message to the speaker. - - The `send_emote` is _global_, so if we want to say to 'Bob', - 'You say ...', we have to both send him a message with the - 'You' as well as everyone else with the 'send_emote'. - """ - to_who = "" - if self.target: - who = self.target.get_display_name(speaker) - if verb == "ask": - to_who = " " + who - else: - to_who = " to " + who - - adverb = self.adverb + " " if self.adverb else "" - - full_speech = f"You {adverb}{verb}{to_who}, \"{phrase}\"" - speaker.msg(full_speech, from_obj=speaker) - - def to_others(self, speaker, verb, phrase): - """Use the 'send_emote' to have speaker 'say' something to others. - - We use /me to be replaced by the user's appearance to others. - We also need to deal with weird English usage. - """ - to_whom = "" - if self.target: - whom = f"/{self.target.key}" - if verb == "ask": - to_whom = " " + whom - else: - to_whom = " to " + whom - - adverb = self.adverb + " " if self.adverb else "" - # English is weird... - verb = 'replie' if verb == 'reply' else verb - targets = [item for item in speaker.location.contents - if item != speaker] - - full_speech = f"/me {adverb}{verb}s{to_whom}, \"{phrase}\"" - send_emote(speaker, targets, full_speech, msg_type="say", - anonymous_add=None) - - def to_puppets(self, speaker, phrase, target=None): - """Send phrase to puppet, target. - - If target is None, then send to all puppets. - """ - # Speak to everyone in the room: - if not target: - for char in speaker.location.contents: - if hasattr(char, 'other_say') and callable(char.other_say): - char.other_say(speaker, phrase) - else: - if hasattr(target, 'other_sayto') and callable(target.other_sayto): - logger.info(f"Found {target.key}: {phrase}") - target.other_sayto(speaker, phrase) - - class CmdThink(Command): """Think a thought out loud. diff --git a/commands/say.py b/commands/say.py new file mode 100755 index 0000000..d4ceac7 --- /dev/null +++ b/commands/say.py @@ -0,0 +1,289 @@ +#!/usr/bin/env python + +from random import random +from re import match, split, sub, MULTILINE + +from evennia.contrib.rpg.rpsystem import send_emote +from evennia.utils.verb_conjugation.pronouns import PRONOUN_MAPPING + +from commands.command import Command +from utils.word_list import routput, paragraph, choices + + +ADVERBS = [ + "aloud", + "abruptly", "actually", "angrily", "animatedly", "bitterly", "boldly", "cheerfully", "clearly", + "confidently", "continuously", "cruelly", "defiantly", "desperately", "earnestly", "elaborately", + "emphatically", "excitedly", "frantically", "frequently", "gently", "gravely", "hesitantly", + "impatiently", "insistently", "intelligently", "jokingly", "joyfully", "loudly", "mockingly", + "nonchalantly", "passionately", "playfully", "quietly", "reassuringly", "reluctantly", "seriously", + "sharply", "sharply", "simply", "silently", "sincerely", "softly", "suddenly", "surprisingly", + "thoughtfully", "unexpectedly", "voraciously", "whimsically", "wistfully", +] + + +def get_adverb(phrase): + """Return tuple of initial adverb (or None) and the phrase.""" + m = match(r"(\w+)[, ]+(.*)", phrase) + if m: + first_word = m.group(1) + rest_words = m.group(2) + if first_word in ADVERBS: + return first_word, rest_words + return None, phrase + + +def get_say_direction(phrase): + """Return tuple of object of communication and the rest of the phrase.""" + m = match(r"to (\w+)[, ]+(.*)", phrase) + if m: + first_word = m.group(1) + rest_words = m.group(2) + if first_word.endswith("self"): + return "self", rest_words + else: + return first_word, rest_words + return None, phrase + + +def speech_effect(speech, verb, target, effects): + """ + Return speech after applying 'effects'. + + Return a tuple for what a user thinks he said, and what + others actually here. If no effect, just return speech twice. + + Effects: + - mute ... can't talk + - donkied ... target and others hear the value of effect + - sloshed ... slurred speech + + To administer: + + @set woman/donkied:effect = "Heehaw! ;; Heehaw, heehaw!" + """ + for effect in effects: + if effect: + if effect.db_key == 'donkied': + msg = choices(effect.value) + return (msg, msg, "bray") + + return (speech, speech, verb) + + +class CmdSay(Command): + """Say something to the characters in the same area. + + Usage: + + |gsay [[] [to ],] |n + + This command has a couple optional strings. as you see, the text + within brackets, [...] are optional, while text in <...> are to be + replaced, and this technical jargon probably means little, so + perhaps examples would be better: + + |gsay Good evening.|n + |wYou say, "Good evening."|n + + Or: + + |gsay loudly, Good evening!|n + |wYou loudly exclaim, "Good evening!"|n + + In the second example, you gave an |wadverb|n that ends in + |w-ly|n. Also, the exclamation point changes the 'say' to an + 'exclaim'. A question mark changes 'say' to 'ask'. + + If you start your message with an adverb, but want that adverb + |win|n the message and not |waffecting|n the message, surround it + with quotes. For instance: + + |gsay actually, it is pronounced gah-nome. + |wYou say actually, "it is pronounced gah-nome." + |gsay "actually, it is pronounced gah-nome." + |wYou say, "actually, it is pronounced gah-nome."|n + + Of course, adverbs that affect the message are lowercase, and + messages usually begin with an uppercase, as in: + + |gsay Actually, it is pronounced Gah-nome. + |wYou say, "Actually, it is pronounced Gah-nome."|n + + Because this command is so common, you can also use ", as in: + + |g"What's going on?|n + + You can also direct your message to a particular person (or + thing), as in: + + |gsay to heron, How do you do? + |wYou ask the purple heron, "How do you do?"|n + "What can I do for you?" asks the purple heron. + + Since you might be having a conversation with a character, you can + use the |gsayto|n to target the same person. For instance: + + |gsay to pet Who's a widdle cutie? + |wYou ask wee beastie, "Who's a widdle cutie?" + |gsayto You are! + |wYou exclaim to wee beastie, "You are!"|n + + You can replace the 'say' command with any of the following: + + - shout + - scream + - respond + - reply + - yell + - ask + + """ + + key = "say" + verb = "say" + aliases = ["says", "speak", "shout", "yell", "exclaim", "scream", + "ask", "reply", "respond", '"', "'", "sayto"] + locks = "cmd:all()" + # don't require a space after `say/'/"` + arg_regex = None + adverb = None + target = None + + def parse(self): + """Parse the input into speech parts.""" + self.phrase = self.args.strip() + + m = match(r".+\?.*", self.phrase) + if m: + self.verb = "ask" + m = match(r".+!.*", self.phrase) + if m: + self.verb = "exclaim" + + m = match(r'.*".+".*', self.phrase) + if not m: + # No overriding quotes? Process adverbs... + self.adverb, self.phrase = get_adverb(self.phrase) + target, self.phrase = get_say_direction(self.phrase) + + if target: + if target == "self": + self.target = self.caller + else: + self.target = self.caller.search(target) + self.caller.db.say_target = self.target + + # Final quotes can be trimmed: + self.phrase = self.phrase.strip('"').strip("'") + + def func(self): + """Implement the new 'say' command with switches.""" + speaker = self.caller + if not self.phrase or self.phrase == "": + speaker.msg("Say what?") + return + + # If speech is empty, stop here + if not speaker.at_pre_say(self.phrase): + return + + if self.cmdstring == 'sayto': + self.target = self.caller.db.say_target + + if self.cmdstring == 'scream': + self.verb = 'scream' + elif self.cmdstring == 'shout': + self.verb = 'shout' + elif self.cmdstring == 'respond': + self.verb = 'respond' + elif self.cmdstring == 'reply': + self.verb = 'reply' + elif self.cmdstring == 'yell': + self.verb = 'yell' + elif self.cmdstring == 'ask': + self.verb = 'ask' + + for_me, for_others, verb = \ + speech_effect(self.phrase, self.verb, speaker, + speaker.attributes.get(category="effect", + return_obj=True, + return_list=True)) + self.to_puppets(speaker, for_others, self.target) + self.to_you(speaker, verb, for_me) + self.to_others(speaker, verb, for_others) + + def to_you(self, speaker, verb, phrase): + """Send 'say' message to the speaker. + + The `send_emote` is _global_, so if we want to say to 'Bob', + 'You say ...', we have to both send him a message with the + 'You' as well as everyone else with the 'send_emote'. + """ + to_who = "" + if self.target: + if self.target == self.caller: + who = "yourself" + else: + who = self.target.get_display_name(speaker) + + if verb == "ask": + to_who = " " + who + else: + to_who = " to " + who + + verb = " " + verb + adverb = " " + self.adverb if self.adverb else "" + + if adverb.endswith("ly") and random() < 0.5: + full_speech = f"You{adverb}{verb}{to_who}, \"{phrase}\"" + else: + full_speech = f"You{verb}{adverb}{to_who}, \"{phrase}\"" + speaker.msg(full_speech, from_obj=speaker) + + def to_others(self, speaker, verb, phrase): + """Use the 'send_emote' to have speaker 'say' something to others. + + We use /me to be replaced by the user's appearance to others. + We also need to deal with weird English usage. + """ + to_whom = "" + if self.target: + if self.target == self.caller: + whom = PRONOUN_MAPPING['3rd person']['reflexive pronoun'][self.caller.db.gender] + else: + who = self.target.get_display_name(speaker) + whom = f"/{self.target.key}" + + if verb == "ask": + to_whom = " " + whom + else: + to_whom = " to " + whom + + # English is weird... + verb = ' replie' if verb == 'reply' else " " + verb + adverb = " " + self.adverb if self.adverb else "" + targets = [item for item in speaker.location.contents + if item != speaker] + + if adverb.endswith("ly") and random() < 0.5: + full_speech = f"/me{adverb}{verb}s{to_whom}, \"{phrase}\"" + else: + full_speech = f"/me{verb}s{adverb}{to_whom}, \"{phrase}\"" + send_emote(speaker, targets, full_speech, msg_type="say", + anonymous_add=None) + + def to_puppets(self, speaker, phrase, target=None): + """Send phrase to puppet, target. + + If target is None, then send to all puppets. + """ + # Speak to everyone in the room: + if not target: + for char in speaker.location.contents: + if hasattr(char, 'other_say') and callable(char.other_say): + char.other_say(speaker, phrase) + else: + if hasattr(target, 'other_sayto') and callable(target.other_sayto): + logger.info(f"Found {target.key}: {phrase}") + target.other_sayto(speaker, phrase)