Skip to content
Merged
43 changes: 43 additions & 0 deletions .github/workflows/build-sheets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
name: Build Sheets

on:
workflow_dispatch:
push:
paths:
- 'templates/**'
- 'components/**'
- 'scripts/build_sheets.py'
- '.github/workflows/build-sheets.yml'

permissions:
contents: write

jobs:
build-sheets:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.x'

- name: Build sheets from templates
run: python scripts/build_sheets.py

- name: Commit built sheets if changed
run: |
if git diff --quiet -- OutputSheets; then
echo "No OutputSheets changes detected"
exit 0
fi

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add OutputSheets
git commit -m "chore: rebuild OutputSheets from templates"
git push
30 changes: 21 additions & 9 deletions OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@
</style>
</head>
<body>

<!-- ═══ HEADER ═══ -->
<div class="no-break">
<h1>${pcstring('NAME')}</h1>
Expand Down Expand Up @@ -454,6 +453,7 @@
</div>
</div>
</#if>

</div>

</div><!-- end two-col: Special Qualities | Feats/Traits/Domains -->
Expand Down Expand Up @@ -551,10 +551,22 @@
</#if>
</@loop>
<#assign charSize = pcstring('SIZE')?lower_case />
<#assign sizeKey = charSize />
<#if charSize?starts_with("tiny")><#assign sizeKey = "t" />
<#elseif charSize?starts_with("small")><#assign sizeKey = "s" />
<#elseif charSize?starts_with("medium")><#assign sizeKey = "m" />
<#elseif charSize?starts_with("large")><#assign sizeKey = "l" />
<#elseif charSize?starts_with("huge")><#assign sizeKey = "h" />
<#elseif charSize?starts_with("gargantuan")><#assign sizeKey = "g" />
<#elseif charSize?starts_with("colossal")><#assign sizeKey = "c" />
</#if>
<#assign unarmedDie = "1d3" />
<#if charSize = "s"><#assign unarmedDie = "1d2" />
<#elseif charSize = "m"><#assign unarmedDie = "1d3" />
<#elseif charSize = "l"><#assign unarmedDie = "1d4" />
<#if sizeKey == "t" || sizeKey == "s"><#assign unarmedDie = "1d2" />
<#elseif sizeKey == "m"><#assign unarmedDie = "1d3" />
<#elseif sizeKey == "l"><#assign unarmedDie = "1d4" />
<#elseif sizeKey == "h"><#assign unarmedDie = "1d6" />
<#elseif sizeKey == "g"><#assign unarmedDie = "1d8" />
<#elseif sizeKey == "c"><#assign unarmedDie = "2d6" />
</#if>
<#assign strMod = pcstring('STAT.0.MOD.SIGN') />
<tr style="background:var(--c6);">
Expand Down Expand Up @@ -723,7 +735,7 @@
<#if (spelllevelcount > 0)>
<div class="spell-level-block">
<div class="spell-level-head">
<#if (level = 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
<#if (level == 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
</div>
<table style="table-layout:fixed;margin-bottom:2px;">
<tr>
Expand All @@ -749,7 +761,7 @@
<span class="src">[${pcstring('SPELLMEM.${class}.0.${level}.${spell}.SOURCE')}]</span>
</td>
<td class="border" align="center" style="font-size:11pt;letter-spacing:1px;">
<#if (level = 0)>&infin;<#else>${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
<#if (level == 0)>&infin;<#else>${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
</td>
<td class="border" align="center" style="font-size:8pt;">
<#if !hasNoSave><b>${spSaveShort}</b><br/>DC ${spDC}<br/></#if>
Expand Down Expand Up @@ -786,7 +798,7 @@
<#if (spelllevelcount > 0)>
<div class="spell-level-block">
<div class="spell-level-head">
<#if (level = 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
<#if (level == 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
</div>
<table style="table-layout:fixed;margin-bottom:2px;">
<tr>
Expand All @@ -812,7 +824,7 @@
<span class="src">[${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}]</span>
</td>
<td class="border" align="center" style="font-size:11pt;letter-spacing:1px;">
<#if (level = 0)>&infin;<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>&#9744;</@loop></#if>
<#if (level == 0)>&infin;<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>&#9744;</@loop></#if>
</td>
<td class="border" align="center" style="font-size:8pt;">
<#if !hasNoSave><b>${spSaveShort}</b><br/>DC ${spDC}<br/></#if>
Expand Down Expand Up @@ -899,7 +911,6 @@
</div>
</div>


