From 70b54405be51fc07c91401065dc6385ff47fc33b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:01:52 +0000 Subject: [PATCH 1/8] feat: add componentized sheet templates and auto-build workflow Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/cda4b437-1adc-419f-a049-f7f125bae9a4 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .github/workflows/build-sheets.yml | 43 + .../xmlhtml/csheet_known_spells.htm.ftl | 6 + .../xmlhtml/csheet_prepared_spells.htm.ftl | 5 + README.md | 38 +- components/feats.ftl | 55 ++ components/inventory.ftl | 74 ++ components/prepared_spells.ftl | 63 ++ components/skills.ftl | 71 ++ components/spellbook.ftl | 65 ++ components/weapons.ftl | 101 ++ scripts/build_sheets.py | 59 ++ .../xmlhtml/csheet_known_spells.htm.ftl | 917 ++++++++++++++++++ .../xmlhtml/csheet_prepared_spells.htm.ftl | 917 ++++++++++++++++++ 13 files changed, 2408 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/build-sheets.yml create mode 100644 components/feats.ftl create mode 100644 components/inventory.ftl create mode 100644 components/prepared_spells.ftl create mode 100644 components/skills.ftl create mode 100644 components/spellbook.ftl create mode 100644 components/weapons.ftl create mode 100644 scripts/build_sheets.py create mode 100644 templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl create mode 100644 templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl diff --git a/.github/workflows/build-sheets.yml b/.github/workflows/build-sheets.yml new file mode 100644 index 0000000..2220f5d --- /dev/null +++ b/.github/workflows/build-sheets.yml @@ -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 diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 4c62cf8..04a27fa 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -375,6 +375,7 @@ +
@@ -454,6 +455,7 @@
+ @@ -588,6 +590,7 @@ +
@@ -771,6 +774,7 @@ +

Prepared Spells

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 — or search the spell name on the Archives of Nethys (aonprd.com) for free online access to the complete Pathfinder rules text. @@ -834,6 +838,7 @@ +

Concentration Quick Reference

@@ -974,6 +979,7 @@
+

Rules Reference

diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index 522b9db..8b6b837 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -375,6 +375,7 @@
+
@@ -454,6 +455,7 @@
+ @@ -588,6 +590,7 @@ +
@@ -770,6 +773,7 @@ +

Concentration Quick Reference

@@ -910,6 +914,7 @@
+

Rules Reference

diff --git a/README.md b/README.md index 4faa8cf..e058ee1 100644 --- a/README.md +++ b/README.md @@ -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). +- `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: + +``` +{{ component:skills }} +``` + +During build, each token is replaced with the content of `components/.ftl`. + +## Build compiled sheets + +``` +python scripts/build_sheets.py +``` + +This compiles everything from `templates/` to `OutputSheets/` using `components/`. ## Where to put the sheets @@ -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 +- `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 @@ -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 diff --git a/components/feats.ftl b/components/feats.ftl new file mode 100644 index 0000000..d5fa38b --- /dev/null +++ b/components/feats.ftl @@ -0,0 +1,55 @@ + +
+
+

Feats

+
+
+<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=FEAT","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; ft , ft_has_next> + + + + +
+ ${pcstring('ABILITYALL.Feat.VISIBLE.${ft}')}
+ [${pcstring('ABILITYALL.Feat.VISIBLE.${ft}.SOURCE')}]
+ ${pcstring('ABILITYALL.Feat.VISIBLE.${ft}.BENEFIT')} +
+
+ + +<#if (pcvar('count("ABILITIES","CATEGORY=Special Ability","TYPE=Trait","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> +
+

Traits

+
+ +<@loop from=0 to=pcvar('count("ABILITIES","CATEGORY=Special Ability","TYPE=Trait","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; tr , tr_has_next> + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait.DESC')} +
+
+
+ + +<#if (pcstring('DOMAIN.1') != '')> +
+

Domains

+
+ +<@loop from=1 to=pcvar('COUNT[DOMAINS]') ; dm , dm_has_next> + + + + +
+ ${pcstring('DOMAIN.${dm}')}
+ ${pcstring('DOMAIN.${dm}.POWER')} +
+
+
+ diff --git a/components/inventory.ftl b/components/inventory.ftl new file mode 100644 index 0000000..35044e7 --- /dev/null +++ b/components/inventory.ftl @@ -0,0 +1,74 @@ +
+

Equipment

+
+ + + + + + + + + +<@loop from=0 to=pcvar('COUNT[EQUIPMENT.Not.Coin.NOT.Gem]-1') ; eq , eq_has_next> +<#assign eqType = pcstring("EQ.Not.Coin.NOT.Gem.${eq}.TYPE")?lower_case /> +<#assign eqQty = pcvar("EQ.Not.Coin.NOT.Gem.${eq}.QTY") /> +<#assign eqCharges = pcvar("EQ.Not.Coin.NOT.Gem.${eq}.CHARGES") /> +<#assign isUsable = eqType?contains("consumable") || eqType?contains("potion") || eqType?contains("ammunition") || eqType?contains("wand") || eqType?contains("scroll") || eqCharges gt 0 /> +<#assign eqSprop = pcstring('EQ.Not.Coin.NOT.Gem.${eq}.SPROP') /> +<#assign eqDesc = pcstring('EQ.Not.Coin.NOT.Gem.${eq}.DESC') /> +<#assign hasDetail = (eqSprop != "" || eqDesc != "") /> + + + + + + + + + <#if hasDetail> + + + + + + + + + + + +
ItemLocationQtyWeightCostUses (check off when used)
${pcstring('EQ.Not.Coin.NOT.Gem.${eq}.NAME.MAGIC~~')}${pcstring('EQ.Not.Coin.NOT.Gem.${eq}.LOCATION')}${pcstring('EQ.Not.Coin.NOT.Gem.${eq}.QTY')}${pcstring('EQ.Not.Coin.NOT.Gem.${eq}.WT')}${pcstring('EQ.Not.Coin.NOT.Gem.${eq}.COST')} + <#if isUsable> + <#if eqCharges gt 0> + <@loop from=1 to=eqCharges>☐ + <#else> + <@loop from=1 to=eqQty?int>☐ + + +
+ <#if (eqSprop != "")>Special: ${eqSprop}<#if (eqDesc != "")> — <#if (eqDesc != "")>${eqDesc} +
Totals:${pcstring('TOTAL.WEIGHT')}${pcstring('TOTAL.VALUE')}
+
+ Light load: no penalty — + Medium: −3 check penalty, max Dex +3, −10ft speed — + Heavy: −6 check penalty, max Dex +1, −10ft speed — + Lift overhead = heavy max; lift off ground = 2×; drag = 5× +
+
+
+ +
+
+
+ Encumbrance + ${pcstring('TOTAL.WEIGHT')} + Light: ${pcstring('WEIGHT.LIGHT')} / Med: ${pcstring('WEIGHT.MEDIUM')} / Heavy: ${pcstring('WEIGHT.HEAVY')} +
+
+ Unspent Gold + ${pcstring('GOLD.TRUNC')} gp +
+
+
+ diff --git a/components/prepared_spells.ftl b/components/prepared_spells.ftl new file mode 100644 index 0000000..8a73fef --- /dev/null +++ b/components/prepared_spells.ftl @@ -0,0 +1,63 @@ +

Prepared Spells

