#!/usr/bin/env python from random import choice, random, randint from re import split, match, IGNORECASE from enum import Enum from evennia import create_script from evennia.commands.command import InterruptCommand from evennia.prototypes.spawner import spawn from evennia.utils import (logger, delay, iter_to_str, int2str, search) from evennia.utils.search import search_channel from commands.misc import (CmdSetPuddle, CmdSetStick, CmdSetKnock, CmdSetSmoke, CmdSetRummage, CmdSetDice, CmdSetPress, CmdSetTransform, CmdSetPeer) from commands.consumables import CmdSetMakeConsumable from commands.wizards import CmdSetWand, CmdSetScry from utils.word_list import routput, choices, paragraph from utils.scoring import Scores from typeclasses.consumables import Litterable from typeclasses.objects import Object, Recorder from typeclasses.scripts import KnockScript class Medal(Object): """ A medal is mostly a marker for showing how far you made. """ class CoinPurse(Object): """ A pouch containing the character coins. Only the player can see how many coins it contains. """ def at_object_creation(self): """ Set the initial number of coins. """ self.db.gold_amount = 10 def return_appearance(self, looker, **kwargs): """ Return description based on the 'looker'. """ if self.location == looker: if self.has_amount(1): return f"Contains {int2str(self.db.gold_amount)} gold coins." return "It is empty." return "Probably containing coins." def how_much(self): """ Return the amount of gold coins in the purse. """ return self.db.gold_amount or 0 def adjust_amount(self, amount): """ Change the amount of gold in the purse. The 'amount' can be a negative number. """ owner = self.location if self.how_much() + amount < 0: raise InterruptCommand("Not enough coins") self.db.gold_amount = self.how_much() + amount if self.how_much() == 1: owner.msg("You now have one gold coin.") else: owner.msg(f"You now have {int2str(self.how_much())} gold coins.") def has_amount(self, at_least): """Return True is pouch has 'at_least' amount of coins.""" return self.how_much() >= at_least class Rope(Litterable): """ A rope can be used in particular circumstances. """ def search_exit(self, name): x = search.objects(name, typeclass="typeclasses.exits.Exit") try: return x[0] except IndexError: return None def hut_exits(self): return (self.search_exit("hut on stilts"), self.search_exit("rope-bound hut")) def do_use(self, caller, obj=None): if caller.location.key == "Mellow Marsh" or (obj and obj.key == "Homey Hut"): # Randomly miss? hut1_exit, hut2_exit = self.hut_exits() hut1_exit.move_to(None, to_none=True, quiet=True) hut2_exit.move_to(caller.location, quiet=True) caller.location.add_room_state("bound") caller.announce_action("$You() $conj(tie) a knot in $pron(your) vine rope, and $conj(throw) a lasso around the hut, binding the stilts so it can't run away.") delay(3, caller.location.msg_contents, "Something tells you that the rope won't last for long.") delay(60, self.hut_breaks_free, caller.location) else: caller.msg("Unclear how to use the rope that way.") def hut_breaks_free(self, marsh): hut1_exit, hut2_exit = self.hut_exits() hut1_exit.move_to(marsh, quiet=True) hut2_exit.move_to(None, to_none=True, quiet=True) marsh.remove_room_state("bound") marsh.msg_contents("The hut, straining against its bounds, finally breaks free to run around the muddy marsh, rendering the vine rope useless.") self.delete() class Trinket(Object): """ For instance: """ msgs = [ "You see a crystal ball. When the swirling vortex stops, It reads, '<>'.", "You find a deck of Tarot cards, but the card of Death has been replaced by a worker in an office cubical that reads, 'Mostly Death'.", "You see a statue of a <> dragon in the shape of a question mark. You have to ask yourself, Why?", "A Venus fly trap in a shell surrounded by chubby cherubs.", "You see a statue of a large rat. Did it just blink? Wow, after it ran away you think that was a rat of unusual size.", "An ancient Colombian grave owl holding a sign that reads, 'Cannibals don't eat clowns? They taste funny.'", "You find a dirty plate with a pizza crust. This is probably not a trinket as much as just sloppy housecleaning.", "You find a vial of colorless, odorless, tasteless Iocane powder. This is among the deadlier poisons known to man, so you should probably not touch it.", "A troll doll with an outie belly button. Everyone knows that trolls are not placental mammals, but reproduce through a complex series of... wait, you haven't had this talk with your parents, yet?", "A small statue of a flexing giant. When you push its belly, it says, 'Its not my fault I'm the biggest and the strongest. I don't even exercise.'", "You see a license plate from the State of Utah that reads HEATHEN, and think, what is the significance of hens in heat? Oh, and you also think, what is a license plate doing in a fantasy game about escaping the clamors of last stage capitalism?", "You find a brilliant <> scarf around a book, but as soon as you remove it, the book tries to bite you. After it chases you around the room, you double back, jump on it, breaking its spine (which is lets out a sad yelp), and wrap the scarf back around it. Maybe you've seen enough stuff on these shelves for a while.", ] def at_object_creation(self): """ Set the initial number position for cycling through them. """ self.db.last_trinket_num = -1 def return_appearance(self, looker, **kwargs): """ Return a trinket by cycling through the list. """ self.db.last_trinket_num = self.db.last_trinket_num + 1 # Once they have seen the crystal ball, they can now "see" # them, and probably pick one up, if they are around: if self.db.last_trinket_num == 0: looker.tags.add("hidden_ball") # Seen all the trinkets? Oh boy, well, let's loop: if self.db.last_trinket_num >= len(self.msgs): self.db.last_trinket_num = 0 return routput(self.msgs[self.db.last_trinket_num]) class CrystalBall(Object): def return_appearance(self, looker, **kwargs): """Return a different result each time it is looked.""" return routput(self.db.desc) + " |w" + choice([ "Definitely yes", "It is certain", "It is decidedly so", "Without a doubt", "You may rely on it", "As I see it, yes", "Most likely", "Outlook good", "Yes", "Signs point to yes", "Reply hazy, try again", "Ask again later", "Better not tell you now", "Cannot predict now", "Concentrate and ask again", "Nope", "Don’t count on it", "My reply is no", "My sources say no", "Outlook not so good", "Very doubtful", ]) class Ring(Object): def move_to(self, destination, **kwargs): """ The ring should only go to the door knocker... """ if destination.is_typeclass("typeclasses.things.Knocker") or \ destination.is_typeclass("typeclasses.characters.Character"): super().move_to(destination) return True return False class Dice(Object): """ Dice that can be rolled. A "game" should be able to query the dice to get the results. Override the following default properties to adjust the dice: @set dice/size = 6 @set dice/number = 1 """ # Defaults ... should be overridden with db: number = 1 size = 6 def at_object_creation(self): self.cmdset.add_default(CmdSetDice) def roll(self, roller, options): size = self.db.size or self.size number = self.db.number or self.number rolls = [randint(1, size) for d in range(number)] self.db.last_roll = sum(rolls) prepend = "" if number > 1: if "sum" in options: prepend = prepend + f" The sum is {sum(rolls)}." if "high" in options: prepend = prepend + f" The highest is {max(rolls)}." if "low" in options: prepend = prepend + f" The lowest is {min(rolls)}." roller.announce_action("$You() $conj(roll) a " + iter_to_str(rolls) + "." + prepend) class Pipe(Object): """ Simple abstraction for lighting and smoking actions. @set pipe/light_msg = "$You() $conj(pull) out, $conj(pack) and $conj(light) $pron(your) pipe." @set pipe/smoke_msg = "$You() $conj(lean) back and $conj(<< puff ^ smoke>>) $pron(your) pipe." Note that phrases separated by ^ characters and enclosed in << ... >> will be randomly selected. """ def at_object_creation(self): self.cmdset.add_default(CmdSetSmoke) def do_light(self, lighter): msg = choices(self.db.light_msg or f"$You() $conj(pack) and $conj(light) $pron(your) {self.name}.") lighter.announce_action(msg) self.db.is_giving_light = True def do_smoke(self, smoker): msg = choices(self.db.smoke_msg or f"$You() << $conj(lean) back and ^ >> << $conj(puff) ^ $conj(smoke) >> $pron(your) {self.name}.") smoker.announce_action(msg) def do_ring(self, smoker, details=None): self.db._last_option = 'ring' self.db._last_detail = details details = details or "" msg = choices(f"""$You() $conj(blow) a {details} smoke ring. ;; The smoke from $your() {self.name} forms a << beautiful ^ swirling ^ spiraling >> ring. """) smoker.announce_action(msg) if randint(1, 10) < 5: delay(randint(5, 15), self.do_ring_dissipate) def do_ring_dissipate(self): msg = choices(""" The smoke ring << flies around before it ^ shoots across the room before it ^ >> << eventually ^ finally >> << dissipates ^ disperses >>. ;; The ring hovers, spinning in place, before << dissipating ^ dispersing >>. ;; The smoke ring changes << colors ^ to purple ^ to blue ^ to pink>> before << dissipating ^ dispersing >>. """) self.location.location.msg_contents(msg) def do_arrow(self, smoker, details=None): self.db._last_option = 'arrow' self.db._last_detail = details details = details or "" msg = paragraph(choices(f""" The smoke from $your() {self.name} << streaks ^ makes a line ^ makes an arrow ^ shoots out >> << pointing to ^ directly at >> {details}! """)) smoker.announce_action(msg) def do_monster(self, smoker, details=None): self.db._last_option = 'monster' # Pre-message: smoker.announce_action(paragraph(choices( f""" $You() $conj(blow) the smoke from $pron(your) {self.name} into a <>. ;; The smoke from $your() {self.name} << coalesces ^ coheres ^ swirls together >>. """))) details = details or choice(["dragon", "flumph", "froghemoth", "serpent"]) self.db._last_detail = details article = "an" if match(r"^[aeiou]", details) else "a" msg = routput( f"""The smoke << forms ^ forms the shape of ^ begins to resemble ^ looks like ^ becomes >> {article} {details}!""") delay(3, smoker.announce_action, msg) delay(randint(5, 15), self.do_monster_dissipate, details) def do_monster_dissipate(self, monster): msg = choices(f"""The << smoke ^ >> {monster} << flies around ^ soars overhead ^ gently floats ^ waves ^ bellows >> before it << eventually ^ finally ^ >> << dissipates ^ disperses ^ looses its shape becoming a puff >>. ;; The << smoke ^ >> << monster ^ {monster} >> changes << colors ^ to purple ^ to blue ^ to pink>> before << dissipating ^ dispersing >>.""") self.location.location.msg_contents(msg) class Wood(Litterable): "An object to burn." def at_object_creation(self): self.db.singular = "a log" self.db.plural = "some logs" self.db.desc = routput(choice([ "Its log, its log, it's big, it's heavy, it's wood.", "Some <> for the fireplace.", ])) class Stick(Object): "An object to throw." def at_object_creation(self): self.cmdset.add_default(CmdSetStick) def do_throw(self, thrower): thrower.score(Scores.throw_stick) beast = thrower.location.search('beast') if beast and beast.db.is_awake: beast.thrown_stick(thrower) elif thrower.location.is_typeclass("typeclasses.rooms_weather.TimeWeatherRoom"): thrower.db.thrown_times = (thrower.db.thrown_times or 0) + 1 if thrower.db.thrown_times == 1: thrower.msg("The stick flies through the air, and just as you thought, it comes back to you!") else: thrower.msg(choices([ "Yer a wizard, {0}!", "<> It clipped the leaf on that tree before returning.", "This is a <> <>.", "Maybe we should get a pet <> in here who would love to play.", ], thrower.name.capitalize())) thrower.announce_action("$You() $conj(throw) $pron(your) stick.", exclude=thrower) else: thrower.msg("I think you should be outside or a place with more room before you throw that stick around.") class Wand(Stick): def at_object_creation(self): self.cmdset.add_default(CmdSetWand) class Scryer(Object): def at_object_creation(self): self.cmdset.add_default(CmdSetScry) def do_show_room(self, scryer, room_name): import typeclasses.rooms room = scryer.search(room_name, global_search=True) if room and isinstance(room, typeclasses.rooms.Room): desc = room.get_full_appearance(scryer).strip() others = scryer.characters_here(puppets=True) scryer.msg( "You gaze into the crystal ball to see the " + desc, exclude=others) else: scryer.msg(f"You can't scry: {room}") class Puddle(Object): def at_object_creation(self): self.cmdset.add_default(CmdSetPuddle) def do_jump(self, player): player.score(Scores.jump) player.db.jumped_times = (player.db.jumped_times or 0) + 1 if player.db.jumped_times == 1: player.msg("You jump in the puddle! " "This is great fun. You feel childish.") else: player.msg(routput(choice([ "You don't care <>, you jump in again!", "Mud? Whatever. You <> in <<^ again>>.", "You splash around in the puddle.", "You jump in the puddle again.", "This time you dance a little as you kick up your heels.", ]) + " You feel " + choice([ "<> better.", "carefree.", "child-like and free.", "irresponsible.", "happy.", "great.", ]))) player.announce_action("$You() << $conj(jump) ^ $conj(splash) ^ $conj(play) >> in the puddle.", exclude=player) class Knocker_Convo(Enum): "What topics have we covered?" ANY = 0 GREETED = 1 DOOR = 2 LOCK = 3 PASS = 4 HINT = 5 RIDDLE = 6 ANSWERED = 7 class Knocker(Object): """ Knocker The Object is the class for gatekeepers between rooms. Special object that listens to what is said in the room, and attempts to a _real_ NPC. """ muffled_responses = [ "Mmmuufffmm", "Mmmf?", "Mummffmmph", "Umfmuummmfff", "Umf! Umfmmmf mmm mmmuufffmm mff mmuuummph.", ] yes_msg = r"\byes|yeah|yah|sure|please\b" no_msg = r"\bno|nope\b" question_msg = r"\?$" greet_msg = r"\bhello|\bgreet|\bhey\b" # Each state of conversation is listed: close_responses = [ "Oooo...good guess, but we are looking for something more specific.", ] answered_responses = [ "Alright, I'll open the door for yah.", "I heard my favorite, let's get this door unlocked.", ] # Note that the responses are ordered from most specific to least. knocker_responses = [ "[Sorry.|What was that?|Did you say something?] I'm hard of hearing on account of the brass ears.", "Yes, I suppose I'm this amazing puzzle you get to in Chapter Three. Wait, does that mean I'm just an NPC?", "Who me? I thought you were talking to the goblin in the bushes.", "Why yes, I am hard of hearing.", ] password_responses = [ "Of course this door is protected by a super complicated encrypted password.", "If I tell you, it wouldn't be a secret now.", "The password? You just have to guess...unless you want a hint?", "Well, I suppose I could give you a |mhint|n.", ] hint_responses = [ "A hint does sound fair. <> come up with a |mriddle|n<<, huh ^ >>?" ] riddle_responses = [ """ Aged in barrels, smooth and neat, In a glass by the fire, I'm a treat. """, """ A scent of oak, a whiff of grain, A drop of expertise from the cask remains. """, """ In oaken halls, I sleep and bide, till I'm called to warm your insides... hrm, that's a little vague, but kinda nice. """, """ Alright, alright, the riddle should be clever. It should refer to its golden hue, and I should make it obvious that it isn't gold, and adding a reference to quest of the Argonauts would be a complete red herring, and quite a mean thing to do, so I shouldn't add that, but what about barrels? Yeah, need to include barrels, but not the way Bilbo road barrels, for that was definitely intended as a misleading riddle for Smaug, but of course, a smart dragon would always figure such things out, wait, where was I?""" ] locked_responses = [ "Yes, I'm familiar with the door and the fact that it is locked.", "This locked door is to protect the theft of Dabbler's scones.", ] all_responses = [ # The password must be the first entry here, as we "act" on it: [r"whiske?y", Knocker_Convo.ANY, answered_responses, Knocker_Convo.ANSWERED], [r"scotch|bourbon", Knocker_Convo.ANY, [ "A little more specific than needed, but that will do."], Knocker_Convo.ANSWERED], # A _sequence_ of responses, from what is this password business, to give me a hint, to give me the riddle ... [question_msg, Knocker_Convo.RIDDLE, [ "Nope. Would <> like another riddle?", "You <> think that is the answer?", "Personally, <> that riddle could not <> more clear." ], None], [yes_msg, Knocker_Convo.HINT, riddle_responses, Knocker_Convo.RIDDLE], ["riddle", Knocker_Convo.ANY, riddle_responses, Knocker_Convo.RIDDLE], ["another", Knocker_Convo.RIDDLE, riddle_responses, Knocker_Convo.RIDDLE], ["hint", Knocker_Convo.ANY, hint_responses, Knocker_Convo.HINT], ["\btell\b", Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT], [yes_msg, Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT], [question_msg, Knocker_Convo.PASS, hint_responses, Knocker_Convo.HINT], [yes_msg, Knocker_Convo.DOOR, password_responses, Knocker_Convo.PASS], ["password", Knocker_Convo.ANY, password_responses, Knocker_Convo.PASS], ["get in", Knocker_Convo.LOCK, password_responses, Knocker_Convo.PASS], ["get in", Knocker_Convo.DOOR, [ "Well, you see. The door is locked." ], Knocker_Convo.LOCK], [question_msg, Knocker_Convo.DOOR, locked_responses, Knocker_Convo.LOCK], [r"\block", Knocker_Convo.ANY, locked_responses, Knocker_Convo.LOCK], [r"\bdoor\b", Knocker_Convo.ANY, [ "What door? I don't see a door. Ha!", "That's right, I am the clever puzzle hanging on a door.", "I'm hanging on a door? Really? Let's see, can I roll my eyes?", "Just to let you know, the door is locked.", """ I shouldn't be telling you this, but I like the cut of your <>. So, you see, if you speak the |mpassword|n, wait, I've said too much. """ ], Knocker_Convo.DOOR], [greet_msg, Knocker_Convo.GREETED, [ "Didn't we just exchanged pleasantries?", "Just so you know, I'm not much into small talk." ], None], [greet_msg, Knocker_Convo.ANY, [ "How's it going?", "How's it?", "'Sup.", "How are you?", ], Knocker_Convo.GREETED], [r"knocker|goblin", Knocker_Convo.ANY, knocker_responses, None], [r"\bass\b", Knocker_Convo.ANY, [ "Why yes, I am made of brass." ], None], [r"thanks|thank you", Knocker_Convo.ANY, [ "You're <> welcome.", "Glad to be of service." ], None], [yes_msg, Knocker_Convo.ANY, [ "Really? You agree?", "Excellent.", ], None], [no_msg, Knocker_Convo.ANY, [ "Well, it's true. Just ask the <>.", ], None], [question_msg, Knocker_Convo.ANY, [ "What about <>?", "I dunno...", ], None], ] after_unlocked_responses = [ "We could <> and <> this <>, but we both know that |wyou|n know the <>, and can go through the |gdoor|n now.", "<>, let's <> you don't know the <>, and <> this conversation.", ] cant_hear_responses = [ "<> I'm hard of hearing on account of the brass ears.", "Brass ears. Yeah, not the best at hearing.", "Yeah, These brass ears don't hear much except for the |m<> password|n.", ] unknown_responses = [ "Are you talking to me or the goblin <>?", "Knock, knock.", "What do you mean?", "Of course I like <>. Who <>?", "No thank you, I can't eat <>, what with the brass teeth and a lack of guts.", "Tea? While that would be <> of you, I can't really hold a cup.", "I'm fine, thanks. How are you?", "I'll say, <> a spell of weather.", ] def at_object_creation(self): self.cmdset.add_default(CmdSetKnock) def do_knock(self, knocker): if self.has("ring"): knock_script = create_script(key="knocking", typeclass=KnockScript, interval=10, # seconds <=0 means off start_delay=True, # wait interval before first call autostart=True, # This is the _character_ # that does the knocking, not # the door knocker: attributes=[("knocker", knocker), # Waker is the door knocker ("waker", self), ("room", self.db.room_to_msg) ]) knocker.announce_action("$You() $conj(grab) the ring and $conj(knock) << firmly ^ loudly ^ aggressively >> on the door.") else: knocker.msg("This door knocker is defective, as it doesn't have a ring to...er, do the knockin'.") def knocked_timed_out(self): if self.has("brass ring"): self.at_say(choice(self.muffled_responses)) else: self.at_say("Doesn't appear that anyone is home.") def at_heard_say(self, message, talker): """ A simple listener and response. This makes it easy to change for subclasses of NPCs reacting differently to says. """ curr_state = Knocker_Convo(talker.db.knocker_conversation_state or 0) # message will be on the form ` says, "say_text"` # we want to get only say_text without the quotes and any spaces if message.count('"') == 0: return message = message.split('"')[1].strip() # Let's see if a keyword gives a good response: for [regex, convo_state, responses, new_state] in self.all_responses: full_regex = r".*" + regex + r".*" if match(full_regex, message, IGNORECASE) \ and curr_state.value >= convo_state.value: # logger.info(f"Spoke: {message}, matched: {full_regex} and {curr_state} >= {convo_state}") # If we have the ring in our mouth, we are muffled: if self.has("brass ring"): return choice(self.muffled_responses) else: if new_state and new_state.value > curr_state.value: talker.db.knocker_conversation_state = new_state # The first match is the password, so we set a tag # on the character, so that they can go through # the door: if talker.db.knocker_conversation_state == Knocker_Convo.ANSWERED: talker.tags.add("open_red_door", category="mp") return routput(choice(responses)) # We can not do a random response. Of course, we are # pretending we are hard of hearing, so we don't spam the room # _every_ time: if random() * 100 < 45: if self.has("brass ring"): return choice(self.muffled_responses) # If a keyword was not spoken, we want to emphasize that # we are hard of hearing, and mention that every other # time, so we store in the database a setting to keep # track and alternate the responses: elif self.db.hard: self.db.hard = False if talker.tags.get(key="open_red_door", category="mp"): return routput(choice(self.after_unlocked_responses)) else: return routput(choice(self.unknown_responses)) else: self.db.hard = True return routput(choice(self.cant_hear_responses)) def get_display_desc(self, looker, **kwargs): # Use this for random information instead of self.db.desc response = "In a shape of a bald goblin, the brass door knocker in the center of the red door" if self.has("brass ring"): response += " holds a ring in its mouth. " response += "<> <> winked at you." else: response += " <> at you expectantly. " return routput(response) def get_display_things(self, looker): return "" def msg(self, text=None, from_obj=None, **kwargs): "Custom msg() method reacting to say." if from_obj != self: # make sure to not repeat what we ourselves said or we'll create a loop is_say = False is_whisper = False try: # debug(f"text[0]: {text[0]}, text[1]: {text[1]}") # if text comes from a say, `text` is `('say_text', {'type': 'say'})` say_text, is_say = text[0], text[1]['type'] == 'say' is_whisper = text[1]['type'] == 'whisper' except Exception: pass if is_whisper: self.at_say("I'm a little hard of hearing, can you speak up?") elif is_say: # First get the response (if any) response = self.at_heard_say(say_text, from_obj) # If there is a response if response is not None: self.at_say(response) # this is needed if anyone ever puppets this NPC - without it you would never # get any feedback from the server (not even the results of look) super().msg(text=text, from_obj=from_obj, **kwargs) def at_say(self, text): obj_name = choice(["The knocker", "The knocker", "The knocker", "The knocker", "The door knocker", "The door knocker", "The goblin-shaped knocker", "The goblin-shaped door knocker"]) if text.endswith("?"): verb = "asks" elif text.endswith("!"): verb = "exclaims" else: verb = choice(["says", "replies", "answers", "responds", "says", "says"]) phrase = f"{obj_name} {verb}, \"{text}\"" self.location.msg_contents(phrase, exclude=self) def at_object_leave(self, moved_obj, target_location, move_type="move", **kwargs): """ If the ring is removed, we make a comment. """ if moved_obj.key == "brass ring" and target_location != self: self.at_say("Oh my, that feels better.") return True return False def at_pre_object_receive(self, arriving_object, source_location, **kwargs): """ The knocker only accepts the ring as a gift. """ return True if arriving_object.key == "brass ring" else False def at_object_receive(self, obj, source_location, move_type, **kwargs): """ If the ring is returned, we make a comment. """ if obj.key == "brass ring": delay(2, self.chewing) return True def chewing(self): """ Make a muffled comment. """ self.at_say(choice(self.muffled_responses)) class BagofJunk(Object): """ A bag of holding with just trivial junk. Perfect for a tweed jacket. """ human_stuff = [ "wallet", "keys", "smartphone", "handkerchief", "pen", "notebook", "sunglasses", "business cards", "lip balm", "mints", "gum", "pocket watch", "small flashlight", "earbuds", "uSB drive", "travel-sized cologne", "hair comb", "safety pins", "sewing kit", "lint roller", "tissues", "granola bar", "a small book", "reading glasses", "a small umbrella", "phone charger", "portable battery pack", "cough drops", "a small notepad", "a map", "a ticket stub", "a receipt", "a small first aid kit", "a keychain", "a small mirror", "a hair tie", "a small bottle of hand sanitizer", "a travel guide", "a bookmark", "a small camera", "a flash drive", "a mini tool kit", "a stress ball", "a small plant seed packet", "a whistle", "a small notebook for sketches", "a travel pillow", "a portable fan", "a small bottle of water", "a snack bag", "a pair of gloves", "a scarf", "a small blanket", "a portable game console", "a deck of cards", "a small puzzle", "a travel-sized board game", "a notepad for ideas", "a small calendar", "a set of colored pencils", "a small sketchbook", "a travel-sized hairbrush", "a small bottle of lotion", "a portable speaker", "a small sewing kit", "a pair of earplugs", "a small flashlight", "a mini umbrella", "a small bottle of vinegar", "a small bottle of olive oil", "a reusable shopping bag", "a small notebook for journaling", "a small bottle of hot sauce", "a small bottle of soy sauce", "a small bottle of ketchup", "a small bottle of mustard", "a small bottle of salad dressing", "a small bottle of honey", "a small bottle of syrup", "a small bottle of jam", "a small bottle of peanut butter", "a small bottle of jelly", "a small bottle of salsa", "a small bottle of barbecue sauce", "a small bottle of relish", "a small bottle of mayonnaise", "a small bottle of Worcestershire sauce", "a small bottle of teriyaki sauce", "a small bottle of chili sauce", "a small bottle of curry paste", "a small bottle of coconut milk", "a small bottle of broth", "a small bottle of stock", "a small bottle of wine", "a small bottle of beer", "a small bottle of cider", "a small bottle of soda", "a small bottle of juice", ] fey_stuff = [ "pan flute", "an acorn cap", "dried leaves", "a sprig of thyme", "a tiny bell", "a piece of string", "a handful of wildflower seeds", "a feather", "a small pouch of herbs", "a miniature goat figurine", "a piece of bark", "a lucky charm", "a small stone with a hole", "a twig shaped like a heart", "a tiny lantern", "a scrap of parchment", "a small compass", "a piece of cheese", "a bottle of honey", "a small jar of jam", "a dried mushroom", "a tiny scroll", "a thimble", "a small wooden carving", "a piece of rope", "a small pouch of glitter", "a tiny drum", "a handful of nuts", "a piece of flint", "a small bellflower", "a tiny painting", "a small vial of a potion", "a piece of candy", "a small twig flute", "a dried flower", "a small notebook", "a pencil", "a tiny map of the forest", "a small bottle of ink", "a feather quill", "a small pouch of coins", "a tiny key", "a small wooden spoon", "a piece of fabric", "a small bell", "a small bottle of perfume", "a tiny flower crown", "a tiny compass", "a small wooden flute", "a piece of dried fruit", "a small jar of fireflies", "a tiny drumstick", "a small pouch of spices", "a small bottle of potion", "a small wooden toy", "a small feather", "a small pouch of seeds", "a tiny bellflower", ] def at_object_creation(self): self.cmdset.add_default(CmdSetRummage) def do_rummage(self, owner): """ Rummage around this bag and describe finding an item. """ if self.db.stuff == 'human': item = choice(self.human_stuff) else: item = choice(self.fey_stuff) 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.") owner.announce_action(msg) def do_keep(self, keeper, description=None): """ Create an object out of the last thing rummaged. """ if self.db.latest_item: where = self.db._sdesc or self.name if not description or description == "": description = f"An interesting item found in a {where}." item = spawn({ "typeclass": "typeclasses.consumables.Litterable", "key": self.db.latest_item, "desc": description })[0] item.location = keeper keeper.msg(f"You got {item.key}.") self.db.latest_item = None else: self.location.msg("You must |grummage|n first.") class IceBreakerHat(Object): def at_object_creation(self): self.cmdset.add_default(CmdSetMakeConsumable) def at_pre_get(self, getter, **kwargs): """ Can't get a producer. """ self.do_make(getter) return False def do_make(self, picker, **kwargs): """ Create a configurable consumable, given to picker. """ paper = spawn({ "typeclass": self.db.make_class or kwargs['type'] if 'type' in kwargs else "typeclasses.readables.Letter", "key": self.db.make_name, "aliases": self.db.make_aliases, "desc": routput(self.db.make_desc), })[0] paper.db.inside = \ "The slip of paper reads...\n\n " + \ choice([ "Slide the answer to the following question into the flow of conversation:", "Smoothly find out the answer to the following question from another character here:", "Tell a short story about yourself that answers the following question:", ]) + "\n\n " + \ choice([ "Do you have a nickname? Why were you given it, and what does it mean?", "Where is your homeland? What are it's people like?", "What do you do in your free time?", "What is your hobby?", "What is something you carry around constantly? Why?", "Do you have a romantic partner? Any exes?", "If you inherited a massive pile of gold, how would you spend it?", "What do you think about a lot?", "What sort of pet would you want to have?", "If you could write a book, what would it be about?", "Do you have a rival?", "What really annoys you?", "What are you really passionate about?", "What are you afraid of?", "What is the most interesting about you?", "What is the most embarrassing thing that has happened to you?", "If you woke up in jail, why would you be there?", "If you could be an animal, which animal would you be and why?", # "Describe your personality in ten words or less.", "Have you passed up any chances that you now regret? What were they?", "What accomplishment are you most proud of?", "What was the greatest adventure in your life so far?", ]) paper.location = picker picker.announce_action(f"$You() $conj({self.db.make_verb or 'get'}) {self.db.make_name}.") class Teleporter(Object): """ Teleport a character to a new location. Requires the following attributes: @set obj/pre_msg = "You press the lever." @set obj/post_msg = "The catapult launches you into the air!" @set obj/fail_msg = "Other than the click, nothing seems to happen." @set obj/destination = $search(Limbo) @set obj/details = "01189998819991197253" Keep in mind that the details, if given, follows the `press` command, but spaces and punctuation are ignored. """ def at_object_creation(self): """ Add the `press` command to this object. """ self.cmdset.add_default(CmdSetPress) def trigger(self, victim, details=None): """ Called by the `press` command. """ def announce(victim, msg): if match(r"\$", msg): victim.announce_action(msg) else: victim.msg(msg) def announce_seq(time_to_delay, victim, msgs): lines = split(r" *;; *", msgs) for line in lines: logger.info(f"time_to_delay={time_to_delay}, announce, victim={victim}, line={line}") delay(time_to_delay, announce, victim, line) time_to_delay = time_to_delay + 2 logger.info(f"returning {time_to_delay}") return time_to_delay time_to_delay = announce_seq(0, victim, self.db.pre_msg) deets = self.db.details if not deets or (deets and deets == details): time_to_delay = announce_seq(time_to_delay, victim, self.db.post_msg) delay(time_to_delay, victim.move_to, self.db.destination, move_type="traverse") else: announce_seq(time_to_delay, victim, self.db.fail_msg) class MagicRing(Object): """ Ring that can change someone into an object. """ def at_object_creation(self): self.cmdset.add_default(CmdSetTransform) class MagicMonocle(Object): """ Monocle that sees past the 'transform' magic. """ def at_object_creation(self): """ Sets the scoreboard as well as the 'peer' command. """ self.cmdset.add_default(CmdSetPeer) self.reset_counts() def found_someone(self, found): """ Store a set as an attribute with 'found'. """ the_set = self.attributes.get('found', set()) the_set.add(found) self.attributes.add('found', the_set) def failed_at_looking(self): """ nv Increment the failures on the scoreboard. """ self.db.failed = self.db.failed + 1 def reset_counts(self): """ Resets the scoreboard to 0. """ self.attributes.add('found', set()) self.attributes.add('failed', 0) def report_score(self, seeker=None): """ Reports the score for the object to the room. """ if not seeker: seeker = self.location room = seeker.location name = seeker.sdesc.get() room.msg_contents(f"The score for the seeker, {name} is:|/" + f" - Found: {len(self.db.found)}|/" + f" - Misses: {self.db.failed}") class GlobalAlarmClock(Object): """ Clock that announces timers on a public channel. """ def at_object_creation(self): """ Sets up the attributes. """ self.db.channel = "Hide and Seek Game" def start_timer(self, seconds=30, start=None, finish=None): chan = search_channel(self.db.channel).first() if chan: 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)