As character do things, they can now get a "score" for everything I think could be interesting/challenging.
398 lines
14 KiB
Python
Executable file
398 lines
14 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
|
||
|
||
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
|
||
|
||
seq, brew_func = self.can_create_laughter()
|
||
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()
|
||
brew_func()
|
||
else:
|
||
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
|
||
|
||
|
||
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):
|
||
rand = randint(1, 10)
|
||
|
||
if rand == 1:
|
||
msg = "$You() $conj(erupt) into laughter, a deep, rolling sound that fills the room and makes everyone turn to see what’s so funny."
|
||
elif rand == 2:
|
||
msg = "$You() $conj(let) out a series of high-pitched giggles, each one bubbling up like a fizzy drink, light and infectious."
|
||
elif rand == 3:
|
||
msg = "$You() $conj(burst) into cackling laughter, the sharp, gleeful sound that echo off the walls."
|
||
elif rand == 4:
|
||
msg = "$You() $conj(find) yourself wheezing with laughter, gasping for air as the hilarity overwhelms you, tears streaming down your cheeks."
|
||
elif rand == 5:
|
||
msg = "$You() $conj(break) into a fit of snorting giggles; you try to surpress them to no avail."
|
||
elif rand == 6:
|
||
msg = "$You() $conj(let) out a tinkling laugh, a melodic sound that dances through the air, bringing a sense of whimsy to the moment."
|
||
elif rand == 7:
|
||
msg = "$You() $conj(roar) with laughter, a booming sound that resonates with joy."
|
||
elif rand == 8:
|
||
msg = "$You() $conj(chuckle) softly, a warm and genuine sound that reflects your delight."
|
||
elif rand == 9:
|
||
msg = "$You() $conj(giggle) uncontrollably, making it hard for $pron(you) to catch $pron(your) breath."
|
||
else:
|
||
msg = "$You() $conj(let) a stifled chuckle, unsure what became so humorous."
|
||
|
||
self.obj.announce_action(msg)
|