From ffa69e43e7977f52dd07956d1f490434d5cb2030 Mon Sep 17 00:00:00 2001 From: slaguru666 <111923774+slaguru666@users.noreply.github.com> Date: Wed, 22 Jul 2026 22:54:03 +0100 Subject: [PATCH] =?UTF-8?q?VANITY=20v0.10.0=20=E2=80=94=20Draft=2014=20rul?= =?UTF-8?q?es,=20targeting,=20spell=20effects?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Published system carrying the Draft 14 work: the rules journal rebuilt with Common Sense (§6a), Scenes (§6b), the Zero Grit states (§5a), Charm & Command separated (§7) and the Spell Effects menu (§17). Foundry side: the companion card is now a Ranger's only, targeting rolls the defender's guard and applies damage, and casters get their own spend panel priced off Successes above the Threshold. Implemented in the VRPG source; this repo carries the published system. Co-Authored-By: Claude Opus 4.8 --- packs/armour/LOG | 10 +- packs/bestiary/LOG | 10 +- packs/companions/LOG | 10 +- packs/edges/LOG | 10 +- packs/gear/LOG | 10 +- packs/gm/LOG | 10 +- packs/pregens/000004.log | 0 packs/pregens/CURRENT | 2 +- packs/pregens/LOG | 8 +- packs/pregens/MANIFEST-000002 | Bin 0 -> 161 bytes packs/rules/000005.ldb | Bin 63868 -> 77503 bytes packs/rules/LOG | 10 +- packs/rules/MANIFEST-000002 | Bin 162 -> 162 bytes packs/spells/LOG | 10 +- packs/tables/LOG | 10 +- packs/treasure/LOG | 10 +- packs/vices/LOG | 10 +- packs/weapons/LOG | 10 +- styles/vanity.css | 19 +++ system.json | 2 +- templates/character-sheet.hbs | 4 + vanity.mjs | 237 ++++++++++++++++++++++++++++++---- 22 files changed, 304 insertions(+), 88 deletions(-) create mode 100644 packs/pregens/000004.log create mode 100644 packs/pregens/MANIFEST-000002 diff --git a/packs/armour/LOG b/packs/armour/LOG index bad08a5..ea0335e 100644 --- a/packs/armour/LOG +++ b/packs/armour/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.251914 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.252258 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.252372 1744c7000 Level-0 table #5: 2618 bytes OK -2026/07/22-11:06:42.252427 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.252467 1744c7000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.342167 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.342695 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.342842 1724f3000 Level-0 table #5: 2618 bytes OK +2026/07/22-22:53:09.342915 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.342966 1724f3000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end) diff --git a/packs/bestiary/LOG b/packs/bestiary/LOG index 7e8cd12..11d29c3 100644 --- a/packs/bestiary/LOG +++ b/packs/bestiary/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.318704 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.319032 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.319244 1744c7000 Level-0 table #5: 5226 bytes OK -2026/07/22-11:06:42.319295 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.319326 1744c7000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.402765 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.403157 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.403344 1724f3000 Level-0 table #5: 5226 bytes OK +2026/07/22-22:53:09.403402 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.403444 1724f3000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end) diff --git a/packs/companions/LOG b/packs/companions/LOG index f4f1f9e..9ce1fef 100644 --- a/packs/companions/LOG +++ b/packs/companions/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.322638 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.323336 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.323628 1744c7000 Level-0 table #5: 4974 bytes OK -2026/07/22-11:06:42.323701 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.323743 1744c7000 Manual compaction at level-0 from '!actors!1FGl7v1mODKvN9fy' @ 72057594037927935 : 1 .. '!actors!yEfKBqSa4GmIgOpx' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.406577 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.407048 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.407182 1724f3000 Level-0 table #5: 4974 bytes OK +2026/07/22-22:53:09.407246 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.407280 1724f3000 Manual compaction at level-0 from '!actors!1FGl7v1mODKvN9fy' @ 72057594037927935 : 1 .. '!actors!yEfKBqSa4GmIgOpx' @ 0 : 0; will stop at (end) diff --git a/packs/edges/LOG b/packs/edges/LOG index 4a7a673..06f266c 100644 --- a/packs/edges/LOG +++ b/packs/edges/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.310645 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.310944 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.311148 1744c7000 Level-0 table #5: 5417 bytes OK -2026/07/22-11:06:42.311209 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.311251 1744c7000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.395152 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.395469 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.395594 1724f3000 Level-0 table #5: 5417 bytes OK +2026/07/22-22:53:09.395643 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.395676 1724f3000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end) diff --git a/packs/gear/LOG b/packs/gear/LOG index d24c560..0b958bc 100644 --- a/packs/gear/LOG +++ b/packs/gear/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.268229 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.268754 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.268950 1744c7000 Level-0 table #5: 9529 bytes OK -2026/07/22-11:06:42.269017 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.269061 1744c7000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.357214 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.357850 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.358066 1724f3000 Level-0 table #5: 9529 bytes OK +2026/07/22-22:53:09.358124 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.358162 1724f3000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end) diff --git a/packs/gm/LOG b/packs/gm/LOG index 1940d0c..d4f62b9 100644 --- a/packs/gm/LOG +++ b/packs/gm/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.344443 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.344750 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.345011 1744c7000 Level-0 table #5: 10149 bytes OK -2026/07/22-11:06:42.345068 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.345104 1744c7000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.428790 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.429078 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.429289 1724f3000 Level-0 table #5: 10149 bytes OK +2026/07/22-22:53:09.429337 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.429371 1724f3000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end) diff --git a/packs/pregens/000004.log b/packs/pregens/000004.log new file mode 100644 index 0000000..e69de29 diff --git a/packs/pregens/CURRENT b/packs/pregens/CURRENT index f7753e2..1a84852 100644 --- a/packs/pregens/CURRENT +++ b/packs/pregens/CURRENT @@ -1 +1 @@ -MANIFEST-000006 +MANIFEST-000002 diff --git a/packs/pregens/LOG b/packs/pregens/LOG index f02eb1f..5c2f1be 100644 --- a/packs/pregens/LOG +++ b/packs/pregens/LOG @@ -1,3 +1,5 @@ -2026/07/22-11:06:52.981350 16fe8f000 Recovering log #4 -2026/07/22-11:06:52.982189 16fe8f000 Delete type=0 #4 -2026/07/22-11:06:52.982310 16fe8f000 Delete type=3 #2 +2026/07/22-22:53:09.418973 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.419962 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.420751 1724f3000 Level-0 table #5: 120609 bytes OK +2026/07/22-22:53:09.420822 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.420867 1724f3000 Manual compaction at level-0 from '!actors!3Kjqu9ibyf8TjSrk' @ 72057594037927935 : 1 .. '!actors.items!zR0OZzFsVQLTSlBM.uc6AKpWyZeREAX2M' @ 0 : 0; will stop at (end) diff --git a/packs/pregens/MANIFEST-000002 b/packs/pregens/MANIFEST-000002 new file mode 100644 index 0000000000000000000000000000000000000000..91c80775c32364656f04178c98a001de326b3bd5 GIT binary patch literal 161 zcmWIhx#Ncn10$nUPHI_dPD+xVQ)NkNd1i5{bAE0?Vo_pAe$kRS-TOEg7@3$k8JJmE z7`~TD?aKgiS%6$NmS0TlOsosnu`4JhCIi(LD;j%e6_#3NCRL_cgk%L5Wiv7|fPpzw xfnH`wYHqP&Rgi&yRFzwCSfEcxaE_C&UTLzKqjy1gWmIaAt7C+bFJlQrBLKn3F7f~X literal 0 HcmV?d00001 diff --git a/packs/rules/000005.ldb b/packs/rules/000005.ldb index 7b793b39a5204fc87f63842cde7b6d0f792fb90d..769576b47706c9fb85aa16364174c5868ea4be93 100644 GIT binary patch delta 28281 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 D1f@SR delta 14529 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 diff --git a/packs/rules/LOG b/packs/rules/LOG index fe8343e..1fdc4b9 100644 --- a/packs/rules/LOG +++ b/packs/rules/LOG @@ -1,5 +1,5 @@ -2026/07/22-11:06:42.341352 173c2f000 Delete type=3 #1 -2026/07/22-11:06:42.341857 1744c7000 Level-0 table #5: started -2026/07/22-11:06:42.342338 1744c7000 Level-0 table #5: 63868 bytes OK -2026/07/22-11:06:42.342395 1744c7000 Delete type=0 #3 -2026/07/22-11:06:42.342429 1744c7000 Manual compaction at level-0 from '!journal!9PRuE5Q81BnO22fH' @ 72057594037927935 : 1 .. '!journal.pages!9PRuE5Q81BnO22fH.qCt9jGDZePPN5S6G' @ 0 : 0; will stop at (end) +2026/07/22-22:53:09.424854 171c5b000 Delete type=3 #1 +2026/07/22-22:53:09.425505 1724f3000 Level-0 table #5: started +2026/07/22-22:53:09.426080 1724f3000 Level-0 table #5: 77503 bytes OK +2026/07/22-22:53:09.426135 1724f3000 Delete type=0 #3 +2026/07/22-22:53:09.426181 1724f3000 Manual compaction at level-0 from '!journal!CNrYR2rXwHMBm0lH' @ 72057594037927935 : 1 .. '!journal.pages!CNrYR2rXwHMBm0lH.yPi5N6WdEe1jn19i' @ 0 : 0; will stop at (end) diff --git a/packs/rules/MANIFEST-000002 b/packs/rules/MANIFEST-000002 index 74d9f7e2bbd2a491e9b180b30b6cf2ebd9e1822f..ce18d77932e2f06a9bb0716fde75880c7b015820 100644 GIT binary patch delta 98 zcmZ3)xQKCrk)GQr%dAWWMkW?c24*%EE_No?{dZXu6|?e7i}Dh46rKHwB7=;IBFa5{ lopKFwCMHN~q6vBERR(05`k95NxTYFr1ItnSGfIj7%(?49sjST + {{!-- The Bond is a Ranger's Locked Knack (§7). Anyone else who has somehow + acquired a companion keeps their card; nobody else is offered one. --}} + {{#if hasCompanion}}
Companion — the Bond @@ -314,6 +317,7 @@
+ {{/if}}
Notes
{{editor notesHTML target="system.notes" button=true editable=editable engine="prosemirror" collaborate=false}}
diff --git a/vanity.mjs b/vanity.mjs index 96bf4d8..0b4d2e1 100644 --- a/vanity.mjs +++ b/vanity.mjs @@ -96,11 +96,43 @@ const MANEUVERS = [ desc: "Make one extra attack immediately. Remember the cap: at most one extra attack per round, however it's paid for (Press or Rapid Shot, not both — §11a)." } ]; +/** + * Spell Effects (§17) — the caster's maneuver menu. Successes past the + * Threshold buy these as readily as they buy the spell's own scaling line. + * The `fits` line is guidance for the table, not a hard gate: common sense + * (§6a) decides whether this spell could plausibly do this thing. + */ +const SPELL_EFFECTS = [ + { key: "ignite", label: "Ignite", cost: 1, hint: "1 Grit at the start of each of their turns until put out.", + desc: "The target burns: 1 Grit at the start of each of their turns until they spend an Action smothering it. Fire, acid, lightning." }, + { key: "blind", label: "Blind", cost: 1, hint: "−2 dice on anything needing sight, one round.", + desc: "Light, dust or a flood of dark: −2 dice on anything that needs eyes, until the end of their next turn. Light, illusion, weather." }, + { key: "snare", label: "Snare", cost: 1, hint: "Held until they win a Brawn contest.", + desc: "Roots, ice, webbing or grasping shadow — the target is Held until they win a Brawn contest to tear free. Nature, ice, shadow." }, + { key: "push", label: "Push", cost: 1, hint: "Thrown a pace and knocked prone.", + desc: "The target is thrown a pace and knocked prone — off the parapet, out of the circle, into the fire you just lit. Force, wind, water." }, + { key: "silence", label: "Silence", cost: 1, hint: "They cannot cast until their next turn.", + desc: "No breath, no words, no incantation: the target cannot cast until their next turn. The cleanest answer to an enemy spellcaster. Divine, mind, sound." }, + { key: "shield", label: "Shield the Ally", cost: 1, hint: "An ally gains +2 defence dice until their next turn.", + desc: "Spend the power outward instead: an ally gains +2 defence dice until their next turn. Any protective list." }, + { key: "spread", label: "Spread", cost: 2, hint: "Also strikes one further target within reach.", + desc: "The magic carries: it also strikes one further target within reach of the first, for your remaining damage. Fire, sound, area." }, + { key: "linger", label: "Linger", cost: 2, hint: "Lasts a second round — and doesn't strain the Weave for it.", + desc: "The effect lasts a second round with no second casting — and crucially does not strain the Weave (§17) for it. Wards, clouds, blessings." }, + { key: "unmake", label: "Unmake", cost: 2, hint: "Strip a ward, blessing, resistance or enchantment.", + desc: "Strip a magical defence: a ward, a blessing, a resistance, or one enchantment on an object. Arcane, divine." }, + { key: "mark", label: "Mark of the Divine", cost: 2, hint: "Faith only: allies get +1 die against them all scene.", + desc: "Faith only. The target is Marked: every ally's attack against them gains +1 die until the scene ends. Divine only." } +]; + +/** Which menu a card's spare Successes may be spent from. */ +function menuFor(context) { return context === "spell" ? SPELL_EFFECTS : MANEUVERS; } + /** * The interactive spend panel for attack cards: click maneuvers to buy them * with Successes, dial in the defender's Successes, read off the damage. */ -function renderSpendPanel({ successes, spend, sublabel }) { +function renderSpendPanel({ successes, spend, sublabel, menu = MANEUVERS, context = "attack", threshold = 0 }) { const s = { defence: 0, damage: 0, maneuvers: [], ...(spend ?? {}) }; const net = successes - s.defence; @@ -117,35 +149,36 @@ function renderSpendPanel({ successes, spend, sublabel }) {
`; - if (net <= 0) { + if (net <= 0 && context !== "spell") { return `
${defRow}
Turned aside — net ${net}, no harm done.
`; } - const budget = net - 1; - const spentOnMans = s.maneuvers.reduce((n, k) => n + (MANEUVERS.find(m => m.key === k)?.cost ?? 0), 0); + const isSpell = context === "spell"; + const budget = isSpell ? Math.max(0, successes - (threshold || 1)) : net - 1; + const spentOnMans = s.maneuvers.reduce((n, k) => n + (menu.find(m => m.key === k)?.cost ?? 0), 0); const remaining = budget - s.damage - spentOnMans; - const totalDamage = 1 + s.damage; + const totalDamage = (isSpell ? 0 : 1) + s.damage; - const manBtns = MANEUVERS.map(m => { + const manBtns = menu.map(m => { const sel = s.maneuvers.includes(m.key); const disabled = !sel && remaining < m.cost; return ``; }).join(""); - const chosen = s.maneuvers.map(k => MANEUVERS.find(m => m.key === k)).filter(Boolean); + const chosen = s.maneuvers.map(k => menu.find(m => m.key === k)).filter(Boolean); const descs = chosen.map(m => `
${m.label}${m.cost} Success${m.cost > 1 ? "es" : ""}

${m.desc}

`).join(""); return `
- ${defRow} + ${isSpell ? "" : defRow}
- Damage: ${totalDamage} Grit + ${isSpell && !s.damage ? "Effects" : `Damage: ${totalDamage} Grit`} ${remaining > 0 ? `${remaining} Success${remaining === 1 ? "" : "es"} to spend` : "all spent"}
@@ -282,7 +315,7 @@ function renderDice(results) { /** * Build the chat-card HTML for a pool roll. */ -function renderRollCard({ label, sublabel, results, threshold, pushed, context, extra, spend, banked = 0, twistUsed = false, spell = null }) { +function renderRollCard({ label, sublabel, results, threshold, pushed, context, extra, spend, banked = 0, twistUsed = false, spell = null, target = null }) { const successes = results.filter(r => r >= 5).length; const ones = results.filter(r => r === 1).length; const stumble = successes === 0 && ones >= 2; @@ -307,8 +340,8 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context, } } - const specials = context === "attack" && successes >= 1 - ? renderSpendPanel({ successes, spend, sublabel }) + const specials = (context === "attack" || context === "spell") && successes >= 1 + ? renderSpendPanel({ successes, spend, sublabel, menu: menuFor(context), context, threshold }) : ""; const spellBanner = spell ? ` @@ -343,12 +376,77 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context, ? `
` : twistUsed ? `
The knife has been twisted.
` : ""} ${spellScaling} + ${targetBlock(target, results, spend)} ${extra ? `
${extra}
` : ""} ${specials} ${buttons ? `
${buttons}
` : ""}
`; } +/** + * The exchange, resolved (§10): who was struck at, what their guard rolled, + * and what is left to land. Damage is applied on a button so the GM stays in + * charge of the fiction — Subdue, mercy and "he was already down" all live in + * the beat between the roll and the click. + */ +function targetBlock(target, results, spend) { + if (!target) return ""; + const successes = results.filter(r => r >= 5).length; + const net = Math.max(0, successes - (spend?.defence ?? target.successes ?? 0)); + const damage = spend?.damage ?? 0; + const pips = (target.results ?? []).map(r => + `${DIE_FACES[r] ?? r}`).join(""); + const pierceNote = target.piercing + ? (target.pierced + ? ` · armour-piercing — ${target.full}d cut to ${target.pool}d` + : ` · armour-piercing — nothing left to strip`) + : ""; + return ` +
+
${target.name} + ${target.label} ${target.pool}d${pierceNote}
+
${pips}${target.successes} Success${target.successes === 1 ? "" : "es"} stopped
+
${net > 0 + ? `${net} net Success${net === 1 ? "" : "es"} through — spend ${net === 1 ? "it" : "them"} above` + : `Turned aside. The guard held.`}
+ ${damage > 0 + ? `
` + : ""} +
`; +} + +/** + * Take the damage off the target's Grit and say what it did. + * + * Applied once — the button disarms itself afterwards, so a card cannot be + * clicked twice in the noise of a fight. Emptying Grit announces Taken Out + * (§5a) but never decides the state: Down, Broken or Dying is the GM's call, + * and Subdue was declared before the roll. + */ +async function applyDamageToTarget(message, damage) { + const f = message.flags?.vanity; + if (!f?.target?.uuid || damage <= 0) return; + if (f.applied) return ui.notifications.warn("That blow has already been paid for."); + + const actor = await fromUuid(f.target.uuid); + if (!actor) return ui.notifications.warn("That target is no longer on the board."); + + const grit = actor.system?.grit ?? {}; + const before = Number(grit.value) || 0; + const after = Math.max(0, before - damage); + await actor.update({ "system.grit.value": after }); + + const takenOut = after === 0 && before > 0; + await message.update({ "flags.vanity.applied": true }); + await ChatMessage.create({ + speaker: { alias: "The Exchange" }, + content: `
+

${f.target.name} takes ${damage} Grit — ${before} → ${after}.

+ ${takenOut ? `

TAKEN OUT. Down, Broken or Dying is the GM's call (§5a) — and whoever struck the blow chose Subdue or Kill before it landed.

` : ""} +
` + }); +} + /** * Roll a VANITY pool and post the chat card. * options: { threshold, context ("spell"|"attack"|...), spellUuid, sublabel } @@ -387,7 +485,8 @@ async function rollPool(actor, pool, label, options = {}) { extra: "", banked, spell: options.spell ?? null, - spend: { defence: 0, damage: 0, maneuvers: [] } + target: options.target ?? null, + spend: options.spend ?? { defence: 0, damage: 0, maneuvers: [] } } }; @@ -395,12 +494,75 @@ async function rollPool(actor, pool, label, options = {}) { speaker: ChatMessage.getSpeaker({ actor }), rolls: [roll], sound: CONFIG.sounds.dice, - content: renderRollCard({ label, sublabel: options.sublabel, results, threshold: options.threshold, pushed: false, context: options.context, banked, spell: options.spell ?? null }), + content: renderRollCard({ + label, sublabel: options.sublabel, results, threshold: options.threshold, pushed: false, + context: options.context, banked, spell: options.spell ?? null, + target: options.target ?? null, spend: flags.vanity.spend + }), flags }); if (banked) await actor.addBanes(1, `a Stumble on “${label}”`); } +/* -------------------------------------------- */ +/* Targeting — the defender answers for itself */ +/* -------------------------------------------- */ + +/** Does this weapon's properties line claim Armour-Piercing? */ +function isPiercing(text) { + return /armour[-\s]?piercing|armor[-\s]?piercing|\bAP\b/i.test(String(text ?? "")); +} + +/** + * The pool a defender actually rolls. + * + * Characters defend with the better of block or dodge — nobody chooses their + * worse guard. Armour-Piercing strips up to 2 armour dice, but §11a is + * absolute: Attribute dice always defend, so the pool never falls below the + * governing Attribute. A nameless foe has no attribute breakdown to protect, + * so AP simply thins its pool, never below one die. + */ +function defenceProfile(actor, { piercing = false } = {}) { + const sys = actor.system ?? {}; + if (actor.type !== "character") { + const pool = Number(sys.defence?.pool) || 0; + return { + pool: piercing ? Math.max(1, pool - 2) : pool, + label: sys.defence?.note?.trim() || "defence", floor: 1, full: pool + }; + } + const dodge = Number(sys.dodgePool) || 0; + const block = Number(sys.blockPool) || 0; + const useBlock = block >= dodge; + const attr = Number(useBlock ? sys.attributes?.brawn?.value : sys.attributes?.flair?.value) || 0; + const full = useBlock ? block : dodge; + return { + pool: piercing ? Math.max(attr, full - 2) : full, + label: useBlock ? "block" : "dodge", floor: attr, full + }; +} + +/** Roll a defender's guard and report what it stopped. */ +async function rollDefence(actor, { piercing = false } = {}) { + const prof = defenceProfile(actor, { piercing }); + const roll = new Roll(`${Math.max(1, prof.pool)}d6`); + await roll.evaluate(); + const results = roll.dice[0].results.map(r => r.result); + return { + ...prof, results, roll, + successes: results.filter(r => r >= 5).length, + pierced: piercing && prof.pool < prof.full + }; +} + +/** The token the attacker has targeted, if exactly one is marked. */ +function currentTarget() { + const t = [...(game.user?.targets ?? [])]; + if (t.length !== 1) return null; + const token = t[0]; + return token?.actor ? { token, actor: token.actor, name: token.name || token.actor.name } : null; +} + /* -------------------------------------------- */ /* Straining the Weave (§17) — the spam brake */ /* -------------------------------------------- */ @@ -544,7 +706,7 @@ async function handlePush(message) { await message.update({ content: renderRollCard({ label: f.label, sublabel: f.sublabel, results: newResults, - threshold: f.threshold, pushed: true, context: f.context, extra, spend, banked, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null + threshold: f.threshold, pushed: true, context: f.context, extra, spend, banked, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null }), "flags.vanity.results": newResults, "flags.vanity.pushed": true, @@ -574,8 +736,10 @@ async function handleSpend(message, dataset) { } // Never overspend: if the budget shrank (defence went up), drop purchases. - const budget = Math.max(0, successes - spend.defence - 1); - const cost = spend.damage + spend.maneuvers.reduce((n, k) => n + (MANEUVERS.find(m => m.key === k)?.cost ?? 0), 0); + const budget = f.context === "spell" + ? Math.max(0, successes - (f.threshold || 1)) + : Math.max(0, successes - spend.defence - 1); + const cost = spend.damage + spend.maneuvers.reduce((n, k) => n + (menuFor(f.context).find(m => m.key === k)?.cost ?? 0), 0); if (cost > budget) { if (dataset.def !== undefined) { spend.damage = 0; spend.maneuvers = []; } else return; // an illegal purchase — ignore the click @@ -584,7 +748,7 @@ async function handleSpend(message, dataset) { await message.update({ content: renderRollCard({ label: f.label, sublabel: f.sublabel, results: f.results, - threshold: f.threshold, pushed: f.pushed, context: f.context, extra: f.extra, spend, banked: f.banked ?? 0, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null + threshold: f.threshold, pushed: f.pushed, context: f.context, extra: f.extra, spend, banked: f.banked ?? 0, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null }), "flags.vanity.spend": spend }); @@ -601,7 +765,7 @@ async function handleFizzle(message) { await message.update({ content: renderRollCard({ label: f.label, sublabel: f.sublabel, results: f.results, - threshold: f.threshold, pushed: true, context: f.context, extra, banked: f.banked ?? 0, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null + threshold: f.threshold, pushed: true, context: f.context, extra, banked: f.banked ?? 0, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null }), "flags.vanity.pushed": true, "flags.vanity.extra": extra @@ -659,6 +823,12 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet { strain: strainCount(s), threshold: effectiveThreshold(s) })); ctx.strained = ctx.spells.some(s => s.strain > 0); + // The Animal Companion is the Ranger's, via Wildcraft (§7). Other classes + // never see the card — but if one already has a bonded beast (an edge, a + // gift, a GM's ruling), it stays on the sheet rather than vanishing. + const bond = this.actor.system.companion ?? {}; + ctx.hasCompanion = String(this.actor.system.details?.class ?? "").trim().toLowerCase() === "ranger" + || Boolean(String(bond.name ?? "").trim() || String(bond.kind ?? "").trim()); ctx.edges = items.filter(i => i.type === "edge"); ctx.vices = items.filter(i => i.type === "vice"); ctx.hasSpells = ctx.spells.length > 0; @@ -786,7 +956,7 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet { }); // Weapon attack - html.find(".item-attack").click(ev => { + html.find(".item-attack").click(async ev => { const item = actor.items.get(ev.currentTarget.closest("[data-item-id]").dataset.itemId); if (!item) return; const group = item.system.group === "shoot" ? "shoot" : "fight"; @@ -800,11 +970,26 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet { knackDice = Math.max(knackDice, Number(locked.rank) || 0); knackName = "Martial Prowess"; } - promptAndRoll(actor, { + // If exactly one token is targeted, the defender answers for itself: its + // guard is rolled here and the result is dialled into the card, so the + // exchange (§10) resolves in one step instead of two. + const mark = currentTarget(); + const piercing = isPiercing(item.system.properties); + const defence = mark ? await rollDefence(mark.actor, { piercing }) : null; + + await promptAndRoll(actor, { title: `${item.name} — attack`, base: attr + knackDice + (Number(item.system.dice) || 0), baseLabel: `${ATTRIBUTES[attrKey].label} ${attr} + ${knackName} ${knackDice} + weapon ${item.system.dice}`, - options: { context: "attack", sublabel: item.system.properties } + options: { + context: "attack", sublabel: item.system.properties, + target: mark && defence ? { + uuid: mark.actor.uuid, name: mark.name, piercing, + label: defence.label, pool: defence.pool, full: defence.full, + pierced: defence.pierced, results: defence.results, successes: defence.successes + } : null, + spend: mark && defence ? { defence: defence.successes, damage: 0, maneuvers: [] } : undefined + } }); }); @@ -1227,7 +1412,7 @@ async function twistTheKnife({ actorUuid = "", free = false, sourceMessage = nul content: renderRollCard({ label: f.label, sublabel: f.sublabel, results: f.results, threshold: f.threshold, pushed: f.pushed, context: f.context, extra: f.extra, spend: f.spend, - banked: f.banked ?? 0, twistUsed: true, spell: f.spell ?? null + banked: f.banked ?? 0, twistUsed: true, spell: f.spell ?? null, target: f.target ?? null }) }); } @@ -1942,6 +2127,12 @@ Hooks.on("renderChatMessageHTML", (message, html) => { handleSpend(message, btn.dataset); }); }); + html.querySelectorAll(".vanity-apply").forEach(btn => { + btn.addEventListener("click", () => { + if (!game.user.isGM) return ui.notifications.warn("Only the GM applies damage."); + applyDamageToTarget(message, Number(btn.dataset.damage) || 0); + }); + }); html.querySelectorAll(".vanity-twist").forEach(btn => { btn.addEventListener("click", () => { if (!game.user.isGM) return ui.notifications.warn("Only the GM may twist the knife.");