Add 'pet' command for friendly pets.
This commit is contained in:
parent
254a8ace79
commit
e6742e4ab8
6 changed files with 182 additions and 26 deletions
|
|
@ -4,7 +4,6 @@ from evennia import CmdSet
|
|||
from evennia.commands.default.general import CmdGive, NumberedTargetCommand
|
||||
|
||||
from commands.command import Command
|
||||
# from typeclasses.pets import Pet
|
||||
|
||||
|
||||
class CmdFeed(Command, NumberedTargetCommand):
|
||||
|
|
|
|||
24
commands/pets.py
Executable file
24
commands/pets.py
Executable file
|
|
@ -0,0 +1,24 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from evennia import CmdSet
|
||||
|
||||
from commands.command import Command
|
||||
|
||||
class CmdPet(Command):
|
||||
"""
|
||||
Pet a 'friendly' pet.
|
||||
"""
|
||||
key = "pet"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Implements the pet command.
|
||||
"""
|
||||
self.obj.pet_response(self.caller)
|
||||
|
||||
class CmdPetSet(CmdSet):
|
||||
"""
|
||||
Things associated with pets.
|
||||
"""
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdPet)
|
||||
|
|
@ -127,10 +127,10 @@ class Character(Object, GenderCharacter):
|
|||
for thing in self.contents:
|
||||
to = thing.locks.get('tethered')
|
||||
if to:
|
||||
m = match(r".*:id\(#?(.*)\)", to)
|
||||
m = match(r".*:id\((.*)\)", to)
|
||||
if m:
|
||||
id_num = m.group(1)
|
||||
dest = self.global_search(f"#{id_num}")
|
||||
dest = self.search(f"{id_num}")
|
||||
msg = thing.db.tethered_msg
|
||||
if dest and msg:
|
||||
thing.location = dest
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ from typeclasses.objects import Object
|
|||
from typeclasses.characters import Character
|
||||
# from typeclasses.lightables import LightSource
|
||||
from commands.feedables import CmdFeedSet
|
||||
from commands.pets import CmdPetSet
|
||||
from utils.word_list import squish, choices, split_party_msg
|
||||
|
||||
|
||||
|
|
@ -220,6 +221,8 @@ class Friendly(Pet):
|
|||
"""
|
||||
super().at_object_creation()
|
||||
|
||||
self.cmdset.add(CmdPetSet)
|
||||
|
||||
# We have a list of actions that were spammed to the room:
|
||||
self.db.last_actions = []
|
||||
|
||||
|
|
@ -371,7 +374,14 @@ class Friendly(Pet):
|
|||
|
||||
def update_state(self, *args, **kwargs):
|
||||
"""
|
||||
Hrm.
|
||||
Call regularly to adjust the pet's reaction state.
|
||||
|
||||
Change the state associated with _all_ characters, as well
|
||||
as characters in the area, based on 'loneliness_amount' and
|
||||
'shyness_amount' respectively.
|
||||
|
||||
Then, if 'active_amount' is triggered, call 'do_action'.
|
||||
|
||||
"""
|
||||
super().update_state(*args, **kwargs)
|
||||
self.adjust_all(self.db.loneliness_amount or -1)
|
||||
|
|
@ -408,6 +418,33 @@ class Friendly(Pet):
|
|||
self.db.last_actions = self.db.last_actions[-5:]
|
||||
split_party_msg(focus, msg)
|
||||
|
||||
def pet_response(self, petter):
|
||||
"""
|
||||
Called with 'petter' attempts to 'pet' this.
|
||||
Reaction should be based on petter reaction state.
|
||||
"""
|
||||
match self.friendly_reaction(petter):
|
||||
case Reaction.SCARED:
|
||||
msg = self.db.pet_scared_response
|
||||
self.adjust_character(petter, self.db.pet_scared_adjust or 0)
|
||||
case Reaction.CONCERNED:
|
||||
msg = self.db.pet_concerned_response
|
||||
self.adjust_character(petter, self.db.pet_concerned_adjust or 0)
|
||||
case Reaction.INTERESTED:
|
||||
msg = self.db.pet_interested_response
|
||||
self.adjust_character(petter, self.db.pet_interested_adjust or 1)
|
||||
case Reaction.FRIENDLY:
|
||||
msg = self.db.pet_friendly_response
|
||||
self.adjust_character(petter, self.db.pet_friendly_adjust or 8)
|
||||
case Reaction.ECSTATIC:
|
||||
msg = self.db.pet_ecstatic_response
|
||||
self.adjust_character(petter, self.db.pet_ecstatic_adjust or 10)
|
||||
if msg:
|
||||
split_party_msg(petter, msg)
|
||||
else:
|
||||
petter.msg(f"You pet {self.name}.")
|
||||
|
||||
|
||||
|
||||
class BHB(Friendly):
|
||||
def return_appearance(self, looker):
|
||||
|
|
@ -532,7 +569,6 @@ class BHB(Friendly):
|
|||
case Reaction.CONCERNED:
|
||||
msg = "The beast walks over to the stick, sniffs it, and backs away."
|
||||
self.adjust_character(thrower, 5)
|
||||
|
||||
case Reaction.INTERESTED:
|
||||
msg = choices("""
|
||||
The beast <<runs ^ hurries>> at the stick, then looks at <you>, <<wondering ^ curious as to ^ pondering>> what to do with a stick the flies back to its owner. ;;
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class Trinket(Object):
|
|||
# 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:
|
||||
self.tags.add("hidden_ball")
|
||||
looker.tags.add("hidden_ball")
|
||||
|
||||
# Seen all the trinkets? Oh boy, well, let's loop:
|
||||
if self.db.last_trinket_num >= len(self.msgs):
|
||||
|
|
@ -99,6 +99,38 @@ class Ring(Object):
|
|||
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):
|
||||
|
|
|
|||
|
|
@ -1,12 +1,61 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import random
|
||||
import re
|
||||
from itertools import batched
|
||||
from random import choice
|
||||
from re import compile, sub, split
|
||||
|
||||
from evennia.utils import logger
|
||||
|
||||
def squish(text):
|
||||
"Remove series of spaces from the text."
|
||||
return re.sub('[ \n\t]+', ' ', text).strip()
|
||||
return sub('[ \t]+', ' ', text).strip()
|
||||
|
||||
def _routput_choose(text):
|
||||
"""
|
||||
Pick a choice when text is: one ^ two ^ three
|
||||
Done by splitting the text, and calling random.choice().
|
||||
"""
|
||||
choices = split(r" *\^ *", text)
|
||||
return choice(choices)
|
||||
|
||||
def _routput_empty(entry):
|
||||
"""
|
||||
Return True if entry is empty, e.g. None or blank.
|
||||
False otherwise. Note: Spaces are ignored.
|
||||
"""
|
||||
if entry:
|
||||
if isinstance(entry, str) and entry.strip() == '':
|
||||
return True
|
||||
if isinstance(entry, (list, tuple)) and len(entry) == 0:
|
||||
return True
|
||||
return False
|
||||
return True
|
||||
|
||||
def _routput_pair(no_choice, choices = None):
|
||||
"""
|
||||
Return a list based on the ^-separated options in 'choices'.
|
||||
|
||||
Give a pair of split items, the 'no_choice' is the first section
|
||||
(before the << ... >>>), and the 'choices' is the section between
|
||||
the '<< ... >>' delimiter.
|
||||
"""
|
||||
|
||||
# While unlikely, the text before << and the options between the
|
||||
# << ...>> text may be blank:
|
||||
if _routput_empty(no_choice) and _routput_empty(choices):
|
||||
return ''
|
||||
|
||||
# If we start a string with <<...>>, then thte 'no_choice' option
|
||||
# is blank, so we only return a choice:
|
||||
if _routput_empty(no_choice):
|
||||
return _routput_choose(choices)
|
||||
|
||||
# With text before, but not inside the <<...>> section, we just
|
||||
# return the first part:
|
||||
if _routput_empty(choices):
|
||||
return no_choice
|
||||
|
||||
return no_choice + ' ' + _routput_choose(choices)
|
||||
|
||||
def routput(text, *substitutions):
|
||||
"""
|
||||
|
|
@ -22,13 +71,19 @@ def routput(text, *substitutions):
|
|||
'This feels very comfortable.'
|
||||
"""
|
||||
if text:
|
||||
acc = []
|
||||
for s in text.split("<<"):
|
||||
selections, *rest = s.split(">>")
|
||||
choice = random.choice(re.split(r"\s*\^\s*", selections))
|
||||
acc = acc + [choice] + rest
|
||||
# section is a list of some phrase followed by another phrase
|
||||
# that contains ^-separate choices:
|
||||
sections = split(r" *<< *(.*?) *>> *", text)
|
||||
|
||||
proposal = squish(''.join(acc).format(*substitutions))
|
||||
# Parts are the first section followed by the _rendered_
|
||||
# choices, so this^that is either 'this' or 'that':
|
||||
parts = [_routput_pair(*pair) for pair in batched(sections, 2)]
|
||||
|
||||
# The parts may have empty strings, so we filter those out,
|
||||
# leaving on the phrases to join:
|
||||
phrases = [phrase for phrase in parts if not _routput_empty(phrase)]
|
||||
|
||||
proposal = ' '.join(phrases).format(*substitutions)
|
||||
|
||||
# If a choice is at the end of a sentence, and we could have
|
||||
# "no choice", as in:
|
||||
|
|
@ -36,8 +91,9 @@ def routput(text, *substitutions):
|
|||
# We don't want a version that looks like:
|
||||
# "He searches # ."
|
||||
# with a space before the punctuation:
|
||||
|
||||
return re.sub(r"\s+([!?.,])", "\\1", proposal)
|
||||
#
|
||||
# Also, we should remove all double spaces that may show:
|
||||
return squish(sub(r"\s+([!?.,])", "\\1", proposal))
|
||||
|
||||
|
||||
def choices(text, *substitutions):
|
||||
|
|
@ -47,23 +103,32 @@ def choices(text, *substitutions):
|
|||
Note that text can already be separated as a list or tuple.
|
||||
"""
|
||||
if isinstance(text, str):
|
||||
selections = re.split(r"\s*;;\s*", text)
|
||||
selections = split(r"\s*;;\s*", text)
|
||||
elif isinstance(text, (tuple, list)):
|
||||
selections = text
|
||||
|
||||
if selections:
|
||||
return routput(random.choice(selections), *substitutions)
|
||||
return routput(choice(selections), *substitutions)
|
||||
|
||||
|
||||
def split_party_msg(viewer, msg):
|
||||
def split_party_msg(viewer, msg, *substitutions):
|
||||
"""
|
||||
Send a message to 'viewer' as well as all people in the area.
|
||||
|
||||
Note that 'msg' could have choices separated by ;;
|
||||
As well as random words, separated by << one ^ two ^ three >>
|
||||
As well as one word for 'viewer' and other for rest, as in
|
||||
<( You ^ {0} )> <( give ^ gives )> the ball.
|
||||
"""
|
||||
text = choices(msg, viewer.name.title())
|
||||
pattern = compile(r"\<\( *(.*?) *\^ *(.*?) *\)\>")
|
||||
|
||||
# First a message for the view:
|
||||
viewer.msg(
|
||||
re.sub("<You>", "You", re.sub("<you>", "you", msg))
|
||||
)
|
||||
viewer.msg( pattern.sub("\\1", text) )
|
||||
|
||||
# Then the message for the rest of the area:
|
||||
viewer.location.msg_contents(re.sub("<[Yy]ou>",
|
||||
viewer.name.title(), msg),
|
||||
exclude=viewer)
|
||||
viewer.location.msg_contents(
|
||||
pattern.sub("\\2", text), exclude=viewer)
|
||||
|
||||
# def searsonal(text, **kwargs):
|
||||
# season = kwargs['season'] or kwargs['location'].get_season()
|
||||
|
|
|
|||
Loading…
Reference in a new issue