From bc7060ce364e1740ca590b97ae940b912daa72f9 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Jul 2026 23:23:45 +0200 Subject: [PATCH 01/11] Added ComboDice modifier for dice rolls --- daggerheart.mjs | 2 ++ module/dice/_module.mjs | 1 + module/dice/die/_module.mjs | 1 + module/dice/die/advantageDie.mjs | 4 +++- module/dice/die/baseDie.mjs | 31 +++++++++++++++++++++++++++++ module/dice/die/disadvantageDie.mjs | 4 +++- module/dice/die/dualityDie.mjs | 3 ++- 7 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 module/dice/die/baseDie.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index b92be2a2..11e93696 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -38,6 +38,8 @@ CONFIG.RegionBehavior.dataModels = { }; Object.assign(CONFIG.Dice.termTypes, dice.diceTypes); +CONFIG.Dice.terms.d = dice.BaseDie; +CONFIG.Dice.types = [dice.BaseDie, CONFIG.Dice.terms.f]; CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.dataModels = models.actors.config; diff --git a/module/dice/_module.mjs b/module/dice/_module.mjs index e1206f82..6a5724bd 100644 --- a/module/dice/_module.mjs +++ b/module/dice/_module.mjs @@ -4,4 +4,5 @@ export { default as DamageRoll } from './damageRoll.mjs'; export { default as DHRoll } from './dhRoll.mjs'; export { default as DualityRoll } from './dualityRoll.mjs'; export { default as FateRoll } from './fateRoll.mjs'; +export { BaseDie } from './die/_module.mjs'; export { diceTypes } from './die/_module.mjs'; diff --git a/module/dice/die/_module.mjs b/module/dice/die/_module.mjs index 19ca951a..b84cef86 100644 --- a/module/dice/die/_module.mjs +++ b/module/dice/die/_module.mjs @@ -3,6 +3,7 @@ import HopeDie from './hopeDie.mjs'; import FearDie from './fearDie.mjs'; import AdvantageDie from './advantageDie.mjs'; import DisadvantageDie from './disadvantageDie.mjs'; +export { default as BaseDie } from './baseDie.mjs'; export const diceTypes = { DualityDie, diff --git a/module/dice/die/advantageDie.mjs b/module/dice/die/advantageDie.mjs index 9c2f0b03..66b6f124 100644 --- a/module/dice/die/advantageDie.mjs +++ b/module/dice/die/advantageDie.mjs @@ -1,4 +1,6 @@ -export default class AdvantageDie extends foundry.dice.terms.Die { +import BaseDie from './baseDie.mjs'; + +export default class AdvantageDie extends BaseDie { constructor(options) { super(options); diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs new file mode 100644 index 00000000..a14814e4 --- /dev/null +++ b/module/dice/die/baseDie.mjs @@ -0,0 +1,31 @@ +import { adjustDice } from '../../helpers/utils.mjs'; + +export default class BaseDie extends foundry.dice.terms.Die { + static MODIFIERS = { + ...foundry.dice.terms.Die.MODIFIERS, + c: 'comboDice' + }; + + async comboDice(modifier) { + /* ComboDice only works with exactly two dice and both have to be the same denomination */ + if (this.number !== 2) return false; + + const maxIncreasesDiceSize = modifier.endsWith('1'); + return this.continueCombo(maxIncreasesDiceSize); + } + + async continueCombo(maxIncreasesDiceSize) { + const lastIndex = this.results.length - 1; + + /* The Combo only continues if the latest roll was higher than the previous */ + if (this.results[lastIndex].result <= this.results[lastIndex - 1].result) return false; + + const increaseDiceSize = maxIncreasesDiceSize && this.results[lastIndex].result === this.faces; + const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; + const newRoll = await (new Roll(`1${denomination}`)).evaluate(); + this.results.push({ result: newRoll.total, active: true }); + this.number += 1; + + return this.continueCombo(); + } +} \ No newline at end of file diff --git a/module/dice/die/disadvantageDie.mjs b/module/dice/die/disadvantageDie.mjs index f56ebe96..e79845f3 100644 --- a/module/dice/die/disadvantageDie.mjs +++ b/module/dice/die/disadvantageDie.mjs @@ -1,4 +1,6 @@ -export default class DisadvantageDie extends foundry.dice.terms.Die { +import BaseDie from './baseDie.mjs'; + +export default class DisadvantageDie extends BaseDie { constructor(options) { super(options); diff --git a/module/dice/die/dualityDie.mjs b/module/dice/die/dualityDie.mjs index cc7ee75e..1e51e851 100644 --- a/module/dice/die/dualityDie.mjs +++ b/module/dice/die/dualityDie.mjs @@ -1,6 +1,7 @@ import { updateResourcesForDualityReroll } from '../helpers.mjs'; +import BaseDie from './baseDie.mjs'; -export default class DualityDie extends foundry.dice.terms.Die { +export default class DualityDie extends BaseDie { constructor(options) { super(options); From de0d4ef9b4e05f266477c69f622debb86623bc42 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Fri, 3 Jul 2026 23:35:33 +0200 Subject: [PATCH 02/11] . --- module/dice/die/baseDie.mjs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index a14814e4..ed44cb39 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -20,10 +20,12 @@ export default class BaseDie extends foundry.dice.terms.Die { /* The Combo only continues if the latest roll was higher than the previous */ if (this.results[lastIndex].result <= this.results[lastIndex - 1].result) return false; - const increaseDiceSize = maxIncreasesDiceSize && this.results[lastIndex].result === this.faces; + const lastFaces = this.results[lastIndex].denomination?.slice(1) ?? this.faces; + const increaseDiceSize = maxIncreasesDiceSize && lastFaces < 12 && this.results[lastIndex].result === lastFaces; const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; + const newRoll = await (new Roll(`1${denomination}`)).evaluate(); - this.results.push({ result: newRoll.total, active: true }); + this.results.push({ result: newRoll.total, denomination: denomination, active: true }); this.number += 1; return this.continueCombo(); From e25cf0c4cd6fc3e98f6f95ad19fd845fdbeb3b6b Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 16:00:49 +0200 Subject: [PATCH 03/11] Fixed so that dice css uses specific results.denomation if available --- module/dice/die/baseDie.mjs | 19 +++++++++++++++++++ templates/ui/chat/parts/damage-part.hbs | 2 +- templates/ui/chat/parts/roll-part.hbs | 7 ++++++- 3 files changed, 26 insertions(+), 2 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index ed44cb39..fa6086c3 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -30,4 +30,23 @@ export default class BaseDie extends foundry.dice.terms.Die { return this.continueCombo(); } + + /** @override */ + getResultCSS(result) { + const hasSuccess = result.success !== undefined; + const hasFailure = result.failure !== undefined; + const isMax = result.result === this.faces; + const isMin = result.result === 1; + return [ + this.constructor.name.toLowerCase(), + result.denomination ?? this.denomination, // Accomodating ComboDie as a result can have a different denomination than the die as a whole + result.success ? 'success' : null, + result.failure ? 'failure' : null, + result.rerolled ? 'rerolled' : null, + result.exploded ? 'exploded' : null, + result.discarded ? 'discarded' : null, + !(hasSuccess || hasFailure) && isMin ? 'min' : null, + !(hasSuccess || hasFailure) && isMax ? 'max' : null + ]; + } } \ No newline at end of file diff --git a/templates/ui/chat/parts/damage-part.hbs b/templates/ui/chat/parts/damage-part.hbs index 45b09b72..9636e066 100644 --- a/templates/ui/chat/parts/damage-part.hbs +++ b/templates/ui/chat/parts/damage-part.hbs @@ -39,7 +39,7 @@ {{#unless discarded}}
{{#if hasRerolls}}{{/if}} diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index cfee735f..2c5c150a 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -84,8 +84,13 @@ {{else}} {{#each roll.dice}} {{#each results}} + {{debug this}}
-
+
{{result}}
From 9fba3392306af822fc53b85bd283598a7e79619a Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 19:52:15 +0200 Subject: [PATCH 04/11] Fixed DiceSoNice using the upgraded DiceSizes for 2d4c1 --- module/dice/die/baseDie.mjs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index fa6086c3..30013586 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -11,7 +11,25 @@ export default class BaseDie extends foundry.dice.terms.Die { if (this.number !== 2) return false; const maxIncreasesDiceSize = modifier.endsWith('1'); - return this.continueCombo(maxIncreasesDiceSize); + const result = await this.continueCombo(maxIncreasesDiceSize); + + /* The flow of DiceSoNice has no way of knowign that some of the results of a Die should be a different denomination + We solve this by marking the results as hidden so they're not picked up by the auto roll of DiceSoNice. + The actual rolls are done here in place so every dice gets the correct denomination. + */ + if (game.modules.get('dice-so-nice')?.active) { + await Promise.allSettled(this.results.map(async result => { + const roll = await (new Roll(`1${result.denomination ?? this.denomination}`)).evaluate(); + roll.terms[0].results = [result]; + roll._evaluateTotal(); + return game.dice3d.showForRoll(roll, game.user, false); + })); + } + + for (const result of this.results) + result.hidden = true; + + return result; } async continueCombo(maxIncreasesDiceSize) { From 7ab7cc9787d64f190899621ed1c8422888432a0e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 20:03:51 +0200 Subject: [PATCH 05/11] Fixed so things are awaited properly if there are more dice to be rolled --- module/dice/die/baseDie.mjs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 30013586..867ee699 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -18,12 +18,22 @@ export default class BaseDie extends foundry.dice.terms.Die { The actual rolls are done here in place so every dice gets the correct denomination. */ if (game.modules.get('dice-so-nice')?.active) { - await Promise.allSettled(this.results.map(async result => { + const rolls = []; + for (const result of this.results) { const roll = await (new Roll(`1${result.denomination ?? this.denomination}`)).evaluate(); roll.terms[0].results = [result]; roll._evaluateTotal(); - return game.dice3d.showForRoll(roll, game.user, false); - })); + rolls.push(roll); + } + + /* If there are other dice that will be rolled we cannot await here. The other dice will be awaited in the normal flow */ + if (this._root.dice.length > 1) { + for (const roll of rolls) { + game.dice3d.showForRoll(roll, game.user, true); + } + } else { + await Promise.allSettled(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); + } } for (const result of this.results) From f9771635344c1a7d889370c35d4c598eed05dafb Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 21:00:00 +0200 Subject: [PATCH 06/11] Swapped to registering two modifiers, c for comboStrike and cc for compoundComboStrike --- module/dice/die/baseDie.mjs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 867ee699..d0de4582 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -3,14 +3,22 @@ import { adjustDice } from '../../helpers/utils.mjs'; export default class BaseDie extends foundry.dice.terms.Die { static MODIFIERS = { ...foundry.dice.terms.Die.MODIFIERS, + cc: 'compoundComboDice', c: 'comboDice' }; - async comboDice(modifier) { + async compoundComboDice() { + return this.handleComboDice(true); + } + + async comboDice() { + return this.handleComboDice(false); + } + + async handleComboDice(maxIncreasesDiceSize) { /* ComboDice only works with exactly two dice and both have to be the same denomination */ if (this.number !== 2) return false; - const maxIncreasesDiceSize = modifier.endsWith('1'); const result = await this.continueCombo(maxIncreasesDiceSize); /* The flow of DiceSoNice has no way of knowign that some of the results of a Die should be a different denomination @@ -56,7 +64,7 @@ export default class BaseDie extends foundry.dice.terms.Die { this.results.push({ result: newRoll.total, denomination: denomination, active: true }); this.number += 1; - return this.continueCombo(); + return this.continueCombo(maxIncreasesDiceSize); } /** @override */ From 3b87e1d4a8f4c80a70b45e3c159dffbbc55442bb Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 21:03:59 +0200 Subject: [PATCH 07/11] Added a warning notification if comboDice modifiers are used on anything but a pair of dice --- lang/en.json | 3 ++- module/dice/die/baseDie.mjs | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lang/en.json b/lang/en.json index b9a81b29..e24eef0f 100755 --- a/lang/en.json +++ b/lang/en.json @@ -3253,7 +3253,8 @@ "knowTheTide": "Know The Tide gained a token", "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}", "noTokenTargeted": "No token is targeted", - "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM" + "behaviorRegionRequiresGM": "Creating a Region with an attached Behavior requires an online GM", + "comboDiceOnlyTwoDiceError": "Combo dice functionality only works on a single pair of two dice" }, "Progress": { "migrationLabel": "Performing system migration. Please wait and do not close Foundry." diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index d0de4582..0798db26 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -17,7 +17,10 @@ export default class BaseDie extends foundry.dice.terms.Die { async handleComboDice(maxIncreasesDiceSize) { /* ComboDice only works with exactly two dice and both have to be the same denomination */ - if (this.number !== 2) return false; + if (this.number !== 2) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.comboDiceOnlyTwoDiceError')); + return false; + } const result = await this.continueCombo(maxIncreasesDiceSize); From e6f26deab9216aa6f134a0e05bc9a7d1b5f71a20 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 6 Jul 2026 22:22:05 +0200 Subject: [PATCH 08/11] Corrected ot Higher or Equal to for combo to continue --- module/dice/die/baseDie.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 0798db26..37c47d1d 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -56,8 +56,8 @@ export default class BaseDie extends foundry.dice.terms.Die { async continueCombo(maxIncreasesDiceSize) { const lastIndex = this.results.length - 1; - /* The Combo only continues if the latest roll was higher than the previous */ - if (this.results[lastIndex].result <= this.results[lastIndex - 1].result) return false; + /* The Combo only continues if the latest roll was higher or equal to the previous */ + if (this.results[lastIndex].result < this.results[lastIndex - 1].result) return false; const lastFaces = this.results[lastIndex].denomination?.slice(1) ?? this.faces; const increaseDiceSize = maxIncreasesDiceSize && lastFaces < 12 && this.results[lastIndex].result === lastFaces; From a285b3f4dcd2bb5e664cd6decd3d0af930a247fa Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 20 Jul 2026 03:29:41 +0200 Subject: [PATCH 09/11] Added handling for ComboDice single dice reroll --- module/dice/die/baseDie.mjs | 224 +++++++++++++++++++++++++----------- 1 file changed, 154 insertions(+), 70 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 7b9c865a..26ed6d7f 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -1,7 +1,14 @@ -import { adjustDice } from '../../helpers/utils.mjs'; +import { adjustDice, triggerChatRollFx } from '../../helpers/utils.mjs'; export default class BaseDie extends foundry.dice.terms.Die { - async rerollResult(resultIndex) { + static MODIFIERS = { + ...foundry.dice.terms.Die.MODIFIERS, + cc: 'compoundComboDice', + c: 'comboDice' + }; + + async rerollResult(resultToReroll) { + const resultIndex = Number(resultToReroll); const result = this.results[resultIndex]; result.rerolled = true; result.active = false; @@ -10,75 +17,10 @@ export default class BaseDie extends foundry.dice.terms.Die { const rerolledResult = this.results[this.results.length - 1]; this.results.splice(this.results.length - 1, 1); this.results.splice(resultIndex, 0, rerolledResult); - } - static MODIFIERS = { - ...foundry.dice.terms.Die.MODIFIERS, - cc: 'compoundComboDice', - c: 'comboDice' - }; - - async compoundComboDice() { - return this.handleComboDice(true); - } - - async comboDice() { - return this.handleComboDice(false); - } - - async handleComboDice(maxIncreasesDiceSize) { - /* ComboDice only works with exactly two dice and both have to be the same denomination */ - if (this.number !== 2) { - ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.comboDiceOnlyTwoDiceError')); - return false; - } - - const result = await this.continueCombo(maxIncreasesDiceSize); - - /* The flow of DiceSoNice has no way of knowign that some of the results of a Die should be a different denomination - We solve this by marking the results as hidden so they're not picked up by the auto roll of DiceSoNice. - The actual rolls are done here in place so every dice gets the correct denomination. - */ - if (game.modules.get('dice-so-nice')?.active) { - const rolls = []; - for (const result of this.results) { - const roll = await (new Roll(`1${result.denomination ?? this.denomination}`)).evaluate(); - roll.terms[0].results = [result]; - roll._evaluateTotal(); - rolls.push(roll); - } - - /* If there are other dice that will be rolled we cannot await here. The other dice will be awaited in the normal flow */ - if (this._root.dice.length > 1) { - for (const roll of rolls) { - game.dice3d.showForRoll(roll, game.user, true); - } - } else { - await Promise.allSettled(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); - } - } - - for (const result of this.results) - result.hidden = true; - - return result; - } - - async continueCombo(maxIncreasesDiceSize) { - const lastIndex = this.results.length - 1; - - /* The Combo only continues if the latest roll was higher or equal to the previous */ - if (this.results[lastIndex].result < this.results[lastIndex - 1].result) return false; - - const lastFaces = this.results[lastIndex].denomination?.slice(1) ?? this.faces; - const increaseDiceSize = maxIncreasesDiceSize && lastFaces < 12 && this.results[lastIndex].result === lastFaces; - const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; - - const newRoll = await (new Roll(`1${denomination}`)).evaluate(); - this.results.push({ result: newRoll.total, denomination: denomination, active: true }); - this.number += 1; - - return this.continueCombo(maxIncreasesDiceSize); + if (['c', 'cc'].some(x => this.modifiers.includes(x))) { + await this.handleComboDiceReroll(resultIndex); + } } /** @override */ @@ -99,4 +41,146 @@ export default class BaseDie extends foundry.dice.terms.Die { !(hasSuccess || hasFailure) && isMax ? 'max' : null ]; } + + /* -------------------------------------------- */ + /* Modifier Logic */ + /* -------------------------------------------- */ + + async compoundComboDice() { + return this.handleComboDice({ maxIncreasesDiceSize: true}); + } + + async comboDice() { + return this.handleComboDice({ maxIncreasesDiceSize: false }); + } + + async handleComboDice(maxIncreasesDiceSize) { + /* ComboDice only works with exactly two dice and both have to be the same denomination */ + if (this.number !== 2) { + ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.comboDiceOnlyTwoDiceError')); + return false; + } + + return this.rollComboDice(maxIncreasesDiceSize); + } + + async rollComboDice(options) { + const { maxIncreasesDiceSize, rerollStartIndex } = options; + const initialResultsLength = this.results.length; + const result = await this.continueCombo(maxIncreasesDiceSize); + + /* The flow of DiceSoNice has no way of knowing that some of the results of a Die should be a different denomination + We solve this by marking the results as hidden so they're not picked up by the auto roll of DiceSoNice. + The actual rolls are done here in place so every dice gets the correct denomination. + */ + if (game.modules.get('dice-so-nice')?.active) { + const resultsToRoll = this.results.filter((x, index) => { + if (!x.active) return false; + if (rerollStartIndex) + return index === rerollStartIndex || index > initialResultsLength - 1; + + return true; + }); + const rolls = []; + for (const result of resultsToRoll) { + const roll = await (new Roll(`1${result.denomination ?? this.denomination}`)).evaluate(); + roll.terms[0].results = [result]; + roll._evaluateTotal(); + rolls.push(roll); + } + + /* If there are other dice that will be rolled we cannot await here. The other dice will be awaited in the normal flow */ + if (rerollStartIndex === undefined && this._root.dice.length > 1) { + for (const roll of rolls) { + game.dice3d.showForRoll(roll, game.user, true); + } + } else { + await Promise.allSettled(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true))); + } + } + + for (const result of this.results) + result.hidden = true; + + const modifier = maxIncreasesDiceSize ? 'cc' : 'c'; + if (!this.modifiers.includes(modifier)) + this.modifiers.push(modifier); + + return result; + } + + async continueCombo(maxIncreasesDiceSize) { + const activeResults = this.results.filter(x => x.active); + const lastIndex = activeResults.length - 1; + + /* The Combo only continues if the latest roll was higher or equal to the previous */ + if (activeResults[lastIndex].result < activeResults[lastIndex - 1].result) return false; + + const lastFaces = activeResults[lastIndex].denomination?.slice(1) ?? this.faces; + const increaseDiceSize = + maxIncreasesDiceSize && lastFaces < 12 && + activeResults[lastIndex].result === lastFaces; + const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; + + const newRoll = await (new Roll(`1${denomination}`)).evaluate(); + this.results.push({ result: newRoll.total, denomination: denomination, active: true }); + this.number += 1; + + return this.continueCombo(maxIncreasesDiceSize); + } + + async handleComboDiceReroll(rerolledIndex) { + const resultGroupingIndexes = this.results.map((x, index) => ({ index, active: x.active })) + .filter(x => x.active).map(x => x.index); + if (resultGroupingIndexes.length <= 1) return; + + const rerolledResult = this.results[rerolledIndex]; + const compoundCombo = this.modifiers.includes('cc'); + const rerollGroupingIndex = resultGroupingIndexes.indexOf(rerolledIndex); + + const previousIndex = resultGroupingIndexes[rerollGroupingIndex - 1]; + const previousResult = this.results[previousIndex]; + const nextIndex = resultGroupingIndexes[rerollGroupingIndex + 1]; + const nextResult = this.results[nextIndex]; + + /* Rerolling any of the last two dice might introduce new results */ + if ( + rerollGroupingIndex === resultGroupingIndexes.length - 1 && + rerolledResult.result >= previousResult?.result + ) { + return await this.rollComboDice({ + maxIncreasesDiceSize: compoundCombo, + rerollStartIndex: rerollGroupingIndex + }); + } else if ( + rerollGroupingIndex === resultGroupingIndexes.length - 2 && + rerolledResult.result < nextResult?.result + ) { + return await this.rollComboDice({ + maxIncreasesDiceSize: compoundCombo, + rerollStartIndex: rerollGroupingIndex + }); + } + /* Rerolling a preceeding dice might invalidate later dice which should then be dropped */ + else if (rerolledResult.result < previousResult?.result){ + const cutoffIndex = rerollGroupingIndex === 0 ? resultGroupingIndexes[1] + 1 : rerolledIndex + 1; + this.results = this.results.slice(0, cutoffIndex); + this.number = this.results.filter(x => x.active).length; + } + + const fakeRollFaces = + rerolledResult.denomination ? rerolledResult.denomination.slice(1) : this.faces; + const fakeRoll = { + _evaluated: true, + dice: [new foundry.dice.terms.Die({ + ...this, + results: [rerolledResult], + total: rerolledResult.result, + faces: fakeRollFaces + })], + options: { appearance: {} } + }; + await triggerChatRollFx([fakeRoll]); + rerolledResult.hidden = true; + } } \ No newline at end of file From 4a25ffb456ece26de6d12558fb1dbd29da4df07e Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 20 Jul 2026 11:10:04 +0200 Subject: [PATCH 10/11] Fixed so that compoundCombo denomination increases are used for all subsequent results --- module/dice/die/baseDie.mjs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 26ed6d7f..30cb5b45 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -112,15 +112,19 @@ export default class BaseDie extends foundry.dice.terms.Die { async continueCombo(maxIncreasesDiceSize) { const activeResults = this.results.filter(x => x.active); const lastIndex = activeResults.length - 1; + const lastResult = activeResults[lastIndex]; /* The Combo only continues if the latest roll was higher or equal to the previous */ - if (activeResults[lastIndex].result < activeResults[lastIndex - 1].result) return false; + if (lastResult.result < activeResults[lastIndex - 1].result) return false; - const lastFaces = activeResults[lastIndex].denomination?.slice(1) ?? this.faces; + const lastFaces = lastResult.denomination ? + Number(lastResult.denomination.slice(1)) : this.faces; + const currentDenomination = `d${lastFaces}` const increaseDiceSize = maxIncreasesDiceSize && lastFaces < 12 && - activeResults[lastIndex].result === lastFaces; - const denomination = increaseDiceSize ? adjustDice(this.denomination, false) : this.denomination; + lastResult.result === lastFaces; + + const denomination = increaseDiceSize ? adjustDice(currentDenomination, false) : currentDenomination; const newRoll = await (new Roll(`1${denomination}`)).evaluate(); this.results.push({ result: newRoll.total, denomination: denomination, active: true }); From a5c8e79d722863a4790e15eb6346a26451e6e00d Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 20 Jul 2026 12:26:13 +0200 Subject: [PATCH 11/11] Added reroll handling for compound size changes --- module/dice/die/baseDie.mjs | 43 +++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 9 deletions(-) diff --git a/module/dice/die/baseDie.mjs b/module/dice/die/baseDie.mjs index 30cb5b45..4c684dd3 100644 --- a/module/dice/die/baseDie.mjs +++ b/module/dice/die/baseDie.mjs @@ -19,7 +19,7 @@ export default class BaseDie extends foundry.dice.terms.Die { this.results.splice(resultIndex, 0, rerolledResult); if (['c', 'cc'].some(x => this.modifiers.includes(x))) { - await this.handleComboDiceReroll(resultIndex); + await this.handleComboDiceReroll(resultIndex, result); } } @@ -66,7 +66,7 @@ export default class BaseDie extends foundry.dice.terms.Die { async rollComboDice(options) { const { maxIncreasesDiceSize, rerollStartIndex } = options; - const initialResultsLength = this.results.length; + const initialResultsLength = this.results.filter(x => x.active).length; const result = await this.continueCombo(maxIncreasesDiceSize); /* The flow of DiceSoNice has no way of knowing that some of the results of a Die should be a different denomination @@ -133,8 +133,9 @@ export default class BaseDie extends foundry.dice.terms.Die { return this.continueCombo(maxIncreasesDiceSize); } - async handleComboDiceReroll(rerolledIndex) { - const resultGroupingIndexes = this.results.map((x, index) => ({ index, active: x.active })) + async handleComboDiceReroll(rerolledIndex, originalResult) { + /* Potentially sliced when correcting compound dice at (1) */ + let resultGroupingIndexes = this.results.map((x, index) => ({ index, active: x.active })) .filter(x => x.active).map(x => x.index); if (resultGroupingIndexes.length <= 1) return; @@ -147,7 +148,29 @@ export default class BaseDie extends foundry.dice.terms.Die { const nextIndex = resultGroupingIndexes[rerollGroupingIndex + 1]; const nextResult = this.results[nextIndex]; - /* Rerolling any of the last two dice might introduce new results */ + const dropDice = preceeding => { + const cutoffIndex = preceeding ? resultGroupingIndexes[rerollGroupingIndex + 1] + 1 : rerolledIndex + 1; + this.results = this.results.slice(0, cutoffIndex); + this.number = this.results.filter(x => x.active).length; + }; + + /* (1) If subsequent size increases from the compound effect are now incorrect, drop subsequent dice */ + const lastFaces = Number((originalResult.denomination ?? this.denomination).slice(1)); + if ( + compoundCombo && + ( + (originalResult.result === lastFaces && + rerolledResult.result !== lastFaces) + || + (originalResult.result !== lastFaces && + rerolledResult.result === lastFaces) + ) + ) { + dropDice(false); + resultGroupingIndexes = resultGroupingIndexes.slice(0, this.number); + } + + /* (2) Rerolling any of the last two dice might introduce new results */ if ( rerollGroupingIndex === resultGroupingIndexes.length - 1 && rerolledResult.result >= previousResult?.result @@ -165,11 +188,13 @@ export default class BaseDie extends foundry.dice.terms.Die { rerollStartIndex: rerollGroupingIndex }); } - /* Rerolling a preceeding dice might invalidate later dice which should then be dropped */ + /* (3) Rerolling a subsequent dice might invalidate later dice which should then be dropped */ else if (rerolledResult.result < previousResult?.result){ - const cutoffIndex = rerollGroupingIndex === 0 ? resultGroupingIndexes[1] + 1 : rerolledIndex + 1; - this.results = this.results.slice(0, cutoffIndex); - this.number = this.results.filter(x => x.active).length; + dropDice(false); + } + /* (4) Rerolling a preceeding dice might invalidate later dice which should then be dropped */ + else if (rerolledResult.result >= nextResult?.result) { + dropDice(true); } const fakeRollFaces =