moss-n-puddles/commands/say.py
Howard Abrams 66cabcc1a2 Animate the Wee Beastie and scrying into rooms
This commit gives an "AI" capability to any object (still
triggered by the 'say' command), so now the Wee Beastie can do more
than purr.

Also fixes the Witch and the Dragon's movements throughout the Realm.

We can also Scry into rooms in order to watch the behavior of the Chatbots.
2026-04-16 17:09:15 -07:00

316 lines
11 KiB
Python
Executable file

#!/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 import logger, delay
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()
self.reverse = False
m = match(r".+\?.*", self.phrase)
if m:
self.verb = "ask"
m = match(r".+!.*", self.phrase)
if m:
self.verb = "exclaim"
m = match(r'.+\w$', self.phrase)
if m:
self.reverse = True
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 self.reverse:
if adverb.endswith("ly") and random() < 0.5:
full_speech = f"\"{phrase},\" you{adverb}{verb}{to_who}."
else:
full_speech = f"\"{phrase},\" you{verb}{adverb}{to_who}."
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)
if hasattr(self.target, 'sdesc') and self.target.sdesc.get():
whom = f"the /{self.target.sdesc.get()}"
else:
whom = f"the /{self.target.name}"
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 self.reverse:
if adverb.endswith("ly") and random() < 0.5:
full_speech = f"\"{phrase},\" /me{adverb}{verb}s{to_whom}."
else:
full_speech = f"\"{phrase},\" /me{verb}s{adverb}{to_whom}."
else:
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}\""
logger.info(f"Full speech: {full_speech}")
# Full speech: /me asks /Trampoli, "How are you?"
speaker.location.store_event(full_speech, speaker)
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):
logger.info(f"to_puppets: all: {char}")
delay(1, char.other_say, speaker, phrase)
else:
if hasattr(target, 'other_sayto') and callable(target.other_sayto):
logger.info(f"Found {target.key}: {phrase}")
delay(1, target.other_sayto, speaker, phrase)