updated character definitions to use names

This commit is contained in:
Ben Cook
2018-12-22 16:57:14 -05:00
parent 943c572d15
commit b799f8b108
3 changed files with 93 additions and 61 deletions
+89 -57
View File
@@ -63,8 +63,8 @@ class Character():
class_list = []
_level = 1 # Keep internal check of total level
_hit_dice_faces = 2
race = None
background = None
_race = None
_background = None
xp = 0
# Hit points
hp_max = 10
@@ -132,24 +132,22 @@ class Character():
"""Takes a bunch of attrs and passes them to ``set_attrs``"""
self.weapons = []
# make sure class, race, background are set first
classes_levels = char_props.pop('classes_levels', [])
subclasses = char_props.pop('subclasses', [])
class_list = parse_classes(
classes_levels, subclasses,
feature_choices=char_props.get('feature_choices', []))
# accept backwards compatability for single-class characters
if len(class_list) == 0:
name = char_props.pop('character_class').lower().capitalize()
level = char_props.pop('level')
CharClass = getattr(classes, name)
class_list = [CharClass(level)]
char_props['class_list'] = class_list
class_list = attrs.pop('class_list', self.class_list)
race = attrs.pop('race', self.race)
background = attrs.pop('background', self.background)
self.set_attrs(**{'class_list': class_list,
'race': race,
'background': background})
my_classes = attrs.pop('classes', ['Char Class'])
my_levels = attrs.pop('levels', [1])
my_subclasses = attrs.pop('subclasses', [None])
# backwards compatability
if (len(my_classes) == 0) and hasattr(attrs, 'class'):
my_classes = [attrs.pop('class')]
my_levels = [attrs.pop('level', 1)]
my_subclasses = [attrs.pop('subclass', None)]
# Generate the list of class objects
self.class_list = parse_classes(
my_classes, my_levels, my_subclasses,
feature_choices=attrs.get('feature_choices', []))
# parse race and background
self.race = attrs.pop('race', None)
self.background = attrs.pop('background', None)
# parse all other attributes
self.set_attrs(**attrs)
def __str__(self):
@@ -158,6 +156,44 @@ class Character():
def __repr__(self):
return f"<{self.class_name}: {self.name}>"
@property
def race(self):
return self._race
@race.setter
def race(self, newrace):
if isinstance(newrace, race.Race):
self._race = newrace
elif isinstance(newrace, type) and issubclass(newrace, race.Race):
self._race = newrace()
elif isinstance(newrace, str):
try:
self._race = findattr(race, newrace)()
except AttributeError:
msg = (f'Race "{newrace}" not defined. '
f'Please add it to ``race.py``')
self._race = race.Race()
warnings.warn(msg)
@property
def background(self):
return self._background
@background.setter
def background(self, bg):
if isinstance(bg, background.Background):
self.background = bg
elif isinstance(bg, type) and issubclass(bg, background.Background):
self.background = bg()
elif isinstance(bg, str):
try:
self.background = findattr(background, bg)()
except AttributeError:
msg = (f'Background "{bg}" not defined. '
f'Please add it to ``background.py``')
self.background = background.Background()
warnings.warn(msg)
@property
def class_name(self):
if self.num_classes >= 1:
@@ -337,19 +373,6 @@ class Character():
elif attr == 'weapon_proficiencies':
self.other_weapon_proficiencies = tuple([findattr(weapons, w)
for w in val])
elif attr == 'class_list':
if isinstance(val, str):
val = [val]
for cls in val:
if isinstance
elif attr == 'race':
if val is not None:
MyRace = findattr(race, val)
self.race = MyRace()
elif attr == 'background':
if val is not None:
MyBackground = findattr(background, val)
self.background = MyBackground()
elif attr == 'armor':
self.wear_armor(val)
elif attr == 'shield':
@@ -558,7 +581,10 @@ class Character():
@hit_dice_faces.setter
def hit_dice_faces(self, faces):
if self.num_classes == 0:
self._hit_dice_faces = faces
else:
self.primary_class.hit_dice_faces = faces
@property
def proficiency_bonus(self):
@@ -628,8 +654,13 @@ class Character():
def load(cls, character_file):
# Create a character from the character definition
char_props = read_character_file(character_file)
classes = char_props.get('classes', [])
# backwards compatability
if (len(classes) == 0) and hasattr(char_props, 'character_class'):
char_props['classes'] = [char_props.pop('character_class').lower().capitalize()]
char_props['levels'] = [str(char_props.pop('level'))]
# Create the character with loaded properties
char = cls(**char_props)
char = Character(**char_props)
return char
def save(self, filename, template_file='character_template.txt'):
@@ -653,37 +684,38 @@ class Character():
make_sheet(filename, char=self, flatten=kwargs.get('flatten', True))
def parse_classes(classes_levels=[], subclasses=[], feature_choices=[]):
if isinstance(classes_levels, str):
classes_levels = [classes_levels]
def parse_classes(classes_list=[], levels=[], subclasses=[], feature_choices=[]):
if isinstance(classes_list, str):
classes_list = [classes_list]
if isinstance(levels, int) or isinstance(levels, float) or isinstance(levels, str):
levels = [levels]
if len(levels) == 0:
levels = [1]*len(classes_list)
if isinstance(subclasses, str):
subclasses = [subclasses]
if len(subclasses) == 0:
subclasses = [None]*len(classes_levels)
assert len(classes_levels) == len(subclasses), (
'the length of classes_levels {:d} does not match length of '
'subclasses {:d}'.format(len(classes_levels), len(subclasses)))
subclasses = [None]*len(classes_list)
assert len(classes_list) == len(levels), (
'the length of classes {:d} does not match length of '
'levels {:d}'.format(len(classes), len(levels)))
assert len(classes_list) == len(subclasses), (
'the length of classes {:d} does not match length of '
'subclasses {:d}'.format(len(classes_list), len(subclasses)))
class_list = []
for cls, sub in zip(classes_levels, subclasses):
if not isinstance(classes.CharClass, cls):
for cls, lvl, sub in zip(classes_list, levels, subclasses):
if isinstance(cls, str):
cls = cls.strip().title().replace(' ', '')
try:
c, _, lvl = cls.strip().rpartition(' ') # " wizard 3 " => "wizard", "3"
c = c.title().replace(' ', '')
except ValueError:
raise ValueError(
'classes_levels not properly formatted. Each entry should '
'be formatted \"class level\", but got {:s}'.format(cl))
try:
this_class = getattr(classes, c)
this_class = getattr(classes, cls)
this_level = int(lvl)
except AttributeError:
raise AttributeError(
'class was not recognized from classes.py: {:s}'.format(c))
except ValueError:
raise ValueError(
'level was not recognizable as an int: {:s}'.format(lvl))
'level was not recognizable as an int: {:s}'.format(str(lvl)))
params = {}
params['feature_choices'] = char_props.get('feature_choices', [])
params['feature_choices'] = feature_choices
class_list += [this_class(this_level, subclass=sub, **params)]
return class_list
@@ -734,15 +766,15 @@ def read_character_file(filename):
class Druid(Character):
def __init__(self, level=1, **attrs):
MyDruid = classes.Druid(level=level)
self.class_list = [MyDruid]
attrs['classes'] = ['Druid']
attrs['levels'] = [level]
super().__init__(**attrs)
class Wizard(Character):
def __init__(self, level=1, **attrs):
self.class_list = [classes.Wizard(level=level)]
attrs['classes'] = ['Wizard']
attrs['levels'] = [level]
super().__init__(**attrs)
+2 -2
View File
@@ -6,9 +6,9 @@ class CharClass():
"""
A generic Character Class (not to be confused with builtin class)
"""
name = ""
name = "Default"
level = 1
hit_dice_faces = None
hit_dice_faces = 2
weapon_proficiencies = ()
_proficiencies_text = ()
multiclass_weapon_proficiencies = ()
+1 -1
View File
@@ -4,7 +4,7 @@ from unittest import TestCase
import warnings
from dungeonsheets import race, monsters, exceptions, spells
from dungeonsheets.character import Character
from dungeonsheets.character import Character, Wizard, Druid
from dungeonsheets.weapons import Weapon, Shortsword
from dungeonsheets.armor import Armor, LeatherArmor, Shield