diff --git a/dungeonsheets/character.py b/dungeonsheets/character.py index afe28ca..3b14de3 100644 --- a/dungeonsheets/character.py +++ b/dungeonsheets/character.py @@ -248,7 +248,7 @@ class Character(Entity): def clear(self): # reset class-defined items self.class_list = list() - self.weapons = list() + self._weapons = list() self.magic_items = list() self._saving_throw_proficiencies = tuple() self.other_weapon_proficiencies = tuple() @@ -850,8 +850,16 @@ class Character(Entity): warning_message=msg, ) # Save it to the array - self.weapons.append(ThisWeapon(wielder=self)) + self._weapons.append(ThisWeapon(wielder=self)) + @property + def weapons(self): + my_weapons = self._weapons.copy() + # Account for unarmed strike + if len(my_weapons) == 0 or hasattr(self, "Monk"): + my_weapons.append(weapons.Unarmed(wielder=self)) + return my_weapons + @property def hit_dice(self): """What type and how many dice to use for re-gaining hit points. diff --git a/dungeonsheets/classes/fighter.py b/dungeonsheets/classes/fighter.py index 354dc10..17c4237 100644 --- a/dungeonsheets/classes/fighter.py +++ b/dungeonsheets/classes/fighter.py @@ -16,7 +16,7 @@ class Champion(SubClass): name = "Champion" features_by_level = defaultdict(list) features_by_level[3] = [features.ImprovedCritical] - features_by_level[7] = [features.RemarkableAthelete] + features_by_level[7] = [features.RemarkableAthlete] features_by_level[10] = [features.AdditionalFightingStyle] features_by_level[15] = [features.SuperiorCritical] features_by_level[18] = [features.Survivor] diff --git a/dungeonsheets/entity.py b/dungeonsheets/entity.py index bcf7a12..a33b6da 100644 --- a/dungeonsheets/entity.py +++ b/dungeonsheets/entity.py @@ -70,7 +70,7 @@ class Entity(ABC): gp = 0 pp = 0 equipment = "" - weapons = list() + _weapons = list() magic_items = list() armor = None shield = None @@ -87,3 +87,26 @@ class Entity(ABC): def __init__(self): pass + + @property + def weapons(self): + return self._weapons.copy() + + @property + def passive_wisdom(self): + return self.perception.modifier + 10 + + @property + def abilities(self): + return [self.strength, self.dexterity, self.constitution, + self.intelligence, self.wisdom, self.charisma] + + @property + def skills(self): + return [self.acrobatics, self.animal_handling, self.arcana, + self.athletics, self.deception, self.history, + self.insight, self.intimidation, self.investigation, + self.medicine, self.nature, self.perception, + self.performance, self.persuasion, self.religion, + self.sleight_of_hand, self.stealth, self.survival,] + diff --git a/dungeonsheets/epub.py b/dungeonsheets/epub.py index 27442c4..be3c80f 100644 --- a/dungeonsheets/epub.py +++ b/dungeonsheets/epub.py @@ -267,6 +267,7 @@ def to_heading_id(inpt: str) -> str: return inpt.replace(" ", "-") + # Prepare the jinja environment jinja_env = jinja_environment() jinja_env.filters["rst_to_html"] = rst_to_html diff --git a/dungeonsheets/features/fighter.py b/dungeonsheets/features/fighter.py index a0fa3e2..2626dad 100644 --- a/dungeonsheets/features/fighter.py +++ b/dungeonsheets/features/fighter.py @@ -234,7 +234,7 @@ class ImprovedCritical(Feature): source = "Fighter (Champion)" -class RemarkableAthelete(Feature): +class RemarkableAthlete(Feature): """Starting at 7th level, you can add half your proficiency bonus (round up) to any Strength, Dexterity, or Constitution check you make that doesn't already use your proficiency bonus. @@ -244,7 +244,7 @@ class RemarkableAthelete(Feature): """ - name = "Remarkable Athelete" + name = "Remarkable Athlete" source = "Fighter (Champion)" diff --git a/dungeonsheets/fill_pdf_template.py b/dungeonsheets/fill_pdf_template.py index 838955c..1111d0e 100644 --- a/dungeonsheets/fill_pdf_template.py +++ b/dungeonsheets/fill_pdf_template.py @@ -57,7 +57,7 @@ def create_character_pdf_template(character, basename, flatten=False): "AC": str(character.armor_class), "Initiative": str(character.initiative), "Speed": str(character.speed), - "Passive": 10 + character.perception, + "Passive": character.passive_wisdom, # Saving throws (proficiencies handled later) "ST Strength": mod_str(character.strength.saving_throw), "ST Dexterity": mod_str(character.dexterity.saving_throw), @@ -66,24 +66,24 @@ def create_character_pdf_template(character, basename, flatten=False): "ST Wisdom": mod_str(character.wisdom.saving_throw), "ST Charisma": mod_str(character.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.persuasion), - "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(character.acrobatics.modifier), + "Animal": mod_str(character.animal_handling.modifier), + "Arcana": mod_str(character.arcana.modifier), + "Athletics": mod_str(character.athletics.modifier), + "Deception ": mod_str(character.deception.modifier), + "History ": mod_str(character.history.modifier), + "Insight": mod_str(character.insight.modifier), + "Intimidation": mod_str(character.intimidation.modifier), + "Investigation ": mod_str(character.investigation.modifier), + "Medicine": mod_str(character.medicine.modifier), + "Nature": mod_str(character.nature.modifier), + "Perception ": mod_str(character.perception.modifier), + "Performance": mod_str(character.performance.modifier), + "Persuasion": mod_str(character.persuasion.modifier), + "Religion": mod_str(character.religion.modifier), + "SleightofHand": mod_str(character.sleight_of_hand.modifier), + "Stealth ": mod_str(character.stealth.modifier), + "Survival": mod_str(character.survival.modifier), # Hit points "HDTotal": character.hit_dice, "HPMax": str(character.hp_max), @@ -150,8 +150,6 @@ def create_character_pdf_template(character, basename, flatten=False): ("Wpn Name 2", "Wpn2 AtkBonus ", "Wpn2 Damage "), ("Wpn Name 3", "Wpn3 AtkBonus ", "Wpn3 Damage "), ] - if len(character.weapons) == 0 or hasattr(character, "Monk"): - character.wield_weapon("unarmed") for _fields, weapon in zip(weapon_fields, character.weapons): name_field, atk_field, dmg_field = _fields fields[name_field] = weapon.name diff --git a/dungeonsheets/forms.py b/dungeonsheets/forms.py index 8cef656..0cae01a 100644 --- a/dungeonsheets/forms.py +++ b/dungeonsheets/forms.py @@ -3,7 +3,7 @@ import re from jinja2 import Environment, PackageLoader -from dungeonsheets.stats import mod_str +from dungeonsheets.stats import mod_str, ability_mod_str # A dice string, with optional backticks: ``1d6 + 3`` @@ -31,4 +31,5 @@ def jinja_environment(): variable_end_string="]]", ) jinja_env.filters["mod_str"] = mod_str + jinja_env.filters["ability_mod_str"] = ability_mod_str return jinja_env diff --git a/dungeonsheets/forms/background_template.html b/dungeonsheets/forms/background_template.html new file mode 100644 index 0000000..911ce62 --- /dev/null +++ b/dungeonsheets/forms/background_template.html @@ -0,0 +1 @@ +

