The 'say' command works with the webclient Unplugging from the game makes you stand Trying to sit on someone else's character, now uses the RP system The fish now gets the right phrasing.
281 lines
10 KiB
Python
281 lines
10 KiB
Python
"""
|
|
Characters
|
|
|
|
Characters are (by default) Objects setup to be puppeted by Accounts.
|
|
They are what you "see" in game. The Character class in this module
|
|
is setup to be the "default" character type created by the default
|
|
creation commands.
|
|
|
|
"""
|
|
|
|
from re import match, compile
|
|
|
|
from evennia.contrib.game_systems.gendersub import GenderCharacter
|
|
from evennia.contrib.rpg.rpsystem import ContribRPCharacter
|
|
from evennia.contrib.rpg.rpsystem import send_emote
|
|
from evennia.prototypes.spawner import spawn
|
|
from evennia.utils import delay # , logger
|
|
|
|
from utils.word_list import routput, choices
|
|
from .objects import Object
|
|
from .tutorial import TutorBird, TutorialState
|
|
|
|
|
|
INTRO = """
|
|
As the surrounding mists dissipate, you find yourself in an ancient, halcyon forest dripping with moss. You see an envelope of parchment wedged under a scaly protrusion of bark...inside, a letter in familiar penmanship, personally addressed to you, which you pick up.
|
|
|
|
A little gray bird flies by you, almost grazing your ear!"""
|
|
|
|
READ_LETTER = """You read a letter with an oddly familiar penmanship:
|
|
|
|
My dearest {0},
|
|
|
|
If you are reading this, you've found the world I was overly excited in relaying to you over drinks in Marsivan. Most excellent. Enjoy this halcyon world, unspoiled and idyllic.
|
|
|
|
I would suggest seeing/doing these:
|
|
|
|
- Jump in a puddle
|
|
- Sit on some moss
|
|
- Feed the beast until it is friendly
|
|
- Catch an obnoxious fish
|
|
- Get and read a book
|
|
- Have some tea and scones
|
|
|
|
Oh, I also suggest checking out |wWyldwood|n, a fabulous bar that doesn't open very often, but is quite fun when it does.
|
|
|
|
I'm here, so join me in a cup of tea and we can reconnect and reminisce of glorious days gone by, and the utter curiosity that surrounds us.
|
|
|
|
Your friend,
|
|
Dabbler
|
|
|
|
(Type 'help start' for details on playing this game)"""
|
|
|
|
|
|
class Character(Object, GenderCharacter, ContribRPCharacter):
|
|
"""
|
|
The Character just re-implements some of the Object's methods and hooks
|
|
to represent a Character entity in-game.
|
|
|
|
See mygame/typeclasses/objects.py for a list of
|
|
properties and methods available on all Object child classes like this.
|
|
"""
|
|
pose = True
|
|
|
|
def at_object_creation(self):
|
|
"called when a character is first created."
|
|
self.db.tutorstate = 0
|
|
|
|
if self.dbref != "#1":
|
|
self.create_letter()
|
|
|
|
def at_post_puppet(self, **kwargs):
|
|
if self.db.visited:
|
|
self.msg(f"""\n“Welcome back, {self.key.capitalize()}.”\n""")
|
|
self.execute_cmd("look")
|
|
else:
|
|
self.db.visited = True
|
|
self.db.tutorstate = 0
|
|
TutorBird.do_start_tutorial(self)
|
|
self.msg(INTRO)
|
|
self.account.db._last_puppet = self
|
|
|
|
def at_post_unpuppet(self, **kwargs):
|
|
"""
|
|
Make sure we aren't left sitting down when logging out.
|
|
"""
|
|
if sitter.db.is_sitting:
|
|
chair = sitter.db.is_sitting
|
|
chair.db.sitter = None
|
|
sitter.db.is_sitting = None
|
|
|
|
def msg(self, text=None, from_obj=None, session=None, **kwargs):
|
|
"""
|
|
Capitalizes messages sent to the user.
|
|
This just looks better to me.
|
|
"""
|
|
if text and isinstance(text, str) and len(text) > 0:
|
|
text = text[0].upper() + text[1:]
|
|
super().msg(text, from_obj=from_obj, session=session, **kwargs)
|
|
|
|
def create_letter(self):
|
|
"create a welcome letter in a character's inventory"
|
|
letter = spawn({
|
|
"typeclass": "typeclasses.readables.Letter",
|
|
"key": "letter",
|
|
"desc": "A letter of familiar penmanship stuffed in an envelope.",
|
|
})[0]
|
|
letter.db.inside = READ_LETTER.format(self.name.capitalize())
|
|
letter.location = self
|
|
|
|
def get_display_things(self, looker, *args, **kwargs):
|
|
return super().get_display_things(looker, pose=False)
|
|
|
|
def do_take(self, to_take, from_whom):
|
|
"""
|
|
A character has a _steal_command. What are the limitations?
|
|
"""
|
|
victim = self.search(from_whom)
|
|
if victim:
|
|
thing = victim.has(to_take)
|
|
if thing and thing.db.can_take:
|
|
self.msg(f"You take {thing.key} from {victim.key}.")
|
|
thing.move_to(self, quiet=True, use_destination=True)
|
|
return
|
|
|
|
self.msg(f"{victim.key} doesn't have a {to_take} you can take.")
|
|
|
|
def pronoun_subjective(self, uppercase=False):
|
|
gender = self.attributes.get('gender')
|
|
if gender == "male":
|
|
results = 'He'
|
|
elif gender == "female":
|
|
results = 'She'
|
|
elif gender == "neutral":
|
|
results = 'It'
|
|
else:
|
|
results = 'They'
|
|
|
|
return results if uppercase else results.lower()
|
|
|
|
def gendered_text(self, text):
|
|
"""
|
|
Replace entries, like |s and |p with pronouns.
|
|
Like 'he' and 'her'
|
|
"""
|
|
gender_rx = compile(r"(?<!\|)\|(?!\|)[sSoOpPaA]")
|
|
return gender_rx.sub(lambda x: self._get_pronoun(x), text)
|
|
|
|
def return_appearance(self, looker, **kwargs):
|
|
"""
|
|
Can be overridden or appended with an effect.
|
|
Does the looker affect this?
|
|
"""
|
|
# To replace, temporarily, a description:
|
|
# @set woman/temp_desc:effect = "They fade away from view."
|
|
new_desc = self.attributes.get(category="effect",
|
|
key="temp_desc")
|
|
if new_desc:
|
|
return self.gendered_text(new_desc)
|
|
|
|
pre_desc = self.attributes.get(category="effect",
|
|
key="pre_desc") or ""
|
|
post_desc = self.attributes.get(category="effect",
|
|
key="post_desc") or ""
|
|
reg_desc = super().return_appearance(looker)
|
|
|
|
pronoun = self.pronoun_subjective(True)
|
|
return self.gendered_text(pre_desc + \
|
|
reg_desc.replace('|wYou see:|n',
|
|
'|S has') + \
|
|
post_desc)
|
|
|
|
def at_pre_move(self, destination, *args, **kwargs):
|
|
"""
|
|
Called by self.move_to when trying to move somewhere. If this returns
|
|
False, the move is immediately canceled.
|
|
"""
|
|
self.db.tutorstate = self.db.tutorstate | TutorialState.MOVE.value
|
|
|
|
if self.db.is_sitting:
|
|
self.msg("You stand up first...")
|
|
self.db.is_sitting = False
|
|
|
|
# @lock thing = tethered:id(#19)
|
|
# @set thing/tethered_msg = "Let's put that back"
|
|
for thing in self.contents:
|
|
to = thing.locks.get('tethered')
|
|
if to:
|
|
m = match(r".*:id\((.*)\)", to)
|
|
if m:
|
|
id_num = m.group(1)
|
|
dest = self.search(f"{id_num}")
|
|
msg = thing.db.tethered_msg
|
|
if dest and msg:
|
|
thing.location = dest
|
|
self.msg(msg)
|
|
else:
|
|
print(f"Found tethered, {thing} to #{id_num}, but dest is {dest} and msg is '{msg}'. Set both.")
|
|
else:
|
|
print(f"Found tethered, {thing} to #{to}, but that needs to be 'tethered:id(num) where num is the ID# of an object/place.")
|
|
|
|
return super().at_pre_move(destination)
|
|
|
|
def at_pre_say(self, message, **kwargs):
|
|
"While we could/should do 'at_say', this should be easier."
|
|
self.db.tutorstate = self.db.tutorstate | TutorialState.SAY.value
|
|
return super().at_pre_say(message)
|
|
|
|
def at_look(self, target, **kwargs):
|
|
"""
|
|
When we look at something that _might_ be hidden, we check
|
|
if we are looking at something that _is_ hidden, i.e. has a
|
|
view lock of a particular tag, and if so, we interpret this as
|
|
'you are looking at something by name', therefore you see it.
|
|
So we give ourselves the right to see it from now on.
|
|
|
|
To use this, simply add the following lock:
|
|
|
|
@set thing/hidden_tag = target
|
|
@lock thing = view:tag(hidden_target)
|
|
|
|
Where thing is the single name of the hidden object.
|
|
And 'target' is some label. If not given, this defaults
|
|
to the name of the thing.
|
|
"""
|
|
|
|
# If they 'look' at a target with tags, they get those locks:
|
|
# For instance: @set waterfall/hidden_tag = "hidden_cave"
|
|
if target.db.hidden_tag:
|
|
for hidden_tag in target.db.hidden_tag.split(';'):
|
|
self.tags.add(hidden_tag)
|
|
|
|
if target.is_typeclass("typeclasses.rooms.Room"):
|
|
self.db.tutorstate = self.db.tutorstate | TutorialState.LOOK.value
|
|
else:
|
|
self.db.tutorstate = self.db.tutorstate | TutorialState.LOOKAT.value
|
|
|
|
# Regardless of what happened before, we return the normal
|
|
# function call.
|
|
return super().at_look(target)
|
|
|
|
def announce_action(self, message):
|
|
"""
|
|
Replaces a location's 'msg_contents' with an emote.
|
|
"""
|
|
targets = [item for item in self.location.contents if item != self]
|
|
send_emote(self, targets, f"/Me {message}", msg_type="say", anonymous_add=None, quiet=True)
|
|
|
|
def spell_sequence(self, location, messages, time_delay=1):
|
|
"""
|
|
Send one or more messages to 'location' with a delay.
|
|
"""
|
|
location.msg_contents("\n" + routput(messages[0]))
|
|
for idx, msg in enumerate(messages[1:]):
|
|
delay(time_delay * (idx + 1),
|
|
location.msg_contents, "\n" + routput(msg))
|
|
|
|
def do_fly(self, location):
|
|
"""
|
|
Allow a wizard to arrive thematically into a new location.
|
|
"""
|
|
if location == "home":
|
|
dest = self.home
|
|
else:
|
|
dest = self.global_search(location)
|
|
if dest and dest.is_typeclass("typeclasses.characters.Character"):
|
|
dest = dest.location
|
|
elif not dest or not dest.is_typeclass("typeclasses.rooms.Room"):
|
|
self.msg(f"Not sure where, '{location}' is.")
|
|
return
|
|
|
|
if self.db.disappear_msg:
|
|
self.spell_sequence(self.location,
|
|
self.db.disappear_msg.split(';;'),
|
|
self.db.appear_delay or 2)
|
|
self.move_to(dest, move_type="magic", quiet=True)
|
|
|
|
if self.db.reappear_msg:
|
|
self.spell_sequence(self.location,
|
|
self.db.reappear_msg.split(';;'),
|
|
self.db.appear_delay or 2)
|