Add themed Dice So Nice dice: agency / lethal / unnatural

Registers three Delta Green 2 colorsets with Dice So Nice and applies them
per roll type so the dice match the moment:
- dg2-agency (olive drab, metal, cloudy, bone numerals) -> skill/stat/luck
  /weapon checks; set as the table default appearance.
- dg2-lethality (black + blood red, fire texture) -> damage and lethality.
- dg2-unnatural (void violet glass, astral texture, green glow) -> SAN
  checks, rituals, and sanity damage.

Dice are tagged in each toChat() via tagRollDice(); registration runs on
the diceSoNiceReady hook and degrades silently when DSN is absent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 20:41:59 +01:00
co-authored by Claude Fable 5
parent 2458ae808f
commit 7ac9357964
3 changed files with 120 additions and 0 deletions
+10
View File
@@ -17,6 +17,7 @@ import {
rollSkillTestAndDamageForOwnedItem,
} from "./other/macro-functions.js";
import { handleInlineActions } from "./other/inline.js";
import { registerDG2DiceSoNice } from "./integrations/dice-so-nice.js";
import DGNPCSheet from "./sheets/npc-sheet.js";
import DGUnnaturalSheet from "./sheets/unnatural-sheet.js";
import DGVehicleSheet from "./sheets/vehicle-sheet.js";
@@ -211,6 +212,15 @@ Hooks.on("createActor", async (actor, options, userId) => {
}
});
// Register themed Delta Green 2 dice with Dice So Nice, if present.
Hooks.once("diceSoNiceReady", (dice3d) => {
try {
registerDG2DiceSoNice(dice3d);
} catch (ex) {
console.warn("Delta Green 2 | Failed to register Dice So Nice theme", ex);
}
});
Hooks.on("renderGamePause", function (_, html, options) {
if (options.cssClass !== "paused") return;
+98
View File
@@ -0,0 +1,98 @@
/**
* Delta Green 2 — Dice So Nice integration.
*
* Registers three themed colorsets and applies them per roll type:
* - dg2-agency: olive drab, bone numerals — skill/stat/luck/weapon checks
* - dg2-lethality: black and blood red — lethality and damage rolls
* - dg2-unnatural: void violet with a sickly glow — SAN checks, rituals,
* and sanity damage
*/
const CATEGORY = "Delta Green 2";
/**
* Register the DG2 dice appearance with Dice So Nice.
* Called from the diceSoNiceReady hook.
*
* @param {Object} dice3d - the Dice So Nice API object.
*/
export function registerDG2DiceSoNice(dice3d) {
// "default" sets this as the table default for users who have not
// customized their own dice, so the system looks right out of the box.
dice3d.addColorset(
{
name: "dg2-agency",
description: "DG2 — Agency Issue",
category: CATEGORY,
foreground: "#e8e4d5",
background: "#3a4a24",
outline: "#151a0d",
edge: "#242f16",
texture: "cloudy",
material: "metal",
font: "Special Elite",
visibility: "visible",
},
"default",
);
dice3d.addColorset({
name: "dg2-lethality",
description: "DG2 — Lethal Force",
category: CATEGORY,
foreground: "#ff3b30",
background: "#0d0d0f",
outline: "#000000",
edge: "#3d0a06",
texture: "fire",
material: "metal",
font: "Special Elite",
visibility: "visible",
});
dice3d.addColorset({
name: "dg2-unnatural",
description: "DG2 — The Unnatural",
category: CATEGORY,
foreground: "#8dffb0",
background: "#1a0b2e",
outline: "#050208",
edge: "#2c1150",
texture: "astral",
material: "glass",
font: "Special Elite",
emissive: "#2a0a4a",
emissiveIntensity: 0.4,
visibility: "visible",
});
}
/**
* Tag every die in a roll with a DG2 colorset so Dice So Nice renders
* the themed dice for this roll regardless of the user's default.
*
* @param {Roll} roll - an evaluated (or about to be evaluated) roll.
* @param {"dg2-agency"|"dg2-lethality"|"dg2-unnatural"} colorset
*/
export function tagRollDice(roll, colorset) {
try {
for (const die of roll.dice) {
die.options.colorset = colorset;
}
} catch {
// Dice theming must never break the roll itself.
}
}
/**
* Pick the DG2 colorset for a percentile roll based on its type.
*
* @param {DGPercentileRoll} roll
* @returns {string} colorset name
*/
export function colorsetForPercentileRoll(roll) {
if (roll.type === "sanity" || roll.key === "ritual") {
return "dg2-unnatural";
}
return "dg2-agency";
}
+12
View File
@@ -1,6 +1,10 @@
/* eslint-disable max-classes-per-file */
import DGUtils from "../other/utility-functions.js";
import DG from "../config.js";
import {
tagRollDice,
colorsetForPercentileRoll,
} from "../integrations/dice-so-nice.js";
const { renderTemplate } = foundry.applications.handlebars;
const { DialogV2 } = foundry.applications.api;
@@ -260,6 +264,8 @@ export class DGPercentileRoll extends DGRoll {
game.modules.has("dice-so-nice") &&
game.modules.get("dice-so-nice").active;
tagRollDice(this, colorsetForPercentileRoll(this));
const label = this.createLabel();
let resultString = "";
@@ -642,6 +648,8 @@ export class DGLethalityRoll extends DGPercentileRoll {
* @override
*/
async toChat() {
tagRollDice(this, "dg2-lethality");
// Note: intentionally target + modifier, not effectiveTarget - a lethality
// roll is a property of the weapon, so exhaustion penalties do not apply.
const isLethal = this.total <= this.target + this.modifier;
@@ -741,6 +749,8 @@ export class DGDamageRoll extends DGRoll {
* @override
*/
async toChat() {
tagRollDice(this, "dg2-lethality");
let label = this.formula;
try {
label = `${game.i18n.localize("DG.Roll.Rolling")} <b>${game.i18n
@@ -841,6 +851,8 @@ export class DGSanityDamageRoll extends DGRoll {
* @override
*/
async toChat() {
tagRollDice(this, "dg2-unnatural");
const [lowDie, highDie] = this.terms[0].terms.map((formula) => {
return Roll.parse(formula)[0] || { faces: parseInt(formula), number: 1 };
});