diff --git a/README.md b/README.md index 953229c..7f999f5 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ The build runs all five guards before it writes anything: ``` check-rules: OK — 92 rules, 20 files scanned, 3 aliases + 45 constant sets checked, 291 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, 977 keys, no leaf/branch collisions -check-behaviour: OK — 57 behavioural tests +check-lang: OK — en.json, 981 keys, no leaf/branch collisions +check-behaviour: OK — 59 behavioural tests ``` diff --git a/lang/en.json b/lang/en.json index fc3a03b..c289746 100644 --- a/lang/en.json +++ b/lang/en.json @@ -975,5 +975,9 @@ "RINGBRP.Panic.NoChange": "No change.", "RINGBRP.Flavour.Panicking": "panicking ({mod}%)", "RINGBRP.Chat.ToEverything": "to everything", - "RINGBRP.Panic.Backfired": "Fumbled —" + "RINGBRP.Panic.Backfired": "Fumbled —", + "RINGBRP.Panic.SceneEnd": "Scene over", + "RINGBRP.Panic.SceneEndNote": "Panic clears when the scene does. Coherence does not.", + "RINGBRP.Party.Title": "The team, right now", + "RINGBRP.Party.Who": "Agent" } diff --git a/packs/armour/LOG b/packs/armour/LOG index 2d4cb03..a9ce3a0 100644 --- a/packs/armour/LOG +++ b/packs/armour/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.181337 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.183213 16f9eb000 Delete type=3 #1 diff --git a/packs/expedition/LOG b/packs/expedition/LOG index 944c5d5..7b5cdac 100644 --- a/packs/expedition/LOG +++ b/packs/expedition/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.256758 173707000 Delete type=3 #1 +2026/08/18-00:10:28.247767 16e9d3000 Delete type=3 #1 diff --git a/packs/gear/LOG b/packs/gear/LOG index 4537dc0..92bb0dc 100644 --- a/packs/gear/LOG +++ b/packs/gear/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.183232 171ee3000 Delete type=3 #1 +2026/08/18-00:10:28.184766 1701f7000 Delete type=3 #1 diff --git a/packs/lastadmission/LOG b/packs/lastadmission/LOG index 46fe6f1..9fe01e4 100644 --- a/packs/lastadmission/LOG +++ b/packs/lastadmission/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.237581 171ee3000 Delete type=3 #1 +2026/08/18-00:10:28.233431 16f9eb000 Delete type=3 #1 diff --git a/packs/npcs/LOG b/packs/npcs/LOG index cd5cb34..fedaee0 100644 --- a/packs/npcs/LOG +++ b/packs/npcs/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.219821 173707000 Delete type=3 #1 +2026/08/18-00:10:28.217982 16f1df000 Delete type=3 #1 diff --git a/packs/powers/LOG b/packs/powers/LOG index 04e3c83..fecbdf7 100644 --- a/packs/powers/LOG +++ b/packs/powers/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.250188 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.243180 16e9d3000 Delete type=3 #1 diff --git a/packs/pregens/000003.log b/packs/pregens/000003.log index 5245fc9..c49c543 100644 Binary files a/packs/pregens/000003.log and b/packs/pregens/000003.log differ diff --git a/packs/pregens/LOG b/packs/pregens/LOG index 637b3fd..066498d 100644 --- a/packs/pregens/LOG +++ b/packs/pregens/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.204640 173707000 Delete type=3 #1 +2026/08/18-00:10:28.204150 16f9eb000 Delete type=3 #1 diff --git a/packs/primer/000003.log b/packs/primer/000003.log index fff1285..fff3e0e 100644 Binary files a/packs/primer/000003.log and b/packs/primer/000003.log differ diff --git a/packs/primer/LOG b/packs/primer/LOG index cdee379..508b740 100644 --- a/packs/primer/LOG +++ b/packs/primer/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.246285 171ee3000 Delete type=3 #1 +2026/08/18-00:10:28.240419 16f1df000 Delete type=3 #1 diff --git a/packs/prologue/LOG b/packs/prologue/LOG index 280ffa9..1c82dfb 100644 --- a/packs/prologue/LOG +++ b/packs/prologue/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.242302 172efb000 Delete type=3 #1 +2026/08/18-00:10:28.237109 16e9d3000 Delete type=3 #1 diff --git a/packs/rules/LOG b/packs/rules/LOG index 976e934..a652db8 100644 --- a/packs/rules/LOG +++ b/packs/rules/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.254558 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.246420 16f9eb000 Delete type=3 #1 diff --git a/packs/skills/LOG b/packs/skills/LOG index 628f42f..85ead0c 100644 --- a/packs/skills/LOG +++ b/packs/skills/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.174562 171ee3000 Delete type=3 #1 +2026/08/18-00:10:28.177717 16f1df000 Delete type=3 #1 diff --git a/packs/slice/LOG b/packs/slice/LOG index 757cc37..5c78192 100644 --- a/packs/slice/LOG +++ b/packs/slice/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.248311 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.241902 16f9eb000 Delete type=3 #1 diff --git a/packs/starter/000003.log b/packs/starter/000003.log index 5fb9735..b2f102f 100644 Binary files a/packs/starter/000003.log and b/packs/starter/000003.log differ diff --git a/packs/starter/LOG b/packs/starter/LOG index ce52799..eda84ca 100644 --- a/packs/starter/LOG +++ b/packs/starter/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.230398 172efb000 Delete type=3 #1 +2026/08/18-00:10:28.227548 16e9d3000 Delete type=3 #1 diff --git a/packs/tables/LOG b/packs/tables/LOG index ce8f20b..4f4055f 100644 --- a/packs/tables/LOG +++ b/packs/tables/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.224441 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.221971 1701f7000 Delete type=3 #1 diff --git a/packs/talents/LOG b/packs/talents/LOG index b7accf6..4959873 100644 --- a/packs/talents/LOG +++ b/packs/talents/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.177864 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.180038 1701f7000 Delete type=3 #1 diff --git a/packs/vehicles/LOG b/packs/vehicles/LOG index fbedc39..b5a3854 100644 --- a/packs/vehicles/LOG +++ b/packs/vehicles/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.185513 171ee3000 Delete type=3 #1 +2026/08/18-00:10:28.186339 1701f7000 Delete type=3 #1 diff --git a/packs/weapons/LOG b/packs/weapons/LOG index f9cfb6b..c506404 100644 --- a/packs/weapons/LOG +++ b/packs/weapons/LOG @@ -1 +1 @@ -2026/08/17-23:31:35.179597 1726ef000 Delete type=3 #1 +2026/08/18-00:10:28.181674 1701f7000 Delete type=3 #1 diff --git a/ringbrp.mjs b/ringbrp.mjs index caffbc0..50b83ff 100644 --- a/ringbrp.mjs +++ b/ringbrp.mjs @@ -1612,6 +1612,57 @@ async function panicCard(actor, before, after, reason, note, { backfired = false }); } +/** + * Panic clears when the scene does — and until now there was no way to do that except + * editing five sheets by hand while the table waited. Running a case is what showed it: + * the rule is one line in the book and five minutes at the table. + */ +export async function clearPanic(actors, { reason = "" } = {}) { + const list = (Array.isArray(actors) ? actors : [actors]).filter(Boolean); + if (!list.length) return []; + const rows = []; + for (const a of list) { + const before = Number(a.system?.panic?.value ?? 0); + if (before) await a.update({ "system.panic.value": 0, "system.panic.log": [] }); + rows.push({ name: a.name, before }); + } + await ChatMessage.create({ + speaker: { alias: game.i18n.localize("RINGBRP.Panic.Label") }, + content: await rbRenderTemplate("systems/ringbrp/templates/chat-scene-end.hbs", + { rows, reason, anyone: rows.some(r => r.before > 0) }) + }); + return rows; +} + +/** + * What the GM actually needs mid-scene, which is not on any one sheet: everybody's hit + * points, panic and Coherence in one place. Running a case with five agents meant asking + * "what are you on?" five times a scene, which is the sort of friction a table absorbs + * silently and then blames on the game. + */ +export async function partyState(actors = null) { + const list = actors ?? (activeCaseFile() ? caseMembers(activeCaseFile()) + : game.actors.filter(a => a.type === "character" && a.hasPlayerOwner)); + const rows = (Array.isArray(list) ? list : [list]).filter(Boolean).map(a => { + const pv = Number(a.system?.panic?.value ?? 0); + const cv = Number(a.system?.coherence?.value ?? 10); + return { + name: a.name, + hp: `${a.system?.hp?.value ?? "?"}/${a.system?.hp?.max ?? "?"}`, + hurt: (a.system?.hp?.value ?? 0) < (a.system?.hp?.max ?? 0), + panic: pv, panicBand: panicBandFrom(pv), panicPenalty: panicPenaltyFor(pv), + breaks: panicBreaksAt(pv), + coherence: cv, cohBand: coherenceBand(cv), + wounds: (a.system?.wounds ?? []).length + }; + }); + await ChatMessage.create({ + speaker: { alias: game.i18n.localize("RINGBRP.Party.Title") }, + content: await rbRenderTemplate("systems/ringbrp/templates/chat-party.hbs", { rows }) + }); + return rows; +} + /** * Displacement. Reaching zero is not a slow slide to be nursed back; it is an * event. The character does not die — they go home to an Earth that is almost @@ -3927,10 +3978,16 @@ export async function generateCharacter({ const trainedKeys = new Set([...R.core, ...R.support]); for (const k of R.core) add(k, coreBand); for (const k of R.support) add(k, supportBand); - // The agency half. Added AFTER the trade, and skipped where the trade already - // covers it — a former police officer does not un-learn their sidearm to be taught - // it again at a worse number. - if (isTrade) { + // The agency half. Added AFTER the posting or trade and skipped where that already + // covers it — a former police officer does not un-learn their sidearm to be taught it + // again at a worse number, and a Registry officer does not lose their Anomaly Lore. + // + // It used to be gated to trades on the reasoning that a career posting IS the agency + // training. Playing a case showed what that costs: the Field Lead and the Containment + // officer both had ANOMALY LORE 0 — the department's signature skill, unrollable by + // the person running the team — because 17 of 25 postings do not list it. Everybody + // the department employs went through the same six weeks. It is a floor now. + { for (const k of INDUCTION.skills) { const doc = byKey.get(k); if (!doc) continue; if (items.some(i => i.name === doc.name)) continue; @@ -6179,7 +6236,7 @@ Hooks.once("init", () => { readinessRating, percentileFaces, circumstanceTotal, coherenceBand, coherencePenaltyFor, woundPenaltyFor, loseCoherence, regainCoherence, displace, panicBand, panicPenaltyFor, panicBreaksAt, panicReliefFor, panicAfter, - gainPanic, easePanic, breakPanic, + gainPanic, easePanic, breakPanic, clearPanic, partyState, isAcross, setAcross, useTransposed, debrief, activeCaseFile, returnCrossing, caseMembers, earnedDiscoveries, deviceFunctionStatus, earnDiscovery, useDeviceFunction, pickContactActor, resolveContact, newEncounter, claimRewards, activeRewardEffects, diff --git a/styles/ringbrp.css b/styles/ringbrp.css index caef5ee..3871b1d 100644 --- a/styles/ringbrp.css +++ b/styles/ringbrp.css @@ -1840,3 +1840,21 @@ text-transform: uppercase; color: var(--rb-void); background: var(--rb-danger); padding: 2px 6px; margin-right: 6px; } + +/* The GM's two mid-scene cards: what everyone is on, and the scene ending. */ +.rb-party-table { width: 100%; border-collapse: collapse; font-size: 12px; } +.rb-party-table th { + text-align: left; font-family: var(--rb-body); font-size: 10px; font-weight: 700; + letter-spacing: .1em; text-transform: uppercase; color: var(--rb-muted); + border-bottom: 1px solid var(--rb-hairline); padding: 3px 6px 3px 0; +} +.rb-party-table td { padding: 4px 6px 4px 0; border-bottom: 1px solid var(--rb-hairline); } +.rb-party-name { color: var(--rb-white); font-weight: 700; } +.rb-party-hurt { color: var(--rb-danger); } +.rb-party-w { font-weight: 700; color: var(--rb-danger); } +.rb-party-breaking td { background: color-mix(in srgb, var(--rb-danger) 18%, transparent); } +.rb-party-table em { font-style: normal; color: var(--rb-danger); font-weight: 700; } +.rb-scene-list { list-style: none; margin: 6px 0; padding: 0; } +.rb-scene-list li { display: flex; justify-content: space-between; padding: 2px 0; font-size: 13px; } +.rb-scene-name { color: var(--rb-white); } +.rb-scene-drop { color: var(--rb-yellow); font-variant-numeric: tabular-nums; } diff --git a/system.json b/system.json index 06f1108..222aed3 100644 --- a/system.json +++ b/system.json @@ -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.1.3", + "version": "1.2.0", "compatibility": { "minimum": 13, "verified": "14.364" diff --git a/templates/chat-party.hbs b/templates/chat-party.hbs new file mode 100644 index 0000000..dc709a9 --- /dev/null +++ b/templates/chat-party.hbs @@ -0,0 +1,21 @@ +
| {{localize "RINGBRP.Party.Who"}} | +{{localize "RINGBRP.Sheet.HP"}} | +{{localize "RINGBRP.Panic.Label"}} | +{{localize "RINGBRP.Coh.Label"}} | +
|---|---|---|---|
| {{r.name}} | +{{r.hp}}{{#if r.wounds}} {{r.wounds}}W{{/if}} | +{{r.panic}} {{localize r.panicBand.label}}{{#if r.panicPenalty}} {{r.panicPenalty}}%{{/if}} | +{{r.coherence}} {{localize r.cohBand.label}} | +
{{localize "RINGBRP.Panic.SceneEndNote"}}
+ {{else}} +{{localize "RINGBRP.Panic.NoChange"}}
+ {{/if}} +