<div class="no-break">
<h2>Equipment</h2>
<div style="margin-bottom:8px;">
Expand Down Expand Up @@ -1336,5 +1347,6 @@
Player: ${pcstring('PLAYERNAME')} &mdash; Character: ${pcstring('NAME')}
</div>


</body>
</html>
27 changes: 19 additions & 8 deletions OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@
</style>
</head>
<body>

<!-- ═══ HEADER ═══ -->
<div class="no-break">
<h1>${pcstring('NAME')}</h1>
Expand Down Expand Up @@ -454,6 +453,7 @@
</div>
</div>
</#if>

</div>

</div><!-- end two-col: Special Qualities | Feats/Traits/Domains -->
Expand Down Expand Up @@ -551,10 +551,22 @@
</#if>
</@loop>
<#assign charSize = pcstring('SIZE')?lower_case />
<#assign sizeKey = charSize />
<#if charSize?starts_with("tiny")><#assign sizeKey = "t" />
<#elseif charSize?starts_with("small")><#assign sizeKey = "s" />
<#elseif charSize?starts_with("medium")><#assign sizeKey = "m" />
<#elseif charSize?starts_with("large")><#assign sizeKey = "l" />
<#elseif charSize?starts_with("huge")><#assign sizeKey = "h" />
<#elseif charSize?starts_with("gargantuan")><#assign sizeKey = "g" />
<#elseif charSize?starts_with("colossal")><#assign sizeKey = "c" />
</#if>
<#assign unarmedDie = "1d3" />
<#if charSize = "s"><#assign unarmedDie = "1d2" />
<#elseif charSize = "m"><#assign unarmedDie = "1d3" />
<#elseif charSize = "l"><#assign unarmedDie = "1d4" />
<#if sizeKey == "t" || sizeKey == "s"><#assign unarmedDie = "1d2" />
<#elseif sizeKey == "m"><#assign unarmedDie = "1d3" />
<#elseif sizeKey == "l"><#assign unarmedDie = "1d4" />
<#elseif sizeKey == "h"><#assign unarmedDie = "1d6" />
<#elseif sizeKey == "g"><#assign unarmedDie = "1d8" />
<#elseif sizeKey == "c"><#assign unarmedDie = "2d6" />
</#if>
<#assign strMod = pcstring('STAT.0.MOD.SIGN') />
<tr style="background:var(--c6);">
Expand Down Expand Up @@ -706,7 +718,6 @@
</div>
</div>


<h2 style="page-break-before: always;">Prepared Spells</h2>
<div class="help-text" style="margin-bottom:4px;">
Spell descriptions shown here come from PCGen's built-in effect summary. For the full text of any spell, refer to the source book listed in brackets &mdash; or search the spell name on the Archives of Nethys (aonprd.com) for free online access to the complete Pathfinder rules text.
Expand All @@ -722,7 +733,7 @@
<#if (spelllevelcount > 0)>
<div class="spell-level-block">
<div class="spell-level-head">
<#if (level = 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
<#if (level == 0)>Cantrips (Level 0) &mdash; Unlimited Uses<#else>Level ${level} &mdash; Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}</#if>
</div>
<table style="table-layout:fixed;margin-bottom:2px;">
<tr>
Expand All @@ -748,7 +759,7 @@
<span class="src">[${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}]</span>
</td>
<td class="border" align="center" style="font-size:11pt;letter-spacing:1px;">
<#if (level = 0)>&infin;<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>&#9744;</@loop></#if>
<#if (level == 0)>&infin;<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>&#9744;</@loop></#if>
</td>
<td class="border" align="center" style="font-size:8pt;">
<#if !hasNoSave><b>${spSaveShort}</b><br/>DC ${spDC}<br/></#if>
Expand Down Expand Up @@ -835,7 +846,6 @@
</div>
</div>


