476 lines
19 KiB
Python
Executable file
476 lines
19 KiB
Python
Executable file
#!/usr/bin/env python
|
||
|
||
from collections import defaultdict
|
||
from random import choice, randint, random
|
||
|
||
from evennia import CmdSet
|
||
from evennia.commands.default.muxcommand import MuxCommand
|
||
from evennia.prototypes.spawner import spawn
|
||
from evennia.utils import delay, iter_to_str, logger
|
||
|
||
from commands.command import Command
|
||
from typeclasses.objects import Object
|
||
from typeclasses.scripts import Script
|
||
from typeclasses.drinkables import Container
|
||
from utils.scoring import Scores
|
||
from utils.word_list import choices
|
||
|
||
|
||
class CmdEmpty(Command):
|
||
"""
|
||
Empty the cauldron.
|
||
|
||
Usage:
|
||
|
||
empty [ cauldron ]
|
||
|
||
Empty the cauldron before you start working on making a potion.
|
||
This destroys either the ingredients or the potion that it contains.
|
||
|
||
Next, you can start |gadd|ning the potion's components.
|
||
"""
|
||
key = "empty"
|
||
|
||
def func(self):
|
||
self.obj.do_empty(self.caller)
|
||
|
||
|
||
class CmdAdd(Command):
|
||
"""
|
||
Add an item from your inventory to the cauldron.
|
||
|
||
Usage:
|
||
|
||
add <item> [ to cauldron ]
|
||
|
||
The item can be a single item you picked up on your travels, like
|
||
a bunch of tickleweed, or can be the contents of a container, like
|
||
a bottle or teacup.
|
||
|
||
Keep in mind that potions may require |wenough|n of an item.
|
||
For instance, a bottle can contain more liquid than a teacup.
|
||
(You can get an empty bottle from the shelf.)
|
||
|
||
Once all ingredients have been added, you can |gcreate|n a potion.
|
||
"""
|
||
priority = 2
|
||
key = "add"
|
||
aliases = "give"
|
||
|
||
def func(self):
|
||
maker = self.caller
|
||
item_str = self.args.strip().split(" to ")
|
||
if len(item_str) == 0:
|
||
maker.msg(f"What do you want to add to the {self.obj.name}?")
|
||
return
|
||
|
||
item = maker.search(item_str[0], location=maker)
|
||
if item:
|
||
self.obj.do_add(self.caller, item)
|
||
|
||
|
||
class CmdCreate(Command):
|
||
"""
|
||
Create a potion from the contents of the cauldron.
|
||
|
||
Usage:
|
||
|
||
create [ potion ]
|
||
|
||
After you have |gadd|ned all the ingredients for a potion, cast
|
||
this. If you have the correct components, the cauldron will
|
||
contain a potion ready for you to |gbottle|n.
|
||
"""
|
||
key = "create"
|
||
aliases = ["make", "brew", "cook"]
|
||
|
||
def func(self):
|
||
self.obj.do_create(self.caller)
|
||
|
||
|
||
class CmdBottle(Command):
|
||
"""Bottle the contents of the cauldron.
|
||
|
||
Usage:
|
||
|
||
bottle [ potion ]
|
||
|
||
After you have |gcreate|nd a potion, use this command to put it
|
||
into a vial you can consume later (or give to a friend).
|
||
|
||
If you |gdrop|n the vial, you can always get another potion from
|
||
the cauldron (at least, until some one calls |gempty|n).
|
||
"""
|
||
key = "bottle"
|
||
aliases = ["fill", "get vial"]
|
||
|
||
def func(self):
|
||
self.obj.do_bottle(self.caller)
|
||
|
||
|
||
class CmdSetCauldron(CmdSet):
|
||
"""
|
||
The commands available to people next to a cauldron.
|
||
"""
|
||
def at_cmdset_creation(self):
|
||
self.add(CmdEmpty)
|
||
self.add(CmdAdd)
|
||
self.add(CmdCreate)
|
||
self.add(CmdBottle)
|
||
|
||
|
||
class Cauldron(Object):
|
||
"""
|
||
A cauldron where we can collect and mix ingredients.
|
||
|
||
- empty
|
||
- add
|
||
- mix
|
||
- bottle
|
||
"""
|
||
def at_object_creation(self):
|
||
"""
|
||
Associate the commands with this instance.
|
||
"""
|
||
self.cmdset.add_default(CmdSetCauldron)
|
||
|
||
def return_appearance(self, looker):
|
||
"""
|
||
Return the full description and contents of this object.
|
||
|
||
Along with a description of this object, we want to return the
|
||
ingredients and items in this object. For that, we duplicate a
|
||
lot of already implemented code.
|
||
"""
|
||
full_desc = self.db.desc
|
||
num_items = len(self.contents)
|
||
if num_items == 0:
|
||
return full_desc + "|/It is empty, and ready for you to |gadd|n ingredients."
|
||
|
||
# Had to copy the following code from the objects.objects'
|
||
# get_display_things so that I could reformat it:
|
||
grouped_things = defaultdict(list)
|
||
for thing in self.contents:
|
||
grouped_things[thing.get_display_name(looker)].append(thing)
|
||
|
||
thing_names = []
|
||
for thingname, thinglist in sorted(grouped_things.items()):
|
||
nthings = len(thinglist)
|
||
thing = thinglist[0]
|
||
singular, plural = thing.get_numbered_name(nthings, looker, key=thingname)
|
||
thing_names.append(singular if nthings == 1 else plural)
|
||
thing_names = iter_to_str(thing_names)
|
||
|
||
return full_desc + \
|
||
f"|/It contains {thing_names}." if thing_names else ""
|
||
|
||
def do_empty(self, maker=None):
|
||
"""
|
||
Delete the contents of the cauldron.
|
||
|
||
Also randomly return a funny emptying message.
|
||
"""
|
||
if self.contents == []:
|
||
maker.msg(f"The {self.name} is already empty.")
|
||
return
|
||
|
||
msg = choice([
|
||
f"The imp <<climbs ^ flies >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew, then <<downs ^ swallows ^ drinks >> it, emptying the cauldron. Lethargically, it << hoists itself ^ climbs back >> to its branch.",
|
||
"The imp produces a long metal straw and slurps up the brew from the cauldron.",
|
||
"The imp pushes down a lever, flushing the contents of cauldron.",
|
||
"The imp << stokes the ^ breathes >> fire, quickly boiling the brew until it evaporates. It licks clean the remaining sludge, emptying the cauldron."
|
||
])
|
||
if maker:
|
||
maker.announce_action(msg)
|
||
for item in self.contents:
|
||
item.delete()
|
||
|
||
def do_add_liquid(self, maker, obj):
|
||
"""
|
||
Like do_add, but doesn't delete the container.
|
||
|
||
Instead this creates a new "object" based on the contents of
|
||
the container. Makes it safe to delete.
|
||
"""
|
||
name, amount, desc = obj.do_empty()
|
||
if name:
|
||
for cup in range(int(amount / 4)):
|
||
liquid = spawn({
|
||
"typeclass": "typeclasses.objects.Object",
|
||
"key": f"cup of {name}"
|
||
})[0]
|
||
liquid.location = self
|
||
|
||
maker.announce_action(f"$You() $conj(pour) the {name} from $pron(your) {obj.name} into the {self.name}.")
|
||
else:
|
||
maker.msg(f"The {obj.name} is empty. You can |gfill|n it first.")
|
||
|
||
def do_add(self, maker, item):
|
||
"""
|
||
Moves an item to the cauldron.
|
||
|
||
If the item is a container, we call do_add_liquid to add its contents.
|
||
We limit this to Herbs and other consumables.
|
||
"""
|
||
if self.has_potion():
|
||
maker.msg("The cauldron already contains a potion. You need to |gempty|n it first.")
|
||
return
|
||
|
||
if item.has_method("do_empty"):
|
||
self.do_add_liquid(maker, item)
|
||
elif item.is_typeclass("typeclasses.consumables.Consumable") or \
|
||
item.is_typeclass("typeclasses.consumables.Herb"):
|
||
item.move_to(self, quiet=True)
|
||
maker.announce_action(f"$You() $conj(add) {item.name} to {self.name}.")
|
||
else:
|
||
maker.msg("Adding that to a cauldron for brewing potions doesn't make sense.")
|
||
|
||
def do_create(self, maker):
|
||
"""
|
||
Does this make a viable concoction?
|
||
"""
|
||
good = choice([
|
||
"The imp <<climbs ^ flies ^ jumps >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew. Before returning to its roost, it << gives $you() a tiny thumbs up ^ nods ^ smiles ^ nods >>.",
|
||
])
|
||
bad = choice([
|
||
"The imp <<climbs ^ flies ^ jumps >> down from its perch, <<sniffs ^ tastes ^ samples >> the brew. Before returning to its roost, it << shakes its head ^ grimaces ^ retches a bit >>.",
|
||
"As soon as $you() $conj(grab) a wooden spoon to stir, the <<brew ^ concoction>> in the cauldron farts a black cloud. This must not be right."
|
||
])
|
||
|
||
if self.contents == []:
|
||
maker.msg(f"The {self.name} is empty. First, |gadd|n ingredients.")
|
||
return
|
||
|
||
for seq, brew_func in [
|
||
self.can_create_laughter(),
|
||
self.can_create_drugtrip()
|
||
]:
|
||
if seq:
|
||
maker.score(Scores.make_potion)
|
||
maker.announce_action(good)
|
||
for idx, msg in enumerate(seq):
|
||
delay(idx * 3 + 2, maker.announce_action, msg)
|
||
self.do_empty()
|
||
return brew_func()
|
||
|
||
maker.announce_action(bad)
|
||
|
||
def has_potion(self):
|
||
"""
|
||
Return reference to the potion in the cauldron.
|
||
None otherwise.
|
||
"""
|
||
if len(self.contents) == 1 and self.has(Potion):
|
||
return self.contents[0]
|
||
|
||
def do_bottle(self, maker):
|
||
"""
|
||
If the contents are viable, let's create a vial.
|
||
"""
|
||
potion = self.has_potion()
|
||
if not potion:
|
||
maker.msg("The cauldron doesn't have a potion to bottle. Perhaps, you need to |gcreate|n one first?")
|
||
return
|
||
|
||
vial = spawn({
|
||
"typeclass": "typeclasses.alchemy.Vial",
|
||
"key": potion.name,
|
||
"aliases": ["potion", "vial"],
|
||
"desc": potion.db.desc
|
||
})[0]
|
||
vial.db.amount = 1
|
||
vial.db.spell = potion.db.spell
|
||
vial.location = maker
|
||
maker.announce_action(f"$You() $conj(fill) a small vial with $pron(your) << stew ^ elixir ^ potion ^ concoction >>.")
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Potions:
|
||
|
||
def can_create_laughter(self):
|
||
"""
|
||
Return true if the cauldron can make a laughter potion.
|
||
"""
|
||
mushrooms = self.search("gigglecap mushroom", location=self, quiet=True)
|
||
weeds = self.search("tickleweed", location=self, quiet=True)
|
||
water = self.search("fizzy water", location=self, quiet=True)
|
||
|
||
if len(mushrooms) > 0 and len(weeds) > 0 and len(water) > 1:
|
||
return ([
|
||
"$You() $conj(stir) the cauldron with a wooden spoon, watching the liquid change to a deep purple, releasing a fragrant aroma.",
|
||
"The imp climbs down from its perch and mutters, \"Risus ignis, laetitiae flamma, in corde nostro, gaudium humourous.\"",
|
||
"Sparks of vibrant octarine pop over the elixir, making it ready to |gbottle|n."
|
||
], self.create_laughter)
|
||
else:
|
||
return (None, None)
|
||
|
||
def create_laughter(self):
|
||
"""
|
||
Spawn a Potion with the spell function for the cauldron's contents.
|
||
"""
|
||
potion = spawn({
|
||
"typeclass": "typeclasses.alchemy.Potion",
|
||
"key": "potion",
|
||
"desc": "Small glass vial containing a purple liquid, labeled: |mElixir Risorium|n"
|
||
})[0]
|
||
potion.location = self
|
||
potion.db.spell = LaughterSpell
|
||
|
||
def can_create_drugtrip(self):
|
||
"""
|
||
Return true if the cauldron can make the drug trip potion.
|
||
"""
|
||
mushrooms = self.search("dreamshade mushroom", location=self, quiet=True)
|
||
berries = self.search("moonberries", location=self, quiet=True)
|
||
dust = self.search("pixie dust", location=self, quiet=True)
|
||
water = self.search("still water", location=self, quiet=True)
|
||
|
||
if len(mushrooms) > 0 and len(berries) > 0 and len(dust) > 0 and len(water) > 1:
|
||
return ([
|
||
"$You() $conj(stir) the cauldron with a wooden spoon, watching the liquid change to a deep purple, releasing a fragrant aroma.",
|
||
"The imp climbs down from its perch and mutters, \"Risus ignis, laetitiae flamma, in corde nostro, gaudium humourous.\"",
|
||
"Sparks of vibrant octarine pop over the elixir, making it ready to |gbottle|n."
|
||
], self.create_drugtrip)
|
||
else:
|
||
return (None, None)
|
||
|
||
def create_drugtrip(self):
|
||
"""
|
||
Spawn a Potion with the spell function for the cauldron's contents.
|
||
"""
|
||
potion = spawn({
|
||
"typeclass": "typeclasses.alchemy.Potion",
|
||
"key": "potion",
|
||
"desc": "Small glass vial containing a glowing blue liquid, labeled: |mSomnium Illusorium|n"
|
||
})[0]
|
||
potion.location = self
|
||
potion.db.spell = DrugtripSpell
|
||
|
||
class Vial(Container):
|
||
"""
|
||
A vial with a single quaff, but cast a spell.
|
||
"""
|
||
fill_amount = 1
|
||
|
||
def at_object_creation(self):
|
||
"""
|
||
Set up the database.
|
||
"""
|
||
self.db.amount = 0
|
||
self.db.empty_name = "empty vial"
|
||
self.db.empty_desc = "Small crystal vial. May have contained a potion."
|
||
|
||
def do_drink(self, drinker):
|
||
"""
|
||
Called when owner calls the 'drink' command.
|
||
"""
|
||
if not self.do_empty():
|
||
drinker.msg("The vial is empty.")
|
||
else:
|
||
drinker.announce_action("$You() $conj(<< down ^ quaff ^ imbibe >>) a small vial.")
|
||
self.do_empty()
|
||
drinker.scripts.add(self.db.spell, autostart=True)
|
||
|
||
|
||
class Potion(Object):
|
||
"""
|
||
A marker-type object for a potion in the cauldron.
|
||
"""
|
||
pass
|
||
|
||
|
||
# ----------------------------------------------------------------------
|
||
# Scripts that emulate the effects of Potions
|
||
# ----------------------------------------------------------------------
|
||
|
||
class LaughterSpell(Script):
|
||
"""
|
||
This class defines the script itself
|
||
"""
|
||
|
||
def at_script_creation(self):
|
||
self.key = "laughter-spell"
|
||
self.desc = "Adds various timed events to a character."
|
||
self.interval = 20 # seconds
|
||
self.repeats = 15 # repeat only a certain number of times
|
||
self.start_delay = True # wait self.interval until first call
|
||
|
||
def at_repeat(self):
|
||
"""
|
||
This gets called every self.interval seconds. We make
|
||
a random check here so as to only return 33% of the time.
|
||
"""
|
||
if random() < 0.66:
|
||
# no message this time
|
||
return
|
||
self.send_random_message()
|
||
|
||
def send_random_message(self):
|
||
self.obj.announce_action(
|
||
choice([
|
||
"$You() $conj(erupt) into laughter, a deep, rolling sound that fills the room and makes everyone turn to see what’s so funny.",
|
||
"$You() $conj(let) out a series of high-pitched giggles, each one bubbling up like a fizzy drink, light and infectious.",
|
||
"$You() $conj(burst) into cackling laughter, the sharp, gleeful sound that echo off the walls.",
|
||
"$You() $conj(find) yourself wheezing with laughter, gasping for air as the hilarity overwhelms you, tears streaming down your cheeks.",
|
||
"$You() $conj(break) into a fit of snorting giggles; you try to surpress them to no avail.",
|
||
"$You() $conj(let) out a tinkling laugh, a melodic sound that dances through the air, bringing a sense of whimsy to the moment.",
|
||
"$You() $conj(roar) with laughter, a booming sound that resonates with joy.",
|
||
"$You() $conj(chuckle) softly, a warm and genuine sound that reflects your delight.",
|
||
"$You() $conj(giggle) uncontrollably, making it hard for $pron(you) to catch $pron(your) breath.",
|
||
"$You() $conj(let) a stifled chuckle, unsure what became so humorous.",
|
||
]))
|
||
|
||
class DrugtripSpell(Script):
|
||
"""
|
||
This class defines the script itself
|
||
"""
|
||
def at_script_creation(self):
|
||
self.key = "drug-trip-spell"
|
||
self.desc = "Adds various timed events to a character."
|
||
self.interval = 120 # seconds
|
||
self.repeats = 4 # repeat only a certain number of times
|
||
self.start_delay = True # wait self.interval until first call
|
||
|
||
def at_repeat(self):
|
||
"""
|
||
This gets called every self.interval seconds. We make
|
||
a random check here so as to only return 33% of the time.
|
||
"""
|
||
if random() < 0.56:
|
||
self.send_random_message()
|
||
|
||
def send_random_message(self):
|
||
"""
|
||
Send a random message to the 'victim' of this spell,
|
||
and another message to the other people in the room.
|
||
"""
|
||
you_msg, other_msg = choice([
|
||
("You feel your bones turn into hollow reeds. A warm breeze blows through your ribs, playing a hauntingly beautiful flute melody that tastes like honey.",
|
||
"You see the $you() stand perfectly still, emitting a loud, melodic whistling sound from $pron(your,sp) nostrils that may attract a confused songbird."),
|
||
|
||
("You are standing on the ceiling of the sky. The fluffy clouds are surprisingly firm, like soft islands, you must hop across to avoid falling. \"Be quiet,\" you whisper to yourself, \"Don't wake the cloud people.\"",
|
||
"You see the $you() frantically \"climbing\" a nearby tree, though $pron(you,sp) is doing it upside down and backward with terrifying, twitchy agility. \"Be quiet,\" $pron(you,sp) say, \"Don't wake the cloud people.\""),
|
||
|
||
("Your shadow stretches and detaches itself, grows a mouth, and begins whispering to you, \"The trees once had names, but $pron(you,sp)'ve forgotten them. Now $pron(you,sp) desperately want to steal your name, so never use your real name to the woods.\"",
|
||
"You see the $you() engaged in a heated, whispered argument with the ground at $pron(your,sp) feet, eventually pointing a finger and ordering $pron(your,sp) own shadow to \"stay.\""),
|
||
|
||
("You feel yourself stretch and grow, breaking out of the area you knew to reach for the sky. You are a gargantuan giant! Every step feels like it should crush a mountain; you move slowly to avoid destroying the trees.",
|
||
"You see the $you() moving in extreme slow-motion, lifting $pron(your,sp) feet two feet high for every step and looking down at something on the ground with intense pity."),
|
||
|
||
("With loud squawk, you see your hands turn into a pair of bickering, multicolored pheasants! $pron(You,Sp) painfully start to fly away in opposite directions.",
|
||
"You see the $you() shove $pron(your,sp) hands deep into $pron(your,sp) armpits, hunched over and making muffled, frantic clucking noises while $pron(your,sp) elbows flap wildly."),
|
||
|
||
("Thousands of tiny, glowing yellow spiders start weaving a suit of |wGlimmer-Silk|n onto your body. You feel the itch of a thousand needles. Ah, but this silk suit will be wonderful when they are done.",
|
||
"You see the $you() begin frantically stripping off $pron(your,sp) clothes, shouting that $pron(your,sp) clothes are \"smothering the velvet.\""),
|
||
|
||
("The ground turns into a liquid mosaic of memories. Walking feels like treading water in a pool of your own childhood dreams.",
|
||
"You see the $you() drop and begin \"swimming\" across the floor, performing a remarkably proficient breaststroke."),
|
||
|
||
("Your mind is clear, as you realize you've been playing a character in some play this whole time...wearing green tights and a tunic. You also know the |waudience|n has been watching you from behind the trees, and you feel the need to give them a performance of a lifetime. ",
|
||
"You see the $you() break into a booming, theatrical monologue, \"If we shadows have offended, think but this, and all is mended! That you have but slumbered here while these visions did appear. And this weak and idle theme, no more yielding but a dream! Gentles, do not reprehend: if you pardon, we will mend!\" $pron(You,sp) then takes a big bow."),
|
||
])
|
||
self.obj.msg(you_msg)
|
||
self.obj.announce_action(other_msg, self.obj)
|
||
delay(10, self.obj.msg,
|
||
choices("""Your << world ^ reality ^ clarity >> returns, and you feel << back to ^ >> normal. ;; You << snap back ^ re-fade back >> to your << old ^ original >> << self ^ mind >>."""
|
||
))
|