mirror of
https://github.com/Threnklyn/dungeon-sheets.git
synced 2026-05-23 06:08:29 +02:00
updated so all tests pass except distinct changes to desired implementation
This commit is contained in:
+119
-42
@@ -62,6 +62,8 @@ class Character():
|
||||
alignment = "Neutral"
|
||||
dungeonsheets_version = __version__
|
||||
class_list = []
|
||||
_level = 1 # Keep internal check of total level
|
||||
_hit_dice_faces = 2
|
||||
race = None
|
||||
background = None
|
||||
xp = 0
|
||||
@@ -74,6 +76,7 @@ class Character():
|
||||
intelligence = Ability()
|
||||
wisdom = Ability()
|
||||
charisma = Ability()
|
||||
_saving_throw_proficiencies = []
|
||||
other_weapon_proficiencies = tuple()
|
||||
skill_proficiencies = tuple()
|
||||
skill_expertise = tuple()
|
||||
@@ -82,24 +85,24 @@ class Character():
|
||||
proficiencies_extra = tuple()
|
||||
languages = ""
|
||||
# Skills
|
||||
acrobatics = Skill(ability='dexterity', name='acrobatics')
|
||||
animal_handling = Skill(ability='wisdom', name='animal handling')
|
||||
arcana = Skill(ability='intelligence', name='arcana')
|
||||
athletics = Skill(ability='strength', name='athletics')
|
||||
deception = Skill(ability='charisma', name='deception')
|
||||
history = Skill(ability='intelligence', name='history')
|
||||
insight = Skill(ability='wisdom', name='insight')
|
||||
intimidation = Skill(ability='charisma', name='intimidation')
|
||||
investigation = Skill(ability='intelligence', name='investigation')
|
||||
medicine = Skill(ability='wisdom', name='medicine')
|
||||
nature = Skill(ability='intelligence', name='nature')
|
||||
perception = Skill(ability='wisdom', name='perception')
|
||||
performance = Skill(ability='charisma', name='performance')
|
||||
persuasion = Skill(ability='charisma', name='persuasion')
|
||||
religion = Skill(ability='intelligence', name='religion')
|
||||
sleight_of_hand = Skill(ability='dexterity', name='sleight of hand')
|
||||
stealth = Skill(ability='dexterity', name='stealth')
|
||||
survival = Skill(ability='wisdom', name='survival')
|
||||
acrobatics = Skill(ability='dexterity')
|
||||
animal_handling = Skill(ability='wisdom')
|
||||
arcana = Skill(ability='intelligence')
|
||||
athletics = Skill(ability='strength')
|
||||
deception = Skill(ability='charisma')
|
||||
history = Skill(ability='intelligence')
|
||||
insight = Skill(ability='wisdom')
|
||||
intimidation = Skill(ability='charisma')
|
||||
investigation = Skill(ability='intelligence')
|
||||
medicine = Skill(ability='wisdom')
|
||||
nature = Skill(ability='intelligence')
|
||||
perception = Skill(ability='wisdom')
|
||||
performance = Skill(ability='charisma')
|
||||
persuasion = Skill(ability='charisma')
|
||||
religion = Skill(ability='intelligence')
|
||||
sleight_of_hand = Skill(ability='dexterity')
|
||||
stealth = Skill(ability='dexterity')
|
||||
survival = Skill(ability='wisdom')
|
||||
# Characteristics
|
||||
attacks_and_spellcasting = ""
|
||||
personality_traits = ""
|
||||
@@ -146,7 +149,7 @@ class Character():
|
||||
|
||||
@property
|
||||
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])
|
||||
|
||||
@property
|
||||
@@ -159,8 +162,21 @@ class Character():
|
||||
|
||||
@property
|
||||
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
|
||||
def num_classes(self):
|
||||
return len(self.class_list)
|
||||
@@ -185,9 +201,8 @@ class Character():
|
||||
@property
|
||||
def weapon_proficiencies(self):
|
||||
wp = set(self.other_weapon_proficiencies)
|
||||
if not self.class_initialized:
|
||||
return wp
|
||||
wp |= set(self.primary_class.weapon_proficiencies)
|
||||
if self.num_classes > 0:
|
||||
wp |= set(self.primary_class.weapon_proficiencies)
|
||||
if self.num_classes > 1:
|
||||
for c in self.class_list[1:]:
|
||||
wp |= set(c.multiclass_weapon_proficiencies)
|
||||
@@ -196,6 +211,10 @@ class Character():
|
||||
if self.background is not None:
|
||||
wp |= set(getattr(self.background, 'weapon_proficiencies', ()))
|
||||
return tuple(wp)
|
||||
|
||||
@weapon_proficiencies.setter
|
||||
def weapon_proficiencies(self, new_weapons):
|
||||
self.other_weapon_proficiencies = tuple(new_weapons)
|
||||
|
||||
@property
|
||||
def features(self):
|
||||
@@ -220,10 +239,15 @@ class Character():
|
||||
|
||||
@property
|
||||
def saving_throw_proficiencies(self):
|
||||
if self.primary_class is not None:
|
||||
return self.primary_class.saving_throw_proficiencies
|
||||
if self.primary_class is None:
|
||||
return self._saving_throw_proficiencies
|
||||
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
|
||||
def spellcasting_classes(self):
|
||||
@@ -304,14 +328,6 @@ class Character():
|
||||
self.wear_armor(val)
|
||||
elif attr == 'shield':
|
||||
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':
|
||||
for c in self.class_list:
|
||||
if isinstance(c, classes.Druid) and (c.circle == ''):
|
||||
@@ -393,17 +409,17 @@ class Character():
|
||||
@property
|
||||
def proficiencies_text(self):
|
||||
final_text = ""
|
||||
all_proficiencies = set(self._proficiencies_text)
|
||||
all_proficiencies = tuple(self._proficiencies_text)
|
||||
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:
|
||||
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:
|
||||
all_proficiencies |= set(self.race.proficiencies_text)
|
||||
all_proficiencies += tuple(self.race.proficiencies_text)
|
||||
if self.background is not None:
|
||||
all_proficiencies |= set(self.background.proficiencies_text)
|
||||
all_proficiencies |= set(self.proficiencies_extra)
|
||||
all_proficiencies += tuple(self.background.proficiencies_text)
|
||||
all_proficiencies += tuple(self.proficiencies_extra)
|
||||
# Create a single string out of all the proficiencies
|
||||
for txt in all_proficiencies:
|
||||
if not final_text:
|
||||
@@ -499,8 +515,24 @@ class Character():
|
||||
"""What type and how many dice to use for re-gaining hit points.
|
||||
|
||||
To change, set hit_dice_num and hit_dice_faces."""
|
||||
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}'
|
||||
for c in self.class_list])
|
||||
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}'
|
||||
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
|
||||
def proficiency_bonus(self):
|
||||
@@ -540,6 +572,33 @@ class Character():
|
||||
ac += [f.AC_func(self) for f in self.features]
|
||||
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
|
||||
def load(cls, character_file):
|
||||
# Create a character from the character definition
|
||||
@@ -651,3 +710,21 @@ def read_character_file(filename):
|
||||
if prop_name[0:2] != '__':
|
||||
char_props[prop_name] = getattr(module, prop_name)
|
||||
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_name = 'Druid'
|
||||
_wild_shapes = ()
|
||||
_circle = ''
|
||||
hit_dice_faces = 8
|
||||
saving_throw_proficiencies = ('intelligence', 'wisdom')
|
||||
languages = 'Druidic'
|
||||
@@ -144,8 +145,15 @@ class Druid(CharClass):
|
||||
if isinstance(self.subclass, SubClass):
|
||||
return self.subclass.circle.lower()
|
||||
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
|
||||
def all_wild_shapes(self):
|
||||
"""Return all wild shapes, regardless of validity."""
|
||||
|
||||
+116
-113
@@ -69,23 +69,23 @@ def text_box(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')
|
||||
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')
|
||||
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')
|
||||
return create_latex_pdf(char, basename, template)
|
||||
return create_latex_pdf(character, basename, template)
|
||||
|
||||
|
||||
def create_latex_pdf(char, basename, template):
|
||||
tex = template.render(character=char)
|
||||
def create_latex_pdf(character, basename, template):
|
||||
tex = template.render(character=character)
|
||||
# Create tex document
|
||||
tex_file = f'{basename}.tex'
|
||||
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.')
|
||||
|
||||
|
||||
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)
|
||||
for c in char.spellcasting_classes])
|
||||
for c in character.spellcasting_classes])
|
||||
abilities = ' / '.join([c.spellcasting_ability.upper()[:3]
|
||||
for c in char.spellcasting_classes])
|
||||
DCs = ' / '.join([str(char.spell_save_dc(c))
|
||||
for c in char.spellcasting_classes])
|
||||
bonuses = ' / '.join([mod_str(char.spell_attack_bonus(c))
|
||||
for c in char.spellcasting_classes])
|
||||
for c in character.spellcasting_classes])
|
||||
DCs = ' / '.join([str(character.spell_save_dc(c))
|
||||
for c in character.spellcasting_classes])
|
||||
bonuses = ' / '.join([mod_str(character.spell_attack_bonus(c))
|
||||
for c in character.spellcasting_classes])
|
||||
spell_level = lambda x : (x or 0)
|
||||
fields = {
|
||||
'Spellcasting Class 2': class_level,
|
||||
@@ -132,19 +132,19 @@ def create_spells_pdf(char, basename, flatten=False):
|
||||
'SpellSaveDC 2': DCs,
|
||||
'SpellAtkBonus 2': bonuses,
|
||||
# Number of spell slots
|
||||
'SlotsTotal 19': spell_level(char.spell_slots(1)),
|
||||
'SlotsTotal 20': spell_level(char.spell_slots(2)),
|
||||
'SlotsTotal 21': spell_level(char.spell_slots(3)),
|
||||
'SlotsTotal 22': spell_level(char.spell_slots(4)),
|
||||
'SlotsTotal 23': spell_level(char.spell_slots(5)),
|
||||
'SlotsTotal 24': spell_level(char.spell_slots(6)),
|
||||
'SlotsTotal 25': spell_level(char.spell_slots(7)),
|
||||
'SlotsTotal 26': spell_level(char.spell_slots(8)),
|
||||
'SlotsTotal 27': spell_level(char.spell_slots(9)),
|
||||
'SlotsTotal 19': spell_level(character.spell_slots(1)),
|
||||
'SlotsTotal 20': spell_level(character.spell_slots(2)),
|
||||
'SlotsTotal 21': spell_level(character.spell_slots(3)),
|
||||
'SlotsTotal 22': spell_level(character.spell_slots(4)),
|
||||
'SlotsTotal 23': spell_level(character.spell_slots(5)),
|
||||
'SlotsTotal 24': spell_level(character.spell_slots(6)),
|
||||
'SlotsTotal 25': spell_level(character.spell_slots(7)),
|
||||
'SlotsTotal 26': spell_level(character.spell_slots(8)),
|
||||
'SlotsTotal 27': spell_level(character.spell_slots(9)),
|
||||
}
|
||||
# Cantrips
|
||||
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):
|
||||
fields[field_name] = str(spell)
|
||||
# Spells for each level
|
||||
@@ -171,13 +171,13 @@ def create_spells_pdf(char, basename, flatten=False):
|
||||
9: (327, 326, 3079, 3080, 3081, 3082, 3083, ),
|
||||
}
|
||||
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])
|
||||
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):
|
||||
fields[field] = str(spell)
|
||||
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
|
||||
# # Uncomment to post field names instead:
|
||||
# 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)
|
||||
|
||||
|
||||
def create_character_pdf(char, basename, flatten=False):
|
||||
def create_character_pdf(character, basename, flatten=False):
|
||||
# Prepare the list of fields
|
||||
fields = {
|
||||
# Character description
|
||||
'CharacterName': char.name,
|
||||
'ClassLevel': char.class_name,
|
||||
'Background': str(char.background),
|
||||
'PlayerName': char.player_name,
|
||||
'Race ': str(char.race),
|
||||
'Alignment': char.alignment,
|
||||
'XP': str(char.xp),
|
||||
'CharacterName': character.name,
|
||||
'ClassLevel': character.class_name,
|
||||
'Background': str(character.background),
|
||||
'PlayerName': character.player_name,
|
||||
'Race ': str(character.race),
|
||||
'Alignment': character.alignment,
|
||||
'XP': str(character.xp),
|
||||
# Abilities
|
||||
'ProfBonus': mod_str(char.proficiency_bonus),
|
||||
'STRmod': str(char.strength.value),
|
||||
'STR': mod_str(char.strength.modifier),
|
||||
'DEXmod ': str(char.dexterity.value),
|
||||
'DEX': mod_str(char.dexterity.modifier),
|
||||
'CONmod': str(char.constitution.value),
|
||||
'CON': mod_str(char.constitution.modifier),
|
||||
'INTmod': str(char.intelligence.value),
|
||||
'INT': mod_str(char.intelligence.modifier),
|
||||
'WISmod': str(char.wisdom.value),
|
||||
'WIS': mod_str(char.wisdom.modifier),
|
||||
'CHamod': str(char.charisma.value),
|
||||
'CHA': mod_str(char.charisma.modifier),
|
||||
'AC': str(char.armor_class),
|
||||
'Initiative': mod_str(char.dexterity.modifier),
|
||||
'Speed': str(char.speed),
|
||||
'Passive': 10 + char.perception,
|
||||
'ProfBonus': mod_str(character.proficiency_bonus),
|
||||
'STRmod': str(character.strength.value),
|
||||
'STR': mod_str(character.strength.modifier),
|
||||
'DEXmod ': str(character.dexterity.value),
|
||||
'DEX': mod_str(character.dexterity.modifier),
|
||||
'CONmod': str(character.constitution.value),
|
||||
'CON': mod_str(character.constitution.modifier),
|
||||
'INTmod': str(character.intelligence.value),
|
||||
'INT': mod_str(character.intelligence.modifier),
|
||||
'WISmod': str(character.wisdom.value),
|
||||
'WIS': mod_str(character.wisdom.modifier),
|
||||
'CHamod': str(character.charisma.value),
|
||||
'CHA': mod_str(character.charisma.modifier),
|
||||
'AC': str(character.armor_class),
|
||||
'Initiative': mod_str(character.dexterity.modifier),
|
||||
'Speed': str(character.speed),
|
||||
'Passive': 10 + character.perception,
|
||||
# Saving throws (proficiencies handled later)
|
||||
'ST Strength': mod_str(char.strength.saving_throw),
|
||||
'ST Dexterity': mod_str(char.dexterity.saving_throw),
|
||||
'ST Constitution': mod_str(char.constitution.saving_throw),
|
||||
'ST Intelligence': mod_str(char.intelligence.saving_throw),
|
||||
'ST Wisdom': mod_str(char.wisdom.saving_throw),
|
||||
'ST Charisma': mod_str(char.charisma.saving_throw),
|
||||
'ST Strength': mod_str(character.strength.saving_throw),
|
||||
'ST Dexterity': mod_str(character.dexterity.saving_throw),
|
||||
'ST Constitution': mod_str(character.constitution.saving_throw),
|
||||
'ST Intelligence': mod_str(character.intelligence.saving_throw),
|
||||
'ST Wisdom': mod_str(character.wisdom.saving_throw),
|
||||
'ST Charisma': mod_str(character.charisma.saving_throw),
|
||||
# Skills (proficiencies handled below)
|
||||
'Acrobatics': mod_str(char.acrobatics),
|
||||
'Animal': mod_str(char.animal_handling),
|
||||
'Arcana': mod_str(char.arcana),
|
||||
'Athletics': mod_str(char.athletics),
|
||||
'Deception ': mod_str(char.deception),
|
||||
'History ': mod_str(char.history),
|
||||
'Insight': mod_str(char.insight),
|
||||
'Intimidation': mod_str(char.intimidation),
|
||||
'Investigation ': mod_str(char.investigation),
|
||||
'Medicine': mod_str(char.medicine),
|
||||
'Nature': mod_str(char.nature),
|
||||
'Perception ': mod_str(char.perception),
|
||||
'Performance': mod_str(char.performance),
|
||||
'Persuasion': mod_str(char.persuasion),
|
||||
'Religion': mod_str(char.religion),
|
||||
'SleightofHand': mod_str(char.sleight_of_hand),
|
||||
'Stealth ': mod_str(char.stealth),
|
||||
'Survival': mod_str(char.survival),
|
||||
'Acrobatics': mod_str(character.acrobatics),
|
||||
'Animal': mod_str(character.animal_handling),
|
||||
'Arcana': mod_str(character.arcana),
|
||||
'Athletics': mod_str(character.athletics),
|
||||
'Deception ': mod_str(character.deception),
|
||||
'History ': mod_str(character.history),
|
||||
'Insight': mod_str(character.insight),
|
||||
'Intimidation': mod_str(character.intimidation),
|
||||
'Investigation ': mod_str(character.investigation),
|
||||
'Medicine': mod_str(character.medicine),
|
||||
'Nature': mod_str(character.nature),
|
||||
'Perception ': mod_str(character.perception),
|
||||
'Performance': mod_str(character.performance),
|
||||
'Persuasion': mod_str(character.persuasion),
|
||||
'Religion': mod_str(character.religion),
|
||||
'SleightofHand': mod_str(character.sleight_of_hand),
|
||||
'Stealth ': mod_str(character.stealth),
|
||||
'Survival': mod_str(character.survival),
|
||||
# Hit points
|
||||
'HDTotal': char.hit_dice,
|
||||
'HPMax': str(char.hp_max),
|
||||
'HDTotal': character.hit_dice,
|
||||
'HPMax': str(character.hp_max),
|
||||
# Personality traits and other features
|
||||
'PersonalityTraits ': text_box(char.personality_traits),
|
||||
'Ideals': text_box(char.ideals),
|
||||
'Bonds': text_box(char.bonds),
|
||||
'Flaws': text_box(char.flaws),
|
||||
'Features and Traits': text_box(char.features_text + char.features_and_traits),
|
||||
'PersonalityTraits ': text_box(character.personality_traits),
|
||||
'Ideals': text_box(character.ideals),
|
||||
'Bonds': text_box(character.bonds),
|
||||
'Flaws': text_box(character.flaws),
|
||||
'Features and Traits': text_box(character.features_text + character.features_and_traits),
|
||||
# Inventory
|
||||
'CP': char.cp,
|
||||
'SP': char.sp,
|
||||
'EP': char.ep,
|
||||
'GP': char.gp,
|
||||
'PP': char.pp,
|
||||
'Equipment': text_box(char.equipment),
|
||||
'CP': character.cp,
|
||||
'SP': character.sp,
|
||||
'EP': character.ep,
|
||||
'GP': character.gp,
|
||||
'PP': character.pp,
|
||||
'Equipment': text_box(character.equipment),
|
||||
}
|
||||
# Check boxes for proficiencies
|
||||
ST_boxes = {
|
||||
@@ -269,7 +269,7 @@ def create_character_pdf(char, basename, flatten=False):
|
||||
'wisdom': 'Check Box 21',
|
||||
'charisma': 'Check Box 22',
|
||||
}
|
||||
for ability in char.saving_throw_proficiencies:
|
||||
for ability in character.saving_throw_proficiencies:
|
||||
fields[ST_boxes[ability]] = CHECKBOX_ON
|
||||
# Add skill proficiencies
|
||||
skill_boxes = {
|
||||
@@ -292,7 +292,7 @@ def create_character_pdf(char, basename, flatten=False):
|
||||
'stealth': 'Check Box 39',
|
||||
'survival': 'Check Box 40',
|
||||
}
|
||||
for skill in char.skill_proficiencies:
|
||||
for skill in character.skill_proficiencies:
|
||||
try:
|
||||
fields[skill_boxes[skill.replace(' ', '_').lower()]] = CHECKBOX_ON
|
||||
except KeyError:
|
||||
@@ -301,21 +301,21 @@ def create_character_pdf(char, basename, flatten=False):
|
||||
weapon_fields = [('Wpn Name', 'Wpn1 AtkBonus', 'Wpn1 Damage'),
|
||||
('Wpn Name 2', 'Wpn2 AtkBonus ', 'Wpn2 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
|
||||
fields[name_field] = weapon.name
|
||||
fields[atk_field] = '{:+d}'.format(weapon.attack_bonus)
|
||||
fields[dmg_field] = f'{weapon.damage}/{weapon.damage_type}'
|
||||
# Other attack information
|
||||
attack_str = f'Armor: {char.armor}'
|
||||
attack_str = f'Armor: {character.armor}'
|
||||
attack_str += '\n \n'
|
||||
attack_str += f'Shield: {char.shield}'
|
||||
attack_str += f'Shield: {character.shield}'
|
||||
attack_str += '\n \n'
|
||||
attack_str += char.attacks_and_spellcasting
|
||||
attack_str += character.attacks_and_spellcasting
|
||||
fields['AttacksSpellcasting'] = text_box(attack_str)
|
||||
# Other proficiencies and languages
|
||||
prof_text = "Proficiencies:\n" + text_box(char.proficiencies_text)
|
||||
prof_text += "\n\nLanguages:\n" + text_box(char.languages)
|
||||
prof_text = "Proficiencies:\n" + text_box(character.proficiencies_text)
|
||||
prof_text += "\n\nLanguages:\n" + text_box(character.languages)
|
||||
fields['ProficienciesLang'] = prof_text
|
||||
# Prepare the actual PDF
|
||||
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)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
character_file : str
|
||||
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
|
||||
for PDF name
|
||||
flatten : bool, optional
|
||||
If true, the resulting PDF will look better and won't be fillable form.
|
||||
"""
|
||||
if char is None:
|
||||
char = character.Character.load(character_file)
|
||||
if character is None:
|
||||
character = character.Character.load(character_file)
|
||||
# Set the fields in the FDF
|
||||
char_base = os.path.splitext(character_file)[0] + '_char'
|
||||
sheets = [char_base + '.pdf']
|
||||
pages = []
|
||||
char_pdf = create_character_pdf(char=char, basename=char_base,
|
||||
char_pdf = create_character_pdf(character=character, basename=char_base,
|
||||
flatten=flatten)
|
||||
pages.append(char_pdf)
|
||||
if char.is_spellcaster:
|
||||
if character.is_spellcaster:
|
||||
# Create spell sheet
|
||||
spell_base = '{:s}_spells'.format(
|
||||
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')
|
||||
if len(char.features) > 0:
|
||||
if len(character.features) > 0:
|
||||
feat_base = '{:s}_feats'.format(
|
||||
os.path.splitext(character_file)[0])
|
||||
try:
|
||||
create_features_pdf(char=char, basename=feat_base)
|
||||
create_features_pdf(character=character, basename=feat_base)
|
||||
except exceptions.LatexNotFoundError as e:
|
||||
log.warning('``pdflatex`` not available. Skipping features book '
|
||||
f'for {char.name}')
|
||||
f'for {character.name}')
|
||||
else:
|
||||
sheets.append(feat_base + '.pdf')
|
||||
if char.is_spellcaster:
|
||||
if character.is_spellcaster:
|
||||
# Create spell book
|
||||
spellbook_base = os.path.splitext(character_file)[0] + '_spellbook'
|
||||
try:
|
||||
create_spellbook_pdf(char=char, basename=spellbook_base)
|
||||
create_spellbook_pdf(character=character, basename=spellbook_base)
|
||||
except exceptions.LatexNotFoundError as e:
|
||||
log.warning('``pdflatex`` not available. Skipping spellbook '
|
||||
f'for {char.name}')
|
||||
f'for {character.name}')
|
||||
else:
|
||||
sheets.append(spellbook_base + '.pdf')
|
||||
# Create a list of Druid wild_shapes
|
||||
wild_shapes = getattr(char, 'wild_shapes', [])
|
||||
wild_shapes = getattr(character, 'wild_shapes', [])
|
||||
if len(wild_shapes) > 0:
|
||||
shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes'
|
||||
try:
|
||||
create_druid_shapes_pdf(char=char, basename=shapes_base)
|
||||
create_druid_shapes_pdf(character=character, basename=shapes_base)
|
||||
except exceptions.LatexNotFoundError as e:
|
||||
log.warning('``pdflatex`` not available. Skipping wild shapes list '
|
||||
f'for {char.name}')
|
||||
f'for {character.name}')
|
||||
else:
|
||||
sheets.append(shapes_base + '.pdf')
|
||||
# Combine sheets into final pdf
|
||||
@@ -510,6 +510,9 @@ def merge_pdfs(src_filenames, dest_filename, clean_up=False):
|
||||
os.remove(sheet)
|
||||
|
||||
|
||||
load_character_file = character.read_character_file
|
||||
|
||||
|
||||
def main():
|
||||
# Prepare an argument parser
|
||||
parser = argparse.ArgumentParser(
|
||||
|
||||
@@ -30,11 +30,15 @@ class Spell():
|
||||
materials = ""
|
||||
duration = "instantaneous"
|
||||
ritual = False
|
||||
_concentration = False
|
||||
magic_school = ""
|
||||
classes = ()
|
||||
|
||||
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
|
||||
indicators = [('R', self.ritual), ('C', self.concentration), ('$', self.special_material)]
|
||||
indicators = tuple(letter for letter, is_active in indicators if is_active)
|
||||
@@ -60,7 +64,11 @@ class Spell():
|
||||
|
||||
@property
|
||||
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
|
||||
def special_material(self):
|
||||
|
||||
@@ -74,13 +74,12 @@ class Ability():
|
||||
class Skill():
|
||||
"""An ability-based skill, such as athletics."""
|
||||
|
||||
def __init__(self, ability, name):
|
||||
def __init__(self, ability):
|
||||
self.ability_name = ability
|
||||
self.skill_name = name
|
||||
|
||||
# def __set_name__(self, character, name):
|
||||
# self.skill_name = name
|
||||
# self.character = character
|
||||
def __set_name__(self, character, name):
|
||||
self.skill_name = name.lower().replace('_', ' ')
|
||||
self.character = character
|
||||
|
||||
def __get__(self, character, owner):
|
||||
ability = getattr(character, self.ability_name)
|
||||
|
||||
Reference in New Issue
Block a user