Automated puppets react on triggers
This commit is contained in:
parent
428fa8334b
commit
2e69558b3f
5 changed files with 187 additions and 42 deletions
|
|
@ -123,32 +123,14 @@ class CmdSay(MuxCommand):
|
||||||
"""
|
"""
|
||||||
Implements the new 'say' command with switches.
|
Implements the new 'say' command with switches.
|
||||||
"""
|
"""
|
||||||
def charname(name):
|
speaker = self.caller
|
||||||
try:
|
|
||||||
results = self.caller.search(name, quiet=True)
|
|
||||||
return results.get_display_name(self)
|
|
||||||
except:
|
|
||||||
return name
|
|
||||||
|
|
||||||
def chars_for_self(chars):
|
|
||||||
return [charname(c) for c in split(r"[ ,]+", chars)]
|
|
||||||
|
|
||||||
def chars_for_others(chars):
|
|
||||||
return [c if c.startswith('/') else '/'+c for c in split(r"[ ,]+", chars)]
|
|
||||||
|
|
||||||
def chars_list(chars, verb, for_rp=True):
|
|
||||||
char_lst = chars_for_others(chars) if for_rp else chars_for_self(chars)
|
|
||||||
return (' to ' if verb == 'say' else ' ') + \
|
|
||||||
iter_to_str(char_lst, endsep='and')
|
|
||||||
|
|
||||||
# logger.info(f"CmdSayIt: {self.cmdstring} lhs={self.lhs} switches={self.switches}")
|
|
||||||
|
|
||||||
if not self.args:
|
if not self.args:
|
||||||
self.caller.msg("Say what?")
|
speaker.msg("Say what?")
|
||||||
return
|
return
|
||||||
|
|
||||||
if 'to' in self.switches and not self.rhs:
|
if 'to' in self.switches and not self.rhs:
|
||||||
self.caller.msg(paragraph("""
|
speaker.msg(paragraph("""
|
||||||
When attempting to say something to one or more
|
When attempting to say something to one or more
|
||||||
characters, use the '=' character to identify what you
|
characters, use the '=' character to identify what you
|
||||||
want to say.
|
want to say.
|
||||||
|
|
@ -161,7 +143,7 @@ class CmdSay(MuxCommand):
|
||||||
speech = self.args
|
speech = self.args
|
||||||
|
|
||||||
# If speech is empty, stop here
|
# If speech is empty, stop here
|
||||||
if not self.caller.at_pre_say(speech):
|
if not speaker.at_pre_say(speech):
|
||||||
return
|
return
|
||||||
|
|
||||||
adverb = ''
|
adverb = ''
|
||||||
|
|
@ -188,27 +170,50 @@ class CmdSay(MuxCommand):
|
||||||
|
|
||||||
for_me, for_others, verb = \
|
for_me, for_others, verb = \
|
||||||
speech_effect(speech, verb,
|
speech_effect(speech, verb,
|
||||||
self.caller,
|
speaker,
|
||||||
self.caller.attributes.get(category="effect",
|
speaker.attributes.get(category="effect",
|
||||||
return_obj=True,
|
return_obj=True,
|
||||||
return_list=True))
|
return_list=True))
|
||||||
|
|
||||||
|
to_who = to_whom = ''
|
||||||
|
|
||||||
|
if 'to' not in self.switches:
|
||||||
|
for char in speaker.location.contents:
|
||||||
|
if hasattr(char, 'other_say') and callable(char.other_say):
|
||||||
|
char.other_say(speaker, for_others)
|
||||||
|
else:
|
||||||
|
# Send the message to 'puppets' that have the special 'other_sayto'
|
||||||
|
# method (if they are the object of the message):
|
||||||
|
targets = split(r" *, *", self.lhs)
|
||||||
|
to_chars = [speaker.search(c, quiet=True)[0] for c in targets]
|
||||||
|
for char in to_chars:
|
||||||
|
if hasattr(char, 'other_sayto') and callable(char.other_sayto):
|
||||||
|
logger.info(f"Found {char.key}: {for_others}")
|
||||||
|
char.other_sayto(speaker, for_others)
|
||||||
|
|
||||||
|
who = iter_to_str([c.get_display_name(speaker) for c in to_chars])
|
||||||
|
whom = iter_to_str([f"/{c}" for c in targets])
|
||||||
|
|
||||||
|
if verb == "ask":
|
||||||
|
to_who = " " + who
|
||||||
|
to_whom = " " + whom
|
||||||
|
else:
|
||||||
|
to_who = " to " + who
|
||||||
|
to_whom = " to " + whom
|
||||||
|
|
||||||
# The `send_emote` is _global_, so if we want to say to 'Bob',
|
# 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 say ...', we have to both send him a message with the
|
||||||
# 'You' as well as everyone else with the 'send_emote'.
|
# 'You' as well as everyone else with the 'send_emote'.
|
||||||
|
full_speech = f"You {adverb}{verb}{to_who}, \"{for_me}\""
|
||||||
to_whom = chars_list(self.lhs, verb, for_rp=False) if 'to' in self.switches else ''
|
speaker.msg(full_speech, from_obj=speaker)
|
||||||
full_speech = f"You {adverb}{verb}{to_whom}, \"{for_me}\""
|
|
||||||
self.caller.msg(full_speech, from_obj=self.caller)
|
|
||||||
|
|
||||||
# English is weird...
|
# English is weird...
|
||||||
if verb == 'reply':
|
if verb == 'reply':
|
||||||
verb = 'replie'
|
verb = 'replie'
|
||||||
|
|
||||||
targets = [item for item in self.caller.location.contents if item != self.caller]
|
targets = [item for item in speaker.location.contents if item != speaker]
|
||||||
to_whom = chars_list(self.lhs, verb) if 'to' in self.switches else ''
|
|
||||||
full_speech = f"/me {adverb}{verb}s{to_whom}, \"{for_others}\""
|
full_speech = f"/me {adverb}{verb}s{to_whom}, \"{for_others}\""
|
||||||
send_emote(self.caller, targets, full_speech, msg_type="say", anonymous_add=None)
|
send_emote(speaker, targets, full_speech, msg_type="say", anonymous_add=None)
|
||||||
|
|
||||||
|
|
||||||
class CmdThink(Command):
|
class CmdThink(Command):
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,9 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
|
||||||
This just looks better to me.
|
This just looks better to me.
|
||||||
"""
|
"""
|
||||||
def capitalize_line(line):
|
def capitalize_line(line):
|
||||||
if match(r"^\|[bm][a-z].*", line):
|
if not line or line.strip() == "":
|
||||||
|
return line
|
||||||
|
elif match(r"^\|[bm][a-z].*", line):
|
||||||
return "The " + line
|
return "The " + line
|
||||||
elif match(r"^\|[A-z][a-z].*", line):
|
elif match(r"^\|[A-z][a-z].*", line):
|
||||||
return line[0:1] + line[2].upper() + line[3:]
|
return line[0:1] + line[2].upper() + line[3:]
|
||||||
|
|
@ -326,3 +328,26 @@ class Character(Object, GenderCharacter, ContribRPCharacter):
|
||||||
self.spell_sequence(self.location,
|
self.spell_sequence(self.location,
|
||||||
self.db.reappear_msg.split(';;'),
|
self.db.reappear_msg.split(';;'),
|
||||||
self.db.appear_delay or 2)
|
self.db.appear_delay or 2)
|
||||||
|
|
||||||
|
# Hooks to the puppets:
|
||||||
|
|
||||||
|
def puppets_here(self):
|
||||||
|
"""
|
||||||
|
Return a list of puppets in the current location.
|
||||||
|
Only used for calling hooks on the animatronic dolls.
|
||||||
|
"""
|
||||||
|
return [puppet for puppet in
|
||||||
|
self.search("", typeclass="typeclasses.puppets.Puppet",
|
||||||
|
location=self.location, quiet=True)
|
||||||
|
if puppet != self]
|
||||||
|
|
||||||
|
def at_post_move(self, past_location, move_type="move", **kwargs):
|
||||||
|
super().at_post_move(past_location, move_type)
|
||||||
|
for puppet in self.puppets_here():
|
||||||
|
puppet.other_arrive(self)
|
||||||
|
|
||||||
|
def at_pre_move(self, destination, move_type="move", **kwargs):
|
||||||
|
super().at_pre_move(destination, move_type)
|
||||||
|
for puppet in self.puppets_here():
|
||||||
|
puppet.other_leave(self)
|
||||||
|
return True
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,16 @@ Use the ObjectParent class to implement common features for *all* entities
|
||||||
with a location in the game world (like Characters, Rooms, Exits).
|
with a location in the game world (like Characters, Rooms, Exits).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from re import split, match
|
||||||
|
|
||||||
# from evennia.objects.objects import DefaultObject
|
# from evennia.objects.objects import DefaultObject
|
||||||
from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPObject
|
from evennia.contrib.rpg.rpsystem.rpsystem import ContribRPObject
|
||||||
from evennia.utils import delay
|
from evennia.utils import delay, logger
|
||||||
# from evennia.utils import delay, logger, search
|
# from evennia.utils import delay, logger, search
|
||||||
|
|
||||||
|
from utils.word_list import routput
|
||||||
|
|
||||||
|
|
||||||
class ObjectParent:
|
class ObjectParent:
|
||||||
"""
|
"""
|
||||||
|
|
@ -245,6 +250,50 @@ class Object(ObjectParent, ContribRPObject):
|
||||||
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def delay_sequence(self, sequence_str, time_delay=1, *args):
|
||||||
|
"""Run a sequence of messages or commands with a delay.
|
||||||
|
|
||||||
|
The 'sequence_str' is a number of commands separated by ';;'
|
||||||
|
character sequence. The command can by standard things like
|
||||||
|
'say' or 'give', but can be a message delivered to the room
|
||||||
|
location, if it begins with a # or 'gm'.
|
||||||
|
|
||||||
|
The command can also be a number, in which case, the next
|
||||||
|
command (and all subsequent commands), will be delayed by that
|
||||||
|
number of seconds.
|
||||||
|
|
||||||
|
For instance:
|
||||||
|
|
||||||
|
'say Hello there, want a drink? ;; 8 ;; # He works on shaking a cocktail. ;; 2 ;; shake whisky = avatar'
|
||||||
|
|
||||||
|
Which could show:
|
||||||
|
|
||||||
|
Blonde elf says, "Hello there, want a drink?"
|
||||||
|
<8 seconds pass>
|
||||||
|
He works on shaking a cocktail.
|
||||||
|
<2 seconds pass>
|
||||||
|
You now have a whisky.
|
||||||
|
"""
|
||||||
|
def convert(x):
|
||||||
|
try:
|
||||||
|
return int(x)
|
||||||
|
except ValueError:
|
||||||
|
return x
|
||||||
|
|
||||||
|
lines = [convert(line) for line in split(r" *;; *", sequence_str)]
|
||||||
|
pause = 0
|
||||||
|
for line in lines:
|
||||||
|
if isinstance(line, int):
|
||||||
|
time_delay = line
|
||||||
|
else:
|
||||||
|
pause = pause + time_delay
|
||||||
|
|
||||||
|
m = match(r"(# *|gm +)(.*)", line)
|
||||||
|
if m:
|
||||||
|
delay(pause, self.location.msg_contents, routput(m.group(2), *args))
|
||||||
|
else:
|
||||||
|
delay(pause, self.execute_cmd, routput(line, *args))
|
||||||
|
|
||||||
def at_post_move(self, source_location, move_type="move", **kwargs):
|
def at_post_move(self, source_location, move_type="move", **kwargs):
|
||||||
"""
|
"""
|
||||||
Delete ourselves if we are living inside a pet.
|
Delete ourselves if we are living inside a pet.
|
||||||
|
|
@ -252,3 +301,11 @@ class Object(ObjectParent, ContribRPObject):
|
||||||
super().at_post_move(source_location, move_type)
|
super().at_post_move(source_location, move_type)
|
||||||
if self.location.is_typeclass("typeclasses.pets.Pet"):
|
if self.location.is_typeclass("typeclasses.pets.Pet"):
|
||||||
delay(5, self.delete)
|
delay(5, self.delete)
|
||||||
|
|
||||||
|
def at_give(self, giver, getter, **kwargs):
|
||||||
|
"""
|
||||||
|
Call a puppet's 'other_given' method, if defined.
|
||||||
|
"""
|
||||||
|
super().at_give(giver, getter)
|
||||||
|
if hasattr(getter, 'other_given') and callable(getter.other_given):
|
||||||
|
getter.other_given(giver, self)
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from re import split, match, IGNORECASE
|
||||||
|
from evennia.utils import logger
|
||||||
|
|
||||||
from typeclasses.characters import Character
|
from typeclasses.characters import Character
|
||||||
from utils.word_list import routput
|
from utils.word_list import routput
|
||||||
|
|
||||||
|
|
@ -25,7 +28,7 @@ class Puppet(Character):
|
||||||
puppeting this Object.
|
puppeting this Object.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.msg("\nYou are puppeting |c{name}|n.".format(name=self.key))
|
self.msg(f"\nYou are puppeting |c{self.key}|n.")
|
||||||
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
||||||
|
|
||||||
def at_pre_unpuppet(self, **kwargs):
|
def at_pre_unpuppet(self, **kwargs):
|
||||||
|
|
@ -51,7 +54,7 @@ class Puppet(Character):
|
||||||
overriding the call (unused by default).
|
overriding the call (unused by default).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.msg("\nNo longer puppeting |c{name}|n.\n".format(name=self.key))
|
self.msg(f"\nNo longer puppeting |c{self.key}|n.\n")
|
||||||
|
|
||||||
def get_display_desc(self, _, **kwargs):
|
def get_display_desc(self, _, **kwargs):
|
||||||
"""
|
"""
|
||||||
|
|
@ -68,3 +71,62 @@ class Puppet(Character):
|
||||||
return self.db.desc_puppeted if self.db.desc_puppeted else self.db.desc
|
return self.db.desc_puppeted if self.db.desc_puppeted else self.db.desc
|
||||||
else:
|
else:
|
||||||
return self.db.desc_unpuppeted if self.db.desc_unpuppeted else self.db.desc
|
return self.db.desc_unpuppeted if self.db.desc_unpuppeted else self.db.desc
|
||||||
|
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# TRIGGERS
|
||||||
|
|
||||||
|
def get_character_label(self, character):
|
||||||
|
return character.get_display_name(self).split(' ')[-1]
|
||||||
|
|
||||||
|
def trigger_sequence(self, seq, character, *other_stuff):
|
||||||
|
self.delay_sequence(seq, 1, character.key, character.get_display_name(self),
|
||||||
|
self.get_character_label(character), *other_stuff)
|
||||||
|
|
||||||
|
def get_attribute_trigger(self, label, character):
|
||||||
|
"""
|
||||||
|
Return the attribute where 'key' is the label, and the
|
||||||
|
category can by either the 'character' or its display name.
|
||||||
|
"""
|
||||||
|
name = self.get_character_label(character)
|
||||||
|
|
||||||
|
return (self.attributes.get(key=label, category=character.key) or
|
||||||
|
self.attributes.get(key=label, category=name) or
|
||||||
|
self.attributes.get(key=label))
|
||||||
|
|
||||||
|
def move_triggers(self, label, character):
|
||||||
|
"""
|
||||||
|
Return a list of triggers matching 'label' and 'character'.
|
||||||
|
"""
|
||||||
|
seq = self.get_attribute_trigger(label, character)
|
||||||
|
if seq:
|
||||||
|
self.trigger_sequence(seq, character)
|
||||||
|
|
||||||
|
def other_arrive(self, character):
|
||||||
|
"""
|
||||||
|
Execute a command when a character arrives in the same location.
|
||||||
|
"""
|
||||||
|
self.move_triggers('arrive', character)
|
||||||
|
|
||||||
|
def other_leave(self, character):
|
||||||
|
"""
|
||||||
|
Execute a command when a character arrives in the same location.
|
||||||
|
"""
|
||||||
|
self.move_triggers('leave', character)
|
||||||
|
|
||||||
|
def say_triggers(self, label, character, speech):
|
||||||
|
trigs = self.get_attribute_trigger(label, character)
|
||||||
|
if trigs:
|
||||||
|
for _, trigger in enumerate(dict(trigs)):
|
||||||
|
seq = trigs[trigger]
|
||||||
|
if match(trigger, speech, IGNORECASE):
|
||||||
|
self.trigger_sequence(seq, character)
|
||||||
|
|
||||||
|
def other_say(self, speaker, speech):
|
||||||
|
self.say_triggers('say', speaker, speech)
|
||||||
|
|
||||||
|
def other_sayto(self, speaker, speech):
|
||||||
|
self.say_triggers('sayto', speaker, speech)
|
||||||
|
|
||||||
|
def other_given(self, giver, obj):
|
||||||
|
target = giver.get_display_name(self).split(' ')[-1]
|
||||||
|
self.execute_cmd(f"emote /Me says to /{target}, \"Thanks for the {obj.name}.\"")
|
||||||
|
|
|
||||||
|
|
@ -115,14 +115,10 @@ def choices(text, *substitutions):
|
||||||
Note that text can already be separated as a list or tuple.
|
Note that text can already be separated as a list or tuple.
|
||||||
"""
|
"""
|
||||||
if isinstance(text, str):
|
if isinstance(text, str):
|
||||||
selections = split(r"\s*;;\s*", text)
|
text = split(r"\s*;;\s*", text)
|
||||||
elif isinstance(text, (tuple, list)):
|
|
||||||
selections = text
|
|
||||||
else:
|
|
||||||
selections = None
|
|
||||||
|
|
||||||
if selections:
|
if text:
|
||||||
return routput(choice(selections), *substitutions)
|
return routput(choice(text), *substitutions)
|
||||||
|
|
||||||
|
|
||||||
def split_party_msg(viewer, msg, *substitutions):
|
def split_party_msg(viewer, msg, *substitutions):
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue