Created a hard of hearing, NPC, door knocker
Quite pleased with the results. This also fixed a utils issue to see if an object has an item. I'm surprised this isn't in the standard library.
This commit is contained in:
parent
50f089b20f
commit
1c830d1f54
10 changed files with 357 additions and 27 deletions
|
|
@ -17,6 +17,7 @@ own cmdsets by inheriting from them or directly from `evennia.CmdSet`.
|
|||
from evennia import default_cmds
|
||||
from evennia.contrib.grid import extended_room
|
||||
from commands.sittables import CmdNoSitStand
|
||||
from commands.take import CmdTake
|
||||
|
||||
class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
||||
"""
|
||||
|
|
@ -33,6 +34,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet):
|
|||
"""
|
||||
super().at_cmdset_creation()
|
||||
self.add(CmdNoSitStand)
|
||||
self.add(CmdTake())
|
||||
self.add(extended_room.ExtendedRoomCmdSet)
|
||||
#
|
||||
# any commands you add below will overload the default ones.
|
||||
|
|
|
|||
23
commands/take.py
Executable file
23
commands/take.py
Executable file
|
|
@ -0,0 +1,23 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from commands.command import Command
|
||||
from evennia import CmdSet
|
||||
from evennia.utils import logger
|
||||
|
||||
class CmdTake(Command):
|
||||
"""
|
||||
Steals an object from another.
|
||||
Added to the default_cmdsets.
|
||||
"""
|
||||
key = "take"
|
||||
aliases = ["get", "remove"]
|
||||
# only allow this command if command.obj is carried by caller.
|
||||
# locks = "cmd:holds()"
|
||||
|
||||
def func(self):
|
||||
"""
|
||||
Implements the take command. Since this command is designed
|
||||
to work on the object, we operate only on self.obj.
|
||||
"""
|
||||
logger.log_info(f"Dealing with {self.caller.key}")
|
||||
self.caller.do_take(self.args)
|
||||
|
|
@ -10,6 +10,7 @@ creation commands.
|
|||
|
||||
from evennia.objects.objects import DefaultCharacter
|
||||
|
||||
from utils.word_list import Token, routput
|
||||
from .objects import ObjectParent
|
||||
|
||||
|
||||
|
|
@ -22,6 +23,32 @@ class Character(ObjectParent, DefaultCharacter):
|
|||
properties and methods available on all Object child classes like this.
|
||||
|
||||
"""
|
||||
def do_take(self, args):
|
||||
"""
|
||||
A character has a _steal_command. What are the limitations?
|
||||
"""
|
||||
args = Token(args)
|
||||
if args.empty():
|
||||
self.msg("What do you want to take?")
|
||||
elif len(args.words) == 1:
|
||||
self.msg(f"You want to take {args.words[0]}, but from whom?")
|
||||
else:
|
||||
to_take = args.words[0]
|
||||
from_whom = args.words[-1]
|
||||
victim = self.search(from_whom)
|
||||
if victim:
|
||||
thing = victim.has(to_take)
|
||||
if thing:
|
||||
self.msg(f"You take {thing.key} from {victim.key}.")
|
||||
self.location.msg_contents(
|
||||
f"{self.key} takes {thing.key} from {victim.key}!",
|
||||
exclude=self)
|
||||
thing.move_to(self, quiet=True, use_destination=True)
|
||||
return
|
||||
|
||||
self.msg(f"{victim.key} doesn't have a {to_take}.")
|
||||
# else:
|
||||
# self.msg(f"You don't see a {from_whom}.")
|
||||
|
||||
def at_pre_move(self, destination, **kwargs):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
from typeclasses.objects import Object
|
||||
from commands.drinkables import CmdSetTeapot, CmdSetTeacup
|
||||
from utils.word_list import routput, character_has, Token
|
||||
from utils.word_list import routput, Token
|
||||
|
||||
import random
|
||||
|
||||
|
|
@ -110,6 +110,6 @@ class Teapot(Object):
|
|||
self.location.msg_contents(f"{drinker.name} makes a pot of tea.", exclude=drinker)
|
||||
|
||||
def do_fill(self, drinker):
|
||||
teacup = character_has(drinker, "teacup")
|
||||
teacup = drinker.has("teacup")
|
||||
if teacup:
|
||||
teacup.do_fill(drinker, self.db.tea_type, TEA_TYPES[self.db.tea_type])
|
||||
|
|
|
|||
|
|
@ -215,8 +215,21 @@ class Object(ObjectParent, DefaultObject):
|
|||
at_desc(looker=None)
|
||||
|
||||
"""
|
||||
def has(self, item):
|
||||
"""
|
||||
Return true if object has an item.
|
||||
Where item is probably a string name to match an item's key.
|
||||
It can also be a type, for instance:
|
||||
|
||||
pass
|
||||
character.has(typeclasses.drinkables.TeaCup)
|
||||
"""
|
||||
for i in self.contents:
|
||||
if isinstance(item, str) and (i.key == item or i.aliases.get(item)):
|
||||
return i
|
||||
elif type(i) == item:
|
||||
return i
|
||||
elif i == item:
|
||||
return i
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from .objects import LightSource
|
|||
from .drinkables import TEACUP_DESCS
|
||||
from .pets import Hunger
|
||||
from commands.drinkables import CmdSetTrolley
|
||||
from utils.word_list import routput, character_has, Token
|
||||
from utils.word_list import routput, Token
|
||||
|
||||
import random
|
||||
|
||||
|
|
@ -305,7 +305,7 @@ class DabblersRoom(Room):
|
|||
self.cmdset.add_default(CmdSetTrolley, persistent=True)
|
||||
|
||||
def produce_teacup(self, caller):
|
||||
if character_has(caller, "teacup"):
|
||||
if caller.has("teacup"):
|
||||
caller.msg("You already have a teacup.")
|
||||
else:
|
||||
cuptype = self.teacup_prototype
|
||||
|
|
|
|||
250
typeclasses/things.py
Executable file
250
typeclasses/things.py
Executable file
|
|
@ -0,0 +1,250 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""
|
||||
Knocker
|
||||
|
||||
The Object is the class for gatekeepers between rooms.
|
||||
|
||||
Use the ObjectParent class to implement common features for *all* entities
|
||||
with a location in the game world (like Characters, Rooms, Exits).
|
||||
|
||||
"""
|
||||
|
||||
from evennia.utils import logger
|
||||
|
||||
from .objects import Object
|
||||
from .characters import Character
|
||||
from utils.word_list import routput
|
||||
|
||||
from random import choice, random
|
||||
import re
|
||||
|
||||
|
||||
class Returnable(Object):
|
||||
"""
|
||||
This object can't go far from one or two locations.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
class Knocker(Object):
|
||||
"""
|
||||
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 hint."
|
||||
]],
|
||||
["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?
|
||||
"""
|
||||
]],
|
||||
["hello|greet", [
|
||||
"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.
|
||||
"""
|
||||
]],
|
||||
["\byes|yeah|yah\b", [
|
||||
"Really? You agree?",
|
||||
"Excellent",
|
||||
]],
|
||||
["\bno|nope\b", [
|
||||
"Well, it's true. Just ask the [raven|trees|gnome].",
|
||||
]],
|
||||
["\?$", [
|
||||
"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_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 `<Person> 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):
|
||||
if re.match(r".*" + regex + r".*", message):
|
||||
# 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 != 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, obj, target_location, **kwargs):
|
||||
if obj.key == "brass ring" and target_location != self:
|
||||
self.at_say("Oh my, that feels better.")
|
||||
else:
|
||||
self.at_say("Umph")
|
||||
return True
|
||||
0
utils/__init__.py
Normal file
0
utils/__init__.py
Normal file
31
utils/support.py
Executable file
31
utils/support.py
Executable file
|
|
@ -0,0 +1,31 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from typeclasses.characters import Character
|
||||
|
||||
def character_has(holder, item):
|
||||
"""
|
||||
Return true if character, holder, has an item.
|
||||
Where item is probably a string name to match an item's key.
|
||||
It can also be a type, for instance:
|
||||
|
||||
character_has(character, typeclasses.drinkables.TeaCup)
|
||||
"""
|
||||
for i in holder.contents:
|
||||
if isinstance(item, str) and i.key == item:
|
||||
return i
|
||||
elif i.key == item:
|
||||
return i
|
||||
elif type(i) == item:
|
||||
return i
|
||||
elif i == item:
|
||||
return i
|
||||
|
||||
def debug(msg, sender=None, location=None):
|
||||
dabbler = Character.objects.search_object("Dabbler")[0]
|
||||
full_msg = "DEBUG:"
|
||||
if sender:
|
||||
full_msg += f" (from {sender})"
|
||||
if location:
|
||||
full_msg += f" (at {location})"
|
||||
full_msg += f" {msg}"
|
||||
dabbler.msg(full_msg)
|
||||
|
|
@ -5,9 +5,10 @@ import re
|
|||
import string
|
||||
|
||||
class Token():
|
||||
def __init__(self, string):
|
||||
cleaned_words = re.sub(r"[\.,\?!'\"=:;#\&\*\(\)\[\]\{\}]*", "", string)
|
||||
self.words = cleaned_words.split()
|
||||
def __init__(self, s):
|
||||
# cleaned_words = re.sub(r"%s" % string.punctuation, "", s)
|
||||
cleaned_words = re.sub(r"[\.,\?!'\":;^`\|%#\&\*<=>\(\)\[\]\{\}\+\/_-]*", "", s)
|
||||
self.words = [word for word in cleaned_words.split() if word not in ["the", "a", "an"]]
|
||||
|
||||
def empty(self):
|
||||
if len(self.words) == 0:
|
||||
|
|
@ -19,27 +20,11 @@ class Token():
|
|||
if len(result) > 0:
|
||||
return result[0]
|
||||
|
||||
def character_has(holder, item):
|
||||
"""
|
||||
Return true if character, holder, has an item.
|
||||
Where item is probably a string name to match an item's key.
|
||||
It can also be a type, for instance:
|
||||
|
||||
character_has(character, typeclasses.drinkables.TeaCup)
|
||||
"""
|
||||
for i in holder.contents:
|
||||
if isinstance(item, str) and i.key == item:
|
||||
return i
|
||||
elif type(i) == item:
|
||||
return i
|
||||
elif i == item:
|
||||
return i
|
||||
|
||||
def squish(text):
|
||||
"Remove series of spaces from the text."
|
||||
return re.sub('[ \n\t]+', ' ', text).strip()
|
||||
|
||||
def routput(string):
|
||||
def routput(text):
|
||||
"""
|
||||
Return string with internal word choices replaced randomly.
|
||||
For instance, the string:
|
||||
|
|
@ -52,9 +37,8 @@ def routput(string):
|
|||
'This feels cozy.'
|
||||
'This feels very comfortable.'
|
||||
"""
|
||||
# string =
|
||||
acc = []
|
||||
for s in string.split("["):
|
||||
for s in text.split("["):
|
||||
choices, *rest = s.split("]")
|
||||
choice = random.choice(choices.split('|'))
|
||||
acc = acc + [choice] + rest
|
||||
|
|
|
|||
Loading…
Reference in a new issue