mirror of
https://github.com/evennia/evennia.git
synced 2026-03-30 12:37:16 +02:00
More refactoring
This commit is contained in:
parent
ba13e3e44f
commit
1f37f90bea
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
|
||||
`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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
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
|
||||
|
||||
# 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()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue