Fix the say command ... for real this time

More flexible and intuitive, where the comma is no longer critical.
This commit is contained in:
Howard Abrams 2026-02-14 19:55:42 -08:00
parent 5120e365b4
commit 2a0c6e5e10
3 changed files with 293 additions and 215 deletions

View file

@ -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

View file

@ -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 [[<adverb>] [to] [<character>],] <message>|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.

289
commands/say.py Executable file
View file

@ -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 [[<adverb>] [to <character>],] <message>|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)