Compare commits
5 Commits
main
...
ambyPlaygr
Author | SHA1 | Date |
---|---|---|
Amber | 9e56ba1fcc | |
amber | 8e074bdb98 | |
Amber | e3cc6674f3 | |
Hazel Nova | 151db69595 | |
Amber | bdc519048a |
|
@ -9,7 +9,6 @@ from evennia.commands.command import Command as BaseCommand
|
|||
|
||||
# from evennia import default_cmds
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
"""
|
||||
Base command (you may see this if a child command had no help text defined)
|
||||
|
@ -32,6 +31,36 @@ class Command(BaseCommand):
|
|||
#
|
||||
pass
|
||||
|
||||
class CmdMeow(Command):
|
||||
|
||||
key = 'meow'
|
||||
|
||||
def func(self):
|
||||
args = self.args.strip()
|
||||
if not args:
|
||||
self.caller.msg("Who or what would you like to meow at?")
|
||||
return
|
||||
target = self.caller.search(args)
|
||||
if not target:
|
||||
return
|
||||
self.caller.msg(f"You meowed at {target.key}!")
|
||||
target.msg(f"You got meowed at by {self.caller.key}!")
|
||||
|
||||
class CmdBark(Command):
|
||||
|
||||
key = 'bark'
|
||||
|
||||
def func(self):
|
||||
args = self.args.strip()
|
||||
if not args:
|
||||
self.caller.msg("Who or what would you like to bark at?")
|
||||
return
|
||||
target = self.caller.search(args)
|
||||
if not target:
|
||||
return
|
||||
self.caller.msg(f"You barked at {target.key}!")
|
||||
target.msg(f"You got barked at by {self.caller.key}!")
|
||||
|
||||
|
||||
# -------------------------------------------------------------
|
||||
#
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
from evennia import CmdSet
|
||||
#from commands import encounter_cmdset as encounter
|
||||
from commands import command as cmd
|
||||
|
||||
class SetSpeciesHuman(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(cmd.CmdMeow())
|
||||
|
||||
class SetSpeciesCatkin(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(cmd.CmdMeow())
|
||||
|
||||
class SetSpeciesDogkin(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(cmd.CmdBark())
|
|
@ -16,6 +16,8 @@ own cmdsets by inheriting from them or directly from `evennia.CmdSet`.
|
|||
|
||||
from evennia.contrib.game_systems.clothing import ClothedCharacterCmdSet
|
||||
from evennia.contrib.game_systems.containers import ContainerCmdSet
|
||||
from evennia.contrib.rpg.character_creator.character_creator import ContribCmdCharCreate
|
||||
|
||||
from evennia import default_cmds
|
||||
|
||||
from .encounter_cmdset import CmdEngage
|
||||
|
@ -50,14 +52,10 @@ class AccountCmdSet(default_cmds.AccountCmdSet):
|
|||
key = "DefaultAccount"
|
||||
|
||||
def at_cmdset_creation(self):
|
||||
"""
|
||||
Populates the cmdset
|
||||
"""
|
||||
super().at_cmdset_creation()
|
||||
#
|
||||
# any commands you add below will overload the default ones.
|
||||
#
|
||||
|
||||
# Char creation
|
||||
self.add(ContribCmdCharCreate)
|
||||
|
||||
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
||||
"""
|
||||
|
@ -97,3 +95,4 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
|
|||
#
|
||||
# any commands you add below will overload the default ones.
|
||||
#
|
||||
|
||||
|
|
|
@ -77,11 +77,24 @@ class CmdKiss(EncounterCommand):
|
|||
self.caller.msg(f"You kiss {self.target}{self.adverb}")
|
||||
self.target.msg(f"{self.caller} kisses you{self.adverb}")
|
||||
|
||||
|
||||
class SetSpeciesHuman(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdBite)
|
||||
self.add(CmdKiss)
|
||||
|
||||
|
||||
class SetSpeciesCatkin(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdBite)
|
||||
self.add(CmdKiss)
|
||||
|
||||
class SetSpeciesDogkin(CmdSet):
|
||||
def at_cmdset_creation(self):
|
||||
self.add(CmdBite)
|
||||
self.add(CmdKiss)
|
||||
|
||||
|
||||
# Special encounter commands
|
||||
|
||||
class CmdPass(EncounterCommand):
|
||||
|
@ -169,6 +182,8 @@ class SetEncounterSpecial(CmdSet):
|
|||
self.add(CmdFlee)
|
||||
self.add(CmdOOC)
|
||||
self.add(CmdPose)
|
||||
self.add(CmdKiss)
|
||||
self.add(CmdBite)
|
||||
|
||||
# Encounter-related character commands
|
||||
|
||||
|
|
|
@ -33,9 +33,15 @@ from evennia.settings_default import *
|
|||
|
||||
# This is the name of your game. Make it catchy!
|
||||
SERVERNAME = "Multi-User Depravity"
|
||||
|
||||
IDLE_TIMEOUT = -1
|
||||
|
||||
AUTO_CREATE_CHARACTER_WITH_ACCOUNT = False
|
||||
AUTO_PUPPET_ON_LOGIN = False
|
||||
CHARGEN_MENU = "world.example_menu"
|
||||
|
||||
# Custom Traits
|
||||
#TRAIT_CLASS_PATHS = ["world.traits.EarTrait"]
|
||||
|
||||
######################################################################
|
||||
# Settings given in secret_settings.py override those in this file.
|
||||
######################################################################
|
||||
|
|
|
@ -23,9 +23,10 @@ several more options for customizing the Guest account system.
|
|||
"""
|
||||
|
||||
from evennia.accounts.accounts import DefaultAccount, DefaultGuest
|
||||
from evennia.contrib.rpg.character_creator.character_creator import ContribChargenAccount
|
||||
|
||||
|
||||
class Account(DefaultAccount):
|
||||
class Account(ContribChargenAccount):
|
||||
"""
|
||||
This class describes the actual OOC account (i.e. the user connecting
|
||||
to the MUD). It does NOT have visual appearance in the game world (that
|
||||
|
@ -102,3 +103,4 @@ class Guest(DefaultGuest):
|
|||
"""
|
||||
|
||||
pass
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
from evennia.contrib.game_systems.clothing import ClothedCharacter
|
||||
|
||||
from evennia.contrib.rpg.traits import TraitHandler
|
||||
#from evennia.contrib.rpg.traits import TraitProperty
|
||||
#from evennia.utils import lazy_property
|
||||
|
||||
from commands.encounter_cmdset import SetEncounterSpecial
|
||||
from world.species import SPECIES_CMDSET
|
||||
|
@ -11,16 +13,25 @@ class Character(ClothedCharacter):
|
|||
def at_object_creation(self):
|
||||
self.db.version = 1
|
||||
self.db.species = 0
|
||||
# ears = TraitProperty("Ears", trait_type="counter",
|
||||
# base=0, mod=0, min=0, max=2,
|
||||
# descs = {0:"human ears", 1:"cat ears", 2:"dog ears"})
|
||||
# tail = TraitProperty("Tail", trait_type="counter",
|
||||
# base=0, mod=0, min=0, max=2,
|
||||
# descs = {0:"no tail", 1:"cat tail", 2:"dog tail"})
|
||||
|
||||
def apply_encounter_cmdset(self):
|
||||
self.cmdset.add(SetEncounterSpecial)
|
||||
self.cmdset.add(SPECIES_CMDSET[self.db.species])
|
||||
|
||||
def revoke_encounter_cmdset(self):
|
||||
self.cmdset.remove(SPECIES_CMDSET[self.db.species])
|
||||
# self.cmdset.remove(SPECIES_CMDSET[self.db.species])
|
||||
self.cmdset.remove(SetEncounterSpecial)
|
||||
pass
|
||||
|
||||
def apply_basic_cmdset(self):
|
||||
self.cmdset.add(SPECIES_CMDSET[self.db.species])
|
||||
|
||||
def at_pre_move(self, destination, **kwargs):
|
||||
if self.ndb.encounter_handler:
|
||||
self.msg("You would need to leave the encounter first")
|
||||
|
|
|
@ -11,9 +11,34 @@ inheritance.
|
|||
|
||||
"""
|
||||
from evennia.objects.objects import DefaultObject
|
||||
from evennia.utils import lazy_property
|
||||
from evennia.contrib.rpg.traits import TraitProperty
|
||||
from evennia.contrib.rpg.traits import TraitHandler
|
||||
|
||||
class ObjectParent:
|
||||
pass
|
||||
|
||||
class Object(ObjectParent, DefaultObject):
|
||||
pass
|
||||
|
||||
#class ObjectPlayerDefault(DefaultObject):
|
||||
#class Object(ObjectParent, DefaultObject):
|
||||
#
|
||||
## @lazy_property
|
||||
## def traits(self):
|
||||
## return TraitHandler(self)
|
||||
##
|
||||
## @lazy_property
|
||||
## def looks(self):
|
||||
## return TraitHandler(self, db_attribute_key="looks")
|
||||
#
|
||||
# def at_object_creation(self):
|
||||
# self.looks.add("ears", "Ears", trait_type="counter",
|
||||
# base=0, mod=0, min=0, max=2,
|
||||
# descs = {0:"human ears", 1:"cat ears", 2:"dog ears"})
|
||||
# self.looks.add("tail", "Tail", trait_type="counter",
|
||||
# base=0, mod=0, min=0, max=2,
|
||||
# descs = {0:"no tail", 1:"cat tail", 2:"dog tail"})
|
||||
#
|
||||
# def return_looks(self):
|
||||
# pass
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
from commands.encounter_cmdset import SetSpeciesHuman
|
||||
|
||||
SPECIES_LIST = [
|
||||
"Human",
|
||||
]
|
||||
|
||||
SPECIES_CMDSET = [
|
||||
SetSpeciesHuman,
|
||||
]
|
|
@ -0,0 +1,615 @@
|
|||
"""
|
||||
An example EvMenu for the character creator contrib.
|
||||
|
||||
This menu is not intended to be a full character creator, but to demonstrate
|
||||
several different useful node types for your own creator. Any of the different
|
||||
techniques demonstrated here can be combined into a single decision point.
|
||||
|
||||
## Informational Pages
|
||||
|
||||
A set of nodes that let you page through information on different choices.
|
||||
|
||||
The example shows how to have a single informational page for each option, but
|
||||
you can expand that into sub-categories by setting the values to dictionaries
|
||||
instead of simple strings and referencing the "Option Categories" nodes.
|
||||
|
||||
## Option Categories
|
||||
|
||||
A pair of nodes which let you divide your options into separate categories.
|
||||
The base node has a list of categories as the options, which is passed to the
|
||||
child node. That child node then presents the options for that category and
|
||||
allows the player to choose one.
|
||||
|
||||
## Multiple Choice
|
||||
|
||||
Allows players to select and deselect options from the list in order to choose
|
||||
more than one. The example has a requirement of choosing exactly 3 options,
|
||||
but you can change it to a maximum or minimum number of required options -
|
||||
or remove the requirement check entirely.
|
||||
|
||||
## Simple List Options
|
||||
|
||||
If you just want a straightforward list of options, without any of the back-and-forth
|
||||
navigation or modifying of option text, evennia has an easy to use decorator
|
||||
available: `@list_node`
|
||||
|
||||
For an example of how to use it, check out the documentation for evennia.utils.evmenu
|
||||
- there's lots of other useful EvMenu tools too!
|
||||
|
||||
## Starting Objects
|
||||
|
||||
Allows players to choose from a selection of starting objects.
|
||||
|
||||
When creating starting objects e.g. gear, it's best to actually create them
|
||||
at the end, so you don't have to patch checks in for orphaned objects or
|
||||
infinite-object player exploits.
|
||||
|
||||
## Choosing a Name
|
||||
|
||||
The contrib character create command assumes the player will choose their name
|
||||
during character creation. This section validates name choices before confirming
|
||||
them.
|
||||
|
||||
## The End
|
||||
|
||||
It might not seem like an important part, since the players don't make a decision
|
||||
here, but the final node of character creation is where you finalize all of
|
||||
the decisions players made earlier. Initializing skills, creating starting gear,
|
||||
and other one-time method calls and set-up should be put here.
|
||||
"""
|
||||
|
||||
import inflect
|
||||
from typeclasses.characters import Character
|
||||
from evennia.contrib.rpg.traits import TraitProperty
|
||||
|
||||
from evennia.prototypes.spawner import spawn
|
||||
from evennia.utils import dedent
|
||||
from evennia.utils.evtable import EvTable
|
||||
|
||||
_INFLECT = inflect.engine()
|
||||
|
||||
|
||||
#########################################################
|
||||
# Welcome Page
|
||||
#########################################################
|
||||
|
||||
|
||||
def menunode_welcome(caller):
|
||||
"""Starting page."""
|
||||
text = dedent(
|
||||
"""\
|
||||
|wWelcome to Character Creation!|n
|
||||
|
||||
This is the starting node for all brand new characters. It's a good place to
|
||||
remind players that they can exit the character creator and resume later,
|
||||
especially if you're going to have a really long chargen process.
|
||||
|
||||
A brief overview of the game could be a good idea here, too, or a link to your
|
||||
game wiki if you have one.
|
||||
"""
|
||||
)
|
||||
help = "You can explain the commands for exiting and resuming more specifically here."
|
||||
options = {"desc": "Let's begin!", "goto": "menunode_info_base"}
|
||||
return (text, help), options
|
||||
|
||||
|
||||
#########################################################
|
||||
# Informational Pages
|
||||
#########################################################
|
||||
|
||||
# Storing your information in a dictionary like this makes the menu nodes much cleaner,
|
||||
# as well as making info easier to update. You can even import it from a different module,
|
||||
# e.g. wherever you have the classes actually defined, so later updates only happen in one place.
|
||||
_SPECIES_INFO_DICT = {
|
||||
"Human": dedent(
|
||||
"""\
|
||||
The most common species in the adventurers guild, at least for new recruits.
|
||||
|
||||
Humans are well-rounded and very adaptable.
|
||||
While they may not have claws or fangs or any fancy adaptations,
|
||||
they don't have any drawbacks or glaring weaknesses either.
|
||||
"""
|
||||
),
|
||||
"Catkin": dedent(
|
||||
"""\
|
||||
Relatively common, compared to more obscure Monsterkin types.
|
||||
|
||||
A variant of human descended from feline heritage,
|
||||
Catkin keep some traits of their ancestors, including ears, tails, and claws.
|
||||
"""
|
||||
),
|
||||
"Dogkin": dedent(
|
||||
"""\
|
||||
Relatively common, compared to more obscure Monsterkin types.
|
||||
|
||||
A variant of human descended from canine heritage,
|
||||
Dogkin keep some traits of their ancestors, including ears, tails, and fangs.
|
||||
"""
|
||||
)
|
||||
# Descriptions and species still very much WIP
|
||||
}
|
||||
|
||||
|
||||
def menunode_info_base(caller):
|
||||
"""Base node for the informational choices."""
|
||||
# this is a base node for a decision, so we want to save the character's progress here
|
||||
caller.new_char.db.chargen_step = "menunode_info_base"
|
||||
|
||||
text = dedent(
|
||||
"""\
|
||||
|wInformational Pages|n
|
||||
|
||||
Sometimes you'll want to let players read more about options before choosing
|
||||
one of them. This is especially useful for big choices like race or class.
|
||||
"""
|
||||
)
|
||||
help = "A link to your wiki for more information on classes could be useful here."
|
||||
options = []
|
||||
# Build your options from your info dict so you don't need to update this to add new options
|
||||
for species in _SPECIES_INFO_DICT.keys():
|
||||
options.append(
|
||||
{
|
||||
"desc": f"Learn about the |c{species}|n species",
|
||||
"goto": ("menunode_info_species", {"selected_species": species}),
|
||||
}
|
||||
)
|
||||
return (text, help), options
|
||||
|
||||
|
||||
# putting your kwarg in the menu declaration helps keep track of what variables the node needs
|
||||
def menunode_info_species(caller, raw_string, selected_species=None, **kwargs):
|
||||
"""Informational overview of a particular species"""
|
||||
|
||||
# sometimes weird bugs happen - it's best to check for them rather than let the game break
|
||||
if not selected_species:
|
||||
# reset back to the previous step
|
||||
caller.new_char.db.chargen_step = "menunode_welcome"
|
||||
# print error to player and quit the menu
|
||||
return "Something went wrong. Please try again."
|
||||
|
||||
# Since you have all the info in a nice dict, you can just grab it to display here
|
||||
text = _SPECIES_INFO_DICT[selected_species]
|
||||
help = "If you want option-specific help, you can define it in your info dict and reference it."
|
||||
options = []
|
||||
|
||||
# set an option for players to choose this class
|
||||
options.append(
|
||||
{
|
||||
"desc": f"Become {_INFLECT.an(selected_species)}",
|
||||
"goto": (_set_species, {"selected_species": selected_species}),
|
||||
}
|
||||
)
|
||||
|
||||
# once again build your options from the same info dict
|
||||
for species in _SPECIES_INFO_DICT.keys():
|
||||
# make sure you don't print the currently displayed page as an option
|
||||
if species != selected_species:
|
||||
options.append(
|
||||
{
|
||||
"desc": f"Learn about the |c{species}|n species",
|
||||
"goto": ("menunode_info_species", {"selected_species": species}),
|
||||
}
|
||||
)
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _set_species(caller, raw_string, selected_species=None, **kwargs):
|
||||
# a species should always be selected here
|
||||
if not selected_species:
|
||||
# go back to the base node for this decision
|
||||
return "menunode_info_base"
|
||||
|
||||
char = caller.new_char
|
||||
# any special code for setting this option would go here!
|
||||
# but we'll just set an attribute
|
||||
char.db.species = list(_SPECIES_INFO_DICT.keys()).index(selected_species)
|
||||
|
||||
# move on to the next step!
|
||||
return "menunode_multi_choice"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Option Categories
|
||||
#########################################################
|
||||
|
||||
# for these subcategory options, we make a dict of categories and option lists
|
||||
_APPEARANCE_DICT = {
|
||||
# the key is your category; the value is a list of options, in the order you want them to appear
|
||||
"body type": [
|
||||
"skeletal",
|
||||
"skinny",
|
||||
"slender",
|
||||
"slim",
|
||||
"athletic",
|
||||
"muscular",
|
||||
"broad",
|
||||
"round",
|
||||
"curvy",
|
||||
"stout",
|
||||
"chubby",
|
||||
],
|
||||
"height": ["diminutive", "short", "average", "tall", "towering"],
|
||||
}
|
||||
|
||||
|
||||
def menunode_categories(caller, **kwargs):
|
||||
"""Base node for categorized options."""
|
||||
# this is a new decision step, so save your resume point here
|
||||
caller.new_char.db.chargen_step = "menunode_categories"
|
||||
|
||||
text = dedent(
|
||||
"""\
|
||||
|wOption Categories|n
|
||||
|
||||
Some character attributes are part of the same mechanic or decision,
|
||||
but need to be divided up into sub-categories. Character appearance
|
||||
details are an example of where this can be useful.
|
||||
"""
|
||||
)
|
||||
|
||||
help = "Some helpful extra information on what's affected by these choices works well here."
|
||||
options = []
|
||||
|
||||
# just like for informational categories, build the options off of a dictionary to make it
|
||||
# easier to manage
|
||||
for category in _APPEARANCE_DICT.keys():
|
||||
options.append(
|
||||
{
|
||||
"desc": f"Choose your |c{category}|n",
|
||||
"goto": ("menunode_category_options", {"category": category}),
|
||||
}
|
||||
)
|
||||
|
||||
# since this node goes in and out of sub-nodes, you need an option to proceed to the next step
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Next)", "next", "n"),
|
||||
"desc": "Continue to the next step.",
|
||||
"goto": "menunode_multi_choice",
|
||||
}
|
||||
)
|
||||
# once past the first decision, it's also a good idea to include a "back to previous step"
|
||||
# option
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Back)", "back", "b"),
|
||||
"desc": "Go back to the previous step",
|
||||
"goto": "menunode_info_base",
|
||||
}
|
||||
)
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def menunode_category_options(caller, raw_string, category=None, **kwargs):
|
||||
"""Choosing an option within the categories."""
|
||||
if not category:
|
||||
# this shouldn't have happened, so quit and retry
|
||||
return "Something went wrong. Please try again."
|
||||
|
||||
# for mechanics-related choices, you can combine this with the
|
||||
# informational options approach to give specific info
|
||||
text = f"Choose your {category}:"
|
||||
help = f"This will define your {category}."
|
||||
|
||||
options = []
|
||||
# build the list of options from the right category of your dictionary
|
||||
for option in _APPEARANCE_DICT[category]:
|
||||
options.append(
|
||||
{"desc": option, "goto": (_set_category_opt, {"category": category, "value": option})}
|
||||
)
|
||||
# always include a "back" option in case they aren't ready to pick yet
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Back)", "back", "b"),
|
||||
"desc": f"Don't change {category}",
|
||||
"goto": "menunode_categories",
|
||||
}
|
||||
)
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _set_category_opt(caller, raw_string, category, value, **kwargs):
|
||||
"""Set the option for a category"""
|
||||
|
||||
# this is where you would put any more complex code involved in setting the option,
|
||||
# but we're just doing simple attributes
|
||||
caller.new_char.attributes.add(category, value)
|
||||
|
||||
# go back to the base node for the categories choice to pick another
|
||||
return "menunode_categories"
|
||||
|
||||
|
||||
#########################################################
|
||||
# Multiple Choice
|
||||
#########################################################
|
||||
|
||||
# it's not as important to make this a separate list, but like all the others,
|
||||
# it's easier to read and to update if you do!
|
||||
_SKILL_OPTIONS = [
|
||||
"alchemy",
|
||||
"archery",
|
||||
"blacksmithing",
|
||||
"brawling",
|
||||
"dancing",
|
||||
"fencing",
|
||||
"pottery",
|
||||
"tailoring",
|
||||
"weaving",
|
||||
]
|
||||
|
||||
|
||||
def menunode_multi_choice(caller, raw_string, **kwargs):
|
||||
"""A multiple-choice menu node."""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_multi_choice"
|
||||
|
||||
# in order to support picking up from where we left off, get the options from the character
|
||||
# if they weren't passed in
|
||||
# this is again just a simple attribute, but you could retrieve this list however
|
||||
selected = kwargs.get("selected") or char.attributes.get("skill_list", [])
|
||||
|
||||
text = dedent(
|
||||
"""\
|
||||
|wMultiple Choice|n
|
||||
|
||||
Sometimes you want players to be able to pick more than one option -
|
||||
for example, starting skills.
|
||||
|
||||
You can easily define it as a minimum, maximum, or exact number of
|
||||
selected options.
|
||||
"""
|
||||
)
|
||||
|
||||
help = (
|
||||
"This is a good place to specify how many choices are allowed or required. This example"
|
||||
" requires exactly 3."
|
||||
)
|
||||
|
||||
options = []
|
||||
for option in _SKILL_OPTIONS:
|
||||
# check if the option has been selected
|
||||
if option in selected:
|
||||
# if it's been selected, we want to highlight that
|
||||
opt_desc = f"|y{option} (selected)|n"
|
||||
else:
|
||||
opt_desc = option
|
||||
options.append(
|
||||
{"desc": opt_desc, "goto": (_set_multichoice, {"selected": selected, "option": option})}
|
||||
)
|
||||
|
||||
# only display the Next option if the requirements are met!
|
||||
# for this example, you need exactly 3 choices, but you can use an inequality
|
||||
# for "no more than X", or "at least X"
|
||||
if len(selected) == 3:
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Next)", "next", "n"),
|
||||
"desc": "Continue to the next step",
|
||||
"goto": "menunode_choose_objects",
|
||||
}
|
||||
)
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Back)", "back", "b"),
|
||||
"desc": "Go back to the previous step",
|
||||
"goto": "menunode_categories",
|
||||
}
|
||||
)
|
||||
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _set_multichoice(caller, raw_string, selected=[], **kwargs):
|
||||
"""saves the current choices to the character"""
|
||||
# get the option being chosen
|
||||
if option := kwargs.get("option"):
|
||||
# if the option is already in the list, then we want to remove it
|
||||
if option in selected:
|
||||
selected.remove(option)
|
||||
# otherwise, we're adding it
|
||||
else:
|
||||
selected.append(option)
|
||||
|
||||
# now that the options are updated, save it to the character
|
||||
# this is just setting an attribute but it could be anything
|
||||
caller.new_char.attributes.add("skill_list", selected)
|
||||
|
||||
# pass the list back so we don't need to retrieve it again
|
||||
return ("menunode_multi_choice", {"selected": selected})
|
||||
|
||||
|
||||
#########################################################
|
||||
# Starting Objects
|
||||
#########################################################
|
||||
|
||||
# for a real game, you would most likely want to build this list from
|
||||
# your existing game prototypes module(s), but for a demo this'll do!
|
||||
_EXAMPLE_PROTOTYPES = [
|
||||
# starter sword prototype
|
||||
{
|
||||
"key": "basic sword",
|
||||
"desc": "This sword will do fine for stabbing things.",
|
||||
"tags": [("sword", "weapon")],
|
||||
},
|
||||
# starter staff prototype
|
||||
{
|
||||
"key": "basic staff",
|
||||
"desc": "You could hit things with it, or maybe use it as a spell focus.",
|
||||
"tags": [("staff", "weapon"), ("staff", "focus")],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
# this method will be run to create the starting objects
|
||||
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)
|
||||
|
||||
|
||||
def menunode_choose_objects(caller, raw_string, **kwargs):
|
||||
"""Selecting objects to start with"""
|
||||
char = caller.new_char
|
||||
|
||||
# another decision, so save the resume point
|
||||
char.db.chargen_step = "menunode_choose_objects"
|
||||
|
||||
text = dedent(
|
||||
"""\
|
||||
|wStarting Objects|n
|
||||
|
||||
Whether it's a cosmetic outfit, a starting weapon, or a professional
|
||||
tool kit, you probably want to let your players have a choice in
|
||||
what objects they start out with.
|
||||
"""
|
||||
)
|
||||
|
||||
help = (
|
||||
"An overview of what the choice affects - whether it's purely aesthetic or mechanical, and"
|
||||
" whether you can change it later - are good here."
|
||||
)
|
||||
|
||||
options = []
|
||||
|
||||
for proto in _EXAMPLE_PROTOTYPES:
|
||||
# use the key as the option description, but pass the whole prototype
|
||||
options.append(
|
||||
{
|
||||
"desc": f"Choose {_INFLECT.an(proto['key'])}",
|
||||
"goto": (_set_object_choice, {"proto": proto}),
|
||||
}
|
||||
)
|
||||
|
||||
options.append(
|
||||
{
|
||||
"key": ("(Back)", "back", "b"),
|
||||
"desc": "Go back to the previous step",
|
||||
"goto": "menunode_multi_choice",
|
||||
}
|
||||
)
|
||||
|
||||
return (text, help), options
|
||||
|
||||
|
||||
def _set_object_choice(caller, raw_string, proto, **kwargs):
|
||||
"""Save the selected starting object(s)"""
|
||||
|
||||
# we DON'T want to actually create the object, yet! that way players can still go back and
|
||||
# change their mind instead, we save what object was chosen - in this case, by saving the
|
||||
# prototype dict to the character
|
||||
caller.new_char.db.starter_weapon = proto
|
||||
|
||||
# continue to the next step
|
||||
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 = dedent(
|
||||
f"""\
|
||||
|wChoosing a Name|n
|
||||
|
||||
Especially for roleplaying-centric games, being able to choose your
|
||||
character's name after deciding everything else, instead of before,
|
||||
is really useful.
|
||||
|
||||
{prompt_text}
|
||||
"""
|
||||
)
|
||||
|
||||
help = "You'll have a chance to change your mind before confirming, even if the name is free."
|
||||
# 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()
|
||||
|
||||
# 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.\n\nEnter 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
|
||||
|
||||
# since we reserved the name by assigning it, you can reference the character key
|
||||
# if you have any extra validation or normalization that changed the player's input
|
||||
# this also serves to show the player exactly what name they'll get
|
||||
text = f"|w{char.key}|n is available! Confirm?"
|
||||
# let players change their mind and go back to the name choice, if they want
|
||||
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
|
||||
# since everything is finished and confirmed, we actually create the starting objects now
|
||||
create_objects(char)
|
||||
|
||||
# clear in-progress status
|
||||
caller.new_char.attributes.remove("chargen_step")
|
||||
text = dedent(
|
||||
"""
|
||||
Congratulations!
|
||||
|
||||
You have completed character creation. Enjoy the game!
|
||||
"""
|
||||
)
|
||||
return text, None
|
|
@ -1,9 +1,19 @@
|
|||
from commands.encounter_cmdset import SetSpeciesHuman
|
||||
from commands.encounter_cmdset import SetSpeciesCatkin
|
||||
from commands.encounter_cmdset import SetSpeciesDogkin
|
||||
|
||||
from commands.custom_cmdset import SetSpeciesHuman
|
||||
from commands.custom_cmdset import SetSpeciesCatkin
|
||||
from commands.custom_cmdset import SetSpeciesDogkin
|
||||
|
||||
SPECIES_LIST = [
|
||||
"Human",
|
||||
"Catkin",
|
||||
"Dogkin"
|
||||
]
|
||||
|
||||
SPECIES_CMDSET = [
|
||||
SetSpeciesHuman,
|
||||
SetSpeciesCatkin,
|
||||
SetSpeciesDogkin,
|
||||
]
|
||||
|
|
Loading…
Reference in New Issue