Added spell list to the epub, and cleaned up dict-list style sheet.

This commit is contained in:
Mark Wolfman
2021-07-11 12:35:22 -05:00
parent 8be06d2ffe
commit 88bdd59fdd
18 changed files with 304 additions and 120 deletions
+17 -3
View File
@@ -36,7 +36,21 @@ def create_epub(
book.set_language("en")
# Add the css files
css_template = jinja_env.get_template("dungeonsheets_epub.css")
style = css_template.render(use_dnd_decorations=use_dnd_decorations)
dl_widths = { # Width for dl lists, in 'em' units
"character-details": 11,
"combat-stats": 18,
"proficiencies": 8.5,
"faction": 6,
"spellcasting": 12.5,
"spell-slots": 8,
"spell-details": 10,
"beast-stats": 9,
"feature-details": 5.5,
"infusion-details": 8.5,
"magic-item-details": 13.5,
"monster-details": 15,
}
style = css_template.render(use_dnd_decorations=use_dnd_decorations, dl_widths=dl_widths)
css = epub.EpubItem(
uid="style_default",
file_name="style/gm_sheet.css",
@@ -163,7 +177,7 @@ def toc_from_headings(
# Add a leaf or branch depending on the heading structure
if is_leaf:
parent_section[1].append(
epub.Link(href=href, title=heading["title"], uid=href)
epub.Link(href=href, title=heading["title"], uid=href.replace("#", ":"))
)
else:
new_section = (epub.Section(href=href, title=heading["title"]), [])
@@ -264,7 +278,7 @@ def rst_to_html(rst, top_heading_level=0):
def to_heading_id(inpt: str) -> str:
"""Take a string and make it suitable for use as an HTML header id."""
return inpt.replace(" ", "-")
return inpt.replace(" ", "-").replace("'", "")
+2 -1
View File
@@ -3,7 +3,7 @@ import re
from jinja2 import Environment, PackageLoader
from dungeonsheets.stats import mod_str, ability_mod_str
from dungeonsheets.stats import mod_str, ability_mod_str, stat_abbreviation
# A dice string, with optional backticks: ``1d6 + 3``
@@ -32,4 +32,5 @@ def jinja_environment():
)
jinja_env.filters["mod_str"] = mod_str
jinja_env.filters["ability_mod_str"] = ability_mod_str
jinja_env.filters["stat_abbreviation"] = stat_abbreviation
return jinja_env
@@ -1 +0,0 @@
<h1 class="background">Background</h1>
@@ -1,7 +1,8 @@
<h1 class="character-sheet">Character Sheet</h1>
<h1 id="character-sheet">Character Sheet</h1>
<h2 id="basic-stats">Basic Statistics</h2>
<!-- Identity -->
<dl class="character-details">
<dl class="character-details details">
<dt>Character Name</dt>
<dd>[[ character.name ]]</dd>
<dt>Class &amp; Level</dt>
@@ -18,31 +19,60 @@
<dd>[[ character.xp ]]</dd>
<dt>Inspiration</dt>
<dd>[% if character.inspiration %]✓[% else %]&ndash;[% endif %]</dd>
<dt>Age</dt>
<dd>[[ character.age ]]&nbsp;</dd>
<dt>Height</dt>
<dd>[[ character.height ]]&nbsp;</dd>
<dt>Weight</dt>
<dd>[[ character.weight ]]&nbsp;</dd>
<dt>Eyes</dt>
<dd>[[ character.eyes ]]&nbsp;</dd>
<dt>Skin</dt>
<dd>[[ character.skin ]]&nbsp;</dd>
<dt>Hair</dt>
<dd>[[ character.hair ]]&nbsp;</dd>
</dl>
<h2 id="combat-stats details">Combat Statistics</h2>
<dl class="combat-stats">
<dt>Armor Class</dt>
<dd>[[ character.armor_class ]]</dd>
<dt>Initiative</dt>
<dd>[[ character.initiative ]]</dd>
<dt>Speed</dt>
<dd>[[ character.speed ]]</dd>
<dd>[[ character.speed ]] ft</dd>
<dt>Passive Wisdom (Perception)</dt>
<dd>[[ character.passive_wisdom ]]</dd>
</dl>
<dl class="hit-points">
<dt>Hit Point Maximum</dt>
<dd>[[ character.hp_max ]]</dd>
<dt>Current Hit Points</dt>
<dd>[[ character.hp_current ]]</dd>
<dd>[[ character.hp_current ]]&nbsp;</dd>
<dt>Temporary Hit Points</dt>
<dd>[% if character.hp_temp > 0 %][[ character.hp_temp ]][% endif %]</dd>
<dd>[% if character.hp_temp > 0 %][[ character.hp_temp ]][% endif %]&nbsp;</dd>
<dt>Hit Dice Total</dt>
<dd>[[ character.hit_dice ]]</dd>
</dl>
<table class="attacks-and-spellcasting">
<tr>
<th>Name</th>
<th>Atk Bonus</th>
<th>Damage/Type</th>
</tr>
[% for weapon in character.weapons %]
<tr>
<td>[[ weapon.name ]]</td>
<td>[[ weapon.attack_modifier | mod_str ]]</td>
<td>[[ weapon.damage ]] / [[ weapon.damage_type ]]</td>
</tr>
[% endfor %]
</table>
<!-- Character abilities, saving throws and skill modifiers -->
<h2 id="abilitities">Abilities, Savings Throws, and Skills</h2>
<p>(✓ = proficient)</p>
<table class="character-abilities">
<tr>
<th>Ability</th>
@@ -63,6 +93,7 @@
<tr>
<th>Skill</th>
<th>Mod</th>
<th>&nbsp;</th>
</tr>
[% for skill in character.skills %]
<tr>
@@ -79,28 +110,15 @@
[% endfor %]
</table>
<table class="attacks-and-spellcasting">
<tr>
<th>Name</th>
<th>Atk Bonus</th>
<th>Damage/Type</th>
</tr>
[% for weapon in character.weapons %]
<tr>
<td>[[ weapon.name ]]</td>
<td>[[ weapon.attack_modifier | mod_str ]]</td>
<td>[[ weapon.damage ]] / [[ weapon.damage_type ]]</td>
</tr>
[% endfor %]
</table>
<dl class="proficiences">
<dt>Proficiences</dt>
<h2 id="proficiencies">Proficiencies</h2>
<dl class="proficiencies details">
<dt>Proficiencies</dt>
<dd>[[ character.proficiencies_text ]]</dd>
<dt>Languages</dt>
<dd>[[ character.languages ]]</dd>
</dl>
<h2 id="inventory">Inventory</h2>
<ul class="inventory">
<li>[[ character.cp ]] CP</li>
@@ -117,3 +135,58 @@
<li>[[ item ]]</li>
[% endfor %]
</ul>
<h3 id="treasure">Additional Treasure</h3>
<block>
[[ character.treasure ]]
</block>
<h2 id="personaility-traits">Personality Traits</h2>
<block>
[[ character.personality_traits ]]
</block>
<h2 id="ideals">Ideals</h2>
<block>
[[ character.ideals ]]
</block>
<h2 id="bonds">Bonds</h2>
<block>
[[ character.bonds ]]
</block>
<h2 id="flaws">Flaws</h2>
<block>
[[ character.flaws ]]
</block>
<h2 id="appearance">Appearance</h2>
<h2 id="allies">Allies &amp; Organizations</h2>
<dl class="faction details">
<dt>Faction</dt>
<dd>[[ character.faction_name ]]&nbsp;</dd>
</dl>
<block>
[[ character.allies ]]
</block>
<h2 id="other-traits">Additional Features &amp; Traits</h2>
<block>
[[ character.other_feats_traits ]]
</block>
<h2 id="backstory">Backstory</h2>
<block>
[[ character.backstory ]]
</block>
@@ -37,15 +37,17 @@
</tr>
</table>
<dl>
<dt>Skills:</dt><dd>[[ shape.skills ]]</dd>
<dt>Senses:</dt><dd>[[ shape.senses ]]</dd>
<dt>Languages:</dt><dd>[[ shape.languages ]]</dd>
<dt>Resistance:</dt><dd>[[ shape.damage_resistance ]]</dd>
<dt>Immunities:</dt><dd>[[ shape.condition_immunities ]]</dd>
<dl class="beast-stats details">
<dt>Skills</dt><dd>[[ shape.skills ]]&nbsp;</dd>
<dt>Senses</dt><dd>[[ shape.senses ]]&nbsp;</dd>
<dt>Languages</dt><dd>[[ shape.languages ]]&nbsp;</dd>
<dt>Resistances</dt><dd>[[ shape.damage_resistance ]]&nbsp;</dd>
<dt>Immunities</dt><dd>[[ shape.condition_immunities ]]&nbsp;</dd>
</dl>
<p>[[ shape.__doc__ | rst_to_html(top_heading_level=2) ]]</p>
<block>
[[ shape.__doc__ | rst_to_html(top_heading_level=2) ]]
</block>
[% endfor %]
+54 -19
View File
@@ -4,41 +4,83 @@ h1, h2, h3, h4, h5, h6 {
/* End fancy decorations */
/* Spell lists */
dl.spell-slots {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
h3.spell-list-level {
margin-bottom: 0.5em;
}
ul.spell-list {
list-style-type: none;
padding-left: 1em;
margin: 0;
}
ul.spell-list li {
overflow: visible;
padding-left: 17px;
position: relative;
}
ul.spell-list a {
/* Make spell list entries not look like links */
color: inherit;
text-decoration: inherit;
}
ul.spell-list li.prepared:before {
content: '\2713';
position: absolute;
left: 0;
}
/* Spell descriptions */
.spell-school {
font-style: italic;
}
/* Dictionary lists for showing stats, etc */
dt {
dl.details dt {
float: left;
clear: left;
text-align: right;
font-weight: bold;
}
dt::after {
dl.details dt::after {
content: ":";
}
dd {
dl.details dd {
padding: 0 0 0.5em 0;
}
dl.character-details dt {
width: 200px;
[% for class, width in dl_widths.items() %]
dl.[[ class ]] dt {
width: [[ width ]]em;
}
dl.character-details dd {
width: 200px;
margin: 0 0 0 210px;
dl.[[ class ]] dd {
width: auto;
margin: 0 0 0 [[ width + 0.5 ]]em;
}
[% endfor %]
/* For showing beasts */
.known-beast-disabled {
color: lightgrey;
}
/* Errors in rst conversion or missing content */
.not-implemented {
font-weight: bold;
color: darkred;
background: pink;
}
.spell-school {
font-style: italic;
div.system-message {
background: pink;
border-color: darkred;
border-style: solid;
border-width: 2px;
color: darkred;
}
/* General formatting */
table {
margin-bottom: 10px;
}
@@ -66,13 +108,6 @@ dd > p {
margin-top: 0px;
}
div.system-message {
background: pink;
border-color: darkred;
border-style: solid;
border-width: 2px;
color: darkred;
}
.literal {
font-family: monospace;
}
+2 -2
View File
@@ -3,8 +3,8 @@
[% for feat in character.features %]
<h2 id="features-[[ feat.name | to_heading_id ]]">[[ feat.name ]]</h2>
<dl>
<dt>Source:</dt>
<dl class="feature-details details">
<dt>Source</dt>
<dd>[[ feat.source ]]</dd>
</dl>
+3 -3
View File
@@ -3,13 +3,13 @@
[% for inf in character.infusions %]
<h2 id="infusions-[[ inf.name | to_heading_id ]]">[[ inf.name ]]</h2>
<dl>
<dl class="infusion-details details">
[% if inf.prerequisite %]
<dt>Prerequisite:</dt>
<dt>Prerequisite</dt>
<dd>[[ inf.prerequisite ]]</dd>
[% endif %]
[% if inf.item %]
<dt>Item:</dt>
<dt>Item</dt>
<dd>[[ inf.item ]]</dd>
[% endif %]
</dl>
@@ -3,10 +3,10 @@
[% for mitem in character.magic_items %]
<h2 id="magic-items-[[ mitem.name | to_heading_id ]]">[[ mitem.name ]]</h2>
<dl>
<dt>Requires Attunement:</dt>
<dl class="magic-item-details details">
<dt>Requires Attunement</dt>
<dd>[[ mitem.requires_attunement ]]</dd>
<dt>Rarity:</dt>
<dt>Rarity</dt>
<dd>[[ mitem.rarity ]]</dd>
</dl>
+10 -10
View File
@@ -52,16 +52,16 @@
</tr>
</table>
<dl>
[% if monster.skills != "" %]<dt>Skills:</dt><dd>[[ monster.skills ]]</dd>[% endif %]
<dt>Senses:</dt><dd>[% if monster.senses != "" %][[ monster.senses ]][% else %]--[% endif %]</dd>
<dt>Languages:</dt><dd>[% if monster.languages != "" %][[ monster.languages ]][% else %]--[% endif %]</dd>
[% if monster.damage_resistances != "" %]<dt>Damage Resistances:</dt><dd>[[ monster.damage_resistances ]]</dd>[% endif %]
[% if monster.damage_immunities != "" %]<dt>Damage Immunities:</dt><dd>[[ monster.damage_immunities ]]</dd>[% endif %]
[% if monster.damage_vulnerabilities != "" %]<dt>Damage Vulnerabilities:</dt><dd>[[ monster.damage_vulnerabilities ]]</dd>[% endif %]
[% if monster.condition_immunities != "" %]<dt>Condition Immunuties:</dt><dd>[[ monster.condition_immunities ]]</dd>[% endif %]
[% if monster.saving_throws != "" %]<dt>Saving Throws:</dt><dd>[[ monster.saving_throws ]]</dd>[% endif %]
<dt>Challenge:<dd>[[ monster.challenge_rating ]]</dd>
<dl class="monster-details details">
[% if monster.skills != "" %]<dt>Skills</dt><dd>[[ monster.skills ]]</dd>[% endif %]
<dt>Senses</dt><dd>[% if monster.senses != "" %][[ monster.senses ]][% else %]--[% endif %]</dd>
<dt>Languages</dt><dd>[% if monster.languages != "" %][[ monster.languages ]][% else %]--[% endif %]</dd>
[% if monster.damage_resistances != "" %]<dt>Damage Resistances</dt><dd>[[ monster.damage_resistances ]]</dd>[% endif %]
[% if monster.damage_immunities != "" %]<dt>Damage Immunities</dt><dd>[[ monster.damage_immunities ]]</dd>[% endif %]
[% if monster.damage_vulnerabilities != "" %]<dt>Damage Vulnerabilities</dt><dd>[[ monster.damage_vulnerabilities ]]</dd>[% endif %]
[% if monster.condition_immunities != "" %]<dt>Condition Immunuties</dt><dd>[[ monster.condition_immunities ]]</dd>[% endif %]
[% if monster.saving_throws != "" %]<dt>Saving Throws</dt><dd>[[ monster.saving_throws ]]</dd>[% endif %]
<dt>Challenge<dd>[[ monster.challenge_rating ]]&nbsp;</dd>
</dl>
@@ -332,8 +332,8 @@
</table>
<dl>
<dt>*Chicken:</dt>
<dl class="random-table-definitions">
<dt>*Chicken</dt>
<dd>Raven stats with Advantage on checks to wake
up targets instead of mimicry</dd>
</dl>
@@ -427,14 +427,14 @@
</tr>
</table>
<dl>
<dl class="random-table-definitions">
<dt>*Lemur</dt>
<dd>Weasel stats with a common Climb speed instead of a
bite attack</dd>
<dt>**Newt:</dt>
<dt>**Newt</dt>
<dd>Lizard stats with Amphibious instead of a bite
attack</dd>
<dt>***Octopus, Cascadian Tree:</dt>
<dt>***Octopus, Cascadian Tree</dt>
<dd>Octopus stats with Amphibious
and a 10 ft land speed instead of camouflage</dd>
</dl>
@@ -528,11 +528,11 @@
</tr>
</table>
<dl>
<dt>*Shocker Lizard:</dt>
<dl class="random-table-definitions">
<dt>*Shocker Lizard</dt>
<dd>Lizard stats with Static Electricity ranged attack of 1d6
Electricity damage Close/Medium.</dd>
<dt>**Turtle:</dt>
<dt>**Turtle</dt>
<dd>Lizard stats with 14 natural armor and no climb speed.</dd>
</dl>
@@ -1 +0,0 @@
<h1 class="spell-list">Spell List</h1>
+68 -5
View File
@@ -1,5 +1,68 @@
<h1 id="spells">Spells</h1>
<dl class="spellcasting details">
<dt>Spellcasting Class</dt>
<dd>
[% for spell_class in character.spellcasting_classes %]
[% if not loop.first %] / [% endif %]
[[ spell_class.name ]] [[ spell_class.level ]]
[% endfor %]
</dd>
<dt>Spellcasting Abilitiy</dt>
<dd>
[% for spell_class in character.spellcasting_classes %]
[% if not loop.first %] / [% endif %]
[[ spell_class.spellcasting_ability | stat_abbreviation ]]
[% endfor %]
</dd>
<dt>Spell Save DC</dt>
<dd>
[% for spell_class in character.spellcasting_classes %]
[% if not loop.first %] / [% endif %]
[% set spell_save_dc = character.spell_save_dc(spell_class) %]
[[ spell_save_dc ]]
[% endfor %]
</dd>
<dt>Spell Attack Bonus</dt>
<dd>
[% for spell_class in character.spellcasting_classes %]
[% if not loop.first %] / [% endif %]
[% set spell_atk_bonus = character.spell_attack_bonus(spell_class) %]
[[ spell_atk_bonus | mod_str ]]
[% endfor %]
</dd>
</dl>
<!-- List of spells by level -->
<h2 id="spell-list">List of Spells</h2>
<p>(✓ = prepared)</p>
[% for level, spells in character.spells | groupby("level") %]
<h3 id="level-[[ level ]]-spells" class="spell-list-level">[% if level == 0 %]Cantrips[% else %]Level [[ level ]] Spells[% endif %]</h3>
[% if level > 0 %]
<!-- Number of spell slots (except for cantrips) -->
[% set spell_slots = character.spell_slots(level) %]
<dl class="spell-slots details">
<dt>Spell slots</dt>
<dd>[[ spell_slots ]]</dd>
</dl>
[% endif %]
<ul class="spell-list">
[% for spell in spells %]
<li class="[% if spell in character.spells_prepared %]prepared[% else %]unprepared[% endif %]">
<a href="#spells-[[ spell.name | to_heading_id ]]">[[ spell ]]</a>
</li>
[% endfor %]
</ul>
[% endfor %]
<!-- Spell descriptions -->
[% for spl in character.spells %]
<h2 id="spells-[[ spl.name | to_heading_id ]]">[[ spl.name ]]</h2>
@@ -22,14 +85,14 @@
[% endif %]
</p>
<dl class="spell-details">
<dt>Casting Time:</dt>
<dl class="spell-details details">
<dt>Casting Time</dt>
<dd>[[ spl.casting_time ]]</dd>
<dt>Duration:</dt>
<dt>Duration</dt>
<dd>[[ spl.duration ]]</dd>
<dt>Range:</dt>
<dt>Range</dt>
<dd>[[ spl.casting_range ]]</dd>
<dt>Components:</dt>
<dt>Components</dt>
<dd>[[ spl.component_string ]]</dd>
</dl>
+18 -29
View File
@@ -71,9 +71,6 @@ class CharacterRenderer():
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}")
create_magic_items_content = CharacterRenderer("magic_items_template.{suffix}")
@@ -343,14 +340,6 @@ def make_character_content(
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,
@@ -429,30 +418,30 @@ def make_character_sheet(
pages = []
# Prepare the tex/html content
content_suffix = format_suffixes[output_format]
# Start of PDF gen
char_pdf = create_character_pdf_template(
character=character, basename=char_base, flatten=flatten
)
pages.append(char_pdf)
person_pdf = create_personality_pdf_template(
character=character, basename=person_base, flatten=flatten
)
pages.append(person_pdf)
if character.is_spellcaster:
# Create spell sheet
spell_base = "{:s}_spells".format(basename)
create_spells_pdf_template(
character=character, basename=spell_base, flatten=flatten
)
sheets.append(spell_base + ".pdf")
# end of PDF gen
features_base = "{:s}_features".format(basename)
# Create a list of features and magic items
content = make_character_content(character=character,
content_format=content_suffix,
fancy_decorations=fancy_decorations)
# Typeset combined LaTeX file
if output_format == "pdf":
# Fillable PDF forms
char_pdf = create_character_pdf_template(
character=character, basename=char_base, flatten=flatten
)
pages.append(char_pdf)
Person_pdf = create_personality_pdf_template(
character=character, basename=person_base, flatten=flatten
)
pages.append(person_pdf)
if character.is_spellcaster:
# Create spell sheet
spell_base = "{:s}_spells".format(basename)
create_spells_pdf_template(
character=character, basename=spell_base, flatten=flatten
)
sheets.append(spell_base + ".pdf")
# Combined with additional LaTeX pages with detailed character info
features_base = "{:s}_features".format(basename)
try:
if len(content) > 2:
latex.create_latex_pdf(
+1 -1
View File
@@ -16,7 +16,7 @@ class Monster(Entity):
skills = "Perception +3, Stealth +4"
damage_resistances = ""
damage_immunities = ""
damane_vulnerabilities = ""
damage_vulnerabilities = ""
condition_immunities = ""
saving_throws = ""
# TODO: Consider refactoring stats.Speed to consider all of these
+5
View File
@@ -39,6 +39,11 @@ def ability_mod_str(character, ability):
return mod_str(getattr(character, ability).modifier)
def stat_abbreviation(stat_name):
"""Abbreviate the name of an ability."""
return stat_name.upper()[:3]
AbilityScore = namedtuple("AbilityScore", ("value", "modifier", "saving_throw", "name"))
+1 -1
View File
@@ -90,7 +90,7 @@ spells_prepared = ('acid splash', 'animate_objects', 'ray of frost', 'light', 'f
'teleport', 'maze', 'wish') # Todo: Learn some spells
# Which spells have not been prepared
__spells_unprepared = ()
__spells_unprepared = ("fireball",)
# all spells known
spells = spells_prepared + __spells_unprepared
+4
View File
@@ -42,3 +42,7 @@ class TestSpells(TestCase):
# Try with a ritual and a concentration
spell.concentration = True
self.assertEqual(str(spell), "My spell (R, C)")
# Try with ritual/concentration/verbal/somatic
# spell.
# self.assertEqual(str(spell), "My spell (R, C)")
# # Try with material components with a cost