From 0095f708359ddfb45204dc9015518b76f4654d71 Mon Sep 17 00:00:00 2001 From: Griatch Date: Wed, 14 Sep 2022 18:40:21 +0200 Subject: [PATCH] Working on docs --- docs/source/Components/EvMenu.md | 2 +- .../Part3/Beginner-Tutorial-Chargen.md | 71 +++++++ .../Howtos/Tutorial-Persistent-Handler.md | 27 ++- .../contrib/tutorials/evadventure/chargen.py | 181 +++++++++++++++++- 4 files changed, 264 insertions(+), 17 deletions(-) diff --git a/docs/source/Components/EvMenu.md b/docs/source/Components/EvMenu.md index b283298783..5ee1612881 100644 --- a/docs/source/Components/EvMenu.md +++ b/docs/source/Components/EvMenu.md @@ -268,7 +268,7 @@ after start node as if it was entered on a fictional previous node. This can be very useful in order to start a menu differently depending on the Command's arguments in which it was initialized. - `session` (Session): Useful when calling the menu from an [Account](./Accounts.md) in - `MULTISESSION_MODDE` higher than 2, to make sure only the right Session sees the menu output. + `MULTISESSION_MODE` higher than 2, to make sure only the right Session sees the menu output. - `debug` (bool): If set, the `menudebug` command will be made available in the menu. Use it to list the current state of the menu and use `menudebug ` to inspect a specific state variable from the list. diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md index e69de29bb2..a54603218d 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Chargen.md @@ -0,0 +1,71 @@ +# Character Generation + +In previous lessons we have established how a character looks. Now we need to give the player a +chance to create one. + +In _Knave_, most of the character-generation is random. This means this tutorial can be pretty +compact while still showing the basic idea. What we will create is a menu looking like this: + +## How it will work + +We want to have chargen appear when we log in. + +``` +Silas + +STR +1 +DEX +2 +CON +1 +INT +3 +WIS +1 +CHA +2 + +You are lanky with a sunken face and filthy hair, breathy speech, and foreign clothing. +You were a herbalist, but you were pursued and ended up a knave. You are honest but also +suspicious. You are of the neutral alignment. + +Your belongings: +Brigandine armor, ration, ration, sword, torch, torch, torch, torch, torch, +tinderbox, chisel, whistle + +---------------------------------------------------------------------------------------- +1. Change your name +2. Swap two of your ability scores (once) +3. Accept and create character +``` + +If you select 1, you get a new menu node: + +``` +Your current name is Silas. Enter a new name or leave empty to abort. +----------------------------------------------------------------------------------------- +``` +You can now enter a new name. When pressing return you'll get back to the first menu node +showing your character, now with the new name. + +If you select 2, you go to another menu node: + +``` +Your current abilities: + +STR +1 +DEX +2 +CON +1 +INT +3 +WIS +1 +CHA +2 + +You can swap the values of two abilities around. +You can only do this once, so choose carefully! + +To swap the values of e.g. STR and INT, write 'STR INT'. Empty to abort. +------------------------------------------------------------------------------------------ +``` +If you enter `WIS CHA` here, WIS will become `+2` and `CHA` `+1`. You will then again go back +to the main node to see your new character, but this time the option to swap will no longer be +available (you can only do it once). + +If you finally select the `Accept and create character` option, you will leave character +generation and start the game as this character. + + diff --git a/docs/source/Howtos/Tutorial-Persistent-Handler.md b/docs/source/Howtos/Tutorial-Persistent-Handler.md index 15f44cd9c3..3b5e4a93f0 100644 --- a/docs/source/Howtos/Tutorial-Persistent-Handler.md +++ b/docs/source/Howtos/Tutorial-Persistent-Handler.md @@ -1,8 +1,12 @@ # Making a Persistent object Handler -A _handler_ is a convenient way to group functionality on an object. This allows you to logically group all actions related to that thing in one place. This tutorial expemplifies how to make your own handlers and make sure data you store in them survives a reload. +A _handler_ is a convenient way to group functionality on an object. This allows you to logically +group all actions related to that thing in one place. This tutorial expemplifies how to make your +own handlers and make sure data you store in them survives a reload. -For example, when you do `obj.attributes.get("key")` or `obj.tags.add('tagname')` you are evoking handlers stored as `.attributes` and `tags` on the `obj`. On these handlers are methods (`get()` and `add()` in this example). +For example, when you do `obj.attributes.get("key")` or `obj.tags.add('tagname')` you are evoking +handlers stored as `.attributes` and `tags` on the `obj`. On these handlers are methods (`get()` +and `add()` in this example). ## Base Handler example @@ -81,8 +85,10 @@ class Quest: ``` - -We expect the dev to make subclasses of this to implement different quests. Exactly how this works doesn't matter, the key is that we want to track `self.current_step` - a property that _should survive a server reload_. But so far there is no way for `Quest` to accomplish this, it's just a normal Python class with no connection to the database. +We expect the dev to make subclasses of this to implement different quests. Exactly how this works +doesn't matter, the key is that we want to track `self.current_step` - a property that _should +survive a server reload_. But so far there is no way for `Quest` to accomplish this, it's just a +normal Python class with no connection to the database. ### Handler with save/load capability @@ -120,17 +126,24 @@ class QuestHandler: ``` -The handler is just a normal Python class and has no database-storage on its own. But it has a link to `.obj`, which is assumed to be a full typeclased entity, on which we can create persistent [Attributes](../Components/Attributes.md) to store things however we like! +The handler is just a normal Python class and has no database-storage on its own. But it has a link +to `.obj`, which is assumed to be a full typeclased entity, on which we can create +persistent [Attributes](../Components/Attributes.md) to store things however we like! We make two helper methods `_load` and -`_save` that handles local fetches and saves `storage` to an Attribute on the object. To avoid saving more than necessary, we have a property `do_save`. This we will set in `Quest` below. +`_save` that handles local fetches and saves `storage` to an Attribute on the object. To avoid +saving more than necessary, we have a property `do_save`. This we will set in `Quest` below. > Note that once we `_save` the data, we need to call `_load` again. This is to make sure the version we store on the handler is properly de-serialized. If you get an error about data being `bytes`, you probably missed this step. ### Make quests storable -The handler will save all `Quest` objects as a `dict` in an Attribute on `obj`. We are not done yet though, the `Quest` object needs access to the `obj` too - not only will this is important to figure out if the quest is complete (the `Quest` must be able to check the quester's inventory to see if they have the red key, for example), it also allows the `Quest` to tell the handler when its state changed and it should be saved. +The handler will save all `Quest` objects as a `dict` in an Attribute on `obj`. We are not done yet +though, the `Quest` object needs access to the `obj` too - not only will this is important to figure +out if the quest is complete (the `Quest` must be able to check the quester's inventory to see if +they have the red key, for example), it also allows the `Quest` to tell the handler when its state +changed and it should be saved. We change the `Quest` such: diff --git a/evennia/contrib/tutorials/evadventure/chargen.py b/evennia/contrib/tutorials/evadventure/chargen.py index 537c8bcf68..0df688ce36 100644 --- a/evennia/contrib/tutorials/evadventure/chargen.py +++ b/evennia/contrib/tutorials/evadventure/chargen.py @@ -8,20 +8,37 @@ from evennia.utils.evmenu import EvMenu from .random_tables import chargen_table from .rules import dice +_ABILITIES = { + "STR": "strength", + "DEX": "dexterity", + "CON": "constitution", + "INT": "intelligence", + "WIS": "wisdom", + "CHA": "charisma", +} + _TEMP_SHEET = """ -STR +{strength} DEX +{dexterity} CON +{constitution} INT +{intelligence} WIS +{wisdom} CHA +{charisma} +{name} + +STR +{strength} +DEX +{dexterity} +CON +{constitution} +INT +{intelligence} +WIS +{wisdom} +CHA +{charisma} {description} +Your belongings: {equipment} """ -class EvAdventureCharacterGeneration: +class EvAdventureChargenStorage: """ - This collects all the rules for generating a new character. An instance of this class can be - used to track all the stats during generation and will be used to apply all the data to the - character at the end. This class instance can also be saved on the menu to make sure a user + This collects all the rules for generating a new character. An instance of this class is used + to pass around the current character state during character generation and also applied to + the character at the end. This class instance can also be saved on the menu to make sure a user is not losing their half-created character. Note: @@ -37,6 +54,10 @@ class EvAdventureCharacterGeneration: """ + def __init__(self): + # you are only allowed to tweak abilities once + self.ability_changes = 0 + def _random_ability(self): return min(dice.roll("1d6"), dice.roll("1d6"), dice.roll("1d6")) @@ -71,10 +92,10 @@ class EvAdventureCharacterGeneration: alignment = dice.roll_random_table("1d20", chargen_table["alignment"]) self.desc = ( - f"{background.title()}. Wears {clothing} clothes, and has {speech} " - f"speech. Has a {physique} physique, a {face} face, {skin} skin and " - f"{hair} hair. Is {virtue}, but {vice}. Has been {misfortune} in " - f"the past. Favors {alignment}." + f"You are {physique} with a {face} face and {hair} hair, {speech} speech, " + f"and {clothing} clothing. " + f"You were a {background.title()}, but you were {misfortune} and ended up a knave. " + f"You are {virtue} but also {vice}. You are of the {alignment} alignment." ) # same for all @@ -113,6 +134,7 @@ class EvAdventureCharacterGeneration: ) return _TEMP_SHEET.format( + name=self.name, strength=self.strength, dexterity=self.dexterity, constitution=self.constitution, @@ -197,3 +219,144 @@ class EvAdventureCharacterGeneration: # chargen menu + + +def node_chargen(caller, raw_string, **kwargs): + """ + This node is the central point of chargen. We return here to see our current + sheet and break off to edit different parts of it. + + In Knave, not so much can be changed. + """ + tmp_character = kwargs["tmp_character"] + + text = tmp_character.show_sheet() + + options = [{"desc": "Change your name", "goto": ("node_change_name", kwargs)}] + if tmp_character.ability_changes <= 0: + options.append( + { + "desc": "Swap two of your ability scores (once)", + "goto": ("node_swap_abilities", kwargs), + } + ) + options.append( + {"desc": "Accept and create character", "goto": ("node_apply_character", kwargs)}, + ) + + return text, options + + +def _update_name(caller, raw_string, **kwargs): + """ + Used by node_change_name below to check what user entered and update the name if appropriate. + + """ + if raw_string: + tmp_character = kwargs["tmp_character"] + tmp_character.name = raw_string.lower().capitalize() + + return "node_chargen", kwargs + + +def node_change_name(caller, raw_string, **kwargs): + """ + Change the random name of the character. + + """ + tmp_character = kwargs["tmp_character"] + + text = (f"Your current name is |w{tmp_character.name}|n. " + "Enter a new name or leave empty to abort." + + options = {"key": "_default", "goto": (_update_name, kwargs)} + + return text, options + + +def _swap_abilities(caller, raw_string, **kwargs): + """ + Used by node_swap_abilities to parse the user's input and swap ability + values. + + """ + if raw_string: + abi1, *abi2 = raw_string.split(" ", 1) + if not abi2: + caller.msg("That doesn't look right.") + return None, kwargs + abi2 = abi2[0] + abi1, abi2 = abi1.upper().strip(), abi2.upper().strip() + if abi1 not in _ABILITIES or abi2 not in _ABILITIES: + caller.msg("Not a familiar set of abilites.") + return None, kwargs + + # looks okay = swap values. We need to convert STR to strength etc + tmp_character = kwargs["tmp_character"] + abi1 = _ABILITIES[abi1] + abi2 = _ABILITIES[abi2] + abival1 = getattr(tmp_character, abi1) + abival2 = getattr(tmp_character, abi2) + + setattr(tmp_character, abi1, abival2) + setattr(tmp_character, abi2, abival1) + + return "node_chargen", kwargs + + +def node_swap_abilities(caller, raw_string, **kwargs): + """ + One is allowed to swap the values of two abilities around, once. + + """ + tmp_character = kwargs["tmp_character"] + + text = f""" +Your current abilities: + +STR +{tmp_character.strength} +DEX +{tmp_character.dexterity} +CON +{tmp_character.constitution} +INT +{tmp_character.intelligence} +WIS +{tmp_character.wisdom} +CHA +{tmp_character.charisma} + +You can swap the values of two abilities around. +You can only do this once, so choose carefully! + +To swap the values of e.g. STR and INT, write |wSTR INT|n. Empty to abort. +""" + + options = {"key": "_default", "goto": (_swap_abilities, kwargs)} + + return text, options + + +def node_apply_character(caller, raw_string, **kwargs): + """ + End chargen and create the character. We will also puppet it. + + """ + tmp_character = kwargs["tmp_character"] + + tmp_character.apply(caller) + + caller.msg("Character created!") + + +def start_chargen(caller, session=None): + """ + This is a start point for spinning up the chargen from a command later. + + """ + + menutree = { + "node_chargen": node_chargen, + "node_change_name": node_change_name, + "node_swap_abilities": node_swap_abilities, + } + + # this generates all random components of the character + tmp_character = EvAdventureChargenStorage() + + EvMenu(caller, menutree, startnode="node_chargen", session=session, tmp_character=tmp_character)