More refactoring
This commit is contained in:
parent
805fbd5edb
commit
19278a5e64
4 changed files with 323 additions and 324 deletions
|
|
@ -382,7 +382,10 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
|
||||||
|
|
||||||
```
|
```
|
||||||
We use `charclass` rather than `class` here, because `class` is a reserved Python keyword. Naming
|
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
|
## Summary
|
||||||
|
|
|
||||||
|
|
@ -5,9 +5,11 @@ Character class.
|
||||||
|
|
||||||
from evennia.objects.objects import DefaultCharacter
|
from evennia.objects.objects import DefaultCharacter
|
||||||
from evennia.typeclasses.attributes import AttributeProperty
|
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.logger import log_trace
|
||||||
from evennia.utils.utils import lazy_property
|
from evennia.utils.utils import inherits_from, lazy_property
|
||||||
|
|
||||||
from . import rules
|
from . import rules
|
||||||
from .equipment import EquipmentError, EquipmentHandler
|
from .equipment import EquipmentError, EquipmentHandler
|
||||||
|
|
@ -165,9 +167,11 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
|
||||||
hp = AttributeProperty(default=4)
|
hp = AttributeProperty(default=4)
|
||||||
hp_max = AttributeProperty(default=4)
|
hp_max = AttributeProperty(default=4)
|
||||||
level = AttributeProperty(default=1)
|
level = AttributeProperty(default=1)
|
||||||
xp = AttributeProperty(default=0)
|
|
||||||
coins = AttributeProperty(default=0) # copper coins
|
coins = AttributeProperty(default=0) # copper coins
|
||||||
|
|
||||||
|
xp = AttributeProperty(default=0)
|
||||||
|
xp_per_level = 1000
|
||||||
|
|
||||||
@lazy_property
|
@lazy_property
|
||||||
def equipment(self):
|
def equipment(self):
|
||||||
"""Allows to access equipment like char.equipment.worn"""
|
"""Allows to access equipment like char.equipment.worn"""
|
||||||
|
|
@ -275,3 +279,135 @@ class EvAdventureCharacter(LivingMixin, DefaultCharacter):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
pass
|
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)
|
||||||
|
|
|
||||||
180
evennia/contrib/tutorials/evadventure/chargen.py
Normal file
180
evennia/contrib/tutorials/evadventure/chargen.py
Normal file
|
|
@ -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
|
||||||
|
|
@ -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
|
# singletons
|
||||||
|
|
||||||
# access rolls e.g. with rules.dice.opposed_saving_throw(...)
|
# access rolls e.g. with rules.dice.opposed_saving_throw(...)
|
||||||
dice = EvAdventureRollEngine()
|
dice = EvAdventureRollEngine()
|
||||||
# access improvement e.g. with rules.improvement.add_xp(character, xp)
|
|
||||||
improvement = EvAdventureImprovement()
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue