test that backward compatability works

This commit is contained in:
Ben Cook
2018-12-19 09:34:03 -05:00
parent 6c634c0429
commit 8f0ee321a4
18 changed files with 192 additions and 112 deletions
+1 -1
View File
@@ -1 +1 @@
0.6.1
0.7.1
+2 -1
View File
@@ -1,2 +1,3 @@
Add multiclassing hit dice
Add Character.save() option to save to text file
Add Inspiration points
Test Multiclass
-1
View File
@@ -1,3 +1,2 @@
from . import weapons, character
__VERSION__ = "0.7.0"
+2
View File
@@ -1,6 +1,8 @@
class Background():
name = "Generic background"
skill_proficiencies = ()
weapon_proficiencies = ()
proficiencies_text = ()
languages = ()
def __str__(self):
+63 -14
View File
@@ -38,11 +38,9 @@ class Character():
intelligence = Ability()
wisdom = Ability()
charisma = Ability()
saving_throw_proficiencies = []
skill_proficiencies = tuple()
class_skill_choices = tuple()
num_skill_choices = 2
weapon_proficiencies = tuple()
proficiencies_extra = tuple()
languages = ""
# Skills
@@ -90,8 +88,23 @@ class Character():
def __init__(self, **attrs):
"""Takes a bunch of attrs and passes them to ``set_attrs``"""
self.weapons = []
# make sure class, race, background are set first
class_list = attrs.pop('class_list', self.class_list)
race = attrs.pop('race', self.race)
background = attrs.pop('background', self.background)
self.set_attrs(**{'class_list': class_list,
'race': race,
'background': background})
self.set_attrs(**attrs)
for c in self.class_list:
if isinstance(c, classes.Druid):
ws = self.wild_shapes
c.wild_shapes = ws
c.circle = self.circle
self.all_wild_shapes = c.all_wild_shapes
self.wild_shapes = c.wild_shapes
self.can_assume_shape = c.can_assume_shape
def __str__(self):
return self.name
@@ -107,13 +120,41 @@ class Character():
return sum(c.class_level for c in self.class_list)
@property
def primary_class(self):
# for now, assume first class given must be primary class
return self.class_list[0]
def num_classes(self):
return len(self.class_list)
@property
def class_initialized(self):
return (self.num_classes > 0)
@property
def primary_class(self):
# for now, assume first class given must be primary class
if self.class_initialized:
return self.class_list[0]
else:
return None
@property
def weapon_proficiencies(self):
if not self.class_initialized:
return ()
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)
if self.race is not None:
wp |= set(getattr(self.race, 'weapon_proficiencies', ()))
if self.background is not None:
wp |= set(getattr(self.background, 'weapon_proficiencies', ()))
return wp
@property
def saving_throw_proficiencies(self):
return self.primary_class.saving_throw_proficiencies
if self.primary_class is not None:
return self.primary_class.saving_throw_proficiencies
else:
return ()
@property
def spellcasting_classes(self):
@@ -194,19 +235,24 @@ class Character():
The weapon to be tested for proficiency.
"""
all_proficiencies = tuple(self.weapon_proficiencies)
all_proficiencies += tuple(getattr(self.race, 'weapon_proficiencies',
tuple()))
all_proficiencies = self.weapon_proficiencies
is_proficient = any((isinstance(weapon, W) for W in all_proficiencies))
return is_proficient
@property
def proficiencies_text(self):
final_text = ""
all_proficiencies = self._proficiencies_text
all_proficiencies = set(self._proficiencies_text)
if self.class_initialized:
all_proficiencies |= set(self.primary_class._proficiencies_text)
if self.num_classes > 1:
for c in self.class_list[1:]:
all_proficiencies |= set(c._multiclass_proficiencies_text)
if self.race is not None:
all_proficiencies += self.race.proficiencies_text
all_proficiencies += self.proficiencies_extra
all_proficiencies |= set(self.race.proficiencies_text)
if self.background is not None:
all_proficiencies |= set(self.background.proficiencies_text)
all_proficiencies |= set(self.proficiencies_extra)
# Create a single string out of all the proficiencies
for txt in all_proficiencies:
if not final_text:
@@ -290,7 +336,8 @@ 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 f"{self.level}d{self.hit_dice_faces}"
return ' + '.join([f'{c.class_level}d{c.hit_dice_faces}'
for c in self.class_list])
@property
def proficiency_bonus(self):
@@ -333,6 +380,8 @@ class Character():
subclasses = char_props.pop('subclasses', [])
if isinstance(subclasses, str):
subclasses = [subclasses]
if len(subclasses) == 0:
subclasses = [None]*len(classes_levels)
assert len(classes_levels) == len(subclasses), (
'the length of classes_levels {:d} does not match length of '
'subclasses {:d}'.format(len(classes_levels), len(subclasses)))
+4 -3
View File
@@ -14,13 +14,14 @@ class CharClass():
class_name = ""
class_level = 1
hit_dice_faces = None
_proficiencies_text = ()
weapon_proficiencies = ()
_proficiencies_text = ()
multiclass_weapon_proficiencies = ()
_multiclass_proficiencies_text = ()
languages = ()
class_skill_choices = ()
num_skill_choices = 2
spellcasing_ability = None
spellcasting_ability = None
spell_slots_by_level = None
subclass = None
class_features_by_level = {lvl: () for lvl in range(1, 21)}
@@ -254,7 +255,7 @@ class Druid(CharClass):
max_cr = 1
max_swim = None
max_fly = None
# Make adjustments for moon cirlce druids
# Make adjustments for moon circle druids
if self.circle.lower() == "moon":
if 2 <= self.class_level < 6:
max_cr = 1
+82 -83
View File
@@ -58,12 +58,10 @@ PDFTK_CMD = 'pdftk'
def text_box(string):
"""Format a string for displaying in a text box."""
# Remove line breaks
# new_string = string.replace('\n', ' ').replace('\r', ' ')
new_string = string
# Remove multiple whitespace
# new_string = ' '.join(new_string.split())
new_string = new_string
# remove multiple whitespace without removing linebreaks
new_string = ' '.join(string.replace('\n', '\m').split())
# Remove *single* line breaks, swap *multi* line breaks to single (fdf: \r)
new_string = new_string.replace('\m \m', '\r').replace('\m\m', '\r').replace('\m', ' ')
return new_string
@@ -110,11 +108,11 @@ def create_latex_pdf(char, basename, template):
def create_spells_pdf(char, spell_class, basename, flatten=False):
class_level = (spell_class.class_name + ' ' + str(spell_class.level))
class_level = (spell_class.class_name + ' ' + str(spell_class.class_level))
spell_level = lambda x : (x or '')
fields = {
'Spellcasting Class 2': class_level,
'SpellcastingAbility 2': spell_class.spellcasting_ability.capitalize(),
'SpellcastingAbility 2': spell_class.spellcasting_ability.upper()[:3],
'SpellSaveDC 2': char.spell_save_dc(spell_class),
'SpellAtkBonus 2': mod_str(char.spell_attack_bonus(spell_class)),
# Number of spell slots
@@ -177,76 +175,76 @@ def create_spells_pdf(char, spell_class, basename, flatten=False):
def create_character_pdf(char, basename, flatten=False):
# Prepare the list of fields
class_level = ' / '.join([f'{c.class_name} {c.class_level}'
for c in char.class_list])
for c in char.class_list])
fields = {
# Character description
'CharacterName': character.name,
'CharacterName': char.name,
'ClassLevel': class_level,
'Background': str(character.background),
'PlayerName': character.player_name,
'Race ': str(character.race),
'Alignment': character.alignment,
'XP': str(character.xp),
'Background': str(char.background),
'PlayerName': char.player_name,
'Race ': str(char.race),
'Alignment': char.alignment,
'XP': str(char.xp),
# Abilities
'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,
'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,
# Saving throws (proficiencies handled later)
'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),
'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),
# Skills (proficiencies handled below)
'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.persuasian),
'Religion': mod_str(character.religion),
'SleightofHand': mod_str(character.sleight_of_hand),
'Stealth ': mod_str(character.stealth),
'Survival': mod_str(character.survival),
'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.persuasian),
'Religion': mod_str(char.religion),
'SleightofHand': mod_str(char.sleight_of_hand),
'Stealth ': mod_str(char.stealth),
'Survival': mod_str(char.survival),
# Hit points
'HDTotal': character.hit_dice,
'HPMax': str(character.hp_max),
'HDTotal': char.hit_dice,
'HPMax': str(char.hp_max),
# Personality traits and other features
'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_and_traits),
'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_and_traits),
# Inventory
'CP': character.cp,
'SP': character.sp,
'EP': character.ep,
'GP': character.gp,
'PP': character.pp,
'Equipment': text_box(character.equipment),
'CP': char.cp,
'SP': char.sp,
'EP': char.ep,
'GP': char.gp,
'PP': char.pp,
'Equipment': text_box(char.equipment),
}
# Check boxes for proficiencies
ST_boxes = {
@@ -257,7 +255,7 @@ def create_character_pdf(char, basename, flatten=False):
'wisdom': 'Check Box 21',
'charisma': 'Check Box 22',
}
for ability in character.saving_throw_proficiencies:
for ability in char.saving_throw_proficiencies:
fields[ST_boxes[ability]] = CHECKBOX_ON
# Add skill proficiencies
skill_boxes = {
@@ -280,7 +278,7 @@ def create_character_pdf(char, basename, flatten=False):
'stealth': 'Check Box 39',
'survival': 'Check Box 40',
}
for skill in character.skill_proficiencies:
for skill in char.skill_proficiencies:
try:
fields[skill_boxes[skill.replace(' ', '_').lower()]] = CHECKBOX_ON
except KeyError:
@@ -289,20 +287,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, character.weapons):
for _fields, weapon in zip(weapon_fields, char.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: {character.armor}'
attack_str += '\n\r'
attack_str += f'Shield: {character.shield}'
attack_str += character.attacks_and_spellcasting
attack_str = f'Armor: {char.armor}'
attack_str += '\n \n'
attack_str += f'Shield: {char.shield}'
attack_str += '\n \n'
attack_str += char.attacks_and_spellcasting
fields['AttacksSpellcasting'] = text_box(attack_str)
# Other proficiencies and languages
prof_text = "Proficiencies:\n" + text_box(character.proficiencies_text)
prof_text += "\n\nLanguages:\n" + text_box(character.languages)
prof_text = "Proficiencies:\n" + text_box(char.proficiencies_text)
prof_text += "\n\nLanguages:\n" + text_box(char.languages)
fields['ProficienciesLang'] = prof_text
# Prepare the actual PDF
dirname = os.path.dirname(os.path.abspath(__file__))
@@ -405,8 +404,8 @@ def _make_pdf_pdftk(fields, src_pdf, basename, flatten=False):
subprocess.call(popenargs)
# Clean up temporary files
os.remove(fdfname)
def make_sheet(character_file, char=None, flatten=False):
"""Prepare a PDF character sheet from the given character file.
@@ -426,7 +425,7 @@ def make_sheet(character_file, char=None, flatten=False):
char_base = os.path.splitext(character_file)[0] + '_char'
sheets = [char_base + '.pdf']
pages = []
char_pdf = create_character_pdf(character=char, basename=char_base,
char_pdf = create_character_pdf(char=char, basename=char_base,
flatten=flatten)
pages.append(char_pdf)
for spell_class in char.spellcasting_classes:
@@ -443,7 +442,7 @@ def make_sheet(character_file, char=None, flatten=False):
# Create spell book
spellbook_base = os.path.splitext(character_file)[0] + '_spellbook'
try:
create_spellbook_pdf(character=char, basename=spellbook_base)
create_spellbook_pdf(char=char, basename=spellbook_base)
except exceptions.LatexNotFoundError as e:
log.warning('``pdflatex`` not available. Skipping spellbook '
f'for {char.name}')
@@ -454,7 +453,7 @@ def make_sheet(character_file, char=None, flatten=False):
if len(wild_shapes) > 0:
shapes_base = os.path.splitext(character_file)[0] + '_wild_shapes'
try:
create_druid_shapes_pdf(character=char, basename=shapes_base)
create_druid_shapes_pdf(char=char, basename=shapes_base)
except exceptions.LatexNotFoundError as e:
log.warning('``pdflatex`` not available. Skipping wild shapes list '
f'for {char.name}')
Binary file not shown.
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
all: wizard druid rogue warlock
clobber:
rm -f wizard.pdf rogue.pdf warlock.pdf druid.pdf
redo: clobber all
wizard: wizard.pdf
rogue: rogue.pdf
warlock: warlock.pdf
druid: druid.pdf
wizard.pdf: wizard.py
makesheets wizard.py
rogue.pdf: rogue.py
makesheets rogue.py
warlock.pdf: warlock.py
makesheets warlock.py
druid.pdf: druid.py
makesheets druid.py
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+7 -7
View File
@@ -67,29 +67,29 @@ flaws = """Ill do just about anything to uncover historical secrets that
would add to my research."""
features_and_traits = (
"""Spellcasting Ability: Intelligence is your spellcasting ability for
"""*Spellcasting Ability: Intelligence is your spellcasting ability for
your spells. The saving throw DC to resist a spell you cast is
13. Your attack bonus when you make an attack with a spell is
+5. See the rulebook for rules on casting your spells.
Arcane Recovery: You can regain some of your magical energy by
*Arcane Recovery: You can regain some of your magical energy by
studying your spellbook. Once per day during a short rest, you can
choose to recover expended spell slots with a combined level equal
to or less than half your wizard level (rounded up).
Darkvision: You see in dim light within a 60-foot radius of you as
*Darkvision: You see in dim light within a 60-foot radius of you as
if it were bright light, and in darkness in that radius as if it
were dim light. You cant discern color in darkness, only shades
of gray.
Fey Ancestry: You have advantage on saving throws against being
*Fey Ancestry: You have advantage on saving throws against being
charmed, and magic cant put you to sleep.
Trance: Elves dont need to sleep. They meditate deeply, remaining
*Trance: Elves dont need to sleep. They meditate deeply, remaining
semiconscious, for 4 hours a day and gain the same benefit a human
does from 8 hours of sleep.
Shelter of the Faithful: As a servant of Oghma, you command the
*Shelter of the Faithful: As a servant of Oghma, you command the
respect of those who share your faith, and you can perform the
rites of Oghma. You and your companions can expect to receive free
healing and care at a temple, shrine, or other established
Binary file not shown.
+4 -2
View File
@@ -20,9 +20,11 @@ setup(name='dungeonsheets',
download_url = 'https://github.com/canismarko/dungeon-sheets/archive/master.zip',
packages=['dungeonsheets'],
package_data={
'dungeonsheets': ['blank-character-sheet-default.pdf', 'blank-spell-sheet-default.pdf',
'dungeonsheets': ['blank-character-sheet-default.pdf',
'blank-spell-sheet-default.pdf',
'spellbook_template.tex', '../VERSION',
'character_template.txt']
'character_template.txt',
'druid_shapes_template.tex']
},
install_requires=[
'fdfgen', 'npyscreen', 'jinja2', 'pdfrw',