diff --git a/commands/wizards.py b/commands/wizards.py index cb2d590..9406ca4 100755 --- a/commands/wizards.py +++ b/commands/wizards.py @@ -1,17 +1,19 @@ #!/usr/bin/env python from random import choice -from re import match +from re import match, findall from evennia import CmdSet, create_script from evennia.utils import delay, logger from evennia.commands.default.muxcommand import MuxCommand from evennia.contrib.rpg.rpsystem import send_emote +from evennia.prototypes.spawner import spawn +from evennia.utils.utils import int2str from .command import Command from typeclasses.scripts import DonkeyHeadSpell from typeclasses.drinkables import Cocktail -from utils.word_list import routput +from utils.word_list import routput, pluralize class CmdFly(Command): @@ -88,6 +90,98 @@ class CmdMagic(Command): wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3) +class CmdMakeItem(Command): + """ + Create one or more items from thin air. + + Usage: + + make [ number ] name [ description ] + + Where 'effects' is a statement of what happens after the magic erupts. + Note that you can have multiple effects separated by two semicolons. + + You can tailor the effects to your character with the following: + + - |y$you()|n: Replaced by "you" and your name for others, like "old gnome" + - |y$your()|n: Replaced by "your" and your possessive name for others, like "old gnome's" + - |y$conj(verb)|n: Replaced by "verb" for you, and "verbs" for others. + - |y$pron(you)|n: Replaced by "you" for you, but "he" or "she" for others. + - |y$pron(your)|n: Replaced by "your" for you, but "his" or "her" for others. + + While flexible, you would use this complex replacement in either + |gnicks|n or as part of a standard magical prefix, by setting the + property: + + @set self/magic_msg = "$You() $conj(shake) $pron(your) necklace of bones!" + """ + key = "make" + locks = "cmd:holds()" + + def parse(self): + """ + Allows the following phrases: + + candy + 2 candy Piece of Turkish Delight. + candy Piece of Turkish Delight. + "turkish delight" Piece of Turkish Delight. + 4 "turkish delight" Piece of Turkish Delight. + """ + pattern = r'"(.*?)"|(\S+)' + matches = findall(pattern, self.args.strip()) + + try: + self.item_number = int(matches[0][0] or matches[0][1]) + self.item_name = matches[1][0] or matches[1][1] + start = 2 + except ValueError: + self.item_number = 1 + self.item_name = matches[0][0] or matches[0][1] + start = 1 + + words = [m[0] or m[1] for m in matches[start:]] + self.item_desc = " ".join(words) + + def func(self): + """ + Call the 'do_magic' method on the caller. + """ + wizard = self.caller + msgs = wizard.db.make_msg or wizard.db.magic_msg + if msgs: + msgs = msgs.split(';;') + else: + msgs = [ + "$You() $conj(snap) $pron(your) fingers.", + ] + + if self.item_number == 1: + if match(r"^[aeiou]"): + name = f"an {self.item_name}" + else: + name = f"a {self.item_name}" + else: + name = int2str(self.item_number) + " " + \ + pluralize(self.item_name) + + # from evennia.utils.utils import int2str ; print(int2str("2")) + msgs = msgs + [ name + " appear in $pron(your) hand." ] + + wizard.spell_sequence(None, msgs, wizard.db.magic_delay or 3) + + # FIXME So that the cat will eat it and it can't be littered: + if not self.item_desc or self.item_desc == "": + self.item_desc = f"Conjured by {wizard.get_name()}." + + for _ in range(self.item_number): + item = spawn({ + "typeclass": "typeclasses.consumables.Litterable", + "key": self.item_name, + "desc": self.item_desc + })[0] + item.location = wizard + class CmdSetWand(CmdSet): """ All wizard spells are tied to a 'wand' that might be flavored. @@ -96,6 +190,7 @@ class CmdSetWand(CmdSet): super().at_cmdset_creation() self.add(CmdFly) self.add(CmdMagic) + self.add(CmdMakeItem) class CmdMakeCocktail(MuxCommand): diff --git a/transcripts/cozy-header-template.html b/transcripts/cozy-header-template.html new file mode 100644 index 0000000..9815552 --- /dev/null +++ b/transcripts/cozy-header-template.html @@ -0,0 +1,48 @@ + +
+{text}
{text}" + return f"
{text}
\n" + + + 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 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)) + + # Make things a little more interesting by substituting the + # name... + names = actor and actor.db.alt_names + if names: + logger.info("lets change this") + msg = sub(f"^{actor.sdesc.get()}", choice(names), + msg, flags=IGNORECASE) + + actor = self.find_actor(msg, actor) + + msg = self.to_html(msg, msg_type) + filename = datetime.today().strftime(self.db.transcripts_path) + + if not exists(filename): + makedirs(dirname(filename), exist_ok=True) + copyfile(self.db.header_file, 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}") diff --git a/typeclasses/pets.py b/typeclasses/pets.py index 9e792b1..cf30286 100755 --- a/typeclasses/pets.py +++ b/typeclasses/pets.py @@ -1,6 +1,4 @@ #!/usr/bin/env python -# Emacs environement -# (setq python-shell-interpreter "/Users/howard/src/moss-n-puddles/.venv/bin/ipython") """ Pets @@ -16,7 +14,7 @@ from time import time import random from evennia import TICKER_HANDLER -from evennia.utils import logger +from evennia.utils import logger, delay from evennia.utils.gametime import schedule from evennia.utils.search import search_object @@ -482,9 +480,10 @@ class WeeBeastie(Friendly, Familiar, Listener): "Override to return a string in response to message." owner = self.search("Dabbler") if owner: - owner.announce_action(f"$Your() {name} purrs.") + delay(3, owner.announce_action, + f"$Your() {self.get_name()} purrs.") else: - self.execute_cmd(f"emote /me purrs.") + delay(3, self.execute_cmd, f"emote /me purrs.") def feed(self, feeder, item=None): """ @@ -492,9 +491,14 @@ class WeeBeastie(Friendly, Familiar, Listener): the character has, and go with that... """ # Categorize items that can be used to feed the beast: - def is_flower(item): - return (not item and feeder.has("yellow flower")) or \ - (item and item.key == 'yellow flower') + def is_some(item, name): + return (not item and feeder.has(name)) or \ + (item and item.key == name) + + def is_edible(item): + return is_some(item, "yellow flower") or \ + is_some(item, "candy") or \ + is_some(item, "turkish delight") # Based on the reaction to the feeder, the adjectives may alter: noun = "The " + random.choice(["wee", "furry", "white", "adorable"]) + " beastie" @@ -521,10 +525,11 @@ class WeeBeastie(Friendly, Familiar, Listener): "rubs its wee widdle head under $pron(you,op) chin in gratitude", ]) - if is_flower(item): - msg = f"{noun} {how_sniff} sniffs $your() << hand holding a ^>> flower. It {how_eat} eats it, and {and_then}." + edible = is_edible(item) + if edible: + msg = f"{noun} {how_sniff} sniffs $your() << hand holding a ^>> {edible}. It {how_eat} eats it, and {and_then}." self.adjust_character(feeder, 100) - feeder.has('yellow flower').delete() + edible.delete() else: msg = f"{noun} doesn't appear interested in anything you have." @@ -654,7 +659,6 @@ class BHB(Friendly): if msg: feeder.announce_action(msg) - # feeder.msg(msg) def thrown_stick(self, thrower): """ diff --git a/typeclasses/puppets.py b/typeclasses/puppets.py index ac9279a..98dc62e 100755 --- a/typeclasses/puppets.py +++ b/typeclasses/puppets.py @@ -1,16 +1,13 @@ #!/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.utils import logger from commands.command import Command from typeclasses.characters import Character -from typeclasses.objects import Listener +from typeclasses.objects import Listener, Recorder from utils.word_list import routput @@ -101,7 +98,7 @@ class CmdSetShrubSay(CmdSet): self.add(CmdShrubSay) -class Shrub(Puppet): +class Shrub(Puppet, Recorder): """ The 'Shrub' has its own way of communicating. """ @@ -145,89 +142,6 @@ class Shrub(Puppet): 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", '\\1', text) - # Bold and tag the titles: - text = sub(r"\|[cb](.*?)\|n", '\\1', text) - # Bold anything white: - text = sub(r"\|w(.*?)\|n", '\\1', 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", '{text}
{text}" - return f"
{text}
\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. diff --git a/typeclasses/things.py b/typeclasses/things.py index 4ff020b..a3ba961 100755 --- a/typeclasses/things.py +++ b/typeclasses/things.py @@ -25,7 +25,7 @@ from commands.wizards import CmdSetWand from utils.word_list import routput, choices, paragraph from utils.scoring import Scores from typeclasses.consumables import Litterable -from typeclasses.objects import Object +from typeclasses.objects import Object, Recorder from typeclasses.scripts import KnockScript @@ -936,7 +936,7 @@ class BagofJunk(Object): self.db.latest_item = item where = self.db._sdesc or self.name - msg = routput(f"$You() << $conj(scrounge) ^ $conj(rummage) ^ $conj(fish) ^ $conj(rifle) ^ $conj(put) $pron(your) hand >> << around ^ >> in $pron(your) {where}, and << $conj(pull) out ^ $conj(find) ^ $conj(stare) at >> |w{item}|n.") + msg = routput(f"$You() << $conj(scrounge) ^ $conj(rummage) ^ $conj(fish) ^ $conj(rifle) ^ $conj(put) $pron(your) hand >> << around ^ >> in $pron(your) {where}, and << $conj(pull) out ^ $conj(find) ^ $conj(stare) at >>|w{item}|n.") owner.announce_action(msg) def do_keep(self, keeper, description=None): @@ -1142,3 +1142,16 @@ class GlobalAlarmClock(Object): if start: chan.msg(start) delay(seconds, chan.msg, finish) + + +class Scribe(Recorder): + """ + Probably invisible script that records room events. + + Set the following properties: + + @set scribe/directory = "transcripts" + @set scribe/header = "transcripts/header-template.html" + """ + def msg(self, text=None, from_obj=None, session=None, **kwargs): + self.record_msg(text, from_obj) diff --git a/utils/word_list.py b/utils/word_list.py index 84100b1..1fed938 100755 --- a/utils/word_list.py +++ b/utils/word_list.py @@ -147,3 +147,28 @@ def choices(text, *substitutions): if text: return routput(choice(text), *substitutions) + + +def pluralize(noun): + """Convert a singular noun to its plural form.""" + # Basic pluralization rules + + # Change 'y' to 'ies' if preceded by a consonant + if noun.endswith('y') and noun[-2] not in 'aeiou': + return noun[:-1] + 'ies' + + # Add 'es' for words ending in 's', 'x', 'z', 'ch', or 'sh' + elif noun.endswith('s') or noun.endswith('x') or \ + noun.endswith('z') or noun.endswith('ch') or \ + noun.endswith('sh'): + return noun + 'es' + + # Default case for regular nouns + else: + return noun + 's' + +# print(pluralize("dog")) # Outputs: dogs +# print(pluralize("candy")) # Outputs: candies +# print(pluralize("box")) # Outputs: boxes +# print(pluralize("bush")) # Outputs: bushes +# print(pluralize("city")) # Outputs: cities