Background

diff --git a/dungeonsheets/forms/character_sheet_template.html b/dungeonsheets/forms/character_sheet_template.html new file mode 100644 index 0000000..6b3f9ae --- /dev/null +++ b/dungeonsheets/forms/character_sheet_template.html @@ -0,0 +1,119 @@ +

Character Sheet

+ + +
+
Character Name
+
[[ character.name ]]
+
Class & Level
+
[[ character.classes_and_levels ]]
+
Background
+
[[ character.background ]]
+
Player Name
+
[[ character.player_name ]]
+
Race
+
[[ character.race ]]
+
Alignment
+
[[ character.alignment ]]
+
Experience Points
+
[[ character.xp ]]
+
Inspiration
+
[% if character.inspiration %]✓[% else %]–[% endif %]
+
+ +
+
Armor Class
+
[[ character.armor_class ]]
+
Initiative
+
[[ character.initiative ]]
+
Speed
+
[[ character.speed ]]
+
Passive Wisdom (Perception)
+
[[ character.passive_wisdom ]]
+
+ +
+
Hit Point Maximum
+
[[ character.hp_max ]]
+
Current Hit Points
+
[[ character.hp_current ]]
+
Temporary Hit Points
+
[% if character.hp_temp > 0 %][[ character.hp_temp ]][% endif %]
+
Hit Dice Total
+
[[ character.hit_dice ]]
+
+ + + + + + + + + [% for ability in character.abilities %] + + + + + + + [% endfor %] +
AbilityModSaving
Throw
[[ ability.name | capitalize ]][[ ability.modifier | mod_str ]] ([[ ability.value ]])[% if ability.name in character.saving_throw_proficiencies %]✓[% endif %][[ character.strength.saving_throw | mod_str ]]
+ + + + + + + [% for skill in character.skills %] + + + + + + [% endfor %] +
SkillMod
[[ skill ]][[ skill.modifier | mod_str ]] + [% if skill.is_expertise == 1 %]✓✓ + [% elif skill.is_proficient %]✓ + [% elif skill.is_remarkable_athlete %]◓ + [% elif skill.is_jack_of_all_trades %]◒ + [% endif %] +
+ + + + + + + + [% for weapon in character.weapons %] + + + + + + [% endfor %] +
NameAtk BonusDamage/Type
[[ weapon.name ]][[ weapon.attack_modifier | mod_str ]][[ weapon.damage ]] / [[ weapon.damage_type ]]
+ +
+
Proficiences
+
[[ character.proficiencies_text ]]
+
Languages
+
[[ character.languages ]]
+
+ +

