v0.10.3: graft the live-play changes onto Draft 14
Reconciles the parallel Draft 14 line (targeting, spell effects, Straining the Weave) with the mechanics designed in a live pencil-and-paper session: - Banes (replacing the Stumble, by design decision): two or more 1s bank a Bane regardless of Successes; burn 1 Success to cancel a fail (drop below two → no Bane); a single 1 is the free twist. The roll card offers Burn / Take the Bane. Threaded through the Draft 14 targeting/apply-damage so effective Successes drive net damage and spend budgets. - Twist the Knife: the d66 table replaced by a tuned 2d6 table (single fail triggers it; effects hit the roller or a chosen comrade). - Momentum: a 3+ Success roll grants +1 die on the next Draw (keep highest, stacks to +2) via a VanityCombatant subclass; shown on the sheet's Draw button. - Initiative: right-click a combatant to "Go first — make an entrance (1 Vanity)" or (GM) "Move to top", instead of re-rolling for a 6. - Conditions: token/HUD icons rebuilt as bold colour-coded badges (red=combat, teal=spell, plum=state) with white glyphs; renderer moved to headless Chrome. - Marked reworded (no more "Stumble"); build-conditions now targets Draft 14. Rules brought into line: Draft 14 (§1, §3, §6, §11b, §21a, Common Sense, the Reckoning reference), the player rulebook (regenerated HTML + PDF, 43pp), the condition card deck & GM summary, and the system description. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@@ -0,0 +1 @@
|
||||
{"version":1,"sessions":{"2ead103c-b952-47dc-aafe-6f914fbfd398":{"updatedAt":1785224728489,"files":{"/Users/timevans/Git/VRPG/foundry/vanity/styles/vanity.css":{"editCount":4,"findings":["side-tab:627","side-tab:1012","side-tab:632","side-tab:1017","side-tab:639","side-tab:1024","side-tab:672","side-tab:384","side-tab:1057"]}}}}}
|
||||
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 11 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><path d="M62 22 a30 30 0 1 0 18 46 a26 26 0 0 1 -18 -46 Z"/><g stroke="#f7f0de" stroke-width="5" fill="none"><path d="M58 30 h12 l-12 11 h12"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><path d="M62 22 a30 30 0 1 0 18 46 a26 26 0 0 1 -18 -46 Z"/><g stroke="#14544e" stroke-width="5" fill="none"><path d="M58 30 h12 l-12 11 h12"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 376 B After Width: | Height: | Size: 570 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 12 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><circle cx="50" cy="58" r="15" fill="none" stroke-width="6"/><g stroke-width="5"><path d="M50 14 V30 M26 24 l9 12 M74 24 l-9 12"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><circle cx="50" cy="58" r="15" fill="none" stroke-width="6"/><g stroke-width="5"><path d="M50 14 V30 M26 24 l9 12 M74 24 l-9 12"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 363 B After Width: | Height: | Size: 557 B |
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 16 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="5"><circle cx="42" cy="50" r="20"/><circle cx="52" cy="50" r="20" opacity="0.55"/><circle cx="62" cy="50" r="20" opacity="0.28"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="5"><circle cx="42" cy="50" r="20"/><circle cx="52" cy="50" r="20" opacity="0.55"/><circle cx="62" cy="50" r="20" opacity="0.28"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 585 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 11 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><path d="M50 80 C22 58 18 38 32 28 C42 21 48 27 50 34 C52 27 58 21 68 28 C82 38 78 58 50 80 Z" fill="currentColor"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><path d="M50 80 C22 58 18 38 32 28 C42 21 48 27 50 34 C52 27 58 21 68 28 C82 38 78 58 50 80 Z" fill="currentColor"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 345 B After Width: | Height: | Size: 539 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 14 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><circle cx="50" cy="50" r="28" fill="none" stroke-width="6" stroke-dasharray="7 7"/><path d="M50 32 V56 M50 66 v6" stroke-width="7"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><circle cx="50" cy="50" r="28" fill="none" stroke-width="6" stroke-dasharray="7 7"/><path d="M50 32 V56 M50 66 v6" stroke-width="7"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 362 B After Width: | Height: | Size: 556 B |
|
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 11 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#6a3b5e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="6"><circle cx="34" cy="54" r="12"/><path d="M46 58 h34 M48 70 h24"/><path d="M62 34 l8 -8 M70 34 l-8 -8"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#6a3b5e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#4d2a44" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="6"><circle cx="34" cy="54" r="12"/><path d="M46 58 h34 M48 70 h24"/><path d="M62 34 l8 -8 M70 34 l-8 -8"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 368 B After Width: | Height: | Size: 562 B |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 9.7 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><rect x="30" y="40" width="40" height="34" rx="4"/><path d="M40 40 v-8 a10 10 0 0 1 20 0 v8"/><path d="M20 84 h60"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><rect x="30" y="40" width="40" height="34" rx="4"/><path d="M40 40 v-8 a10 10 0 0 1 20 0 v8"/><path d="M20 84 h60"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 575 B |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 12 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><circle cx="34" cy="50" r="14"/><circle cx="66" cy="50" r="14"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><circle cx="34" cy="50" r="14"/><circle cx="66" cy="50" r="14"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 523 B |
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 16 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><circle cx="50" cy="50" r="30"/><circle cx="50" cy="50" r="14"/><circle cx="50" cy="50" r="3" fill="currentColor"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><circle cx="50" cy="50" r="30"/><circle cx="50" cy="50" r="14"/><circle cx="50" cy="50" r="3" fill="currentColor"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 574 B |
|
Before Width: | Height: | Size: 3.9 KiB After Width: | Height: | Size: 12 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><circle cx="50" cy="30" r="12"/><path d="M50 42 L38 70 M50 42 L64 66 M38 70 l-8 12 M64 66 l10 10"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><circle cx="50" cy="30" r="12"/><path d="M50 42 L38 70 M50 42 L64 66 M38 70 l-8 12 M64 66 l10 10"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 364 B After Width: | Height: | Size: 558 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 10 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><circle cx="26" cy="56" r="11"/><path d="M38 60 h44 M40 72 h30"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><circle cx="26" cy="56" r="11"/><path d="M38 60 h44 M40 72 h30"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 524 B |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 9.7 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="8"><path d="M28 30 L50 52 L28 74 M72 30 L50 52 L72 74"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="8"><path d="M28 30 L50 52 L28 74 M72 30 L50 52 L72 74"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 318 B After Width: | Height: | Size: 512 B |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 13 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="6"><path d="M50 16 V84 M22 32 L78 68 M78 32 L22 68"/><path d="M50 16 l-7 9 M50 16 l7 9"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="6"><path d="M50 16 V84 M22 32 L78 68 M78 32 L22 68"/><path d="M50 16 l-7 9 M50 16 l7 9"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 351 B After Width: | Height: | Size: 545 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 9.8 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#6a3b5e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="6"><rect x="30" y="38" width="40" height="24" rx="5"/><rect x="70" y="44" width="6" height="12"/><path d="M38 50 h10" stroke-width="5"/></g><path d="M22 78 L78 22" stroke-width="6"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#6a3b5e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#4d2a44" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="6"><rect x="30" y="38" width="40" height="24" rx="5"/><rect x="70" y="44" width="6" height="12"/><path d="M38 50 h10" stroke-width="5"/></g><path d="M22 78 L78 22" stroke-width="6"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 440 B After Width: | Height: | Size: 634 B |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 14 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#6a3b5e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><g fill="none" stroke-width="7"><circle cx="50" cy="38" r="18"/><path d="M38 34 l8 8 M54 34 l8 8 M40 44 l6 -6 M56 44 l6 -6"/><path d="M28 84 q22 -16 44 0"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#6a3b5e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#4d2a44" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><g fill="none" stroke-width="7"><circle cx="50" cy="38" r="18"/><path d="M38 34 l8 8 M54 34 l8 8 M40 44 l6 -6 M56 44 l6 -6"/><path d="M28 84 q22 -16 44 0"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 389 B After Width: | Height: | Size: 583 B |
|
Before Width: | Height: | Size: 4.2 KiB After Width: | Height: | Size: 14 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#14544e" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><path d="M50 14 L82 26 V52 C82 72 68 84 50 90 C32 84 18 72 18 52 V26 Z" fill="none" stroke-width="7"/><circle cx="50" cy="50" r="9" fill="currentColor"/></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#14544e" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#0e3f3a" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><path d="M50 14 L82 26 V52 C82 72 68 84 50 90 C32 84 18 72 18 52 V26 Z" fill="none" stroke-width="7"/><circle cx="50" cy="50" r="9" fill="currentColor"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 382 B After Width: | Height: | Size: 576 B |
|
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 10 KiB |
@@ -1 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="128" height="128"><circle cx="50" cy="50" r="47" fill="#f7f0de" stroke="#8a2f2f" stroke-width="4"/><g color="#2a2317" fill="#2a2317" stroke="#2a2317"><path d="M50 14 C40 40 40 60 50 86 C60 60 60 40 50 14 Z" fill="currentColor"/><g stroke="#f7f0de" stroke-width="4"><path d="M44 40 h12 M42 56 h16"/></g></g></svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="256" height="256"><rect x="5" y="5" width="90" height="90" rx="22" fill="#8a2f2f" stroke="#f7f0de" stroke-width="5"/><rect x="11" y="11" width="78" height="78" rx="16" fill="none" stroke="#5f1d1d" stroke-width="2" opacity="0.6"/><g color="#ffffff" fill="#ffffff" stroke="#ffffff" stroke-linecap="round" stroke-linejoin="round" stroke-width="7"><path d="M50 14 C40 40 40 60 50 86 C60 60 60 40 50 14 Z" fill="currentColor"/><g stroke="#8a2f2f" stroke-width="4"><path d="M44 40 h12 M42 56 h16"/></g></g></svg>
|
||||
|
Before Width: | Height: | Size: 381 B After Width: | Height: | Size: 575 B |
@@ -44,9 +44,9 @@ export const VANITY_CONDITIONS = [
|
||||
"id": "marked",
|
||||
"name": "Marked",
|
||||
"group": "combat",
|
||||
"short": "Your next Stumble banks 2 Banes",
|
||||
"rules": "Something patient has written your name. <b>Your next Stumble banks 2 Banes</b> instead of 1 — the GM will tell you when it lands.",
|
||||
"ends": "Your next Stumble spends it"
|
||||
"short": "The next Bane you bank counts as 2",
|
||||
"rules": "Something patient has written your name. <b>The next Bane you bank counts as 2</b> instead of 1 — the GM will tell you when it lands.",
|
||||
"ends": "The next time you bank a Bane"
|
||||
},
|
||||
{
|
||||
"id": "encumbered",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.379791 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.380374 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.380531 17448f000 Level-0 table #5: 2618 bytes OK
|
||||
2026/07/28-08:51:35.380609 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.380657 17448f000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.440581 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.440964 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.441167 17448f000 Level-0 table #5: 5226 bytes OK
|
||||
2026/07/28-08:51:35.441259 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.441324 17448f000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.445181 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.445636 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.445866 17448f000 Level-0 table #5: 4974 bytes OK
|
||||
2026/07/28-08:51:35.445971 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.446024 17448f000 Manual compaction at level-0 from '!actors!1FGl7v1mODKvN9fy' @ 72057594037927935 : 1 .. '!actors!yEfKBqSa4GmIgOpx' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.430932 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.431308 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.431493 17448f000 Level-0 table #5: 5417 bytes OK
|
||||
2026/07/28-08:51:35.431570 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.431614 17448f000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.389889 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.390383 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.390663 17448f000 Level-0 table #5: 9529 bytes OK
|
||||
2026/07/28-08:51:35.390746 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.390791 17448f000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.469895 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.470270 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.470468 17448f000 Level-0 table #5: 10157 bytes OK
|
||||
2026/07/28-08:51:35.470538 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.470582 17448f000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.459780 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.461023 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.461965 17448f000 Level-0 table #5: 120609 bytes OK
|
||||
2026/07/28-08:51:35.462052 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.462103 17448f000 Manual compaction at level-0 from '!actors!3Kjqu9ibyf8TjSrk' @ 72057594037927935 : 1 .. '!actors.items!zR0OZzFsVQLTSlBM.uc6AKpWyZeREAX2M' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
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)
|
||||
2026/07/28-08:51:35.465802 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.466501 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.467349 17448f000 Level-0 table #5: 76482 bytes OK
|
||||
2026/07/28-08:51:35.467439 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.467519 17448f000 Manual compaction at level-0 from '!journal!CNrYR2rXwHMBm0lH' @ 72057594037927935 : 1 .. '!journal.pages!CNrYR2rXwHMBm0lH.yPi5N6WdEe1jn19i' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/07/22-22:53:09.388862 171c5b000 Delete type=3 #1
|
||||
2026/07/22-22:53:09.389229 1724f3000 Level-0 table #5: started
|
||||
2026/07/22-22:53:09.389426 1724f3000 Level-0 table #5: 11569 bytes OK
|
||||
2026/07/22-22:53:09.389478 1724f3000 Delete type=0 #3
|
||||
2026/07/22-22:53:09.389511 1724f3000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
|
||||
2026/07/28-08:51:35.425396 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.425797 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.426025 17448f000 Level-0 table #5: 11569 bytes OK
|
||||
2026/07/28-08:51:35.426103 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.426147 17448f000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/07/22-22:53:09.432699 171c5b000 Delete type=3 #1
|
||||
2026/07/22-22:53:09.433167 1724f3000 Level-0 table #5: started
|
||||
2026/07/22-22:53:09.433520 1724f3000 Level-0 table #5: 15588 bytes OK
|
||||
2026/07/22-22:53:09.433594 1724f3000 Delete type=0 #3
|
||||
2026/07/22-22:53:09.433642 1724f3000 Manual compaction at level-0 from '!tables!JEOcxCxjK6S6pVEZ' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
|
||||
2026/07/28-08:51:35.473894 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.474273 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.474514 17448f000 Level-0 table #5: 11912 bytes OK
|
||||
2026/07/28-08:51:35.474600 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.474660 17448f000 Manual compaction at level-0 from '!tables!Ci4uLuCx15pTKdhP' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/07/22-22:53:09.382119 171c5b000 Delete type=3 #1
|
||||
2026/07/22-22:53:09.382653 1724f3000 Level-0 table #5: started
|
||||
2026/07/22-22:53:09.383100 1724f3000 Level-0 table #5: 27619 bytes OK
|
||||
2026/07/22-22:53:09.383168 1724f3000 Delete type=0 #3
|
||||
2026/07/22-22:53:09.383221 1724f3000 Manual compaction at level-0 from '!items!0NDwQbhQQNeCCEco' @ 72057594037927935 : 1 .. '!items!yxds1AZFNXBpERdk' @ 0 : 0; will stop at (end)
|
||||
2026/07/28-08:51:35.417879 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.418464 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.418843 17448f000 Level-0 table #5: 27619 bytes OK
|
||||
2026/07/28-08:51:35.418931 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.418979 17448f000 Manual compaction at level-0 from '!items!0NDwQbhQQNeCCEco' @ 72057594037927935 : 1 .. '!items!yxds1AZFNXBpERdk' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/07/22-22:53:09.397830 171c5b000 Delete type=3 #1
|
||||
2026/07/22-22:53:09.398050 1724f3000 Level-0 table #5: started
|
||||
2026/07/22-22:53:09.398158 1724f3000 Level-0 table #5: 1461 bytes OK
|
||||
2026/07/22-22:53:09.398227 1724f3000 Delete type=0 #3
|
||||
2026/07/22-22:53:09.398280 1724f3000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
|
||||
2026/07/28-08:51:35.433688 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.433973 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.434099 17448f000 Level-0 table #5: 1461 bytes OK
|
||||
2026/07/28-08:51:35.434174 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.434224 17448f000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
2026/07/22-22:53:09.334806 171c5b000 Delete type=3 #1
|
||||
2026/07/22-22:53:09.336531 1724f3000 Level-0 table #5: started
|
||||
2026/07/22-22:53:09.336701 1724f3000 Level-0 table #5: 7415 bytes OK
|
||||
2026/07/22-22:53:09.336755 1724f3000 Delete type=0 #3
|
||||
2026/07/22-22:53:09.336799 1724f3000 Manual compaction at level-0 from '!items!0d6FZCtl78jEPkof' @ 72057594037927935 : 1 .. '!items!v3IghflF7cI2FzbU' @ 0 : 0; will stop at (end)
|
||||
2026/07/28-08:51:35.372738 173bf7000 Delete type=3 #1
|
||||
2026/07/28-08:51:35.375852 17448f000 Level-0 table #5: started
|
||||
2026/07/28-08:51:35.376112 17448f000 Level-0 table #5: 7415 bytes OK
|
||||
2026/07/28-08:51:35.376192 17448f000 Delete type=0 #3
|
||||
2026/07/28-08:51:35.376246 17448f000 Manual compaction at level-0 from '!items!0d6FZCtl78jEPkof' @ 72057594037927935 : 1 .. '!items!v3IghflF7cI2FzbU' @ 0 : 0; will stop at (end)
|
||||
|
||||
@@ -592,6 +592,30 @@
|
||||
.vanity-buttons button:hover { background: var(--v-teal-d); }
|
||||
.vanity-buttons .vanity-fizzle { background: var(--v-plum); }
|
||||
|
||||
/* The Bane decision: burn a Success, or take the Bane */
|
||||
.vanity-bane-choice {
|
||||
margin-top: 7px; padding: 8px 10px; border-radius: 5px;
|
||||
background: rgba(138, 31, 31, 0.08); border: 1px solid rgba(138, 31, 31, 0.35);
|
||||
}
|
||||
.vanity-bane-choice .warn {
|
||||
display: block; color: #8a1f1f; font-size: 12px; line-height: 1.35;
|
||||
margin-bottom: 7px; text-transform: none; letter-spacing: normal;
|
||||
}
|
||||
.vanity-bane-choice .vanity-buttons { margin-top: 0; }
|
||||
.vanity-bane-choice .vanity-buttons button {
|
||||
display: inline-flex; align-items: center; justify-content: center; gap: 5px;
|
||||
line-height: 1.2; letter-spacing: 0.02em; padding: 8px 6px; text-align: center;
|
||||
}
|
||||
.vanity-bane-choice .vanity-burn { background: var(--v-gold); color: #2a1c06; }
|
||||
.vanity-bane-choice .vanity-burn:hover { background: #caa23f; }
|
||||
.vanity-bane-choice .vanity-takebane { background: #8a1f1f; color: var(--v-paper); }
|
||||
.vanity-bane-choice .vanity-takebane:hover { background: #6f1717; }
|
||||
|
||||
/* Momentum — a great roll earns a die on the next Draw */
|
||||
.vanity-momentum { border-color: var(--v-gold); }
|
||||
.vanity-momentum .vanity-roll-label, .vanity-momentum p b { color: var(--v-gold); }
|
||||
.vanity-sheet .roll-draw .mo { color: var(--v-gold); font-weight: 700; margin-left: 3px; }
|
||||
|
||||
/* ---------------- Roll dialog ---------------- */
|
||||
|
||||
.vanity-roll-dialog .pool-line { font-size: 14px; }
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"id": "vanity",
|
||||
"title": "VANITY — the Vanity Role-Playing Game",
|
||||
"description": "A generic d6 RPG where ego is a resource and the universe keeps score. Pool = Attribute + Knack + situation; 5–6 = a Success; fail with two 1s = a Stumble that banks a Bane of Karma (Draft 13). No hit points — Grit. Vanity, Karma and Glory drive play. Includes the full Draft 13 rulebook, all spells and gear, the starter bestiary, a 100-item Treasures & Relics library, twenty ready-to-play characters (each with cameo art, token and backstory), ten animal companions with the d10 Bond table and the one-shot adventure The Vain Crown.",
|
||||
"version": "0.10.2",
|
||||
"description": "A generic d6 RPG where ego is a resource and the universe keeps score. Pool = Attribute + Knack + situation; 5–6 = a Success; two or more 1s bank a Bane of Karma unless you burn a Success to cancel one (Draft 14). No hit points — Grit. Vanity, Karma and Glory drive play. Includes the full Draft 13 rulebook, all spells and gear, the starter bestiary, a 100-item Treasures & Relics library, twenty ready-to-play characters (each with cameo art, token and backstory), ten animal companions with the d10 Bond table and the one-shot adventure The Vain Crown.",
|
||||
"version": "0.10.3",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
|
||||
@@ -90,7 +90,8 @@
|
||||
"notes": ""
|
||||
},
|
||||
"notes": "",
|
||||
"biography": ""
|
||||
"biography": "",
|
||||
"momentum": 0
|
||||
},
|
||||
"npc": {
|
||||
"category": "mook",
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
<input class="attr-input" type="number" name="system.attributes.{{key}}.value" value="{{value}}" min="1" max="5" />
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="hint">Pool = Attribute + Knack + situation. 5–6 = a Success. Fail with two 1s = a Stumble (1 Bane).</div>
|
||||
<div class="hint">Pool = Attribute + Knack + situation. 5–6 = a Success. Two or more 1s bank a Bane — burn a Success to cancel one.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
{{#each karmaPips}}<i class="pip hex pip-btn {{#if filled}}filled{{/if}}" data-res="karma" data-i="{{i}}"></i>{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="resnote">Gain: Stumble · Push · Refuse to Fall · your Vice. 6 → THE RECKONING (2d6, §21b). Clear by humility & rest.</div>
|
||||
<div class="resnote">Gain: two fails · Push · Refuse to Fall · your Vice. 6 → THE RECKONING (2d6, §21b). Clear by humility & rest.</div>
|
||||
<div class="resrow glory">
|
||||
<span class="rl">Glory</span>
|
||||
<input type="number" name="system.glory" value="{{sys.glory}}" min="0" />
|
||||
@@ -159,7 +159,7 @@
|
||||
{{!-- ============ TAB: COMBAT & GEAR ============ --}}
|
||||
<div class="tab" data-tab="combat" data-group="primary">
|
||||
<div class="v-quickbar">
|
||||
<button type="button" class="roll-draw"><i class="fa-solid fa-bolt"></i> The Draw (1d6)</button>
|
||||
<button type="button" class="roll-draw" title="{{#if sys.momentum}}Momentum: +{{sys.momentum}} die on this Draw (keep highest){{else}}Act highest to lowest{{/if}}"><i class="fa-solid fa-bolt"></i> The Draw ({{#if sys.momentum}}{{add 1 sys.momentum}}d6 kh{{else}}1d6{{/if}}){{#if sys.momentum}} <span class="mo">✦{{sys.momentum}}</span>{{/if}}</button>
|
||||
<button type="button" class="roll-dodge"><i class="fa-solid fa-person-running"></i> Dodge {{sys.dodgePool}}d</button>
|
||||
<button type="button" class="roll-block"><i class="fa-solid fa-shield-halved"></i> Block {{sys.blockPool}}d</button>
|
||||
<button type="button" class="refuse-fall" title="A blow would empty your Grit: spend 1 Vanity to stop at 1 Grit (+2 Banes)"><i class="fa-solid fa-hand"></i> Refuse to Fall</button>
|
||||
|
||||
@@ -287,7 +287,7 @@ class VanityActor extends Actor {
|
||||
<div class="vanity-buttons">
|
||||
${karma >= 6
|
||||
? `<button type="button" class="vanity-reckon" data-actor-uuid="${this.uuid}"><i class="fa-solid fa-skull"></i> GM: THE RECKONING — roll 2d6, wipe the tab</button>`
|
||||
: `<button type="button" class="vanity-twist" data-actor-uuid="${this.uuid}"><i class="fa-solid fa-dice"></i> GM: Twist the Knife — roll the d66</button>`}
|
||||
: `<button type="button" class="vanity-twist" data-actor-uuid="${this.uuid}"><i class="fa-solid fa-dice"></i> GM: Twist the Knife — roll 2d6</button>`}
|
||||
</div>
|
||||
</div>`
|
||||
});
|
||||
@@ -296,6 +296,16 @@ class VanityActor extends Actor {
|
||||
|
||||
class VanityItem extends Item {}
|
||||
|
||||
/** The Draw with Momentum: a hero who earned Momentum (a 3+ Success roll) adds dice
|
||||
* to their next initiative and keeps the highest. Consumed as it is rolled. */
|
||||
class VanityCombatant extends foundry.documents.Combatant {
|
||||
_getInitiativeFormula() {
|
||||
const m = Math.max(0, Math.floor(this.actor?.system?.momentum ?? 0));
|
||||
if (m > 0 && this.actor?.isOwner) this.actor.update({ "system.momentum": 0 }).catch(() => {});
|
||||
return m > 0 ? `${1 + m}d6kh1` : (CONFIG.Combat.initiative?.formula || "1d6");
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
/* The dice engine */
|
||||
/* -------------------------------------------- */
|
||||
@@ -315,10 +325,21 @@ 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, target = null }) {
|
||||
const successes = results.filter(r => r >= 5).length;
|
||||
const ones = results.filter(r => r === 1).length;
|
||||
const stumble = successes === 0 && ones >= 2;
|
||||
/** The tab, resolved (Draft 14.1). Two+ fails threaten a Bane regardless of Successes;
|
||||
* a player may burn 1 Success to cancel one fail (drop below two dodges the Bane). A
|
||||
* single remaining fail is the GM's free Twist. `canceled` = fails cancelled so far. */
|
||||
function baneState(results, canceled = 0) {
|
||||
const rawSucc = results.filter(r => r >= 5).length;
|
||||
const rawOnes = results.filter(r => r === 1).length;
|
||||
const succ = Math.max(0, rawSucc - canceled);
|
||||
const ones = Math.max(0, rawOnes - canceled);
|
||||
return { rawSucc, rawOnes, canceled, succ, ones,
|
||||
threat: ones >= 2, canBurn: ones >= 2 && succ >= 1, singleFail: ones === 1 };
|
||||
}
|
||||
|
||||
function renderRollCard({ label, sublabel, results, threshold, pushed, context, extra, spend, canceled = 0, baneResolved = null, isChar = false, twistUsed = false, spell = null, target = null }) {
|
||||
const st = baneState(results, canceled);
|
||||
const successes = st.succ;
|
||||
const met = threshold ? successes >= threshold : successes >= 1;
|
||||
|
||||
let outcome;
|
||||
@@ -344,6 +365,26 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context,
|
||||
? renderSpendPanel({ successes, spend, sublabel, menu: menuFor(context), context, threshold })
|
||||
: "";
|
||||
|
||||
// ── the tab ── 2+ fails threaten a Bane (burn 1 Success to cancel one); a single fail is a free twist.
|
||||
const pending = isChar && st.threat && baneResolved !== "banked" && baneResolved !== "cleared";
|
||||
let baneUI = "";
|
||||
if (isChar && baneResolved === "banked") {
|
||||
baneUI = `<div class="vanity-outcome"><span class="banes">· ${st.canceled ? `burned ${st.canceled} Success${st.canceled > 1 ? "es" : ""}, then ` : ""}the tab grows — <b>+1 Bane</b> banked</span></div>`;
|
||||
} else if (pending) {
|
||||
const burn = st.canBurn
|
||||
? `<button type="button" class="vanity-burn"><i class="fa-solid fa-fire-flame-simple"></i> Burn a Success</button>`
|
||||
: "";
|
||||
baneUI = `<div class="vanity-bane-choice">
|
||||
<span class="warn"><i class="fa-solid fa-triangle-exclamation"></i> <b>${st.ones} fails</b> — the tab threatens a <b>Bane</b>.${st.canBurn ? " Burn a Success to cancel one, or take it." : " No Success to spare — it banks."}</span>
|
||||
<div class="vanity-buttons">${burn}<button type="button" class="vanity-takebane"><i class="fa-solid fa-skull"></i> Take the Bane</button></div>
|
||||
</div>`;
|
||||
} else if (isChar && baneResolved === "cleared") {
|
||||
baneUI = `<div class="vanity-outcome"><span class="banes soft">· burned ${st.canceled} Success${st.canceled > 1 ? "es" : ""} — the tab stays clean</span></div>`;
|
||||
}
|
||||
const freeTwist = isChar && st.singleFail && !twistUsed && !pending && baneResolved !== "banked"
|
||||
? `<div class="vanity-buttons twist-row"><button type="button" class="vanity-twist free" data-free="true"><i class="fa-solid fa-dice"></i> GM: Twist the Knife — free 2d6</button></div>`
|
||||
: (twistUsed ? `<div class="twist-spent">The knife has been twisted.</div>` : "");
|
||||
|
||||
const spellBanner = spell ? `
|
||||
<div class="vanity-spellcard ${spell.school ?? ""}">
|
||||
<img src="${spell.img}" alt="">
|
||||
@@ -367,16 +408,14 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context,
|
||||
<div class="vanity-dice">${renderDice(results)}</div>
|
||||
<div class="vanity-outcome">
|
||||
${outcome}
|
||||
${stumble
|
||||
? `<span class="banes">· a STUMBLE${banked ? " — 1 Bane banked" : ""}</span>`
|
||||
: ones ? `<span class="banes soft">· ${ones === 1 ? "a lone 1" : `${ones} ones`} — the GM may twist the knife now</span>` : ""}
|
||||
${st.ones && !pending && baneResolved !== "banked" && baneResolved !== "cleared"
|
||||
? `<span class="banes soft">· ${st.ones === 1 ? "a lone 1 — a single fail" : `${st.ones} ones`}</span>` : ""}
|
||||
${pushed ? `<span class="pushed">· Pushed</span>` : ""}
|
||||
</div>
|
||||
${!stumble && ones && !twistUsed
|
||||
? `<div class="vanity-buttons twist-row"><button type="button" class="vanity-twist free" data-free="true"><i class="fa-solid fa-dice"></i> GM: Twist the Knife — free d66</button></div>`
|
||||
: twistUsed ? `<div class="twist-spent">The knife has been twisted.</div>` : ""}
|
||||
${baneUI}
|
||||
${freeTwist}
|
||||
${spellScaling}
|
||||
${targetBlock(target, results, spend, context)}
|
||||
${targetBlock(target, results, spend, context, canceled)}
|
||||
${extra ? `<div class="vanity-extra">${extra}</div>` : ""}
|
||||
${specials}
|
||||
${buttons ? `<div class="vanity-buttons">${buttons}</div>` : ""}
|
||||
@@ -389,9 +428,9 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context,
|
||||
* 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, context = "attack") {
|
||||
function targetBlock(target, results, spend, context = "attack", canceled = 0) {
|
||||
if (!target) return "";
|
||||
const successes = results.filter(r => r >= 5).length;
|
||||
const successes = Math.max(0, results.filter(r => r >= 5).length - canceled);
|
||||
const net = Math.max(0, successes - (spend?.defence ?? target.successes ?? 0));
|
||||
// In an exchange the first net Success IS the hit — 1 Grit before anything is
|
||||
// bought. Only a spell starts from nothing, because its Threshold bought the
|
||||
@@ -468,11 +507,16 @@ async function rollPool(actor, pool, label, options = {}) {
|
||||
lockVanityDice(roll);
|
||||
const results = roll.dice[0].results.map(r => r.result);
|
||||
|
||||
// The Stumble (Draft 11): bank 1 Bane only on a failed roll with two or more 1s.
|
||||
const ones = results.filter(r => r === 1).length;
|
||||
const successes = results.filter(r => r >= 5).length;
|
||||
const stumble = successes === 0 && ones >= 2;
|
||||
const banked = stumble && actor?.type === "character" ? 1 : 0;
|
||||
// Two or more fails threaten a Bane regardless of Successes. If the roller can burn
|
||||
// (1 Success cancels one fail) they decide on the card; if not, it banks automatically.
|
||||
// A single fail is the GM's free twist, not a Bane.
|
||||
const isChar = actor?.type === "character";
|
||||
const st = baneState(results, 0);
|
||||
let baneResolved = null, autoBank = 0;
|
||||
if (isChar && st.threat) {
|
||||
if (st.canBurn) baneResolved = null; // pending — the player chooses
|
||||
else { baneResolved = "banked"; autoBank = 1; } // no Success to burn — it banks
|
||||
}
|
||||
|
||||
const flags = {
|
||||
vanity: {
|
||||
@@ -486,7 +530,9 @@ async function rollPool(actor, pool, label, options = {}) {
|
||||
actorUuid: actor?.uuid ?? "",
|
||||
pushed: false,
|
||||
extra: "",
|
||||
banked,
|
||||
canceled: 0,
|
||||
baneResolved,
|
||||
isChar,
|
||||
spell: options.spell ?? null,
|
||||
target: options.target ?? null,
|
||||
spend: options.spend ?? { defence: 0, damage: 0, maneuvers: [] }
|
||||
@@ -499,12 +545,26 @@ async function rollPool(actor, pool, label, options = {}) {
|
||||
sound: CONFIG.sounds.dice,
|
||||
content: renderRollCard({
|
||||
label, sublabel: options.sublabel, results, threshold: options.threshold, pushed: false,
|
||||
context: options.context, banked, spell: options.spell ?? null,
|
||||
context: options.context, canceled: 0, baneResolved, isChar, spell: options.spell ?? null,
|
||||
target: options.target ?? null, spend: flags.vanity.spend
|
||||
}),
|
||||
flags
|
||||
});
|
||||
if (banked) await actor.addBanes(1, `a Stumble on “${label}”`);
|
||||
if (autoBank) await actor.addBanes(1, `two fails on “${label}”`);
|
||||
|
||||
// Momentum: a roll of 3+ Successes (two beyond the one needed) earns a die on the
|
||||
// next Draw. Caps at +2; a fresh grant only heralds when it actually rises.
|
||||
if (isChar && st.succ >= 3 && options.context !== "draw") {
|
||||
const old = actor.system.momentum ?? 0;
|
||||
const m = Math.min(2, old + 1);
|
||||
if (m > old) {
|
||||
await actor.update({ "system.momentum": m });
|
||||
await ChatMessage.create({
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
content: `<div class="vanity-roll vanity-momentum"><p>✦ <b>Momentum</b> — ${st.succ} Successes. ${actor.name} carries <b>+${m} die</b> on their next Draw (keep highest).</p></div>`
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------- */
|
||||
@@ -680,16 +740,19 @@ async function handlePush(message) {
|
||||
if (game.dice3d) await game.dice3d.showForRoll(roll, game.user, true);
|
||||
}
|
||||
|
||||
// Push costs 1 Bane; a pushed roll that STILL fails with two or more 1s Stumbles for another.
|
||||
const successes = newResults.filter(r => r >= 5).length;
|
||||
const ones = newResults.filter(r => r === 1).length;
|
||||
const pushedStumble = successes === 0 && ones >= 2;
|
||||
// The Push always bills 1 Bane (chosen hubris). The pushed result's 2+ fails then
|
||||
// threaten a further Bane, which the player may burn a Success to dodge on the card.
|
||||
const isCharacter = actor?.type === "character";
|
||||
const paid = 1 + (pushedStumble ? 1 : 0);
|
||||
const banked = pushedStumble && isCharacter ? 1 : 0;
|
||||
const st = baneState(newResults, 0);
|
||||
let baneResolved = null, autoBank = 0;
|
||||
if (isCharacter && st.threat) {
|
||||
if (st.canBurn) baneResolved = null;
|
||||
else { baneResolved = "banked"; autoBank = 1; }
|
||||
}
|
||||
const successes = st.succ;
|
||||
|
||||
let extra = pushedStumble
|
||||
? `<span class="pushed-note">${game.i18n.localize("VANITY.Pushed")} — and pushed straight into a Stumble (+${paid} Banes).</span>`
|
||||
let extra = autoBank
|
||||
? `<span class="pushed-note">${game.i18n.localize("VANITY.Pushed")} — and straight into two fails (+2 Banes).</span>`
|
||||
: `<span class="pushed-note">${game.i18n.localize("VANITY.Pushed")} (+1 Bane).</span>`;
|
||||
|
||||
// A failed Push on a spell is a CATASTROPHE.
|
||||
@@ -709,16 +772,18 @@ 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, target: f.target ?? null
|
||||
threshold: f.threshold, pushed: true, context: f.context, extra, spend,
|
||||
canceled: 0, baneResolved, isChar: isCharacter, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null
|
||||
}),
|
||||
"flags.vanity.results": newResults,
|
||||
"flags.vanity.pushed": true,
|
||||
"flags.vanity.extra": extra,
|
||||
"flags.vanity.banked": banked,
|
||||
"flags.vanity.canceled": 0,
|
||||
"flags.vanity.baneResolved": baneResolved,
|
||||
"flags.vanity.spend": spend
|
||||
});
|
||||
if (isCharacter) {
|
||||
await actor.addBanes(paid, pushedStumble ? `Pushed “${f.label}” straight into a Stumble` : `Pushed “${f.label}”`);
|
||||
await actor.addBanes(1 + autoBank, autoBank ? `Pushed “${f.label}” into two fails` : `Pushed “${f.label}”`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -726,7 +791,7 @@ async function handlePush(message) {
|
||||
async function handleSpend(message, dataset) {
|
||||
const f = message.flags?.vanity;
|
||||
if (!f) return;
|
||||
const successes = f.results.filter(r => r >= 5).length;
|
||||
const successes = baneState(f.results, f.canceled ?? 0).succ;
|
||||
const spend = { defence: 0, damage: 0, maneuvers: [], ...(f.spend ?? {}) };
|
||||
|
||||
if (dataset.def !== undefined) {
|
||||
@@ -751,12 +816,57 @@ 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, target: f.target ?? null
|
||||
threshold: f.threshold, pushed: f.pushed, context: f.context, extra: f.extra, spend, canceled: f.canceled ?? 0, baneResolved: f.baneResolved ?? null, isChar: f.isChar, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null
|
||||
}),
|
||||
"flags.vanity.spend": spend
|
||||
});
|
||||
}
|
||||
|
||||
/** Burn 1 Success to cancel one fail. Drop below two fails and the Bane is dodged. */
|
||||
async function handleBurn(message) {
|
||||
const f = message.flags?.vanity;
|
||||
if (!f || f.baneResolved === "banked" || f.baneResolved === "cleared") return;
|
||||
if (!baneState(f.results, f.canceled ?? 0).canBurn) return;
|
||||
const canceled = (f.canceled ?? 0) + 1;
|
||||
const st = baneState(f.results, canceled);
|
||||
const baneResolved = st.threat ? (f.baneResolved ?? null) : "cleared";
|
||||
|
||||
// Fewer Successes may make a prior attack-spend illegal — trim to the new budget.
|
||||
const spend = { defence: f.spend?.defence ?? 0, damage: f.spend?.damage ?? 0, maneuvers: [...(f.spend?.maneuvers ?? [])] };
|
||||
const budget = f.context === "spell"
|
||||
? Math.max(0, st.succ - (f.threshold || 1))
|
||||
: Math.max(0, st.succ - 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) { spend.damage = 0; spend.maneuvers = []; }
|
||||
|
||||
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,
|
||||
canceled, baneResolved, isChar: f.isChar, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null
|
||||
}),
|
||||
"flags.vanity.canceled": canceled,
|
||||
"flags.vanity.baneResolved": baneResolved,
|
||||
"flags.vanity.spend": spend
|
||||
});
|
||||
}
|
||||
|
||||
/** Accept the Bane rather than burn Successes: the tab grows by one. */
|
||||
async function handleTakeBane(message) {
|
||||
const f = message.flags?.vanity;
|
||||
if (!f || f.baneResolved === "banked" || f.baneResolved === "cleared") return;
|
||||
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: f.spend,
|
||||
canceled: f.canceled ?? 0, baneResolved: "banked", isChar: f.isChar, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null
|
||||
}),
|
||||
"flags.vanity.baneResolved": "banked"
|
||||
});
|
||||
const actor = f.actorUuid ? await fromUuid(f.actorUuid) : null;
|
||||
if (actor?.type === "character") await actor.addBanes(1, `two fails on “${f.label}”`);
|
||||
}
|
||||
|
||||
async function handleFizzle(message) {
|
||||
const f = message.flags?.vanity;
|
||||
if (!f || f.pushed) return;
|
||||
@@ -768,7 +878,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, target: f.target ?? null
|
||||
threshold: f.threshold, pushed: true, context: f.context, extra, canceled: f.canceled ?? 0, baneResolved: f.baneResolved ?? null, isChar: f.isChar, twistUsed: f.twistUsed ?? false, spell: f.spell ?? null, target: f.target ?? null
|
||||
}),
|
||||
"flags.vanity.pushed": true,
|
||||
"flags.vanity.extra": extra
|
||||
@@ -953,9 +1063,18 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet {
|
||||
html.find(".roll-draw").click(async () => {
|
||||
if (actor.inCombat) {
|
||||
const c = game.combat?.combatants.find(c => c.actorId === actor.id);
|
||||
if (c) return game.combat.rollInitiative([c.id]);
|
||||
if (c) return game.combat.rollInitiative([c.id]); // VanityCombatant folds in Momentum
|
||||
}
|
||||
await rollPool(actor, 1, "The Draw (initiative)", { sublabel: "act highest to lowest — reroll each round" });
|
||||
// Out of combat: 1d6 + any Momentum dice, keep the highest; consume the Momentum.
|
||||
const m = Math.max(0, Math.floor(actor.system.momentum ?? 0));
|
||||
const roll = new Roll(`${1 + m}d6kh1`);
|
||||
await roll.evaluate();
|
||||
lockVanityDice(roll);
|
||||
if (m) await actor.update({ "system.momentum": 0 });
|
||||
await roll.toMessage({
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
flavor: `The Draw (initiative)${m ? ` — +${m} Momentum` : ""} · act highest to lowest`
|
||||
});
|
||||
});
|
||||
|
||||
// Weapon attack
|
||||
@@ -1321,7 +1440,7 @@ Hooks.on("diceSoNiceRollStart", (messageId, context) => {
|
||||
/* Twist the Knife — the GM spends the tab */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
const TWIST_TABLE_NAME = "Twist the Knife — spending Banes (d66)";
|
||||
const TWIST_TABLE_NAME = "Twist the Knife — a single fail (2d6)";
|
||||
const RECKONING_TABLE_NAME = "THE RECKONING — the tab reads six (2d6)";
|
||||
const BOND_TABLE_NAME = "The Bond — Animal Companions (d10)";
|
||||
|
||||
@@ -1387,7 +1506,7 @@ async function twistTheKnife({ actorUuid = "", free = false, sourceMessage = nul
|
||||
const table = await getVanityTable(TWIST_TABLE_NAME);
|
||||
if (!table) return ui.notifications.error(`"${TWIST_TABLE_NAME}" not found in world or compendium.`);
|
||||
|
||||
const roll = new Roll("1d6*10 + 1d6");
|
||||
const roll = new Roll("2d6");
|
||||
await roll.evaluate();
|
||||
await showBaneDice(roll);
|
||||
// table.draw({roll}) re-rolls internally — map our roll to its result so the red dice
|
||||
@@ -1415,7 +1534,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, target: f.target ?? null
|
||||
canceled: f.canceled ?? 0, baneResolved: f.baneResolved ?? null, isChar: f.isChar, twistUsed: true, spell: f.spell ?? null, target: f.target ?? null
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -2039,6 +2158,7 @@ Hooks.once("init", () => {
|
||||
CONFIG.specialStatusEffects.DEFEATED = "taken-out";
|
||||
|
||||
CONFIG.Actor.documentClass = VanityActor;
|
||||
CONFIG.Combatant.documentClass = VanityCombatant;
|
||||
CONFIG.Item.documentClass = VanityItem;
|
||||
CONFIG.Combat.initiative = { formula: "1d6", decimals: 0 };
|
||||
|
||||
@@ -2106,6 +2226,52 @@ Hooks.on("combatRound", async (combat, updateData, updateOptions) => {
|
||||
await combat.rollAll();
|
||||
});
|
||||
|
||||
/* Make an Entrance — promote a combatant to the top of The Draw without re-rolling
|
||||
* for a 6. Right-click a combatant in the tracker. */
|
||||
function combatantIdFromEntry(li) {
|
||||
const el = li?.[0] ?? li;
|
||||
return el?.dataset?.combatantId
|
||||
?? el?.closest?.("[data-combatant-id]")?.dataset?.combatantId
|
||||
?? (li?.data ? li.data("combatant-id") : null);
|
||||
}
|
||||
async function promoteToTop(combatant) {
|
||||
const combat = combatant?.combat ?? game.combat;
|
||||
if (!combat || !combatant) return;
|
||||
const top = Math.max(0, ...combat.combatants.map(c => (Number.isFinite(c.initiative) ? c.initiative : 0)));
|
||||
await combatant.update({ initiative: top + 1 });
|
||||
}
|
||||
Hooks.on("getCombatTrackerEntryContext", (html, options) => {
|
||||
options.push({
|
||||
name: "VANITY: Go first — make an entrance (1 Vanity)",
|
||||
icon: '<i class="fa-solid fa-crown"></i>',
|
||||
condition: li => {
|
||||
const c = game.combat?.combatants.get(combatantIdFromEntry(li));
|
||||
return !!c && c.actor?.type === "character" && (game.user.isGM || c.actor?.isOwner);
|
||||
},
|
||||
callback: async li => {
|
||||
const c = game.combat?.combatants.get(combatantIdFromEntry(li));
|
||||
if (!c?.actor) return;
|
||||
const van = c.actor.system.vanity ?? 0;
|
||||
if (van < 1) return ui.notifications.warn(`${c.actor.name} has no Vanity to spend on an entrance.`);
|
||||
await c.actor.update({ "system.vanity": van - 1 });
|
||||
await promoteToTop(c);
|
||||
await ChatMessage.create({
|
||||
speaker: { alias: "The Draw" },
|
||||
content: `<div class="vanity-roll vanity-tab-note"><p><b>${c.name}</b> spends <b>1 Vanity</b> and makes an entrance — <b>first in the round</b>.</p></div>`
|
||||
});
|
||||
}
|
||||
});
|
||||
options.push({
|
||||
name: "VANITY: Move to top of the order",
|
||||
icon: '<i class="fa-solid fa-angles-up"></i>',
|
||||
condition: () => game.user.isGM,
|
||||
callback: async li => {
|
||||
const c = game.combat?.combatants.get(combatantIdFromEntry(li));
|
||||
if (c) await promoteToTop(c);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/* Chat card buttons. */
|
||||
Hooks.on("renderChatMessageHTML", (message, html) => {
|
||||
html.querySelectorAll(".vanity-push").forEach(btn => {
|
||||
@@ -2136,6 +2302,22 @@ Hooks.on("renderChatMessageHTML", (message, html) => {
|
||||
applyDamageToTarget(message, Number(btn.dataset.damage) || 0);
|
||||
});
|
||||
});
|
||||
html.querySelectorAll(".vanity-burn").forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!game.user.isGM && message.author?.id !== game.user.id) {
|
||||
return ui.notifications.warn("Only the roller (or the GM) may burn Successes.");
|
||||
}
|
||||
handleBurn(message);
|
||||
});
|
||||
});
|
||||
html.querySelectorAll(".vanity-takebane").forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!game.user.isGM && message.author?.id !== game.user.id) {
|
||||
return ui.notifications.warn("Only the roller (or the GM) may take the Bane.");
|
||||
}
|
||||
handleTakeBane(message);
|
||||
});
|
||||
});
|
||||
html.querySelectorAll(".vanity-twist").forEach(btn => {
|
||||
btn.addEventListener("click", () => {
|
||||
if (!game.user.isGM) return ui.notifications.warn("Only the GM may twist the knife.");
|
||||
|
||||