updated so all tests pass except distinct changes to desired implementation

This commit is contained in:
Ben Cook
2018-12-22 15:17:58 -05:00
parent 8ff94d0f13
commit 40b070d782
5 changed files with 259 additions and 164 deletions
+119 -42
View File
@@ -62,6 +62,8 @@ class Character():
alignment = "Neutral" alignment = "Neutral"
dungeonsheets_version = __version__ dungeonsheets_version = __version__
class_list = [] class_list = []
_level = 1 # Keep internal check of total level
_hit_dice_faces = 2
race = None race = None
background = None background = None
xp = 0 xp = 0
@@ -74,6 +76,7 @@ class Character():
intelligence = Ability() intelligence = Ability()
wisdom = Ability() wisdom = Ability()
charisma = Ability() charisma = Ability()
_saving_throw_proficiencies = []
other_weapon_proficiencies = tuple() other_weapon_proficiencies = tuple()
skill_proficiencies = tuple() skill_proficiencies = tuple()
skill_expertise = tuple() skill_expertise = tuple()
@@ -82,24 +85,24 @@ class Character():
proficiencies_extra = tuple() proficiencies_extra = tuple()
languages = "" languages = ""
# Skills # Skills
acrobatics = Skill(ability='dexterity', name='acrobatics') acrobatics = Skill(ability='dexterity')
animal_handling = Skill(ability='wisdom', name='animal handling') animal_handling = Skill(ability='wisdom')
arcana = Skill(ability='intelligence', name='arcana') arcana = Skill(ability='intelligence')
athletics = Skill(ability='strength', name='athletics') athletics = Skill(ability='strength')
deception = Skill(ability='charisma', name='deception') deception = Skill(ability='charisma')
history = Skill(ability='intelligence', name='history') history = Skill(ability='intelligence')
insight = Skill(ability='wisdom', name='insight') insight = Skill(ability='wisdom')
intimidation = Skill(ability='charisma', name='intimidation') intimidation = Skill(ability='charisma')
investigation = Skill(ability='intelligence', name='investigation') investigation = Skill(ability='intelligence')
medicine = Skill(ability='wisdom', name='medicine') medicine = Skill(ability='wisdom')
nature = Skill(ability='intelligence', name='nature') nature = Skill(ability='intelligence')
perception = Skill(ability='wisdom', name='perception') perception = Skill(ability='wisdom')
performance = Skill(ability='charisma', name='performance') performance = Skill(ability='charisma')
persuasion = Skill(ability='charisma', name='persuasion') persuasion = Skill(ability='charisma')
religion = Skill(ability='intelligence', name='religion') religion = Skill(ability='intelligence')
sleight_of_hand = Skill(ability='dexterity', name='sleight of hand') sleight_of_hand = Skill(ability='dexterity')
stealth = Skill(ability='dexterity', name='stealth') stealth = Skill(ability='dexterity')
survival = Skill(ability='wisdom', name='survival') survival = Skill(ability='wisdom')
# Characteristics # Characteristics
attacks_and_spellcasting = "" attacks_and_spellcasting = ""
personality_traits = "" personality_traits = ""
@@ -146,7 +149,7 @@ class Character():
@property @property
def class_name(self): def class_name(self):
return ' / '.join([f'{c.class_name} {c.class_level}' return ' / '.join([f'{c.class_name}'
for c in self.class_list]) for c in self.class_list])
@property @property
@@ -159,8 +162,21 @@ class Character():
@property @property
def level(self): def level(self):
return sum(c.class_level for c in self.class_list) if self.num_classes == 0:
return self._level
else:
return sum(c.class_level for c in self.class_list)
@level.setter
def level(self, new_level):
if self.num_classes == 0:
self._level = new_level
else:
self.primary_class.class_level = new_level
if self.num_classes > 1:
warnings.warn("Unable to tell which level to set. Updating "
"level of primary class {:s}".format(self.primary_class.class_name))
@property @property
def num_classes(self): def num_classes(self):
return len(self.class_list) return len(self.class_list)
@@ -185,9 +201,8 @@ class Character():
@property @property
def weapon_proficiencies(self): def weapon_proficiencies(self):
wp = set(self.other_weapon_proficiencies) wp = set(self.other_weapon_proficiencies)
if not self.class_initialized: if self.num_classes > 0:
return wp wp |= set(self.primary_class.weapon_proficiencies)
wp |= set(self.primary_class.weapon_proficiencies)
if self.num_classes > 1: if self.num_classes > 1:
for c in self.class_list[1:]: for c in self.class_list[1:]:
wp |= set(c.multiclass_weapon_proficiencies) wp |= set(c.multiclass_weapon_proficiencies)
@@ -196,6 +211,10 @@ class Character():
if self.background is not None: if self.background is not None:
wp |= set(getattr(self.background, 'weapon_proficiencies', ())) wp |= set(getattr(self.background, 'weapon_proficiencies', ()))
return tuple(wp) return tuple(wp)
@weapon_proficiencies.setter
def weapon_proficiencies(self, new_weapons):
self.other_weapon_proficiencies = tuple(new_weapons)
@property @property
def features(self): def features(self):
@@ -220,10 +239,15 @@ class Character():
@property @property
def saving_throw_proficiencies(self): def saving_throw_proficiencies(self):
if self.primary_class is not None: if self.primary_class is None:
return self.primary_class.saving_throw_proficiencies return self._saving_throw_proficiencies
else: else:
return () return (self._saving_throw_proficiencies or
self.primary_class.saving_throw_proficiencies)
@saving_throw_proficiencies.setter
def saving_throw_proficiencies(self, vals):
self._saving_throw_proficiencies = vals
@property @property
def spellcasting_classes(self): def spellcasting_classes(self):
@@ -304,14 +328,6 @@ class Character():
self.wear_armor(val) self.wear_armor(val)
elif attr == 'shield': elif attr == 'shield':
self.wield_shield(val) self.wield_shield(val)
elif attr == 'wild_shapes':
for c in self.class_list:
if isinstance(c, classes.Druid):
c.wild_shapes = val
self.all_wild_shapes = c.all_wild_shapes
self.wild_shapes = c.wild_shapes
self.can_assume_shape = c.can_assume_shape
break
elif attr == 'circle': elif attr == 'circle':
for c in self.class_list: for c in self.class_list:
if isinstance(c, classes.Druid) and (c.circle == ''): if isinstance(c, classes.Druid) and (c.circle == ''):
@@ -393,17 +409,17 @@ class Character():
@property @property
def proficiencies_text(self): def proficiencies_text(self):
final_text = "" final_text = ""
all_proficiencies = set(self._proficiencies_text) all_proficiencies = tuple(self._proficiencies_text)
if self.class_initialized: if self.class_initialized:
all_proficiencies |= set(self.primary_class._proficiencies_text) all_proficiencies += tuple(self.primary_class._proficiencies_text)
if self.num_classes > 1: if self.num_classes > 1:
for c in self.class_list[1:]: for c in self.class_list[1:]:
all_proficiencies |= set(c._multiclass_proficiencies_text) all_proficiencies += tuple(c._multiclass_proficiencies_text)
if self.race is not None: if self.race is not None:
all_proficiencies |= set(self.race.proficiencies_text) all_proficiencies += tuple(self.race.proficiencies_text)
if self.background is not None: if self.background is not None:
all_proficiencies |= set(self.background.proficiencies_text) all_proficiencies += tuple(self.background.proficiencies_text)
all_proficiencies |= set(self.proficiencies_extra) all_proficiencies += tuple(self.proficiencies_extra)
# Create a single string out of all the proficiencies # Create a single string out of all the proficiencies
for txt in all_proficiencies: for txt in all_proficiencies:
if not final_text: if not final_text:
@@ -499,8 +515,24 @@ class Character():
"""What type and how many dice to use for re-gaining hit points. """What type and how many dice to use for re-gaining hit points.
To change, set hit_dice_num and hit_dice_faces.""" To change, set hit_dice_num and hit_dice_faces."""
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}' if self.num_classes == 0:
for c in self.class_list]) return '{:d}d{:d}'.format(self.level, self._hit_dice_faces)
else:
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}'
for c in self.class_list])
@property
def hit_dice_faces(self):
if self.num_classes == 0:
return self._hit_dice_faces
else: # Not a valid function if multiclass
if self.num_classes > 1:
warnings.warn("hit_dice_faces is not valid for multiclass characters")
return self.primary_class.hit_dice_faces
@hit_dice_faces.setter
def hit_dice_faces(self, faces):
self._hit_dice_faces = faces
@property @property
def proficiency_bonus(self): def proficiency_bonus(self):
@@ -540,6 +572,33 @@ class Character():
ac += [f.AC_func(self) for f in self.features] ac += [f.AC_func(self) for f in self.features]
return max(ac) return max(ac)
def can_assume_shape(self, shape: monsters.Monster):
for c in self.class_list:
if isinstance(c, classes.Druid):
return c.can_assume_shape(shape)
return False
@property
def all_wild_shapes(self):
for c in self.class_list:
if isinstance(c, classes.Druid):
return c.all_wild_shapes
return ()
@property
def wild_shapes(self):
for c in self.class_list:
if isinstance(c, classes.Druid):
return c.wild_shapes
return ()
@wild_shapes.setter
def wild_shapes(self, new_shapes):
for c in self.class_list:
if isinstance(c, classes.Druid):
c.wild_shapes = new_shapes
@classmethod @classmethod
def load(cls, character_file): def load(cls, character_file):
# Create a character from the character definition # Create a character from the character definition
@@ -651,3 +710,21 @@ def read_character_file(filename):
if prop_name[0:2] != '__': if prop_name[0:2] != '__':
char_props[prop_name] = getattr(module, prop_name) char_props[prop_name] = getattr(module, prop_name)
return char_props return char_props
# Add backwards compatability for tests
class Druid(Character):
def __init__(self, level=1, **attrs):
MyDruid = classes.Druid(level=level)
self.class_list = [MyDruid]
super().__init__(**attrs)
class Wizard(Character):
def __init__(self, level=1, **attrs):
self.class_list = [classes.Wizard(level=level)]
super().__init__(**attrs)
+10 -2
View File
@@ -84,6 +84,7 @@ class ShepherdCircle(SubClass):
class Druid(CharClass): class Druid(CharClass):
class_name = 'Druid' class_name = 'Druid'
_wild_shapes = () _wild_shapes = ()
_circle = ''
hit_dice_faces = 8 hit_dice_faces = 8
saving_throw_proficiencies = ('intelligence', 'wisdom') saving_throw_proficiencies = ('intelligence', 'wisdom')
languages = 'Druidic' languages = 'Druidic'
@@ -144,8 +145,15 @@ class Druid(CharClass):
if isinstance(self.subclass, SubClass): if isinstance(self.subclass, SubClass):
return self.subclass.circle.lower() return self.subclass.circle.lower()
else: else:
return '' return self._circle
@circle.setter
def circle(self, circle_str):
if isinstance(self.subclass, SubClass):
self.subclass = self.select_subclass(circle_str)
else:
self._circle = circle_str
@property @property
def all_wild_shapes(self): def all_wild_shapes(self):
"""Return all wild shapes, regardless of validity.""" """Return all wild shapes, regardless of validity."""
+116 -113
View File
@@ -69,23 +69,23 @@ def text_box(string):
return new_string return new_string
def create_druid_shapes_pdf(char, basename): def create_druid_shapes_pdf(character, basename):
template = jinja_env.get_template('druid_shapes_template.tex') template = jinja_env.get_template('druid_shapes_template.tex')
return create_latex_pdf(char, basename, template) return create_latex_pdf(character, basename, template)
def create_spellbook_pdf(char, basename): def create_spellbook_pdf(character, basename):
template = jinja_env.get_template('spellbook_template.tex') template = jinja_env.get_template('spellbook_template.tex')
return create_latex_pdf(char, basename, template) return create_latex_pdf(character, basename, template)
def create_features_pdf(char, basename): def create_features_pdf(character, basename):
template = jinja_env.get_template('features_template.tex') template = jinja_env.get_template('features_template.tex')
return create_latex_pdf(char, basename, template) return create_latex_pdf(character, basename, template)
def create_latex_pdf(char, basename, template): def create_latex_pdf(character, basename, template):
tex = template.render(character=char) tex = template.render(character=character)
# Create tex document # Create tex document
tex_file = f'{basename}.tex' tex_file = f'{basename}.tex'
with open(tex_file, mode='w') as f: with open(tex_file, mode='w') as f:
@@ -116,15 +116,15 @@ def create_latex_pdf(char, basename, template):
raise exceptions.LatexError(f'Processing of {basename}.tex failed.') raise exceptions.LatexError(f'Processing of {basename}.tex failed.')
def create_spells_pdf(char, basename, flatten=False): def create_spells_pdf(character, basename, flatten=False):
class_level = ' / '.join([c.class_name + ' ' + str(c.class_level) class_level = ' / '.join([c.class_name + ' ' + str(c.class_level)
for c in char.spellcasting_classes]) for c in character.spellcasting_classes])
abilities = ' / '.join([c.spellcasting_ability.upper()[:3] abilities = ' / '.join([c.spellcasting_ability.upper()[:3]
for c in char.spellcasting_classes]) for c in character.spellcasting_classes])
DCs = ' / '.join([str(char.spell_save_dc(c)) DCs = ' / '.join([str(character.spell_save_dc(c))
for c in char.spellcasting_classes]) for c in character.spellcasting_classes])
bonuses = ' / '.join([mod_str(char.spell_attack_bonus(c)) bonuses = ' / '.join([mod_str(character.spell_attack_bonus(c))
for c in char.spellcasting_classes]) for c in character.spellcasting_classes])
spell_level = lambda x : (x or 0) spell_level = lambda x : (x or 0)
fields = { fields = {
'Spellcasting Class 2': class_level, 'Spellcasting Class 2': class_level,
@@ -132,19 +132,19 @@ def create_spells_pdf(char, basename, flatten=False):
'SpellSaveDC 2': DCs, 'SpellSaveDC 2': DCs,
'SpellAtkBonus 2': bonuses, 'SpellAtkBonus 2': bonuses,
# Number of spell slots # Number of spell slots
'SlotsTotal 19': spell_level(char.spell_slots(1)), 'SlotsTotal 19': spell_level(character.spell_slots(1)),
'SlotsTotal 20': spell_level(char.spell_slots(2)), 'SlotsTotal 20': spell_level(character.spell_slots(2)),
'SlotsTotal 21': spell_level(char.spell_slots(3)), 'SlotsTotal 21': spell_level(character.spell_slots(3)),
'SlotsTotal 22': spell_level(char.spell_slots(4)), 'SlotsTotal 22': spell_level(character.spell_slots(4)),
'SlotsTotal 23': spell_level(char.spell_slots(5)), 'SlotsTotal 23': spell_level(character.spell_slots(5)),
'SlotsTotal 24': spell_level(char.spell_slots(6)), 'SlotsTotal 24': spell_level(character.spell_slots(6)),
'SlotsTotal 25': spell_level(char.spell_slots(7)), 'SlotsTotal 25': spell_level(character.spell_slots(7)),
'SlotsTotal 26': spell_level(char.spell_slots(8)), 'SlotsTotal 26': spell_level(character.spell_slots(8)),
'SlotsTotal 27': spell_level(char.spell_slots(9)), 'SlotsTotal 27': spell_level(character.spell_slots(9)),
} }
# Cantrips # Cantrips
cantrip_fields = (f'Spells 10{i}' for i in (14, 16, 17, 18, 19, 20, 21, 22)) cantrip_fields = (f'Spells 10{i}' for i in (14, 16, 17, 18, 19, 20, 21, 22))
cantrips = (spl for spl in char.spells if spl.level == 0) cantrips = (spl for spl in character.spells if spl.level == 0)
for spell, field_name in zip(cantrips, cantrip_fields): for spell, field_name in zip(cantrips, cantrip_fields):
fields[field_name] = str(spell) fields[field_name] = str(spell)
# Spells for each level # Spells for each level
@@ -171,13 +171,13 @@ def create_spells_pdf(char, basename, flatten=False):
9: (327, 326, 3079, 3080, 3081, 3082, 3083, ), 9: (327, 326, 3079, 3080, 3081, 3082, 3083, ),
} }
for level in field_numbers.keys(): for level in field_numbers.keys():
spells = tuple(spl for spl in char.spells if spl.level == level) spells = tuple(spl for spl in character.spells if spl.level == level)
field_names = tuple(f'Spells {i}' for i in field_numbers[level]) field_names = tuple(f'Spells {i}' for i in field_numbers[level])
prep_names = tuple(f'Check Box {i}' for i in prep_numbers[level]) prep_names = tuple(f'Check Box {i}' for i in prep_numbers[level])
for spell, field, chk_field in zip(spells, field_names, prep_names): for spell, field, chk_field in zip(spells, field_names, prep_names):
fields[field] = str(spell) fields[field] = str(spell)
is_prepared = any([spell == Spl is_prepared = any([spell == Spl
for Spl in char.spells_prepared]) for Spl in character.spells_prepared])
fields[chk_field] = CHECKBOX_ON if is_prepared else CHECKBOX_OFF fields[chk_field] = CHECKBOX_ON if is_prepared else CHECKBOX_OFF
# # Uncomment to post field names instead: # # Uncomment to post field names instead:
# for field in field_names: # for field in field_names:
@@ -188,77 +188,77 @@ def create_spells_pdf(char, basename, flatten=False):
make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten) make_pdf(fields, src_pdf=src_pdf, basename=basename, flatten=flatten)
def create_character_pdf(char, basename, flatten=False): def create_character_pdf(character, basename, flatten=False):
# Prepare the list of fields # Prepare the list of fields
fields = { fields = {
# Character description # Character description
'CharacterName': char.name, 'CharacterName': character.name,
'ClassLevel': char.class_name, 'ClassLevel': character.class_name,
'Background': str(char.background), 'Background': str(character.background),
'PlayerName': char.player_name, 'PlayerName': character.player_name,
'Race ': str(char.race), 'Race ': str(character.race),
'Alignment': char.alignment, 'Alignment': character.alignment,
'XP': str(char.xp), 'XP': str(character.xp),
# Abilities # Abilities
'ProfBonus': mod_str(char.proficiency_bonus), 'ProfBonus': mod_str(character.proficiency_bonus),
'STRmod': str(char.strength.value), 'STRmod': str(character.strength.value),
'STR': mod_str(char.strength.modifier), 'STR': mod_str(character.strength.modifier),
'DEXmod ': str(char.dexterity.value), 'DEXmod ': str(character.dexterity.value),
'DEX': mod_str(char.dexterity.modifier), 'DEX': mod_str(character.dexterity.modifier),
'CONmod': str(char.constitution.value), 'CONmod': str(character.constitution.value),
'CON': mod_str(char.constitution.modifier), 'CON': mod_str(character.constitution.modifier),
'INTmod': str(char.intelligence.value), 'INTmod': str(character.intelligence.value),
'INT': mod_str(char.intelligence.modifier), 'INT': mod_str(character.intelligence.modifier),
'WISmod': str(char.wisdom.value), 'WISmod': str(character.wisdom.value),
'WIS': mod_str(char.wisdom.modifier), 'WIS': mod_str(character.wisdom.modifier),
'CHamod': str(char.charisma.value), 'CHamod': str(character.charisma.value),
'CHA': mod_str(char.charisma.modifier), 'CHA': mod_str(character.charisma.modifier),
'AC': str(char.armor_class), 'AC': str(character.armor_class),
'Initiative': mod_str(char.dexterity.modifier), 'Initiative': mod_str(character.dexterity.modifier),
'Speed': str(char.speed), 'Speed': str(character.speed),
'Passive': 10 + char.perception, 'Passive': 10 + character.perception,
# Saving throws (proficiencies handled later) # Saving throws (proficiencies handled later)
'ST Strength': mod_str(char.strength.saving_throw), 'ST Strength': mod_str(character.strength.saving_throw),
'ST Dexterity': mod_str(char.dexterity.saving_throw), 'ST Dexterity': mod_str(character.dexterity.saving_throw),
'ST Constitution': mod_str(char.constitution.saving_throw), 'ST Constitution': mod_str(character.constitution.saving_throw),
'ST Intelligence': mod_str(char.intelligence.saving_throw), 'ST Intelligence': mod_str(character.intelligence.saving_throw),
'ST Wisdom': mod_str(char.wisdom.saving_throw), 'ST Wisdom': mod_str(character.wisdom.saving_throw),
'ST Charisma': mod_str(char.charisma.saving_throw), 'ST Charisma': mod_str(character.charisma.saving_throw),
# Skills (proficiencies handled below) # Skills (proficiencies handled below)
'Acrobatics': mod_str(char.acrobatics), 'Acrobatics': mod_str(character.acrobatics),
'Animal': mod_str(char.animal_handling), 'Animal': mod_str(character.animal_handling),
'Arcana': mod_str(char.arcana), 'Arcana': mod_str(character.arcana),
'Athletics': mod_str(char.athletics), 'Athletics': mod_str(character.athletics),
'Deception ': mod_str(char.deception), 'Deception ': mod_str(character.deception),
'History ': mod_str(char.history), 'History ': mod_str(character.history),
'Insight': mod_str(char.insight), 'Insight': mod_str(character.insight),
'Intimidation': mod_str(char.intimidation), 'Intimidation': mod_str(character.intimidation),
'Investigation ': mod_str(char.investigation), 'Investigation ': mod_str(character.investigation),
'Medicine': mod_str(char.medicine), 'Medicine': mod_str(character.medicine),
'Nature': mod_str(char.nature), 'Nature': mod_str(character.nature),
'Perception ': mod_str(char.perception), 'Perception ': mod_str(character.perception),
'Performance': mod_str(char.performance), 'Performance': mod_str(character.performance),
'Persuasion': mod_str(char.persuasion), 'Persuasion': mod_str(character.persuasion),
'Religion': mod_str(char.religion), 'Religion': mod_str(character.religion),
'SleightofHand': mod_str(char.sleight_of_hand), 'SleightofHand': mod_str(character.sleight_of_hand),
'Stealth ': mod_str(char.stealth), 'Stealth ': mod_str(character.stealth),
'Survival': mod_str(char.survival), 'Survival': mod_str(character.survival),
# Hit points # Hit points
'HDTotal': char.hit_dice, 'HDTotal': character.hit_dice,
'HPMax': str(char.hp_max), 'HPMax': str(character.hp_max),
# Personality traits and other features # Personality traits and other features
'PersonalityTraits ': text_box(char.personality_traits), 'PersonalityTraits ': text_box(character.personality_traits),
'Ideals': text_box(char.ideals), 'Ideals': text_box(character.ideals),
'Bonds': text_box(char.bonds), 'Bonds': text_box(character.bonds),
'Flaws': text_box(char.flaws), 'Flaws': text_box(character.flaws),
'Features and Traits': text_box(char.features_text + char.features_and_traits), 'Features and Traits': text_box(character.features_text + character.features_and_traits),
# Inventory # Inventory
'CP': char.cp, 'CP': character.cp,
'SP': char.sp, 'SP': character.sp,
'EP': char.ep, 'EP': character.ep,
'GP': char.gp, 'GP': character.gp,
'PP': char.pp, 'PP': character.pp,
'Equipment': text_box(char.equipment), 'Equipment': text_box(character.equipment),
} }
# Check boxes for proficiencies # Check boxes for proficiencies
ST_boxes = { ST_boxes = {
@@ -269,7 +269,7 @@ def create_character_pdf(char, basename, flatten=False):
'wisdom': 'Check Box 21', 'wisdom': 'Check Box 21',
'charisma': 'Check Box 22', 'charisma': 'Check Box 22',
} }
for ability in char.saving_throw_proficiencies: for ability in character.saving_throw_proficiencies:
fields[ST_boxes[ability]] = CHECKBOX_ON fields[ST_boxes[ability]] = CHECKBOX_ON
# Add skill proficiencies # Add skill proficiencies
skill_boxes = { skill_boxes = {
@@ -292,7 +292,7 @@ def create_character_pdf(char, basename, flatten=False):
'stealth': 'Check Box 39', 'stealth': 'Check Box 39',
'survival': 'Check Box 40', 'survival': 'Check Box 40',
} }
for skill in char.skill_proficiencies: for skill in character.skill_proficiencies:
try: try:
fields[skill_boxes[skill.replace(' ', '_').lower()]] = CHECKBOX_ON fields[skill_boxes[skill.replace(' ', '_').lower()]] = CHECKBOX_ON
except KeyError: except KeyError:
@@ -301,21 +301,21 @@ def create_character_pdf(char, basename, flatten=False):
weapon_fields = [('Wpn Name', 'Wpn1 AtkBonus', 'Wpn1 Damage'), weapon_fields = [('Wpn Name', 'Wpn1 AtkBonus', 'Wpn1 Damage'),
('Wpn Name 2', 'Wpn2 AtkBonus ', 'Wpn2 Damage '), ('Wpn Name 2', 'Wpn2 AtkBonus ', 'Wpn2 Damage '),
('Wpn Name 3', 'Wpn3 AtkBonus ', 'Wpn3 Damage '),] ('Wpn Name 3', 'Wpn3 AtkBonus ', 'Wpn3 Damage '),]
for _fields, weapon in zip(weapon_fields, char.weapons): for _fields, weapon in zip(weapon_fields, character.weapons):
name_field, atk_field, dmg_field = _fields name_field, atk_field, dmg_field = _fields
fields[name_field] = weapon.name fields[name_field] = weapon.name
fields[atk_field] = '{:+d}'.format(weapon.attack_bonus) fields[atk_field] = '{:+d}'.format(weapon.attack_bonus)
fields[dmg_field] = f'{weapon.damage}/{weapon.damage_type}' fields[dmg_field] = f'{weapon.damage}/{weapon.damage_type}'
# Other attack information # Other attack information
attack_str = f'Armor: {char.armor}' attack_str = f'Armor: {character.armor}'
attack_str += '\n \n' attack_str += '\n \n'
attack_str += f'Shield: {char.shield}' attack_str += f'Shield: {character.shield}'
attack_str += '\n \n' attack_str += '\n \n'
attack_str += char.attacks_and_spellcasting attack_str += character.attacks_and_spellcasting
fields['AttacksSpellcasting'] = text_box(attack_str) fields['AttacksSpellcasting'] = text_box(attack_str)
# Other proficiencies and languages # Other proficiencies and languages
prof_text = "Proficiencies:\n" + text_box(char.proficiencies_text) prof_text = "Proficiencies:\n" + text_box(character.proficiencies_text)
prof_text += "\n\nLanguages:\n" + text_box(char.languages) prof_text += "\n\nLanguages:\n" + text_box(character.languages)
fields['ProficienciesLang'] = prof_text fields['ProficienciesLang'] = prof_text
# Prepare the actual PDF # Prepare the actual PDF
dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'forms/') dirname = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'forms/')
@@ -420,63 +420,63 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
os.remove(fdfname) os.remove(fdfname)
def make_sheet(character_file, char=None, flatten=False): def make_sheet(character_file, character=None, flatten=False):
"""Prepare a PDF character sheet from the given character file. """Prepare a PDF character sheet from the given character file.
Parameters Parameters
---------- ----------
character_file : str character_file : str
File (.py) to load character from. Will save PDF using same name File (.py) to load character from. Will save PDF using same name
char : Character, optional character : Character, optional
If provided, will not load from the character file, just use file If provided, will not load from the character file, just use file
for PDF name for PDF name
flatten : bool, optional flatten : bool, optional
If true, the resulting PDF will look better and won't be fillable form. If true, the resulting PDF will look better and won't be fillable form.
""" """
if char is None: if character is None:
char = character.Character.load(character_file) character = character.Character.load(character_file)
# Set the fields in the FDF # Set the fields in the FDF
char_base = os.path.splitext(character_file)[0] + '_char' char_base = os.path.splitext(character_file)[0] + '_char'
sheets = [char_base + '.pdf'] sheets = [char_base + '.pdf']
pages = [] pages = []
char_pdf = create_character_pdf(char=char, basename=char_base, char_pdf = create_character_pdf(character=character, basename=char_base,
flatten=flatten) flatten=flatten)
pages.append(char_pdf) pages.append(char_pdf)
if char.is_spellcaster: if character.is_spellcaster:
# Create spell sheet # Create spell sheet
spell_base = '{:s}_spells'.format( spell_base = '{:s}_spells'.format(
os.path.splitext(character_file)[0]) os.path.splitext(character_file)[0])
create_spells_pdf(char=char, basename=spell_base, flatten=flatten) create_spells_pdf(character=character, basename=spell_base, flatten=flatten)
sheets.append(spell_base + '.pdf') sheets.append(spell_base + '.pdf')
if len(char.features) > 0: if len(character.features) > 0:
feat_base = '{:s}_feats'.format( feat_base = '{:s}_feats'.format(
os.path.splitext(character_file)[0]) os.path.splitext(character_file)[0])
try: try:
create_features_pdf(char=char, basename=feat_base) create_features_pdf(character=character, basename=feat_base)
except exceptions.LatexNotFoundError as e: except exceptions.LatexNotFoundError as e:
log.warning('``pdflatex`` not available. Skipping features book ' log.warning('``pdflatex`` not available. Skipping features book '
f'for {char.name}') f'for {character.name}')
else: else:
sheets.append(feat_base + '.pdf') sheets.append(feat_base + '.pdf')
if char.is_spellcaster: if character.is_spellcaster:
# Create spell book # Create spell book
spellbook_base = os.path.splitext(character_file)[0] + '_spellbook' spellbook_base = os.path.splitext(character_file)[0] + '_spellbook'
try: try:
create_spellbook_pdf(char=char, basename=spellbook_base) create_spellbook_pdf(character=character, basename=spellbook_base)
except exceptions.LatexNotFoundError as e: except exceptions.LatexNotFoundError as e:
log.warning('``pdflatex`` not available. Skipping spellbook ' log.warning('``pdflatex`` not available. Skipping spellbook '
f'for {char.name}') f'for {character.name}')
else: else:
sheets.append(spellbook_base + '.pdf') sheets.append(spellbook_base + '.pdf')
# Create a list of Druid wild_shapes # Create a list of Druid wild_shapes
wild_shapes = getattr(char, 'wild_shapes', []) wild_shapes = getattr(character, 'wild_shapes', [])
if len(wild_shapes) > 0: if len(wild_shapes) > 0:
shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes' shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes'
try: try:
create_druid_shapes_pdf(char=char, basename=shapes_base) create_druid_shapes_pdf(character=character, basename=shapes_base)
except exceptions.LatexNotFoundError as e: except exceptions.LatexNotFoundError as e:
log.warning('``pdflatex`` not available. Skipping wild shapes list ' log.warning('``pdflatex`` not available. Skipping wild shapes list '
f'for {char.name}') f'for {character.name}')
else: else:
sheets.append(shapes_base + '.pdf') sheets.append(shapes_base + '.pdf')
# Combine sheets into final pdf # Combine sheets into final pdf
@@ -510,6 +510,9 @@ def merge_pdfs(src_filenames, dest_filename, clean_up=False):
os.remove(sheet) os.remove(sheet)
load_character_file = character.read_character_file
def main(): def main():
# Prepare an argument parser # Prepare an argument parser
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
+10 -2
View File
@@ -30,11 +30,15 @@ class Spell():
materials = "" materials = ""
duration = "instantaneous" duration = "instantaneous"
ritual = False ritual = False
_concentration = False
magic_school = "" magic_school = ""
classes = () classes = ()
def __str__(self): def __str__(self):
s = self.name + ' ({:s}) '.format(','.join(self.components)) if len(self.components) == 0:
s = self.name
else:
s = self.name + ' ({:s}) '.format(','.join(self.components))
# Indicate if this is a ritual or a concentration # Indicate if this is a ritual or a concentration
indicators = [('R', self.ritual), ('C', self.concentration), ('$', self.special_material)] indicators = [('R', self.ritual), ('C', self.concentration), ('$', self.special_material)]
indicators = tuple(letter for letter, is_active in indicators if is_active) indicators = tuple(letter for letter, is_active in indicators if is_active)
@@ -60,7 +64,11 @@ class Spell():
@property @property
def concentration(self): def concentration(self):
return ('concentration' in self.duration.lower()) return ('concentration' in self.duration.lower()) or self._concentration
@concentration.setter
def concentration(self, val: bool):
self._concentration = val
@property @property
def special_material(self): def special_material(self):
+4 -5
View File
@@ -74,13 +74,12 @@ class Ability():
class Skill(): class Skill():
"""An ability-based skill, such as athletics.""" """An ability-based skill, such as athletics."""
def __init__(self, ability, name): def __init__(self, ability):
self.ability_name = ability self.ability_name = ability
self.skill_name = name
# def __set_name__(self, character, name): def __set_name__(self, character, name):
# self.skill_name = name self.skill_name = name.lower().replace('_', ' ')
# self.character = character self.character = character
def __get__(self, character, owner): def __get__(self, character, owner):
ability = getattr(character, self.ability_name) ability = getattr(character, self.ability_name)