Inventory

+ diff --git a/dungeonsheets/forms/dungeonsheets_epub.css b/dungeonsheets/forms/dungeonsheets_epub.css index 2038e7f..cf6d59d 100644 --- a/dungeonsheets/forms/dungeonsheets_epub.css +++ b/dungeonsheets/forms/dungeonsheets_epub.css @@ -3,6 +3,29 @@ h1, h2, h3, h4, h5, h6 { } /* End fancy decorations */ + +/* Dictionary lists for showing stats, etc */ +dt { + float: left; + clear: left; + text-align: right; + font-weight: bold; +} +dt::after { + content: ":"; +} +dd { + padding: 0 0 0.5em 0; +} + +dl.character-details dt { + width: 200px; +} +dl.character-details dd { + width: 200px; + margin: 0 0 0 210px; +} + .known-beast-disabled { color: lightgrey; } diff --git a/dungeonsheets/forms/party_summary_template.html b/dungeonsheets/forms/party_summary_template.html index 511d3c8..3f8ff24 100644 --- a/dungeonsheets/forms/party_summary_template.html +++ b/dungeonsheets/forms/party_summary_template.html @@ -44,7 +44,7 @@ [[ member.name[:28] ]] [[ member.armor_class ]] - [[ member.perception + 10 ]] + [[ member.perception.modifier + 10 ]] [% for class in member.class_list %] [% if class.spellcasting_ability %] [[ member.spell_save_dc(class) ]], [% endif %] [% endfor %] diff --git a/dungeonsheets/forms/party_summary_template.tex b/dungeonsheets/forms/party_summary_template.tex index fbebc2b..fa25c81 100644 --- a/dungeonsheets/forms/party_summary_template.tex +++ b/dungeonsheets/forms/party_summary_template.tex @@ -29,7 +29,7 @@ [% for member in party %] [[ member.name[:28] ]] & [[ member.armor_class ]] - & [[ member.perception + 10 ]] + & [[ member.perception.modifier + 10 ]] & [% for class in member.class_list %] [% if class.spellcasting_ability %] [[ member.spell_save_dc(class) ]], [% endif %] [% endfor %] @@ -57,7 +57,7 @@ [% for member in party %] [[ member.name[:28] ]] & [[ member.armor_class ]] - & [[ member.perception + 10 ]] + & [[ member.perception.modifier + 10 ]] & [% for class in member.class_list %] [% if class.spellcasting_ability %] [[ member.spell_save_dc(class) ]], [% endif %] [% endfor %] diff --git a/dungeonsheets/forms/spell_list_template.html b/dungeonsheets/forms/spell_list_template.html new file mode 100644 index 0000000..1c9765f --- /dev/null +++ b/dungeonsheets/forms/spell_list_template.html @@ -0,0 +1 @@ +

Spell List

diff --git a/dungeonsheets/make_sheets.py b/dungeonsheets/make_sheets.py index 040a0ec..f5d1308 100755 --- a/dungeonsheets/make_sheets.py +++ b/dungeonsheets/make_sheets.py @@ -9,7 +9,7 @@ import re from pathlib import Path from multiprocessing import Pool, cpu_count from itertools import product -from typing import Union, Sequence, Optional +from typing import Union, Sequence, Optional, Literal, List from dungeonsheets import ( character as _char, @@ -70,6 +70,9 @@ class CharacterRenderer(): return template.render(character=character, use_dnd_decorations=use_dnd_decorations, ordinals=ORDINALS) +create_character_sheet_content = CharacterRenderer("character_sheet_template.{suffix}") +create_spell_list_content = CharacterRenderer("spell_list_template.{suffix}") +create_background_content = CharacterRenderer("background_template.{suffix}") create_subclasses_content = CharacterRenderer("subclasses_template.{suffix}") create_features_content = CharacterRenderer("features_template.{suffix}") @@ -192,7 +195,7 @@ def make_gm_sheet( gm_file = Path(gm_file) basename = gm_file.stem gm_props = readers.read_sheet_file(gm_file) - session_title = gm_props.get("session_title", f"GM Notes: {basename}") + session_title = gm_props.pop("session_title", f"GM Notes: {basename}") # Create the intro tex content_suffix = format_suffixes[output_format] content = [ @@ -295,6 +298,96 @@ def make_gm_sheet( ) +def make_character_content( + character: Character, + content_format: Literal["tex", "html"], + fancy_decorations: bool = False,) -> List[str]: + """Prepare the inner content for a character sheet. + + This will produce a fully renderable document, suitable for + passing to routines in either the ``epub`` or ``latex`` + modules. If *content_format* is ``"html"``, the returned content + is just the portion that would be found inside the + ```` tag. + + If *content_format* is ``"tex"``, the content returned will not + include the character, spell list, or biography sheets, since + these are currently processed through fillable PDFs. + + Parameters + ---------- + character + The character to render content for. + content_format + Which markup syntax to use. + fancy_decorations + Use fancy page layout and decorations for extra sheets, namely + the dnd style file for *tex*, or extended CSS for *html*. + + Returns + ------- + content + The list of rendered character sheet contents for *character* in + markup format *content_format*. + + """ + # Preamble, empty for HTML + content = [ + jinja_env.get_template(f"preamble.{content_format}").render( + use_dnd_decorations=fancy_decorations, + title="Features, Magical Items and Spells", + ) + ] + # Make the character sheet, and background pages if producing HTML + if content_format != "tex": + content.append(create_character_sheet_content(character, + content_suffix=content_format, + use_dnd_decorations=fancy_decorations)) + content.append(create_spell_list_content(character, + content_suffix=content_format, + use_dnd_decorations=fancy_decorations) + ) + content.append(create_background_content(character, + content_suffix=content_format, + use_dnd_decorations=fancy_decorations) + ) + # Create a list of subcasses, features, spells, etc + if character.subclasses: + content.append(create_subclasses_content(character, + content_suffix=content_format, + use_dnd_decorations=fancy_decorations) + ) + if character.features: + content.append( + create_features_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) + ) + if character.magic_items: + content.append( + create_magic_items_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) + ) + if character.is_spellcaster: + content.append( + create_spellbook_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) + ) + if len(getattr(character, "infusions", [])) > 0: + content.append( + create_infusions_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) + ) + + # Create a list of Druid wild_shapes + if len(getattr(character, "all_wild_shapes", [])) > 0: + content.append( + create_druid_shapes_content(character, content_suffix=content_format, use_dnd_decorations=fancy_decorations) + ) + # Postamble, empty for HTML + content.append( + jinja_env.get_template(f"postamble.{content_format}").render( + use_dnd_decorations=fancy_decorations + ) + ) + return content + + def make_character_sheet( char_file: Union[str, Path], character: Optional[Character] = None, @@ -336,12 +429,6 @@ def make_character_sheet( pages = [] # Prepare the tex/html content content_suffix = format_suffixes[output_format] - content = [ - jinja_env.get_template(f"preamble.{content_suffix}").render( - use_dnd_decorations=fancy_decorations, - title="Features, Magical Items and Spells", - ) - ] # Start of PDF gen char_pdf = create_character_pdf_template( character=character, basename=char_base, flatten=flatten @@ -360,43 +447,10 @@ def make_character_sheet( sheets.append(spell_base + ".pdf") # end of PDF gen features_base = "{:s}_features".format(basename) - # Create a list of subcasses - if character.subclasses: - content.append( create_subclasses_content(character, - content_suffix=content_suffix, - use_dnd_decorations=fancy_decorations) ) # Create a list of features and magic items - if character.features: - content.append( - create_features_content(character, content_suffix=content_suffix, use_dnd_decorations=fancy_decorations) - ) - if character.magic_items: - content.append( - create_magic_items_content(character, content_suffix=content_suffix, use_dnd_decorations=fancy_decorations) - ) - # Create a list of spells - if character.is_spellcaster: - content.append( - create_spellbook_content(character, content_suffix=content_suffix, use_dnd_decorations=fancy_decorations) - ) - - # Create a list of Artificer infusions - if getattr(character, "infusions", []): - content.append( - create_infusions_content(character, content_suffix=content_suffix, use_dnd_decorations=fancy_decorations) - ) - - # Create a list of Druid wild_shapes - if getattr(character, "wild_shapes", []): - content.append( - create_druid_shapes_content(character, content_suffix=content_suffix, use_dnd_decorations=fancy_decorations) - ) - - content.append( - jinja_env.get_template(f"postamble.{content_suffix}").render( - use_dnd_decorations=fancy_decorations - ) - ) + content = make_character_content(character=character, + content_format=content_suffix, + fancy_decorations=fancy_decorations) # Typeset combined LaTeX file if output_format == "pdf": try: diff --git a/dungeonsheets/stats.py b/dungeonsheets/stats.py index 465e9b5..442f410 100644 --- a/dungeonsheets/stats.py +++ b/dungeonsheets/stats.py @@ -17,7 +17,7 @@ from dungeonsheets.features import ( NaturalExplorerRevised, QuickDraw, RakishAudacity, - RemarkableAthelete, + RemarkableAthlete, SeaSoul, SoulOfTheForge, SuperiorMobility, @@ -35,7 +35,11 @@ def mod_str(modifier): return "{:+d}".format(modifier) -AbilityScore = namedtuple("AbilityScore", ("value", "modifier", "saving_throw")) +def ability_mod_str(character, ability): + return mod_str(getattr(character, ability).modifier) + + +AbilityScore = namedtuple("AbilityScore", ("value", "modifier", "saving_throw", "name")) class Ability: @@ -68,7 +72,7 @@ class Ability: if is_proficient: saving_throw += entity.proficiency_bonus # Create the named tuple - value = AbilityScore(modifier=modifier, value=score, saving_throw=saving_throw) + value = AbilityScore(modifier=modifier, value=score, saving_throw=saving_throw, name=self.ability_name) return value def __set__(self, entity, val): @@ -78,38 +82,98 @@ class Ability: class Skill: - """An ability-based skill, such as athletics.""" + """An ability-based skill, such as athletics. + Attributes + ---------- + ability_name: + The name of the ability, as a python-compatible string. + skill_name: + The name of the base ability that determines the skill + modifier. + is_proficient + Bool that describes if the owner is proficient in this skill. + is_expertise + Bool that describes if the owner has expertise in this skill. + is_jack_of_all_trades + Bool that describes if this skill benefits from Jack of All + Trades feature (False if already proficient). + is_remarkable_athlete + Bool that describes if this skill benefits from Remarkable + Athlete feature (False if already proficient). + modifier + The base ability modifier, after relevant proficiency bonuses + have been applied. + proficiency_modifier + The bonus that is applied to the base ability. Usually the same + as the owner's proficiency bonus if ``is_proficient`` is True, + but can be different based on class features., + + """ + + ability_name = "" + skill_name = "" + entity = None + def __init__(self, ability): self.ability_name = ability - def __set_name__(self, entity, name): + def __set_name__(self, owner, name): self.skill_name = name.lower().replace("_", " ") - self.character = entity def __get__(self, entity, owner): - log.debug("Getting skill '%s' for '%s'", self.skill_name, entity.name) - ability = getattr(entity, self.ability_name) - modifier = ability.modifier - # Check for proficiency - proficiencies = [p.replace("_", " ") for p in entity.skill_proficiencies] - is_proficient = self.skill_name in proficiencies - log.debug( - "%s is proficient in %s: %s", entity.name, self.skill_name, is_proficient - ) - if is_proficient: - modifier += entity.proficiency_bonus - elif entity.has_feature(JackOfAllTrades): - modifier += entity.proficiency_bonus // 2 - elif entity.has_feature(RemarkableAthelete): - if self.ability_name.lower() in ("strength", "dexterity", "constitution"): - modifier += ceil(entity.proficiency_bonus / 2.0) + self.entity = entity + return self + def __str__(self): + return self.skill_name.title() + + @property + def is_remarkable_athlete(self): + already_proficient = (self.is_proficient or self.is_expertise) + if self.entity.has_feature(RemarkableAthlete) and not already_proficient: + return True + else: + return False + + @property + def is_jack_of_all_trades(self): + already_proficient = (self.is_proficient or self.is_expertise or self.is_remarkable_athlete) + if self.entity.has_feature(JackOfAllTrades) and not already_proficient: + return True + else: + return False + + @property + def is_proficient(self): + # Check for proficiency + proficiencies = [p.replace("_", " ") for p in self.entity.skill_proficiencies] + is_proficient = self.skill_name in proficiencies + return is_proficient + + @property + def is_expertise(self): + return self.skill_name in self.entity.skill_expertise + + @property + def proficiency_modifier(self): + modifier = 0 + if self.is_proficient: + modifier += self.entity.proficiency_bonus + if self.is_remarkable_athlete: + modifier += ceil(self.entity.proficiency_bonus / 2.0) + if self.is_jack_of_all_trades: + modifier += self.entity.proficiency_bonus // 2 # Check for expertise - is_expert = self.skill_name in entity.skill_expertise - if is_expert: - modifier += entity.proficiency_bonus - log.info("'%s' modifier for '%s': %d", self.skill_name, entity.name, modifier) + if self.is_expertise: + modifier += self.entity.proficiency_bonus + return modifier + + @property + def modifier(self): + ability = getattr(self.entity, self.ability_name) + modifier = ability.modifier + self.proficiency_modifier + log.info("%s modifier for '%s': %d", self, self.entity.name, modifier) return modifier diff --git a/tests/test_make_sheets.py b/tests/test_make_sheets.py index 800f727..73ff1ca 100644 --- a/tests/test_make_sheets.py +++ b/tests/test_make_sheets.py @@ -56,11 +56,40 @@ class EpubOutputTestCase(unittest.TestCase): gm_epub = Path(f"{GMFILE.stem}.epub").resolve() char_epub = Path(f"{CHARFILE.stem}.epub").resolve() + def new_character(self): + char = character.Character( + name="Dr. Who", + classes=["Monk", "Druid", "Artificer"], + levels=[1, 1, 1], + subclasses=["way of the open hand", None, None], + magic_items=["cloak of protection"], + spells=["invisibility"], + wild_shapes=["crocodile"], + infusions=["boots of the winding path"] + ) + return char + def tearDown(self): for f in [self.gm_epub, self.char_epub]: if f.exists(): f.unlink() + def test_character_html_content(self): + my_char = self.new_character() + html = make_sheets.make_character_content(character=my_char, + content_format="html") + html = "".join(html) + # Make sure the various sections get rendered + self.assertIn("Subclasses", html) + self.assertIn("Features", html) + self.assertIn("Magic Items", html) + self.assertIn("Spells", html) + self.assertIn("Infusions", html) + self.assertIn("Known Beasts", html) + # Check the character sheet + self.assertIn("Character Sheet", html) + self.assertIn("Dr. Who", html) + def test_gm_file_created(self): # Check that a file is created once the function is run make_sheets.make_gm_sheet(gm_file=GMFILE, output_format="epub") diff --git a/tests/test_stats.py b/tests/test_stats.py index a99d632..7ecfe61 100644 --- a/tests/test_stats.py +++ b/tests/test_stats.py @@ -77,10 +77,14 @@ class TestStats(TestCase): proficiency_bonus = 2 my_class = MyClass() - self.assertEqual(my_class.acrobatics, 2) + self.assertEqual(str(my_class.acrobatics), "Acrobatics") + self.assertEqual(my_class.acrobatics.modifier, 2) + self.assertEqual(str(my_class.sleight_of_hand), "Sleight Of Hand") # Check for a proficiency my_class.skill_proficiencies = ["acrobatics"] - self.assertEqual(my_class.acrobatics, 4) + self.assertTrue(my_class.acrobatics.is_proficient) + self.assertEqual(my_class.acrobatics.proficiency_modifier, 2) + self.assertEqual(my_class.acrobatics.modifier, 4) # Check for a proficiency with spaces in the name my_class.skill_proficiencies = ["sleight_of_hand"] - self.assertEqual(my_class.sleight_of_hand, 4) + self.assertEqual(my_class.sleight_of_hand.modifier, 4)