mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-06-07 13:15:53 +02:00
updated so all tests pass except distinct changes to desired implementation
This commit is contained in:
+115
-38
@@ -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):
|
||||||
|
if self.num_classes == 0:
|
||||||
|
return self._level
|
||||||
|
else:
|
||||||
return sum(c.class_level for c in self.class_list)
|
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,8 +201,7 @@ 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:]:
|
||||||
@@ -197,6 +212,10 @@ class Character():
|
|||||||
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):
|
||||||
fts = set(self.custom_features)
|
fts = set(self.custom_features)
|
||||||
@@ -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,9 +515,25 @@ 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."""
|
||||||
|
if self.num_classes == 0:
|
||||||
|
return '{:d}d{:d}'.format(self.level, self._hit_dice_faces)
|
||||||
|
else:
|
||||||
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}'
|
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}'
|
||||||
for c in self.class_list])
|
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):
|
||||||
if self.level < 5:
|
if self.level < 5:
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -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,7 +145,14 @@ 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):
|
||||||
|
|||||||
+116
-113
@@ -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(
|
||||||
|
|||||||
@@ -30,10 +30,14 @@ 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):
|
||||||
|
if len(self.components) == 0:
|
||||||
|
s = self.name
|
||||||
|
else:
|
||||||
s = self.name + ' ({:s}) '.format(','.join(self.components))
|
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)]
|
||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user