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
|
# from evennia import default_cmds
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
"""
|
"""
|
||||||
Base command (you may see this if a child command had no help text defined)
|
Base command (you may see this if a child command had no help text defined)
|
||||||
|
@ -32,6 +31,36 @@ class Command(BaseCommand):
|
||||||
#
|
#
|
||||||
pass
|
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.clothing import ClothedCharacterCmdSet
|
||||||
from evennia.contrib.game_systems.containers import ContainerCmdSet
|
from evennia.contrib.game_systems.containers import ContainerCmdSet
|
||||||
|
from evennia.contrib.rpg.character_creator.character_creator import ContribCmdCharCreate
|
||||||
|
|
||||||
from evennia import default_cmds
|
from evennia import default_cmds
|
||||||
|
|
||||||
from .encounter_cmdset import CmdEngage
|
from .encounter_cmdset import CmdEngage
|
||||||
|
@ -50,14 +52,10 @@ class AccountCmdSet(default_cmds.AccountCmdSet):
|
||||||
key = "DefaultAccount"
|
key = "DefaultAccount"
|
||||||
|
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
"""
|
|
||||||
Populates the cmdset
|
|
||||||
"""
|
|
||||||
super().at_cmdset_creation()
|
super().at_cmdset_creation()
|
||||||
#
|
|
||||||
# any commands you add below will overload the default ones.
|
|
||||||
#
|
|
||||||
|
|
||||||
|
# Char creation
|
||||||
|
self.add(ContribCmdCharCreate)
|
||||||
|
|
||||||
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
class UnloggedinCmdSet(default_cmds.UnloggedinCmdSet):
|
||||||
"""
|
"""
|
||||||
|
@ -97,3 +95,4 @@ class SessionCmdSet(default_cmds.SessionCmdSet):
|
||||||
#
|
#
|
||||||
# any commands you add below will overload the default ones.
|
# 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.caller.msg(f"You kiss {self.target}{self.adverb}")
|
||||||
self.target.msg(f"{self.caller} kisses you{self.adverb}")
|
self.target.msg(f"{self.caller} kisses you{self.adverb}")
|
||||||
|
|
||||||
|
|
||||||
class SetSpeciesHuman(CmdSet):
|
class SetSpeciesHuman(CmdSet):
|
||||||
def at_cmdset_creation(self):
|
def at_cmdset_creation(self):
|
||||||
self.add(CmdBite)
|
self.add(CmdBite)
|
||||||
self.add(CmdKiss)
|
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
|
# Special encounter commands
|
||||||
|
|
||||||
class CmdPass(EncounterCommand):
|
class CmdPass(EncounterCommand):
|
||||||
|
@ -169,6 +182,8 @@ class SetEncounterSpecial(CmdSet):
|
||||||
self.add(CmdFlee)
|
self.add(CmdFlee)
|
||||||
self.add(CmdOOC)
|
self.add(CmdOOC)
|
||||||
self.add(CmdPose)
|
self.add(CmdPose)
|
||||||
|
self.add(CmdKiss)
|
||||||
|
self.add(CmdBite)
|
||||||
|
|
||||||
# Encounter-related character commands
|
# Encounter-related character commands
|
||||||
|
|
||||||
|
|
|
@ -33,9 +33,15 @@ from evennia.settings_default import *
|
||||||
|
|
||||||
# This is the name of your game. Make it catchy!
|
# This is the name of your game. Make it catchy!
|
||||||
SERVERNAME = "Multi-User Depravity"
|
SERVERNAME = "Multi-User Depravity"
|
||||||
|
|
||||||
IDLE_TIMEOUT = -1
|
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.
|
# 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.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
|
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
|
to the MUD). It does NOT have visual appearance in the game world (that
|
||||||
|
@ -102,3 +103,4 @@ class Guest(DefaultGuest):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
from evennia.contrib.game_systems.clothing import ClothedCharacter
|
from evennia.contrib.game_systems.clothing import ClothedCharacter
|
||||||
|
|
||||||
from evennia.contrib.rpg.traits import TraitHandler
|
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 commands.encounter_cmdset import SetEncounterSpecial
|
||||||
from world.species import SPECIES_CMDSET
|
from world.species import SPECIES_CMDSET
|
||||||
|
@ -11,16 +13,25 @@ class Character(ClothedCharacter):
|
||||||
def at_object_creation(self):
|
def at_object_creation(self):
|
||||||
self.db.version = 1
|
self.db.version = 1
|
||||||
self.db.species = 0
|
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):
|
def apply_encounter_cmdset(self):
|
||||||
self.cmdset.add(SetEncounterSpecial)
|
self.cmdset.add(SetEncounterSpecial)
|
||||||
self.cmdset.add(SPECIES_CMDSET[self.db.species])
|
self.cmdset.add(SPECIES_CMDSET[self.db.species])
|
||||||
|
|
||||||
def revoke_encounter_cmdset(self):
|
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)
|
self.cmdset.remove(SetEncounterSpecial)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def apply_basic_cmdset(self):
|
||||||
|
self.cmdset.add(SPECIES_CMDSET[self.db.species])
|
||||||
|
|
||||||
def at_pre_move(self, destination, **kwargs):
|
def at_pre_move(self, destination, **kwargs):
|
||||||
if self.ndb.encounter_handler:
|
if self.ndb.encounter_handler:
|
||||||
self.msg("You would need to leave the encounter first")
|
self.msg("You would need to leave the encounter first")
|
||||||
|
|
|
@ -11,9 +11,34 @@ inheritance.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from evennia.objects.objects import DefaultObject
|
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:
|
class ObjectParent:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
class Object(ObjectParent, DefaultObject):
|
class Object(ObjectParent, DefaultObject):
|
||||||
pass
|
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 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 = [
|
SPECIES_LIST = [
|
||||||
"Human",
|
"Human",
|
||||||
|
"Catkin",
|
||||||
|
"Dogkin"
|
||||||
]
|
]
|
||||||
|
|
||||||
SPECIES_CMDSET = [
|
SPECIES_CMDSET = [
|
||||||
SetSpeciesHuman,
|
SetSpeciesHuman,
|
||||||
|
SetSpeciesCatkin,
|
||||||
|
SetSpeciesDogkin,
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue