From 19278a5e64a4535f1e4991a70d5c9e2b54bbcfd2 Mon Sep 17 00:00:00 2001 From: Griatch Date: Fri, 2 Sep 2022 08:10:39 +0200 Subject: [PATCH] More refactoring --- .../Part3/Beginner-Tutorial-Characters.md | 5 +- .../tutorials/evadventure/characters.py | 142 +++++++- .../contrib/tutorials/evadventure/chargen.py | 180 ++++++++++ .../contrib/tutorials/evadventure/rules.py | 320 ------------------ 4 files changed, 323 insertions(+), 324 deletions(-) create mode 100644 evennia/contrib/tutorials/evadventure/chargen.py diff --git a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md index eb8b2f9baf..a1f16ff86f 100644 --- a/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md +++ b/docs/source/Howtos/Beginner-Tutorial/Part3/Beginner-Tutorial-Characters.md @@ -382,7 +382,10 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter): ``` We use `charclass` rather than `class` here, because `class` is a reserved Python keyword. Naming -`race` as `charrace` thus matches in style. +`race` as `charrace` thus matches in style. + +We'd then need to expand our [rules module](Beginner-Tutorial-Rules.md) (and later +[character generation](Beginner-Tutorial-Chargen.md) to check and include what these classes mean. ## Summary diff --git a/evennia/contrib/tutorials/evadventure/characters.py b/evennia/contrib/tutorials/evadventure/characters.py index 77c6b0370c..0f2a8559d3 100644 --- a/evennia/contrib/tutorials/evadventure/characters.py +++ b/evennia/contrib/tutorials/evadventure/characters.py @@ -5,9 +5,11 @@ Character class. from evennia.objects.objects import DefaultCharacter from evennia.typeclasses.attributes import AttributeProperty -from evennia.utils.evmenu import ask_yes_no +from evennia.utils.evform import EvForm +from evennia.utils.evmenu import EvMenu, ask_yes_no +from evennia.utils.evtable import EvTable from evennia.utils.logger import log_trace -from evennia.utils.utils import lazy_property +from evennia.utils.utils import inherits_from, lazy_property from . import rules from .equipment import EquipmentError, EquipmentHandler @@ -165,9 +167,11 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter): hp = AttributeProperty(default=4) hp_max = AttributeProperty(default=4) level = AttributeProperty(default=1) - xp = AttributeProperty(default=0) coins = AttributeProperty(default=0) # copper coins + xp = AttributeProperty(default=0) + xp_per_level = 1000 + @lazy_property def equipment(self): """Allows to access equipment like char.equipment.worn""" @@ -275,3 +279,135 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter): """ pass + + def add_xp(self, xp): + """ + Add new XP. + + Args: + xp (int): The amount of gained XP. + + Returns: + bool: If a new level was reached or not. + + Notes: + level 1 -> 2 = 1000 XP + level 2 -> 3 = 2000 XP etc + + """ + self.xp += xp + next_level_xp = self.level * self.xp_per_level + return self.xp >= next_level_xp + + def level_up(self, *abilities): + """ + Perform the level-up action. + + Args: + *abilities (str): A set of abilities (like 'strength', 'dexterity' (normally 3) + to upgrade by 1. Max is usually +10. + Notes: + We block increases above a certain value, but we don't raise an error here, that + will need to be done earlier, when the user selects the ability to increase. + + """ + + self.level += 1 + for ability in set(abilities[:3]): + # limit to max amount allowed, each one unique + try: + # set at most to the max bonus + current_bonus = getattr(self, ability) + setattr( + self, + ability, + min(10, current_bonus + 1), + ) + except AttributeError: + pass + + # update hp + self.hp_max = max(self.max_hp + 1, rules.dice.roll(f"{self.level}d8")) + + +# character sheet visualization + + +_SHEET = """ + +----------------------------------------------------------------------------+ + | Name: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + +----------------------------------------------------------------------------+ + | STR: x2xxxxx DEX: x3xxxxx CON: x4xxxxx WIS: x5xxxxx CHA: x6xxxxx | + +----------------------------------------------------------------------------+ + | HP: x7xxxxx XP: x8xxxxx Level: x9x | + +----------------------------------------------------------------------------+ + | Desc: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | + +----------------------------------------------------------------------------+ + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccc1ccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | + +----------------------------------------------------------------------------+ + """ + + +def get_character_sheet(data): + """ + Generate a character sheet. This is grouped in a class in order to make + it easier to override the look of the sheet. + + Args: + data (EvAdventureCharacter or EvAdventureCharacterGeneration): This contains + the data to put in the sheet, either as stored on a finished character + or on the temporary chargen storage object. + + """ + + @staticmethod + def get(data): + """ + Generate a character sheet from the character's stats. + + data + + """ + if inherits_from(data, DefaultCharacter): + # a character, get info normally + equipment = [item.key for item in character.equipment.all()] + else: + equipment + + # divide into chunks of max 10 length (to go into two columns) + equipment_table = EvTable( + table=[equipment[i : i + 10] for i in range(0, len(equipment), 10)] + ) + form = EvForm({"FORMCHAR": "x", "TABLECHAR": "c", "SHEET": _SHEET}) + form.map( + cells={ + 1: character.key, + 2: f"+{data.strength}({data.strength + 10})", + 3: f"+{data.dexterity}({data.dexterity + 10})", + 4: f"+{data.constitution}({data.constitution + 10})", + 5: f"+{data.wisdom}({data.wisdom + 10})", + 6: f"+{data.charisma}({data.charisma + 10})", + 7: f"{data.hp}/{data.hp_max}", + 8: data.xp, + 9: data.level, + "A": data.db.desc, + }, + tables={ + 1: equipment_table, + }, + ) + return str(form) diff --git a/evennia/contrib/tutorials/evadventure/chargen.py b/evennia/contrib/tutorials/evadventure/chargen.py new file mode 100644 index 0000000000..d3fc1c041d --- /dev/null +++ b/evennia/contrib/tutorials/evadventure/chargen.py @@ -0,0 +1,180 @@ +""" +EvAdventure character generation + +""" +from evennia.prototypes.spawner import spawn +from evennia.utils.evmenu import EvMenu + +from .random_tables import chargen_table +from .rules import dice + + +class EvAdventureCharacterGeneration: + """ + 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 + is not losing their half-created character. + + Note: + In standard Knave, the character's attribute bonus is rolled randomly and will give a + value 1-6; and there is no guarantee for 'equal' starting characters. + + Knave uses a d8 roll to get the initial hit points. We will follow the recommendation + from the rule that we will use a minimum of 5 HP. + + We *will* roll random start equipment though. Contrary to standard Knave, we'll also + randomly assign the starting weapon among a small selection of equal-dmg weapons (since + there is no GM to adjudicate a different choice). + + """ + + def random_ability(self): + """ """ + return min(dice.roll("1d6"), dice.roll("1d6"), dice.roll("1d6")) + + def generate(self): + """ + Generate random values for character. + + """ + + # name will likely be modified later + self.name = dice.roll_random_table("1d282", chargen_table["name"]) + + # base attribute values + self.strength = self.random_ability() + self.dexterity = self.random_ability() + self.constitution = self.random_ability() + self.intelligence = self.random_ability() + self.wisdom = self.random_ability() + self.charisma = self.random_ability() + + # physical attributes (only for rp purposes) + self.physique = dice.roll_random_table("1d20", chargen_table["physique"]) + self.face = dice.roll_random_table("1d20", chargen_table["face"]) + self.skin = dice.roll_random_table("1d20", chargen_table["skin"]) + self.hair = dice.roll_random_table("1d20", chargen_table["hair"]) + self.clothing = dice.roll_random_table("1d20", chargen_table["clothing"]) + self.speech = dice.roll_random_table("1d20", chargen_table["speech"]) + self.virtue = dice.roll_random_table("1d20", chargen_table["virtue"]) + self.vice = dice.roll_random_table("1d20", chargen_table["vice"]) + self.background = dice.roll_random_table("1d20", chargen_table["background"]) + self.misfortune = dice.roll_random_table("1d20", chargen_table["misfortune"]) + self.alignment = dice.roll_random_table("1d20", chargen_table["alignment"]) + + # same for all + self.exploration_speed = 120 + self.combat_speed = 40 + self.hp_max = max(5, dice.roll("1d8")) + self.hp = self.hp_max + self.xp = 0 + self.level = 1 + + # random equipment + self.armor = dice.roll_random_table("1d20", chargen_table["armor"]) + + _helmet_and_shield = dice.roll_random_table("1d20", chargen_table["helmets and shields"]) + self.helmet = "helmet" if "helmet" in _helmet_and_shield else "none" + self.shield = "shield" if "shield" in _helmet_and_shield else "none" + + self.weapon = dice.roll_random_table("1d20", chargen_table["starting weapon"]) + + self.backpack = [ + "ration", + "ration", + dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_table["dungeoning gear"]), + dice.roll_random_table("1d20", chargen_table["general gear 1"]), + dice.roll_random_table("1d20", chargen_table["general gear 2"]), + ] + + def build_desc(self): + """ + Generate a backstory / description paragraph from random elements. + + """ + return ( + f"{self.background.title()}. Wears {self.clothing} clothes, and has {self.speech} " + f"speech. Has a {self.physique} physique, a {self.face} face, {self.skin} skin and " + f"{self.hair} hair. Is {self.virtue}, but {self.vice}. Has been {self.misfortune} in " + f"the past. Favors {self.alignment}." + ) + + def show_sheet(self): + return get_character_sheet(self) + + def adjust_attribute(self, source_attribute, target_attribute, value): + """ + Redistribute bonus from one attribute to another. The resulting values + must not be lower than +1 and not above +6. + + Args: + source_attribute (enum.Ability): The name of the attribute to deduct bonus from, + like 'strength' + target_attribute (str): The attribute to give the bonus to, like 'dexterity'. + value (int): How much to change. This is always 1 for the current chargen. + + Raises: + ValueError: On input error, using invalid values etc. + + Notes: + We assume the strings are provided by the chargen, so we don't do + much input validation here, we do make sure we don't overcharge ourselves though. + + """ + if source_attribute == target_attribute: + return + + # we use getattr() to fetch the Ability of e.g. the .strength property etc + source_current = getattr(self, source_attribute.value, 1) + target_current = getattr(self, target_attribute.value, 1) + + if source_current - value < 1: + raise ValueError(f"You can't reduce the {source_attribute} bonus below +1.") + if target_current + value > 6: + raise ValueError(f"You can't increase the {target_attribute} bonus above +6.") + + # all is good, apply the change. + setattr(self, source_attribute.value, source_current - value) + setattr(self, target_attribute.value, target_current + value) + + def apply(self, character): + """ + Once the chargen is complete, call this to transfer all the data to the character + permanently. + + """ + character.key = self.name + character.strength = self.strength + character.dexterity = self.dexterity + character.constitution = self.constitution + character.intelligence = self.intelligence + character.wisdom = self.wisdom + character.charisma = self.charisma + + character.hp = self.hp + character.level = self.level + character.xp = self.xp + + character.db.desc = self.build_desc() + + if self.weapon: + weapon = spawn(self.weapon) + character.equipment.move(weapon) + if self.shield: + shield = spawn(self.shield) + character.equipment.move(shield) + if self.armor: + armor = spawn(self.armor) + character.equipment.move(armor) + if self.helmet: + helmet = spawn(self.helmet) + character.equipment.move(helmet) + + for item in self.backpack: + item = spawn(item) + character.equipment.store(item) + + +# chargen menu diff --git a/evennia/contrib/tutorials/evadventure/rules.py b/evennia/contrib/tutorials/evadventure/rules.py index a719e33386..a198e4796d 100644 --- a/evennia/contrib/tutorials/evadventure/rules.py +++ b/evennia/contrib/tutorials/evadventure/rules.py @@ -344,327 +344,7 @@ class EvAdventureRollEngine: ) -# character generation - - -class EvAdventureCharacterGeneration: - """ - 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 - is not losing their half-created character. - - Note: - Unlike standard Knave, characters will come out more similar here. This is because in - a table top game it's fun to roll randomly and have to live with a crappy roll - but - online players can (and usually will) just disconnect and reroll until they get values - they are happy with. - - In standard Knave, the character's attribute bonus is rolled randomly and will give a - value 1-6; and there is no guarantee for 'equal' starting characters. Instead we - homogenize the results to a flat +2 bonus and let people redistribute the - points afterwards. This also allows us to show off some more advanced concepts in the - chargen menu. - - In the same way, Knave uses a d8 roll to get the initial hit points. Instead we use a - flat max of 8 HP to start, in order to give players a little more survivability. - - We *will* roll random start equipment though. Contrary to standard Knave, we'll also - randomly assign the starting weapon among a small selection of equal-dmg weapons (since - there is no GM to adjudicate a different choice). - - """ - - def __init__(self): - """ - Initialize starting values - - """ - # for clarity we initialize the engine here rather than use the - # global singleton at the end of the module - roll_engine = EvAdventureRollEngine() - - # name will likely be modified later - self.name = roll_engine.roll_random_table("1d282", chargen_table["name"]) - - # base attribute bonuses (flat +1 bonus) - self.strength = 2 - self.dexterity = 2 - self.constitution = 2 - self.intelligence = 2 - self.wisdom = 2 - self.charisma = 2 - - # physical attributes (only for rp purposes) - self.physique = roll_engine.roll_random_table("1d20", chargen_table["physique"]) - self.face = roll_engine.roll_random_table("1d20", chargen_table["face"]) - self.skin = roll_engine.roll_random_table("1d20", chargen_table["skin"]) - self.hair = roll_engine.roll_random_table("1d20", chargen_table["hair"]) - self.clothing = roll_engine.roll_random_table("1d20", chargen_table["clothing"]) - self.speech = roll_engine.roll_random_table("1d20", chargen_table["speech"]) - self.virtue = roll_engine.roll_random_table("1d20", chargen_table["virtue"]) - self.vice = roll_engine.roll_random_table("1d20", chargen_table["vice"]) - self.background = roll_engine.roll_random_table("1d20", chargen_table["background"]) - self.misfortune = roll_engine.roll_random_table("1d20", chargen_table["misfortune"]) - self.alignment = roll_engine.roll_random_table("1d20", chargen_table["alignment"]) - - # same for all - self.exploration_speed = 120 - self.combat_speed = 40 - self.hp_max = 8 - self.hp = self.hp_max - self.xp = 0 - self.level = 1 - - # random equipment - self.armor = roll_engine.roll_random_table("1d20", chargen_table["armor"]) - - _helmet_and_shield = roll_engine.roll_random_table( - "1d20", chargen_table["helmets and shields"] - ) - self.helmet = "helmet" if "helmet" in _helmet_and_shield else "none" - self.shield = "shield" if "shield" in _helmet_and_shield else "none" - - self.weapon = roll_engine.roll_random_table("1d20", chargen_table["starting weapon"]) - - self.backpack = [ - "ration", - "ration", - roll_engine.roll_random_table("1d20", chargen_table["dungeoning gear"]), - roll_engine.roll_random_table("1d20", chargen_table["dungeoning gear"]), - roll_engine.roll_random_table("1d20", chargen_table["general gear 1"]), - roll_engine.roll_random_table("1d20", chargen_table["general gear 2"]), - ] - - def build_desc(self): - """ - Generate a backstory / description paragraph from random elements. - - """ - return ( - f"{self.background.title()}. Wears {self.clothing} clothes, and has {self.speech} " - f"speech. Has a {self.physique} physique, a {self.face} face, {self.skin} skin and " - f"{self.hair} hair. Is {self.virtue}, but {self.vice}. Has been {self.misfortune} in " - f"the past. Favors {self.alignment}." - ) - - def adjust_attribute(self, source_attribute, target_attribute, value): - """ - Redistribute bonus from one attribute to another. The resulting values - must not be lower than +1 and not above +6. - - Args: - source_attribute (enum.Ability): The name of the attribute to deduct bonus from, - like 'strength' - target_attribute (str): The attribute to give the bonus to, like 'dexterity'. - value (int): How much to change. This is always 1 for the current chargen. - - Raises: - ValueError: On input error, using invalid values etc. - - Notes: - We assume the strings are provided by the chargen, so we don't do - much input validation here, we do make sure we don't overcharge ourselves though. - - """ - if source_attribute == target_attribute: - return - - # we use getattr() to fetch the Ability of e.g. the .strength property etc - source_current = getattr(self, source_attribute.value, 1) - target_current = getattr(self, target_attribute.value, 1) - - if source_current - value < 1: - raise ValueError(f"You can't reduce the {source_attribute} bonus below +1.") - if target_current + value > 6: - raise ValueError(f"You can't increase the {target_attribute} bonus above +6.") - - # all is good, apply the change. - setattr(self, source_attribute.value, source_current - value) - setattr(self, target_attribute.value, target_current + value) - - def apply(self, character): - """ - Once the chargen is complete, call this to transfer all the data to the character - permanently. - - """ - character.key = self.name - character.strength = self.strength - character.dexterity = self.dexterity - character.constitution = self.constitution - character.intelligence = self.intelligence - character.wisdom = self.wisdom - character.charisma = self.charisma - - character.weapon = self.weapon - character.armor = self.armor - - character.hp = self.hp - character.level = self.level - character.xp = self.xp - - character.db.desc = self.build_desc() - - # TODO - spawn the actual equipment objects before adding them to equipment! - - if self.weapon: - character.equipment.use(self.weapon) - if self.shield: - character.equipment.use(self.shield) - if self.armor: - character.equipment.use(self.armor) - if self.helmet: - character.equipment.use(self.helmet) - - for item in self.backpack: - # TODO create here - character.equipment.store(item) - - -# character improvement - - -class EvAdventureImprovement: - """ - Handle XP gains and level upgrades. Grouped in a class in order to - make it easier to override the mechanism. - - """ - - xp_per_level = 1000 - amount_of_abilities_to_upgrade = 3 - max_ability_bonus = 10 # bonus +10, defense 20 - - @staticmethod - def add_xp(character, xp): - """ - Add new XP. - - Args: - character (Character): The character to improve. - xp (int): The amount of gained XP. - - Returns: - bool: If a new level was reached or not. - - Notes: - level 1 -> 2 = 1000 XP - level 2 -> 3 = 2000 XP etc - - """ - character.xp += xp - next_level_xp = character.level * EvAdventureImprovement.xp_per_level - return character.xp >= next_level_xp - - @staticmethod - def level_up(character, *abilities): - """ - Perform the level-up action. - - Args: - character (Character): The entity to level-up. - *abilities (str): A set of abilities (like 'strength', 'dexterity' (normally 3) - to upgrade by 1. Max is usually +10. - Notes: - We block increases above a certain value, but we don't raise an error here, that - will need to be done earlier, when the user selects the ability to increase. - - """ - roll_engine = EvAdventureRollEngine() - - character.level += 1 - for ability in set(abilities[: EvAdventureImprovement.amount_of_abilities_to_upgrades]): - # limit to max amount allowed, each one unique - try: - # set at most to the max bonus - current_bonus = getattr(character, ability) - setattr( - character, - ability, - min(EvAdventureImprovement.max_ability_bonus, current_bonus + 1), - ) - except AttributeError: - pass - - character.hp_max = max(character.max_hp + 1, roll_engine.roll(f"{character.level}d8")) - - -# character sheet visualization - - -_SHEET = """ - +----------------------------------------------------------------------------+ - | Name: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx1xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | - +----------------------------------------------------------------------------+ - | STR: x2xxxxx DEX: x3xxxxx CON: x4xxxxx WIS: x5xxxxx CHA: x6xxxxx | - +----------------------------------------------------------------------------+ - | HP: x7xxxxx XP: x8xxxxx Exploration speed: x9x Combat speed: xAx | - +----------------------------------------------------------------------------+ - | Desc: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | - | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | - | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx | - +----------------------------------------------------------------------------+ - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccc1ccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - | cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc | - +----------------------------------------------------------------------------+ - """ - - -def get_character_sheet(character): - """ - Generate a character sheet. This is grouped in a class in order to make - it easier to override the look of the sheet. - - """ - - @staticmethod - def get(character): - """ - Generate a character sheet from the character's stats. - - """ - equipment = character.equipment.wielded + character.equipment.worn + character.carried - # divide into chunks of max 10 length (to go into two columns) - equipment_table = EvTable( - table=[equipment[i : i + 10] for i in range(0, len(equipment), 10)] - ) - form = EvForm({"FORMCHAR": "x", "TABLECHAR": "c", "SHEET": _SHEET}) - form.map( - cells={ - 1: character.key, - 2: f"+{character.strength}({character.strength + 10})", - 3: f"+{character.dexterity}({character.dexterity + 10})", - 4: f"+{character.constitution}({character.constitution + 10})", - 5: f"+{character.wisdom}({character.wisdom + 10})", - 6: f"+{character.charisma}({character.charisma + 10})", - 7: f"{character.hp}/{character.hp_max}", - 8: character.xp, - 9: character.exploration_speed, - "A": character.combat_speed, - "B": character.db.desc, - }, - tables={ - 1: equipment_table, - }, - ) - return str(form) - - # singletons # access rolls e.g. with rules.dice.opposed_saving_throw(...) dice = EvAdventureRollEngine() -# access improvement e.g. with rules.improvement.add_xp(character, xp) -improvement = EvAdventureImprovement()