228 lines
8.3 KiB
Python
228 lines
8.3 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
|
|
|
|
from evennia.objects.objects import DefaultCharacter
|
|
from evennia.contrib.game_systems.gendersub import GenderCharacter
|
|
from evennia.prototypes.spawner import spawn
|
|
from evennia.utils import delay, logger
|
|
|
|
from commands.wizards import CmdSetWizardSpells
|
|
from utils.word_list import routput
|
|
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
|
|
|
|
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):
|
|
"""
|
|
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.
|
|
"""
|
|
def at_object_creation(self):
|
|
"called when a character is first created."
|
|
self.db.tutorstate = 0
|
|
|
|
if self.dbref != "#1":
|
|
self.create_letter()
|
|
TutorBird.do_start_tutorial(self)
|
|
|
|
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
|
|
self.msg(INTRO)
|
|
self.account.db._last_puppet = self
|
|
|
|
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 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}.")
|
|
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} you can take.")
|
|
|
|
def at_pre_move(self, destination, **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)
|
|
|
|
class Wizard(Character):
|
|
"""
|
|
Upgrade a character with the following features:
|
|
|
|
@update self = typeclasses.character.Wizard
|
|
"""
|
|
def at_object_creation(self):
|
|
"""
|
|
Called when character is getting promoted to wizardy.
|
|
"""
|
|
self.cmdset.add(CmdSetWizardSpells)
|
|
|
|
def spell_sequence(self, location, messages, time_delay):
|
|
"""
|
|
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)
|