<div class="no-break">
<h2>Equipment</h2>
<div style="margin-bottom:8px;">
Expand Down Expand Up @@ -1272,5 +1282,6 @@
Player: ${pcstring('PLAYERNAME')} &mdash; Character: ${pcstring('NAME')}
</div>


</body>
</html>
38 changes: 32 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,30 @@ Custom PCGen output sheets for Pathfinder 1e. Currently includes a prepared-spel

- A collection of PCGen **output sheets** (FreeMarker `.ftl` templates) for Pathfinder 1e characters.
- The sheets are drop-in replacements or additions for the standard PCGen output-sheet folder.
- `OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl` — a character sheet focused on prepared spells, styled for easy table-side reading.
- `templates/d20/fantasy/xmlhtml/` — source sheet templates using component placeholders.
- `components/` — reusable sheet blocks (skills, feats, weapons, inventory, spellbook, prepared spells, quick view, common conditions, biography, and more).
- `OutputSheets/d20/fantasy/xmlhtml/` — compiled output sheets ready to copy into PCGen.

If you want to change how the sheet looks or what it shows, edit the `.ftl` file directly. The template uses PCGen's `${pcstring(...)}` and `<#...>` directives to pull character data at export time.
If you want to change how the sheet looks or what it shows, edit files in `templates/` and `components/`, then compile to `OutputSheets/`. The templates use PCGen's `${pcstring(...)}` and `<#...>` directives to pull character data at export time.


## Component syntax

Templates use component tokens in this format (templates are now mostly style + ordered component tokens):

```
{{ component:skills }}
```

During build, each token is replaced with the content of `components/<name>.ftl`.

## Build compiled sheets

```
python scripts/build_sheets.py
```

This compiles everything from `templates/` to `OutputSheets/` using `components/`.

## Where to put the sheets

Expand Down Expand Up @@ -36,7 +57,11 @@ PCGen looks for output sheets inside its own `outputsheets` folder. The director

## Where the important pieces live

- `OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl` — the prepared-spells sheet template
- `templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl` — prepared-spells source template
- `templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl` — known+prepared spells source template
- `components/*.ftl` — reusable sheet blocks inserted during build (including quick view, common conditions, biography, rules references, and combat sections)
- `scripts/build_sheets.py` — local compiler for templates/components into `OutputSheets/`
- `.github/workflows/build-sheets.yml` — CI workflow that rebuilds and auto-commits `OutputSheets/`

## Contributing

Expand All @@ -50,9 +75,10 @@ Pull requests are welcome, especially if you want to:
### A good contribution path

1. Fork the repository or create a branch.
2. Make your changes to the `.ftl` template(s).
3. Export a test character from PCGen to verify the output looks correct.
4. Open a pull request with a clear explanation of what the sheet shows or how the layout changed.
2. Make your changes in `templates/` and `components/`.
3. Compile with `python scripts/build_sheets.py` to regenerate `OutputSheets/`.
4. Export a test character from PCGen to verify the output looks correct.
5. Open a pull request with a clear explanation of what the sheet shows or how the layout changed.

### Content conventions