+
+ 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 — or search the spell name on the Archives of Nethys (aonprd.com) for free online access to the complete Pathfinder rules text. +
+<@loop from=2 to=pcvar('COUNT[SPELLBOOKS]-1') ; spellbook , spellbook_has_next> +<@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> +<#if (pcstring("SPELLLISTCLASS.${class}") != '')> +
+ ${pcstring('SPELLBOOKNAME.${spellbook}')} — ${pcstring('SPELLLISTCLASS.${class}')} +
+ <@loop from=0 to=9 ; level , level_has_next> + <#assign spelllevelcount = pcvar('COUNT[SPELLSINBOOK.${class}.${spellbook}.${level}]') /> + <#if (spelllevelcount > 0)> +
+
+ <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')} +
+ + + + + + + + +<@loop from=0 to=spelllevelcount-1 ; spell , spell_has_next> + <#assign spSave = pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SAVEINFO') /> + <#assign spDC = pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.DC') /> + <#assign spSR = pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SR') /> + <#assign spSaveShort = spSave /> + <#if spSave?lower_case?contains("fortitude")><#assign spSaveShort = "Fortitude" /> + <#if spSave?lower_case?contains("reflex")><#assign spSaveShort = "Reflex" /> + <#if spSave?lower_case?contains("will")><#assign spSaveShort = "Will" /> + <#assign hasNoSave = (spSave = "None" || spSave = "" || spSave?lower_case = "none") /> + + + + + + + + +
SpellUsesSave, DC & SRRange, Time & DurationEffect / Description
+ ${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.BONUSSPELL')}${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.NAME')}
+ ${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SCHOOL')}
+ [${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}] +
+ <#if (level = 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ + + <#if !hasNoSave>${spSaveShort}
DC ${spDC}
+ SR: <#if (spSR != "")>${spSR}<#else>— +
+ ${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.RANGE')}
+ ${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.CASTINGTIME')}
+ ${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.DURATION')} +
${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.EFFECT')}
+
+ + + + + + diff --git a/components/skills.ftl b/components/skills.ftl new file mode 100644 index 0000000..23a5105 --- /dev/null +++ b/components/skills.ftl @@ -0,0 +1,71 @@ + +
+

Skills

+<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> +<#assign skillHalf = (skillCount / 2)?int /> +
+
+ + + + + + + +<@loop from=0 to=skillHalf-1 ; sk , sk_has_next> + <#assign skMisc = pcvar('SKILLSIT.${sk}.MISC') /> + <#assign isClassSkill = (skMisc >= 3) /> + class="shaded"> + + + + + + +
SkillTotal+ Rnks+ Misc
+ <#if pcboolean("SKILLSIT.${sk}.UNTRAINED")>◆${pcstring('SKILLSIT.${sk}')}<#if (pcstring('SKILLSIT.${sk}.ACPv') != "v")>* +
${pcstring('SKILLSIT.${sk}.ABILITY')} (+${pcstring('SKILLSIT.${sk}.ABMOD')}) +
${pcstring('SKILLSIT.${sk}.TOTAL')}${pcstring("SKILLSIT.${sk}.RANK")?replace("\\.0","","rf")}${pcstring('SKILLSIT.${sk}.MISC')}
+
+
+ + + + + + + +<@loop from=skillHalf to=skillCount-1 ; sk , sk_has_next> + <#assign skMisc = pcvar('SKILLSIT.${sk}.MISC') /> + <#assign isClassSkill = (skMisc >= 3) /> + class="shaded"> + + + + + + +
SkillTotal+ Rnks+ Misc
+ <#if pcboolean("SKILLSIT.${sk}.UNTRAINED")>◆${pcstring('SKILLSIT.${sk}')}<#if (pcstring('SKILLSIT.${sk}.ACPv') != "v")>* +
${pcstring('SKILLSIT.${sk}.ABILITY')} (+${pcstring('SKILLSIT.${sk}.ABMOD')}) +
${pcstring('SKILLSIT.${sk}.TOTAL')}${pcstring("SKILLSIT.${sk}.RANK")?replace("\\.0","","rf")}${pcstring('SKILLSIT.${sk}.MISC')}
+
+
+
Bold = class skill with rank (+3 bonus)   ◆ = usable untrained   * = armor check penalty applies
+
+ Class skills give +3 if you have ≥1 rank — + Max ranks = character level — + Take 10: non-stressful, treat roll as 10 — + Take 20: 20× longer, treat roll as 20 (no fail consequence) +
+
+ Conditional Skill Modifiers:
+ <#assign hasSkillCond = false /> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=SkillBonus")-1') ; ab , ab_has_next> + <#assign hasSkillCond = true /> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SkillBonus.ASPECT.SkillBonus')}
+ + <#if !hasSkillCond>No modifiers +
+
+ diff --git a/components/spellbook.ftl b/components/spellbook.ftl new file mode 100644 index 0000000..4e3d488 --- /dev/null +++ b/components/spellbook.ftl @@ -0,0 +1,65 @@ + +<@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> +<#if (pcstring("SPELLLISTCLASS.${class}") != '')> + <#assign knownCount = 0 /> + <@loop from=0 to=9 ; level , level_has_next> + <#assign knownCount = knownCount + pcvar('COUNT[SPELLSINBOOK.${class}.0.${level}]') /> + + <#if (knownCount > 0)> +

Known Spells — ${pcstring('SPELLLISTCLASS.${class}')}

+
+ Known spells are available at any time and cast spontaneously. Spells per day (uses) are shown per level. +
+ <@loop from=0 to=9 ; level , level_has_next> + <#assign spelllevelcount = pcvar('COUNT[SPELLSINBOOK.${class}.0.${level}]') /> + <#if (spelllevelcount > 0)> +
+
+ <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')} +
+ + + + + + + + +<@loop from=0 to=spelllevelcount-1 ; spell , spell_has_next> + <#assign spSave = pcstring('SPELLMEM.${class}.0.${level}.${spell}.SAVEINFO') /> + <#assign spDC = pcstring('SPELLMEM.${class}.0.${level}.${spell}.DC') /> + <#assign spSR = pcstring('SPELLMEM.${class}.0.${level}.${spell}.SR') /> + <#assign spSaveShort = spSave /> + <#if spSave?lower_case?contains("fortitude")><#assign spSaveShort = "Fortitude" /> + <#if spSave?lower_case?contains("reflex")><#assign spSaveShort = "Reflex" /> + <#if spSave?lower_case?contains("will")><#assign spSaveShort = "Will" /> + <#assign hasNoSave = (spSave = "None" || spSave = "" || spSave?lower_case = "none") /> + + + + + + + + +
SpellUsesSave, DC & SRRange, Time & DurationEffect / Description
+ ${pcstring('SPELLMEM.${class}.0.${level}.${spell}.NAME')}
+ ${pcstring('SPELLMEM.${class}.0.${level}.${spell}.SCHOOL')}
+ [${pcstring('SPELLMEM.${class}.0.${level}.${spell}.SOURCE')}] +
+ <#if (level = 0)>∞<#else>${pcstring('SPELLLISTCAST.${class}.${level}')} + + <#if !hasNoSave>${spSaveShort}
DC ${spDC}
+ SR: <#if (spSR != "")>${spSR}<#else>— +
+ ${pcstring('SPELLMEM.${class}.0.${level}.${spell}.RANGE')}
+ ${pcstring('SPELLMEM.${class}.0.${level}.${spell}.CASTINGTIME')}
+ ${pcstring('SPELLMEM.${class}.0.${level}.${spell}.DURATION')} +
${pcstring('SPELLMEM.${class}.0.${level}.${spell}.EFFECT')}
+
+ + + + + + diff --git a/components/weapons.ftl b/components/weapons.ftl new file mode 100644 index 0000000..3c7d648 --- /dev/null +++ b/components/weapons.ftl @@ -0,0 +1,101 @@ +
+

Weapons

+
+ + + + + + + + + +<@loop from=0 to=pcvar('COUNT[EQTYPE.Weapon]-1') ; wp , wp_has_next> +<#if (pcstring("WEAPON.${wp}.NAME") != "")> +<#assign wCat = pcstring("WEAPON.${wp}.CATEGORY")?lower_case /> +<#assign isRanged = wCat?contains("ranged") /> +<#assign isLight = pcboolean("WEAPON.${wp}.ISTYPE.Light") /> +<#assign wType = pcstring("WEAPON.${wp}.TYPE")?lower_case /> +<#assign wTypeFull = "" /> +<#if wType?contains("p")><#assign wTypeFull = wTypeFull + "Piercing " /> +<#if wType?contains("s")><#assign wTypeFull = wTypeFull + "Slashing " /> +<#if wType?contains("b")><#assign wTypeFull = wTypeFull + "Bludgeoning" /> +<#assign wTypeFull = wTypeFull?trim /> + + + <#if isRanged> + + + <#else> + + + + + + + + <#if isRanged> + + + + + <#if (pcstring('WEAPON.${wp}.SPROP') != "")> + + + + + + +<#assign charSize = pcstring('SIZE')?lower_case /> +<#assign unarmedDie = "1d3" /> +<#if charSize = "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize = "m"><#assign unarmedDie = "1d3" /> +<#elseif charSize = "l"><#assign unarmedDie = "1d4" /> + +<#assign strMod = pcstring('STAT.0.MOD.SIGN') /> + + + + + + + + +
Weapon1H-P2H2W-PDMG (2H)Crit
+ ${pcstring('WEAPON.${wp}.NAME')}<#if isLight> △
+ ${wTypeFull} +
${pcstring('WEAPON.${wp}.TOTALHIT')} (ranged, ${pcstring('WEAPON.${wp}.RANGE')})${pcstring('WEAPON.${wp}.DAMAGE')}${pcstring('WEAPON.${wp}.BASEHIT')}${pcstring('WEAPON.${wp}.THHIT')}${pcstring('WEAPON.${wp}.TWPHITH')} + ${pcstring('WEAPON.${wp}.BASICDAMAGE')}
+ 2H: ${pcstring('WEAPON.${wp}.THDAMAGE')} +
${pcstring('WEAPON.${wp}.CRIT')}/x${pcstring('WEAPON.${wp}.MULT')}
+ Range increments (−2 per increment): +  |  2x: ${pcstring('WEAPON.${wp}.RANGELIST.1.TOTALHIT')} +  |  3x: ${pcstring('WEAPON.${wp}.RANGELIST.2.TOTALHIT')} +  |  4x: ${pcstring('WEAPON.${wp}.RANGELIST.3.TOTALHIT')} +  |  5x: ${pcstring('WEAPON.${wp}.RANGELIST.4.TOTALHIT')} +  (max 5) +
+ Special: ${pcstring('WEAPON.${wp}.SPROP')} +
+ Unarmed
+ Bludgeoning +
${pcstring('ATTACK.MELEE.TOTAL')}${pcstring('ATTACK.MELEE.TOTAL')}${pcstring('ATTACK.MELEE.TOTAL')} + ${unarmedDie}${strMod}
+ 2H: ${unarmedDie}${strMod} +
20/x2
+
+ 1H-P = one-handed primary   + 2H = two-handed, adds 1.5× Str to damage   + 2W-P = two weapons, primary hand   + △ = light weapon (Small or Tiny size) +
+
+ Two-weapon attack penalties — + 2W-P: −4 (or −2 with TWF feat)   + 2W-O(L) light off-hand: −4/−4 (or −2/−2 with TWF feat)   + 2W-O(H) heavy off-hand: −4/−8 (or −2/−6 with TWF feat) — + Power Attack: −1 hit per 4 BAB for +2 dmg (+3 two-handed) +
+
+
+ diff --git a/scripts/build_sheets.py b/scripts/build_sheets.py new file mode 100644 index 0000000..652374c --- /dev/null +++ b/scripts/build_sheets.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +from pathlib import Path +import re + +ROOT = Path(__file__).resolve().parent.parent +TEMPLATES_DIR = ROOT / "templates" +COMPONENTS_DIR = ROOT / "components" +OUTPUT_DIR = ROOT / "OutputSheets" +TOKEN_RE = re.compile(r"\{\{\s*component:([a-zA-Z0-9_-]+)\s*\}\}") + + +def load_components() -> dict[str, str]: + components: dict[str, str] = {} + for path in sorted(COMPONENTS_DIR.glob("*.ftl")): + components[path.stem] = path.read_text(encoding="utf-8") + return components + + +def expand_components(content: str, components: dict[str, str]) -> str: + expanded = content + for _ in range(20): + changed = False + + def repl(match: re.Match[str]) -> str: + nonlocal changed + name = match.group(1) + if name not in components: + raise ValueError(f"Missing component: {name}") + changed = True + return components[name] + + new_expanded = TOKEN_RE.sub(repl, expanded) + expanded = new_expanded + if not changed: + break + if TOKEN_RE.search(expanded): + raise ValueError("Unresolved component tokens remain after expansion") + return expanded + + +def build() -> None: + components = load_components() + if not components: + raise ValueError("No components found in components/") + + for template_path in sorted(TEMPLATES_DIR.rglob("*.ftl")): + rel = template_path.relative_to(TEMPLATES_DIR) + output_path = OUTPUT_DIR / rel + output_path.parent.mkdir(parents=True, exist_ok=True) + template_content = template_path.read_text(encoding="utf-8") + output_content = expand_components(template_content, components) + output_path.write_text(output_content, encoding="utf-8") + print(f"Built {output_path.relative_to(ROOT)}") + + +if __name__ == "__main__": + build() diff --git a/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl new file mode 100644 index 0000000..1655853 --- /dev/null +++ b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -0,0 +1,917 @@ +<#ftl encoding="UTF-8" strip_whitespace=true> + + + + +${pcstring('NAME')} + + + + + +
+

${pcstring('NAME')}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
${pcstring('PLAYERNAME')}Lvl ${pcstring('TOTALLEVELS')} (${pcstring('EXP.CURRENT')} / ${pcstring('EXP.NEXT')})${pcstring('RACE')}${pcstring('GENDER')} (${pcstring('AGE')})${pcstring('HEIGHT')} (${pcstring('SIZELONG')})${pcstring('WEIGHT')}
PlayerLevel (XP)RaceGender (Age)Height (Size)Weight
${pcstring('DEITY')}, ${pcstring('ALIGNMENT')}${pcstring('LANGUAGES')}
Deity & AlignmentLanguages
+
+ + +
+

Ability Scores

+
+<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next> +
+ ${pcstring('STAT.${stat}.NAME')} + ${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')} + ${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')} +
+ +
+
+ + +

Combat

+
+ + + + + + + + + +
Max HP${pcstring('HP')}
HP Lost Today 
Current HP 
Initiative${pcstring('INITIATIVEMOD')}
Speed<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
DR<#if (pcstring('DR') != '')>${pcstring('DR')}<#else>0
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ AC + ${pcstring('AC.Total')} + :Touch${pcstring('AC.Touch')}:Flat${pcstring('AC.Flatfooted')}=Base${pcstring('AC.Base')}+Armor*${pcstring('AC.Armor')}+Shield${pcstring('AC.Shield')}+Dex${pcstring('AC.Ability')}+Size${pcstring('AC.Size')}+Nat${pcstring('AC.NaturalArmor')}+Dodge${pcstring('AC.Dodge')}+Defl${pcstring('AC.Deflection')}+Misc${pcstring('AC.Misc')}
+
+ Touch AC ignores Armor, Shield, and Natural Armor — + Flat-Footed AC ignores Dexterity and Dodge — + Deflection and Size apply to all three — + * Armor bonus reduced by check penalty if not proficient — + Max Dex cap limits Dex bonus when wearing armor +
+ + + + + + + + + + +
BAB${pcstring('ATTACK.MELEE')}
Melee Hit${pcstring('ATTACK.MELEE.TOTAL')}
Ranged Hit${pcstring('ATTACK.RANGED.TOTAL')}
Hit Die${pcstring('HITDICE')}
CMB${pcstring('VAR.CMB.INTVAL.SIGN')}
CMD${pcstring('VAR.CMD.INTVAL')}
SR<#if (pcstring('SR') != '')>${pcstring('SR')}<#else>0
+
+ CMB = BAB + Str + Size — CMD = 10 + BAB + Str + Dex + Size — + SR: attacker rolls d20 + caster level ≥ SR to affect you +
+
+ + +
+

Saves

+ + + + + + + + + +<@loop from=0 to=pcvar('COUNT[CHECKS]-1') ; chk , chk_has_next> + + + + + + + + + +
TotalBaseMagicMisc
+ ${pcstring('CHECK.${chk}.NAME')} + <#if (pcstring('CHECK.${chk}.NAME') = 'Fortitude')>
CON (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Reflex')>
DEX (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Will')>
WIS (${pcstring('CHECK.${chk}.STATMOD')})
+
${pcstring('CHECK.${chk}.TOTAL')}${pcstring('CHECK.${chk}.BASE')}${pcstring('CHECK.${chk}.MAGIC')}${pcstring('CHECK.${chk}.MISC.NOMAGIC.NOSTAT')}
+ <#assign hasSaveCond = pcvar('countdistinct("ABILITIES","ASPECT=SaveBonus")-1') /> + <#if (hasSaveCond >= 0)> +
+ Conditional Save Modifiers:
+ <@loop from=0 to=hasSaveCond ; ab , ab_has_next> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SaveBonus.ASPECT.SaveBonus')}
+ +
+ +
+ Fortitude (CON): resists poison, disease, death effects — + Reflex (DEX): resists area effects, traps — + Will (WIS): resists mind-affecting, compulsion, fear — + DC set by the source ability or spell +
+
+ + +
+

Notes (Description)

+
+ <#assign descRaw = pcstring('DESC') /> + <#assign descRaw = descRaw?replace("<","<") /> + <#assign descRaw = descRaw?replace(">",">") /> + <#assign descRaw = descRaw?replace("&","&") /> + <#assign descRaw = descRaw?replace(""",'"') /> + <#assign descRaw = descRaw?replace("'","'") /> + ${descRaw} +
+
+ +{{ component:skills }} +
+ + +
+<#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> +
+

Special Qualities

+
+ +<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sq , sq_has_next> + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.DESC')} +
+
+
+ +
+ +{{ component:feats }} +
+ + + +
+

Special Attacks

+
+ + <#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sa , sa_has_next> + + + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.DESC')} +
+ <#assign saUses = pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay') /> + <#if (saUses != "")> + Uses/day: ${saUses}
+ <#assign saUsesN = pcvar('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay.INTVAL') /> + <#if (saUsesN > 0)> + <@loop from=1 to=saUsesN>☐ + + +
+
+
+ +{{ component:weapons }} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ManeuverCMBCMDNotes
Grapple${pcstring('VAR.CMB_Grapple.INTVAL.SIGN')}${pcstring('VAR.CMD_Grapple.INTVAL')}Pin, tie up, damage, or move a grappled foe. Grappled = −2 attack/AC, no two-handed.
Trip${pcstring('VAR.CMB_Trip.INTVAL.SIGN')}<#if (pcvar("CantBeTripped") != 0)>Immune<#else>${pcstring('VAR.CMD_Trip.INTVAL')}Knock prone. Prone = −4 melee attack, −4 AC vs melee, +4 AC vs ranged.
Disarm${pcstring('VAR.CMB_Disarm.INTVAL.SIGN')}${pcstring('VAR.CMD_Disarm.INTVAL')}Knock weapon from foe. Beat CMD by 10+ = item lands 10ft away.
Bull Rush${pcstring('VAR.CMB_BullRush.INTVAL.SIGN')}${pcstring('VAR.CMD_BullRush.INTVAL')}Push foe back 5ft + 5ft per 5 over CMD. You may follow.
Sunder${pcstring('VAR.CMB_Sunder.INTVAL.SIGN')}${pcstring('VAR.CMD_Sunder.INTVAL')}Damage a held/worn item. Broken = −2 attack/damage or halved effectiveness.
Overrun${pcstring('VAR.CMB_Overrun.INTVAL.SIGN')}${pcstring('VAR.CMD_Overrun.INTVAL')}Move through foe's space. Fail = blocked; beat by 5+ = foe prone.
Dirty Trick${pcstring('VAR.CMB_DirtyTrick.INTVAL.SIGN')}${pcstring('VAR.CMD_DirtyTrick.INTVAL')}Blind, entangle, or sicken 1 round (+1 per 5 over CMD). Std action to remove.
Drag${pcstring('VAR.CMB_Drag.INTVAL.SIGN')}${pcstring('VAR.CMD_Drag.INTVAL')}Pull foe 5ft + 5ft per 5 over CMD toward you. Must move with them.
Reposition${pcstring('VAR.CMB_Reposition.INTVAL.SIGN')}${pcstring('VAR.CMD_Reposition.INTVAL')}Move foe to any adjacent square. Foe must remain adjacent.
Steal${pcstring('VAR.CMB_Steal.INTVAL.SIGN')}${pcstring('VAR.CMD_Steal.INTVAL')}Take one carried/worn item (not wielded). No free hand required.
+
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
+
+ +
+ Conditional Attack / Combat Modifiers:
+ <#assign hasCombatCond = false /> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=CombatBonus")-1') ; ab , ab_has_next> + <#assign hasCombatCond = true /> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=CombatBonus.ASPECT.CombatBonus')}
+ + <#if !hasCombatCond>No modifiers +
+ +
+

Armor & Shields

+
+ + + + + + + + + + +<@loop from=0 to=2 ; ar , ar_has_next> +<#if (pcstring("ARMOR.${ar}.NAME") != "")> + + + + + + + + + + + +
ItemTypeAC BonusMax Dex*Check Pen†Spell Fail
${pcstring('ARMOR.EQUIPPED.${ar}.NAME')}${pcstring('ARMOR.EQUIPPED.${ar}.TYPE')}${pcstring('ARMOR.EQUIPPED.${ar}.TOTALAC')}${pcstring('ARMOR.EQUIPPED.${ar}.MAXDEX')}${pcstring('ARMOR.EQUIPPED.${ar}.ACCHECK')}${pcstring('ARMOR.EQUIPPED.${ar}.SPELLFAIL')}
+
+ * Max Dex: Dex bonus to AC cannot exceed this — + † Check Penalty: applies to Str/Dex skill checks and attacks if not proficient — + Spell Failure: % chance arcane spells fail (divine spells ignore this) — + Heavy armor reduces speed to 20ft — sleeping in medium/heavy armor leaves you fatigued +
+
+
+ +{{ component:spellbook }} +{{ component:prepared_spells }} +
+

Concentration Quick Reference

+
+ Concentration Check
+ d20 + caster level + spellcasting ability modifier + other bonuses
+ Spellcasting ability modifier is INT (wizard), WIS (cleric/druid), CHA (sorcerer/bard/oracle), etc. + <#assign hasConcClass = false /> + <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> + <#if (pcstring("SPELLLISTCLASS.${class}") != '' && pcstring("SPELLLISTCLASS.${class}.CONCENTRATION") != '')> + <#assign hasConcClass = true /> +
${pcstring('SPELLLISTCLASS.${class}')}: d20${pcstring('SPELLLISTCLASS.${class}.CONCENTRATION')} total + (CL ${pcstring('SPELLLISTCLASS.${class}.CASTERLEVEL')} + ${pcstring('SPELLLISTDCSTAT.${class}.0')}) + + + <#if !hasConcClass>
No spellcasting class concentration values found. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
When A Check Is RequiredConcentration DC
Cast defensively (to avoid provoking)15 + (2 × spell level)
Take damage while casting10 + damage dealt + spell level
Taking continuous damage while casting10 + half last damage dealt + spell level
Vigorous motion (mount, rough vehicle, choppy water)10 + spell level
Violent motion (violent weather, heavy turbulence)15 + spell level
Extra violent motion (earthquake-level disruption)20 + spell level
Weather with high wind, rain, or debris5 + spell level
Entangled while casting15 + spell level
Grappled or pinned while casting10 + grappler CMB + spell level
+
+ Common Modifiers: Combat Casting gives +4 on concentration checks to cast defensively or while grappled/pinned — + ability score increases, feats, traits, class features, and situational bonuses also apply.
+ If you fail the concentration check, the spell is lost and has no effect. +
+
+ + +{{ component:inventory }} + +
+

Rules Reference

+
+ Rest
+ 8 hrs: Recover 1 HP / level + 1 point of ability damage.
+ 24 hrs: Same, but 2 HP and points.
+ With healer present/health check: Same, but 3 HP and points. +
+
+ House Rules
+ Level Up: Player and DM roll 1 Hit Die + Con modifier for extra HP. Highest value counts. +
+
+ Common Quick Rules
+ • Flanking: +2 attack.
+ • Aid Another: DC 10 check for ally +2 attack, AC, or check.
+ • Cover / Soft Cover: +4 AC (+2 Reflex); creatures can grant soft cover.
+ • Ranged Into Melee: −4 to hit. Target 2 size > ally? −2 to hit. 3 sizes or Precise Shot? No penalty.
+ • Concealment: Roll to hit and then 1d100 that should be greater than % of concealment (20%/50%).
+ • Casting Defensively: Concentration DC = 15 + (2 × spell level).
+ • DR / Resistance: DR reduces weapon damage; resistance reduces matching energy damage.
+ • Reach / Threatened Squares: you threaten where you can melee; leaving can provoke.
+ • Swift / Immediate: one per round; immediate uses next turn's swift.
+ • Criticals: Nat 20 threatens; confirm with another hit roll (not another 20).
+ • Dying / Stabilize: <0 HP lose 1 HP/round; stabilize check DC 10 + negative HP. +
+
+ Coins
+ Gold (gp): Most common coin piece
+ Silver (sp): 10sp = 1gp
+ Copper (cp): 100cp = 10sp = 1gp
+ Platinum (pp): 1pp = 10gp +
+
+ + +
+

AoO Quick Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionNotes
Move out of threatened square5-ft step, withdraw (first square), or Acrobatics can avoid
Ranged attack in meleeAny ranged attack while threatened
Cast spell in meleeCast defensively to avoid provoking (Concentration check)
Drink potion / use scrollUsing items in melee commonly provokes
Stand up from proneCommon trigger after trip
Combat maneuver (without Improved feat)Trip, disarm, grapple, etc.; improved feat usually prevents
Retrieve stowed itemDigging in backpack/pouch while threatened
Pick up itemPicking up from ground in melee
Load crossbowMost loading actions in melee provoke
Unarmed strike without Improved Unarmed StrikeBarehanded attacks vs armed foes can provoke
+
+ + +
+

Ability Influence (Quick Calc)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AbilityScore / ModPrimary Effects On This SheetCurrent Derived Values
STR14 (+2)Melee attack, melee damage, CMB, CMD, carry/lift limitsMelee: +2 | CMB: +2 | CMD: 13
DEX12 (+1)Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex saveRanged: +1 | Init: +1 | AC ability: 1
CON14 (+2)Hit points per level/HD, Fortitude save, concentration-related checksMax HP: 10 | Hit Dice: (1d8)+2
INT11 (+0)Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisitesSee Skills table for INT-based totals
WIS16 (+3)Will save, WIS-based skills, divine spellcasting checks/DCs when applicableSee Saves/Skills and Prepared Spells sections
CHA12 (+1)Social skills, class features that key from CHA, turning/channel effects when usedSee class feature and ability notes
+
+ Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above. +
+
+ + +
+

Common Conditions & Modifiers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Condition / SituationMain EffectQuick Modifier
ShakenGeneral fear penalties−2 attacks, saves, skills, ability checks
FrightenedAs shaken; must flee if possible−2 attacks, saves, skills, checks
CourageMorale combat boost+1 to hit; +1 saves vs fear
GuidanceSingle-use bonus+1 attack, +1 save, or +1 skill check
SickenedNausea/weakness penalties−2 attacks, weapon dmg, saves, skills, checks
FatiguedTired; cannot run/charge−2 Str, −2 Dex
ExhaustedSevere fatigue; slower movement−6 Str, −6 Dex
ProneWorse in melee, better vs ranged−4 melee attacks, −4 AC vs melee, +4 AC vs ranged
Flat-FootedNot ready to reactLose Dex to AC (and dodge bonuses)
EntangledRestricted movement/offense−2 attack, −4 Dex, move at half speed
GrappledLimited actions in grapple−2 attacks and AC; no AoO
PinnedImmobile in grappleCannot move; very limited actions
StaggeredOnly one major actionEither one standard or one move action
StunnedDrop items; no actionsDrop everything, −2 AC, lose Dex to AC, no actions
FlankingAttacking same foe from opposite sides+2 melee attack
ChargeMove then strike aggressively+2 attack, −2 AC until next turn
Fighting DefensivelyTrade attack for defense−4 attack, +2 AC
Total DefenseNo attacks; full defense+4 AC
BlindedCannot see−2 AC, lose Dex to AC, −4 many Str/Dex checks, move half speed
Invisible AttackerDefender cannot see attackerAttacker often +2 melee; target loses Dex to AC; may require miss chance
Helpless / Coup de GraceHelpless targets are vulnerableCoup de grace: auto crit; target Fort save (DC 10 + damage dealt) or die
+
+ Quick reference only: if an effect conflicts with a specific spell, feat, or monster ability, use the specific rule text first. +
+
+ +
+

Quick Reference Sheet

+ +
+
STR${pcstring('STAT.0.NOTEMP.NOEQUIP')}${pcstring('STAT.0.MOD.NOTEMP.NOEQUIP')}
+
DEX${pcstring('STAT.1.NOTEMP.NOEQUIP')}${pcstring('STAT.1.MOD.NOTEMP.NOEQUIP')}
+
CON${pcstring('STAT.2.NOTEMP.NOEQUIP')}${pcstring('STAT.2.MOD.NOTEMP.NOEQUIP')}
+
INT${pcstring('STAT.3.NOTEMP.NOEQUIP')}${pcstring('STAT.3.MOD.NOTEMP.NOEQUIP')}
+
WIS${pcstring('STAT.4.NOTEMP.NOEQUIP')}${pcstring('STAT.4.MOD.NOTEMP.NOEQUIP')}
+
CHA${pcstring('STAT.5.NOTEMP.NOEQUIP')}${pcstring('STAT.5.MOD.NOTEMP.NOEQUIP')}
+
+ + + + + + + + + + + + + +
HP${pcstring('HP')}
Init${pcstring('INITIATIVEMOD')}
Move<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
AC${pcstring('AC.Total')}
Touch${pcstring('AC.Touch')}
Flat${pcstring('AC.Flatfooted')}
BAB${pcstring('ATTACK.MELEE')}
Melee${pcstring('ATTACK.MELEE.TOTAL')}
Ranged${pcstring('ATTACK.RANGED.TOTAL')}
+ + + + + + + + + + +
SaveTotalQuick Notes
Fortitude${pcstring('CHECK.0.TOTAL')}Poison, disease, body effects
Reflex${pcstring('CHECK.1.TOTAL')}Area effects, traps, avoid damage
Will${pcstring('CHECK.2.TOTAL')}Mental control, fear, compulsion
+ +
+
+ + + + + +<@loop from=0 to=(pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")')/2)?int-1 ; skq1 , skq1_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq1}')}${pcstring('SKILLSIT.${skq1}.TOTAL')}
+
+
+ + + + + +<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> +<#assign splitPoint = (skillCount/2)?int /> +<@loop from=splitPoint to=skillCount-1 ; skq2 , skq2_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq2}')}${pcstring('SKILLSIT.${skq2}.TOTAL')}
+
+
+ +
+ Quick use: keep this page visible during combat for AC, attacks, saves, and top skill totals. +
+
+ +
+

Biography

+
+ <#assign bioRaw = pcstring('BIO') /> + <#assign bioRaw = bioRaw?replace("<","<") /> + <#assign bioRaw = bioRaw?replace(">",">") /> + <#assign bioRaw = bioRaw?replace("&","&") /> + <#assign bioRaw = bioRaw?replace(""",'"') /> + <#assign bioRaw = bioRaw?replace("'","'") /> + ${bioRaw} +
+
+ +<#if (pcstring('PORTRAIT') != '')> +
+

Portrait

+
+ ${pcstring('NAME')} +
+
+ + +
+
+ PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — + Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')} +
+ + + diff --git a/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl new file mode 100644 index 0000000..ad0ef0b --- /dev/null +++ b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -0,0 +1,917 @@ +<#ftl encoding="UTF-8" strip_whitespace=true> + + + + +${pcstring('NAME')} + + + + + +
+

${pcstring('NAME')}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
${pcstring('PLAYERNAME')}Lvl ${pcstring('TOTALLEVELS')} (${pcstring('EXP.CURRENT')} / ${pcstring('EXP.NEXT')})${pcstring('RACE')}${pcstring('GENDER')} (${pcstring('AGE')})${pcstring('HEIGHT')} (${pcstring('SIZELONG')})${pcstring('WEIGHT')}
PlayerLevel (XP)RaceGender (Age)Height (Size)Weight
${pcstring('DEITY')}, ${pcstring('ALIGNMENT')}${pcstring('LANGUAGES')}
Deity & AlignmentLanguages
+
+ + +
+

Ability Scores

+
+<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next> +
+ ${pcstring('STAT.${stat}.NAME')} + ${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')} + ${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')} +
+ +
+
+ + +

Combat

+
+ + + + + + + + + +
Max HP${pcstring('HP')}
HP Lost Today 
Current HP 
Initiative${pcstring('INITIATIVEMOD')}
Speed<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
DR<#if (pcstring('DR') != '')>${pcstring('DR')}<#else>0
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ AC + ${pcstring('AC.Total')} + :Touch${pcstring('AC.Touch')}:Flat${pcstring('AC.Flatfooted')}=Base${pcstring('AC.Base')}+Armor*${pcstring('AC.Armor')}+Shield${pcstring('AC.Shield')}+Dex${pcstring('AC.Ability')}+Size${pcstring('AC.Size')}+Nat${pcstring('AC.NaturalArmor')}+Dodge${pcstring('AC.Dodge')}+Defl${pcstring('AC.Deflection')}+Misc${pcstring('AC.Misc')}
+
+ Touch AC ignores Armor, Shield, and Natural Armor — + Flat-Footed AC ignores Dexterity and Dodge — + Deflection and Size apply to all three — + * Armor bonus reduced by check penalty if not proficient — + Max Dex cap limits Dex bonus when wearing armor +
+ + + + + + + + + + +
BAB${pcstring('ATTACK.MELEE')}
Melee Hit${pcstring('ATTACK.MELEE.TOTAL')}
Ranged Hit${pcstring('ATTACK.RANGED.TOTAL')}
Hit Die${pcstring('HITDICE')}
CMB${pcstring('VAR.CMB.INTVAL.SIGN')}
CMD${pcstring('VAR.CMD.INTVAL')}
SR<#if (pcstring('SR') != '')>${pcstring('SR')}<#else>0
+
+ CMB = BAB + Str + Size — CMD = 10 + BAB + Str + Dex + Size — + SR: attacker rolls d20 + caster level ≥ SR to affect you +
+
+ + +
+

Saves

+ + + + + + + + + +<@loop from=0 to=pcvar('COUNT[CHECKS]-1') ; chk , chk_has_next> + + + + + + + + + +
TotalBaseMagicMisc
+ ${pcstring('CHECK.${chk}.NAME')} + <#if (pcstring('CHECK.${chk}.NAME') = 'Fortitude')>
CON (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Reflex')>
DEX (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Will')>
WIS (${pcstring('CHECK.${chk}.STATMOD')})
+
${pcstring('CHECK.${chk}.TOTAL')}${pcstring('CHECK.${chk}.BASE')}${pcstring('CHECK.${chk}.MAGIC')}${pcstring('CHECK.${chk}.MISC.NOMAGIC.NOSTAT')}
+ <#assign hasSaveCond = pcvar('countdistinct("ABILITIES","ASPECT=SaveBonus")-1') /> + <#if (hasSaveCond >= 0)> +
+ Conditional Save Modifiers:
+ <@loop from=0 to=hasSaveCond ; ab , ab_has_next> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SaveBonus.ASPECT.SaveBonus')}
+ +
+ +
+ Fortitude (CON): resists poison, disease, death effects — + Reflex (DEX): resists area effects, traps — + Will (WIS): resists mind-affecting, compulsion, fear — + DC set by the source ability or spell +
+
+ + +
+

Notes (Description)

+
+ <#assign descRaw = pcstring('DESC') /> + <#assign descRaw = descRaw?replace("<","<") /> + <#assign descRaw = descRaw?replace(">",">") /> + <#assign descRaw = descRaw?replace("&","&") /> + <#assign descRaw = descRaw?replace(""",'"') /> + <#assign descRaw = descRaw?replace("'","'") /> + ${descRaw} +
+
+ +{{ component:skills }} +
+ + +
+<#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> +
+

Special Qualities

+
+ +<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sq , sq_has_next> + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.DESC')} +
+
+
+ +
+ +{{ component:feats }} +
+ + + +
+

Special Attacks

+
+ + <#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sa , sa_has_next> + + + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.DESC')} +
+ <#assign saUses = pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay') /> + <#if (saUses != "")> + Uses/day: ${saUses}
+ <#assign saUsesN = pcvar('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay.INTVAL') /> + <#if (saUsesN > 0)> + <@loop from=1 to=saUsesN>☐ + + +
+
+
+ +{{ component:weapons }} +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ManeuverCMBCMDNotes
Grapple${pcstring('VAR.CMB_Grapple.INTVAL.SIGN')}${pcstring('VAR.CMD_Grapple.INTVAL')}Pin, tie up, damage, or move a grappled foe. Grappled = −2 attack/AC, no two-handed.
Trip${pcstring('VAR.CMB_Trip.INTVAL.SIGN')}<#if (pcvar("CantBeTripped") != 0)>Immune<#else>${pcstring('VAR.CMD_Trip.INTVAL')}Knock prone. Prone = −4 melee attack, −4 AC vs melee, +4 AC vs ranged.
Disarm${pcstring('VAR.CMB_Disarm.INTVAL.SIGN')}${pcstring('VAR.CMD_Disarm.INTVAL')}Knock weapon from foe. Beat CMD by 10+ = item lands 10ft away.
Bull Rush${pcstring('VAR.CMB_BullRush.INTVAL.SIGN')}${pcstring('VAR.CMD_BullRush.INTVAL')}Push foe back 5ft + 5ft per 5 over CMD. You may follow.
Sunder${pcstring('VAR.CMB_Sunder.INTVAL.SIGN')}${pcstring('VAR.CMD_Sunder.INTVAL')}Damage a held/worn item. Broken = −2 attack/damage or halved effectiveness.
Overrun${pcstring('VAR.CMB_Overrun.INTVAL.SIGN')}${pcstring('VAR.CMD_Overrun.INTVAL')}Move through foe's space. Fail = blocked; beat by 5+ = foe prone.
Dirty Trick${pcstring('VAR.CMB_DirtyTrick.INTVAL.SIGN')}${pcstring('VAR.CMD_DirtyTrick.INTVAL')}Blind, entangle, or sicken 1 round (+1 per 5 over CMD). Std action to remove.
Drag${pcstring('VAR.CMB_Drag.INTVAL.SIGN')}${pcstring('VAR.CMD_Drag.INTVAL')}Pull foe 5ft + 5ft per 5 over CMD toward you. Must move with them.
Reposition${pcstring('VAR.CMB_Reposition.INTVAL.SIGN')}${pcstring('VAR.CMD_Reposition.INTVAL')}Move foe to any adjacent square. Foe must remain adjacent.
Steal${pcstring('VAR.CMB_Steal.INTVAL.SIGN')}${pcstring('VAR.CMD_Steal.INTVAL')}Take one carried/worn item (not wielded). No free hand required.
+
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
+
+ +
+ Conditional Attack / Combat Modifiers:
+ <#assign hasCombatCond = false /> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=CombatBonus")-1') ; ab , ab_has_next> + <#assign hasCombatCond = true /> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=CombatBonus.ASPECT.CombatBonus')}
+ + <#if !hasCombatCond>No modifiers +
+ +
+

Armor & Shields

+
+ + + + + + + + + + +<@loop from=0 to=2 ; ar , ar_has_next> +<#if (pcstring("ARMOR.${ar}.NAME") != "")> + + + + + + + + + + + +
ItemTypeAC BonusMax Dex*Check Pen†Spell Fail
${pcstring('ARMOR.EQUIPPED.${ar}.NAME')}${pcstring('ARMOR.EQUIPPED.${ar}.TYPE')}${pcstring('ARMOR.EQUIPPED.${ar}.TOTALAC')}${pcstring('ARMOR.EQUIPPED.${ar}.MAXDEX')}${pcstring('ARMOR.EQUIPPED.${ar}.ACCHECK')}${pcstring('ARMOR.EQUIPPED.${ar}.SPELLFAIL')}
+
+ * Max Dex: Dex bonus to AC cannot exceed this — + † Check Penalty: applies to Str/Dex skill checks and attacks if not proficient — + Spell Failure: % chance arcane spells fail (divine spells ignore this) — + Heavy armor reduces speed to 20ft — sleeping in medium/heavy armor leaves you fatigued +
+
+
+ + +{{ component:prepared_spells }} +
+

Concentration Quick Reference

+
+ Concentration Check
+ d20 + caster level + spellcasting ability modifier + other bonuses
+ Spellcasting ability modifier is INT (wizard), WIS (cleric/druid), CHA (sorcerer/bard/oracle), etc. + <#assign hasConcClass = false /> + <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> + <#if (pcstring("SPELLLISTCLASS.${class}") != '' && pcstring("SPELLLISTCLASS.${class}.CONCENTRATION") != '')> + <#assign hasConcClass = true /> +
${pcstring('SPELLLISTCLASS.${class}')}: d20${pcstring('SPELLLISTCLASS.${class}.CONCENTRATION')} total + (CL ${pcstring('SPELLLISTCLASS.${class}.CASTERLEVEL')} + ${pcstring('SPELLLISTDCSTAT.${class}.0')}) + + + <#if !hasConcClass>
No spellcasting class concentration values found. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
When A Check Is RequiredConcentration DC
Cast defensively (to avoid provoking)15 + (2 × spell level)
Take damage while casting10 + damage dealt + spell level
Taking continuous damage while casting10 + half last damage dealt + spell level
Vigorous motion (mount, rough vehicle, choppy water)10 + spell level
Violent motion (violent weather, heavy turbulence)15 + spell level
Extra violent motion (earthquake-level disruption)20 + spell level
Weather with high wind, rain, or debris5 + spell level
Entangled while casting15 + spell level
Grappled or pinned while casting10 + grappler CMB + spell level
+
+ Common Modifiers: Combat Casting gives +4 on concentration checks to cast defensively or while grappled/pinned — + ability score increases, feats, traits, class features, and situational bonuses also apply.
+ If you fail the concentration check, the spell is lost and has no effect. +
+
+ + +{{ component:inventory }} + +
+

Rules Reference

+
+ Rest
+ 8 hrs: Recover 1 HP / level + 1 point of ability damage.
+ 24 hrs: Same, but 2 HP and points.
+ With healer present/health check: Same, but 3 HP and points. +
+
+ House Rules
+ Level Up: Player and DM roll 1 Hit Die + Con modifier for extra HP. Highest value counts. +
+
+ Common Quick Rules
+ • Flanking: +2 attack.
+ • Aid Another: DC 10 check for ally +2 attack, AC, or check.
+ • Cover / Soft Cover: +4 AC (+2 Reflex); creatures can grant soft cover.
+ • Ranged Into Melee: −4 to hit. Target 2 size > ally? −2 to hit. 3 sizes or Precise Shot? No penalty.
+ • Concealment: Roll to hit and then 1d100 that should be greater than % of concealment (20%/50%).
+ • Casting Defensively: Concentration DC = 15 + (2 × spell level).
+ • DR / Resistance: DR reduces weapon damage; resistance reduces matching energy damage.
+ • Reach / Threatened Squares: you threaten where you can melee; leaving can provoke.
+ • Swift / Immediate: one per round; immediate uses next turn's swift.
+ • Criticals: Nat 20 threatens; confirm with another hit roll (not another 20).
+ • Dying / Stabilize: <0 HP lose 1 HP/round; stabilize check DC 10 + negative HP. +
+
+ Coins
+ Gold (gp): Most common coin piece
+ Silver (sp): 10sp = 1gp
+ Copper (cp): 100cp = 10sp = 1gp
+ Platinum (pp): 1pp = 10gp +
+
+ + +
+

AoO Quick Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionNotes
Move out of threatened square5-ft step, withdraw (first square), or Acrobatics can avoid
Ranged attack in meleeAny ranged attack while threatened
Cast spell in meleeCast defensively to avoid provoking (Concentration check)
Drink potion / use scrollUsing items in melee commonly provokes
Stand up from proneCommon trigger after trip
Combat maneuver (without Improved feat)Trip, disarm, grapple, etc.; improved feat usually prevents
Retrieve stowed itemDigging in backpack/pouch while threatened
Pick up itemPicking up from ground in melee
Load crossbowMost loading actions in melee provoke
Unarmed strike without Improved Unarmed StrikeBarehanded attacks vs armed foes can provoke
+
+ + +
+

Ability Influence (Quick Calc)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AbilityScore / ModPrimary Effects On This SheetCurrent Derived Values
STR14 (+2)Melee attack, melee damage, CMB, CMD, carry/lift limitsMelee: +2 | CMB: +2 | CMD: 13
DEX12 (+1)Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex saveRanged: +1 | Init: +1 | AC ability: 1
CON14 (+2)Hit points per level/HD, Fortitude save, concentration-related checksMax HP: 10 | Hit Dice: (1d8)+2
INT11 (+0)Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisitesSee Skills table for INT-based totals
WIS16 (+3)Will save, WIS-based skills, divine spellcasting checks/DCs when applicableSee Saves/Skills and Prepared Spells sections
CHA12 (+1)Social skills, class features that key from CHA, turning/channel effects when usedSee class feature and ability notes
+
+ Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above. +
+
+ + +
+

Common Conditions & Modifiers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Condition / SituationMain EffectQuick Modifier
ShakenGeneral fear penalties−2 attacks, saves, skills, ability checks
FrightenedAs shaken; must flee if possible−2 attacks, saves, skills, checks
CourageMorale combat boost+1 to hit; +1 saves vs fear
GuidanceSingle-use bonus+1 attack, +1 save, or +1 skill check
SickenedNausea/weakness penalties−2 attacks, weapon dmg, saves, skills, checks
FatiguedTired; cannot run/charge−2 Str, −2 Dex
ExhaustedSevere fatigue; slower movement−6 Str, −6 Dex
ProneWorse in melee, better vs ranged−4 melee attacks, −4 AC vs melee, +4 AC vs ranged
Flat-FootedNot ready to reactLose Dex to AC (and dodge bonuses)
EntangledRestricted movement/offense−2 attack, −4 Dex, move at half speed
GrappledLimited actions in grapple−2 attacks and AC; no AoO
PinnedImmobile in grappleCannot move; very limited actions
StaggeredOnly one major actionEither one standard or one move action
StunnedDrop items; no actionsDrop everything, −2 AC, lose Dex to AC, no actions
FlankingAttacking same foe from opposite sides+2 melee attack
ChargeMove then strike aggressively+2 attack, −2 AC until next turn
Fighting DefensivelyTrade attack for defense−4 attack, +2 AC
Total DefenseNo attacks; full defense+4 AC
BlindedCannot see−2 AC, lose Dex to AC, −4 many Str/Dex checks, move half speed
Invisible AttackerDefender cannot see attackerAttacker often +2 melee; target loses Dex to AC; may require miss chance
Helpless / Coup de GraceHelpless targets are vulnerableCoup de grace: auto crit; target Fort save (DC 10 + damage dealt) or die
+
+ Quick reference only: if an effect conflicts with a specific spell, feat, or monster ability, use the specific rule text first. +
+
+ +
+

Quick Reference Sheet

+ +
+
STR${pcstring('STAT.0.NOTEMP.NOEQUIP')}${pcstring('STAT.0.MOD.NOTEMP.NOEQUIP')}
+
DEX${pcstring('STAT.1.NOTEMP.NOEQUIP')}${pcstring('STAT.1.MOD.NOTEMP.NOEQUIP')}
+
CON${pcstring('STAT.2.NOTEMP.NOEQUIP')}${pcstring('STAT.2.MOD.NOTEMP.NOEQUIP')}
+
INT${pcstring('STAT.3.NOTEMP.NOEQUIP')}${pcstring('STAT.3.MOD.NOTEMP.NOEQUIP')}
+
WIS${pcstring('STAT.4.NOTEMP.NOEQUIP')}${pcstring('STAT.4.MOD.NOTEMP.NOEQUIP')}
+
CHA${pcstring('STAT.5.NOTEMP.NOEQUIP')}${pcstring('STAT.5.MOD.NOTEMP.NOEQUIP')}
+
+ + + + + + + + + + + + + +
HP${pcstring('HP')}
Init${pcstring('INITIATIVEMOD')}
Move<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
AC${pcstring('AC.Total')}
Touch${pcstring('AC.Touch')}
Flat${pcstring('AC.Flatfooted')}
BAB${pcstring('ATTACK.MELEE')}
Melee${pcstring('ATTACK.MELEE.TOTAL')}
Ranged${pcstring('ATTACK.RANGED.TOTAL')}
+ + + + + + + + + + +
SaveTotalQuick Notes
Fortitude${pcstring('CHECK.0.TOTAL')}Poison, disease, body effects
Reflex${pcstring('CHECK.1.TOTAL')}Area effects, traps, avoid damage
Will${pcstring('CHECK.2.TOTAL')}Mental control, fear, compulsion
+ +
+
+ + + + + +<@loop from=0 to=(pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")')/2)?int-1 ; skq1 , skq1_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq1}')}${pcstring('SKILLSIT.${skq1}.TOTAL')}
+
+
+ + + + + +<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> +<#assign splitPoint = (skillCount/2)?int /> +<@loop from=splitPoint to=skillCount-1 ; skq2 , skq2_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq2}')}${pcstring('SKILLSIT.${skq2}.TOTAL')}
+
+
+ +
+ Quick use: keep this page visible during combat for AC, attacks, saves, and top skill totals. +
+
+ +
+

Biography

+
+ <#assign bioRaw = pcstring('BIO') /> + <#assign bioRaw = bioRaw?replace("<","<") /> + <#assign bioRaw = bioRaw?replace(">",">") /> + <#assign bioRaw = bioRaw?replace("&","&") /> + <#assign bioRaw = bioRaw?replace(""",'"') /> + <#assign bioRaw = bioRaw?replace("'","'") /> + ${bioRaw} +
+
+ +<#if (pcstring('PORTRAIT') != '')> +
+

Portrait

+
+ ${pcstring('NAME')} +
+
+ + +
+
+ PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — + Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')} +
+ + + From 336c1d64a624aeb9023e6c02e54145bb318cd142 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:23:42 +0000 Subject: [PATCH 2/8] refactor: extract additional sheet sections into components Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../xmlhtml/csheet_known_spells.htm.ftl | 18 + .../xmlhtml/csheet_prepared_spells.htm.ftl | 17 + README.md | 6 +- components/ability_influence.ftl | 51 ++ components/ability_scores.ftl | 13 + components/aoo_reference.ftl | 50 ++ components/armor_shields.ftl | 35 + components/biography.ftl | 12 + components/combat.ftl | 69 ++ components/combat_conditionals.ftl | 9 + components/combat_maneuvers.ftl | 71 ++ components/common_conditions.ftl | 119 +++ components/concentration_reference.ftl | 64 ++ components/footer.ftl | 5 + components/header.ftl | 30 + components/inventory.ftl | 1 - components/notes.ftl | 13 + components/portrait.ftl | 8 + components/prepared_spells.ftl | 1 - components/quick_view.ftl | 74 ++ components/rules_reference.ftl | 35 + components/saves.ftl | 44 + components/skills.ftl | 1 - components/special_attacks.ftl | 28 + components/special_qualities.ftl | 83 ++ components/spellbook.ftl | 1 - components/weapons.ftl | 1 - .../xmlhtml/csheet_known_spells.htm.ftl | 809 +----------------- .../xmlhtml/csheet_prepared_spells.htm.ftl | 807 +---------------- 29 files changed, 923 insertions(+), 1552 deletions(-) create mode 100644 components/ability_influence.ftl create mode 100644 components/ability_scores.ftl create mode 100644 components/aoo_reference.ftl create mode 100644 components/armor_shields.ftl create mode 100644 components/biography.ftl create mode 100644 components/combat.ftl create mode 100644 components/combat_conditionals.ftl create mode 100644 components/combat_maneuvers.ftl create mode 100644 components/common_conditions.ftl create mode 100644 components/concentration_reference.ftl create mode 100644 components/footer.ftl create mode 100644 components/header.ftl create mode 100644 components/notes.ftl create mode 100644 components/portrait.ftl create mode 100644 components/quick_view.ftl create mode 100644 components/rules_reference.ftl create mode 100644 components/saves.ftl create mode 100644 components/special_attacks.ftl create mode 100644 components/special_qualities.ftl diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 04a27fa..3485fbd 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -161,6 +161,7 @@ +

Ability Scores

@@ -175,6 +176,7 @@
+

Combat

@@ -245,6 +247,7 @@
+

Saves

@@ -290,6 +293,7 @@
+

Notes (Description)

@@ -304,6 +308,7 @@
+

Skills

@@ -460,6 +465,7 @@
+

Special Attacks

@@ -489,6 +495,7 @@
+

Weapons

@@ -663,6 +670,7 @@
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
+
Conditional Attack / Combat Modifiers:
<#assign hasCombatCond = false /> @@ -673,6 +681,7 @@ <#if !hasCombatCond>No modifiers
+

Armor & Shields

@@ -709,6 +718,7 @@
+ <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> <#if (pcstring("SPELLLISTCLASS.${class}") != '')> @@ -1016,6 +1026,7 @@
+

AoO Quick Reference

@@ -1067,6 +1078,7 @@
+

Ability Influence (Quick Calc)

@@ -1119,6 +1131,7 @@
+

Common Conditions & Modifiers

@@ -1239,6 +1252,7 @@
+

Quick Reference Sheet

@@ -1314,6 +1328,7 @@
+

Biography

@@ -1327,6 +1342,7 @@
+ <#if (pcstring('PORTRAIT') != '')>

Portrait

@@ -1336,11 +1352,13 @@
+
PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')}
+ diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index 8b6b837..791a497 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -161,6 +161,7 @@ +

Ability Scores

@@ -175,6 +176,7 @@
+

Combat

@@ -245,6 +247,7 @@
+

Saves

@@ -290,6 +293,7 @@
+

Notes (Description)

@@ -304,6 +308,7 @@
+

Skills

@@ -460,6 +465,7 @@
+

Special Attacks

@@ -489,6 +495,7 @@
+

Weapons

@@ -663,6 +670,7 @@
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
+
Conditional Attack / Combat Modifiers:
<#assign hasCombatCond = false /> @@ -673,6 +681,7 @@ <#if !hasCombatCond>No modifiers
+

Armor & Shields

@@ -951,6 +960,7 @@
+

AoO Quick Reference

@@ -1002,6 +1012,7 @@
+

Ability Influence (Quick Calc)

@@ -1054,6 +1065,7 @@
+

Common Conditions & Modifiers

@@ -1174,6 +1186,7 @@
+

Quick Reference Sheet

@@ -1249,6 +1262,7 @@
+

Biography

@@ -1262,6 +1276,7 @@
+ <#if (pcstring('PORTRAIT') != '')>

Portrait

@@ -1271,11 +1286,13 @@
+
PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')}
+ diff --git a/README.md b/README.md index e058ee1..7526ac4 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ 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. - `templates/d20/fantasy/xmlhtml/` — source sheet templates using component placeholders. -- `components/` — reusable sheet blocks (skills, feats, weapons, inventory, spellbook, prepared spells). +- `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 files in `templates/` and `components/`, then compile to `OutputSheets/`. The templates use PCGen's `${pcstring(...)}` and `<#...>` directives to pull character data at export time. @@ -15,7 +15,7 @@ If you want to change how the sheet looks or what it shows, edit files in `templ ## Component syntax -Templates use component tokens in this format: +Templates use component tokens in this format (templates are now mostly style + ordered component tokens): ``` {{ component:skills }} @@ -59,7 +59,7 @@ PCGen looks for output sheets inside its own `outputsheets` folder. The director - `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 +- `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/` diff --git a/components/ability_influence.ftl b/components/ability_influence.ftl new file mode 100644 index 0000000..0322a04 --- /dev/null +++ b/components/ability_influence.ftl @@ -0,0 +1,51 @@ + +
+

Ability Influence (Quick Calc)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AbilityScore / ModPrimary Effects On This SheetCurrent Derived Values
STR14 (+2)Melee attack, melee damage, CMB, CMD, carry/lift limitsMelee: +2 | CMB: +2 | CMD: 13
DEX12 (+1)Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex saveRanged: +1 | Init: +1 | AC ability: 1
CON14 (+2)Hit points per level/HD, Fortitude save, concentration-related checksMax HP: 10 | Hit Dice: (1d8)+2
INT11 (+0)Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisitesSee Skills table for INT-based totals
WIS16 (+3)Will save, WIS-based skills, divine spellcasting checks/DCs when applicableSee Saves/Skills and Prepared Spells sections
CHA12 (+1)Social skills, class features that key from CHA, turning/channel effects when usedSee class feature and ability notes
+
+ Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above. +
+
diff --git a/components/ability_scores.ftl b/components/ability_scores.ftl new file mode 100644 index 0000000..35b5e67 --- /dev/null +++ b/components/ability_scores.ftl @@ -0,0 +1,13 @@ + +
+

Ability Scores

+
+<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next> +
+ ${pcstring('STAT.${stat}.NAME')} + ${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')} + ${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')} +
+ +
+
diff --git a/components/aoo_reference.ftl b/components/aoo_reference.ftl new file mode 100644 index 0000000..e2c5226 --- /dev/null +++ b/components/aoo_reference.ftl @@ -0,0 +1,50 @@ + +
+

AoO Quick Reference

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ActionNotes
Move out of threatened square5-ft step, withdraw (first square), or Acrobatics can avoid
Ranged attack in meleeAny ranged attack while threatened
Cast spell in meleeCast defensively to avoid provoking (Concentration check)
Drink potion / use scrollUsing items in melee commonly provokes
Stand up from proneCommon trigger after trip
Combat maneuver (without Improved feat)Trip, disarm, grapple, etc.; improved feat usually prevents
Retrieve stowed itemDigging in backpack/pouch while threatened
Pick up itemPicking up from ground in melee
Load crossbowMost loading actions in melee provoke
Unarmed strike without Improved Unarmed StrikeBarehanded attacks vs armed foes can provoke
+
diff --git a/components/armor_shields.ftl b/components/armor_shields.ftl new file mode 100644 index 0000000..cadeeb2 --- /dev/null +++ b/components/armor_shields.ftl @@ -0,0 +1,35 @@ +
+

Armor & Shields

+
+ + + + + + + + + + +<@loop from=0 to=2 ; ar , ar_has_next> +<#if (pcstring("ARMOR.${ar}.NAME") != "")> + + + + + + + + + + + +
ItemTypeAC BonusMax Dex*Check Pen†Spell Fail
${pcstring('ARMOR.EQUIPPED.${ar}.NAME')}${pcstring('ARMOR.EQUIPPED.${ar}.TYPE')}${pcstring('ARMOR.EQUIPPED.${ar}.TOTALAC')}${pcstring('ARMOR.EQUIPPED.${ar}.MAXDEX')}${pcstring('ARMOR.EQUIPPED.${ar}.ACCHECK')}${pcstring('ARMOR.EQUIPPED.${ar}.SPELLFAIL')}
+
+ * Max Dex: Dex bonus to AC cannot exceed this — + † Check Penalty: applies to Str/Dex skill checks and attacks if not proficient — + Spell Failure: % chance arcane spells fail (divine spells ignore this) — + Heavy armor reduces speed to 20ft — sleeping in medium/heavy armor leaves you fatigued +
+
+
diff --git a/components/biography.ftl b/components/biography.ftl new file mode 100644 index 0000000..709f4cf --- /dev/null +++ b/components/biography.ftl @@ -0,0 +1,12 @@ +
+

Biography

+
+ <#assign bioRaw = pcstring('BIO') /> + <#assign bioRaw = bioRaw?replace("<","<") /> + <#assign bioRaw = bioRaw?replace(">",">") /> + <#assign bioRaw = bioRaw?replace("&","&") /> + <#assign bioRaw = bioRaw?replace(""",'"') /> + <#assign bioRaw = bioRaw?replace("'","'") /> + ${bioRaw} +
+
diff --git a/components/combat.ftl b/components/combat.ftl new file mode 100644 index 0000000..c69f639 --- /dev/null +++ b/components/combat.ftl @@ -0,0 +1,69 @@ + +

Combat

+
+ + + + + + + + + +
Max HP${pcstring('HP')}
HP Lost Today 
Current HP 
Initiative${pcstring('INITIATIVEMOD')}
Speed<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
DR<#if (pcstring('DR') != '')>${pcstring('DR')}<#else>0
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ AC + ${pcstring('AC.Total')} + :Touch${pcstring('AC.Touch')}:Flat${pcstring('AC.Flatfooted')}=Base${pcstring('AC.Base')}+Armor*${pcstring('AC.Armor')}+Shield${pcstring('AC.Shield')}+Dex${pcstring('AC.Ability')}+Size${pcstring('AC.Size')}+Nat${pcstring('AC.NaturalArmor')}+Dodge${pcstring('AC.Dodge')}+Defl${pcstring('AC.Deflection')}+Misc${pcstring('AC.Misc')}
+
+ Touch AC ignores Armor, Shield, and Natural Armor — + Flat-Footed AC ignores Dexterity and Dodge — + Deflection and Size apply to all three — + * Armor bonus reduced by check penalty if not proficient — + Max Dex cap limits Dex bonus when wearing armor +
+ + + + + + + + + + +
BAB${pcstring('ATTACK.MELEE')}
Melee Hit${pcstring('ATTACK.MELEE.TOTAL')}
Ranged Hit${pcstring('ATTACK.RANGED.TOTAL')}
Hit Die${pcstring('HITDICE')}
CMB${pcstring('VAR.CMB.INTVAL.SIGN')}
CMD${pcstring('VAR.CMD.INTVAL')}
SR<#if (pcstring('SR') != '')>${pcstring('SR')}<#else>0
+
+ CMB = BAB + Str + Size — CMD = 10 + BAB + Str + Dex + Size — + SR: attacker rolls d20 + caster level ≥ SR to affect you +
+
diff --git a/components/combat_conditionals.ftl b/components/combat_conditionals.ftl new file mode 100644 index 0000000..e6340d1 --- /dev/null +++ b/components/combat_conditionals.ftl @@ -0,0 +1,9 @@ +
+ Conditional Attack / Combat Modifiers:
+ <#assign hasCombatCond = false /> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=CombatBonus")-1') ; ab , ab_has_next> + <#assign hasCombatCond = true /> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=CombatBonus.ASPECT.CombatBonus')}
+ + <#if !hasCombatCond>No modifiers +
diff --git a/components/combat_maneuvers.ftl b/components/combat_maneuvers.ftl new file mode 100644 index 0000000..404bb34 --- /dev/null +++ b/components/combat_maneuvers.ftl @@ -0,0 +1,71 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ManeuverCMBCMDNotes
Grapple${pcstring('VAR.CMB_Grapple.INTVAL.SIGN')}${pcstring('VAR.CMD_Grapple.INTVAL')}Pin, tie up, damage, or move a grappled foe. Grappled = −2 attack/AC, no two-handed.
Trip${pcstring('VAR.CMB_Trip.INTVAL.SIGN')}<#if (pcvar("CantBeTripped") != 0)>Immune<#else>${pcstring('VAR.CMD_Trip.INTVAL')}Knock prone. Prone = −4 melee attack, −4 AC vs melee, +4 AC vs ranged.
Disarm${pcstring('VAR.CMB_Disarm.INTVAL.SIGN')}${pcstring('VAR.CMD_Disarm.INTVAL')}Knock weapon from foe. Beat CMD by 10+ = item lands 10ft away.
Bull Rush${pcstring('VAR.CMB_BullRush.INTVAL.SIGN')}${pcstring('VAR.CMD_BullRush.INTVAL')}Push foe back 5ft + 5ft per 5 over CMD. You may follow.
Sunder${pcstring('VAR.CMB_Sunder.INTVAL.SIGN')}${pcstring('VAR.CMD_Sunder.INTVAL')}Damage a held/worn item. Broken = −2 attack/damage or halved effectiveness.
Overrun${pcstring('VAR.CMB_Overrun.INTVAL.SIGN')}${pcstring('VAR.CMD_Overrun.INTVAL')}Move through foe's space. Fail = blocked; beat by 5+ = foe prone.
Dirty Trick${pcstring('VAR.CMB_DirtyTrick.INTVAL.SIGN')}${pcstring('VAR.CMD_DirtyTrick.INTVAL')}Blind, entangle, or sicken 1 round (+1 per 5 over CMD). Std action to remove.
Drag${pcstring('VAR.CMB_Drag.INTVAL.SIGN')}${pcstring('VAR.CMD_Drag.INTVAL')}Pull foe 5ft + 5ft per 5 over CMD toward you. Must move with them.
Reposition${pcstring('VAR.CMB_Reposition.INTVAL.SIGN')}${pcstring('VAR.CMD_Reposition.INTVAL')}Move foe to any adjacent square. Foe must remain adjacent.
Steal${pcstring('VAR.CMB_Steal.INTVAL.SIGN')}${pcstring('VAR.CMD_Steal.INTVAL')}Take one carried/worn item (not wielded). No free hand required.
+
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
+
diff --git a/components/common_conditions.ftl b/components/common_conditions.ftl new file mode 100644 index 0000000..95444b4 --- /dev/null +++ b/components/common_conditions.ftl @@ -0,0 +1,119 @@ + +
+

Common Conditions & Modifiers

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Condition / SituationMain EffectQuick Modifier
ShakenGeneral fear penalties−2 attacks, saves, skills, ability checks
FrightenedAs shaken; must flee if possible−2 attacks, saves, skills, checks
CourageMorale combat boost+1 to hit; +1 saves vs fear
GuidanceSingle-use bonus+1 attack, +1 save, or +1 skill check
SickenedNausea/weakness penalties−2 attacks, weapon dmg, saves, skills, checks
FatiguedTired; cannot run/charge−2 Str, −2 Dex
ExhaustedSevere fatigue; slower movement−6 Str, −6 Dex
ProneWorse in melee, better vs ranged−4 melee attacks, −4 AC vs melee, +4 AC vs ranged
Flat-FootedNot ready to reactLose Dex to AC (and dodge bonuses)
EntangledRestricted movement/offense−2 attack, −4 Dex, move at half speed
GrappledLimited actions in grapple−2 attacks and AC; no AoO
PinnedImmobile in grappleCannot move; very limited actions
StaggeredOnly one major actionEither one standard or one move action
StunnedDrop items; no actionsDrop everything, −2 AC, lose Dex to AC, no actions
FlankingAttacking same foe from opposite sides+2 melee attack
ChargeMove then strike aggressively+2 attack, −2 AC until next turn
Fighting DefensivelyTrade attack for defense−4 attack, +2 AC
Total DefenseNo attacks; full defense+4 AC
BlindedCannot see−2 AC, lose Dex to AC, −4 many Str/Dex checks, move half speed
Invisible AttackerDefender cannot see attackerAttacker often +2 melee; target loses Dex to AC; may require miss chance
Helpless / Coup de GraceHelpless targets are vulnerableCoup de grace: auto crit; target Fort save (DC 10 + damage dealt) or die
+
+ Quick reference only: if an effect conflicts with a specific spell, feat, or monster ability, use the specific rule text first. +
+
diff --git a/components/concentration_reference.ftl b/components/concentration_reference.ftl new file mode 100644 index 0000000..945699a --- /dev/null +++ b/components/concentration_reference.ftl @@ -0,0 +1,64 @@ +
+

Concentration Quick Reference

+
+ Concentration Check
+ d20 + caster level + spellcasting ability modifier + other bonuses
+ Spellcasting ability modifier is INT (wizard), WIS (cleric/druid), CHA (sorcerer/bard/oracle), etc. + <#assign hasConcClass = false /> + <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> + <#if (pcstring("SPELLLISTCLASS.${class}") != '' && pcstring("SPELLLISTCLASS.${class}.CONCENTRATION") != '')> + <#assign hasConcClass = true /> +
${pcstring('SPELLLISTCLASS.${class}')}: d20${pcstring('SPELLLISTCLASS.${class}.CONCENTRATION')} total + (CL ${pcstring('SPELLLISTCLASS.${class}.CASTERLEVEL')} + ${pcstring('SPELLLISTDCSTAT.${class}.0')}) + + + <#if !hasConcClass>
No spellcasting class concentration values found. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
When A Check Is RequiredConcentration DC
Cast defensively (to avoid provoking)15 + (2 × spell level)
Take damage while casting10 + damage dealt + spell level
Taking continuous damage while casting10 + half last damage dealt + spell level
Vigorous motion (mount, rough vehicle, choppy water)10 + spell level
Violent motion (violent weather, heavy turbulence)15 + spell level
Extra violent motion (earthquake-level disruption)20 + spell level
Weather with high wind, rain, or debris5 + spell level
Entangled while casting15 + spell level
Grappled or pinned while casting10 + grappler CMB + spell level
+
+ Common Modifiers: Combat Casting gives +4 on concentration checks to cast defensively or while grappled/pinned — + ability score increases, feats, traits, class features, and situational bonuses also apply.
+ If you fail the concentration check, the spell is lost and has no effect. +
+
diff --git a/components/footer.ftl b/components/footer.ftl new file mode 100644 index 0000000..d36cdf8 --- /dev/null +++ b/components/footer.ftl @@ -0,0 +1,5 @@ +
+
+ PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — + Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')} +
diff --git a/components/header.ftl b/components/header.ftl new file mode 100644 index 0000000..db50c17 --- /dev/null +++ b/components/header.ftl @@ -0,0 +1,30 @@ + +
+

${pcstring('NAME')}

+ + + + + + + + + + + + + + + + + + + + + + + + + +
${pcstring('PLAYERNAME')}Lvl ${pcstring('TOTALLEVELS')} (${pcstring('EXP.CURRENT')} / ${pcstring('EXP.NEXT')})${pcstring('RACE')}${pcstring('GENDER')} (${pcstring('AGE')})${pcstring('HEIGHT')} (${pcstring('SIZELONG')})${pcstring('WEIGHT')}
PlayerLevel (XP)RaceGender (Age)Height (Size)Weight
${pcstring('DEITY')}, ${pcstring('ALIGNMENT')}${pcstring('LANGUAGES')}
Deity & AlignmentLanguages
+
diff --git a/components/inventory.ftl b/components/inventory.ftl index 35044e7..c4a5bcf 100644 --- a/components/inventory.ftl +++ b/components/inventory.ftl @@ -71,4 +71,3 @@ - diff --git a/components/notes.ftl b/components/notes.ftl new file mode 100644 index 0000000..3122200 --- /dev/null +++ b/components/notes.ftl @@ -0,0 +1,13 @@ + +
+

Notes (Description)

+
+ <#assign descRaw = pcstring('DESC') /> + <#assign descRaw = descRaw?replace("<","<") /> + <#assign descRaw = descRaw?replace(">",">") /> + <#assign descRaw = descRaw?replace("&","&") /> + <#assign descRaw = descRaw?replace(""",'"') /> + <#assign descRaw = descRaw?replace("'","'") /> + ${descRaw} +
+
diff --git a/components/portrait.ftl b/components/portrait.ftl new file mode 100644 index 0000000..bef0f08 --- /dev/null +++ b/components/portrait.ftl @@ -0,0 +1,8 @@ +<#if (pcstring('PORTRAIT') != '')> +
+

Portrait

+
+ ${pcstring('NAME')} +
+
+ diff --git a/components/prepared_spells.ftl b/components/prepared_spells.ftl index 8a73fef..42f7723 100644 --- a/components/prepared_spells.ftl +++ b/components/prepared_spells.ftl @@ -60,4 +60,3 @@ - diff --git a/components/quick_view.ftl b/components/quick_view.ftl new file mode 100644 index 0000000..4050b6d --- /dev/null +++ b/components/quick_view.ftl @@ -0,0 +1,74 @@ +
+

Quick Reference Sheet

+ +
+
STR${pcstring('STAT.0.NOTEMP.NOEQUIP')}${pcstring('STAT.0.MOD.NOTEMP.NOEQUIP')}
+
DEX${pcstring('STAT.1.NOTEMP.NOEQUIP')}${pcstring('STAT.1.MOD.NOTEMP.NOEQUIP')}
+
CON${pcstring('STAT.2.NOTEMP.NOEQUIP')}${pcstring('STAT.2.MOD.NOTEMP.NOEQUIP')}
+
INT${pcstring('STAT.3.NOTEMP.NOEQUIP')}${pcstring('STAT.3.MOD.NOTEMP.NOEQUIP')}
+
WIS${pcstring('STAT.4.NOTEMP.NOEQUIP')}${pcstring('STAT.4.MOD.NOTEMP.NOEQUIP')}
+
CHA${pcstring('STAT.5.NOTEMP.NOEQUIP')}${pcstring('STAT.5.MOD.NOTEMP.NOEQUIP')}
+
+ + + + + + + + + + + + + +
HP${pcstring('HP')}
Init${pcstring('INITIATIVEMOD')}
Move<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
AC${pcstring('AC.Total')}
Touch${pcstring('AC.Touch')}
Flat${pcstring('AC.Flatfooted')}
BAB${pcstring('ATTACK.MELEE')}
Melee${pcstring('ATTACK.MELEE.TOTAL')}
Ranged${pcstring('ATTACK.RANGED.TOTAL')}
+ + + + + + + + + + +
SaveTotalQuick Notes
Fortitude${pcstring('CHECK.0.TOTAL')}Poison, disease, body effects
Reflex${pcstring('CHECK.1.TOTAL')}Area effects, traps, avoid damage
Will${pcstring('CHECK.2.TOTAL')}Mental control, fear, compulsion
+ +
+
+ + + + + +<@loop from=0 to=(pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")')/2)?int-1 ; skq1 , skq1_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq1}')}${pcstring('SKILLSIT.${skq1}.TOTAL')}
+
+
+ + + + + +<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> +<#assign splitPoint = (skillCount/2)?int /> +<@loop from=splitPoint to=skillCount-1 ; skq2 , skq2_has_next> + class="shaded"> + + + + +
SkillTotal
${pcstring('SKILLSIT.${skq2}')}${pcstring('SKILLSIT.${skq2}.TOTAL')}
+
+
+ +
+ Quick use: keep this page visible during combat for AC, attacks, saves, and top skill totals. +
+
diff --git a/components/rules_reference.ftl b/components/rules_reference.ftl new file mode 100644 index 0000000..8e5154c --- /dev/null +++ b/components/rules_reference.ftl @@ -0,0 +1,35 @@ + +
+

Rules Reference

+
+ Rest
+ 8 hrs: Recover 1 HP / level + 1 point of ability damage.
+ 24 hrs: Same, but 2 HP and points.
+ With healer present/health check: Same, but 3 HP and points. +
+
+ House Rules
+ Level Up: Player and DM roll 1 Hit Die + Con modifier for extra HP. Highest value counts. +
+
+ Common Quick Rules
+ • Flanking: +2 attack.
+ • Aid Another: DC 10 check for ally +2 attack, AC, or check.
+ • Cover / Soft Cover: +4 AC (+2 Reflex); creatures can grant soft cover.
+ • Ranged Into Melee: −4 to hit. Target 2 size > ally? −2 to hit. 3 sizes or Precise Shot? No penalty.
+ • Concealment: Roll to hit and then 1d100 that should be greater than % of concealment (20%/50%).
+ • Casting Defensively: Concentration DC = 15 + (2 × spell level).
+ • DR / Resistance: DR reduces weapon damage; resistance reduces matching energy damage.
+ • Reach / Threatened Squares: you threaten where you can melee; leaving can provoke.
+ • Swift / Immediate: one per round; immediate uses next turn's swift.
+ • Criticals: Nat 20 threatens; confirm with another hit roll (not another 20).
+ • Dying / Stabilize: <0 HP lose 1 HP/round; stabilize check DC 10 + negative HP. +
+
+ Coins
+ Gold (gp): Most common coin piece
+ Silver (sp): 10sp = 1gp
+ Copper (cp): 100cp = 10sp = 1gp
+ Platinum (pp): 1pp = 10gp +
+
diff --git a/components/saves.ftl b/components/saves.ftl new file mode 100644 index 0000000..4093330 --- /dev/null +++ b/components/saves.ftl @@ -0,0 +1,44 @@ + +
+

Saves

+ + + + + + + + + +<@loop from=0 to=pcvar('COUNT[CHECKS]-1') ; chk , chk_has_next> + + + + + + + + + +
TotalBaseMagicMisc
+ ${pcstring('CHECK.${chk}.NAME')} + <#if (pcstring('CHECK.${chk}.NAME') = 'Fortitude')>
CON (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Reflex')>
DEX (${pcstring('CHECK.${chk}.STATMOD')})
+ <#if (pcstring('CHECK.${chk}.NAME') = 'Will')>
WIS (${pcstring('CHECK.${chk}.STATMOD')})
+
${pcstring('CHECK.${chk}.TOTAL')}${pcstring('CHECK.${chk}.BASE')}${pcstring('CHECK.${chk}.MAGIC')}${pcstring('CHECK.${chk}.MISC.NOMAGIC.NOSTAT')}
+ <#assign hasSaveCond = pcvar('countdistinct("ABILITIES","ASPECT=SaveBonus")-1') /> + <#if (hasSaveCond >= 0)> +
+ Conditional Save Modifiers:
+ <@loop from=0 to=hasSaveCond ; ab , ab_has_next> + • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SaveBonus.ASPECT.SaveBonus')}
+ +
+ +
+ Fortitude (CON): resists poison, disease, death effects — + Reflex (DEX): resists area effects, traps — + Will (WIS): resists mind-affecting, compulsion, fear — + DC set by the source ability or spell +
+
diff --git a/components/skills.ftl b/components/skills.ftl index 23a5105..7d0a11e 100644 --- a/components/skills.ftl +++ b/components/skills.ftl @@ -68,4 +68,3 @@ <#if !hasSkillCond>No modifiers - diff --git a/components/special_attacks.ftl b/components/special_attacks.ftl new file mode 100644 index 0000000..ceae23a --- /dev/null +++ b/components/special_attacks.ftl @@ -0,0 +1,28 @@ +
+

Special Attacks

+
+ + <#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> + <@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sa , sa_has_next> + + + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.DESC')} +
+ <#assign saUses = pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay') /> + <#if (saUses != "")> + Uses/day: ${saUses}
+ <#assign saUsesN = pcvar('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay.INTVAL') /> + <#if (saUsesN > 0)> + <@loop from=1 to=saUsesN>☐ + + +
+
+
diff --git a/components/special_qualities.ftl b/components/special_qualities.ftl new file mode 100644 index 0000000..b315a06 --- /dev/null +++ b/components/special_qualities.ftl @@ -0,0 +1,83 @@ +
+ + +
+<#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> +
+

Special Qualities

+
+ +<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sq , sq_has_next> + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.DESC')} +
+
+
+ +
+ + +
+
+

Feats

+
+ +<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=FEAT","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; ft , ft_has_next> + + + + +
+ ${pcstring('ABILITYALL.Feat.VISIBLE.${ft}')}
+ [${pcstring('ABILITYALL.Feat.VISIBLE.${ft}.SOURCE')}]
+ ${pcstring('ABILITYALL.Feat.VISIBLE.${ft}.BENEFIT')} +
+
+
+ +<#if (pcvar('count("ABILITIES","CATEGORY=Special Ability","TYPE=Trait","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> +
+

Traits

+
+ +<@loop from=0 to=pcvar('count("ABILITIES","CATEGORY=Special Ability","TYPE=Trait","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; tr , tr_has_next> + + + + +
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait')}
+ [${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait.SOURCE')}]
+ ${pcstring('ABILITYALL.Special Ability.VISIBLE.${tr}.TYPE=Trait.DESC')} +
+
+
+ + +<#if (pcstring('DOMAIN.1') != '')> +
+

Domains

+
+ +<@loop from=1 to=pcvar('COUNT[DOMAINS]') ; dm , dm_has_next> + + + + +
+ ${pcstring('DOMAIN.${dm}')}
+ ${pcstring('DOMAIN.${dm}.POWER')} +
+
+
+ + +
+ +
diff --git a/components/spellbook.ftl b/components/spellbook.ftl index 4e3d488..64e3f3e 100644 --- a/components/spellbook.ftl +++ b/components/spellbook.ftl @@ -62,4 +62,3 @@ - diff --git a/components/weapons.ftl b/components/weapons.ftl index 3c7d648..a71fca1 100644 --- a/components/weapons.ftl +++ b/components/weapons.ftl @@ -98,4 +98,3 @@ - diff --git a/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 1655853..d6a79b2 100644 --- a/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -130,788 +130,53 @@ - -
-

${pcstring('NAME')}

- - - - - - - - - - - - - - - - - - - - - - - - - -
${pcstring('PLAYERNAME')}Lvl ${pcstring('TOTALLEVELS')} (${pcstring('EXP.CURRENT')} / ${pcstring('EXP.NEXT')})${pcstring('RACE')}${pcstring('GENDER')} (${pcstring('AGE')})${pcstring('HEIGHT')} (${pcstring('SIZELONG')})${pcstring('WEIGHT')}
PlayerLevel (XP)RaceGender (Age)Height (Size)Weight
${pcstring('DEITY')}, ${pcstring('ALIGNMENT')}${pcstring('LANGUAGES')}
Deity & AlignmentLanguages
-
- - -
-

Ability Scores

-
-<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next> -
- ${pcstring('STAT.${stat}.NAME')} - ${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')} - ${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')} -
- -
-
- - -

Combat

-
- - - - - - - - - -
Max HP${pcstring('HP')}
HP Lost Today 
Current HP 
Initiative${pcstring('INITIATIVEMOD')}
Speed<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
DR<#if (pcstring('DR') != '')>${pcstring('DR')}<#else>0
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- AC - ${pcstring('AC.Total')} - :Touch${pcstring('AC.Touch')}:Flat${pcstring('AC.Flatfooted')}=Base${pcstring('AC.Base')}+Armor*${pcstring('AC.Armor')}+Shield${pcstring('AC.Shield')}+Dex${pcstring('AC.Ability')}+Size${pcstring('AC.Size')}+Nat${pcstring('AC.NaturalArmor')}+Dodge${pcstring('AC.Dodge')}+Defl${pcstring('AC.Deflection')}+Misc${pcstring('AC.Misc')}
-
- Touch AC ignores Armor, Shield, and Natural Armor — - Flat-Footed AC ignores Dexterity and Dodge — - Deflection and Size apply to all three — - * Armor bonus reduced by check penalty if not proficient — - Max Dex cap limits Dex bonus when wearing armor -
- - - - - - - - - - -
BAB${pcstring('ATTACK.MELEE')}
Melee Hit${pcstring('ATTACK.MELEE.TOTAL')}
Ranged Hit${pcstring('ATTACK.RANGED.TOTAL')}
Hit Die${pcstring('HITDICE')}
CMB${pcstring('VAR.CMB.INTVAL.SIGN')}
CMD${pcstring('VAR.CMD.INTVAL')}
SR<#if (pcstring('SR') != '')>${pcstring('SR')}<#else>0
-
- CMB = BAB + Str + Size — CMD = 10 + BAB + Str + Dex + Size — - SR: attacker rolls d20 + caster level ≥ SR to affect you -
-
- - -
-

Saves

- - - - - - - - - -<@loop from=0 to=pcvar('COUNT[CHECKS]-1') ; chk , chk_has_next> - - - - - - - - - -
TotalBaseMagicMisc
- ${pcstring('CHECK.${chk}.NAME')} - <#if (pcstring('CHECK.${chk}.NAME') = 'Fortitude')>
CON (${pcstring('CHECK.${chk}.STATMOD')})
- <#if (pcstring('CHECK.${chk}.NAME') = 'Reflex')>
DEX (${pcstring('CHECK.${chk}.STATMOD')})
- <#if (pcstring('CHECK.${chk}.NAME') = 'Will')>
WIS (${pcstring('CHECK.${chk}.STATMOD')})
-
${pcstring('CHECK.${chk}.TOTAL')}${pcstring('CHECK.${chk}.BASE')}${pcstring('CHECK.${chk}.MAGIC')}${pcstring('CHECK.${chk}.MISC.NOMAGIC.NOSTAT')}
- <#assign hasSaveCond = pcvar('countdistinct("ABILITIES","ASPECT=SaveBonus")-1') /> - <#if (hasSaveCond >= 0)> -
- Conditional Save Modifiers:
- <@loop from=0 to=hasSaveCond ; ab , ab_has_next> - • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SaveBonus.ASPECT.SaveBonus')}
- -
- -
- Fortitude (CON): resists poison, disease, death effects — - Reflex (DEX): resists area effects, traps — - Will (WIS): resists mind-affecting, compulsion, fear — - DC set by the source ability or spell -
-
- - -
-

Notes (Description)

-
- <#assign descRaw = pcstring('DESC') /> - <#assign descRaw = descRaw?replace("<","<") /> - <#assign descRaw = descRaw?replace(">",">") /> - <#assign descRaw = descRaw?replace("&","&") /> - <#assign descRaw = descRaw?replace(""",'"') /> - <#assign descRaw = descRaw?replace("'","'") /> - ${descRaw} -
-
+{{ component:header }} + +{{ component:ability_scores }} + +{{ component:combat }} + +{{ component:saves }} + +{{ component:notes }} {{ component:skills }} -
- - -
-<#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> -
-

Special Qualities

-
- -<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sq , sq_has_next> - - - - -
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality')}
- [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.SOURCE')}]
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.DESC')} -
-
-
- -
- -{{ component:feats }} -
- - - -
-

Special Attacks

-
- - <#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> - <@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sa , sa_has_next> - - - - - - -
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack')}
- [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.SOURCE')}]
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.DESC')} -
- <#assign saUses = pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay') /> - <#if (saUses != "")> - Uses/day: ${saUses}
- <#assign saUsesN = pcvar('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay.INTVAL') /> - <#if (saUsesN > 0)> - <@loop from=1 to=saUsesN>☐ - - -
-
-
+ +{{ component:special_qualities }} + +{{ component:special_attacks }} {{ component:weapons }} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ManeuverCMBCMDNotes
Grapple${pcstring('VAR.CMB_Grapple.INTVAL.SIGN')}${pcstring('VAR.CMD_Grapple.INTVAL')}Pin, tie up, damage, or move a grappled foe. Grappled = −2 attack/AC, no two-handed.
Trip${pcstring('VAR.CMB_Trip.INTVAL.SIGN')}<#if (pcvar("CantBeTripped") != 0)>Immune<#else>${pcstring('VAR.CMD_Trip.INTVAL')}Knock prone. Prone = −4 melee attack, −4 AC vs melee, +4 AC vs ranged.
Disarm${pcstring('VAR.CMB_Disarm.INTVAL.SIGN')}${pcstring('VAR.CMD_Disarm.INTVAL')}Knock weapon from foe. Beat CMD by 10+ = item lands 10ft away.
Bull Rush${pcstring('VAR.CMB_BullRush.INTVAL.SIGN')}${pcstring('VAR.CMD_BullRush.INTVAL')}Push foe back 5ft + 5ft per 5 over CMD. You may follow.
Sunder${pcstring('VAR.CMB_Sunder.INTVAL.SIGN')}${pcstring('VAR.CMD_Sunder.INTVAL')}Damage a held/worn item. Broken = −2 attack/damage or halved effectiveness.
Overrun${pcstring('VAR.CMB_Overrun.INTVAL.SIGN')}${pcstring('VAR.CMD_Overrun.INTVAL')}Move through foe's space. Fail = blocked; beat by 5+ = foe prone.
Dirty Trick${pcstring('VAR.CMB_DirtyTrick.INTVAL.SIGN')}${pcstring('VAR.CMD_DirtyTrick.INTVAL')}Blind, entangle, or sicken 1 round (+1 per 5 over CMD). Std action to remove.
Drag${pcstring('VAR.CMB_Drag.INTVAL.SIGN')}${pcstring('VAR.CMD_Drag.INTVAL')}Pull foe 5ft + 5ft per 5 over CMD toward you. Must move with them.
Reposition${pcstring('VAR.CMB_Reposition.INTVAL.SIGN')}${pcstring('VAR.CMD_Reposition.INTVAL')}Move foe to any adjacent square. Foe must remain adjacent.
Steal${pcstring('VAR.CMB_Steal.INTVAL.SIGN')}${pcstring('VAR.CMD_Steal.INTVAL')}Take one carried/worn item (not wielded). No free hand required.
-
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
-
- -
- Conditional Attack / Combat Modifiers:
- <#assign hasCombatCond = false /> - <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=CombatBonus")-1') ; ab , ab_has_next> - <#assign hasCombatCond = true /> - • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=CombatBonus.ASPECT.CombatBonus')}
- - <#if !hasCombatCond>No modifiers -
- -
-

Armor & Shields

-
- - - - - - - - - - -<@loop from=0 to=2 ; ar , ar_has_next> -<#if (pcstring("ARMOR.${ar}.NAME") != "")> - - - - - - - - - - - -
ItemTypeAC BonusMax Dex*Check Pen†Spell Fail
${pcstring('ARMOR.EQUIPPED.${ar}.NAME')}${pcstring('ARMOR.EQUIPPED.${ar}.TYPE')}${pcstring('ARMOR.EQUIPPED.${ar}.TOTALAC')}${pcstring('ARMOR.EQUIPPED.${ar}.MAXDEX')}${pcstring('ARMOR.EQUIPPED.${ar}.ACCHECK')}${pcstring('ARMOR.EQUIPPED.${ar}.SPELLFAIL')}
-
- * Max Dex: Dex bonus to AC cannot exceed this — - † Check Penalty: applies to Str/Dex skill checks and attacks if not proficient — - Spell Failure: % chance arcane spells fail (divine spells ignore this) — - Heavy armor reduces speed to 20ft — sleeping in medium/heavy armor leaves you fatigued -
-
-
+ +{{ component:combat_maneuvers }} + +{{ component:combat_conditionals }} + +{{ component:armor_shields }} {{ component:spellbook }} + {{ component:prepared_spells }} -
-

Concentration Quick Reference

-
- Concentration Check
- d20 + caster level + spellcasting ability modifier + other bonuses
- Spellcasting ability modifier is INT (wizard), WIS (cleric/druid), CHA (sorcerer/bard/oracle), etc. - <#assign hasConcClass = false /> - <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> - <#if (pcstring("SPELLLISTCLASS.${class}") != '' && pcstring("SPELLLISTCLASS.${class}.CONCENTRATION") != '')> - <#assign hasConcClass = true /> -
${pcstring('SPELLLISTCLASS.${class}')}: d20${pcstring('SPELLLISTCLASS.${class}.CONCENTRATION')} total - (CL ${pcstring('SPELLLISTCLASS.${class}.CASTERLEVEL')} + ${pcstring('SPELLLISTDCSTAT.${class}.0')}) - - - <#if !hasConcClass>
No spellcasting class concentration values found. -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
When A Check Is RequiredConcentration DC
Cast defensively (to avoid provoking)15 + (2 × spell level)
Take damage while casting10 + damage dealt + spell level
Taking continuous damage while casting10 + half last damage dealt + spell level
Vigorous motion (mount, rough vehicle, choppy water)10 + spell level
Violent motion (violent weather, heavy turbulence)15 + spell level
Extra violent motion (earthquake-level disruption)20 + spell level
Weather with high wind, rain, or debris5 + spell level
Entangled while casting15 + spell level
Grappled or pinned while casting10 + grappler CMB + spell level
-
- Common Modifiers: Combat Casting gives +4 on concentration checks to cast defensively or while grappled/pinned — - ability score increases, feats, traits, class features, and situational bonuses also apply.
- If you fail the concentration check, the spell is lost and has no effect. -
-
+{{ component:concentration_reference }} {{ component:inventory }} - -
-

Rules Reference

-
- Rest
- 8 hrs: Recover 1 HP / level + 1 point of ability damage.
- 24 hrs: Same, but 2 HP and points.
- With healer present/health check: Same, but 3 HP and points. -
-
- House Rules
- Level Up: Player and DM roll 1 Hit Die + Con modifier for extra HP. Highest value counts. -
-
- Common Quick Rules
- • Flanking: +2 attack.
- • Aid Another: DC 10 check for ally +2 attack, AC, or check.
- • Cover / Soft Cover: +4 AC (+2 Reflex); creatures can grant soft cover.
- • Ranged Into Melee: −4 to hit. Target 2 size > ally? −2 to hit. 3 sizes or Precise Shot? No penalty.
- • Concealment: Roll to hit and then 1d100 that should be greater than % of concealment (20%/50%).
- • Casting Defensively: Concentration DC = 15 + (2 × spell level).
- • DR / Resistance: DR reduces weapon damage; resistance reduces matching energy damage.
- • Reach / Threatened Squares: you threaten where you can melee; leaving can provoke.
- • Swift / Immediate: one per round; immediate uses next turn's swift.
- • Criticals: Nat 20 threatens; confirm with another hit roll (not another 20).
- • Dying / Stabilize: <0 HP lose 1 HP/round; stabilize check DC 10 + negative HP. -
-
- Coins
- Gold (gp): Most common coin piece
- Silver (sp): 10sp = 1gp
- Copper (cp): 100cp = 10sp = 1gp
- Platinum (pp): 1pp = 10gp -
-
- - -
-

AoO Quick Reference

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ActionNotes
Move out of threatened square5-ft step, withdraw (first square), or Acrobatics can avoid
Ranged attack in meleeAny ranged attack while threatened
Cast spell in meleeCast defensively to avoid provoking (Concentration check)
Drink potion / use scrollUsing items in melee commonly provokes
Stand up from proneCommon trigger after trip
Combat maneuver (without Improved feat)Trip, disarm, grapple, etc.; improved feat usually prevents
Retrieve stowed itemDigging in backpack/pouch while threatened
Pick up itemPicking up from ground in melee
Load crossbowMost loading actions in melee provoke
Unarmed strike without Improved Unarmed StrikeBarehanded attacks vs armed foes can provoke
-
- - -
-

Ability Influence (Quick Calc)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AbilityScore / ModPrimary Effects On This SheetCurrent Derived Values
STR14 (+2)Melee attack, melee damage, CMB, CMD, carry/lift limitsMelee: +2 | CMB: +2 | CMD: 13
DEX12 (+1)Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex saveRanged: +1 | Init: +1 | AC ability: 1
CON14 (+2)Hit points per level/HD, Fortitude save, concentration-related checksMax HP: 10 | Hit Dice: (1d8)+2
INT11 (+0)Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisitesSee Skills table for INT-based totals
WIS16 (+3)Will save, WIS-based skills, divine spellcasting checks/DCs when applicableSee Saves/Skills and Prepared Spells sections
CHA12 (+1)Social skills, class features that key from CHA, turning/channel effects when usedSee class feature and ability notes
-
- Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above. -
-
- - -
-

Common Conditions & Modifiers

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Condition / SituationMain EffectQuick Modifier
ShakenGeneral fear penalties−2 attacks, saves, skills, ability checks
FrightenedAs shaken; must flee if possible−2 attacks, saves, skills, checks
CourageMorale combat boost+1 to hit; +1 saves vs fear
GuidanceSingle-use bonus+1 attack, +1 save, or +1 skill check
SickenedNausea/weakness penalties−2 attacks, weapon dmg, saves, skills, checks
FatiguedTired; cannot run/charge−2 Str, −2 Dex
ExhaustedSevere fatigue; slower movement−6 Str, −6 Dex
ProneWorse in melee, better vs ranged−4 melee attacks, −4 AC vs melee, +4 AC vs ranged
Flat-FootedNot ready to reactLose Dex to AC (and dodge bonuses)
EntangledRestricted movement/offense−2 attack, −4 Dex, move at half speed
GrappledLimited actions in grapple−2 attacks and AC; no AoO
PinnedImmobile in grappleCannot move; very limited actions
StaggeredOnly one major actionEither one standard or one move action
StunnedDrop items; no actionsDrop everything, −2 AC, lose Dex to AC, no actions
FlankingAttacking same foe from opposite sides+2 melee attack
ChargeMove then strike aggressively+2 attack, −2 AC until next turn
Fighting DefensivelyTrade attack for defense−4 attack, +2 AC
Total DefenseNo attacks; full defense+4 AC
BlindedCannot see−2 AC, lose Dex to AC, −4 many Str/Dex checks, move half speed
Invisible AttackerDefender cannot see attackerAttacker often +2 melee; target loses Dex to AC; may require miss chance
Helpless / Coup de GraceHelpless targets are vulnerableCoup de grace: auto crit; target Fort save (DC 10 + damage dealt) or die
-
- Quick reference only: if an effect conflicts with a specific spell, feat, or monster ability, use the specific rule text first. -
-
- -
-

Quick Reference Sheet

- -
-
STR${pcstring('STAT.0.NOTEMP.NOEQUIP')}${pcstring('STAT.0.MOD.NOTEMP.NOEQUIP')}
-
DEX${pcstring('STAT.1.NOTEMP.NOEQUIP')}${pcstring('STAT.1.MOD.NOTEMP.NOEQUIP')}
-
CON${pcstring('STAT.2.NOTEMP.NOEQUIP')}${pcstring('STAT.2.MOD.NOTEMP.NOEQUIP')}
-
INT${pcstring('STAT.3.NOTEMP.NOEQUIP')}${pcstring('STAT.3.MOD.NOTEMP.NOEQUIP')}
-
WIS${pcstring('STAT.4.NOTEMP.NOEQUIP')}${pcstring('STAT.4.MOD.NOTEMP.NOEQUIP')}
-
CHA${pcstring('STAT.5.NOTEMP.NOEQUIP')}${pcstring('STAT.5.MOD.NOTEMP.NOEQUIP')}
-
- - - - - - - - - - - - - -
HP${pcstring('HP')}
Init${pcstring('INITIATIVEMOD')}
Move<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
AC${pcstring('AC.Total')}
Touch${pcstring('AC.Touch')}
Flat${pcstring('AC.Flatfooted')}
BAB${pcstring('ATTACK.MELEE')}
Melee${pcstring('ATTACK.MELEE.TOTAL')}
Ranged${pcstring('ATTACK.RANGED.TOTAL')}
- - - - - - - - - - -
SaveTotalQuick Notes
Fortitude${pcstring('CHECK.0.TOTAL')}Poison, disease, body effects
Reflex${pcstring('CHECK.1.TOTAL')}Area effects, traps, avoid damage
Will${pcstring('CHECK.2.TOTAL')}Mental control, fear, compulsion
- -
-
- - - - - -<@loop from=0 to=(pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")')/2)?int-1 ; skq1 , skq1_has_next> - class="shaded"> - - - - -
SkillTotal
${pcstring('SKILLSIT.${skq1}')}${pcstring('SKILLSIT.${skq1}.TOTAL')}
-
-
- - - - - -<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> -<#assign splitPoint = (skillCount/2)?int /> -<@loop from=splitPoint to=skillCount-1 ; skq2 , skq2_has_next> - class="shaded"> - - - - -
SkillTotal
${pcstring('SKILLSIT.${skq2}')}${pcstring('SKILLSIT.${skq2}.TOTAL')}
-
-
- -
- Quick use: keep this page visible during combat for AC, attacks, saves, and top skill totals. -
-
- -
-

Biography

-
- <#assign bioRaw = pcstring('BIO') /> - <#assign bioRaw = bioRaw?replace("<","<") /> - <#assign bioRaw = bioRaw?replace(">",">") /> - <#assign bioRaw = bioRaw?replace("&","&") /> - <#assign bioRaw = bioRaw?replace(""",'"') /> - <#assign bioRaw = bioRaw?replace("'","'") /> - ${bioRaw} -
-
- -<#if (pcstring('PORTRAIT') != '')> -
-

Portrait

-
- ${pcstring('NAME')} -
-
- - -
-
- PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — - Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')} -
+ +{{ component:rules_reference }} + +{{ component:aoo_reference }} + +{{ component:ability_influence }} + +{{ component:common_conditions }} + +{{ component:quick_view }} + +{{ component:biography }} + +{{ component:portrait }} + +{{ component:footer }} diff --git a/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index ad0ef0b..d33137b 100644 --- a/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -130,788 +130,51 @@ - -
-

${pcstring('NAME')}

- - - - - - - - - - - - - - - - - - - - - - - - - -
${pcstring('PLAYERNAME')}Lvl ${pcstring('TOTALLEVELS')} (${pcstring('EXP.CURRENT')} / ${pcstring('EXP.NEXT')})${pcstring('RACE')}${pcstring('GENDER')} (${pcstring('AGE')})${pcstring('HEIGHT')} (${pcstring('SIZELONG')})${pcstring('WEIGHT')}
PlayerLevel (XP)RaceGender (Age)Height (Size)Weight
${pcstring('DEITY')}, ${pcstring('ALIGNMENT')}${pcstring('LANGUAGES')}
Deity & AlignmentLanguages
-
- - -
-

Ability Scores

-
-<@loop from=0 to=pcvar('COUNT[STATS]-1') ; stat , stat_has_next> -
- ${pcstring('STAT.${stat}.NAME')} - ${pcstring('STAT.${stat}.NOTEMP.NOEQUIP')} - ${pcstring('STAT.${stat}.MOD.NOTEMP.NOEQUIP')} -
- -
-
- - -

Combat

-
- - - - - - - - - -
Max HP${pcstring('HP')}
HP Lost Today 
Current HP 
Initiative${pcstring('INITIATIVEMOD')}
Speed<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
DR<#if (pcstring('DR') != '')>${pcstring('DR')}<#else>0
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - -
- AC - ${pcstring('AC.Total')} - :Touch${pcstring('AC.Touch')}:Flat${pcstring('AC.Flatfooted')}=Base${pcstring('AC.Base')}+Armor*${pcstring('AC.Armor')}+Shield${pcstring('AC.Shield')}+Dex${pcstring('AC.Ability')}+Size${pcstring('AC.Size')}+Nat${pcstring('AC.NaturalArmor')}+Dodge${pcstring('AC.Dodge')}+Defl${pcstring('AC.Deflection')}+Misc${pcstring('AC.Misc')}
-
- Touch AC ignores Armor, Shield, and Natural Armor — - Flat-Footed AC ignores Dexterity and Dodge — - Deflection and Size apply to all three — - * Armor bonus reduced by check penalty if not proficient — - Max Dex cap limits Dex bonus when wearing armor -
- - - - - - - - - - -
BAB${pcstring('ATTACK.MELEE')}
Melee Hit${pcstring('ATTACK.MELEE.TOTAL')}
Ranged Hit${pcstring('ATTACK.RANGED.TOTAL')}
Hit Die${pcstring('HITDICE')}
CMB${pcstring('VAR.CMB.INTVAL.SIGN')}
CMD${pcstring('VAR.CMD.INTVAL')}
SR<#if (pcstring('SR') != '')>${pcstring('SR')}<#else>0
-
- CMB = BAB + Str + Size — CMD = 10 + BAB + Str + Dex + Size — - SR: attacker rolls d20 + caster level ≥ SR to affect you -
-
- - -
-

Saves

- - - - - - - - - -<@loop from=0 to=pcvar('COUNT[CHECKS]-1') ; chk , chk_has_next> - - - - - - - - - -
TotalBaseMagicMisc
- ${pcstring('CHECK.${chk}.NAME')} - <#if (pcstring('CHECK.${chk}.NAME') = 'Fortitude')>
CON (${pcstring('CHECK.${chk}.STATMOD')})
- <#if (pcstring('CHECK.${chk}.NAME') = 'Reflex')>
DEX (${pcstring('CHECK.${chk}.STATMOD')})
- <#if (pcstring('CHECK.${chk}.NAME') = 'Will')>
WIS (${pcstring('CHECK.${chk}.STATMOD')})
-
${pcstring('CHECK.${chk}.TOTAL')}${pcstring('CHECK.${chk}.BASE')}${pcstring('CHECK.${chk}.MAGIC')}${pcstring('CHECK.${chk}.MISC.NOMAGIC.NOSTAT')}
- <#assign hasSaveCond = pcvar('countdistinct("ABILITIES","ASPECT=SaveBonus")-1') /> - <#if (hasSaveCond >= 0)> -
- Conditional Save Modifiers:
- <@loop from=0 to=hasSaveCond ; ab , ab_has_next> - • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=SaveBonus.ASPECT.SaveBonus')}
- -
- -
- Fortitude (CON): resists poison, disease, death effects — - Reflex (DEX): resists area effects, traps — - Will (WIS): resists mind-affecting, compulsion, fear — - DC set by the source ability or spell -
-
- - -
-

Notes (Description)

-
- <#assign descRaw = pcstring('DESC') /> - <#assign descRaw = descRaw?replace("<","<") /> - <#assign descRaw = descRaw?replace(">",">") /> - <#assign descRaw = descRaw?replace("&","&") /> - <#assign descRaw = descRaw?replace(""",'"') /> - <#assign descRaw = descRaw?replace("'","'") /> - ${descRaw} -
-
+{{ component:header }} + +{{ component:ability_scores }} + +{{ component:combat }} + +{{ component:saves }} + +{{ component:notes }} {{ component:skills }} -
- - -
-<#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> -
-

Special Qualities

-
- -<@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialQuality","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sq , sq_has_next> - - - - -
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality')}
- [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.SOURCE')}]
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sq}.TYPE=SpecialQuality.DESC')} -
-
-
- -
- -{{ component:feats }} -
- - - -
-

Special Attacks

-
- - <#if (pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")') > 0)> - <@loop from=0 to=pcvar('countdistinct("ABILITIES","CATEGORY=Special Ability","TYPE=SpecialAttack","VISIBILITY=DEFAULT[or]VISIBILITY=OUTPUT_ONLY")-1') ; sa , sa_has_next> - - - - - - -
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack')}
- [${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.SOURCE')}]
- ${pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.DESC')} -
- <#assign saUses = pcstring('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay') /> - <#if (saUses != "")> - Uses/day: ${saUses}
- <#assign saUsesN = pcvar('ABILITYALL.Special Ability.VISIBLE.${sa}.TYPE=SpecialAttack.ASPECT.UsesPerDay.INTVAL') /> - <#if (saUsesN > 0)> - <@loop from=1 to=saUsesN>☐ - - -
-
-
+ +{{ component:special_qualities }} + +{{ component:special_attacks }} {{ component:weapons }} -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ManeuverCMBCMDNotes
Grapple${pcstring('VAR.CMB_Grapple.INTVAL.SIGN')}${pcstring('VAR.CMD_Grapple.INTVAL')}Pin, tie up, damage, or move a grappled foe. Grappled = −2 attack/AC, no two-handed.
Trip${pcstring('VAR.CMB_Trip.INTVAL.SIGN')}<#if (pcvar("CantBeTripped") != 0)>Immune<#else>${pcstring('VAR.CMD_Trip.INTVAL')}Knock prone. Prone = −4 melee attack, −4 AC vs melee, +4 AC vs ranged.
Disarm${pcstring('VAR.CMB_Disarm.INTVAL.SIGN')}${pcstring('VAR.CMD_Disarm.INTVAL')}Knock weapon from foe. Beat CMD by 10+ = item lands 10ft away.
Bull Rush${pcstring('VAR.CMB_BullRush.INTVAL.SIGN')}${pcstring('VAR.CMD_BullRush.INTVAL')}Push foe back 5ft + 5ft per 5 over CMD. You may follow.
Sunder${pcstring('VAR.CMB_Sunder.INTVAL.SIGN')}${pcstring('VAR.CMD_Sunder.INTVAL')}Damage a held/worn item. Broken = −2 attack/damage or halved effectiveness.
Overrun${pcstring('VAR.CMB_Overrun.INTVAL.SIGN')}${pcstring('VAR.CMD_Overrun.INTVAL')}Move through foe's space. Fail = blocked; beat by 5+ = foe prone.
Dirty Trick${pcstring('VAR.CMB_DirtyTrick.INTVAL.SIGN')}${pcstring('VAR.CMD_DirtyTrick.INTVAL')}Blind, entangle, or sicken 1 round (+1 per 5 over CMD). Std action to remove.
Drag${pcstring('VAR.CMB_Drag.INTVAL.SIGN')}${pcstring('VAR.CMD_Drag.INTVAL')}Pull foe 5ft + 5ft per 5 over CMD toward you. Must move with them.
Reposition${pcstring('VAR.CMB_Reposition.INTVAL.SIGN')}${pcstring('VAR.CMD_Reposition.INTVAL')}Move foe to any adjacent square. Foe must remain adjacent.
Steal${pcstring('VAR.CMB_Steal.INTVAL.SIGN')}${pcstring('VAR.CMD_Steal.INTVAL')}Take one carried/worn item (not wielded). No free hand required.
-
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
-
- -
- Conditional Attack / Combat Modifiers:
- <#assign hasCombatCond = false /> - <@loop from=0 to=pcvar('countdistinct("ABILITIES","ASPECT=CombatBonus")-1') ; ab , ab_has_next> - <#assign hasCombatCond = true /> - • ${pcstring('ABILITYALL.ANY.${ab}.ASPECT=CombatBonus.ASPECT.CombatBonus')}
- - <#if !hasCombatCond>No modifiers -
- -
-

Armor & Shields

-
- - - - - - - - - - -<@loop from=0 to=2 ; ar , ar_has_next> -<#if (pcstring("ARMOR.${ar}.NAME") != "")> - - - - - - - - - - - -
ItemTypeAC BonusMax Dex*Check Pen†Spell Fail
${pcstring('ARMOR.EQUIPPED.${ar}.NAME')}${pcstring('ARMOR.EQUIPPED.${ar}.TYPE')}${pcstring('ARMOR.EQUIPPED.${ar}.TOTALAC')}${pcstring('ARMOR.EQUIPPED.${ar}.MAXDEX')}${pcstring('ARMOR.EQUIPPED.${ar}.ACCHECK')}${pcstring('ARMOR.EQUIPPED.${ar}.SPELLFAIL')}
-
- * Max Dex: Dex bonus to AC cannot exceed this — - † Check Penalty: applies to Str/Dex skill checks and attacks if not proficient — - Spell Failure: % chance arcane spells fail (divine spells ignore this) — - Heavy armor reduces speed to 20ft — sleeping in medium/heavy armor leaves you fatigued -
-
-
+{{ component:combat_maneuvers }} + +{{ component:combat_conditionals }} + +{{ component:armor_shields }} {{ component:prepared_spells }} -
-

Concentration Quick Reference

-
- Concentration Check
- d20 + caster level + spellcasting ability modifier + other bonuses
- Spellcasting ability modifier is INT (wizard), WIS (cleric/druid), CHA (sorcerer/bard/oracle), etc. - <#assign hasConcClass = false /> - <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> - <#if (pcstring("SPELLLISTCLASS.${class}") != '' && pcstring("SPELLLISTCLASS.${class}.CONCENTRATION") != '')> - <#assign hasConcClass = true /> -
${pcstring('SPELLLISTCLASS.${class}')}: d20${pcstring('SPELLLISTCLASS.${class}.CONCENTRATION')} total - (CL ${pcstring('SPELLLISTCLASS.${class}.CASTERLEVEL')} + ${pcstring('SPELLLISTDCSTAT.${class}.0')}) - - - <#if !hasConcClass>
No spellcasting class concentration values found. -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
When A Check Is RequiredConcentration DC
Cast defensively (to avoid provoking)15 + (2 × spell level)
Take damage while casting10 + damage dealt + spell level
Taking continuous damage while casting10 + half last damage dealt + spell level
Vigorous motion (mount, rough vehicle, choppy water)10 + spell level
Violent motion (violent weather, heavy turbulence)15 + spell level
Extra violent motion (earthquake-level disruption)20 + spell level
Weather with high wind, rain, or debris5 + spell level
Entangled while casting15 + spell level
Grappled or pinned while casting10 + grappler CMB + spell level
-
- Common Modifiers: Combat Casting gives +4 on concentration checks to cast defensively or while grappled/pinned — - ability score increases, feats, traits, class features, and situational bonuses also apply.
- If you fail the concentration check, the spell is lost and has no effect. -
-
+{{ component:concentration_reference }} {{ component:inventory }} - -
-

Rules Reference

-
- Rest
- 8 hrs: Recover 1 HP / level + 1 point of ability damage.
- 24 hrs: Same, but 2 HP and points.
- With healer present/health check: Same, but 3 HP and points. -
-
- House Rules
- Level Up: Player and DM roll 1 Hit Die + Con modifier for extra HP. Highest value counts. -
-
- Common Quick Rules
- • Flanking: +2 attack.
- • Aid Another: DC 10 check for ally +2 attack, AC, or check.
- • Cover / Soft Cover: +4 AC (+2 Reflex); creatures can grant soft cover.
- • Ranged Into Melee: −4 to hit. Target 2 size > ally? −2 to hit. 3 sizes or Precise Shot? No penalty.
- • Concealment: Roll to hit and then 1d100 that should be greater than % of concealment (20%/50%).
- • Casting Defensively: Concentration DC = 15 + (2 × spell level).
- • DR / Resistance: DR reduces weapon damage; resistance reduces matching energy damage.
- • Reach / Threatened Squares: you threaten where you can melee; leaving can provoke.
- • Swift / Immediate: one per round; immediate uses next turn's swift.
- • Criticals: Nat 20 threatens; confirm with another hit roll (not another 20).
- • Dying / Stabilize: <0 HP lose 1 HP/round; stabilize check DC 10 + negative HP. -
-
- Coins
- Gold (gp): Most common coin piece
- Silver (sp): 10sp = 1gp
- Copper (cp): 100cp = 10sp = 1gp
- Platinum (pp): 1pp = 10gp -
-
- - -
-

AoO Quick Reference

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
ActionNotes
Move out of threatened square5-ft step, withdraw (first square), or Acrobatics can avoid
Ranged attack in meleeAny ranged attack while threatened
Cast spell in meleeCast defensively to avoid provoking (Concentration check)
Drink potion / use scrollUsing items in melee commonly provokes
Stand up from proneCommon trigger after trip
Combat maneuver (without Improved feat)Trip, disarm, grapple, etc.; improved feat usually prevents
Retrieve stowed itemDigging in backpack/pouch while threatened
Pick up itemPicking up from ground in melee
Load crossbowMost loading actions in melee provoke
Unarmed strike without Improved Unarmed StrikeBarehanded attacks vs armed foes can provoke
-
- - -
-

Ability Influence (Quick Calc)

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
AbilityScore / ModPrimary Effects On This SheetCurrent Derived Values
STR14 (+2)Melee attack, melee damage, CMB, CMD, carry/lift limitsMelee: +2 | CMB: +2 | CMD: 13
DEX12 (+1)Ranged attack, initiative, AC ability bonus, CMD, Dexterity skills, Reflex saveRanged: +1 | Init: +1 | AC ability: 1
CON14 (+2)Hit points per level/HD, Fortitude save, concentration-related checksMax HP: 10 | Hit Dice: (1d8)+2
INT11 (+0)Bonus skill ranks/level, INT-based skills, knowledge checks, some feat prerequisitesSee Skills table for INT-based totals
WIS16 (+3)Will save, WIS-based skills, divine spellcasting checks/DCs when applicableSee Saves/Skills and Prepared Spells sections
CHA12 (+1)Social skills, class features that key from CHA, turning/channel effects when usedSee class feature and ability notes
-
- Ability damage/drain quick rule: every 2 points usually changes the ability modifier by 1, which then shifts all dependent values above. -
-
- - -
-

Common Conditions & Modifiers

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Condition / SituationMain EffectQuick Modifier
ShakenGeneral fear penalties−2 attacks, saves, skills, ability checks
FrightenedAs shaken; must flee if possible−2 attacks, saves, skills, checks
CourageMorale combat boost+1 to hit; +1 saves vs fear
GuidanceSingle-use bonus+1 attack, +1 save, or +1 skill check
SickenedNausea/weakness penalties−2 attacks, weapon dmg, saves, skills, checks
FatiguedTired; cannot run/charge−2 Str, −2 Dex
ExhaustedSevere fatigue; slower movement−6 Str, −6 Dex
ProneWorse in melee, better vs ranged−4 melee attacks, −4 AC vs melee, +4 AC vs ranged
Flat-FootedNot ready to reactLose Dex to AC (and dodge bonuses)
EntangledRestricted movement/offense−2 attack, −4 Dex, move at half speed
GrappledLimited actions in grapple−2 attacks and AC; no AoO
PinnedImmobile in grappleCannot move; very limited actions
StaggeredOnly one major actionEither one standard or one move action
StunnedDrop items; no actionsDrop everything, −2 AC, lose Dex to AC, no actions
FlankingAttacking same foe from opposite sides+2 melee attack
ChargeMove then strike aggressively+2 attack, −2 AC until next turn
Fighting DefensivelyTrade attack for defense−4 attack, +2 AC
Total DefenseNo attacks; full defense+4 AC
BlindedCannot see−2 AC, lose Dex to AC, −4 many Str/Dex checks, move half speed
Invisible AttackerDefender cannot see attackerAttacker often +2 melee; target loses Dex to AC; may require miss chance
Helpless / Coup de GraceHelpless targets are vulnerableCoup de grace: auto crit; target Fort save (DC 10 + damage dealt) or die
-
- Quick reference only: if an effect conflicts with a specific spell, feat, or monster ability, use the specific rule text first. -
-
- -
-

Quick Reference Sheet

- -
-
STR${pcstring('STAT.0.NOTEMP.NOEQUIP')}${pcstring('STAT.0.MOD.NOTEMP.NOEQUIP')}
-
DEX${pcstring('STAT.1.NOTEMP.NOEQUIP')}${pcstring('STAT.1.MOD.NOTEMP.NOEQUIP')}
-
CON${pcstring('STAT.2.NOTEMP.NOEQUIP')}${pcstring('STAT.2.MOD.NOTEMP.NOEQUIP')}
-
INT${pcstring('STAT.3.NOTEMP.NOEQUIP')}${pcstring('STAT.3.MOD.NOTEMP.NOEQUIP')}
-
WIS${pcstring('STAT.4.NOTEMP.NOEQUIP')}${pcstring('STAT.4.MOD.NOTEMP.NOEQUIP')}
-
CHA${pcstring('STAT.5.NOTEMP.NOEQUIP')}${pcstring('STAT.5.MOD.NOTEMP.NOEQUIP')}
-
- - - - - - - - - - - - - -
HP${pcstring('HP')}
Init${pcstring('INITIATIVEMOD')}
Move<@loop from=0 to=pcvar('COUNT[MOVE]-1') ; mv , mv_has_next>${pcstring('MOVE.${mv}.RATE')}
AC${pcstring('AC.Total')}
Touch${pcstring('AC.Touch')}
Flat${pcstring('AC.Flatfooted')}
BAB${pcstring('ATTACK.MELEE')}
Melee${pcstring('ATTACK.MELEE.TOTAL')}
Ranged${pcstring('ATTACK.RANGED.TOTAL')}
- - - - - - - - - - -
SaveTotalQuick Notes
Fortitude${pcstring('CHECK.0.TOTAL')}Poison, disease, body effects
Reflex${pcstring('CHECK.1.TOTAL')}Area effects, traps, avoid damage
Will${pcstring('CHECK.2.TOTAL')}Mental control, fear, compulsion
- -
-
- - - - - -<@loop from=0 to=(pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")')/2)?int-1 ; skq1 , skq1_has_next> - class="shaded"> - - - - -
SkillTotal
${pcstring('SKILLSIT.${skq1}')}${pcstring('SKILLSIT.${skq1}.TOTAL')}
-
-
- - - - - -<#assign skillCount = pcvar('count("SKILLSIT", "VIEW=VISIBLE_EXPORT")') /> -<#assign splitPoint = (skillCount/2)?int /> -<@loop from=splitPoint to=skillCount-1 ; skq2 , skq2_has_next> - class="shaded"> - - - - -
SkillTotal
${pcstring('SKILLSIT.${skq2}')}${pcstring('SKILLSIT.${skq2}.TOTAL')}
-
-
- -
- Quick use: keep this page visible during combat for AC, attacks, saves, and top skill totals. -
-
- -
-

Biography

-
- <#assign bioRaw = pcstring('BIO') /> - <#assign bioRaw = bioRaw?replace("<","<") /> - <#assign bioRaw = bioRaw?replace(">",">") /> - <#assign bioRaw = bioRaw?replace("&","&") /> - <#assign bioRaw = bioRaw?replace(""",'"') /> - <#assign bioRaw = bioRaw?replace("'","'") /> - ${bioRaw} -
-
- -<#if (pcstring('PORTRAIT') != '')> -
-

Portrait

-
- ${pcstring('NAME')} -
-
- - -
-
- PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — - Player: ${pcstring('PLAYERNAME')} — Character: ${pcstring('NAME')} -
+ +{{ component:rules_reference }} + +{{ component:aoo_reference }} + +{{ component:ability_influence }} + +{{ component:common_conditions }} + +{{ component:quick_view }} + +{{ component:biography }} + +{{ component:portrait }} + +{{ component:footer }} From a766247e54aeeb23988bfcfe6aea5c2258c4919c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:24:35 +0000 Subject: [PATCH 3/8] chore: compact template tokens and normalize compiled spacing Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../xmlhtml/csheet_known_spells.htm.ftl | 24 ------------------- .../xmlhtml/csheet_prepared_spells.htm.ftl | 23 ------------------ .../xmlhtml/csheet_known_spells.htm.ftl | 24 ------------------- .../xmlhtml/csheet_prepared_spells.htm.ftl | 23 ------------------ 4 files changed, 94 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 3485fbd..3afc670 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -129,7 +129,6 @@ -

${pcstring('NAME')}

@@ -161,7 +160,6 @@
-

Ability Scores

@@ -176,7 +174,6 @@
-

Combat

@@ -247,7 +244,6 @@
-

Saves

@@ -293,7 +289,6 @@
-

Notes (Description)

@@ -308,7 +303,6 @@
-

Skills

@@ -380,7 +374,6 @@
-
@@ -465,7 +458,6 @@
-

Special Attacks

@@ -495,7 +487,6 @@
-

Weapons

@@ -597,7 +588,6 @@
-
@@ -670,7 +660,6 @@
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
-
Conditional Attack / Combat Modifiers:
<#assign hasCombatCond = false /> @@ -681,7 +670,6 @@ <#if !hasCombatCond>No modifiers
-

Armor & Shields

@@ -718,7 +706,6 @@
- <@loop from=pcvar('COUNT[SPELLRACE]') to=pcvar('COUNT[SPELLRACE]+COUNT[CLASSES]-1') ; class , class_has_next> <#if (pcstring("SPELLLISTCLASS.${class}") != '')> @@ -784,7 +771,6 @@ -

Prepared Spells

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 — or search the spell name on the Archives of Nethys (aonprd.com) for free online access to the complete Pathfinder rules text. @@ -848,7 +834,6 @@ -

Concentration Quick Reference

@@ -914,7 +899,6 @@
-

Equipment

@@ -989,7 +973,6 @@
-

Rules Reference

@@ -1026,7 +1009,6 @@
-

AoO Quick Reference

@@ -1078,7 +1060,6 @@
-

Ability Influence (Quick Calc)

@@ -1131,7 +1112,6 @@
-

Common Conditions & Modifiers

@@ -1252,7 +1232,6 @@
-

Quick Reference Sheet

@@ -1328,7 +1307,6 @@
-

Biography

@@ -1342,7 +1320,6 @@
- <#if (pcstring('PORTRAIT') != '')>

Portrait

@@ -1352,7 +1329,6 @@
-
PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index 791a497..fe8c9dc 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -129,7 +129,6 @@ -

${pcstring('NAME')}

@@ -161,7 +160,6 @@
-

Ability Scores

@@ -176,7 +174,6 @@
-

Combat

@@ -247,7 +244,6 @@
-

Saves

@@ -293,7 +289,6 @@
-

Notes (Description)

@@ -308,7 +303,6 @@
-

Skills

@@ -380,7 +374,6 @@
-
@@ -465,7 +458,6 @@
-

Special Attacks

@@ -495,7 +487,6 @@
-

Weapons

@@ -597,7 +588,6 @@
-
@@ -670,7 +660,6 @@
All maneuvers provoke AoO unless you have the Improved feat for that maneuver — failing by 5+ lets the foe attempt the same maneuver on you as a free action
-
Conditional Attack / Combat Modifiers:
<#assign hasCombatCond = false /> @@ -681,7 +670,6 @@ <#if !hasCombatCond>No modifiers
-

Armor & Shields

@@ -718,7 +706,6 @@
-

Prepared Spells

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 — or search the spell name on the Archives of Nethys (aonprd.com) for free online access to the complete Pathfinder rules text. @@ -782,7 +769,6 @@ -

Concentration Quick Reference

@@ -848,7 +834,6 @@
-

Equipment

@@ -923,7 +908,6 @@
-

Rules Reference

@@ -960,7 +944,6 @@
-

AoO Quick Reference

@@ -1012,7 +995,6 @@
-

Ability Influence (Quick Calc)

@@ -1065,7 +1047,6 @@
-

Common Conditions & Modifiers

@@ -1186,7 +1167,6 @@
-

Quick Reference Sheet

@@ -1262,7 +1242,6 @@
-

Biography

@@ -1276,7 +1255,6 @@
- <#if (pcstring('PORTRAIT') != '')>

Portrait

@@ -1286,7 +1264,6 @@
-
PCGen ${pcstring('EXPORT.VERSION')} — ${pcstring('EXPORT.DATE')} — diff --git a/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index d6a79b2..beb6a0e 100644 --- a/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/templates/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -129,53 +129,29 @@ - {{ component:header }} - {{ component:ability_scores }} - {{ component:combat }} - {{ component:saves }} - {{ component:notes }} - {{ component:skills }} - {{ component:special_qualities }} - {{ component:special_attacks }} - {{ component:weapons }} - {{ component:combat_maneuvers }} - {{ component:combat_conditionals }} - {{ component:armor_shields }} - {{ component:spellbook }} - {{ component:prepared_spells }} - {{ component:concentration_reference }} - {{ component:inventory }} - {{ component:rules_reference }} - {{ component:aoo_reference }} - {{ component:ability_influence }} - {{ component:common_conditions }} - {{ component:quick_view }} - {{ component:biography }} - {{ component:portrait }} - {{ component:footer }} diff --git a/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index d33137b..f9b8f9e 100644 --- a/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/templates/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -129,51 +129,28 @@ - {{ component:header }} - {{ component:ability_scores }} - {{ component:combat }} - {{ component:saves }} - {{ component:notes }} - {{ component:skills }} - {{ component:special_qualities }} - {{ component:special_attacks }} - {{ component:weapons }} - {{ component:combat_maneuvers }} - {{ component:combat_conditionals }} - {{ component:armor_shields }} - {{ component:prepared_spells }} - {{ component:concentration_reference }} - {{ component:inventory }} - {{ component:rules_reference }} - {{ component:aoo_reference }} - {{ component:ability_influence }} - {{ component:common_conditions }} - {{ component:quick_view }} - {{ component:biography }} - {{ component:portrait }} - {{ component:footer }} From 8cdc28191efe50d87beaa4c2cdbcc8a6730d0a2b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:27:08 +0000 Subject: [PATCH 4/8] fix: address review feedback in component comparison and build script Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl | 6 +++--- .../d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl | 6 +++--- components/weapons.ftl | 6 +++--- scripts/build_sheets.py | 3 ++- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 3afc670..a9a9319 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -552,9 +552,9 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize = "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize = "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize = "l"><#assign unarmedDie = "1d4" /> +<#if charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> +<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index fe8c9dc..b676c47 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -552,9 +552,9 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize = "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize = "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize = "l"><#assign unarmedDie = "1d4" /> +<#if charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> +<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/components/weapons.ftl b/components/weapons.ftl index a71fca1..05a7d0b 100644 --- a/components/weapons.ftl +++ b/components/weapons.ftl @@ -63,9 +63,9 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize = "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize = "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize = "l"><#assign unarmedDie = "1d4" /> +<#if charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> +<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/scripts/build_sheets.py b/scripts/build_sheets.py index 652374c..dca43f6 100644 --- a/scripts/build_sheets.py +++ b/scripts/build_sheets.py @@ -9,6 +9,7 @@ COMPONENTS_DIR = ROOT / "components" OUTPUT_DIR = ROOT / "OutputSheets" TOKEN_RE = re.compile(r"\{\{\s*component:([a-zA-Z0-9_-]+)\s*\}\}") +MAX_EXPANSION_DEPTH = 20 def load_components() -> dict[str, str]: @@ -20,7 +21,7 @@ def load_components() -> dict[str, str]: def expand_components(content: str, components: dict[str, str]) -> str: expanded = content - for _ in range(20): + for _ in range(MAX_EXPANSION_DEPTH): changed = False def repl(match: re.Match[str]) -> str: From a25e3d8fed0592dcd046d0d802c457d412772a9f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:29:29 +0000 Subject: [PATCH 5/8] fix: improve component expansion diagnostics and spell level comparisons Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl | 8 ++++---- .../d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl | 4 ++-- components/prepared_spells.ftl | 4 ++-- components/spellbook.ftl | 4 ++-- scripts/build_sheets.py | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index a9a9319..f27e991 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -723,7 +723,7 @@ <#if (spelllevelcount > 0)>
- <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')}
@@ -749,7 +749,7 @@ [${pcstring('SPELLMEM.${class}.0.${level}.${spell}.SOURCE')}]
- <#if (level = 0)>∞<#else>${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>∞<#else>${pcstring('SPELLLISTCAST.${class}.${level}')} <#if !hasNoSave>${spSaveShort}
DC ${spDC}
@@ -786,7 +786,7 @@ <#if (spelllevelcount > 0)>
- <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}
@@ -812,7 +812,7 @@ [${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}]
- <#if (level = 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ + <#if (level == 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ <#if !hasNoSave>${spSaveShort}
DC ${spDC}
diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index b676c47..e1690e4 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -721,7 +721,7 @@ <#if (spelllevelcount > 0)>
- <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}
@@ -747,7 +747,7 @@ [${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}]
- <#if (level = 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ + <#if (level == 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ <#if !hasNoSave>${spSaveShort}
DC ${spDC}
diff --git a/components/prepared_spells.ftl b/components/prepared_spells.ftl index 42f7723..c8866b9 100644 --- a/components/prepared_spells.ftl +++ b/components/prepared_spells.ftl @@ -13,7 +13,7 @@ <#if (spelllevelcount > 0)>
- <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Prepared: ${pcstring('SPELLLISTCAST.${class}.${level}')}
@@ -39,7 +39,7 @@ [${pcstring('SPELLMEM.${class}.${spellbook}.${level}.${spell}.SOURCE')}]
- <#if (level = 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ + <#if (level == 0)>∞<#else><@loop from=1 to=pcvar("SPELLMEM.${class}.${spellbook}.${level}.${spell}.TIMES")>☐ <#if !hasNoSave>${spSaveShort}
DC ${spDC}
diff --git a/components/spellbook.ftl b/components/spellbook.ftl index 64e3f3e..fff0b83 100644 --- a/components/spellbook.ftl +++ b/components/spellbook.ftl @@ -15,7 +15,7 @@ <#if (spelllevelcount > 0)>
- <#if (level = 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>Cantrips (Level 0) — Unlimited Uses<#else>Level ${level} — Spells/Day: ${pcstring('SPELLLISTCAST.${class}.${level}')}
@@ -41,7 +41,7 @@ [${pcstring('SPELLMEM.${class}.0.${level}.${spell}.SOURCE')}] diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index e1690e4..68d7220 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -552,9 +552,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> +<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> +<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> +<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> +<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> +<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/components/weapons.ftl b/components/weapons.ftl index 05a7d0b..d15b088 100644 --- a/components/weapons.ftl +++ b/components/weapons.ftl @@ -63,9 +63,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> +<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> +<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> +<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> +<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> +<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> From 4659bc3aba26c91a64b3a5b170acbfa2be771336 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:32:22 +0000 Subject: [PATCH 7/8] fix: use explicit size aliases for unarmed die mapping Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl | 12 ++++++------ .../fantasy/xmlhtml/csheet_prepared_spells.htm.ftl | 12 ++++++------ components/weapons.ftl | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 5ab3e05..9cec46c 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -552,12 +552,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> -<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> -<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> -<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> -<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> +<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> +<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> +<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> +<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> +<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> +<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index 68d7220..1f587e1 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -552,12 +552,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> -<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> -<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> -<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> -<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> +<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> +<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> +<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> +<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> +<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> +<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/components/weapons.ftl b/components/weapons.ftl index d15b088..2d0b874 100644 --- a/components/weapons.ftl +++ b/components/weapons.ftl @@ -63,12 +63,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> -<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> -<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> -<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> -<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> +<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> +<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> +<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> +<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> +<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> +<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> From 15aeb60e5ae5448a5bf889c5ed2f3fe472b33c60 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:34:07 +0000 Subject: [PATCH 8/8] refactor: normalize size key mapping for unarmed damage lookup Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../xmlhtml/csheet_known_spells.htm.ftl | 21 +++++++++++++------ .../xmlhtml/csheet_prepared_spells.htm.ftl | 21 +++++++++++++------ components/weapons.ftl | 21 +++++++++++++------ scripts/build_sheets.py | 2 +- 4 files changed, 46 insertions(+), 19 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index 9cec46c..dd28777 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -551,13 +551,22 @@ <#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" /> + <#assign unarmedDie = "1d3" /> -<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> -<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> -<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> -<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> -<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> -<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> +<#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" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl index 1f587e1..5da4c94 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl @@ -551,13 +551,22 @@ <#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" /> + <#assign unarmedDie = "1d3" /> -<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> -<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> -<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> -<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> -<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> -<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> +<#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" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/components/weapons.ftl b/components/weapons.ftl index 2d0b874..7cd4aa9 100644 --- a/components/weapons.ftl +++ b/components/weapons.ftl @@ -62,13 +62,22 @@ <#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" /> + <#assign unarmedDie = "1d3" /> -<#if (charSize == "t" || charSize?starts_with("tiny") || charSize == "s" || charSize?starts_with("small"))><#assign unarmedDie = "1d2" /> -<#elseif (charSize == "m" || charSize?starts_with("medium"))><#assign unarmedDie = "1d3" /> -<#elseif (charSize == "l" || charSize?starts_with("large"))><#assign unarmedDie = "1d4" /> -<#elseif (charSize == "h" || charSize?starts_with("huge"))><#assign unarmedDie = "1d6" /> -<#elseif (charSize == "g" || charSize?starts_with("gargantuan"))><#assign unarmedDie = "1d8" /> -<#elseif (charSize == "c" || charSize?starts_with("colossal"))><#assign unarmedDie = "2d6" /> +<#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" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') /> diff --git a/scripts/build_sheets.py b/scripts/build_sheets.py index 3aa9903..46a37b0 100644 --- a/scripts/build_sheets.py +++ b/scripts/build_sheets.py @@ -9,7 +9,7 @@ COMPONENTS_DIR = ROOT / "components" OUTPUT_DIR = ROOT / "OutputSheets" TOKEN_RE = re.compile(r"\{\{\s*component:([a-zA-Z0-9_-]+)\s*\}\}") -MAX_EXPANSION_DEPTH = 20 # Prevent infinite recursion from cyclic/nested component references +MAX_EXPANSION_DEPTH = 20 # Guard against cyclic/nested references; normal templates should resolve in a few passes def load_components() -> dict[str, str]:
- <#if (level = 0)>∞<#else>${pcstring('SPELLLISTCAST.${class}.${level}')} + <#if (level == 0)>∞<#else>${pcstring('SPELLLISTCAST.${class}.${level}')} <#if !hasNoSave>${spSaveShort}
DC ${spDC}
diff --git a/scripts/build_sheets.py b/scripts/build_sheets.py index dca43f6..3aa9903 100644 --- a/scripts/build_sheets.py +++ b/scripts/build_sheets.py @@ -9,7 +9,7 @@ COMPONENTS_DIR = ROOT / "components" OUTPUT_DIR = ROOT / "OutputSheets" TOKEN_RE = re.compile(r"\{\{\s*component:([a-zA-Z0-9_-]+)\s*\}\}") -MAX_EXPANSION_DEPTH = 20 +MAX_EXPANSION_DEPTH = 20 # Prevent infinite recursion from cyclic/nested component references def load_components() -> dict[str, str]: @@ -37,7 +37,7 @@ def repl(match: re.Match[str]) -> str: if not changed: break if TOKEN_RE.search(expanded): - raise ValueError("Unresolved component tokens remain after expansion") + raise ValueError(f"Unresolved component tokens remain after expansion: {TOKEN_RE.findall(expanded)}") return expanded From 39acc9a9463e587a7dc2fc9475fb8df20b884c05 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 12:30:55 +0000 Subject: [PATCH 6/8] fix: broaden unarmed damage size handling in weapons component Agent-Logs-Url: https://github.com/bart-sysf/pf-csheets/sessions/ad4fb6c3-8ea3-4b2b-a8d0-7c3c6de7a7d0 Co-authored-by: bart-sysf <16703428+bart-sysf@users.noreply.github.com> --- .../d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl | 9 ++++++--- .../d20/fantasy/xmlhtml/csheet_prepared_spells.htm.ftl | 9 ++++++--- components/weapons.ftl | 9 ++++++--- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl index f27e991..5ab3e05 100644 --- a/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl +++ b/OutputSheets/d20/fantasy/xmlhtml/csheet_known_spells.htm.ftl @@ -552,9 +552,12 @@ <#assign charSize = pcstring('SIZE')?lower_case /> <#assign unarmedDie = "1d3" /> -<#if charSize == "s"><#assign unarmedDie = "1d2" /> -<#elseif charSize == "m"><#assign unarmedDie = "1d3" /> -<#elseif charSize == "l"><#assign unarmedDie = "1d4" /> +<#if charSize?starts_with("t") || charSize == "s"><#assign unarmedDie = "1d2" /> +<#elseif charSize?starts_with("m")><#assign unarmedDie = "1d3" /> +<#elseif charSize?starts_with("l")><#assign unarmedDie = "1d4" /> +<#elseif charSize?starts_with("h")><#assign unarmedDie = "1d6" /> +<#elseif charSize?starts_with("g")><#assign unarmedDie = "1d8" /> +<#elseif charSize?starts_with("c")><#assign unarmedDie = "2d6" /> <#assign strMod = pcstring('STAT.0.MOD.SIGN') />