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
- } )`;
- } 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 @@
-