diff --git a/README.md b/README.md index 52370f8..b7aff58 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ A modified fork of the official _Delta Green: The Role-Playing Game_ Foundry VTT - **Apply Lethality** — a successful lethality roll offers a one-click "reduce target to 0 HP" button. - **Sanity application** — SAN damage cards offer success/failure apply buttons. The GM is whispered alerts for temporary insanity (5+ SAN in one hit), breaking-point crossings, and 0 SAN. - **Ammo tracking** — weapon ammo is numeric with a magazine capacity; attack rolls expend a round, the Equipment tab shows an ammo column with a reload control, and the chat card shows remaining rounds / out-of-ammo warnings. Toggleable via the Automation settings (on by default). +- **Dossier chat cards** — every roll posts as a dark "Eyes Only" case card with a classification strip (FIELD OPERATION / THE UNNATURAL / LETHAL FORCE), the roll name, and a target chip, colour-accented to match the roll type. +- **Themed 3D dice** — three Dice So Nice colorsets applied per roll type: olive Agency dice for skills/stats, blood-red Lethal Force dice for damage and lethality, and violet-glass Unnatural dice for Sanity. +- **Field Equipment compendium** — 60 modern investigation and support items (surveillance, forensics, hazmat, breaching, comms, medical) with white-on-black icon art, mechanical bonus notes, and Agent's Handbook expense tiers. - **Offline reliability** — the live Google Fonts imports were removed; the system now uses only bundled fonts. Install by pointing Foundry at this repo, or copy the folder into `Data/systems/deltagreen2`. The system id is `deltagreen2`, so it installs alongside the original without conflict. diff --git a/css/deltagreen.css b/css/deltagreen.css index 82e31b1..c8ca2f8 100644 --- a/css/deltagreen.css +++ b/css/deltagreen.css @@ -1597,3 +1597,187 @@ button.dg2-apply-button.dg2-san-fail:hover { width: 55px; text-align: center; } + + +/* ------------------------------------------------------------------ */ +/* Delta Green 2 - dossier chat cards */ +/* ------------------------------------------------------------------ */ + +.chat-message.dg2-msg { + background: linear-gradient(160deg, #1b1e24 0%, #16181d 100%); + color: #e6e8ea; + border: 1px solid #2c313a; + border-left: 3px solid #8aa353; + border-radius: 4px; +} + +.chat-message.dg2-msg-lethality { + border-left-color: #d4553c; +} + +.chat-message.dg2-msg-unnatural { + border-left-color: #9d7bd8; +} + +.chat-message.dg2-msg .message-header { + color: #8f98a3; + border-bottom: 1px solid #272b32; + padding-bottom: 2px; +} + +.chat-message.dg2-msg .message-header .message-sender { + color: #e6e8ea; + font-family: "PublicSans", sans-serif; + font-weight: 700; + letter-spacing: 1px; + text-transform: uppercase; + font-size: 0.85rem; +} + +.chat-message.dg2-msg .message-header .message-metadata { + color: #5c6572; + font-size: 0.7rem; +} + +.dg2-card { + font-family: "PublicSans", sans-serif; +} + +.dg2-card-strip { + display: flex; + justify-content: space-between; + align-items: baseline; + font-size: 0.62rem; + font-weight: 700; + letter-spacing: 2px; + text-transform: uppercase; + margin: 2px 0 4px; +} + +.dg2-card-strip .dg2-card-type { + color: #8aa353; +} + +.dg2-msg-lethality .dg2-card-strip .dg2-card-type { + color: #d4553c; +} + +.dg2-msg-unnatural .dg2-card-strip .dg2-card-type { + color: #9d7bd8; +} + +.dg2-card-strip .dg2-card-org { + color: #5c6572; + letter-spacing: 1.5px; +} + +.dg2-card-title { + display: flex; + justify-content: space-between; + align-items: center; + gap: 6px; + margin-bottom: 4px; +} + +.dg2-card-title .dg2-card-name { + font-size: 1rem; + font-weight: 800; + letter-spacing: 0.5px; + text-transform: uppercase; + color: #e6e8ea; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.dg2-chip { + flex: 0 0 auto; + font-size: 0.68rem; + font-weight: 700; + letter-spacing: 1px; + color: #a9c46a; + background: rgba(138, 163, 83, 0.12); + border: 1px solid rgba(138, 163, 83, 0.45); + border-radius: 3px; + padding: 1px 6px; + white-space: nowrap; +} + +.dg2-msg-lethality .dg2-chip { + color: #e08a7a; + background: rgba(212, 85, 60, 0.12); + border-color: rgba(212, 85, 60, 0.45); +} + +.dg2-msg-unnatural .dg2-chip { + color: #bda4e8; + background: rgba(157, 123, 216, 0.12); + border-color: rgba(157, 123, 216, 0.45); +} + +/* Dice internals on the dark card */ +.chat-message.dg2-msg .dice-total { + background: #12141a; + border: 1px solid #2c313a; + border-radius: 4px; + color: #e6e8ea; + font-family: "PublicSans", sans-serif; + font-weight: 800; +} + +.chat-message.dg2-msg .dice-tooltip .part-formula, +.chat-message.dg2-msg .dice-tooltip .part-total { + color: #8f98a3; +} + +.chat-message.dg2-msg .dice-rolls .roll { + filter: invert(0.9) hue-rotate(180deg); +} + +/* Banner tuning on dark cards */ +.chat-message.dg2-msg .dg2-success { + color: #a9c46a; + background: rgba(138, 163, 83, 0.12); +} + +.chat-message.dg2-msg .dg2-crit-success { + color: #c9e59a; + background: rgba(138, 163, 83, 0.28); +} + +.chat-message.dg2-msg .dg2-failure { + color: #e08a7a; + background: rgba(212, 85, 60, 0.1); +} + +.chat-message.dg2-msg .dg2-crit-failure, +.chat-message.dg2-msg .dg2-lethal { + color: #ff9d8a; + background: rgba(212, 85, 60, 0.25); +} + +.chat-message.dg2-msg button.dg2-apply-button { + background: rgba(138, 163, 83, 0.14); + border: 1px solid #3d434e; + color: #e6e8ea; +} + +.chat-message.dg2-msg button.dg2-apply-button:hover { + border-color: #8aa353; + background: rgba(138, 163, 83, 0.3); +} + +.chat-message.dg2-msg button.dg2-apply-button.dg2-lethal, +.chat-message.dg2-msg button.dg2-apply-button.dg2-san-fail { + background: rgba(212, 85, 60, 0.12); +} + +.chat-message.dg2-msg button.dg2-apply-button.dg2-lethal:hover, +.chat-message.dg2-msg button.dg2-apply-button.dg2-san-fail:hover { + border-color: #d4553c; + background: rgba(212, 85, 60, 0.3); +} + +.chat-message.dg2-msg .rollback-section label { + color: #8f98a3; +} diff --git a/lang/en.json b/lang/en.json index 76fc1ca..ffcd4e4 100644 --- a/lang/en.json +++ b/lang/en.json @@ -397,5 +397,10 @@ "DG2.Apply.SanFailTooltip": "Apply the higher SAN loss (the SAN test failed).", "DG2.Apply.NoTarget": "Target or select a token first.", "DG2.Apply.NoSanTarget": "No valid target with a Sanity score.", - "DG.Settings.charactersheet.tactical": "Tactical (Delta Green 2 modern)" + "DG.Settings.charactersheet.tactical": "Tactical (Delta Green 2 modern)", + "DG2.Chat.FieldOperation": "Field Operation", + "DG2.Chat.LethalForce": "Lethal Force", + "DG2.Chat.TheUnnatural": "The Unnatural", + "DG2.Chat.EyesOnly": "Delta Green // Eyes Only", + "DG2.Chat.Target": "Target" } \ No newline at end of file diff --git a/module/deltagreen.js b/module/deltagreen.js index 5acf05c..ce6029e 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -255,6 +255,12 @@ Hooks.on("renderChatLog", async (app, element) => { }); Hooks.on("renderChatMessageHTML", async (app, element, context) => { + // Style DG2 roll messages as dossier cards (variant set in DGRoll.toMessage). + const variant = app.getFlag?.("deltagreen2", "variant"); + if (variant) { + element.classList.add("dg2-msg", `dg2-msg-${variant}`); + } + // ignore non chat card notifications if (!context.canClose) return; addEventListenerToChatMessage(element); diff --git a/module/roll/roll.js b/module/roll/roll.js index 20f68c2..3fcd609 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -51,6 +51,19 @@ export class DGRoll extends Roll { async toMessage(messageData = {}, { rollMode, create = true } = {}) { // eslint-disable-next-line no-param-reassign messageData.speaker = ChatMessage.getSpeaker({ actor: this.actor }); + + // Tag the message with its DG2 variant (agency/lethality/unnatural), + // derived from the Dice So Nice colorset set in toChat. The chat + // renderer uses this to style the card. + const colorset = this.dice[0]?.options?.colorset; + if (colorset?.startsWith("dg2-")) { + // eslint-disable-next-line no-param-reassign + messageData.flags = foundry.utils.mergeObject( + { deltagreen2: { variant: colorset.replace("dg2-", "") } }, + messageData.flags ?? {}, + ); + } + return super.toMessage(messageData, { rollMode: this.options.rollMode || rollMode, create, @@ -264,9 +277,39 @@ export class DGPercentileRoll extends DGRoll { game.modules.has("dice-so-nice") && game.modules.get("dice-so-nice").active; - tagRollDice(this, colorsetForPercentileRoll(this)); + const colorset = colorsetForPercentileRoll(this); + tagRollDice(this, colorset); - const label = this.createLabel(); + // Structured card header data. + const typeLabel = + colorset === "dg2-unnatural" + ? DGUtils.localizeWithFallback("DG2.Chat.TheUnnatural", "THE UNNATURAL") + : DGUtils.localizeWithFallback( + "DG2.Chat.FieldOperation", + "FIELD OPERATION", + ); + let rollName = this.localizedKey ?? this.formula; + if (this.isInhuman) { + rollName += ` [${game.i18n.localize("DG.Roll.Inhuman").toUpperCase()}]`; + } + const targetText = `${DGUtils.localizeWithFallback( + "DG2.Chat.Target", + "TARGET", + )} ${this.effectiveTarget}%`; + // Show the math when the effective target differs from the base value. + let chipDetail = ""; + const { isExhausted, exhaustedCheckPenalty } = this.exhausted; + if (this.modifier || isExhausted) { + chipDetail = `${this.target}%`; + if (this.modifier) { + chipDetail += `${DGUtils.formatStringWithLeadingPlus(this.modifier)}%`; + } + if (isExhausted) { + chipDetail += `${DGUtils.formatStringWithLeadingPlus( + exhaustedCheckPenalty, + )}%`; + } + } let resultString = ""; let outcomeClass = ""; @@ -327,6 +370,10 @@ export class DGPercentileRoll extends DGRoll { const html = await renderTemplate( "systems/deltagreen2/templates/roll/percentile-roll.hbs", { + typeLabel, + rollName, + targetText, + chipDetail, outcomeClass, outcomeIcon, marginText, @@ -352,7 +399,6 @@ export class DGPercentileRoll extends DGRoll { }, }, content: html, - flavor: label, }); if (diceSoNice) { @@ -367,7 +413,7 @@ export class DGPercentileRoll extends DGRoll { return message; } - return this.toMessage({ content: html, flavor: label }); + return this.toMessage({ content: html }); } /** @@ -668,20 +714,22 @@ export class DGLethalityRoll extends DGPercentileRoll { } const { nonLethalDamage } = this; - let label = `${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n - .localize("DG.Roll.Lethality") - .toUpperCase()} ${game.i18n.localize( - "DG.Roll.For", - )} ${this.item.name.toUpperCase()} ${game.i18n.localize( - "DG.Roll.Target", - )} ${this.target + this.modifier}`; + let targetText = `${game.i18n.localize("DG.Roll.Lethality").toUpperCase()} ${ + this.target + this.modifier + }%`; if (this.modifier) { - label += ` (${DGUtils.formatStringWithLeadingPlus(this.modifier)}%)`; + targetText += ` (${DGUtils.formatStringWithLeadingPlus(this.modifier)}%)`; } const html = await renderTemplate( "systems/deltagreen2/templates/roll/lethality-roll.hbs", { + typeLabel: DGUtils.localizeWithFallback( + "DG2.Chat.LethalForce", + "LETHAL FORCE", + ), + rollName: this.item.name, + targetText, resultString, outcomeClass, outcomeIcon, @@ -692,7 +740,7 @@ export class DGLethalityRoll extends DGPercentileRoll { }, ); - return this.toMessage({ content: html, flavor: label }); + return this.toMessage({ content: html }); } /** @@ -751,23 +799,17 @@ export class DGDamageRoll extends DGRoll { async toChat() { tagRollDice(this, "dg2-lethality"); - let label = this.formula; - try { - label = `${game.i18n.localize("DG.Roll.Rolling")} ${game.i18n - .localize("DG.Roll.Damage") - .toUpperCase()} ${game.i18n.localize("DG.Roll.For")} ${ - this.item.name - } (${ - this.item.system.armorPiercing - } armor penetration)`; - } catch (ex) { - // console.log(ex); - label = `Rolling DAMAGE for ${label.toUpperCase()}`; - } - const html = await renderTemplate( "systems/deltagreen2/templates/roll/damage-roll.hbs", { + typeLabel: DGUtils.localizeWithFallback( + "DG2.Chat.LethalForce", + "LETHAL FORCE", + ), + rollName: + this.item?.name ?? + game.i18n.localize("DG.Roll.Damage").toUpperCase(), + targetText: game.i18n.localize("DG.Roll.Damage").toUpperCase(), formula: this.formula, total: this.total, dice: this.dice.flatMap((die) => @@ -777,7 +819,7 @@ export class DGDamageRoll extends DGRoll { }, ); - return this.toMessage({ content: html, flavor: label }); + return this.toMessage({ content: html }); } async showDialog() { @@ -859,14 +901,18 @@ export class DGSanityDamageRoll extends DGRoll { const [lowResult, highResult] = this.damageResults; - const flavor = `Rolling ${DGUtils.localizeWithFallback( - "DG.Generic.SanDamage", - "SAN DAMAGE", - )} For ${lowDie.formula} / ${highDie.formula}`; - const html = await renderTemplate( "systems/deltagreen2/templates/roll/sanity-damage-roll.hbs", { + typeLabel: DGUtils.localizeWithFallback( + "DG2.Chat.TheUnnatural", + "THE UNNATURAL", + ), + rollName: DGUtils.localizeWithFallback( + "DG.Generic.SanDamage", + "SAN DAMAGE", + ), + targetText: `${lowDie.formula} / ${highDie.formula}`, lowFormula: lowDie.formula, highFormula: highDie.formula, lowFaces: lowDie.faces, @@ -876,7 +922,7 @@ export class DGSanityDamageRoll extends DGRoll { }, ); - return this.toMessage({ content: html, flavor }); + return this.toMessage({ content: html }); } /** diff --git a/packs/field-equipment/000004.log b/packs/field-equipment/000004.log new file mode 100644 index 0000000..e69de29 diff --git a/packs/field-equipment/000005.ldb b/packs/field-equipment/000005.ldb new file mode 100644 index 0000000..cd5cf73 Binary files /dev/null and b/packs/field-equipment/000005.ldb differ diff --git a/packs/field-equipment/CURRENT b/packs/field-equipment/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/field-equipment/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/field-equipment/LOCK b/packs/field-equipment/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/field-equipment/LOG b/packs/field-equipment/LOG new file mode 100644 index 0000000..29e5568 --- /dev/null +++ b/packs/field-equipment/LOG @@ -0,0 +1,5 @@ +2026/07/15-21:04:44.645310 17171b000 Delete type=3 #1 +2026/07/15-21:04:44.647621 1717a7000 Level-0 table #5: started +2026/07/15-21:04:44.648040 1717a7000 Level-0 table #5: 13451 bytes OK +2026/07/15-21:04:44.648151 1717a7000 Delete type=0 #3 +2026/07/15-21:04:44.648243 1717a7000 Manual compaction at level-0 from '!items!0ggfh90orzn6ldrf' @ 72057594037927935 : 1 .. '!items!zzorhwhnj04fox00' @ 0 : 0; will stop at (end) diff --git a/packs/field-equipment/MANIFEST-000002 b/packs/field-equipment/MANIFEST-000002 new file mode 100644 index 0000000..e511d0a Binary files /dev/null and b/packs/field-equipment/MANIFEST-000002 differ diff --git a/system.json b/system.json index 3769f93..5ea51b0 100644 --- a/system.json +++ b/system.json @@ -107,6 +107,7 @@ "firearms", "armor", "hand-to-hand-weapons", + "field-equipment", "agent-pregens", "operation-code-name-generator-tables" ], @@ -159,6 +160,18 @@ "ASSISTANT": "OWNER" } }, + { + "name": "field-equipment", + "label": "Field Equipment (DG2)", + "system": "deltagreen2", + "path": "packs/field-equipment", + "type": "Item", + "flags": {}, + "ownership": { + "PLAYER": "OBSERVER", + "ASSISTANT": "OWNER" + } + }, { "name": "agent-pregens", "label": "Agent Pregens", diff --git a/templates/roll/damage-roll.hbs b/templates/roll/damage-roll.hbs index bf5301a..0e96bfd 100644 --- a/templates/roll/damage-roll.hbs +++ b/templates/roll/damage-roll.hbs @@ -1,31 +1,41 @@ -
-
-
{{formula}}
-
-
-
-
-
- {{formula}} - {{total}} -
-
    - {{#each dice}} -
  1. {{this.result}}
  2. - {{/each}} -
-
-
+
+
+ {{typeLabel}} + {{localize 'DG2.Chat.EyesOnly'}} +
+
+ {{rollName}} + {{targetText}} {{formula}}{{#if ap}} · AP {{ap}}{{/if}} +
+ +
+
+
+
+
+
+
+ {{formula}} + {{total}} +
+
    + {{#each dice}} +
  1. {{this.result}}
  2. + {{/each}} +
+
+
+
+
+

{{total}}

+
+
-
-

{{total}}

-
-
diff --git a/templates/roll/lethality-roll.hbs b/templates/roll/lethality-roll.hbs index 3de81e0..b3d3f4e 100644 --- a/templates/roll/lethality-roll.hbs +++ b/templates/roll/lethality-roll.hbs @@ -1,49 +1,60 @@ -
-
-
- - {{resultString}} -
-
-
-
-
-
- d100 - {{total}} -
-
    -
  1. {{total}}
  2. -
-
-
- 2d10 (d10 + d10) - {{nonLethal.total}} -
-
    -
  1. {{nonLethal.die1}}
  2. -
  3. {{nonLethal.die2}}
  4. -
-
-
+
+
+ {{typeLabel}} + {{localize 'DG2.Chat.EyesOnly'}} +
+
+ {{rollName}} + {{targetText}} +
+ +
+
+
+ + {{resultString}} +
+
+
+
+
+
+ d100 + {{total}} +
+
    +
  1. {{total}}
  2. +
+
+
+ 2d10 (d10 + d10) + {{nonLethal.total}} +
+
    +
  1. {{nonLethal.die1}}
  2. +
  3. {{nonLethal.die2}}
  4. +
+
+
+
+
+

{{total}} ({{nonLethal.total}} {{localize 'DG.Roll.Damage'}})

+
+ {{#if isLethal}} + + {{else}} + + {{/if}}
-
-

{{total}} ({{nonLethal.total}} {{localize 'DG.Roll.Damage'}})

-
- {{#if isLethal}} - - {{else}} - - {{/if}}
diff --git a/templates/roll/percentile-roll.hbs b/templates/roll/percentile-roll.hbs index ff7cdf5..4e6b5c5 100644 --- a/templates/roll/percentile-roll.hbs +++ b/templates/roll/percentile-roll.hbs @@ -1,41 +1,53 @@ -
-
-
- - {{resultString}} - {{#if marginText}}{{marginText}}{{/if}} -
-
-
-
-
-
- {{formula}} - {{total}} -
-
    -
  1. {{total}}
  2. -
-
-
-
-
-

{{total}}

- {{#if ammoLine}} -
- {{ammoLine}} -
+
+
+ {{typeLabel}} + {{localize 'DG2.Chat.EyesOnly'}} +
+
+ {{rollName}} + {{#if targetText}} + {{targetText}} {{/if}}
- {{#if failureMark}} -
- - - +
+
+
+ + {{resultString}} + {{#if marginText}}{{marginText}}{{/if}} +
+
+
+
+
+
+ {{formula}} + {{total}} +
+
    +
  1. {{total}}
  2. +
+
+
+
+
+

{{total}}

+ {{#if ammoLine}} +
+ {{ammoLine}} +
+ {{/if}}
- {{/if}} + {{#if failureMark}} +
+ + + +
+ {{/if}} +
diff --git a/templates/roll/sanity-damage-roll.hbs b/templates/roll/sanity-damage-roll.hbs index 763befe..3ae551b 100644 --- a/templates/roll/sanity-damage-roll.hbs +++ b/templates/roll/sanity-damage-roll.hbs @@ -1,43 +1,53 @@ -
-
-
{{lowFormula}} / {{highFormula}}
-
-
-
-
-
- {{lowFormula}} - {{lowResult}} -
-
    -
  1. {{lowResult}}
  2. -
-
-
- {{highFormula}} - {{highResult}} -
-
    -
  1. {{highResult}}
  2. -
-
-
+
+
+ {{typeLabel}} + {{localize 'DG2.Chat.EyesOnly'}} +
+
+ {{rollName}} + {{targetText}} +
+ +
+
+
+
+
+
+
+ {{lowFormula}} + {{lowResult}} +
+
    +
  1. {{lowResult}}
  2. +
+
+
+ {{highFormula}} + {{highResult}} +
+
    +
  1. {{highResult}}
  2. +
+
+
+
+
+

{{lowResult}} / {{highResult}}

+
+ +
-
-

{{lowResult}} / {{highResult}}

-
- -