moss-n-puddles/typeclasses/drinkables.py
Howard Abrams 43a1aefb49 Add a Scoring system to character
As character do things, they can now get a "score" for everything I
think could be interesting/challenging.
2025-08-24 22:34:11 -07:00

561 lines
20 KiB
Python
Executable file
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python
from evennia.prototypes.spawner import spawn
from evennia.utils import logger
from typeclasses.objects import Object
from commands.consumables import CmdSetTeapot, CmdSetFillable
from utils.scoring import Scores
from utils.word_list import routput, choices
import re
import random
def choose_drink(drink_list, name=None, rando=True):
"""
Return an entry from 'drink_list'.
The 'name' must match an entry with a similar 'title' key.
The drink_list should be a list of dicts.
"""
if name and name != "":
rx = rf".*{name}.*"
# logger.info(f"Working on {rx}")
details = [c for c in drink_list
if re.match(rx, c.get("title"), re.IGNORECASE) or
name in c.get("aliases", [])]
if len(details) > 0:
return details[0]
if rando:
return random.choice(drink_list)
return None
TEAS = [
{"title": "black",
"desc": "a rich, bold and <<malty ^ fragrant ^ almost spicy>> black tea",
},
{"title": "green",
"desc": "a green tea with <<grassy ^ floral ^ vanilla ^ vegetal>> notes",
},
{"title": "oolong",
"desc": "a complex and <<earthy ^ floral>> oolong",
},
{"title": "rooibos",
"desc": "an almost <<sweet and ^ >> <<woody ^ nutty ^ vanilla-y>> rooibos herbal infusion",
},
{"title": "dandelion",
"desc": "a roasted herbal infusion of dandelions that tastes a bit like coffee",
},
{"title": "mint",
"desc": "a blend of peppermint and spearmint to create the ultimate minty herbal infusion",
},
{"title": "chamomile",
"desc": "a relaxing, yet subtle infusion of chamomile flowers that makes you want to go to sleep",
},
{"title": "earl",
"desc": "a flowery Earl Grey tea",
},
{"title": "herbal",
"desc": "an herbal infusion of <<chamomile ^ mint ^ chicory root ^ lavender>>",
},
{"title": "chai",
"desc": "a spicy chai tea, tempered with milk and sweetness",
},
{"title": "hibiscus",
"desc": "a dark red, tart herbal infusion of roselle flowers",
},
{"title": "matcha",
"desc": "a frothy and aromatic matcha of the most vibrant green"
},
{"title": "white",
"desc": "a subtle tea with notes of <<rose ^ berries ^ chamomile flowers>>",
},
{"title": "pu-erh",
"desc": "an earthy, almost mushroom flavored, pu-erh tea",
},
{"title": "puerh",
"desc": "an earthy, almost mushroom flavored, pu-erh tea",
},
{"title": "barley",
"desc": "a nutty herbal infusion of barley with just a hint of smokiness",
},
{"title": "chaga",
"desc": "an earthy, mushroomy chaga infusion",},
{
"title": "",
"desc": "",
},
]
class Container(Object):
"""
All re-fillable cups, glasses and bottles inherit from this object.
Database settings:
type: "tea"
details:
"""
# Defaults
sip_amount = 1
fill_amount = 1
details = {"title": "unknown liquid",
"desc": "Not sure what this is."}
empty_msgs = ["It is empty."]
drink_msgs = [
'You take a <<sip ^ sample ^ swig ^ drink>> << of {0}^ >> << ^ from your {2} >>.',
'You <<sip ^ sample ^ swig ^ drink>> some of your {0} << ^ from your {2} >>.',
]
fill_msgs = ["$You() $conj(fill) $pron(your) {2} with {0}."]
def at_object_creation(self):
"""
called at creation
"""
self.db.amount = 0
def do_drink(self, drinker):
"""
Drink command results, gives a message to drinker.
And lowers the amount by the 'sip_amount'.
When empty, resets the container description to its
'empty_desc' setting.
"""
amount = self.db.amount or 0
details = self.db.details or self.details or {}
sip_amount = self.db.sip_amount or self.sip_amount or 1
if amount == 0:
drinker.msg(choices(self.empty_msgs, details["title"],
details["desc"], self.name))
else:
self.db.amount = amount - sip_amount
drinker.msg(choices(self.drink_msgs, details["title"],
details["desc"], self.name))
if self.db.amount == 0:
self.db.desc = self.db.empty_desc
def do_empty(self):
"""
Return the title of the contents and empty the container.
If already empty, return None.
"""
if self.db.amount == 0:
return None
details = self.db.details or self.details or {}
results = (details["title"], self.db.amount, self.db.desc)
self.db.amount = 0
if self.db.empty_name:
self.key = self.db.empty_name
if self.db.empty_desc:
self.db.desc = self.db.empty_desc
return results
def do_fill(self, filler, details):
"""
Fill this container with 'details'.
The containers description will match the contents.
"""
if (self.db.amount or 0) > 0:
already_full = True
else:
already_full = False
# Render and restore the description:
desc = routput(details['desc'])
details['desc'] = desc
self.db.details = details
self.db.amount = self.db.fill_amount or self.fill_amount or 1
if already_full:
msg = f"$You() $conj(empty) $pron(your) {self.name} and then $pron(you) $conj(fill) it with {desc}"
else:
msg = choices((self.db.fill_msgs or self.fill_msgs),
details["title"], desc, self.name)
# Save the current description as the "empty" description,
# for re-using later.
if not self.db.empty_desc:
self.db.empty_desc = self.db.desc
self.db.desc = f"Contains {desc}"
filler.announce_action(msg)
def at_pre_drop(self, dropper):
"""
Let's keep this world tidy.
"""
dropper.announce_action(f"$You() $conj(drop) the {self.name}, shattering it.")
self.delete()
class Bottle(Container):
"""
Generic large container that can be filled.
"""
fill_amount = 8
empty_msgs = [
'Your {2} is empty. Perhaps you can |gfill|n it?',
'It appears that you need to fill your {2}.'
]
def at_object_creation(self):
"""
called at creation
"""
self.db.amount = 0
class Water(Object):
"""
Object that contains water or other fillable liquid.
You must set the following:
@set this/fill_name = "water"
@set this/fill_desc = "fizzy water with bubbles trapped from waterfall."
"""
def at_object_creation(self):
"""
called at creation
"""
self.cmdset.add_default(CmdSetFillable, persistent=True)
class TeaCup(Container):
fill_amount = 4
sip_amount = 1
drink_msgs = [
'You take a sip of {0} tea.',
'You take a sip of {0} tea <<in ^ from>> your <<teacup ^ cup>>.',
'You <<sip ^ sample ^ drink>> from your <<teacup ^ cup>>.',
'You savor {1} in your <<teacup ^ cup>>.',
]
empty_msgs = [
'Your <<teacup ^ cup>> is empty. Perhaps you can |gmake|n some |gtea|n?',
"Your cup certainly doesn't runneth over, as it be quite empty.",
'Alas, you find your cup devoid of any sort of watery infusion.',
'You notice your cup is empty.',
'It appears that you need to refill your tea cup.'
]
fill_msgs = [
'You fill your <<teacup ^ cup>> with {1}.',
'You pour <<some ^ >> {0} tea into your <<teacup ^ cup>>.',
]
def at_pre_drop(self, dropper):
if dropper.location.key in ("Cozy House", "Cramped Kitchen"):
dropper.announce_action("$You() $conj(wash) and $conj(replace) the teacup in the cabinet with the others.")
self.delete()
else:
return True
class Teapot(Object):
def at_object_creation(self):
"""
called at creation
"""
self.cmdset.add_default(CmdSetTeapot, persistent=True)
def do_make_tea(self, drinker, words):
"""
Make tea.
If 'args', try to make a particular type of tea, otherwise,
pick one at random.
"""
tea_choice = choose_drink(TEAS, words)
desc = routput(tea_choice['desc'])
self.db.tea_choice = tea_choice
self.db.desc = f"A large, brown teapot full of {desc}."
drinker.announce_action(f"$You() $conj(make) a teapot of {desc}.")
drinker.score(Scores.make_tea)
def do_fill(self, drinker):
teatype = self.db.tea_choice
if not teatype:
drinker.msg("You need to |gmake tea|n first.")
return
teacup = drinker.has("teacup")
if not teacup:
drinker.msg("You need to |gget teacup|n first.")
return
teacup.do_fill(drinker, self.db.tea_choice)
COCKTAILS = [
{
"title": "Moonlit Mirage",
"type": "cocktail",
"desc": "A shimmering, colorful drink, garnished with glitter and a twist of tart, almost sour fruit.",
"pre": "The bartender grabs a pixie flying by and shakes the glitter from its wings into the glass.",
"flavors": ["lemon", "tartness", "sweetness"],
"effects": [
"You notice the swirling glitter make a pattern.",
"Your cocktail just changed color to << red ^ pink ^ orange ^ yellow ^ blue ^ purple >>."
]
},
{
"title": "Puck's Revenge",
"type": "cocktail",
"desc": "A vibrant concoction of fruit juices and sparkling fairy dust, served in a glass shaped like a flower.",
"flavors": ["fruity", "... fairy dust?"],
"effects": [
"Wow, this drink really packs a punch.",
"You feel a little light-headed.",
]
},
{
"title": "Glimmering Gossamer",
"type": "cocktail",
"desc": "A delicate blend of elderflower liqueur and sparkling wine, adorned with a butterfly-shaped ice cube.",
"pre": "The bartender uses a butterfly net to catch an ice cube and then places it in the glass.",
"flavors": ["elderflower"],
"effects": ["The butterfly shaped ice cube just started fluttering, and flew away."]
},
{
"title": "Whimsical Willow",
"type": "cocktail",
"desc": "A herbal infusion of gin, rosemary, and elderberry, served with a sprig of fresh mint and a splash of tonic.",
"pre": "As the bartender hands over the cocktail, the garnish of a sprig of mint grows a long twisting vine that he casually clips off.",
"flavors": ["fresh mint", "rosemary", "elderberry"],
"effects": [
"Hrm ... did the bartender always have flowers in his hair?",
"Hrm ... why are vines sprouting from everyone's hair?",
]
},
{
"title": "Charmed Chalice",
"type": "cocktail",
"desc": "A mysterious potion that glows softly, made with enchanted spirits and a hint of honey, served in a chalice that changes shape.",
"pre": "Ghostly apparitions appear in the air, as the bartender grabs one, twists it, to add a drop of ectoplasm to the drink.",
"flavors": ["honey", "uhm...ectoplasm"],
"effects": [
"Your cocktail glass just changed into a flute for sparkling wine.",
"Your glass has changed shape into a small vase. Good thing the drink is still in it.",
"Your glass changes shape into a glass bowl, still holding the contents of your drink.",
]
},
{
"title": "Enchanted Elixir",
"type": "cocktail",
"desc": "A deep blue drink with hints of blueberry and a touch of magic, served with a glowing ice sphere.",
"pre": "The bartender opens a box that bathes the bar in light. He takes a glowing sphere of ice and garnishes the cocktail before passing it over.",
"flavors": ["blueberry", "blue"],
"effects": [
"The ice sphere in the glass pulses with light and energy.",
"You see stars everywhere in the room.",
"The room became brighter...maybe that it just because you can see stars.",
]
# - Glowing orbs of light spin about the drinkers head
# - Drinker sees stars … SPELL?
},
{
"title": "Sylvan Serenade",
"type": "cocktail",
"desc": "A melodic blend of apple cider and spiced rum, garnished with a cinnamon stick and a slice of star fruit.",
"pre": "The bartender plays a flute as the jars and ingredients line up and pour themselves into a cocktail shaker.",
"flavors": ["apple", "cider", "spices", "cinnamon"],
"effects": [
"You hear the song of a distant choir.",
"You hear the distant sounds of flutes and tambourines.",
],
},
{
"title": "Brambleberry Bliss",
"type": "cocktail",
"desc": "A sweet and tart mix of brambleberries and gin, served over crushed ice with a sprinkle of edible flowers.",
"pre": "The bartender garnishes the cocktail with a flower pulled from a pot on the bar. The plant immediately wilts and drops away.",
"flavors": ["brambleberries", "berry", "floral"],
"effects": [
"The flowers floating in the glass whispers to you, \"I'm always getting into trouble. I guess that's because I'm a wild flower.\"",
"A flower floating in the drink whispers to you, \"I got a promotion. I guess I was just outstanding in my field.\"",
"The flowers in the cocktail whispers to you, \"I like to drive fast. I guess I'm always putting the petal to the metal.\"",
],
# - The flowers talk .. maybe tell jokes.
},
{
"title": "Twilight Tonic",
"type": "cocktail",
"desc": "A dark, mysterious drink made with blackcurrant and tonic water, served with a slice of lime and a hint of mint.",
"pre": "After the cocktail is complete, the branches above the bar part, and light from the moon shines directly on the glass causing bubbles to form.",
"flavors": ["lime", "blackcurrant", "mint"],
"effects": [
"A shadow falls on your face making you look seductively mysterious.",
"The room briefly grows dim.",
]
},
{
"title": "Bee Knees",
"type": "mead",
"desc": "Slightly fizzy, slightly sweet, served with a wedge of honeycomb.",
"pre": "After filling the glass, the bartender takes a bee out of jar, gently squeezes it until vomits a drop of honey to the concoction.",
"flavors": ["honey", "vanilla"],
"effects": [
"You feel like a dwarf in a tunnel digging a hole. Diggy, diggy hole!",
"Just like being in the Mud World."
]
},
{
"title": "whisky",
"type": "whisky",
"desc": "A dram of smokey, amber ambrosia.",
"flavors": ["tar", "peat", "smoke", "brine", "vanilla", "toffee"],
"effects": [
"This reminds you of your travels in the Mud World.",
"Lovely.",
"Friendships and conversations distilled in a glass.",
"A dram to << certainly ^ >> << savor ^ cherish ^ enjoy>>.",
"Here's to friends that aren't here to share."
]
},
{
"title": "whiskey",
"type": "whiskey",
"desc": "A dram of strong, amber ambrosia.",
"flavors": ["vanilla", "toffee"],
"effects": [
"This reminds you of your travels in the Mud World.",
"Lovely.",
"Friendships and conversations distilled in a glass.",
"A dram to << certainly ^ >> << savor ^ cherish ^ enjoy>>.",
"Here's to friends that aren't here to share."
]
},
{
"title": "ale",
"aliases": ["beer"],
"type": "beer",
"desc": "A malty, only slightly carbonated, opaque beverage with overt herbal notes.",
"flavors": ["rosemary", "bog myrtle", "yarrow"],
"effects": [
"Wonder where they got the recipe, twelfth century England?",
"Tastes like drinking a loaf of bread."
]
},
{
"title": "red wine",
"type": "wine",
"desc": "A rich glass a blood red color with notes of raspberries and toffee.",
"pre": "After popping a cork from a bottle, the bartender then empties the bottle into the glass.",
"flavors": ["earthiness", "toffee", "raspberries"],
"effects": [
"Ah, what was the name of the land in the Mud World where you first had this vintage?",
"You swirl your glass and watch the legs return to the bottom.",
]
},
{
"title": "water",
"type": "water",
"desc": "A clean cocktail with absolutely no taste.",
"flavors": ["water", "blandness"],
"effects": [
"Wow, this drink really packs a punch...NOT",
"Refreshing.",
"Unintoxicating.",
"Your liver is thanking you."
]
},
]
DRINK_COCKTAIL_MSGS = [
# 0 -> drink title or name
# 1 -> type of drink, e.g. cocktail or whiskey
# 2 -> one of the flavors
'You take a <<sip ^ sample ^ drink>> of your << |w{0}|n ^ {1} ^ drink >>.',
'You <<sip ^ sample ^ drink>> your << |w{0}|n ^ {1} ^ drink >>.',
'You << notice ^ savor ^ relish ^ enjoy >> the {2}.',
]
EMPTY_COCKTAIL_MSGS = [
'That {1} went down fast.',
'Hrm ... did you enjoy that {1}?',
'You drain your glass.',
"You've finished that concoction.",
]
class Cocktail(Object):
fill_amount = 4
sip_amount = 1
def make(owner, shaker, name=None):
"""
Create for 'owner', a drink that matches 'name'.
If name doesn't match, get a random one.
"""
details = choose_drink(COCKTAILS, name, rando=False)
if not details or details == []:
shaker.msg(f"No match for '{name}'.")
return
drink = spawn({
"typeclass": "typeclasses.drinkables.Cocktail",
"key": details.get("title"),
"aliases": ["drink", "glass", "cocktail"],
"desc": details.get("desc")
})[0]
drink.db.cocktail_type = details.get("type")
drink.db.flavors = details.get("flavors")
drink.db.effects = details.get("effects")
drink.db.amount = Cocktail.fill_amount
drink.location = owner
pre_msg = details.get("pre", "")
drink.location.msg(routput(pre_msg + " The << bartender ^ barkeep ^ elf >> << passes ^ slides ^ gives ^ hands >> you a |y{0}|n.", drink.name))
owner.score(Scores.get_cocktail)
theroom = drink.location.location
char = owner.sdesc.get() or owner.get_display_name(theroom)
msg = routput(pre_msg + " The << bartender ^ barkeep ^ elf >> << passes ^ slides ^ gives ^ hands >> a {0} to {1}.",
drink.db.cocktail_type, char)
theroom.msg_contents(msg, exclude=owner)
def do_drink(self, drinker):
"""
Called when owner calls the 'drink' command.
"""
amount = self.db.amount or 0
cocktail_type = self.db.cocktail_type or "cocktail"
cocktail_flavors = self.db.flavors
if amount > 2:
flavor = random.choice(cocktail_flavors)
drinker.msg(choices(DRINK_COCKTAIL_MSGS, self.key, cocktail_type, flavor))
elif amount > 0:
drinker.msg(choices(self.db.effects, self.key, cocktail_type))
drinker.score(Scores.finish_cocktail)
else:
self.key = f"{self.db.cocktail_type} glass"
self.aliases.add('glass')
self.db.desc = f"An empty {self.db.cocktail_type} glass"
drinker.msg(choices(EMPTY_COCKTAIL_MSGS, self.key, cocktail_type))
self.db.amount = self.db.amount - self.sip_amount
def at_pre_drop(self, dropper):
if dropper.location.key == 'Wyldwood Bar':
dropper.msg(routput(f"<< A ^ The >> mushroom << man ^ person >> << bounces ^ shimmies over ^ appears >> and takes the {self.name} << from you ^ >>."))
self.delete()
elif dropper.location.key in ("Cozy House", "Cramped Kitchen"):
dropper.msg(routput(f"The {self.db.cocktail_type} << falls ^ drops >> to the << floor ^ ground >> and shatters! A dust ball from under a chair forms into a little impish-looking fellow, who disappears with glass."))
self.delete()
else:
return True