553 lines
23 KiB
Python
Executable file
553 lines
23 KiB
Python
Executable file
#!/usr/bin/env python
|
||
|
||
from random import choice, random
|
||
from enum import Enum
|
||
|
||
from evennia import create_script
|
||
from evennia.utils import logger, delay
|
||
|
||
from commands.misc import CmdSetPuddle, CmdSetStick, CmdSetKnock
|
||
from utils.word_list import routput, choices
|
||
from .scripts import KnockScript
|
||
from .objects import Object
|
||
|
||
import re
|
||
|
||
|
||
class Trinket(Object):
|
||
"""
|
||
For instance:
|
||
|
||
"""
|
||
msgs = [
|
||
"You see a crystal ball. When the swirling vortex stops, It reads, '<<Why not? ^ Don't even think about it ^ Maybe, maybe not ^ Sure, just don't tell>>'.",
|
||
"You find a deck of Tarot cards, but the card of Death has been replaced by a worker in an office cubical that reads, 'Mostly Death'.",
|
||
"You see a statue of a <<black ^ red ^ yellow>> dragon in the shape of a question mark. You have to ask yourself, Why?",
|
||
"A Venus fly trap in a shell surrounded by chubby cherubs.",
|
||
"You see a statue of a large rat. Did it just blink? Wow, after it ran away you think that was a rat of unusual size.",
|
||
"An ancient Colombian grave owl holding a sign that reads, 'Cannibals don't eat clowns? They taste funny.'",
|
||
"You find a dirty plate with a pizza crust. This is probably not a trinket as much as just sloppy housecleaning.",
|
||
"You find a vial of colorless, odorless, tasteless Iocane powder. This is among the deadlier poisons known to man, so you should probably not touch it.",
|
||
"A troll doll with an outie belly button. Everyone knows that trolls are not placental mammals, but reproduce through a complex series of... wait, you haven't had this talk with your parents, yet?",
|
||
"A small statue of a flexing giant. When you push its belly, it says, 'Its not my fault I'm the biggest and the strongest. I don't even exercise.'",
|
||
"You see a license plate from the State of Utah that reads HEATHEN, and think, what is the significance of hens in heat? Oh, and you also think, what is a license plate doing in a fantasy game about escaping the clamors of last stage capitalism?",
|
||
"You find a brilliant <<blue ^ green ^ iridescent>> scarf around a book, but as soon as you remove it, the book tries to bite you. After it chases you around the room, you double back, jump on it, breaking its spine (which is lets out a sad yelp), and wrap the scarf back around it. Maybe you've seen enough stuff on these shelves for a while.",
|
||
]
|
||
|
||
def at_object_creation(self):
|
||
"""
|
||
Set the initial number position for cycling through them.
|
||
"""
|
||
self.db.last_trinket_num = -1
|
||
|
||
def return_appearance(self, looker, **kwargs):
|
||
"""
|
||
Return a trinket by cycling through the list.
|
||
"""
|
||
self.db.last_trinket_num = self.db.last_trinket_num + 1
|
||
|
||
# Once they have seen the crystal ball, they can now "see"
|
||
# them, and probably pick one up, if they are around:
|
||
if self.db.last_trinket_num == 0:
|
||
looker.tags.add("hidden_ball")
|
||
|
||
# Seen all the trinkets? Oh boy, well, let's loop:
|
||
if self.db.last_trinket_num >= len(self.msgs):
|
||
self.db.last_trinket_num = 0
|
||
|
||
return routput(self.msgs[self.db.last_trinket_num])
|
||
|
||
|
||
class CrystalBall(Object):
|
||
def return_appearance(self, looker, **kwargs):
|
||
"""Return a different result each time it is looked."""
|
||
return routput(self.db.desc) + " |w" + choice([
|
||
"Definitely yes",
|
||
"It is certain",
|
||
"It is decidedly so",
|
||
"Without a doubt",
|
||
"You may rely on it",
|
||
"As I see it, yes",
|
||
"Most likely",
|
||
"Outlook good",
|
||
"Yes",
|
||
"Signs point to yes",
|
||
|
||
"Reply hazy, try again",
|
||
"Ask again later",
|
||
"Better not tell you now",
|
||
"Cannot predict now",
|
||
"Concentrate and ask again",
|
||
|
||
"Nope",
|
||
"Don’t count on it",
|
||
"My reply is no",
|
||
"My sources say no",
|
||
"Outlook not so good",
|
||
"Very doubtful",
|
||
])
|
||
|
||
class Ring(Object):
|
||
def move_to(self, destination, **kwargs):
|
||
"""
|
||
The ring should only go to the door knocker...
|
||
"""
|
||
if destination.is_typeclass("typeclasses.things.Knocker") or \
|
||
destination.is_typeclass("typeclasses.characters.Character"):
|
||
super().move_to(destination)
|
||
return True
|
||
return False
|
||
|
||
|
||
class Pipe(Object):
|
||
"""Simple abstraction for the following actions.
|
||
|
||
Note that each message has two versions, one for the smoker (you)
|
||
and one for everyone else in the room.
|
||
|
||
@set pipe/light_msg = "You pull out, pack and light a pipe."
|
||
@set pipe/light_msg_other = "{0} packs |p pipe and lights it."
|
||
|
||
Where the |p is a possessive gender, if set, e.g. his or her.
|
||
|
||
The random messages has available substitutions based on if the message is for the smoker or for the audience in the room. Specifically:
|
||
|
||
- {0} :: either "you" or your name
|
||
- {1} :: either "your" or your name with an apostrophe 's.
|
||
- {2} :: blank (for you) or "s" for everyone else, e.g. "blow{2}"
|
||
|
||
For instance:
|
||
|
||
@set pipe/random_msgs = "{0} blow{1} a <<large ^ small ^ >> smoke-ring followed by another that flies through the first. ;; {1} smoke collesce to form a <<dragon ^ large woodland beast ^ beholder ^ bugbear>> ... or
|
||
"""
|
||
def do_light(self, lighter):
|
||
you_msg = choices(self.db.light_msg or "You pack and light your pipe.")
|
||
lighter.msg(you_msg)
|
||
# desc = self.return_appearance()[:1].lower() + self.return_appearance()[1:]
|
||
other_msg = choices(self.db.light_msg_other or "{0} packs and lights |p pipe.", lighter.name)
|
||
lighter.location.msg_contents(other_msg, exclude=lighter)
|
||
|
||
def do_puff(self, smoker):
|
||
pass
|
||
|
||
|
||
class Wood(Object):
|
||
"An object to burn."
|
||
def at_object_creation(self):
|
||
self.db.singular = "a log"
|
||
self.db.plural = "some logs"
|
||
self.db.desc = routput(choice([
|
||
"Its log, its log, it's big, it's heavy, it's wood.",
|
||
"Some <<logs ^ wood>> for the fireplace.",
|
||
]))
|
||
|
||
|
||
class Stick(Object):
|
||
"An object to throw."
|
||
def at_object_creation(self):
|
||
self.cmdset.add_default(CmdSetStick)
|
||
|
||
def do_throw(self, thrower):
|
||
beast = thrower.location.search('beast')
|
||
if beast and beast.db.is_awake:
|
||
beast.thrown_stick(thrower)
|
||
elif thrower.location.is_typeclass("typeclasses.rooms_weather.TimeWeatherRoom"):
|
||
thrower.db.thrown_times = (thrower.db.thrown_times or 0) + 1
|
||
if thrower.db.thrown_times == 1:
|
||
thrower.msg("The stick flies through the air, and just as you thought, it comes back to you!")
|
||
else:
|
||
thrower.msg(choices([
|
||
"Yer a wizard, {0}!",
|
||
"<<Did you see that? ^ >> It clipped the leaf on that tree before returning.",
|
||
"This is a <<fun ^ pleasant ^ nice>> <<past-time ^ game>>.",
|
||
"Maybe we should get a pet <<or beast ^ beastie ^ >> in here who would love to play.",
|
||
], thrower.name.capitalize()))
|
||
self.location.msg_contents(f"{thrower.name} throws a stick.",
|
||
exclude=thrower)
|
||
else:
|
||
thrower.msg("I think you should be outside or a place with more room before you throw that stick around.")
|
||
|
||
|
||
class Puddle(Object):
|
||
def at_object_creation(self):
|
||
self.cmdset.add_default(CmdSetPuddle)
|
||
|
||
def do_jump(self, player):
|
||
player.db.jumped_times = (player.db.jumped_times or 0) + 1
|
||
if player.db.jumped_times == 1:
|
||
player.msg("You jump in the puddle! "
|
||
"This is great fun. You feel childish.")
|
||
else:
|
||
player.msg(routput(choice([
|
||
"You don't care <<how muddy ^ who notices>>, you jump in again!",
|
||
"Mud? Whatever. You <<splash ^ jump>> in <<^ again>>.",
|
||
"You splash around in the puddle.",
|
||
"You jump in the puddle again.",
|
||
"This time you dance a little as you kick up your heels.",
|
||
]) + " You feel " + choice([
|
||
"<<so much ^ >> better.",
|
||
"carefree.",
|
||
"child-like and free.",
|
||
"irresponsible.",
|
||
"happy.",
|
||
"great.",
|
||
])))
|
||
|
||
self.location.msg_contents(f"{player.name} jumps in the puddle.",
|
||
exclude=player)
|
||
|
||
|
||
|
||
class Knocker_Convo(Enum):
|
||
"What topics have we covered?"
|
||
ANY = 0
|
||
GREETED = 1
|
||
DOOR = 2
|
||
LOCK = 3
|
||
PASS = 4
|
||
HINT = 5
|
||
RIDDLE = 6
|
||
ANSWERED = 7
|
||
|
||
class Knocker(Object):
|
||
"""
|
||
Knocker
|
||
|
||
The Object is the class for gatekeepers between rooms.
|
||
Special object that listens to what is said in the room, and
|
||
attempts to a _real_ NPC.
|
||
"""
|
||
muffled_responses = [
|
||
"Mmmuufffmm",
|
||
"Mmmf?",
|
||
"Mummffmmph",
|
||
"Umfmuummmfff",
|
||
"Umf! Umfmmmf mmm mmmuufffmm mff mmuuummph.",
|
||
]
|
||
|
||
yes_msg = r"\byes|yeah|yah|sure|please\b"
|
||
no_msg = r"\bno|nope\b"
|
||
question_msg = r"\?$"
|
||
greet_msg = r"\bhello|\bgreet|\bhey\b"
|
||
|
||
# Each state of conversation is listed:
|
||
close_responses = [
|
||
"Oooo...good guess, but we are looking for something more specific.",
|
||
]
|
||
answered_responses = [
|
||
"Alright, I'll open the door for yah.",
|
||
"I heard my favorite, let's get this door unlocked.",
|
||
]
|
||
|
||
# Note that the responses are ordered from most specific to least.
|
||
knocker_responses = [
|
||
"[Sorry.|What was that?|Did you say something?] I'm hard of hearing on account of the brass ears.",
|
||
"Yes, I suppose I'm this amazing puzzle you get to in Chapter Three. Wait, does that mean I'm just an NPC?",
|
||
"Who me? I thought you were talking to the goblin in the bushes.",
|
||
"Why yes, I am hard of hearing.",
|
||
]
|
||
password_responses = [
|
||
"Of course this door is protected by a super complicated encrypted password.",
|
||
"If I tell you, it wouldn't be a secret now.",
|
||
"The password? You just have to guess...unless you want a hint?",
|
||
"Well, I suppose I could give you a |mhint|n.",
|
||
]
|
||
hint_responses = [
|
||
"A hint does sound fair. <<Should I|I should>> come up with a |mriddle|n<<, huh ^ >>?"
|
||
]
|
||
riddle_responses = [
|
||
"Aged in barrels, smooth and neat, in a glass by the fire, I'm a treat.",
|
||
"A scent of oak, a whiff of grain, a drop of expertise from the cask remains.",
|
||
"""
|
||
In oaken halls, I sleep and bide,
|
||
till I'm called to warm your insides...
|
||
hrm, that's a little vague, but kinda nice.
|
||
""",
|
||
"""
|
||
Alright, alright, the riddle should be clever.
|
||
It should refer to its golden hue, and I should make it obvious
|
||
that it isn't gold, and adding a reference to quest of the Argonauts
|
||
would be a complete red herring, and quite a mean thing to do,
|
||
so I shouldn't add that,
|
||
but what about barrels? Yeah, need to include barrels, but not the
|
||
way Bilbo road barrels, for that was definitely intended as a misleading
|
||
riddle for Smaug, but of course, a smart dragon would always figure such
|
||
things out, wait, where was I?
|
||
"""
|
||
]
|
||
|
||
locked_responses = [
|
||
"Yes, I'm familiar with the door and the fact that it is locked.",
|
||
"This locked door is to protect the theft of Dabbler's scones.",
|
||
]
|
||
all_responses = [
|
||
# The password must be the first entry here, as we "act" on it:
|
||
[r"whiske?y", Knocker_Convo.ANY, answered_responses, Knocker_Convo.ANSWERED],
|
||
[r"scotch|bourbon", Knocker_Convo.ANY, [
|
||
"A little more specific than needed, but that will do."],
|
||
Knocker_Convo.ANSWERED],
|
||
|
||
# A _sequence_ of responses, from what is this password business, to give me a hint, to give me the riddle ...
|
||
[question_msg, Knocker_Convo.RIDDLE, [
|
||
"Nope. Would <<ya ^ you>> like another riddle?",
|
||
"You <<really ^ >> think that is the answer?",
|
||
"Personally, <<I think ^>> that riddle could not <<be ^ have been>> more clear."
|
||
], None],
|
||
[yes_msg, Knocker_Convo.HINT, riddle_responses, Knocker_Convo.RIDDLE],
|
||
["riddle", Knocker_Convo.ANY, riddle_responses, Knocker_Convo.RIDDLE],
|
||
["another", Knocker_Convo.RIDDLE, riddle_responses, Knocker_Convo.RIDDLE],
|
||
|
||
["hint", Knocker_Convo.ANY, hint_responses, Knocker_Convo.HINT],
|
||
["\btell\b", Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT],
|
||
[yes_msg, Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT],
|
||
[question_msg, Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT],
|
||
|
||
[yes_msg, Knocker_Convo.DOOR, password_responses, Knocker_Convo.PASS],
|
||
["password", Knocker_Convo.ANY, password_responses, Knocker_Convo.PASS],
|
||
["get in", Knocker_Convo.LOCK, password_responses, Knocker_Convo.PASS],
|
||
|
||
["get in", Knocker_Convo.DOOR, [
|
||
"Well, you see. The door is locked."
|
||
], Knocker_Convo.LOCK],
|
||
[question_msg, Knocker_Convo.DOOR, locked_responses, Knocker_Convo.LOCK],
|
||
[r"\block", Knocker_Convo.ANY, locked_responses, Knocker_Convo.LOCK],
|
||
|
||
[r"\bdoor\b", Knocker_Convo.ANY, [
|
||
"What door? I don't see a door. Ha!",
|
||
"That's right, I am the clever puzzle hanging on a door.",
|
||
"I'm hanging on a door? Really? Let's see, can I roll my eyes?",
|
||
"Just to let you know, the door is locked.",
|
||
"""
|
||
I shouldn't be telling you this,
|
||
but I like the cut of your <<suit ^ cloak ^ jib>>.
|
||
So, you see, if you speak the |mpassword|n, wait, I've said too much.
|
||
"""
|
||
], Knocker_Convo.DOOR],
|
||
|
||
[greet_msg, Knocker_Convo.GREETED, [
|
||
"Didn't we just exchanged pleasantries?",
|
||
"Just so you know, I'm not much into small talk."
|
||
], None],
|
||
[greet_msg, Knocker_Convo.ANY, [
|
||
"How's it going?",
|
||
"How's it?",
|
||
"'Sup.",
|
||
"How are you?",
|
||
], Knocker_Convo.GREETED],
|
||
|
||
[r"knocker|goblin", Knocker_Convo.ANY, knocker_responses, None],
|
||
|
||
[r"\bass\b", Knocker_Convo.ANY, [
|
||
"Why yes, I am made of brass."
|
||
], None],
|
||
|
||
[r"thanks|thank you", Knocker_Convo.ANY, [
|
||
"You're <<very ^ >> welcome.",
|
||
"Glad to be of service."
|
||
], None],
|
||
|
||
[yes_msg, Knocker_Convo.ANY, [
|
||
"Really? You agree?",
|
||
"Excellent.",
|
||
], None],
|
||
[no_msg, Knocker_Convo.ANY, [
|
||
"Well, it's true. Just ask the <<raven ^ trees ^ gnome>>.",
|
||
], None],
|
||
[question_msg, Knocker_Convo.ANY, [
|
||
"What about <<me ^ it ^ 'im>>?",
|
||
"I dunno...",
|
||
], None],
|
||
]
|
||
|
||
after_unlocked_responses = [
|
||
"""
|
||
We could <<feign ^ pretend ^ play make-believe>> and
|
||
<<carry on ^ continue ^ persist>> this <<conversation ^ charade>>,
|
||
but we both know that |wyou|n know the <<password ^ secret ^ secret word ^ magic>>,
|
||
and can go through the |gdoor|n now.
|
||
""",
|
||
"""
|
||
<<Sure ^ Why not ^ Why yes>>, let's <<feign ^ pretend ^ play make-believe that>> you
|
||
don't know the <<password ^ secret ^ secret word ^ magic>>, and
|
||
<<carry on ^ continue ^ persist>> this conversation.
|
||
""",
|
||
]
|
||
|
||
cant_hear_responses = [
|
||
"<<Sorry. ^ What was that? ^ Did you say something?>> I'm hard of hearing on account of the brass ears.",
|
||
"Brass ears. Yeah, not the best at hearing.",
|
||
"Yeah, These brass ears don't hear much except for the |m<<secret ^ magic ^ >> password|n.",
|
||
]
|
||
|
||
unknown_responses = [
|
||
"Are you talking to me or the goblin <<in the bushes ^ up the tree ^ behind the rock>>?",
|
||
"Knock, knock.",
|
||
"What do you mean?",
|
||
"Of course I like <<hard candy ^ squirrels ^ apples>>. Who <<doesn't ^ wouldn't ^ do you know that doesn't>>?",
|
||
"No thank you, I can't eat <<apples ^ kumquats ^ figs>>, what with the brass teeth and a lack of guts.",
|
||
"Tea? While that would be <<nice ^ sweet>> of you, I can't really hold a cup.",
|
||
"I'm fine, thanks. How are you?",
|
||
"I'll say, <<we have had ^ that is>> a spell of weather.",
|
||
]
|
||
|
||
def at_object_creation(self):
|
||
super().at_object_creation()
|
||
self.cmdset.add(CmdSetKnock)
|
||
|
||
def do_knock(self, knocker):
|
||
if self.has("ring"):
|
||
knock_script = create_script(key="knocking",
|
||
typeclass=KnockScript,
|
||
interval=10, # seconds <=0 means off
|
||
start_delay=True, # wait interval before first call
|
||
autostart=True,
|
||
# This is the _character_
|
||
# that does the knocking, not
|
||
# the door knocker:
|
||
attributes=[("knocker", knocker),
|
||
# Waker is the door knocker
|
||
("waker", self),
|
||
("room", self.db.room_to_msg)
|
||
])
|
||
knocker.msg("You grab the ring and knock firmly on the door.")
|
||
self.msg_contents(f"{knocker.name} grabs the ring and knocks firmly on the door.",
|
||
exclude=knocker)
|
||
else:
|
||
knocker.msg("This door knocker is defective, as it doesn't have a ring to...er, do the knockin'.")
|
||
|
||
def knocked_timed_out(self):
|
||
if self.has("brass ring"):
|
||
self.at_say(choice(self.muffled_responses))
|
||
else:
|
||
self.at_say("Doesn't appear that anyone is home.")
|
||
|
||
def at_heard_say(self, message, talker):
|
||
"""
|
||
A simple listener and response. This makes it easy to change for
|
||
subclasses of NPCs reacting differently to says.
|
||
"""
|
||
|
||
curr_state = Knocker_Convo(talker.db.knocker_conversation_state or 0)
|
||
# message will be on the form `<Person> says, "say_text"`
|
||
# we want to get only say_text without the quotes and any spaces
|
||
message = message.split('says, ')[1].strip(' "')
|
||
|
||
# Let's see if a keyword gives a good response:
|
||
for [regex, convo_state, responses, new_state] in self.all_responses:
|
||
full_regex = r".*" + regex + r".*"
|
||
|
||
if re.match(full_regex, message, re.IGNORECASE) \
|
||
and curr_state.value >= convo_state.value:
|
||
# logger.info(f"Spoke: {message}, matched: {full_regex} and {curr_state} >= {convo_state}")
|
||
|
||
# If we have the ring in our mouth, we are muffled:
|
||
if self.has("brass ring"):
|
||
return choice(self.muffled_responses)
|
||
else:
|
||
if new_state and new_state.value > curr_state.value:
|
||
talker.db.knocker_conversation_state = new_state
|
||
|
||
# The first match is the password, so we set a tag
|
||
# on the character, so that they can go through
|
||
# the door:
|
||
if talker.db.knocker_conversation_state == Knocker_Convo.ANSWERED:
|
||
talker.tags.add("open_red_door", category="mp")
|
||
|
||
return routput(choice(responses))
|
||
|
||
|
||
|
||
# We can not do a random response. Of course, we are
|
||
# pretending we are hard of hearing, so we don't spam the room
|
||
# _every_ time:
|
||
if random() * 100 < 45:
|
||
if self.has("brass ring"):
|
||
return choice(self.muffled_responses)
|
||
|
||
# If a keyword was not spoken, we want to emphasize that
|
||
# we are hard of hearing, and mention that every other
|
||
# time, so we store in the database a setting to keep
|
||
# track and alternate the responses:
|
||
elif self.db.hard:
|
||
self.db.hard = False
|
||
if talker.tags.get(key="open_red_door", category="mp"):
|
||
return routput(choice(self.after_unlocked_responses))
|
||
else:
|
||
return routput(choice(self.unknown_responses))
|
||
else:
|
||
self.db.hard = True
|
||
return routput(choice(self.cant_hear_responses))
|
||
|
||
def get_display_desc(self, looker, **kwargs):
|
||
# Use this for random information instead of self.db.desc
|
||
response = "In a shape of a bald goblin, the brass door knocker in the center of the red door"
|
||
if self.has("brass ring"):
|
||
response += " holds a ring in its mouth. "
|
||
response += "<<You think it ^ You are sure it ^ You could've sworn it ^ It>> <<just ^ >> winked at you."
|
||
else:
|
||
response += " <<smiles ^ looks>> at you expectantly. "
|
||
|
||
return routput(response)
|
||
|
||
def get_display_things(self, looker):
|
||
return ""
|
||
|
||
def msg(self, text=None, from_obj=None, **kwargs):
|
||
"Custom msg() method reacting to say."
|
||
if from_obj != self:
|
||
# make sure to not repeat what we ourselves said or we'll create a loop
|
||
is_say = False
|
||
is_whisper = False
|
||
|
||
try:
|
||
# debug(f"text[0]: {text[0]}, text[1]: {text[1]}")
|
||
# if text comes from a say, `text` is `('say_text', {'type': 'say'})`
|
||
say_text, is_say = text[0], text[1]['type'] == 'say'
|
||
is_whisper = text[1]['type'] == 'whisper'
|
||
except Exception:
|
||
pass
|
||
|
||
if is_whisper:
|
||
self.at_say("I'm a little hard of hearing, can you speak up?")
|
||
elif is_say:
|
||
# First get the response (if any)
|
||
response = self.at_heard_say(say_text, from_obj)
|
||
# If there is a response
|
||
if response is not None:
|
||
self.at_say(response)
|
||
|
||
# this is needed if anyone ever puppets this NPC - without it you would never
|
||
# get any feedback from the server (not even the results of look)
|
||
super().msg(text=text, from_obj=from_obj, **kwargs)
|
||
|
||
def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs):
|
||
"""
|
||
If the ring is removed, we make a comment.
|
||
"""
|
||
if moved_obj.key == "brass ring" and target_location != self:
|
||
self.at_say("Oh my, that feels better.")
|
||
return True
|
||
return False
|
||
|
||
def at_pre_object_receive(self, arriving_object,
|
||
source_location, **kwargs):
|
||
"""
|
||
The knocker only accepts the ring as a gift.
|
||
"""
|
||
return True if arriving_object.key == "brass ring" else False
|
||
|
||
def at_object_receive(self, obj, source_location,
|
||
move_type, **kwargs):
|
||
"""
|
||
If the ring is returned, we make a comment.
|
||
"""
|
||
if obj.key == "brass ring":
|
||
delay(2, self.chewing)
|
||
return True
|
||
|
||
def chewing(self):
|
||
"""
|
||
Make a muffled comment.
|
||
"""
|
||
self.at_say(choice(self.muffled_responses))
|