Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51bd8e618a |
@@ -182,7 +182,7 @@ The build runs all three guards before it writes anything:
|
||||
```
|
||||
check-rules: OK — 87 rules, 21 files scanned, 3 aliases + 41 constant sets checked, 275 formulas verified
|
||||
check-kits: OK — 25 roles, 10 trades, 218 catalogue items, every kit key resolves, every posting can use what it carries, every loadout distinct
|
||||
check-lang: OK — en.json, 927 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 44 behavioural tests
|
||||
check-lang: OK — en.json, 945 keys, no leaf/branch collisions
|
||||
check-behaviour: OK — 45 behavioural tests
|
||||
```
|
||||
<!-- guards:end -->
|
||||
|
||||
@@ -0,0 +1,159 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="2800" height="2000" viewBox="0 0 2800 2000">
|
||||
<!-- CULLAND HALL — ground floor and grounds, for LAST ADMISSION.
|
||||
Drawn rather than generated: a generator cannot be trusted with a room count,
|
||||
a door position or a label, and all three are load-bearing here.
|
||||
2800x2000 at 100px to the square = 28 x 20 squares of 5ft. -->
|
||||
<defs>
|
||||
<style>
|
||||
.rm { fill:#16215f; stroke:#6674d8; stroke-width:6; }
|
||||
.rm2 { fill:#101a6e; stroke:#6674d8; stroke-width:6; }
|
||||
.out { fill:#0f1750; stroke:#4d5ac0; stroke-width:4; }
|
||||
.wall { stroke:#8f9ae6; stroke-width:10; stroke-linecap:square; }
|
||||
.door { stroke:#f2ea72; stroke-width:11; stroke-linecap:round; }
|
||||
.lbl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:34px; fill:#f2ea72; letter-spacing:2px; }
|
||||
.sub { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif;
|
||||
font-size:24px; fill:#a8b2e3; letter-spacing:1px; }
|
||||
.num { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:44px; fill:#0c125b; }
|
||||
.ttl { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-weight:700;
|
||||
font-size:54px; fill:#ecefee; letter-spacing:5px; }
|
||||
.key { font-family:'Helvetica Neue',Helvetica,Arial,sans-serif; font-size:26px; fill:#ecefee; }
|
||||
.grid { stroke:#233073; stroke-width:1; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect width="2800" height="2000" fill="#0c125b"/>
|
||||
|
||||
<!-- one-square grid, so the Foundry grid lands on the drawn one -->
|
||||
<g class="grid">
|
||||
<path d="M100 0V2000M200 0V2000M300 0V2000M400 0V2000M500 0V2000M600 0V2000M700 0V2000M800 0V2000M900 0V2000M1000 0V2000M1100 0V2000M1200 0V2000M1300 0V2000M1400 0V2000M1500 0V2000M1600 0V2000M1700 0V2000M1800 0V2000M1900 0V2000M2000 0V2000M2100 0V2000M2200 0V2000M2300 0V2000M2400 0V2000M2500 0V2000M2600 0V2000M2700 0V2000"/>
|
||||
<path d="M0 100H2800M0 200H2800M0 300H2800M0 400H2800M0 500H2800M0 600H2800M0 700H2800M0 800H2800M0 900H2800M0 1000H2800M0 1100H2800M0 1200H2800M0 1300H2800M0 1400H2800M0 1500H2800M0 1600H2800M0 1700H2800M0 1800H2800M0 1900H2800"/>
|
||||
</g>
|
||||
|
||||
<text x="120" y="120" class="ttl">CULLAND HALL</text>
|
||||
<text x="120" y="160" class="sub">GROUND FLOOR · THE CULLAND HAUNTING · LAST ADMISSION 22:00</text>
|
||||
|
||||
<!-- ================= the house ================= -->
|
||||
<rect x="400" y="300" width="2000" height="1200" class="rm2"/>
|
||||
|
||||
<!-- 5 · Long Gallery -->
|
||||
<rect x="700" y="400" width="430" height="300" class="rm"/>
|
||||
<circle cx="760" cy="450" r="30" fill="#f2ea72"/><text x="746" y="465" class="num">5</text>
|
||||
<text x="730" y="560" class="lbl" font-size="30">LONG GALLERY</text>
|
||||
<text x="730" y="598" class="sub" font-size="21">strobe · mirrors · no cast at all</text>
|
||||
|
||||
<!-- 6 · Ballroom -->
|
||||
<rect x="700" y="720" width="430" height="360" class="rm"/>
|
||||
<circle cx="760" cy="770" r="30" fill="#f2ea72"/><text x="746" y="785" class="num">6</text>
|
||||
<text x="730" y="880" class="lbl" font-size="30">BALLROOM</text>
|
||||
<text x="730" y="918" class="sub" font-size="21">lights up · fire exit · "thanks for coming"</text>
|
||||
|
||||
<!-- 1 · Entrance Hall -->
|
||||
<rect x="1150" y="1100" width="500" height="400" class="rm"/>
|
||||
<circle cx="1190" cy="1140" r="30" fill="#f2ea72"/><text x="1176" y="1155" class="num">1</text>
|
||||
<text x="1188" y="1235" class="lbl" font-size="30">ENTRANCE HALL</text>
|
||||
<text x="1188" y="1275" class="sub" font-size="21">the WALL PANEL — four names</text>
|
||||
<rect x="1188" y="1300" width="200" height="26" fill="#f2ea72"/>
|
||||
<text x="1188" y="1372" class="sub" font-size="21">ticket desk · head count</text>
|
||||
|
||||
<!-- main stairs (Edith) -->
|
||||
<rect x="1660" y="1140" width="170" height="250" class="rm2"/>
|
||||
<g stroke="#6674d8" stroke-width="4">
|
||||
<path d="M1660 1180H1830M1660 1220H1830M1660 1260H1830M1660 1300H1830M1660 1340H1830"/>
|
||||
</g>
|
||||
<text x="1663" y="1168" class="sub" font-size="21">MAIN STAIRS</text>
|
||||
<circle cx="1745" cy="1260" r="26" fill="#ff7a5c"/>
|
||||
<path d="M1650 1265H1600" class="door"/>
|
||||
<text x="1870" y="1268" class="sub" fill="#ff7a5c">EDITH CRANE stands here</text>
|
||||
|
||||
<!-- 2 · The Ward, east corridor -->
|
||||
<rect x="1700" y="900" width="700" height="200" class="rm"/>
|
||||
<circle cx="1740" cy="940" r="30" fill="#f2ea72"/><text x="1726" y="955" class="num">2</text>
|
||||
<text x="1786" y="1005" class="lbl" font-size="30">THE WARD — EAST CORRIDOR</text>
|
||||
<text x="1786" y="1045" class="sub" font-size="21">Sister Enderby counts</text>
|
||||
<rect x="2320" y="905" width="16" height="190" fill="#ff7a5c"/>
|
||||
<text x="1990" y="1160" class="sub" fill="#ff7a5c" font-size="21">JOSH PARDOE is behind this wall →</text>
|
||||
|
||||
<!-- 3 · Nursery -->
|
||||
<rect x="1900" y="400" width="500" height="420" class="rm"/>
|
||||
<circle cx="1940" cy="440" r="30" fill="#f2ea72"/><text x="1926" y="455" class="num">3</text>
|
||||
<text x="1990" y="530" class="lbl">THE NURSERY</text>
|
||||
<text x="1990" y="570" class="sub">Sammy in the wall · hole at ankle height</text>
|
||||
<rect x="1905" y="700" width="60" height="24" fill="#f2ea72"/>
|
||||
|
||||
<!-- green room -->
|
||||
<rect x="450" y="400" width="220" height="300" class="rm"/>
|
||||
<text x="466" y="446" class="lbl" font-size="20">GREEN</text>
|
||||
<text x="466" y="472" class="lbl" font-size="20">ROOM</text>
|
||||
<text x="466" y="508" class="sub" font-size="19">call sheet</text>
|
||||
<text x="466" y="534" class="sub" font-size="19">costumes</text>
|
||||
<text x="466" y="560" class="sub" font-size="19">SET PLANS</text>
|
||||
<text x="466" y="586" class="sub" font-size="19">pinned up</text>
|
||||
|
||||
<!-- control room -->
|
||||
<rect x="450" y="720" width="220" height="180" class="rm"/>
|
||||
<text x="466" y="768" class="lbl" font-size="22">CONTROL</text>
|
||||
<text x="466" y="802" class="sub" font-size="19">Ferriby · comms</text>
|
||||
|
||||
<!-- kitchens -->
|
||||
<rect x="450" y="920" width="220" height="300" class="rm"/>
|
||||
<text x="466" y="966" class="lbl" font-size="22">KITCHENS</text>
|
||||
<text x="466" y="1000" class="sub" font-size="19">Sharrock's way</text>
|
||||
<text x="466" y="1026" class="sub" font-size="19">out — NOT on</text>
|
||||
<text x="466" y="1052" class="sub" font-size="19">the fire plan</text>
|
||||
|
||||
<!-- yard -->
|
||||
<rect x="200" y="980" width="230" height="240" class="out"/>
|
||||
<text x="220" y="1030" class="lbl" font-size="28">YARD</text>
|
||||
<text x="220" y="1070" class="sub">skip · taped hasp</text>
|
||||
|
||||
<!-- doors -->
|
||||
<g class="door">
|
||||
<path d="M1370 1500H1450"/> <!-- front door -->
|
||||
<path d="M1130 1180H1150"/> <!-- hall to ballroom side -->
|
||||
<path d="M915 1080V1100"/> <!-- ballroom to hall run -->
|
||||
<path d="M1650 1000H1700"/> <!-- hall to ward -->
|
||||
<path d="M2150 900V820"/> <!-- ward to nursery -->
|
||||
<path d="M670 560H700"/> <!-- green room to gallery -->
|
||||
<path d="M670 800H700"/> <!-- control to ballroom -->
|
||||
<path d="M430 1070H450"/> <!-- kitchens to yard -->
|
||||
<path d="M915 700V720"/> <!-- gallery to ballroom -->
|
||||
</g>
|
||||
|
||||
<!-- ================= grounds ================= -->
|
||||
<rect x="200" y="1560" width="2400" height="380" class="out"/>
|
||||
<text x="230" y="1610" class="sub">THE LAWN — ninety feet of it</text>
|
||||
|
||||
<!-- marquee -->
|
||||
<rect x="1150" y="1560" width="500" height="230" class="rm"/>
|
||||
<text x="1200" y="1650" class="lbl" font-size="30">MARQUEE</text>
|
||||
<text x="1200" y="1690" class="sub">queue · box office · muster point</text>
|
||||
|
||||
<!-- glasshouse -->
|
||||
<rect x="2000" y="1600" width="560" height="300" class="rm"/>
|
||||
<circle cx="2040" cy="1640" r="30" fill="#f2ea72"/><text x="2026" y="1655" class="num">4</text>
|
||||
<text x="2090" y="1720" class="lbl">THE GLASSHOUSE</text>
|
||||
<text x="2090" y="1760" class="sub">Old Marrow · the taped line</text>
|
||||
<path d="M2000 1830H2560" stroke="#f2ea72" stroke-width="6" stroke-dasharray="18 14"/>
|
||||
<text x="2090" y="1880" class="sub" fill="#f2ea72">he stops here — and tonight he does not</text>
|
||||
|
||||
<!-- car park -->
|
||||
<rect x="250" y="1700" width="620" height="220" class="out"/>
|
||||
<text x="280" y="1760" class="lbl" font-size="28">CAR PARK</text>
|
||||
<text x="280" y="1800" class="sub">the van · the tea urn · blue lights</text>
|
||||
|
||||
<!-- Marrow's ground: he will not come indoors -->
|
||||
<path d="M700 1545H2600" stroke="#ff7a5c" stroke-width="8" stroke-dasharray="26 18"/>
|
||||
<text x="700" y="1530" class="sub" fill="#ff7a5c">OLD MARROW WILL NOT CROSS THIS LINE — inside is a complete answer</text>
|
||||
|
||||
<!-- key -->
|
||||
<g transform="translate(2420,120)">
|
||||
<rect x="-20" y="-40" width="360" height="230" fill="#101a6e" stroke="#4d5ac0" stroke-width="4"/>
|
||||
<text x="0" y="0" class="key" fill="#f2ea72">KEY</text>
|
||||
<rect x="0" y="30" width="34" height="14" fill="#f2ea72"/><text x="50" y="44" class="key">door / opening</text>
|
||||
<circle cx="17" cy="85" r="14" fill="#ff7a5c"/><text x="50" y="94" class="key">a body, or a person</text>
|
||||
<path d="M0 135H34" stroke="#ff7a5c" stroke-width="7" stroke-dasharray="12 8"/>
|
||||
<text x="50" y="144" class="key">Marrow's limit</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 8.9 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 131 KiB |
@@ -439,6 +439,7 @@ If there is time — and there usually is, because this ends early — she says
|
||||
| LA-ART-06 | The green room: mirrors, costumes, a clipboard on the door — used on the Act One page, where the call sheet is |
|
||||
| LA-ART-07 | Edith Crane on the stairs |
|
||||
| LA-ART-08 | The car park afterwards: a van, a tea urn, blue lights |
|
||||
| LA-MAP-01 | **Culland Hall floor plan** — drawn, not generated (`art/maps/culland-hall.svg`), and shipped as a playable **Scene** in the adventure: 2800×2000 at 100px to the square, 5ft grid, no walls (add them if you want line-of-sight). |
|
||||
|
||||
---
|
||||
|
||||
|
||||
+19
-1
@@ -13,6 +13,8 @@
|
||||
"TYPES.Item.group": "Group",
|
||||
"TYPES.Item.vehicle": "Vehicle",
|
||||
"TYPES.Item.authority": "Authority",
|
||||
"TYPES.Item.talent": "Talent",
|
||||
"TYPES.Item.power": "Art",
|
||||
"RINGBRP.Tab.Access": "Access",
|
||||
"RINGBRP.New.authority": "New authority",
|
||||
"RINGBRP.Scope.nothing": "nothing at all",
|
||||
@@ -925,5 +927,21 @@
|
||||
"RINGBRP.Gen.RolledChar": "{ch}",
|
||||
"RINGBRP.Gen.TakeThem": "Keep these",
|
||||
"RINGBRP.Gen.Nth": "agent {n}",
|
||||
"RINGBRP.Dialog.Circumstance": "How are the circumstances?"
|
||||
"RINGBRP.Dialog.Circumstance": "How are the circumstances?",
|
||||
"RINGBRP.Item.TalentId": "Talent key",
|
||||
"RINGBRP.Item.TalentCategory": "Category",
|
||||
"RINGBRP.Item.TalentSummary": "In one line",
|
||||
"RINGBRP.Item.TalentRule": "What it does",
|
||||
"RINGBRP.Item.GrantsAction": "Grants action",
|
||||
"RINGBRP.Item.UsesPerCase": "Uses per case",
|
||||
"RINGBRP.Item.UsedThisCase": "Used this case",
|
||||
"RINGBRP.Item.SkillBonusFam": "Bonus to skill",
|
||||
"RINGBRP.Item.SkillBonusVal": "Bonus",
|
||||
"RINGBRP.Item.ArtId": "Art key",
|
||||
"RINGBRP.Item.ArtSummary": "In one line",
|
||||
"RINGBRP.Item.ArtRule": "What it does",
|
||||
"RINGBRP.Item.ArtRating": "Rating",
|
||||
"RINGBRP.Item.PushCost": "Push cost",
|
||||
"RINGBRP.Item.PushRule": "What pushing does",
|
||||
"RINGBRP.Item.Origin": "Where it came from"
|
||||
}
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.665018 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.231003 174603000 Delete type=3 #1
|
||||
|
||||
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.733783 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.298648 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.666832 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.232848 174603000 Delete type=3 #1
|
||||
|
||||
Binary file not shown.
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.717519 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.282484 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.701249 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.266480 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.728905 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.293373 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.686790 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.252536 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.725345 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.290196 172ddf000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.721830 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.286378 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.732314 17391f000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.297168 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.659409 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.224954 172ddf000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.727208 173113000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.291920 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.711308 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.276268 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.705364 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.270666 173df7000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.662077 172907000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.227877 174603000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.668653 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.234705 1735eb000 Delete type=3 #1
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2026/08/12-18:41:45.663680 17412b000 Delete type=3 #1
|
||||
2026/08/12-19:01:54.229633 174603000 Delete type=3 #1
|
||||
|
||||
+9
-1
@@ -1524,7 +1524,10 @@ export async function loseCoherence(actor, reasonKey, { amount = null, note = ""
|
||||
speaker: ChatMessage.getSpeaker({ actor }),
|
||||
content: await rbRenderTemplate("systems/ringbrp/templates/chat-coherence.hbs", {
|
||||
actorName: actor.name, before, after, cost,
|
||||
reason: game.i18n.localize(spec?.label ?? reasonKey), note,
|
||||
// The cost is already applied by this point, so anything that throws here loses
|
||||
// the announcement and leaves the actor quietly poorer. localize() throws on a
|
||||
// non-string, which an unrecognised reason will be.
|
||||
reason: game.i18n.localize(String(spec?.label ?? reasonKey ?? "")), note,
|
||||
band: bAfter, crossed: bBefore.id !== bAfter.id,
|
||||
pips: Array.from({ length: Number(actor.system.coherence?.max ?? 10) }, (_, i) => i < after)
|
||||
})
|
||||
@@ -5514,6 +5517,11 @@ class RingBRPItemSheet extends ItemSheet {
|
||||
ctx.isMaterial = this.item.type === "material";
|
||||
ctx.isVehicle = this.item.type === "vehicle";
|
||||
ctx.isAuthority = this.item.type === "authority";
|
||||
// Talents and Arts were added after this list and never joined it, so their sheets
|
||||
// rendered an empty Detail tab over real data — category, rule, uses and cost all
|
||||
// sat in the document with nothing on screen reading them.
|
||||
ctx.isTalent = this.item.type === "talent";
|
||||
ctx.isPower = this.item.type === "power";
|
||||
ctx.descriptionHTML = await rbEnrich(ctx.sys.description ?? "");
|
||||
|
||||
if (ctx.isGear) {
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@
|
||||
"id": "ringbrp",
|
||||
"title": "The Custodians",
|
||||
"description": "A Basic Roleplaying game of agents who cross the crossing to contain what should not be there — and who come back a little less from here each time.",
|
||||
"version": "1.0.1",
|
||||
"version": "1.0.2",
|
||||
"compatibility": {
|
||||
"minimum": 13,
|
||||
"verified": "14.364"
|
||||
|
||||
@@ -171,6 +171,28 @@
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if isTalent}}
|
||||
<section class="rb-statline">
|
||||
<div class="rb-stat rb-stat-primary"><span class="rb-stat-label">{{localize "RINGBRP.Item.TalentCategory"}}</span>
|
||||
<span class="rb-stat-value rb-stat-small">{{sys.category}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Item.UsesPerCase"}}</span>
|
||||
<span class="rb-stat-value">{{#if sys.usesPerCase}}{{sys.usedThisCase}} / {{sys.usesPerCase}}{{else}}—{{/if}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Coh.Label"}}</span>
|
||||
<span class="rb-stat-value">{{#if sys.coherenceCost}}{{sys.coherenceCost}}{{else}}—{{/if}}</span></div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
{{#if isPower}}
|
||||
<section class="rb-statline">
|
||||
<div class="rb-stat rb-stat-primary"><span class="rb-stat-label">{{localize "RINGBRP.Item.ArtRating"}}</span>
|
||||
<span class="rb-stat-value">{{sys.value}}%</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Coh.Label"}}</span>
|
||||
<span class="rb-stat-value">{{sys.coherenceCost}}</span></div>
|
||||
<div class="rb-stat"><span class="rb-stat-label">{{localize "RINGBRP.Item.PushCost"}}</span>
|
||||
<span class="rb-stat-value">{{sys.pushCost}}</span></div>
|
||||
</section>
|
||||
{{/if}}
|
||||
|
||||
<nav class="sheet-tabs rb-tabs" data-group="primary">
|
||||
<a class="item" data-tab="detail">{{localize "RINGBRP.Tab.Detail"}}</a>
|
||||
<a class="item" data-tab="desc">{{localize "RINGBRP.Tab.Description"}}</a>
|
||||
@@ -471,6 +493,42 @@
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.BlocksSensors"}}</h3>
|
||||
<p class="rb-tags">{{#each sys.blocksSensors as |t|}}<span class="rb-tag">{{t}}</span>{{else}}<span class="rb-dim">—</span>{{/each}}</p>
|
||||
{{/if}}
|
||||
|
||||
{{#if isTalent}}
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.Identity"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.TalentId"}}<input type="text" name="system.talentId" value="{{sys.talentId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.TalentCategory"}}<input type="text" name="system.category" value="{{sys.category}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.TalentSummary"}}<input type="text" name="system.summary" value="{{sys.summary}}"/></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.TalentRule"}}<textarea name="system.rule" rows="3">{{sys.rule}}</textarea></label>
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Tab.Combat"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.UsesPerCase"}}<input type="number" name="system.usesPerCase" value="{{sys.usesPerCase}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.UsedThisCase"}}<input type="number" name="system.usedThisCase" value="{{sys.usedThisCase}}"/></label>
|
||||
<label>{{localize "RINGBRP.Coh.Label"}}<input type="number" name="system.coherenceCost" value="{{sys.coherenceCost}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.GrantsAction"}}<input type="text" name="system.grantsAction" value="{{sys.grantsAction}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.SkillBonusFam"}}<input type="text" name="system.skillBonus.skillFamilyId" value="{{sys.skillBonus.skillFamilyId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.SkillBonusVal"}}<input type="number" name="system.skillBonus.value" value="{{sys.skillBonus.value}}"/></label>
|
||||
</div>
|
||||
{{/if}}
|
||||
|
||||
{{#if isPower}}
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Item.Identity"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Item.ArtId"}}<input type="text" name="system.artId" value="{{sys.artId}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.ArtRating"}}<input type="number" name="system.value" value="{{sys.value}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.ArtSummary"}}<input type="text" name="system.summary" value="{{sys.summary}}"/></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.ArtRule"}}<textarea name="system.rule" rows="3">{{sys.rule}}</textarea></label>
|
||||
<h3 class="rb-cat-head">{{localize "RINGBRP.Coh.Label"}}</h3>
|
||||
<div class="rb-grid2">
|
||||
<label>{{localize "RINGBRP.Coh.Label"}}<input type="number" name="system.coherenceCost" value="{{sys.coherenceCost}}"/></label>
|
||||
<label>{{localize "RINGBRP.Item.PushCost"}}<input type="number" name="system.pushCost" value="{{sys.pushCost}}"/></label>
|
||||
</div>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.PushRule"}}<textarea name="system.pushRule" rows="2">{{sys.pushRule}}</textarea></label>
|
||||
<label class="rb-wide">{{localize "RINGBRP.Item.Origin"}}<input type="text" name="system.origin" value="{{sys.origin}}"/></label>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="tab" data-group="primary" data-tab="desc">
|
||||
|
||||
+37
-1
@@ -793,6 +793,42 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
+ `${S.STARTER_PAGES.length} GM pages, ${S.STARTER_HANDOUTS.length} handouts`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Culland Hall as a playable Scene.
|
||||
*
|
||||
* The map is DRAWN (art/maps/culland-hall.svg) rather than generated, because a room
|
||||
* count, a door position and a label are all load-bearing in this scenario and a
|
||||
* generator cannot be trusted with any of them. 2800x2000 at 100px to the square, so
|
||||
* the Foundry grid lands exactly on the grid drawn into the image.
|
||||
*
|
||||
* No walls or lighting: the scenario is a conversation with a floor plan behind it, and
|
||||
* a GM who wants line-of-sight can add them in ten minutes. Shipping half-done walls
|
||||
* would be worse than shipping none.
|
||||
*/
|
||||
function cullandScene() {
|
||||
const id = idFor("scene:culland_hall");
|
||||
return {
|
||||
_id: id,
|
||||
name: "Culland Hall — ground floor and grounds",
|
||||
navigation: true, navName: "Culland Hall", active: false,
|
||||
background: { src: "systems/ringbrp/art/maps/culland-hall.webp" },
|
||||
foreground: null, thumb: null,
|
||||
width: 2800, height: 2000, padding: 0.1,
|
||||
initial: { x: 1400, y: 1000, scale: 0.4 },
|
||||
backgroundColor: "#0c125b",
|
||||
grid: { type: 1, size: 100, style: "solidLines", thickness: 1,
|
||||
color: "#6674d8", alpha: 0.12, distance: 5, units: "ft" },
|
||||
tokenVision: false, fog: { exploration: false, overlay: null },
|
||||
globalLight: true, darkness: 0,
|
||||
environment: { globalLight: { enabled: true }, darknessLevel: 0 },
|
||||
drawings: [], tokens: [], lights: [], sounds: [], regions: [], templates: [],
|
||||
tiles: [], walls: [], notes: [],
|
||||
playlist: null, playlistSound: null, journal: null, journalEntryPage: null,
|
||||
weather: "", flags: { ringbrp: { key: "culland_hall" } },
|
||||
...BASE()
|
||||
};
|
||||
}
|
||||
|
||||
// LAST ADMISSION — the two-hour prologue, and the one to run by default. Same shape as
|
||||
// OPEN DAY's adventure and for the same reason: journals and cast only, because the
|
||||
// player characters are the "(before)" pregens in the Pregens pack.
|
||||
@@ -837,7 +873,7 @@ await writePack("armour", ARMOURS.map(a => {
|
||||
{ ...gmJournal.doc, pages: gmJournal.pages },
|
||||
{ ...handouts.doc, pages: handouts.pages }
|
||||
],
|
||||
scenes: [], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
|
||||
scenes: [cullandScene()], combats: [], tables: [], macros: [], cards: [], playlists: [], folders: [],
|
||||
sort: 0, ...BASE()
|
||||
};
|
||||
|
||||
|
||||
@@ -398,6 +398,34 @@ test("every compendium pack is declared in exactly one packFolders folder", asyn
|
||||
assert.equal(dupes.length, 0, `declared in more than one folder: ${dupes.join(", ")}`);
|
||||
});
|
||||
|
||||
// Talent and Art were added to template.json and then forgotten twice over: no
|
||||
// TYPES.Item.* label, so the sheet printed the raw key under the name, and no branch in
|
||||
// item-sheet.hbs, so the Detail tab was blank over a document full of real data. Eleven
|
||||
// of thirteen types were handled, which is exactly the shape a human eye skips.
|
||||
test("every declared document type has a label and a sheet section", async () => {
|
||||
const { readFile } = await import("node:fs/promises");
|
||||
const here = u => new URL(u, import.meta.url);
|
||||
const tmpl = JSON.parse(await readFile(here("../template.json"), "utf8"));
|
||||
const lang = JSON.parse(await readFile(here("../lang/en.json"), "utf8"));
|
||||
const itemSheet = await readFile(here("../templates/item-sheet.hbs"), "utf8");
|
||||
const system = await readFile(here("../ringbrp.mjs"), "utf8");
|
||||
|
||||
for (const doc of ["Actor", "Item"]) {
|
||||
for (const t of tmpl[doc].types) {
|
||||
assert.ok(lang[`TYPES.${doc}.${t}`],
|
||||
`no TYPES.${doc}.${t} in en.json — the sheet prints the raw key`);
|
||||
}
|
||||
}
|
||||
// Item types share one sheet, so each needs its own branch and the flag that drives it.
|
||||
for (const t of tmpl.Item.types) {
|
||||
const flag = `is${t[0].toUpperCase()}${t.slice(1)}`;
|
||||
assert.ok(system.includes(`ctx.${flag} =`),
|
||||
`the item sheet never sets ${flag}, so its section can never render`);
|
||||
assert.ok(itemSheet.includes(`{{#if ${flag}}}`),
|
||||
`item-sheet.hbs has no {{#if ${flag}}} section — Detail will be blank for ${t}`);
|
||||
}
|
||||
});
|
||||
|
||||
/* ---------------------------------------------------------------- */
|
||||
|
||||
await runAll();
|
||||
|
||||
Reference in New Issue
Block a user