From a6500f6801909e0f5be75f4d8bbec2b71414bdfb Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 30 Mar 2026 20:58:13 +0200 Subject: [PATCH 1/8] Fixed advantage/disadvantage --- daggerheart.mjs | 2 +- module/dice/_module.mjs | 2 +- module/dice/die/_module.mjs | 8 ++-- module/dice/die/advantageDie.mjs | 7 ++++ module/dice/die/disadvantageDie.mjs | 7 ++++ module/dice/dualityRoll.mjs | 40 +++++++++---------- templates/dialogs/dice-roll/rollSelection.hbs | 6 +-- templates/ui/chat/parts/roll-part.hbs | 16 ++++---- 8 files changed, 51 insertions(+), 37 deletions(-) create mode 100644 module/dice/die/advantageDie.mjs create mode 100644 module/dice/die/disadvantageDie.mjs diff --git a/daggerheart.mjs b/daggerheart.mjs index 2cb02170..82fae3c9 100644 --- a/daggerheart.mjs +++ b/daggerheart.mjs @@ -35,7 +35,7 @@ CONFIG.Dice.daggerheart = { FateRoll: FateRoll }; -CONFIG.Dice.termTypes.DualityDie = dice.diceTypes.DualityDie; +Object.assign(CONFIG.Dice.termTypes, dice.diceTypes); CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.dataModels = models.actors.config; diff --git a/module/dice/_module.mjs b/module/dice/_module.mjs index 523f15c1..e1206f82 100644 --- a/module/dice/_module.mjs +++ b/module/dice/_module.mjs @@ -4,4 +4,4 @@ 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 { Dice, diceTypes } 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 e2a96208..ed892f6a 100644 --- a/module/dice/die/_module.mjs +++ b/module/dice/die/_module.mjs @@ -1,7 +1,9 @@ import DualityDie from './dualityDie.mjs'; - -export const Dice = [DualityDie]; +import AdvantageDie from './advantageDie.mjs'; +import DisadvantageDie from './disadvantageDie.mjs'; export const diceTypes = { - DualityDie + DualityDie, + AdvantageDie, + DisadvantageDie }; diff --git a/module/dice/die/advantageDie.mjs b/module/dice/die/advantageDie.mjs new file mode 100644 index 00000000..9c2f0b03 --- /dev/null +++ b/module/dice/die/advantageDie.mjs @@ -0,0 +1,7 @@ +export default class AdvantageDie extends foundry.dice.terms.Die { + constructor(options) { + super(options); + + this.modifiers = []; + } +} diff --git a/module/dice/die/disadvantageDie.mjs b/module/dice/die/disadvantageDie.mjs new file mode 100644 index 00000000..f56ebe96 --- /dev/null +++ b/module/dice/die/disadvantageDie.mjs @@ -0,0 +1,7 @@ +export default class DisadvantageDie extends foundry.dice.terms.Die { + constructor(options) { + super(options); + + this.modifiers = []; + } +} diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index 56cecfd7..be935d4b 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -17,22 +17,6 @@ export default class DualityRoll extends D20Roll { static DefaultDialog = D20RollDialog; - /**@inheritdoc */ - static instantiateAST(ast) { - /* First two dice are always the DualityDice */ - const nodes = CONFIG.Dice.parser.flattenTree(ast); - const dieNodes = nodes.filter(x => x.class === 'DiceTerm'); - if (dieNodes.length > 1) { - dieNodes[0].class = 'DualityDie'; - dieNodes[1].class = 'DualityDie'; - } - - return nodes.map(node => { - const cls = foundry.dice.terms[node.class] ?? foundry.dice.terms.RollTerm; - return cls.fromParseNode(node); - }); - } - get title() { return game.i18n.localize( `DAGGERHEART.GENERAL.${this.options?.actionType === 'reaction' ? 'reactionRoll' : 'dualityRoll'}` @@ -40,27 +24,31 @@ export default class DualityRoll extends D20Roll { } get dHope() { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); return this.dice[0]; } set dHope(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); this.dice[0].faces = this.getFaces(faces); } get dFear() { - if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); return this.dice[1]; } set dFear(faces) { - if (!(this.dice[1] instanceof foundry.dice.terms.Die)) this.createBaseDice(); + if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); this.dice[1].faces = this.getFaces(faces); } get dAdvantage() { - return this.dice[2]; + return this.dice[2] instanceof game.system.api.dice.diceTypes.AdvantageDie ? this.dice[2] : null; + } + + get dDisadvantage() { + return this.dice[2] instanceof game.system.api.dice.diceTypes.DisadvantageDie ? this.dice[2] : null; } get advantageFaces() { @@ -145,6 +133,16 @@ export default class DualityRoll extends D20Roll { return [...(hooks ?? []), 'Duality']; } + /** @inheritDoc */ + static fromData(data) { + data.terms[0].class = 'DualityDie'; + data.terms[2].class = 'DualityDie'; + + if (data.options.roll.advantage?.type === 1) data.terms[4].class = 'AdvantageDie'; + else if (data.options.roll.advantage?.type === -1) data.terms[4].class = 'DisadvantageDie'; + return super.fromData(data); + } + createBaseDice() { if ( this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie && diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index 4451160b..64a3cdcb 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -53,14 +53,14 @@ {{#if @root.advantage}} {{#if (eq @root.advantage 1)}}
- +
{{localize "DAGGERHEART.GENERAL.Advantage.full"}}
{{else if (eq @root.advantage -1)}}
- +
{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}
@@ -158,7 +158,7 @@ {{/times}}
{{#if abilities}} diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index b823b270..33884161 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -63,14 +63,14 @@ {{#if roll.dAdvantage}} -
- {{#if roll.hasAdvantage}} - -
{{roll.dAdvantage.total}}
- {{else}} - -
{{roll.dAdvantage.total}}
- {{/if}} +
+ +
{{roll.dAdvantage.total}}
+
+ {{else if roll.dDisadvantage}} +
+ +
{{roll.dDisadvantage.total}}
{{/if}} {{#if roll.rally.dice}} From bf5a6d2a4d259e6fbfc5820a6da60c5f6478bd62 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 30 Mar 2026 21:05:00 +0200 Subject: [PATCH 2/8] . --- module/dice/dualityRoll.mjs | 5 +++++ templates/ui/chat/parts/roll-part.hbs | 14 +++++--------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index be935d4b..a6944a56 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -67,6 +67,11 @@ export default class DualityRoll extends D20Roll { this._advantageNumber = Number(value); } + get extraDice() { + const { DualityDie, AdvantageDie, DisadvantageDie } = game.system.api.dice.diceTypes; + return this.dice.filter(x => ![DualityDie, AdvantageDie, DisadvantageDie].some(die => x instanceof die)); + } + /* This isn't fullproof, but trying to cover parathetical situations is ridiculously complex */ get modifierTotal() { let modifierTotal = 0; diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 33884161..6cab38df 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -79,15 +79,11 @@
{{roll.rally.value}}
{{/if}} - {{#each roll.extra}} - {{#each results}} - {{#unless discarded}} -
- -
{{result}}
-
- {{/unless}} - {{/each}} + {{#each roll.extraDice}} +
+ +
{{this.total}}
+
{{/each}} {{else}} {{#each roll.dice}} From fe0cb4468c4e99783eb8d58f6579cb1f36b80d68 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 30 Mar 2026 22:25:10 +0200 Subject: [PATCH 3/8] . --- module/dice/fateRoll.mjs | 16 ++++++++++++++++ module/documents/chatMessage.mjs | 4 ++-- templates/ui/chat/parts/roll-part.hbs | 14 +++++++------- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/module/dice/fateRoll.mjs b/module/dice/fateRoll.mjs index 418c8465..5b52262a 100644 --- a/module/dice/fateRoll.mjs +++ b/module/dice/fateRoll.mjs @@ -43,6 +43,22 @@ export default class FateRoll extends D20Roll { return this.data.fateType; } + get withHope() { + if (!this._evaluatedl) return; + return this.dHope.total >= this.dFear.total; + } + + get withFear() { + if (!this._evaluated) return; + return this.dHope.total < this.dFear.total; + } + + get totalLabel() { + const label = this.withHope ? 'DAGGERHEART.GENERAL.hope' : 'DAGGERHEART.GENERAL.fear'; + + return game.i18n.localize(label); + } + static getHooks(hooks) { return [...(hooks ?? []), 'Fate']; } diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 920b2875..884ba84f 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -84,10 +84,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { } if (this.type === 'fateRoll') { html.classList.add('fate'); - if (this.system.roll?.fate.fateDie == 'Hope') { + if (this.system.roll?.fateDie == 'Hope') { html.classList.add('hope'); } - if (this.system.roll?.fate.fateDie == 'Fear') { + if (this.system.roll?.fateDie == 'Fear') { html.classList.add('fear'); } } diff --git a/templates/ui/chat/parts/roll-part.hbs b/templates/ui/chat/parts/roll-part.hbs index 6cab38df..850f0e92 100644 --- a/templates/ui/chat/parts/roll-part.hbs +++ b/templates/ui/chat/parts/roll-part.hbs @@ -29,20 +29,20 @@
- {{#if roll.fate}} - {{#if (eq roll.fate.fateDie "Hope")}} + {{#if roll.fateDie}} + {{#if (eq roll.fateDie "Hope")}}
-
- {{roll.fate.value}} +
+ {{roll.dHope.total}}
{{/if}} - {{#if (eq roll.fate.fateDie "Fear")}} + {{#if (eq roll.fateDie "Fear")}}
-
- {{roll.fate.value}} +
+ {{roll.dFear.total}}
{{/if}} From 714d4edc1605df434776d6c37d3660511fb48fe8 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Mon, 30 Mar 2026 22:47:28 +0200 Subject: [PATCH 4/8] Fixed TagTeamDialog --- module/applications/dialogs/tagTeamDialog.mjs | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/module/applications/dialogs/tagTeamDialog.mjs b/module/applications/dialogs/tagTeamDialog.mjs index 33ddfbc0..2d363471 100644 --- a/module/applications/dialogs/tagTeamDialog.mjs +++ b/module/applications/dialogs/tagTeamDialog.mjs @@ -452,8 +452,12 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const dieIndex = diceType === 'hope' ? 0 : diceType === 'fear' ? 1 : 2; const newRoll = game.system.api.dice.DualityRoll.fromData(memberData.rollData); const dice = newRoll.dice[dieIndex]; - await dice.reroll(`/r1=${dice.total}`, { liveRoll: true }); - await newRoll._evaluate(); + await dice.reroll(`/r1=${dice.total}`, { + liveRoll: { + roll: newRoll, + isReaction: true + } + }); const rollData = newRoll.toJSON(); this.updatePartyData( { @@ -694,6 +698,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio const joinedRoll = await this.getJoinedRoll(); const mainRoll = joinedRoll.rollData; + const finalRoll = foundry.utils.deepClone(joinedRoll.roll); const mainActor = this.party.system.partyMembers.find(x => x.uuid === mainRoll.options.source.actor); mainRoll.options.title = game.i18n.localize('DAGGERHEART.APPLICATIONS.TagTeamSelect.chatMessageRollTitle'); @@ -712,34 +717,33 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio await cls.create(msgData); /* Handle resource updates from the finished TagTeamRoll */ - // const tagTeamData = this.party.system.tagTeam; - // const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; - // for (let memberId in tagTeamData.members) { - // const resourceUpdates = []; - // const rollGivesHope = mainRoll.options.roll.isCritical || mainRoll.options.roll.result.duality === 1; - // if (memberId === tagTeamData.initiator.memberId) { - // const value = tagTeamData.initiator.cost - // ? rollGivesHope - // ? 1 - tagTeamData.initiator.cost - // : -tagTeamData.initiator.cost - // : 1; - // resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); - // } else if (rollGivesHope) { - // resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); - // } - // if (mainRoll.options.roll.isCritical) - // resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); - // if (mainRoll.options.roll.result.duality === -1) { - // fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; - // fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; - // } + const tagTeamData = this.party.system.tagTeam; + const fearUpdate = { key: 'fear', value: null, total: null, enabled: true }; + for (let memberId in tagTeamData.members) { + const resourceUpdates = []; + const rollGivesHope = finalRoll.isCritical || finalRoll.withHope; + if (memberId === tagTeamData.initiator.memberId) { + const value = tagTeamData.initiator.cost + ? rollGivesHope + ? 1 - tagTeamData.initiator.cost + : -tagTeamData.initiator.cost + : 1; + resourceUpdates.push({ key: 'hope', value: value, total: -value, enabled: true }); + } else if (rollGivesHope) { + resourceUpdates.push({ key: 'hope', value: 1, total: -1, enabled: true }); + } + if (finalRoll.isCritical) resourceUpdates.push({ key: 'stress', value: -1, total: 1, enabled: true }); + if (finalRoll.withFear) { + fearUpdate.value = fearUpdate.value === null ? 1 : fearUpdate.value + 1; + fearUpdate.total = fearUpdate.total === null ? -1 : fearUpdate.total - 1; + } - // game.actors.get(memberId).modifyResource(resourceUpdates); - // } + game.actors.get(memberId).modifyResource(resourceUpdates); + } - // if (fearUpdate.value) { - // mainActor.modifyResource([fearUpdate]); - // } + if (fearUpdate.value) { + mainActor.modifyResource([fearUpdate]); + } /* Fin */ this.cancelRoll({ confirm: false }); From 3cdea0c7897bc4c5afeaae029c0ddf50025cecf0 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 30 Mar 2026 21:15:44 -0400 Subject: [PATCH 5/8] Reuse getter in faces setter --- module/dice/dualityRoll.mjs | 8 ++++---- module/dice/fateRoll.mjs | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index a6944a56..f4cdeba2 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -29,8 +29,8 @@ export default class DualityRoll extends D20Roll { } set dHope(faces) { - if (!(this.dice[0] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces + this.dHope.faces = this.getFaces(faces); } get dFear() { @@ -39,8 +39,8 @@ export default class DualityRoll extends D20Roll { } set dFear(faces) { - if (!(this.dice[1] instanceof game.system.api.dice.diceTypes.DualityDie)) this.createBaseDice(); - this.dice[1].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces + this.dFear.faces = this.getFaces(faces); } get dAdvantage() { diff --git a/module/dice/fateRoll.mjs b/module/dice/fateRoll.mjs index 5b52262a..ec03715a 100644 --- a/module/dice/fateRoll.mjs +++ b/module/dice/fateRoll.mjs @@ -21,8 +21,8 @@ export default class FateRoll extends D20Roll { } set dHope(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dHope.faces + this.dHope.faces = this.getFaces(faces); } get dFear() { @@ -31,8 +31,8 @@ export default class FateRoll extends D20Roll { } set dFear(faces) { - if (!(this.dice[0] instanceof foundry.dice.terms.Die)) this.createBaseDice(); - this.dice[0].faces = this.getFaces(faces); + // TODO this should not be asymmetrical with the getter. updateRollConfiguration() should use dFear.faces + this.dFear.faces = this.getFaces(faces); } get isCritical() { From 980cbc7ae067c65ff17725cedde1548f7c30048e Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 30 Mar 2026 21:40:06 -0400 Subject: [PATCH 6/8] Simplify fate roll type css class --- module/documents/chatMessage.mjs | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 884ba84f..307677bb 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -84,11 +84,8 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { } if (this.type === 'fateRoll') { html.classList.add('fate'); - if (this.system.roll?.fateDie == 'Hope') { - html.classList.add('hope'); - } - if (this.system.roll?.fateDie == 'Fear') { - html.classList.add('fear'); + if (this.system.roll?.fateDie) { + html.classList.add(this.system.roll.fateDie.toLowerCase()); } } From a7c4549e6f8ded87478a8be9f73fe474f8101b82 Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 30 Mar 2026 22:10:41 -0400 Subject: [PATCH 7/8] Add more caution to the dualityRoll fromData function --- module/dice/dualityRoll.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index f4cdeba2..291b8995 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -142,9 +142,9 @@ export default class DualityRoll extends D20Roll { static fromData(data) { data.terms[0].class = 'DualityDie'; data.terms[2].class = 'DualityDie'; - - if (data.options.roll.advantage?.type === 1) data.terms[4].class = 'AdvantageDie'; - else if (data.options.roll.advantage?.type === -1) data.terms[4].class = 'DisadvantageDie'; + if (data.options.roll.advantage?.type && data.terms[4]?.faces) { + data.terms[4].class = data.options.roll.advantage.type === 1 ? 'AdvantageDie' : 'DisadvantageDie'; + } return super.fromData(data); } From 766c1742d73c270fb325f9c7e4c43608365f263a Mon Sep 17 00:00:00 2001 From: Carlos Fernandez Date: Mon, 30 Mar 2026 22:15:40 -0400 Subject: [PATCH 8/8] Apply suggestion from @CarlosFdez --- module/applications/ui/chatLog.mjs | 1 - 1 file changed, 1 deletion(-) diff --git a/module/applications/ui/chatLog.mjs b/module/applications/ui/chatLog.mjs index 79f12e7f..fc826425 100644 --- a/module/applications/ui/chatLog.mjs +++ b/module/applications/ui/chatLog.mjs @@ -218,7 +218,6 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo isReaction: message.system.roll.options.actionType === 'reaction' } }); - // await message.system.roll._evaluate(); await message.update({ rolls: [message.system.roll.toJSON()] });