The tutorial bird of happiness
This commit is contained in:
parent
107241f925
commit
1876919acd
5 changed files with 333 additions and 59 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -55,3 +55,4 @@ nosetests.xml
|
|||
# VSCode config
|
||||
.vscode
|
||||
/.venv/
|
||||
.DS_Store
|
||||
|
|
|
|||
|
|
@ -13,7 +13,25 @@ from evennia.prototypes.spawner import spawn
|
|||
|
||||
from utils.word_list import Token, 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 blue 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'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, DefaultCharacter):
|
||||
"""
|
||||
|
|
@ -26,23 +44,21 @@ class Character(Object, DefaultCharacter):
|
|||
"""
|
||||
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):
|
||||
if self.db.visited:
|
||||
self.msg(f"""\n“Welcome back, {self.key.capitalize()}.”\n""")
|
||||
self.execute_cmd("look")
|
||||
else:
|
||||
self.db.visited = True
|
||||
self.msg("""
|
||||
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.
|
||||
|
||||
(Type 'inventory' or 'inv' or just 'i' to see what you carry).
|
||||
""")
|
||||
self.db.tutorstate = 0
|
||||
self.msg(INTRO)
|
||||
self.account.db._last_puppet = self
|
||||
self.execute_cmd("look")
|
||||
|
||||
def create_letter(self):
|
||||
"create a welcome letter in a character's inventory"
|
||||
|
|
@ -51,19 +67,7 @@ You see an envelope of parchment wedged under a scaly protrusion of bark...insid
|
|||
"key": "letter",
|
||||
"desc": "A letter of familiar penmanship stuffed in an envelope.",
|
||||
})[0]
|
||||
letter.db.inside = f"""You read a letter with an oddly familiar penmanship:
|
||||
|
||||
My dearest {self.name.capitalize()},
|
||||
|
||||
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'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 me start' for details on playing this game)
|
||||
"""
|
||||
letter.db.inside = READ_LETTER.format(self.name.capitalize())
|
||||
letter.location = self
|
||||
|
||||
def do_take(self, args):
|
||||
|
|
@ -81,7 +85,7 @@ You see an envelope of parchment wedged under a scaly protrusion of bark...insid
|
|||
victim = self.search(from_whom)
|
||||
if victim:
|
||||
thing = victim.has(to_take)
|
||||
if thing:
|
||||
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}!",
|
||||
|
|
@ -94,11 +98,50 @@ You see an envelope of parchment wedged under a scaly protrusion of bark...insid
|
|||
self.msg(f"You don't see {from_whom}.")
|
||||
|
||||
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 cancelled.
|
||||
"""
|
||||
if self.db.is_sitting:
|
||||
self.msg("You stand up first...")
|
||||
self.db.is_sitting = False;
|
||||
return True
|
||||
"""
|
||||
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
|
||||
self.msg(f"State: {self.db.tutorstate}")
|
||||
|
||||
if self.db.is_sitting:
|
||||
self.msg("You stand up first...")
|
||||
self.db.is_sitting = False;
|
||||
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:
|
||||
|
||||
@lock thing = view:tag(hidden_thing, mp)
|
||||
|
||||
Where thing is the single name of the hidden object.
|
||||
"""
|
||||
hidden_tag = f"hidden_{target}"
|
||||
lock_string = f"view:tag({hidden_tag}, mp)"
|
||||
view_lock = target.locks.get("view")
|
||||
|
||||
if view_lock == lock_string:
|
||||
self.tags.add(hidden_tag, category="mp")
|
||||
|
||||
self.msg(f"Initial look State: {self.db.tutorstate}")
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -234,28 +234,3 @@ class Object(ObjectParent, DefaultObject):
|
|||
return i
|
||||
elif i == item:
|
||||
return i
|
||||
|
||||
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:
|
||||
|
||||
@lock thing = view:tag(hidden_thing, mp)
|
||||
|
||||
Where thing is the single name of the hidden object.
|
||||
"""
|
||||
hidden_tag = f"hidden_{target}"
|
||||
lock_string = f"view:tag({hidden_tag}, mp)"
|
||||
view_lock = target.locks.get("view")
|
||||
|
||||
if view_lock == lock_string:
|
||||
self.tags.add(hidden_tag, category="mp")
|
||||
|
||||
# Regardless of what happened before, we return the normal
|
||||
# function call.
|
||||
return super().at_look(target)
|
||||
|
|
|
|||
183
typeclasses/tutorial.py
Executable file
183
typeclasses/tutorial.py
Executable file
|
|
@ -0,0 +1,183 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
from evennia import (
|
||||
Command,
|
||||
CmdSet,
|
||||
TICKER_HANDLER,
|
||||
syscmdkeys,
|
||||
create_script,
|
||||
)
|
||||
from evennia.prototypes.spawner import spawn
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from typeclasses.objects import Object
|
||||
from typeclasses.npcs import CarriableNPC
|
||||
from utils.word_list import routput
|
||||
|
||||
import random
|
||||
|
||||
MSGS = {
|
||||
"START": [
|
||||
"A small bird flies over to you, and perches on your shoulder. “Hello,” it says in your ear. “I’m the Tutorial Bird,” it chirps, “and I’m here to break the fourth wall, and help you figure out this game. Feel free to |bshoo|n me away, and I’ll leave you alone to explore.”",
|
||||
"The bird says, “In this story game, you type commands (verbs) to do things. For instance, type the word |blook|n, and press the |wReturn|n key to look around at your surroundings and to see where you are, what you can do, and where you can go.”",
|
||||
"The bird perched on your shoulder looks at you expectantly.",
|
||||
"“Go ahead,” says the bird, “Type |blook|n. If you are on the web site, you may need to click in the box in the lower part of the screen.”",
|
||||
"The bird preens itself.",
|
||||
"The bird says, “Would you like me to stay and help you out?”",
|
||||
"“If so,” says the bird, “type |blook|n to look around at your surroundings.”",
|
||||
],
|
||||
"LOOK": [
|
||||
"“That’s great,” says the little blue bird. “From that, you know you are standing in a grove of trees near a puddle. You can now type |blook puddle|n to examine it.”",
|
||||
"“You can also |blook trees|n, |blook moss|n, or … well, you get the idea,” it says.",
|
||||
],
|
||||
"LOOKAT": [
|
||||
"“You seem to be getting the hang of this game,” chirps the bird. “When you typed |blook|n before, you noticed exits. These are directions or places you can go. For instance, if you type |bsouth|n (or just |bs|n as a shortcut), you can check out our pond where you can catch obnoxious fish.”",
|
||||
"“The path to the |beast|n is the meadow of the BHB, the Big Hairy Beast,” the little bird on your shoulder says. “It's adorable if you can find it. The path to the |bwest|n leads to Dabbler’s place.”",
|
||||
"“Yes, I could fly myself,” warbles the little bird, “but I’m trying to help you get around.”",
|
||||
"The bird looks at you expectantly.",
|
||||
"“Shall we explore other places?” asks the bird on your shoulder.",
|
||||
"“I hope I’m not rushing you,” the bird mentions, “as you can always come back to explore more.”",
|
||||
],
|
||||
"MOVE": [
|
||||
"\"Thanks for lift. This is a nice place,\" says the bird on your shoulder. \"When you come to a new area, the game shows you what you are seeing, so you don’t have to type |blook|n … even though you still can.\"",
|
||||
"\"Another interesting command,\" it chirps, \"is the |bsay|n command, that lets you talk to anyone in the area. Usually, we use this command for talking to other players in the game, but some creatures listen to what you say.\"",
|
||||
"\"Go ahead, just typing |bsay Hello.|n\"",
|
||||
|
||||
],
|
||||
"SAY": [
|
||||
"“How’s it going?” the bird asks, “Are you enjoying this game so far?”",
|
||||
"The bird says, “This place has been a respite from the outside turbulence.”",
|
||||
"\"A shortcut to |bsay|n is to type either a double or single quote, and then your message.\"",
|
||||
"\"You can also use |bpose|n to state something about yourself, \" it says. \" For instance, type |bpose smiles.|n\"",
|
||||
# POSE:
|
||||
"The bird stands on one leg.",
|
||||
"“See,” says the bird, “I s’pose I can pose too.”",
|
||||
"“A shortcut to that command is typing |b:|n (a colon character).”",
|
||||
"The bird says, \"The game has more commands. Typing |bhelp|n gives you a list of commands. That list changes depending on where you are and what you are carrying. For instance, you could type |bshoo|n for me to fly away, and then that command wouldn’t be available again.\"",
|
||||
# ],
|
||||
# "HELP": [
|
||||
"The bird chirps, “You can learn more about the individual commands. For instance, type |bhelp say|n and you would see about the shortcuts I told you about. You can also see new commands, like |bhelp get|n.”",
|
||||
"“Type |bhelp start|n for a repeat of much of what we’ve talked about,” says the bird.",
|
||||
"The bird says, “The |binv|n shows you what you are carrying. I see you picked up a letter. You can also type |bread letter|n.”",
|
||||
"“Since this game hosts many users,” says the bird, “you can show them what they see when they look at you, using the |b@setdesc|n command. Check out the help, |bhelp @setdesc|n, or just type: |w@setdesc A pixie with a shock of blue hair and a gold chain.|n”",
|
||||
"“I’m guessing your last question,” chirps the little bird on your shoulder, “is the goal of this game.”",
|
||||
"“That is a good question,” says the bird, “and I’m not sure what to say. This game has no goal, nor point. I guess you could look at it as a philosophical metaphor for existential existence in a post-modern world in the death throes of capitalism, but…”",
|
||||
"The bird says, “This is a cozy little game about role playing a storybook character and maybe exploring and looking for eggs… no, that’s not right. Something about eggs. Easter eggs? Something like that.”",
|
||||
"“Wander and look around. Find a cozy place to chat with others who have logged in,” says the bird.",
|
||||
"“If you’re interested in |wbuilding|n and expanding this world,” chirps the bird, “talk to Dabbler... Oh, see if you can find Dabbler, the guy that made most of this. That is a great goal.”",
|
||||
"“What do you say?” says the bird. “Think you got the hang of playing this?”",
|
||||
]
|
||||
}
|
||||
|
||||
MSGS_WAITING = [
|
||||
"The [little|blue|] bird [on your shoulder|] preens itself.",
|
||||
"The [little|blue|] bird [on your shoulder|] [looks|stares] at you [expectantly|waiting|patiently].",
|
||||
"The bird looks up at the sky. “It rains here often,” it says.",
|
||||
"The bird looks up at the sky. “Looks like rain is coming,” it says.",
|
||||
"“I like the rain,” the bird says, “as it keeps the moss green and the puddles full.”",
|
||||
"“They say if you don’t like the weather,” tweets the bird, “just wait.”",
|
||||
]
|
||||
|
||||
class TutorialState(Enum):
|
||||
"The acceptable states of fishing."
|
||||
START = 0
|
||||
LOOK = 1
|
||||
LOOKAT = 2
|
||||
MOVE = 4
|
||||
SAY = 8
|
||||
|
||||
|
||||
class CmdShoo(Command):
|
||||
"""
|
||||
End the tutorial by shooing the bird away.
|
||||
"""
|
||||
key = "shoo"
|
||||
|
||||
def func(self):
|
||||
self.obj.do_end_tutorial(self.caller)
|
||||
|
||||
|
||||
class CmdSetBird(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdShoo)
|
||||
|
||||
|
||||
class TutorBird(CarriableNPC):
|
||||
"""
|
||||
Bird that routinely tells a player a new hint.
|
||||
"""
|
||||
# The number of ticks to check for the time for a message:
|
||||
bird_tick = 18
|
||||
|
||||
# The number of waiting messages to issue before stopping the tutorial:
|
||||
wait_messages = 3
|
||||
|
||||
def at_object_creation(self):
|
||||
self.cmdset.add_default(CmdSetBird)
|
||||
|
||||
# Set the positions for all the different states:
|
||||
self.db.tutor_msg_num = {
|
||||
"START": 0,
|
||||
"LOOK": 0,
|
||||
"LOOKAT": 0,
|
||||
"MOVE": 0,
|
||||
"SAY": 0,
|
||||
}
|
||||
self.db.waiting = 0
|
||||
|
||||
TICKER_HANDLER.add(interval=self.bird_tick,
|
||||
callback=self.do_speak)
|
||||
|
||||
def do_start_tutorial(character):
|
||||
bird = spawn({
|
||||
"typeclass": "typeclasses.tutorial.TutorBird",
|
||||
"key": "bird",
|
||||
"aliases": ["blue bird", "tutor", "tutor bird"],
|
||||
"desc": "You see a tiny bird with blue feathers trimmed with white. It's large black eyes looks at you quizically. It seems friendly, and overly helpful.",
|
||||
})[0]
|
||||
bird.location = character
|
||||
|
||||
def do_end_tutorial(self, character):
|
||||
"""
|
||||
A visual way to stop the tutorial
|
||||
"""
|
||||
character.msg("“Have fun, and I’ll leave you here,” chirps the bird, as it flies away.")
|
||||
self.delete()
|
||||
|
||||
def get_msg_level(self, character, level):
|
||||
choices = MSGS[level.name]
|
||||
msg_num = self.db.tutor_msg_num.get(level.name, 0)
|
||||
if msg_num >= len(choices):
|
||||
# Ran out of messages?
|
||||
if self.db.waiting > self.wait_messages or level == TutorialState.SAY:
|
||||
msg = self.do_end_tutorial(character)
|
||||
else:
|
||||
msg = routput(random.choice(MSGS_WAITING))
|
||||
self.db.waiting += 1
|
||||
else:
|
||||
msg = choices[msg_num]
|
||||
self.db.tutor_msg_num[level.name] += 1
|
||||
self.db.waiting = 0 # Reset the waiting period
|
||||
return msg
|
||||
|
||||
def get_msg(self, character, remaining_states):
|
||||
try:
|
||||
# Take the highest value from states...
|
||||
level = remaining_states.pop()
|
||||
if character.db.tutorstate >= level.value:
|
||||
return self.get_msg_level(character, level)
|
||||
else:
|
||||
return self.get_msg(character, remaining_states)
|
||||
except IndexError:
|
||||
return self.get_msg_level(character, TutorialState.START)
|
||||
|
||||
def do_speak(self):
|
||||
"""
|
||||
Called at a repeatable sequence by the ticker, and
|
||||
it calls at_say() in order to do a type of monologue.
|
||||
"""
|
||||
character = self.location
|
||||
msg = self.get_msg(character, [level for level in TutorialState])
|
||||
if msg:
|
||||
character.msg(f"\n{msg}")
|
||||
|
|
@ -24,26 +24,98 @@ Each dict is on the form
|
|||
|
||||
"""
|
||||
|
||||
# """
|
||||
# },
|
||||
# {
|
||||
# "key": "commands",
|
||||
# "alias": ["command"],
|
||||
# "locks": "read:all()",
|
||||
# "text": """
|
||||
|
||||
HELP_ENTRY_DICTS = [
|
||||
{
|
||||
"key": "start",
|
||||
"aliases": ["me start"],
|
||||
"aliases": ["intro"],
|
||||
"locks": "read:all()",
|
||||
"text": """Again, welcome to my cozy little game.
|
||||
|
||||
To |wplay this game|n, you typically type a |w<verb>|b for an action, or |w<verb> <object>|b combinations. For instance, type |blook|n to look around the area, and |blook tree|n to examine the trees in particular. The more you look, the more you explore.
|
||||
|
||||
What verbs are available depends on where you are and what you might be holding. Type |bhelp|n with so other option to get a list. Then type |bhelp look|n to get details on how to use the |wlook|n verb.
|
||||
What verbs are available depends on where you are and what you might be holding. Type |bhelp|n with no other option to get a list of those commands. Then type |bhelp look|n to get details on how to use the |wlook|n verb.
|
||||
|
||||
This is a multi-user game, you might run into other characters, and they may look at you, so let's use some special verbs to affect the world from outside the storybook. Type:
|
||||
A bit about this |bhelp|n system. This document is longer, and you'll see at the end of this paragraph, some _transient commands_, for instance, you can type |bnext|n to see the next page. However, you can ignore that list, and type |blook puddle|n to skip this and gander at the puddle that might be at your feet. For now, type |bnext|n to keep reading...
|
||||
\f
|
||||
This is a multi-user game, you might run into other characters, and they may look at you, so let's type the following to tell others what they see when they look at you:
|
||||
|
||||
|w@setdesc A frumpy, but spry person with large ears and a dark blue cloak.|n
|
||||
|
||||
You see what people see when they look at you by typing:
|
||||
While it can be quite long and descriptive, you are limited to a single paragraph, since when you hit the 'Return' key, you submit your description.
|
||||
|
||||
To see what people see when they look at you, type:
|
||||
|
||||
|wlook self|n
|
||||
|
||||
What is the goal of this game? Just to escape the chaos of the world and explore an idyllic setting. I call it my |gegg hunt game|n, as the game is full odd stuff to discover. Feel free to find me to chat, but good luck finding me, as I may be hiding.
|
||||
|
||||
This is the end of this help section, but I have some related topics to this intro, so you can type |bhelp start/commands|n to get a list of typical commands, or just start playing this game.
|
||||
|
||||
Enjoy!
|
||||
|
||||
# Subtopics
|
||||
|
||||
## Commands
|
||||
|
||||
A command is typically a 'verb'. The most common ones in this game are:
|
||||
|
||||
- 'look' to look around at your surroundings
|
||||
- 'look <thing>' to look at something particular
|
||||
- 'get <thing>' to pick up something
|
||||
- 'inventory' (or 'inv' or just 'i') for a list of what you carry
|
||||
- 'drop <thing>' to leave the thing in this area (but don't litter)
|
||||
- 'say' to talk to the characters and object in your current location
|
||||
- 'pose' to state a fact about yourself.
|
||||
|
||||
### Say
|
||||
|
||||
We often want to chat in these sorts of games, so instead of typing:
|
||||
|
||||
|wsay Good evening!|n
|
||||
|
||||
You can simply type:
|
||||
|
||||
|w"Good evening!
|
||||
|
||||
Or:
|
||||
|
||||
|w'Good evening!
|
||||
|
||||
Creatures and objects in this game can sometimes respond to what you say.
|
||||
|
||||
### Pose
|
||||
|
||||
To tell others what you are doing (without |bsay|ning it), you can |bpose|n, as in:
|
||||
|
||||
|wpose smiles.|n
|
||||
|
||||
We do this a lot, so use this shortcut:
|
||||
|
||||
|w: smiles.|n
|
||||
|
||||
What about abbreviating "is" statements? Yeah, this works:
|
||||
|
||||
|w:'s happy, and wants you to know it.|n
|
||||
|
||||
Your "pose" can be anything, for instance, you can pretend to be a wizard:
|
||||
|
||||
|w: casts a fireball, exploding the bush behind you!|n
|
||||
|
||||
But you may find staying in character more 'escape-y'.
|
||||
|
||||
## Exits
|
||||
|
||||
Exits are special commands, and move you to a new location. So typing |bsouth|n will move you to a new location (assuming that exit is available). Some exits can be abbreviated, so typing |bs|n alone is the same as 'south'.
|
||||
|
||||
Keep in mind that some exits may not be available until you |blook|n for them (but that is part of the exploratory game, like this).
|
||||
"""
|
||||
},
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue