diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs index d872a1f8..099b2d66 100644 --- a/module/applications/dialogs/d20RollDialog.mjs +++ b/module/applications/dialogs/d20RollDialog.mjs @@ -10,6 +10,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.config = config; this.config.experiences = []; this.reactionOverride = config.actionType === 'reaction'; + this.selectedEffects = this.config.bonusEffects; if (config.source?.action) { this.item = config.data.parent.items.get(config.source.item) ?? config.data.parent; @@ -35,6 +36,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio selectExperience: this.selectExperience, toggleReaction: this.toggleReaction, toggleTagTeamRoll: this.toggleTagTeamRoll, + toggleSelectedEffect: this.toggleSelectedEffect, submitRoll: this.submitRoll }, form: { @@ -76,6 +78,9 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio icon })); + context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length); + context.selectedEffects = this.selectedEffects; + this.config.costs ??= []; if (this.config.costs?.length) { const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call( @@ -208,6 +213,11 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio this.render(); } + static toggleSelectedEffect(_event, button) { + this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected; + this.render(); + } + static async submitRoll() { await this.close({ submitted: true }); } diff --git a/module/applications/dialogs/damageDialog.mjs b/module/applications/dialogs/damageDialog.mjs index fbc584e4..b24570cc 100644 --- a/module/applications/dialogs/damageDialog.mjs +++ b/module/applications/dialogs/damageDialog.mjs @@ -6,6 +6,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application this.roll = roll; this.config = config; + this.selectedEffects = this.config.bonusEffects; } static DEFAULT_OPTIONS = { @@ -20,6 +21,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application icon: 'fa-solid fa-dice' }, actions: { + toggleSelectedEffect: this.toggleSelectedEffect, submitRoll: this.submitRoll }, form: { @@ -57,6 +59,9 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application icon })); context.modifiers = this.config.modifiers; + context.hasSelectedEffects = Boolean(Object.keys(this.selectedEffects).length); + context.selectedEffects = this.selectedEffects; + return context; } @@ -69,6 +74,11 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application this.render(); } + static toggleSelectedEffect(_event, button) { + this.selectedEffects[button.dataset.key].selected = !this.selectedEffects[button.dataset.key].selected; + this.render(); + } + static async submitRoll() { await this.close({ submitted: true }); } diff --git a/module/data/action/baseAction.mjs b/module/data/action/baseAction.mjs index 18a09904..a935468a 100644 --- a/module/data/action/baseAction.mjs +++ b/module/data/action/baseAction.mjs @@ -197,6 +197,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel let config = this.prepareConfig(event); if (!config) return; + await this.addEffects(config); + if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return; // Display configuration window if necessary @@ -263,6 +265,16 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel return config; } + /** */ + async addEffects(config) { + let effects = []; + if (this.actor) { + effects = Array.from(await this.actor.allApplicableEffects()); + } + + config.effects = effects; + } + /** * Method used to know if a configuration dialog must be shown or not when there is no roll. * @param {*} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. diff --git a/module/dice/d20Roll.mjs b/module/dice/d20Roll.mjs index 0256f281..5ca5f4f2 100644 --- a/module/dice/d20Roll.mjs +++ b/module/dice/d20Roll.mjs @@ -136,6 +136,13 @@ export default class D20Roll extends DHRoll { return modifiers; } + bonusEffectBuilder(config) { + const changeKeys = [`roll.${this.options.actionType}`, `roll.${this.options.roll.type}`]; + config.bonusEffects = foundry.utils.deepClone( + config.effects.filter(x => x.changes.some(x => changeKeys.includes(x.key))) + ); + } + static postEvaluate(roll, config = {}) { const data = super.postEvaluate(roll, config); data.type = config.actionType; diff --git a/module/dice/damageRoll.mjs b/module/dice/damageRoll.mjs index c10ee6ff..7aaf31ba 100644 --- a/module/dice/damageRoll.mjs +++ b/module/dice/damageRoll.mjs @@ -93,7 +93,6 @@ export default class DamageRoll extends DHRoll { type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'), options = part ?? this.options; - modifiers.push(...this.getBonus(`${type}`, `${type.capitalize()} Bonus`)); if (!this.options.hasHealing) { options.damageTypes?.forEach(t => { modifiers.push(...this.getBonus(`${type}.${t}`, `${t.capitalize()} ${type.capitalize()} Bonus`)); @@ -108,6 +107,25 @@ export default class DamageRoll extends DHRoll { return modifiers; } + bonusEffectBuilder() { + const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage'); + + return this.options.effects.reduce((acc, effect) => { + if (effect.changes.some(x => x.key.includes(`system.bonuses.${type}`))) { + acc[effect.id] = { + id: effect.id, + name: effect.name, + description: effect.description, + changes: effect.changes, + origEffect: effect, + selected: !effect.disabled + }; + } + + return acc; + }, {}); + } + constructFormula(config) { this.options.roll.forEach((part, index) => { part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data)); diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs index ea24f238..5133f2c8 100644 --- a/module/dice/dhRoll.mjs +++ b/module/dice/dhRoll.mjs @@ -1,9 +1,11 @@ import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; +import { BonusFields } from '../data/actor/character.mjs'; export default class DHRoll extends Roll { baseTerms = []; constructor(formula, data = {}, options = {}) { super(formula, data, options); + options.bonusEffects = this.bonusEffectBuilder(); if (!this.data || !Object.keys(this.data).length) this.data = options.data; } @@ -164,12 +166,18 @@ export default class DHRoll extends Roll { new foundry.dice.terms.OperatorTerm({ operator: '+' }), ...this.constructor.parse(modifier.join(' + '), this.options.data) ]; - } else { + } else if (Number.isNumeric(modifier)) { const numTerm = modifier < 0 ? '-' : '+'; return [ new foundry.dice.terms.OperatorTerm({ operator: numTerm }), new foundry.dice.terms.NumericTerm({ number: Math.abs(modifier) }) ]; + } else { + const numTerm = modifier < 0 ? '-' : '+'; + return [ + new foundry.dice.terms.OperatorTerm({ operator: numTerm }), + ...this.constructor.parse(modifier, this.options.data) + ]; } } @@ -185,18 +193,22 @@ export default class DHRoll extends Roll { } getBonus(path, label) { - const bonus = foundry.utils.getProperty(this.data.bonuses, path), - modifiers = []; - if (bonus?.bonus) - modifiers.push({ - label: label, - value: bonus?.bonus - }); - if (bonus?.dice?.length) - modifiers.push({ - label: label, - value: bonus?.dice - }); + const modifiers = []; + for (const effect of Object.values(this.options.bonusEffects)) { + if (effect.selected) { + for (const change of effect.changes) { + if (change.key.includes(path)) { + const changeValue = game.system.api.documents.DhActiveEffect.getChangeValue( + this.data, + change, + effect.origEffect + ); + modifiers.push({ label: label, value: changeValue }); + } + } + } + } + return modifiers; } @@ -235,4 +247,8 @@ export default class DHRoll extends Roll { static temporaryModifierBuilder(config) { return {}; } + + bonusEffectBuilder() { + return []; + } } diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs index d2e20213..8c82f6b1 100644 --- a/module/dice/dualityRoll.mjs +++ b/module/dice/dualityRoll.mjs @@ -173,6 +173,23 @@ export default class DualityRoll extends D20Roll { return modifiers; } + bonusEffectBuilder() { + return this.options.effects.reduce((acc, effect) => { + if (effect.changes.some(x => x.key.includes(`system.bonuses.roll`))) { + acc[effect.id] = { + id: effect.id, + name: effect.name, + description: effect.description, + changes: effect.changes, + origEffect: effect, + selected: !effect.disabled + }; + } + + return acc; + }, {}); + } + static async buildEvaluate(roll, config = {}, message = {}) { await super.buildEvaluate(roll, config, message); diff --git a/module/documents/activeEffect.mjs b/module/documents/activeEffect.mjs index 2297ea27..c5abb77c 100644 --- a/module/documents/activeEffect.mjs +++ b/module/documents/activeEffect.mjs @@ -106,23 +106,29 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect { /**@inheritdoc*/ static applyField(model, change, field) { - const isOriginTarget = change.value.toLowerCase().includes('origin.@'); + change.value = DhActiveEffect.getChangeValue(model, change, change.effect); + super.applyField(model, change, field); + } + + /** */ + static getChangeValue(model, change, effect) { + let value = change.value; + const isOriginTarget = value.toLowerCase().includes('origin.@'); let parseModel = model; - if (isOriginTarget && change.effect.origin) { - change.value = change.value.replaceAll(/origin\.@/gi, '@'); + if (isOriginTarget && effect.origin) { + value = change.value.replaceAll(/origin\.@/gi, '@'); try { - const effect = foundry.utils.fromUuidSync(change.effect.origin); + const originEffect = foundry.utils.fromUuidSync(effect.origin); const doc = - effect.parent?.parent instanceof game.system.api.documents.DhpActor - ? effect.parent - : effect.parent.parent; + originEffect.parent?.parent instanceof game.system.api.documents.DhpActor + ? originEffect.parent + : originEffect.parent.parent; if (doc) parseModel = doc; } catch (_) {} } - const evalValue = this.effectSafeEval(itemAbleRollParse(change.value, parseModel, change.effect.parent)); - change.value = evalValue ?? change.value; - super.applyField(model, change, field); + const evalValue = this.effectSafeEval(itemAbleRollParse(value, parseModel, effect.parent)); + return evalValue ?? value; } /** diff --git a/module/documents/chatMessage.mjs b/module/documents/chatMessage.mjs index 7e313891..d85bcb45 100644 --- a/module/documents/chatMessage.mjs +++ b/module/documents/chatMessage.mjs @@ -157,7 +157,10 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage { event.stopPropagation(); const config = foundry.utils.deepClone(this.system); config.event = event; - await this.system.action?.workflow.get('damage')?.execute(config, this._id, true); + if (this.system.action) { + await this.system.action.addEffects(config); + await this.system.action.workflow.get('damage')?.execute(config, this._id, true); + } Hooks.callAll(socketEvent.Refresh, { refreshType: RefreshType.TagTeamRoll }); await game.socket.emit(`system.${CONFIG.DH.id}`, { diff --git a/styles/less/global/dialog.less b/styles/less/global/dialog.less index 8c532c2b..a3400700 100644 --- a/styles/less/global/dialog.less +++ b/styles/less/global/dialog.less @@ -67,6 +67,35 @@ } } + .dialog-selection-container { + display: flex; + gap: 10px; + flex-wrap: wrap; + + .selection-chip { + display: flex; + align-items: center; + border-radius: 5px; + width: fit-content; + gap: 5px; + cursor: pointer; + padding: 5px; + background: light-dark(@dark-blue-10, @golden-10); + color: light-dark(@dark-blue, @golden); + + .label { + font-style: normal; + font-weight: 400; + font-size: var(--font-size-14); + line-height: 17px; + } + + &.selected { + background: light-dark(@dark-blue-40, @golden-40); + } + } + } + .standard-form { font-family: @font-body; } diff --git a/templates/dialogs/dice-roll/damageSelection.hbs b/templates/dialogs/dice-roll/damageSelection.hbs index ba542666..c0dbae62 100644 --- a/templates/dialogs/dice-roll/damageSelection.hbs +++ b/templates/dialogs/dice-roll/damageSelection.hbs @@ -2,6 +2,20 @@

{{title}}

+ + {{#if hasSelectedEffects}} +
+ {{localize "DAGGERHEART.GENERAL.Effect.plural"}} + + {{#each selectedEffects as |effect id|}} +
+ + {{effect.name}} +
+ {{/each}} +
+ {{/if}} + {{#each @root.formula}}
{{localize "DAGGERHEART.GENERAL.formula"}}: {{roll.formula}} diff --git a/templates/dialogs/dice-roll/rollSelection.hbs b/templates/dialogs/dice-roll/rollSelection.hbs index c7a9b0f9..0e51b174 100644 --- a/templates/dialogs/dice-roll/rollSelection.hbs +++ b/templates/dialogs/dice-roll/rollSelection.hbs @@ -70,6 +70,21 @@ {{/if}}
+ {{#if hasSelectedEffects}} +
+ {{localize "DAGGERHEART.GENERAL.Effect.plural"}} + +
+ {{#each selectedEffects as |effect id|}} +
+ + {{effect.name}} +
+ {{/each}} +
+
+ {{/if}} + {{#if experiences.length}}
{{localize "DAGGERHEART.GENERAL.experience.plural"}}