496 lines
15 KiB
Python
496 lines
15 KiB
Python
"""
|
|
The WyldWood Character Creation
|
|
"""
|
|
|
|
import inflect
|
|
from typeclasses.characters import Character
|
|
|
|
# from evennia.prototypes.spawner import spawn
|
|
# from evennia.utils.evtable import EvTable
|
|
|
|
from utils.word_list import paragraph
|
|
|
|
_INFLECT = inflect.engine()
|
|
|
|
|
|
#########################################################
|
|
# Utilities
|
|
#########################################################
|
|
|
|
# def create_objects(character):
|
|
# """do the actual object spawning"""
|
|
# # since our example chargen saves the starting prototype to an attribute, we retrieve that here
|
|
# proto = dict(character.db.starter_weapon)
|
|
# # set the location to our character, so they actually have it
|
|
# proto["location"] = character
|
|
# # create the object
|
|
# spawn(proto)
|
|
|
|
|
|
#########################################################
|
|
# Welcome Page
|
|
#########################################################
|
|
|
|
|
|
def menunode_welcome(caller):
|
|
"""Starting page."""
|
|
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_welcome"
|
|
|
|
text = paragraph("""\
|
|
|wHoward's Character Creator, v1|n
|
|
|
|
This game takes place in the |cDomain of the WyldWood|n, and your
|
|
character may be a faery denizen of this strange wonderland, or
|
|
perhaps someone more typical, but who stepped into a faery circle
|
|
or other portal and landed in this strange, magical place.
|
|
|
|
To create this fantasy character, I will ask a series of
|
|
questions, and you will type your answer.
|
|
Type |ghelp|n at any of these prompts, for additional information.
|
|
Type |gquit|n to stop the process (recover your session using
|
|
|gcharcreate|n).
|
|
""") + """
|
|
|
|
1. Short Description
|
|
2. Set Gender
|
|
3. Character's "Pose"
|
|
4. Long Description
|
|
5. Character's Name
|
|
""" + "|/Type |cReturn|n to begin creating your character."
|
|
|
|
help = paragraph("""
|
|
Type |gnext|n or |gskip|n (or even a blank answer), to keep any
|
|
previously entered data for the step and go to the next prompt.
|
|
|
|
Type |gback|n to return to a previous step, well, except for
|
|
this first message. Type |gquit|n instead. :-D
|
|
""")
|
|
|
|
options = {"key": "_default", "goto": _check_welcome}
|
|
return (text, help), options
|
|
|
|
|
|
def _check_welcome(caller, raw_string, **kwargs):
|
|
"""Check and fix the welcome message."""
|
|
return "menunode_sdesc"
|
|
|
|
|
|
#########################################################
|
|
# Short Description
|
|
#########################################################
|
|
|
|
def menunode_sdesc(caller, raw_string, **kwargs):
|
|
"""Starting page."""
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_sdesc"
|
|
|
|
# check if an error message was passed to the node. if so, you'll
|
|
# want to include it into your "name prompt" at the end of the
|
|
# node text.
|
|
|
|
if error := kwargs.get("error"):
|
|
prompt_text = f"{error}. Enter a different short description:"
|
|
elif char.db._sdesc:
|
|
prompt_text = f"You previously wrote: |w{char.db._sdesc}|n"
|
|
else:
|
|
prompt_text = paragraph("""\
|
|
Let's begin with a two or three word description, e.g.
|
|
"big-eared hobbit", "beardless dwarf" or "frumpy, old woman":
|
|
""")
|
|
|
|
text = paragraph(f"""\
|
|
|wShort Description|n
|
|
|
|
Since this game emphasizes |wrole playing|n, you will pick your
|
|
character's name at the end (since no one will know, or should
|
|
know, your |wreal|n name).
|
|
|
|
When you and another player characters uses the |glook|n command,
|
|
you will see a description of the area, including a list of all
|
|
the other characters, but using a short description instead of the
|
|
character's name.
|
|
|
|
What should your |wshort description|n identify your character?
|
|
|
|
{prompt_text}""")
|
|
|
|
help = paragraph("""\
|
|
In fact, you will even use this description to refer to them, for
|
|
instance, to get a better description of the character, you might
|
|
type |glook hobbit|n, or if there are two hobbits in the area,
|
|
they would need to type more of
|
|
the description: |glook big-eared hobbit|n
|
|
|
|
|wNote:|n Type, |gnext|n to keep your value and skip to the next step.""")
|
|
|
|
# Free-formed text:
|
|
options = {"key": "_default", "goto": _check_sdesc}
|
|
return (text, help), options
|
|
|
|
|
|
def _check_sdesc(caller, raw_string, **kwargs):
|
|
"""Check and confirm short description"""
|
|
|
|
# strip any extraneous whitespace from the raw text
|
|
sdesc = raw_string.strip()
|
|
|
|
if sdesc == 'back':
|
|
return "menunode_welcome"
|
|
if sdesc in ('next', 'skip') or not sdesc:
|
|
return "menunode_gender"
|
|
|
|
# aside from validation, the built-in normalization function from
|
|
# the caller's Account does some useful cleanup on the input, just
|
|
# in case they try something sneaky:
|
|
sdesc = caller.account.normalize_username(sdesc)
|
|
|
|
# Make sure it isn't too long.
|
|
if len(sdesc) > 30:
|
|
return (
|
|
"menunode_sdesc",
|
|
{"error": f"|w{sdesc}|n is too long. Try less than two dozen letters."},
|
|
)
|
|
|
|
caller.new_char.db._sdesc = sdesc
|
|
return "menunode_gender"
|
|
|
|
|
|
#########################################################
|
|
# Character Gender
|
|
#########################################################
|
|
|
|
def menunode_gender(caller, raw_string, **kwargs):
|
|
"""Choosing a gender for the character."""
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_gender"
|
|
|
|
text = paragraph(f"""\
|
|
|wCharacter Gender|n
|
|
|
|
Specifying the gender allows the system to use the correct
|
|
pronouns for your character. You can change this in the game
|
|
using the |ggender|n command.
|
|
|
|
Choose one of the following:""")
|
|
|
|
help = paragraph("""\
|
|
This feature only works from the system, not from other players.
|
|
A character would need to tell others of their preferred pronouns.
|
|
""")
|
|
|
|
options = (
|
|
{"key": "m",
|
|
"desc": "male with pronouns, he/him/his",
|
|
"goto": (_check_gender, {"gender": "male"}),
|
|
},
|
|
{"key": "f",
|
|
"desc": "female with pronouns, she/her/hers",
|
|
"goto": (_check_gender, {"gender": "female"}),
|
|
},
|
|
{"key": "n",
|
|
"desc": "neutral with pronouns, it/its",
|
|
"goto": (_check_gender, {"gender": "neutral"}),
|
|
},
|
|
{"key": "a",
|
|
"desc": "ambiguous with pronouns, they/them/their",
|
|
"goto": (_check_gender, {"gender": "ambiguous"}),
|
|
},
|
|
{"key": "b",
|
|
"desc": "go back and change the short description",
|
|
"goto": (menunode_sdesc),
|
|
},
|
|
)
|
|
return (text, help), options
|
|
|
|
|
|
def _check_gender(caller, rawstring, **kwargs):
|
|
"""Check and confirm pose"""
|
|
|
|
caller.new_char.db.gender = kwargs['gender']
|
|
return "menunode_pose"
|
|
|
|
|
|
#########################################################
|
|
# Posing the Character
|
|
#########################################################
|
|
|
|
def menunode_pose(caller, raw_string, **kwargs):
|
|
"""Posing a character."""
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_pose"
|
|
|
|
if char.db.pose:
|
|
prompt_text = f"You previously wrote: |w{char.db.pose}|n"
|
|
else:
|
|
prompt_text = ''
|
|
|
|
text = paragraph(f"""\
|
|
|wPosing a Character|n
|
|
|
|
Along with the short description given in the previous step, we
|
|
can |wpose|n our character. For instance, entering:
|
|
|
|
|wsmoking a pipe|n
|
|
|
|
Might show our character as:
|
|
|
|
a |mbig-eared hobbit|w smoking a pipe|n
|
|
|
|
As the game plays, you can change this at any time using the
|
|
|gpose|n command. This is just sets up a default pose, which can
|
|
return to with |gpose reset|n. Even this isn't permanent, as you
|
|
can change the default with something like:
|
|
|
|
|gpose default sitting on the ground|n
|
|
|
|
{prompt_text}
|
|
|
|
Enter an initial pose to describe, |m{char.db._sdesc}|n:""")
|
|
|
|
help = paragraph("""\
|
|
Don't worry much about this pose, since you can (and will) change
|
|
it as you play the game.
|
|
|
|
Having an initial pose makes the game (and your character) look
|
|
better.
|
|
|
|
Enter an initial pose to describe your character, or type |gback|n
|
|
to change your short description.""")
|
|
|
|
# Free-formed text:
|
|
options = {"key": "_default", "goto": _check_pose}
|
|
return (text, help), options
|
|
|
|
|
|
def _check_pose(caller, raw_string, **kwargs):
|
|
"""Check and confirm pose"""
|
|
|
|
# strip any extraneous whitespace from the raw text
|
|
pose = raw_string.strip()
|
|
|
|
if pose == 'back':
|
|
return "menunode_gender"
|
|
if pose in ('next', 'skip') or not pose:
|
|
return "menunode_desc"
|
|
|
|
# First, allow the character to be posed:
|
|
caller.new_char.pose = True
|
|
caller.new_char.db.pose = pose
|
|
caller.new_char.db.pose_default = pose
|
|
|
|
return "menunode_desc"
|
|
|
|
|
|
#########################################################
|
|
# Full Description
|
|
#########################################################
|
|
|
|
def menunode_desc(caller, raw_string, **kwargs):
|
|
"""Posing a character."""
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_desc"
|
|
|
|
if char.db.desc and char.db.desc != "This is a character.":
|
|
prompt_text = f"You previously wrote:\n\n |w{char.db.desc}|n"
|
|
else:
|
|
prompt_text = ''
|
|
|
|
text = paragraph(f"""
|
|
|wCharacter's Full Description|n
|
|
|
|
Unlike the short description given in the previous step (see only
|
|
when glancing around the room), a player will see this description
|
|
when they type:
|
|
|
|
|glook {char.db._sdesc}|n
|
|
|
|
This can be as long as you would like. Keep in mind this
|
|
description is what a player |wsees|n. so, no backstory here.
|
|
Well, unless you can cleverly weave in backstory hints from that
|
|
scar across your cheek.
|
|
|
|
You can change this at any time using the |gdesc|n command.
|
|
|
|
{prompt_text}
|
|
|
|
Enter your character's full description:""")
|
|
|
|
help = paragraph("""\
|
|
What makes a good character description? You want someone to be
|
|
able to picture your character in their mind, so hair, skin and
|
|
eye color, as well as what they are wearing.
|
|
|
|
To make your character truly memorable, mention the unique
|
|
features, so think of your character's backstory, and describe the
|
|
small hammer on your necklace, so you can tell others who inquire
|
|
about your father's last wish...
|
|
|
|
Enter your character's full description or type |gback|n to change
|
|
your pose.""")
|
|
|
|
# Free-formed text:
|
|
options = {"key": "_default", "goto": _check_desc}
|
|
return (text, help), options
|
|
|
|
|
|
def _check_desc(caller, raw_string, **kwargs):
|
|
"""Check and confirm desc"""
|
|
|
|
# strip any extraneous whitespace from the raw text
|
|
desc = raw_string.strip()
|
|
|
|
if desc == 'back':
|
|
return "menunode_pose"
|
|
if desc in ('next', 'skip') or not desc:
|
|
return "menunode_choose_name"
|
|
|
|
# First, allow the character to be descd:
|
|
caller.new_char.db.desc = desc
|
|
|
|
return "menunode_choose_name"
|
|
|
|
|
|
#########################################################
|
|
# Choosing a Name
|
|
#########################################################
|
|
|
|
def menunode_choose_name(caller, raw_string, **kwargs):
|
|
"""Name selection"""
|
|
char = caller.new_char
|
|
|
|
# another decision, so save the resume point
|
|
char.db.chargen_step = "menunode_choose_name"
|
|
|
|
# check if an error message was passed to the node. if so, you'll
|
|
# want to include it into your "name prompt" at the end of the
|
|
# node text.
|
|
if error := kwargs.get("error"):
|
|
prompt_text = f"{error}. Enter a different name."
|
|
else:
|
|
# there was no error, so just ask them to enter a name.
|
|
prompt_text = "Enter a name here to check if it's available."
|
|
|
|
# this will print every time the player is prompted to choose a name,
|
|
# including the prompt text defined above
|
|
text = paragraph(f"""\
|
|
|wChoosing a Name|n
|
|
|
|
Now, let's give your character a name. Unless you tell others, no
|
|
one will know (or need to know) your this name, however, you will
|
|
use this name, when you type |gic Name|n to choose this character
|
|
to play.
|
|
|
|
{prompt_text}""")
|
|
|
|
help = paragraph("""
|
|
Since you may have to type the entire name, you may want this to
|
|
be a single word for the first name. Your background could include
|
|
your full family name.
|
|
""")
|
|
|
|
# since this is a free-text field, we just have the one
|
|
options = {"key": "_default", "goto": _check_charname}
|
|
return (text, help), options
|
|
|
|
|
|
def _check_charname(caller, raw_string, **kwargs):
|
|
"""Check and confirm name choice"""
|
|
|
|
# strip any extraneous whitespace from the raw text
|
|
# if you want to do any other validation on the name, e.g. no punctuation allowed, this
|
|
# is the place!
|
|
charname = raw_string.strip()
|
|
|
|
if charname == 'back':
|
|
return "menunode_desc"
|
|
if charname in ('next', 'skip') or not charname:
|
|
return (
|
|
"menunode_choose_name",
|
|
{"error": f"Can't skip this step. Please enter a name."},
|
|
)
|
|
|
|
# aside from validation, the built-in normalization function from the caller's Account does
|
|
# some useful cleanup on the input, just in case they try something sneaky
|
|
charname = caller.account.normalize_username(charname)
|
|
|
|
# check to make sure that the name doesn't already exist
|
|
candidates = Character.objects.filter_family(db_key__iexact=charname)
|
|
if len(candidates):
|
|
# the name is already taken - report back with the error
|
|
return (
|
|
"menunode_choose_name",
|
|
{"error": f"|w{charname}|n is unavailable. Enter a different name."},
|
|
)
|
|
else:
|
|
# it's free! set the character's key to the name to reserve it
|
|
caller.new_char.key = charname
|
|
# continue on to the confirmation node
|
|
return "menunode_confirm_name"
|
|
|
|
|
|
def menunode_confirm_name(caller, raw_string, **kwargs):
|
|
"""Confirm the name choice"""
|
|
char = caller.new_char
|
|
|
|
text = paragraph(f"""\
|
|
|wCreate Character?|n
|
|
|
|
Shall I create your character, |c{char.key}|n:
|
|
|
|
|w{char.db._sdesc} {char.db.pose}|n
|
|
|
|
Description:
|
|
|
|
{char.db.desc}""")
|
|
|
|
options = [
|
|
{"key": ("Yes", "y"), "goto": "menunode_end"},
|
|
{"key": ("No", "n"), "goto": "menunode_choose_name"},
|
|
]
|
|
return text, options
|
|
|
|
|
|
#########################################################
|
|
# The End
|
|
#########################################################
|
|
|
|
|
|
def menunode_end(caller, raw_string):
|
|
"""End-of-chargen cleanup."""
|
|
char = caller.new_char
|
|
|
|
# clear in-progress status
|
|
char.attributes.remove("chargen_step")
|
|
char.db.tutorstate = 0
|
|
char.db.visited = False
|
|
|
|
text = paragraph(f"""
|
|
You have created a character. I hope you enjoy playing this game.
|
|
|
|
To stop playing with this character, type: |gooc|n
|
|
|
|
You can create a new character with: |gcharcreate|n
|
|
|
|
To play with this one, type: |gic {caller.new_char.key}|n
|
|
|
|
As always, type |ghelp|n (especially |ghelp start|n),
|
|
to learn more about how to play this game.
|
|
|
|
Alrighty then, let's log in and start playing the game.
|
|
Imagine that you are now in a strange land on the edge of Forever.
|
|
Welcome to the cozy forest section in the |wDomain of the WyldWood|n.
|
|
""")
|
|
return text, None
|