Expand Down
51 changes: 51 additions & 0 deletions components/ability_influence.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<!-- ═══ ABILITY INFLUENCE REFERENCE ═══ -->
<div class="no-break" style="margin-bottom:8px;">
<h2>Ability Influence (Quick Calc)</h2>
<table style="table-layout:fixed; margin-bottom:4px;">
<tr>
<th class="border" align="left" style="width:12%;">Ability</th>
<th class="border" style="width:13%;" align="center">Score / Mod</th>
<th class="border" align="left" style="width:43%;">Primary Effects On This Sheet</th>
<th class="border" align="left" style="width:32%;">Current Derived Values</th>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;"><b>STR</b></td>
<td class="border val">14 (+2)</td>
<td class="border" style="font-size:8pt;">Melee attack, melee damage, CMB, CMD, carry/lift limits</td>
<td class="border" style="font-size:8pt;">Melee: +2 | CMB: +2 | CMD: 13</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;"><b>DEX</b></td>
<td class="border val">12 (+1)</td>
<td class="border" style="font-size:8pt;">Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex save</td>
<td class="border" style="font-size:8pt;">Ranged: +1 | Init: +1 | AC ability: 1</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;"><b>CON</b></td>
<td class="border val">14 (+2)</td>
<td class="border" style="font-size:8pt;">Hit points per level/HD, Fortitude save, concentration-related checks</td>
<td class="border" style="font-size:8pt;">Max HP: 10 | Hit Dice: (1d8)+2</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;"><b>INT</b></td>
<td class="border val">11 (+0)</td>
<td class="border" style="font-size:8pt;">Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisites</td>
<td class="border" style="font-size:8pt;">See Skills table for INT-based totals</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;"><b>WIS</b></td>
<td class="border val">16 (+3)</td>
<td class="border" style="font-size:8pt;">Will save, WIS-based skills, divine spellcasting checks/DCs when applicable</td>
<td class="border" style="font-size:8pt;">See Saves/Skills and Prepared Spells sections</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;"><b>CHA</b></td>
<td class="border val">12 (+1)</td>
<td class="border" style="font-size:8pt;">Social skills, class features that key from CHA, turning/channel effects when used</td>
<td class="border" style="font-size:8pt;">See class feature and ability notes</td>
</tr>
</table>
<div class="help-text">
Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above.
</div>
</div>
13 changes: 13 additions & 0 deletions components/ability_scores.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- ═══ ABILITY SCORES ═══ -->
<div class="no-break">
<h2>Ability Scores</h2>
<div class="six-col" style="margin-bottom:8px;">
<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next>
<div class="stat-box">
<span class="sname">${pcstring('STAT.${stat}.NAME')}</span>
<span class="score">${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')}</span>
<span class="mod">${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')}</span>
</div>
</@loop>
</div>
</div>
50 changes: 50 additions & 0 deletions components/aoo_reference.ftl
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!-- ═══ ATTACKS OF OPPORTUNITY REFERENCE ═══ -->
<div class="no-break" style="margin-bottom:8px;">
<h2>AoO Quick Reference</h2>
<table style="table-layout:fixed; margin-bottom:4px;">
<tr>
<th class="border" align="left" style="width:45%;">Action</th>
<th class="border" align="left" style="width:55%;">Notes</th>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;">Move out of threatened square</td>
<td class="border" style="font-size:8pt;">5-ft step, withdraw (first square), or Acrobatics can avoid</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;">Ranged attack in melee</td>
<td class="border" style="font-size:8pt;">Any ranged attack while threatened</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;">Cast spell in melee</td>
<td class="border" style="font-size:8pt;">Cast defensively to avoid provoking (Concentration check)</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;">Drink potion / use scroll</td>
<td class="border" style="font-size:8pt;">Using items in melee commonly provokes</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;">Stand up from prone</td>
<td class="border" style="font-size:8pt;">Common trigger after trip</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;">Combat maneuver (without Improved feat)</td>
<td class="border" style="font-size:8pt;">Trip, disarm, grapple, etc.; improved feat usually prevents</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;">Retrieve stowed item</td>
<td class="border" style="font-size:8pt;">Digging in backpack/pouch while threatened</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;">Pick up item</td>
<td class="border" style="font-size:8pt;">Picking up from ground in melee</td>
</tr>
<tr class="shaded">
<td class="border" style="font-size:8pt;">Load crossbow</td>
<td class="border" style="font-size:8pt;">Most loading actions in melee provoke</td>
</tr>
<tr>
<td class="border" style="font-size:8pt;">Unarmed strike without Improved Unarmed Strike</td>
<td class="border" style="font-size:8pt;">Barehanded attacks vs armed foes can provoke</td>
</tr>
</table>
</div>
Loading
Loading