Update published system to v0.9.4

Brings the public system repo up to date with the current build:
- Dice So Nice: the VANITY peacock dice are now forced for every player, with
  a roll hook stamping the colourset on every die (red Bane dice preserved).
- Character sheet: conditions strip moved to the foot, rebuilt as an even grid
  with larger (34px) icons.
- Twist the Knife no longer spends the banked Bane — the tab only clears
  through humility, a rest, or the Reckoning.
- Equipment: +50 items (weapons, armour & shields, gear, consumables).
- New compendium: VANITY · Treasures & Relics — 100 magic items, and the
  Forge's richer hoards now surface them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 08:50:37 +01:00
co-authored by Claude Opus 4.8
parent 8543f10fc9
commit 9e0363dac2
34 changed files with 415 additions and 104 deletions
+4
View File
@@ -0,0 +1,4 @@
<!doctype html><html><head><meta charset="utf-8"><style>
html,body{margin:0;padding:0;background:#03100d;width:1920px;height:1080px;overflow:hidden}
img{display:block;width:1920px;height:1080px}
</style></head><body><img src="vanity-splash.svg"></body></html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 258 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 MiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.408746 16e987000 Delete type=3 #1
2026/07/14-09:38:57.409498 170237000 Level-0 table #5: started
2026/07/14-09:38:57.409719 170237000 Level-0 table #5: 1282 bytes OK
2026/07/14-09:38:57.409845 170237000 Delete type=0 #3
2026/07/14-09:38:57.409925 170237000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!jf5ovEG8ufV1oiUf' @ 0 : 0; will stop at (end)
2026/07/14-22:09:36.994122 171e13000 Delete type=3 #1
2026/07/14-22:09:36.994965 1726ab000 Level-0 table #5: started
2026/07/14-22:09:36.995304 1726ab000 Level-0 table #5: 2618 bytes OK
2026/07/14-22:09:36.995487 1726ab000 Delete type=0 #3
2026/07/14-22:09:36.995615 1726ab000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.438743 16e987000 Delete type=3 #1
2026/07/14-09:38:57.439229 170237000 Level-0 table #5: started
2026/07/14-09:38:57.439444 170237000 Level-0 table #5: 5226 bytes OK
2026/07/14-09:38:57.439529 170237000 Delete type=0 #3
2026/07/14-09:38:57.439587 170237000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.067644 171e13000 Delete type=3 #1
2026/07/14-22:09:37.068205 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.068437 1726ab000 Level-0 table #5: 5226 bytes OK
2026/07/14-22:09:37.068519 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.068571 1726ab000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.429069 16e987000 Delete type=3 #1
2026/07/14-09:38:57.429648 170237000 Level-0 table #5: started
2026/07/14-09:38:57.429879 170237000 Level-0 table #5: 5417 bytes OK
2026/07/14-09:38:57.429999 170237000 Delete type=0 #3
2026/07/14-09:38:57.430077 170237000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.056786 171e13000 Delete type=3 #1
2026/07/14-22:09:37.057417 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.057682 1726ab000 Level-0 table #5: 5417 bytes OK
2026/07/14-22:09:37.057805 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.057901 1726ab000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.413966 16e987000 Delete type=3 #1
2026/07/14-09:38:57.414551 170237000 Level-0 table #5: started
2026/07/14-09:38:57.414799 170237000 Level-0 table #5: 3221 bytes OK
2026/07/14-09:38:57.414887 170237000 Delete type=0 #3
2026/07/14-09:38:57.414944 170237000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.005326 171e13000 Delete type=3 #1
2026/07/14-22:09:37.005985 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.006294 1726ab000 Level-0 table #5: 9529 bytes OK
2026/07/14-22:09:37.006403 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.006497 1726ab000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.460195 16e987000 Delete type=3 #1
2026/07/14-09:38:57.460906 170237000 Level-0 table #5: started
2026/07/14-09:38:57.461170 170237000 Level-0 table #5: 10149 bytes OK
2026/07/14-09:38:57.461298 170237000 Delete type=0 #3
2026/07/14-09:38:57.461375 170237000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.085141 171e13000 Delete type=3 #1
2026/07/14-22:09:37.085689 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.085988 1726ab000 Level-0 table #5: 10149 bytes OK
2026/07/14-22:09:37.086085 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.086167 1726ab000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.444631 16e987000 Delete type=3 #1
2026/07/14-09:38:57.448892 170237000 Level-0 table #5: started
2026/07/14-09:38:57.449570 170237000 Level-0 table #5: 23539 bytes OK
2026/07/14-09:38:57.449745 170237000 Delete type=0 #3
2026/07/14-09:38:57.449971 170237000 Manual compaction at level-0 from '!actors!WDcRWtpmw3YGHDSi' @ 72057594037927935 : 1 .. '!actors.items!z6lMSAkEENJFTvIP.wwJcCyyHbp1gt9iq' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.074451 171e13000 Delete type=3 #1
2026/07/14-22:09:37.075086 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.075456 1726ab000 Level-0 table #5: 23539 bytes OK
2026/07/14-22:09:37.075539 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.075600 1726ab000 Manual compaction at level-0 from '!actors!WDcRWtpmw3YGHDSi' @ 72057594037927935 : 1 .. '!actors.items!z6lMSAkEENJFTvIP.wwJcCyyHbp1gt9iq' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.454787 16e987000 Delete type=3 #1
2026/07/14-09:38:57.455790 170237000 Level-0 table #5: started
2026/07/14-09:38:57.456601 170237000 Level-0 table #5: 63789 bytes OK
2026/07/14-09:38:57.456761 170237000 Delete type=0 #3
2026/07/14-09:38:57.456863 170237000 Manual compaction at level-0 from '!journal!9PRuE5Q81BnO22fH' @ 72057594037927935 : 1 .. '!journal.pages!9PRuE5Q81BnO22fH.qCt9jGDZePPN5S6G' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.079746 171e13000 Delete type=3 #1
2026/07/14-22:09:37.080784 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.081591 1726ab000 Level-0 table #5: 63868 bytes OK
2026/07/14-22:09:37.081700 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.081862 1726ab000 Manual compaction at level-0 from '!journal!9PRuE5Q81BnO22fH' @ 72057594037927935 : 1 .. '!journal.pages!9PRuE5Q81BnO22fH.qCt9jGDZePPN5S6G' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.422502 16e987000 Delete type=3 #1
2026/07/14-09:38:57.423123 170237000 Level-0 table #5: started
2026/07/14-09:38:57.423395 170237000 Level-0 table #5: 11569 bytes OK
2026/07/14-09:38:57.423488 170237000 Delete type=0 #3
2026/07/14-09:38:57.423549 170237000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.050594 171e13000 Delete type=3 #1
2026/07/14-22:09:37.051265 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.051557 1726ab000 Level-0 table #5: 11569 bytes OK
2026/07/14-22:09:37.051647 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.051708 1726ab000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.465680 16e987000 Delete type=3 #1
2026/07/14-09:38:57.466297 170237000 Level-0 table #5: started
2026/07/14-09:38:57.466636 170237000 Level-0 table #5: 12927 bytes OK
2026/07/14-09:38:57.466760 170237000 Delete type=0 #3
2026/07/14-09:38:57.466830 170237000 Manual compaction at level-0 from '!tables!JEOcxCxjK6S6pVEZ' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.091924 171e13000 Delete type=3 #1
2026/07/14-22:09:37.092662 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.093008 1726ab000 Level-0 table #5: 12927 bytes OK
2026/07/14-22:09:37.093109 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.093180 1726ab000 Manual compaction at level-0 from '!tables!JEOcxCxjK6S6pVEZ' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
View File
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2026/07/14-22:09:37.041630 171e13000 Delete type=3 #1
2026/07/14-22:09:37.042589 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.043013 1726ab000 Level-0 table #5: 27619 bytes OK
2026/07/14-22:09:37.043119 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.043193 1726ab000 Manual compaction at level-0 from '!items!0NDwQbhQQNeCCEco' @ 72057594037927935 : 1 .. '!items!yxds1AZFNXBpERdk' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.432838 16e987000 Delete type=3 #1
2026/07/14-09:38:57.433254 170237000 Level-0 table #5: started
2026/07/14-09:38:57.433533 170237000 Level-0 table #5: 1461 bytes OK
2026/07/14-09:38:57.433620 170237000 Delete type=0 #3
2026/07/14-09:38:57.433675 170237000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
2026/07/14-22:09:37.060948 171e13000 Delete type=3 #1
2026/07/14-22:09:37.061687 1726ab000 Level-0 table #5: started
2026/07/14-22:09:37.061961 1726ab000 Level-0 table #5: 1461 bytes OK
2026/07/14-22:09:37.062129 1726ab000 Delete type=0 #3
2026/07/14-22:09:37.062277 1726ab000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/14-09:38:57.402153 16e987000 Delete type=3 #1
2026/07/14-09:38:57.404481 170237000 Level-0 table #5: started
2026/07/14-09:38:57.404888 170237000 Level-0 table #5: 4010 bytes OK
2026/07/14-09:38:57.405081 170237000 Delete type=0 #3
2026/07/14-09:38:57.405192 170237000 Manual compaction at level-0 from '!items!2XKXBWRByluX4WDG' @ 72057594037927935 : 1 .. '!items!qAvk2AcDGNNy1Ce9' @ 0 : 0; will stop at (end)
2026/07/14-22:09:36.985504 171e13000 Delete type=3 #1
2026/07/14-22:09:36.988878 1726ab000 Level-0 table #5: started
2026/07/14-22:09:36.989190 1726ab000 Level-0 table #5: 7415 bytes OK
2026/07/14-22:09:36.989351 1726ab000 Delete type=0 #3
2026/07/14-22:09:36.989442 1726ab000 Manual compaction at level-0 from '!items!0d6FZCtl78jEPkof' @ 72057594037927935 : 1 .. '!items!v3IghflF7cI2FzbU' @ 0 : 0; will stop at (end)
Binary file not shown.
+32 -20
View File
@@ -904,40 +904,52 @@
/* ============================================================
Conditions — grouped icon chips + a details panel at the foot
============================================================ */
.vanity-sheet .v-conditions {
.vanity-sheet .v-cond-foot {
flex: none;
display: flex;
flex-wrap: wrap;
gap: 4px 10px;
padding: 4px 2px 7px;
margin-bottom: 6px;
border-bottom: 1.5px dotted var(--v-line);
margin: 12px 2px 0;
padding-top: 8px;
border-top: 1.5px dotted var(--v-line);
}
.vanity-sheet .v-cond-title {
font-family: "Cinzel", Georgia, serif;
font-size: 9px; letter-spacing: 0.14em; text-transform: uppercase;
color: var(--v-ink2); margin-bottom: 7px;
}
/* An even grid: every condition is an equal-sized tile, aligned in tidy rows. */
.vanity-sheet .v-conditions {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(74px, 1fr));
gap: 6px;
}
.vanity-sheet .cond-group { display: flex; flex-wrap: wrap; gap: 3px; padding-right: 8px; }
.vanity-sheet .cond-group + .cond-group { border-left: 1px solid var(--v-line); padding-left: 10px; }
.vanity-sheet .cond-badge {
display: inline-flex;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
gap: 4px;
padding: 1px 8px 1px 2px;
border: 1.3px solid transparent;
border-radius: 12px;
padding: 7px 4px 6px;
border: 1.4px solid var(--v-line);
border-radius: 8px;
background: var(--v-paper2);
cursor: pointer;
opacity: 0.62;
transition: opacity 0.12s, background 0.12s, box-shadow 0.12s;
opacity: 0.6;
text-align: center;
transition: opacity 0.12s, background 0.12s, box-shadow 0.12s, border-color 0.12s;
}
.vanity-sheet .cond-badge img {
width: 20px; height: 20px; flex: none;
filter: grayscale(0.55);
width: 34px; height: 34px; flex: none;
filter: grayscale(0.6);
transition: filter 0.12s;
}
.vanity-sheet .cond-badge .cond-name { font-size: 10.5px; color: var(--v-ink2); white-space: nowrap; }
.vanity-sheet .cond-badge:hover { opacity: 0.9; background: rgba(176,135,47,0.08); }
.vanity-sheet .cond-badge .cond-name {
font-size: 10.5px; line-height: 1.15; color: var(--v-ink2);
}
.vanity-sheet .cond-badge:hover { opacity: 0.92; background: rgba(176,135,47,0.10); }
.vanity-sheet .cond-badge.on {
opacity: 1;
background: #fff;
border-color: var(--cc, var(--v-gold));
box-shadow: 0 0 5px var(--ccg, rgba(176,135,47,.45));
box-shadow: 0 0 6px var(--ccg, rgba(176,135,47,.45));
}
.vanity-sheet .cond-badge.on img { filter: none; }
.vanity-sheet .cond-badge.on .cond-name { color: var(--v-ink); font-weight: 600; }
+8 -1
View File
@@ -2,7 +2,7 @@
"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, five ready-to-play characters and the one-shot adventure The Vain Crown.",
"version": "0.9.0",
"version": "0.9.4",
"compatibility": {
"minimum": 13,
"verified": "14.364"
@@ -55,6 +55,13 @@
"type": "Item",
"system": "vanity"
},
{
"name": "treasure",
"label": "VANITY · Treasures & Relics",
"path": "packs/treasure",
"type": "Item",
"system": "vanity"
},
{
"name": "spells",
"label": "VANITY · Spells & Nature Tricks",
+14 -14
View File
@@ -18,19 +18,6 @@
{{else if sys.rattled}}<div class="v-banner rattled">RATTLED — −1 die on everything until you recover</div>{{/if}}
{{#if sys.reckoning}}<div class="v-banner reckoning">SIX BANES — THE RECKONING IS DUE</div>{{/if}}
{{!-- Conditions strip — grouped icon chips, always visible, click to toggle & read below --}}
<div class="v-conditions">
{{#each conditionGroups}}
<div class="cond-group {{group}}">
{{#each items}}
<span class="cond-badge {{../group}} {{#if active}}on{{/if}}" data-cond="{{id}}" title="{{name}} — {{short}} · ends: {{ends}}">
<img src="{{img}}" alt=""><span class="cond-name">{{name}}</span>
</span>
{{/each}}
</div>
{{/each}}
</div>
<nav class="sheet-tabs tabs" data-group="primary">
<a class="item" data-tab="main">Character</a>
<a class="item" data-tab="combat">Combat &amp; Gear</a>
@@ -307,7 +294,20 @@
</section>
{{!-- Conditions in play — pinned to the foot of the sheet, across every tab --}}
{{!-- Conditions — an even grid at the foot, across every tab; click to toggle & read below --}}
<div class="v-cond-foot">
<div class="v-cond-title">Conditions · click to set &amp; read</div>
<div class="v-conditions">
{{#each conditionGroups}}
{{#each items}}
<span class="cond-badge {{../group}} {{#if active}}on{{/if}}" data-cond="{{id}}" title="{{name}} — {{short}} · ends: {{ends}}">
<img src="{{img}}" alt=""><span class="cond-name">{{name}}</span>
</span>
{{/each}}
{{/each}}
</div>
</div>
{{#if focusCond}}
<div class="v-cond-detail {{focusCond.group}}">
<img src="{{focusCond.img}}" alt="">
+44 -14
View File
@@ -353,10 +353,18 @@ function renderRollCard({ label, sublabel, results, threshold, pushed, context,
* Roll a VANITY pool and post the chat card.
* options: { threshold, context ("spell"|"attack"|...), spellUuid, sublabel }
*/
/** Stamp the VANITY dice appearance on a roll so it shows the peacock dice regardless
* of the player's own Dice So Nice settings (belt-and-suspenders with the roll hook). */
function lockVanityDice(roll, colorset = "vanity") {
for (const die of roll.dice) die.options.appearance = { system: "vanity", colorset };
return roll;
}
async function rollPool(actor, pool, label, options = {}) {
pool = Math.max(1, Math.floor(pool));
const roll = new Roll(`${pool}d6`);
await roll.evaluate();
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.
@@ -463,6 +471,7 @@ async function handlePush(message) {
if (rerollCount > 0) {
roll = new Roll(`${rerollCount}d6`);
await roll.evaluate();
lockVanityDice(roll);
newResults = keep.concat(roll.dice[0].results.map(r => r.result));
if (game.dice3d) await game.dice3d.showForRoll(roll, game.user, true);
}
@@ -947,7 +956,9 @@ class VanityItemSheet extends foundry.appv1.sheets.ItemSheet {
/* -------------------------------------------- */
Hooks.once("diceSoNiceReady", dice3d => {
dice3d.addSystem({ id: "vanity", name: "VANITY — Peacock & Gold" }, "preferred");
// "force" locks the VANITY dice system (the custom plume/crown/Bane faces) on for
// every player, overriding whatever dice they picked personally.
dice3d.addSystem({ id: "vanity", name: "VANITY — Peacock & Gold" }, "force");
dice3d.addColorset({
name: "vanity",
@@ -990,6 +1001,18 @@ Hooks.once("diceSoNiceReady", dice3d => {
}, "default");
});
// Lock the VANITY colours onto every die of every roll, whatever the player's own
// Dice So Nice settings. Fires for pool rolls, Pushes, initiative and manual rolls
// alike. Dice already flagged as the blood-red Bane set are left untouched.
Hooks.on("diceSoNiceRollStart", (messageId, context) => {
const roll = context?.roll;
if (!roll?.dice) return;
for (const die of roll.dice) {
if (die.options?.appearance?.colorset === "vanity-bane") continue;
die.options.appearance = { system: "vanity", colorset: "vanity" };
}
});
/* -------------------------------------------- */
/* Twist the Knife — the GM spends the tab */
/* -------------------------------------------- */
@@ -1008,7 +1031,7 @@ async function getVanityTable(name) {
/** Roll dice on the playing surface in the blood-red Bane set, without ever blocking. */
async function showBaneDice(roll) {
for (const d of roll.dice) d.options.appearance = { colorset: "vanity-bane" };
for (const d of roll.dice) d.options.appearance = { system: "vanity", colorset: "vanity-bane" };
if (game.dice3d && !game.settings.get("dice-so-nice", "animateRollTable")) {
await Promise.race([
game.dice3d.showForRoll(roll, game.user, true).catch(() => {}),
@@ -1050,8 +1073,9 @@ async function reckoningNow({ actorUuid = "" } = {}) {
/**
* GM-only: roll the d66 on Twist the Knife with the blood-red Bane dice.
* A paid twist (actorUuid set) deducts the row's Bane cost from the tab.
* A free twist (lone unbanked 1) burns the button on its source card.
* The twist is the Bane's BITE, not its price — it never reduces the tab. A banked
* Bane stays put (building toward the Reckoning) and only clears through humility,
* a rest, or the Reckoning at six. A free twist (lone unbanked 1) burns its button.
*/
async function twistTheKnife({ actorUuid = "", free = false, sourceMessage = null } = {}) {
if (!game.user.isGM) return ui.notifications.warn("Only the GM may twist the knife.");
@@ -1067,15 +1091,14 @@ async function twistTheKnife({ actorUuid = "", free = false, sourceMessage = nul
await table.toMessage(results, { roll });
const draw = { roll, results };
// The twist bites, but the tab holds — a banked Bane is never spent by twisting.
if (!free && actorUuid) {
const actor = await fromUuid(actorUuid);
if (actor?.type === "character") {
const cost = draw.roll.total >= 61 ? 2 : 1;
const karma = Math.max(0, (actor.system.karma ?? 0) - cost);
await actor.update({ "system.karma": karma });
const karma = actor.system.karma ?? 0;
await ChatMessage.create({
speaker: { alias: "The Tab" },
content: `<div class="vanity-roll vanity-tab-note"><p><b>${cost} Bane${cost > 1 ? "s" : ""} spent</b> against ${actor.name} — the tab now reads <b>${karma}</b> of 6.</p></div>`
content: `<div class="vanity-roll vanity-tab-note"><p>The knife twists — but the Bane stays banked. ${actor.name}'s tab still reads <b>${karma}</b> of 6.</p></div>`
});
}
}
@@ -1501,16 +1524,16 @@ const FORGE_HOOKS = [
];
const HOARDS = {
pocket: { label: "Pocket", coin: () => `${sum(rollDice(2, 6))} sp`, draws: 0, consum: 0.3, trinkets: [0, 1], hook: 0.15 },
cache: { label: "Cache", coin: () => `${sum(rollDice(1, 6))} gp, ${sum(rollDice(2, 6))} sp`, draws: 1, consum: 0.8, trinkets: [0, 1], hook: 0.3 },
chest: { label: "Chest", coin: () => `${sum(rollDice(4, 6))} gp`, draws: 2, consum: 1, trinkets: [1, 1], hook: 0.5 },
vault: { label: "Vault", coin: () => `${sum(rollDice(2, 6)) * 10} gp`, draws: 3, consum: 1, trinkets: [1, 2], hook: 1 },
kingly: { label: "Kingly", coin: () => `${sum(rollDice(6, 6)) * 10} gp`, draws: 5, consum: 1, trinkets: [2, 2], hook: 1, centerpiece: true }
pocket: { label: "Pocket", coin: () => `${sum(rollDice(2, 6))} sp`, draws: 0, consum: 0.3, trinkets: [0, 1], hook: 0.15, relics: 0 },
cache: { label: "Cache", coin: () => `${sum(rollDice(1, 6))} gp, ${sum(rollDice(2, 6))} sp`, draws: 1, consum: 0.8, trinkets: [0, 1], hook: 0.3, relics: 0.2 },
chest: { label: "Chest", coin: () => `${sum(rollDice(4, 6))} gp`, draws: 2, consum: 1, trinkets: [1, 1], hook: 0.5, relics: 0.5 },
vault: { label: "Vault", coin: () => `${sum(rollDice(2, 6)) * 10} gp`, draws: 3, consum: 1, trinkets: [1, 2], hook: 1, relics: 1 },
kingly: { label: "Kingly", coin: () => `${sum(rollDice(6, 6)) * 10} gp`, draws: 5, consum: 1, trinkets: [2, 2], hook: 1, centerpiece: true, relics: 2 }
};
async function forgeHoard({ size = "chest" } = {}) {
const tier = HOARDS[size] ?? HOARDS.chest;
const [weapons, armour, gear] = await Promise.all(["weapons", "armour", "gear"].map(packDocs));
const [weapons, armour, gear, treasure] = await Promise.all(["weapons", "armour", "gear", "treasure"].map(packDocs));
const consumables = gear.filter(g => g.system.consumable);
const mundane = [...weapons, ...armour, ...gear.filter(g => !g.system.consumable)];
@@ -1522,6 +1545,13 @@ async function forgeHoard({ size = "chest" } = {}) {
for (let i = 0; i < tier.draws; i++) found.push(rnd(mundane));
if (found.length) lines.push(`<b>Goods:</b> ${found.map(d => `@UUID[${d.uuid}]{${d.name}}`).join(" · ")}`);
// A magic relic or two in the richer hoards (a fraction is a chance, an integer a count).
const nRelics = (tier.relics ?? 0) >= 1 ? tier.relics : (Math.random() < (tier.relics ?? 0) ? 1 : 0);
if (nRelics && treasure.length) {
const relics = pickN(treasure, Math.min(nRelics, treasure.length));
lines.push(`<b>Relic${relics.length > 1 ? "s" : ""}:</b> ${relics.map(d => `@UUID[${d.uuid}]{${d.name}}`).join(" · ")}`);
}
const nTr = between(tier.trinkets);
if (nTr) lines.push(`<b>Vanities:</b> ${pickN(FORGE_TRINKETS, nTr).map(([t, v]) => `${t} <i>(${v} gp)</i>`).join(" · ")}`);