diff --git a/module/applications/dialogs/d20RollDialog.mjs b/module/applications/dialogs/d20RollDialog.mjs
index 34ca02cd..d8306923 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(this.selectedEffects && 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/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs
index 594269be..0c46f0a1 100644
--- a/module/applications/sheets/actors/character.mjs
+++ b/module/applications/sheets/actors/character.mjs
@@ -338,15 +338,20 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
const type = 'effect';
const cls = game.system.api.models.actions.actionsTypes[type];
- const action = new cls({
- ...cls.getSourceConfig(doc.system),
- type: type,
- chatDisplay: false,
- cost: [{
- key: 'stress',
- value: doc.system.recallCost
- }]
- }, { parent: doc.system });
+ const action = new cls(
+ {
+ ...cls.getSourceConfig(doc.system),
+ type: type,
+ chatDisplay: false,
+ cost: [
+ {
+ key: 'stress',
+ value: doc.system.recallCost
+ }
+ ]
+ },
+ { parent: doc.system }
+ );
const config = await action.use(event);
if (config) {
return doc.update({ 'system.inVault': false });
@@ -707,8 +712,10 @@ export default class CharacterSheet extends DHBaseActorSheet {
headerTitle: game.i18n.format('DAGGERHEART.UI.Chat.dualityRoll.abilityCheckTitle', {
ability: abilityLabel
}),
+ effects: Array.from(await this.document.allApplicableEffects()),
roll: {
- trait: button.dataset.attribute
+ trait: button.dataset.attribute,
+ type: 'trait'
},
hasRoll: true,
actionType: 'action',
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..f679d725 100644
--- a/module/dice/d20Roll.mjs
+++ b/module/dice/d20Roll.mjs
@@ -127,15 +127,55 @@ export default class D20Roll extends DHRoll {
const modifiers = foundry.utils.deepClone(this.options.roll.baseModifiers) ?? [];
modifiers.push(
- ...this.getBonus(`roll.${this.options.actionType}`, `${this.options.actionType?.capitalize()} Bonus`)
- );
- modifiers.push(
- ...this.getBonus(`roll.${this.options.roll.type}`, `${this.options.roll.type?.capitalize()} Bonus`)
+ ...this.getBonus(
+ `system.bonuses.roll.${this.options.actionType}`,
+ `${this.options.actionType?.capitalize()} Bonus`
+ )
);
+ if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
+ modifiers.push(
+ ...this.getBonus(
+ `system.bonuses.roll.${this.options.roll.type}`,
+ `${this.options.roll.type?.capitalize()} Bonus`
+ )
+ );
+ }
+
+ if (
+ this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
+ (this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
+ ) {
+ modifiers.push(
+ ...this.getBonus(`system.bonuses.roll.attack`, `${this.options.roll.type?.capitalize()} Bonus`)
+ );
+ }
+
return modifiers;
}
+ getActionChangeKeys() {
+ const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
+
+ if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
+ changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
+ }
+
+ if (
+ this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
+ (this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
+ ) {
+ changeKeys.add(`system.bonuses.roll.attack`);
+ }
+
+ if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
+ if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
+ changeKeys.add('system.bonuses.roll.trait');
+ }
+
+ return changeKeys;
+ }
+
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..f1da7654 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,29 @@ export default class DamageRoll extends DHRoll {
return modifiers;
}
+ getActionChangeKeys() {
+ const type = this.options.messageType ?? (this.options.hasHealing ? 'healing' : 'damage');
+ const changeKeys = [];
+
+ for (const roll of this.options.roll) {
+ for (const damageType of roll.damageTypes) changeKeys.push(`system.bonuses.${type}.${damageType}`);
+ }
+
+ const item = this.data.parent.items?.get(this.options.source.item);
+ if (item) {
+ switch (item.type) {
+ case 'weapon':
+ if (!this.options.hasHealing)
+ ['primaryWeapon', 'secondaryWeapon'].forEach(w =>
+ changeKeys.push(`system.bonuses.damage.${w}`)
+ );
+ break;
+ }
+ }
+
+ return changeKeys;
+ }
+
constructFormula(config) {
this.options.roll.forEach((part, index) => {
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
@@ -143,7 +165,7 @@ export default class DamageRoll extends DHRoll {
}
if (config.isCritical && part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
- const total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
+ let total = part.roll.dice.reduce((acc, term) => acc + term._faces * term._number, 0);
if (total > 0) {
part.roll.terms.push(...this.formatModifier(total));
}
diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs
index ea24f238..1485085e 100644
--- a/module/dice/dhRoll.mjs
+++ b/module/dice/dhRoll.mjs
@@ -4,6 +4,7 @@ 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 +165,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 +192,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 +246,28 @@ export default class DHRoll extends Roll {
static temporaryModifierBuilder(config) {
return {};
}
+
+ bonusEffectBuilder() {
+ const changeKeys = this.getActionChangeKeys();
+ return (
+ this.options.effects?.reduce((acc, effect) => {
+ if (effect.changes.some(x => changeKeys.some(key => x.key.includes(key)))) {
+ acc[effect.id] = {
+ id: effect.id,
+ name: effect.name,
+ description: effect.description,
+ changes: effect.changes,
+ origEffect: effect,
+ selected: !effect.disabled
+ };
+ }
+
+ return acc;
+ }, {}) ?? []
+ );
+ }
+
+ getActionChangeKeys() {
+ return [];
+ }
}
diff --git a/module/dice/dualityRoll.mjs b/module/dice/dualityRoll.mjs
index d2e20213..9490292a 100644
--- a/module/dice/dualityRoll.mjs
+++ b/module/dice/dualityRoll.mjs
@@ -173,6 +173,34 @@ export default class DualityRoll extends D20Roll {
return modifiers;
}
+ getActionChangeKeys() {
+ const changeKeys = new Set([`system.bonuses.roll.${this.options.actionType}`]);
+
+ if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.attack.id) {
+ changeKeys.add(`system.bonuses.roll.${this.options.roll.type}`);
+ }
+
+ if (
+ this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.attack.id ||
+ (this.options.roll.type === CONFIG.DH.GENERAL.rollTypes.spellcast.id && this.options.hasDamage)
+ ) {
+ changeKeys.add(`system.bonuses.roll.attack`);
+ }
+
+ if (this.options.roll.trait && this.data.traits?.[this.options.roll.trait]) {
+ if (this.options.roll.type !== CONFIG.DH.GENERAL.rollTypes.spellcast.id)
+ changeKeys.add('system.bonuses.roll.trait');
+ }
+
+ const weapons = ['primaryWeapon', 'secondaryWeapon'];
+ weapons.forEach(w => {
+ if (this.options.source.item && this.options.source.item === this.data[w]?.id)
+ changeKeys.add(`system.bonuses.roll.${w}`);
+ });
+
+ return changeKeys;
+ }
+
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}}