diff --git a/module/deltagreen.js b/module/deltagreen.js index 6848474..5acf05c 100644 --- a/module/deltagreen.js +++ b/module/deltagreen.js @@ -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; diff --git a/module/integrations/dice-so-nice.js b/module/integrations/dice-so-nice.js new file mode 100644 index 0000000..f5a7e2d --- /dev/null +++ b/module/integrations/dice-so-nice.js @@ -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"; +} diff --git a/module/roll/roll.js b/module/roll/roll.js index f8501a5..20f68c2 100644 --- a/module/roll/roll.js +++ b/module/roll/roll.js @@ -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")} ${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 }; });