moss-n-puddles/world/chargen_menu.py
2025-07-27 12:28:54 -07:00

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