A character can acquire a horn (from stealing it from the witch) and own the horn (traveling at will). The puzzle now gifts the players who win a blue medal for their effort.
315 lines
13 KiB
Python
Executable file
315 lines
13 KiB
Python
Executable file
#!/usr/bin/env python
|
|
|
|
from os.path import exists
|
|
from datetime import datetime
|
|
from re import split, match, sub, IGNORECASE
|
|
from shutil import copyfile
|
|
|
|
from evennia import CmdSet
|
|
from evennia.prototypes.spawner import spawn
|
|
from evennia.utils import logger
|
|
|
|
from commands.command import Command
|
|
from typeclasses.characters import Character
|
|
from typeclasses.objects import Listener
|
|
from utils.word_list import routput
|
|
|
|
|
|
class Puppet(Character, Listener):
|
|
"""
|
|
Special character that if not puppetable, stays put.
|
|
Perhaps responding or logging information to the GM.
|
|
"""
|
|
def at_post_puppet(self, **kwargs):
|
|
"""
|
|
Called just after puppeting has been completed and all
|
|
Account<->Object links have been established.
|
|
|
|
Args:
|
|
**kwargs (dict): Arbitrary, optional arguments for users
|
|
overriding the call (unused by default).
|
|
Notes:
|
|
|
|
You can use `self.account` and `self.sessions.get()` to get
|
|
account and sessions at this point; the last entry in the
|
|
list from `self.sessions.get()` is the latest Session
|
|
puppeting this Object.
|
|
|
|
"""
|
|
self.msg(f"\nYou are puppeting |c{self.key}|n.")
|
|
self.execute_cmd("pose reset")
|
|
self.msg((self.at_look(self.location), {"type": "look"}), options=None)
|
|
|
|
def at_pre_unpuppet(self, **kwargs):
|
|
"""
|
|
Set our 'sleeping' pose.
|
|
"""
|
|
super().at_pre_unpuppet()
|
|
sleep_pose = self.attributes.get("pose_sleep")
|
|
if sleep_pose:
|
|
self.execute_cmd(f"pose {sleep_pose}")
|
|
|
|
def at_post_unpuppet(self, account=None, session=None, **kwargs):
|
|
"""
|
|
Make the character object remain in the room.
|
|
|
|
Args:
|
|
account (DefaultAccount): The account object that just disconnected
|
|
from this object.
|
|
session (Session): Session controlling the connection that
|
|
just disconnected.
|
|
Keyword Args:
|
|
reason (str): If given, adds a reason for the unpuppet. This
|
|
is set when the user is auto-unpuppeted due to being link-dead.
|
|
**kwargs: Arbitrary, optional arguments for users
|
|
overriding the call (unused by default).
|
|
|
|
"""
|
|
self.msg(f"\nNo longer puppeting |c{self.key}|n.\n")
|
|
|
|
def get_display_desc(self, _, **kwargs):
|
|
"""
|
|
Return one of two appearances based on if it is being puppeted or not.
|
|
|
|
Set either or both:
|
|
|
|
@set me/desc_puppeted = "A clown bouncing up and down from a box."
|
|
@set me/desc_uppuppeted = "A colorful box with a closed lid."
|
|
|
|
If either is not specified, it uses the standard description.
|
|
"""
|
|
if self.tags.get(key='puppeted', category='account'):
|
|
return self.db.desc_puppeted if self.db.desc_puppeted else self.db.desc
|
|
else:
|
|
return self.db.desc_unpuppeted if self.db.desc_unpuppeted else self.db.desc
|
|
|
|
def do_gift(self, recipient, gift, name=None, desc=None):
|
|
"""
|
|
Give a 'gift' by name to a 'recipient'.
|
|
|
|
This doesn't give the gift if the recipient has already
|
|
received the gift.
|
|
"""
|
|
gifts = {
|
|
'purse': {"typeclass": "typeclasses.things.CoinPurse",
|
|
"key": name or gift,
|
|
"desc": desc or "Small leather coin purse, with a gold clasp.",
|
|
},
|
|
'ball': {"typeclass": "typeclasses.things.CrystalBall",
|
|
"key": name or "crystal ball",
|
|
"desc": desc or "Swirling glass ball of colored ectoplasm.",
|
|
},
|
|
'dice': {"typeclass": "typeclasses.things.Dice",
|
|
"key": name or "pair of dice",
|
|
"desc": desc or "Two bone knuckles with painted dots.",
|
|
"attr": {
|
|
"number": 2
|
|
}
|
|
},
|
|
'pipe': {"typeclass": "typeclasses.things.Pipe",
|
|
"key": name or gift,
|
|
"desc": desc or "Smoking pipe carved with esoteric symbols.",
|
|
},
|
|
'wand': {"typeclass": "typeclasses.things.Wand",
|
|
"key": name or gift,
|
|
"desc": desc or "Curiously crafted wand carved with runes: ᛑ ᛒ ᚱ",
|
|
},
|
|
'bag': {"typeclass": "typeclasses.things.BagofJunk",
|
|
"key": name or "sack",
|
|
"desc": desc or "Small cloth bag with a leather drawstring.|/You could probably |grummage|n around in that, and maybe |gkeep|n something.",
|
|
},
|
|
'junk': {"typeclass": "typeclasses.things.BagofJunk",
|
|
"key": name or "sack",
|
|
"desc": desc or "Small cloth bag with a leather drawstring.|/You could probably |grummage|n around in that, and maybe |gkeep|n something.",
|
|
"attr": {
|
|
"stuff": "human"
|
|
},
|
|
},
|
|
'blue': {"typeclass": "typeclasses.things.Medal",
|
|
"key": name or "blue medal",
|
|
"desc": desc or "Gold medallion decorated with a trident and conch suspended by a blue ribbon.",
|
|
},
|
|
}
|
|
receiver = self.search(recipient, global_search=True)
|
|
if not receiver:
|
|
logger.info(f"Didn't find {recipient}.")
|
|
return None
|
|
|
|
if gift in gifts.keys() and \
|
|
not receiver.attributes.get(f"received_{gift}"):
|
|
details = gifts[gift]
|
|
logger.info(f"Giving {details['key']} to {receiver.key}")
|
|
|
|
obj = spawn(details)[0]
|
|
for attr, value in enumerate(details.get("attr", {})):
|
|
obj.attributes.add(attr, value)
|
|
obj.location = receiver
|
|
receiver.attributes.add(f"received_{gift}", True)
|
|
|
|
their_name = receiver.get_display_name(self)
|
|
self.announce_action(f"$You() $conj(give) something to {their_name}.",
|
|
exclude=receiver)
|
|
|
|
my_name = self.get_display_name(receiver)
|
|
receiver.msg(f"{my_name} gives you a {obj.name}.")
|
|
return True
|
|
|
|
self.msg(f"You can't give '{gift}' to {receiver.key}... {gifts.keys()}")
|
|
return None
|
|
|
|
|
|
class CmdShrubSay(Command):
|
|
"""Erase and write on the shrub's chalkboard.
|
|
"""
|
|
key = "say"
|
|
aliases = ["write"]
|
|
|
|
def func(self):
|
|
self.obj.write(self.args.strip())
|
|
|
|
|
|
class CmdSetShrubSay(CmdSet):
|
|
"""
|
|
The shrub's version of the 'say' command.
|
|
"""
|
|
def at_cmdset_creation(self):
|
|
super().at_cmdset_creation()
|
|
self.add(CmdShrubSay)
|
|
|
|
|
|
class Shrub(Puppet):
|
|
"""
|
|
The 'Shrub' has its own way of communicating.
|
|
"""
|
|
def at_object_creation(self):
|
|
"Called when a character is first created."
|
|
self.cmdset.add(CmdSetShrubSay, persistent=True)
|
|
|
|
def intro(self):
|
|
self.record_msg("After tuning their instruments, a quartet of pixies start their first number. \"Quiet evening, eh Mushy?\" asks the elf bartender to a stout myconid. It makes a vaguely <i>shrugging</i> motion with a pair of pseudopods it uses to manipulate its environment. It knew the question was rhetorical, as everyone knew the portal was opening, and that always brought characters.")
|
|
self.execute_cmd("look")
|
|
self.record_msg("The bartender says, \"Fetch that bowl of candies for bar, please, Mushy.\"")
|
|
self.execute_cmd("recog elf as bartender")
|
|
self.note("Not sure how I landed in this bar, but here I am. Don't get me wrong, I enjoy it. The bartender, a fairly haughty elf originally from the <i>Mud World</i>, always supplies me with a nice glass of water.")
|
|
self.execute_cmd("look elf")
|
|
self.record_msg("The mushroom man returns, balancing the bowl on its cap. \"One more thing,\" the bartender says, \"I had a box of oddly shaped, orange crackers. I have a feeling they may come in useful.\"")
|
|
self.note("Helping to keep the place respectable, <i>Mushy</i> also gathers empty cocktail glasses. One of the <i>fungus ones</i>, or mushroom people, it too has always been kind to me.")
|
|
# self.execute_cmd("look mushroom man")
|
|
self.record_msg("\"Any requests?\" asks Lemon of the bartender.")
|
|
self.note("Lemon, Cart, Hairy and Ring, this pixie quartet can't decided on a name for their group; currently calling themselves, the <i>Pixie Stones</i>, provide the entertainment.")
|
|
self.execute_cmd("look pixies")
|
|
self.record_msg("The bartender replies, \"Nah, just remember to play that weird <i>juzz</i> music when the boss arrives later.\"")
|
|
self.note("And here I am, an <i>Awakened Shrub</i>, joting down the events that traspire in this wyld bar somewhere in the <i>Domain of Moss</i>, out of reach from both the Seelie and the Unseelie courts. Of course, that may change.")
|
|
self.note("Now then, let's see what characters may arrive...")
|
|
self.record_msg("<hr/>")
|
|
|
|
def note(self, text):
|
|
"""
|
|
Record in the transcript log, a special note.
|
|
"""
|
|
self.record_msg((text, {'type': 'note'}))
|
|
|
|
def write(self, new_text):
|
|
"""
|
|
Change the readable message on the chalkboard.
|
|
|
|
This will also record the new message in the log.
|
|
"""
|
|
here = self.location
|
|
msg = routput("The shrub uses a branch to << brush ^ wipe >> << off ^ >> << the chalk from ^ >> its << small ^ >> chalkboard, and with another branch, << slowly ^ carefully ^ deliberately ^ >> starts to write << something ^ a message ^ >>.")
|
|
here.msg_contents(msg)
|
|
self.db.inside = f"The chalkboard reads: |w{new_text}|n"
|
|
self.record_msg(self.db.inside)
|
|
|
|
def find_actor(self, text, default=None):
|
|
"""
|
|
Extract the character performing the action from the text.
|
|
"""
|
|
if default:
|
|
return default
|
|
|
|
m = match(r".*\|b(.*?)\|n.*", text)
|
|
if m:
|
|
return m.group(1)
|
|
|
|
def capitalize_msg(self, text, msg_type=None):
|
|
"""
|
|
If text is lowercase, capitalize it.
|
|
|
|
Maybe prepend a 'The' to the front.
|
|
"""
|
|
logger.info(f"capitalize: {text} / {msg_type}")
|
|
if msg_type and msg_type in ('traverse', 'teleport'):
|
|
if match(r"^(\|[A-z])?[aeiou]", text):
|
|
return "An " + text
|
|
else:
|
|
return "A " + text
|
|
# If the line is colored and lowercase, it probably assumes a
|
|
# character's sdesc, so let's add a 'the' to the front:
|
|
# Do we need to only do this on 'say'?
|
|
elif match(r"^\|[mb][a-z]", text):
|
|
return "The " + text
|
|
elif match(r"^(\|[A-z])[a-z]", text) and len(text) > 2:
|
|
return text[0:2] + text[2].upper() + text[3:]
|
|
elif match(r"^[a-z]", text) and len(text) > 2:
|
|
return text[0].upper() + text[1:]
|
|
return text
|
|
|
|
def to_html(self, text, msg_type=None):
|
|
"""
|
|
Convert the 'text' to an HTML formatted string.
|
|
"""
|
|
# Yellow text should be italics:
|
|
text = sub(r"\|y(.*?)\|n", '<i>\\1</i>', text)
|
|
# Bold and tag the titles:
|
|
text = sub(r"\|[cb](.*?)\|n", '<b class="title">\\1</b>', text)
|
|
# Bold anything white:
|
|
text = sub(r"\|w(.*?)\|n", '<b class="heavy">\\1</b>', text)
|
|
# Remove the rest:
|
|
text = sub(r"\|[A-z]", '', text)
|
|
# Remove initial or final carriage returns:
|
|
text = sub(r"^\n", '', text)
|
|
text = sub(r"\n$", '', text)
|
|
# Convert the carriage returns:
|
|
text = sub(r"\n", ' <br/>\n', text)
|
|
|
|
if msg_type and msg_type == 'look':
|
|
text = text[0].upper() + text[1:]
|
|
return f"<div class=\"special\"><p>{text}</p></div>\n"
|
|
if msg_type and msg_type == 'note':
|
|
return f"<blockquote>{text}</blockquote>"
|
|
return f"<p>{text}</p>\n"
|
|
|
|
def record_msg(self, text, actor=None):
|
|
msg_type = None
|
|
|
|
if isinstance(text, tuple):
|
|
if text[1] and isinstance(text[1], dict):
|
|
msg_type = text[1]['type']
|
|
msg = self.capitalize_msg(text[0], msg_type)
|
|
else:
|
|
msg = self.capitalize_msg(str(text))
|
|
|
|
actor = self.find_actor(msg, actor)
|
|
msg = self.to_html(msg, msg_type)
|
|
filename = datetime.today().strftime('transcripts/%Y-%m-%d.html')
|
|
if not exists(filename):
|
|
copyfile("transcripts/header-template.html", filename)
|
|
|
|
with open(filename, "a") as myfile:
|
|
myfile.write(msg)
|
|
|
|
# Follow-up Actions ...
|
|
if match(r".* arrives .*", msg) and actor and \
|
|
msg_type in ('traverse', 'teleport'):
|
|
self.execute_cmd(f"look {actor}")
|
|
|
|
def msg(self, text=None, from_obj=None, session=None, **kwargs):
|
|
"""
|
|
Record everything that happens in the room for a transcript.
|
|
|
|
Some key events may trigger more actions to get more information.
|
|
"""
|
|
self.record_msg(text, from_obj)
|
|
super().msg(text, from_obj, session)
|