From 8627ff7283d2c88c0031a64d281313c031582634 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 23 Jan 2026 11:32:04 -0600 Subject: [PATCH 1/2] Add support for power pools - Added a new **Power Pool** checkbox to trackers on talents if their type is set to pool. When enabled, the pool roll will function as a power pool and open the action roll dialog rather than rolling the pool dice immediately. --- src/module/apps/roll-dialog.mjs | 4 +- src/module/data/actor-character.mjs | 1 + src/module/dice/rolls.mjs | 10 ++ src/module/grimwild.mjs | 4 +- src/module/helpers/schema.mjs | 3 +- src/module/sheets/actor-sheet-vue.mjs | 121 ++++++++++++++---- src/templates/chat/roll-action.hbs | 6 + src/templates/dialog/stat-roll.hbs | 2 +- .../components/item/talent/TalentTrackers.vue | 18 ++- 9 files changed, 135 insertions(+), 34 deletions(-) diff --git a/src/module/apps/roll-dialog.mjs b/src/module/apps/roll-dialog.mjs index 45453b6..6952551 100644 --- a/src/module/apps/roll-dialog.mjs +++ b/src/module/apps/roll-dialog.mjs @@ -183,7 +183,9 @@ export class GrimwildRollDialog extends foundry.applications.api.DialogV2 { if (value !== 0) { // Ensure there is a name to the assist const name = nameInput.value || "Assist"; - assisters[name] = value; + assisters[name] = !assisters[name] + ? value + : assisters[name] + value; } }); const sparks = dialog.element.querySelectorAll(".sparkCheck"); diff --git a/src/module/data/actor-character.mjs b/src/module/data/actor-character.mjs index 3db4967..4fcd0b5 100644 --- a/src/module/data/actor-character.mjs +++ b/src/module/data/actor-character.mjs @@ -300,6 +300,7 @@ export default class GrimwildCharacter extends GrimwildActorBase { name: this?.name ?? this?.parent?.name, spark: rollData?.spark, stat: options.stat, + diceLabel: game.i18n.localize(`GRIMWILD.Stat.${options.stat}.long`), diceDefault: rollData?.stats?.[options.stat].value, isBloodied: rollData?.isBloodied, isRattled: rollData?.isRattled, diff --git a/src/module/dice/rolls.mjs b/src/module/dice/rolls.mjs index ed7cd47..44e9a1b 100644 --- a/src/module/dice/rolls.mjs +++ b/src/module/dice/rolls.mjs @@ -32,6 +32,16 @@ export default class GrimwildRoll extends Roll { hasActions: false }; + if (this.options.pool) { + const dropped = chatData.dice.slice(0, this.options.pool.diceNum).filter((die) => die.result < 4); + chatData.startPool = !isPrivate + ? `${this.options.pool.diceNum}d` + : "???"; + chatData.endPool = !isPrivate + ? `${this.options.pool.diceNum - dropped.length}d` + : "???"; + } + const sixes = chatData.dice.filter((die) => die.result === 6); const cuts = chatData.thorns.filter((die) => die.result >= 7); diff --git a/src/module/grimwild.mjs b/src/module/grimwild.mjs index d70add3..76cc551 100644 --- a/src/module/grimwild.mjs +++ b/src/module/grimwild.mjs @@ -11,6 +11,7 @@ import { GrimwildActorMonsterSheetVue } from "./sheets/actor-monster-sheet-vue.m import { GrimwildItemSheet } from "./sheets/item-sheet.mjs"; import { GrimwildItemSheetVue } from "./sheets/item-sheet-vue.mjs"; import { GrimwildRollTableCrucibleSheet } from "./sheets/table-crucible-sheet.mjs"; +import { GrimwildRollDialog } from "./apps/roll-dialog.mjs"; // Import helper/utility classes and constants. import { GRIMWILD } from "./helpers/config.mjs"; import * as dice from "./dice/_module.mjs"; @@ -42,7 +43,8 @@ globalThis.grimwild = { GrimwildItemSheet, GrimwildItemSheetVue, GrimwildCombatTracker, - GrimwildRollTableCrucibleSheet + GrimwildRollTableCrucibleSheet, + GrimwildRollDialog, }, utils: { rollItemMacro diff --git a/src/module/helpers/schema.mjs b/src/module/helpers/schema.mjs index 2133bd6..e586381 100644 --- a/src/module/helpers/schema.mjs +++ b/src/module/helpers/schema.mjs @@ -13,7 +13,8 @@ export class DicePoolField extends fields.SchemaField { }), max: new fields.NumberField({ min: 0 - }) + }), + powerPool: new fields.BooleanField(), }; super(dpFields, options, context); } diff --git a/src/module/sheets/actor-sheet-vue.mjs b/src/module/sheets/actor-sheet-vue.mjs index f6c03d6..8e87910 100644 --- a/src/module/sheets/actor-sheet-vue.mjs +++ b/src/module/sheets/actor-sheet-vue.mjs @@ -570,6 +570,8 @@ export class GrimwildActorSheetVue extends VueRenderingMixin(GrimwildBaseVueActo let pool = null; let rollData = {}; let fieldData = null; + let dropped = []; + const update = {}; // Handle item pools (talents). if (itemId) { @@ -591,27 +593,97 @@ export class GrimwildActorSheetVue extends VueRenderingMixin(GrimwildBaseVueActo // Handle roll. if (pool.diceNum > 0) { - const roll = new grimwild.diePools(`{${pool.diceNum}d6}`, rollData); - const result = await roll.evaluate(); - const dice = result.dice[0].results; - const dropped = dice.filter((die) => die.result < 4); - - // Initialize chat data. - const speaker = ChatMessage.getSpeaker({ actor: this.actor }); - const rollMode = game.settings.get("core", "rollMode"); - const label = item - ? `[${item.type}] ${item.name}` - : `[${field}] ${fieldData[key]?.name ?? ""}`; - // Send to chat. - const msg = await roll.toMessage({ - speaker: speaker, - rollMode: rollMode, - flavor: label - }); - // Wait for Dice So Nice if enabled. - if (game.dice3d && msg?.id) { - await game.dice3d.waitFor3DAnimationByMessageID(msg.id); + if (tracker.pool.powerPool) { + const options = {}; + const rollData = this.actor.getRollData(); + const rollDialog = await grimwild.applications.GrimwildRollDialog.open({ + rollData: { + name: this.actor.name, + spark: rollData?.spark, + stat: null, + diceLabel: `[${item.name}] ${tracker.label}`, + diceDefault: pool.diceNum, + isBloodied: rollData?.isBloodied, + isRattled: rollData?.isRattled, + isMarked: false, + actor: this.actor + } + }); + if (rollDialog === null) { + return; + } + + rollData.thorns = rollDialog.thorns; + rollData.statDice = rollDialog.dice; + options.assists = rollDialog.assisters; + options.pool = tracker.pool; + const formula = `{${rollData.statDice}d6kh, ${rollData.thorns}d8}`; + const roll = new grimwild.roll(formula, rollData, options); + const result = await roll.evaluate(); + // Limit to just the pool dice when checking for dropped dice. + const dice = result.dice[0].results.slice(0, pool.diceNum); + dropped = dice.filter((die) => die.result < 4); + + // Add spark to the update. + if (rollDialog.sparkUsed > 0) { + let sparkUsed = rollDialog.sparkUsed; + const newSpark = { + steps: this.document.system.spark.steps + }; + // All of your spark is used. + if (sparkUsed > 1 || this.document.system.spark.value === 1) { + newSpark.steps[0] = false; + newSpark.steps[1] = false; + } + // If half of your spark is used. + else if (sparkUsed === 1 && this.document.system.spark.value > 1) { + newSpark.steps[0] = true; + newSpark.steps[1] = false; + } + update["system.spark"] = newSpark; + } + + // Initialize chat data. + const speaker = ChatMessage.getSpeaker({ actor: this.actor }); + const rollMode = game.settings.get("core", "rollMode"); + const label = item && tracker.label + ? `[${item.name}] ${tracker.label}` + : (item ? item.name : `[${field}] ${fieldData[key]?.name ?? ""}`); + // Send to chat. + const msg = await roll.toMessage({ + speaker: speaker, + rollMode: rollMode, + flavor: label + }); + // Wait for Dice So Nice if enabled. + if (game.dice3d && msg?.id) { + await game.dice3d.waitFor3DAnimationByMessageID(msg.id); + } + } + else { + const roll = new grimwild.diePools(`{${pool.diceNum}d6}`, rollData); + const result = await roll.evaluate(); + const dice = result.dice[0].results; + dropped = dice.filter((die) => die.result < 4); + + // Initialize chat data. + const speaker = ChatMessage.getSpeaker({ actor: this.actor }); + const rollMode = game.settings.get("core", "rollMode"); + const label = item + ? `[${item.type}] ${item.name}` + : `[${field}] ${fieldData[key]?.name ?? ""}`; + // Send to chat. + const msg = await roll.toMessage({ + speaker: speaker, + rollMode: rollMode, + flavor: label + }); + // Wait for Dice So Nice if enabled. + if (game.dice3d && msg?.id) { + await game.dice3d.waitFor3DAnimationByMessageID(msg.id); + } } + // Recalculate the pool value. pool.diceNum -= dropped.length; // Update the item. @@ -627,11 +699,12 @@ export class GrimwildActorSheetVue extends VueRenderingMixin(GrimwildBaseVueActo else { fieldData.pool = pool; } - const update = {}; update[`system.${field}`] = fieldData; - await this.document.update({ - [`system.${field}`]: fieldData - }); + } + + //Handle actor updates. + if (Object.keys(update).length > 0) { + await this.document.update(update); } } } diff --git a/src/templates/chat/roll-action.hbs b/src/templates/chat/roll-action.hbs index 8ceefcc..47f3b12 100644 --- a/src/templates/chat/roll-action.hbs +++ b/src/templates/chat/roll-action.hbs @@ -6,6 +6,12 @@ {{/if}} {{localize (concat "GRIMWILD.Dice.results." result)}} {{#if stat}}{{localize (concat "GRIMWILD.Stat." stat ".long")}}{{/if}} + {{#if startPool}} +
+ {{ startPool }} → {{ endPool }} +
+ {{/if}} +
diff --git a/src/templates/dialog/stat-roll.hbs b/src/templates/dialog/stat-roll.hbs index 805fa67..ee6a203 100644 --- a/src/templates/dialog/stat-roll.hbs +++ b/src/templates/dialog/stat-roll.hbs @@ -4,7 +4,7 @@ {{localize "GRIMWILD.Dice.dice"}}
- +
diff --git a/src/vue/components/item/talent/TalentTrackers.vue b/src/vue/components/item/talent/TalentTrackers.vue index ff1bc20..82a4bcb 100644 --- a/src/vue/components/item/talent/TalentTrackers.vue +++ b/src/vue/components/item/talent/TalentTrackers.vue @@ -7,12 +7,18 @@
-
- - +
+
+ + +
+
+ + +
From 5be11386472944f0678cbea7c76130f6810f5e53 Mon Sep 17 00:00:00 2001 From: Matt Smith Date: Fri, 23 Jan 2026 11:36:04 -0600 Subject: [PATCH 2/2] Lint fixes --- src/module/grimwild.mjs | 2 +- src/module/helpers/schema.mjs | 2 +- src/module/sheets/actor-sheet-vue.mjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/module/grimwild.mjs b/src/module/grimwild.mjs index 76cc551..947783e 100644 --- a/src/module/grimwild.mjs +++ b/src/module/grimwild.mjs @@ -44,7 +44,7 @@ globalThis.grimwild = { GrimwildItemSheetVue, GrimwildCombatTracker, GrimwildRollTableCrucibleSheet, - GrimwildRollDialog, + GrimwildRollDialog }, utils: { rollItemMacro diff --git a/src/module/helpers/schema.mjs b/src/module/helpers/schema.mjs index e586381..d517525 100644 --- a/src/module/helpers/schema.mjs +++ b/src/module/helpers/schema.mjs @@ -14,7 +14,7 @@ export class DicePoolField extends fields.SchemaField { max: new fields.NumberField({ min: 0 }), - powerPool: new fields.BooleanField(), + powerPool: new fields.BooleanField() }; super(dpFields, options, context); } diff --git a/src/module/sheets/actor-sheet-vue.mjs b/src/module/sheets/actor-sheet-vue.mjs index 8e87910..48ce321 100644 --- a/src/module/sheets/actor-sheet-vue.mjs +++ b/src/module/sheets/actor-sheet-vue.mjs @@ -702,7 +702,7 @@ export class GrimwildActorSheetVue extends VueRenderingMixin(GrimwildBaseVueActo update[`system.${field}`] = fieldData; } - //Handle actor updates. + // Handle actor updates. if (Object.keys(update).length > 0) { await this.document.update(update); }