#!/usr/bin/env python from random import choice, random from evennia import create_script from evennia.utils import logger, delay from commands.misc import CmdSetPuddle, CmdSetStick, CmdSetKnock from utils.word_list import routput from .scripts import KnockScript from .objects import Object import re 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 Wood(Object): "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 [logs|wood] 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): 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(routput(choice([ f"Yer a wizard, {thrower.name.capitalize()}!", "[Did you see that?|] It clipped the leaf on that tree before returning.", "This is a [fun|pleasant|nice] [past-time|game].", "Maybe we should get a pet [or beast|beastie] in here who would love to play.", ]))) self.location.msg_contents(f"{thrower.name} throws a 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 Puddle(Object): def at_object_creation(self): self.cmdset.add_default(CmdSetPuddle) def do_jump(self, player): 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 [how muddy|who notices], you jump in again!", "Mud? Whatever. You [splash|jump] 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([ "[so much|] better.", "carefree.", "child-like and free.", "irresponsible.", "happy.", "great.", ]))) self.location.msg_contents(f"{player.name} jumps in the puddle.", exclude=player) 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.", ] # Note that the responses are ordered from most specific to least. all_responses = [ # The password must be the first entry here, as we "act" on it: ["whiske?y", [ "Alright, I'll open the door for yah.", "I heard my favorite, let's get this door unlocked.", ]], [r"knocker|goblin", [ "[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", [ "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.", "Well, I suppose I could give you a |bhint|n.", ]], ["hint", [ "A hint does sound fair. [Should I|I should] come up with a |briddle|n[|, huh]?" ]], ["riddle", [ "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? """ ]], [r"\bhello|\bgreet|\bhey\b", [ "How's it going?", "How's it?", "'Sup.", "How are you?", ]], [r"\bass\b", [ "Why yes, I am made of brass." ]], [r"\block", [ "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.", ]], [r"\bdoor\b", [ "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 quiet, and not] be telling you this, but I like the cut of your [suit|cloak|jib]. So, you see, if you speak the |bpassword|n, wait, I've said too much. """ ]], [r"\byes|yeah|yah\b", [ "Really? You agree?", "Excellent", ]], [r"\bno|nope\b", [ "Well, it's true. Just ask the [raven|trees|gnome].", ]], [r"\?$", [ "What about [me|it|'im]?", "I dunno...", ]], ] after_unlocked_responses = [ """ We could [feign|pretend|play make-believe] and [carry on|continue|persist] this [conversation|charade], but we both know that |byou|n know the [password|secret|secret word|magic], and can go through the |bdoor|n now. """, """ [Sure|Why not|Why yes], let's [feign|pretend|play make-believe that] you don't know the [password|secret|secret word|magic], and [carry on|continue|persist] this conversation. """, ] cant_hear_responses = [ "[Sorry.|What was that?|Did you say something?] 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 |b[secret|magic|] password|n.", ] unknown_responses = [ "Are you talking to me or the goblin [in the bushes|up the tree|behind the rock]?", "Knock, knock.", "What do you mean?", "Of course I like [hard candy|squirrels|apples]. Who [doesn't|wouldn't|do you know that doesn't]?", "No thank you, I can't eat [apples|kumquats|figs], what with the brass teeth and a lack of guts.", "Tea? While that would be [nice|sweet] of you, I can't really hold a cup.", "I'm fine, thanks. How are you?", "I'll say, [we have had|that is] a spell of weather.", ] def at_object_creation(self): super().at_object_creation() self.cmdset.add(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.msg("You grab the ring and knock firmly on the door.") self.msg_contents(f"{knocker.name} grabs the ring and knocks firmly on the door.", exclude=knocker) 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, from_obj): """ A simple listener and response. This makes it easy to change for subclasses of NPCs reacting differently to says. """ # message will be on the form ` says, "say_text"` # we want to get only say_text without the quotes and any spaces message = message.split('says, ')[1].strip(' "') # Let's see if a keyword gives a good response: for idx, [regex, responses] in enumerate(self.all_responses): full_regex = r".*" + regex + r".*" if re.match(full_regex, message, re.IGNORECASE): # The first match is the password, so we set a tag on # the character, so that they can go through the door: if idx == 0: from_obj.tags.add("open_red_door", category="mp") # If we have the ring in our mouth, we are muffled: if self.has("brass ring"): return choice(self.muffled_responses) else: 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 from_obj.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 += "[You think it|You are sure it|You could've sworn it|It] [just|] winked at you." else: response += " [smiles|looks] at you expectantly. " return routput(response) def at_desc(self, looker): return "what" # self.get_display_desc(looker) 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_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))