Add the Bond: 10 animal companions, with sheet support and rules

Companions were a single line on the Ranger's edge; now they are a real
subsystem, rollable or pickable, with art, tokens and a place on the sheet.

- New compendium "companions" (VANITY · Animal Companions): 10 beasts as full
  NPC actors with painted core artwork, droppable as tokens — wolf, hawk, owl,
  raven, hound, lynx, serpent, stag, great bat, ram. Each keeps the printed
  baseline (Grit 3, attacks at 3 dice) and differs in dodge (2-4d), senses and
  one distinctive trick, plus which companion action it favours.
- New d10 roll table, "The Bond — Animal Companions".
- Ranger sheet: the companion block is now a card — art, name, kind, attack,
  dodge, Grit (with a DOWNED flag), its trick, and one-click Help / Harry /
  Guard (each posting what it does to chat) plus Attack and Dodge rolls.
  "Roll the Bond" rolls the d10 and binds the beast straight onto the sheet.
- template.json: companion gains kind, img, dodge, trick.
- Rules updated in both places: §9a in the Draft 13 source (so the in-world
  journal carries it) and a new Bond section in the player rulebook, which is
  regenerated to HTML + PDF (39pp).
- Version -> 0.9.6; README/FOUNDRY/system description updated (13 compendia).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 13:57:14 +01:00
co-authored by Claude Opus 4.8
parent b8ab9b4d97
commit 87ae980435
30 changed files with 316 additions and 109 deletions
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.695783 173683000 Delete type=3 #1
2026/07/15-11:47:01.696366 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.696541 173f1b000 Level-0 table #5: 2618 bytes OK
2026/07/15-11:47:01.696627 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.696697 173f1b000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end)
2026/07/15-13:54:31.980954 173337000 Delete type=3 #1
2026/07/15-13:54:31.982902 173bcf000 Level-0 table #5: started
2026/07/15-13:54:31.983101 173bcf000 Level-0 table #5: 2618 bytes OK
2026/07/15-13:54:31.983209 173bcf000 Delete type=0 #3
2026/07/15-13:54:31.983289 173bcf000 Manual compaction at level-0 from '!items!GOAakTqMDZSCDOdE' @ 72057594037927935 : 1 .. '!items!vHyUhdri4IKpq9CW' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.788244 173683000 Delete type=3 #1
2026/07/15-11:47:01.792649 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.792913 173f1b000 Level-0 table #5: 5226 bytes OK
2026/07/15-11:47:01.793011 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.793107 173f1b000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.036496 173337000 Delete type=3 #1
2026/07/15-13:54:32.036915 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.037137 173bcf000 Level-0 table #5: 5226 bytes OK
2026/07/15-13:54:32.037231 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.037330 173bcf000 Manual compaction at level-0 from '!actors!351sA95uALULY3Kv' @ 72057594037927935 : 1 .. '!actors!rPfEYHmSoprnQdrY' @ 0 : 0; will stop at (end)
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
MANIFEST-000002
View File
+5
View File
@@ -0,0 +1,5 @@
2026/07/15-13:54:32.041086 173337000 Delete type=3 #1
2026/07/15-13:54:32.041857 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.042052 173bcf000 Level-0 table #5: 4974 bytes OK
2026/07/15-13:54:32.042132 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.042191 173bcf000 Manual compaction at level-0 from '!actors!1FGl7v1mODKvN9fy' @ 72057594037927935 : 1 .. '!actors!yEfKBqSa4GmIgOpx' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.770669 173683000 Delete type=3 #1
2026/07/15-11:47:01.771135 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.771347 173f1b000 Level-0 table #5: 5417 bytes OK
2026/07/15-11:47:01.771446 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.771504 173f1b000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.027062 173337000 Delete type=3 #1
2026/07/15-13:54:32.028202 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.028447 173bcf000 Level-0 table #5: 5417 bytes OK
2026/07/15-13:54:32.028559 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.028646 173bcf000 Manual compaction at level-0 from '!items!21hRQJtWGk5fDQlo' @ 72057594037927935 : 1 .. '!items!zMphj752klzDFU3Q' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.714562 173683000 Delete type=3 #1
2026/07/15-11:47:01.715170 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.715419 173f1b000 Level-0 table #5: 9529 bytes OK
2026/07/15-11:47:01.715518 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.715585 173f1b000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end)
2026/07/15-13:54:31.993791 173337000 Delete type=3 #1
2026/07/15-13:54:31.994306 173bcf000 Level-0 table #5: started
2026/07/15-13:54:31.994579 173bcf000 Level-0 table #5: 9529 bytes OK
2026/07/15-13:54:31.994675 173bcf000 Delete type=0 #3
2026/07/15-13:54:31.994726 173bcf000 Manual compaction at level-0 from '!items!27LcrYsIEEF3KYHA' @ 72057594037927935 : 1 .. '!items!yo48EzCVEwNdpkXF' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.833917 173683000 Delete type=3 #1
2026/07/15-11:47:01.834447 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.834747 173f1b000 Level-0 table #5: 10149 bytes OK
2026/07/15-11:47:01.834842 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.834916 173f1b000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.071464 173337000 Delete type=3 #1
2026/07/15-13:54:32.071868 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.072129 173bcf000 Level-0 table #5: 10149 bytes OK
2026/07/15-13:54:32.072216 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.072285 173bcf000 Manual compaction at level-0 from '!journal!JJEgDJvtWBnFbyZA' @ 72057594037927935 : 1 .. '!journal.pages!kBclT2QiwEQmOsX1.HSVD8c7BenQjb45v' @ 0 : 0; will stop at (end)
View File
+1 -1
View File
@@ -1 +1 @@
MANIFEST-000006
MANIFEST-000002
+5 -3
View File
@@ -1,3 +1,5 @@
2026/07/15-11:47:22.058812 172c77000 Recovering log #4
2026/07/15-11:47:22.059752 172c77000 Delete type=0 #4
2026/07/15-11:47:22.059797 172c77000 Delete type=3 #2
2026/07/15-13:54:32.058937 173337000 Delete type=3 #1
2026/07/15-13:54:32.060571 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.061856 173bcf000 Level-0 table #5: 120604 bytes OK
2026/07/15-13:54:32.061959 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.062074 173bcf000 Manual compaction at level-0 from '!actors!3Kjqu9ibyf8TjSrk' @ 72057594037927935 : 1 .. '!actors.items!zR0OZzFsVQLTSlBM.uc6AKpWyZeREAX2M' @ 0 : 0; will stop at (end)
-5
View File
@@ -1,5 +0,0 @@
2026/07/15-11:47:01.820316 173683000 Delete type=3 #1
2026/07/15-11:47:01.821872 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.824245 173f1b000 Level-0 table #5: 120604 bytes OK
2026/07/15-11:47:01.824415 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.824651 173f1b000 Manual compaction at level-0 from '!actors!3Kjqu9ibyf8TjSrk' @ 72057594037927935 : 1 .. '!actors.items!zR0OZzFsVQLTSlBM.uc6AKpWyZeREAX2M' @ 0 : 0; will stop at (end)
Binary file not shown.
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.829347 173683000 Delete type=3 #1
2026/07/15-11:47:01.830098 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.830873 173f1b000 Level-0 table #5: 63868 bytes OK
2026/07/15-11:47:01.831105 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.831206 173f1b000 Manual compaction at level-0 from '!journal!9PRuE5Q81BnO22fH' @ 72057594037927935 : 1 .. '!journal.pages!9PRuE5Q81BnO22fH.qCt9jGDZePPN5S6G' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.066661 173337000 Delete type=3 #1
2026/07/15-13:54:32.067500 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.068353 173bcf000 Level-0 table #5: 63868 bytes OK
2026/07/15-13:54:32.068488 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.068594 173bcf000 Manual compaction at level-0 from '!journal!9PRuE5Q81BnO22fH' @ 72057594037927935 : 1 .. '!journal.pages!9PRuE5Q81BnO22fH.qCt9jGDZePPN5S6G' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.760698 173683000 Delete type=3 #1
2026/07/15-11:47:01.761279 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.761565 173f1b000 Level-0 table #5: 11569 bytes OK
2026/07/15-11:47:01.761661 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.761720 173f1b000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.019121 173337000 Delete type=3 #1
2026/07/15-13:54:32.019649 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.019916 173bcf000 Level-0 table #5: 11569 bytes OK
2026/07/15-13:54:32.020010 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.020081 173bcf000 Manual compaction at level-0 from '!items!13KOIsQOsgYqdVEX' @ 72057594037927935 : 1 .. '!items!zywiqamhRlcCgLJY' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.839105 173683000 Delete type=3 #1
2026/07/15-11:47:01.839563 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.839866 173f1b000 Level-0 table #5: 12927 bytes OK
2026/07/15-11:47:01.839954 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.840012 173f1b000 Manual compaction at level-0 from '!tables!JEOcxCxjK6S6pVEZ' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.076783 173337000 Delete type=3 #1
2026/07/15-13:54:32.077935 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.078454 173bcf000 Level-0 table #5: 15588 bytes OK
2026/07/15-13:54:32.078562 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.078643 173bcf000 Manual compaction at level-0 from '!tables!JEOcxCxjK6S6pVEZ' @ 72057594037927935 : 1 .. '!tables.results!rurx5ZPcPObdbNDD.bWBU56ZArkeat9DO' @ 0 : 0; will stop at (end)
Binary file not shown.
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.747896 173683000 Delete type=3 #1
2026/07/15-11:47:01.748609 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.748977 173f1b000 Level-0 table #5: 27619 bytes OK
2026/07/15-11:47:01.749069 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.749125 173f1b000 Manual compaction at level-0 from '!items!0NDwQbhQQNeCCEco' @ 72057594037927935 : 1 .. '!items!yxds1AZFNXBpERdk' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.011854 173337000 Delete type=3 #1
2026/07/15-13:54:32.012716 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.013114 173bcf000 Level-0 table #5: 27619 bytes OK
2026/07/15-13:54:32.013198 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.013256 173bcf000 Manual compaction at level-0 from '!items!0NDwQbhQQNeCCEco' @ 72057594037927935 : 1 .. '!items!yxds1AZFNXBpERdk' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.775377 173683000 Delete type=3 #1
2026/07/15-11:47:01.775829 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.776000 173f1b000 Level-0 table #5: 1461 bytes OK
2026/07/15-11:47:01.776104 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.776160 173f1b000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
2026/07/15-13:54:32.031270 173337000 Delete type=3 #1
2026/07/15-13:54:32.031615 173bcf000 Level-0 table #5: started
2026/07/15-13:54:32.031760 173bcf000 Level-0 table #5: 1461 bytes OK
2026/07/15-13:54:32.031837 173bcf000 Delete type=0 #3
2026/07/15-13:54:32.031883 173bcf000 Manual compaction at level-0 from '!items!7kO3VzfFPt8QRVXT' @ 72057594037927935 : 1 .. '!items!s50CZgFUaS5puSOp' @ 0 : 0; will stop at (end)
+5 -5
View File
@@ -1,5 +1,5 @@
2026/07/15-11:47:01.686516 173683000 Delete type=3 #1
2026/07/15-11:47:01.688737 173f1b000 Level-0 table #5: started
2026/07/15-11:47:01.688968 173f1b000 Level-0 table #5: 7415 bytes OK
2026/07/15-11:47:01.689074 173f1b000 Delete type=0 #3
2026/07/15-11:47:01.689142 173f1b000 Manual compaction at level-0 from '!items!0d6FZCtl78jEPkof' @ 72057594037927935 : 1 .. '!items!v3IghflF7cI2FzbU' @ 0 : 0; will stop at (end)
2026/07/15-13:54:31.973874 173337000 Delete type=3 #1
2026/07/15-13:54:31.975860 173bcf000 Level-0 table #5: started
2026/07/15-13:54:31.976110 173bcf000 Level-0 table #5: 7415 bytes OK
2026/07/15-13:54:31.976203 173bcf000 Delete type=0 #3
2026/07/15-13:54:31.976263 173bcf000 Manual compaction at level-0 from '!items!0d6FZCtl78jEPkof' @ 72057594037927935 : 1 .. '!items!v3IghflF7cI2FzbU' @ 0 : 0; will stop at (end)
+37 -4
View File
@@ -425,10 +425,43 @@
/* ---------------- Companion ---------------- */
.vanity-sheet .companion-grid { display: grid; grid-template-columns: 2fr 1fr 1.4fr auto; gap: 10px; align-items: end; }
.vanity-sheet .companion-grid .wide { grid-column: 1 / -1; }
.vanity-sheet .companion-grid input[type="number"] { width: 44px; text-align: center; }
.vanity-sheet .companion-roll { padding: 5px 12px; }
.vanity-sheet .companion-card { display: flex; gap: 12px; align-items: flex-start; }
.vanity-sheet .cmp-art {
flex: none; width: 92px; height: 92px; border-radius: 6px; overflow: hidden;
border: 1.5px solid var(--v-gold); background: var(--v-paper2);
}
.vanity-sheet .cmp-art img { width: 100%; height: 100%; object-fit: cover; display: block; }
.vanity-sheet .cmp-art-empty {
width: 100%; height: 100%; display: flex; align-items: center; justify-content: center;
color: var(--v-line); font-size: 30px;
}
.vanity-sheet .cmp-body { flex: 1; display: flex; flex-direction: column; gap: 7px; min-width: 0; }
.vanity-sheet .cmp-line { display: flex; gap: 10px; align-items: end; }
.vanity-sheet .cmp-line .grow { flex: 1; min-width: 0; }
.vanity-sheet .companion-card input[type="number"] { width: 42px; text-align: center; }
.vanity-sheet .companion-card .sl { color: var(--v-ink2); padding: 0 2px; }
.vanity-sheet .companion-card .wide { width: 100%; }
.vanity-sheet .cmp-downed {
font-family: "Cinzel", Georgia, serif; font-size: 9px; letter-spacing: .1em;
color: #fff; background: #8a2f2f; border-radius: 8px; padding: 2px 8px; align-self: center;
}
.vanity-sheet .cmp-actions { display: flex; flex-wrap: wrap; gap: 6px; align-items: center; }
.vanity-sheet .cmp-actions .cap { width: 100%; margin-bottom: 1px; }
.vanity-sheet .cmp-act, .vanity-sheet .cmp-roll {
padding: 4px 10px; font-size: 11px; border: 1.3px solid var(--v-line);
border-radius: 12px; background: var(--v-paper2); cursor: pointer; white-space: nowrap;
}
.vanity-sheet .cmp-act b { color: var(--v-teal); font-weight: 600; }
.vanity-sheet .cmp-act:hover, .vanity-sheet .cmp-roll:hover {
border-color: var(--v-gold); background: rgba(176,135,47,.10);
}
.vanity-sheet .cmp-roll { border-color: var(--v-teal); color: var(--v-teal-d); }
.vanity-sheet .cmp-bond {
float: right; margin: -2px 0 0; padding: 2px 9px; font-size: 10px;
border: 1.2px solid var(--v-gold); border-radius: 10px; background: transparent;
color: var(--v-paper); cursor: pointer;
}
.vanity-sheet .cmp-bond:hover { background: rgba(255,255,255,.14); }
/* ---------------- NPC sheet ---------------- */
+9 -2
View File
@@ -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) and the one-shot adventure The Vain Crown.",
"version": "0.9.5",
"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.9.6",
"compatibility": {
"minimum": 13,
"verified": "14.364"
@@ -90,6 +90,13 @@
"type": "Actor",
"system": "vanity"
},
{
"name": "companions",
"label": "VANITY · Animal Companions",
"path": "packs/companions",
"type": "Actor",
"system": "vanity"
},
{
"name": "pregens",
"label": "VANITY · Ready-to-Play Characters",
+100 -27
View File
@@ -1,27 +1,65 @@
{
"Actor": {
"types": ["character", "npc"],
"types": [
"character",
"npc"
],
"character": {
"attributes": {
"brawn": { "value": 2 },
"flair": { "value": 2 },
"wits": { "value": 2 },
"poise": { "value": 2 }
"brawn": {
"value": 2
},
"flair": {
"value": 2
},
"wits": {
"value": 2
},
"poise": {
"value": 2
}
},
"grit": {
"value": 8,
"max": 8
},
"vanity": {
"value": 3
},
"karma": {
"value": 0
},
"glory": {
"value": 0
},
"grit": { "value": 8, "max": 8 },
"vanity": { "value": 3 },
"karma": { "value": 0 },
"glory": { "value": 0 },
"knacks": {
"fight": { "rank": 0 },
"shoot": { "rank": 0 },
"athletics": { "rank": 0 },
"stealth": { "rank": 0 },
"notice": { "rank": 0 },
"lore": { "rank": 0 },
"survive": { "rank": 0 },
"charm": { "rank": 0 },
"command": { "rank": 0 }
"fight": {
"rank": 0
},
"shoot": {
"rank": 0
},
"athletics": {
"rank": 0
},
"stealth": {
"rank": 0
},
"notice": {
"rank": 0
},
"lore": {
"rank": 0
},
"survive": {
"rank": 0
},
"charm": {
"rank": 0
},
"command": {
"rank": 0
}
},
"locked": {
"name": "",
@@ -32,12 +70,23 @@
"class": "",
"concept": "",
"fightingStyle": "",
"coin": { "cp": 0, "sp": 0, "gp": 15 }
"coin": {
"cp": 0,
"sp": 0,
"gp": 15
}
},
"companion": {
"name": "",
"grit": { "value": 3, "max": 3 },
"kind": "",
"img": "",
"grit": {
"value": 3,
"max": 3
},
"pool": 3,
"dodge": 3,
"trick": "",
"notes": ""
},
"notes": "",
@@ -45,18 +94,42 @@
},
"npc": {
"category": "mook",
"attack1": { "name": "Attack", "pool": 3, "note": "" },
"attack2": { "name": "", "pool": 0, "note": "" },
"defence": { "pool": 2, "note": "" },
"grit": { "value": 1, "max": 1 },
"resolve": { "value": 0, "max": 0 },
"attack1": {
"name": "Attack",
"pool": 3,
"note": ""
},
"attack2": {
"name": "",
"pool": 0,
"note": ""
},
"defence": {
"pool": 2,
"note": ""
},
"grit": {
"value": 1,
"max": 1
},
"resolve": {
"value": 0,
"max": 0
},
"nerve": 2,
"trick": "",
"notes": ""
}
},
"Item": {
"types": ["weapon", "armour", "gear", "spell", "edge", "vice"],
"types": [
"weapon",
"armour",
"gear",
"spell",
"edge",
"vice"
],
"weapon": {
"dice": 0,
"hands": 1,
@@ -98,4 +171,4 @@
"description": ""
}
}
}
}
+37 -7
View File
@@ -273,13 +273,43 @@
{{!-- ============ TAB: STORY ============ --}}
<div class="tab" data-tab="story" data-group="primary">
<div class="sect">
<div class="bar">Companion</div>
<div class="body companion-grid">
<label class="f"><span class="cap">Name</span><input type="text" name="system.companion.name" value="{{sys.companion.name}}" placeholder="Ash the hawk…" /></label>
<label class="f"><span class="cap">Pool</span><input type="number" name="system.companion.pool" value="{{sys.companion.pool}}" /></label>
<label class="f"><span class="cap">Grit</span><input type="number" name="system.companion.grit.value" value="{{sys.companion.grit.value}}" /> / <input type="number" name="system.companion.grit.max" value="{{sys.companion.grit.max}}" /></label>
<button type="button" class="companion-roll" title="Roll the companion's pool"><i class="fa-solid fa-paw"></i> Roll</button>
<label class="f wide"><span class="cap">Notes (Help · Harry · Guard — Downed at 0 Grit, not dead)</span><input type="text" name="system.companion.notes" value="{{sys.companion.notes}}" /></label>
<div class="bar">Companion — the Bond
<button type="button" class="cmp-bond" title="Roll d10 on The Bond, or pick from the Animal Companions compendium"><i class="fa-solid fa-dice-d20"></i> Roll the Bond</button>
</div>
<div class="body companion-card">
<div class="cmp-art">
{{#if sys.companion.img}}<img src="{{sys.companion.img}}" alt="{{sys.companion.name}}" />
{{else}}<div class="cmp-art-empty"><i class="fa-solid fa-paw"></i></div>{{/if}}
</div>
<div class="cmp-body">
<div class="cmp-line">
<label class="f grow"><span class="cap">Name</span>
<input type="text" name="system.companion.name" value="{{sys.companion.name}}" placeholder="Ash…" /></label>
<label class="f grow"><span class="cap">Kind</span>
<input type="text" name="system.companion.kind" value="{{sys.companion.kind}}" placeholder="hawk" /></label>
</div>
<div class="cmp-line">
<label class="f"><span class="cap">Attack</span>
<input type="number" name="system.companion.pool" value="{{sys.companion.pool}}" /></label>
<label class="f"><span class="cap">Dodge</span>
<input type="number" name="system.companion.dodge" value="{{sys.companion.dodge}}" /></label>
<label class="f"><span class="cap">Grit</span>
<input type="number" name="system.companion.grit.value" value="{{sys.companion.grit.value}}" />
<span class="sl">/</span>
<input type="number" name="system.companion.grit.max" value="{{sys.companion.grit.max}}" /></label>
{{#if companionDowned}}<span class="cmp-downed">DOWNED — not dead</span>{{/if}}
</div>
<label class="f wide"><span class="cap">Trick</span>
<input type="text" name="system.companion.trick" value="{{sys.companion.trick}}" placeholder="its one distinctive knack…" /></label>
<div class="cmp-actions">
<span class="cap">Each round, one action</span>
<button type="button" class="cmp-act" data-act="help" title="It flanks, flushes or calls the shot: you gain +1 die">Help <b>+1 die</b></button>
<button type="button" class="cmp-act" data-act="harry" title="It worries a foe: −1 die, and you may treat it as flanked">Harry <b>−1 die</b></button>
<button type="button" class="cmp-act" data-act="guard" title="Reaction: it intercepts one hit meant for you">Guard <b>reaction</b></button>
<button type="button" class="cmp-roll" data-roll="attack" title="Roll the companion's attack pool"><i class="fa-solid fa-paw"></i> Attack</button>
<button type="button" class="cmp-roll" data-roll="dodge" title="Roll the companion's dodge"><i class="fa-solid fa-feather"></i> Dodge</button>
</div>
</div>
</div>
</div>
<div class="sect">
+66 -5
View File
@@ -633,6 +633,8 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet {
items: ctx.conditions.filter(c => c.group === g)
}));
ctx.activeConditions = ctx.conditions.filter(c => c.active);
const cmp = this.actor.system.companion;
ctx.companionDowned = !!cmp?.name && (cmp.grit?.value ?? 3) <= 0;
ctx.focusCond = this._focusCond
? ctx.conditions.find(c => c.id === this._focusCond) ?? null
: null;
@@ -881,11 +883,69 @@ class VanityActorSheet extends foundry.appv1.sheets.ActorSheet {
this.render(false);
});
// Companion roll
html.find(".companion-roll").click(() => {
const c = actor.system.companion;
if (!c?.name) return;
promptAndRoll(actor, { title: `${c.name} (companion)`, base: c.pool || 3, baseLabel: `Companion pool ${c.pool || 3}` });
// Companion — attack / dodge
html.find(".cmp-roll").click(ev => {
const c = actor.system.companion ?? {};
const name = c.name || "The companion";
if (ev.currentTarget.dataset.roll === "dodge") {
return promptAndRoll(actor, { title: `${name} — dodges`, base: c.dodge || 3,
baseLabel: `Companion dodge ${c.dodge || 3}` });
}
return promptAndRoll(actor, { title: `${name} — attacks`, base: c.pool || 3,
baseLabel: `Companion pool ${c.pool || 3}`, options: { context: "attack" } });
});
// Companion — Help · Harry · Guard (one action a round)
html.find(".cmp-act").click(async ev => {
const c = actor.system.companion ?? {};
const name = c.name || "The companion";
const kind = c.kind ? ` the ${c.kind}` : "";
const ACTS = {
help: ["Help", "+1 die",
`${name}${kind} flanks, flushes or calls the shot — <b>${actor.name} gains +1 die</b> on their next roll this round.`],
harry: ["Harry", "−1 die",
`${name}${kind} worries the foe — that foe takes <b>−1 die</b>, and ${actor.name} may treat it as <b>flanked</b> for Ambush-style shots.`],
guard: ["Guard", "reaction",
`${name}${kind} throws itself in the way — it <b>intercepts one hit</b> meant for ${actor.name}. That is the companion's reaction this round.`],
};
const [label, tag, text] = ACTS[ev.currentTarget.dataset.act] ?? [];
if (!label) return;
await ChatMessage.create({
speaker: ChatMessage.getSpeaker({ actor }),
content: `<div class="vanity-roll vanity-companion-card">
<header><span class="vanity-roll-label">🐾 ${label} — ${tag}</span>
<span class="vanity-roll-sub">${name}${kind} · the companion's action this round</span></header>
<p>${text}</p>
${c.trick ? `<p class="cmp-trick"><b>Trick:</b> ${c.trick}</p>` : ""}
</div>`
});
});
// Companion — roll the Bond (d10) and bind the beast to the sheet
html.find(".cmp-bond").click(async () => {
const table = await getVanityTable(BOND_TABLE_NAME);
if (!table) return ui.notifications.error(`"${BOND_TABLE_NAME}" not found in world or compendium.`);
const roll = new Roll("1d10");
await roll.evaluate();
lockVanityDice(roll);
if (game.dice3d) await game.dice3d.showForRoll(roll, game.user, true).catch(() => {});
const results = table.getResultsForRoll(roll.total);
await table.toMessage(results, { roll });
// Bind it: pull the matching beast from the companions compendium.
const kind = (results?.[0]?.name ?? "").split("—")[0].trim();
const beast = (await packDocs("companions")).find(d => d.name === `${kind} (companion)`);
if (!beast) return;
await actor.update({
"system.companion.kind": kind.toLowerCase(),
"system.companion.img": beast.img,
"system.companion.pool": beast.system.attack1?.pool ?? 3,
"system.companion.dodge": beast.system.defence?.pool ?? 3,
"system.companion.trick": beast.system.trick ?? "",
"system.companion.grit.value": beast.system.grit?.max ?? 3,
"system.companion.grit.max": beast.system.grit?.max ?? 3,
});
ui.notifications.info(`The Bond: a ${kind.toLowerCase()}. Name it — the table will be shouting that name soon enough.`);
});
}
}
@@ -1019,6 +1079,7 @@ Hooks.on("diceSoNiceRollStart", (messageId, context) => {
const TWIST_TABLE_NAME = "Twist the Knife — spending Banes (d66)";
const RECKONING_TABLE_NAME = "THE RECKONING — the tab reads six (2d6)";
const BOND_TABLE_NAME = "The Bond — Animal Companions (d10)";
async function getVanityTable(name) {
let table = game.tables.getName(name);