From 1172ab7d38502cf6d3fad713ccd1d760c5cd29ba Mon Sep 17 00:00:00 2001 From: juanferrer Date: Sun, 5 Nov 2023 21:33:43 +0000 Subject: [PATCH 01/12] Add token class to recolour damage bar (more red as it fills up) --- src/module/actor/token.js | 66 +++++++++++++++++++++++++++++++++++++++ src/module/demonlord.js | 2 ++ src/module/utils/utils.js | 8 +++++ 3 files changed, 76 insertions(+) create mode 100644 src/module/actor/token.js diff --git a/src/module/actor/token.js b/src/module/actor/token.js new file mode 100644 index 00000000..0247cf34 --- /dev/null +++ b/src/module/actor/token.js @@ -0,0 +1,66 @@ +/* globals Color */ + +import {MapRange} from '../utils/utils.js' + +// Shamelessly stolen from Shadow of the Weird Wizard +export class DemonlordToken extends Token { + + static getDamageColor(current, max) { + const minDegrees = 30 + const maxDegrees = 120 + + // Get the degrees on the HSV wheel, going from 30º (green) to 120º (red) + const degrees = MapRange(current, 0, max, minDegrees, maxDegrees) + // Invert the degrees and map them from 0 to a third + const hue = MapRange(maxDegrees - degrees, 0, maxDegrees, 0, 1 / 3) + + return Color.fromHSV([hue, 1, 0.9]) + } + + _drawBar(number, bar, data) { + if (data?.attribute === 'characteristics.health') { + return this._drawDamageBar(number, bar, data) + } + + return super._drawBar(number, bar, data) + } + + _drawDamageBar(number, bar, data) { + const { value, max } = data + const colorPct = Math.clamped(value, 0, max) / max + const damageColor = DemonlordToken.getDamageColor(value, max) + + // Determine the container size (logic borrowed from core) + const w = this.w + let h = Math.max(canvas.dimensions.size / 12, 8) + if (this.document.height >= 2) + h *= 1.6 + const stroke = Math.clamped(h / 8, 1, 2) + + // Set up bar container + this._resetVitalsBar(bar, w, h, stroke) + + // Fill bar as damage increases, gradually going from green to red as it fills + bar + .beginFill(damageColor, 1.0) + .lineStyle(stroke, this.blk, 1.0) + .drawRoundedRect(0, 0, colorPct * w, h, 2) + + // Position the bar according to its number + this._setVitalsBarPosition(bar, number, h) + } + + _resetVitalsBar(bar, width, height, stroke) { + bar + .clear() + .beginFill(this.blk, 0.5) + .lineStyle(stroke, this.blk, 1.0) + .drawRoundedRect(0, 0, width, height, 3) + } + + _setVitalsBarPosition(bar, order, height) { + // Set position + const posY = order === 0 ? this.h - height : 0 + bar.position.set(0, posY) + } +} \ No newline at end of file diff --git a/src/module/demonlord.js b/src/module/demonlord.js index 4d41b4ed..731c643f 100644 --- a/src/module/demonlord.js +++ b/src/module/demonlord.js @@ -1,6 +1,7 @@ // Import Modules import {DL} from './config.js' import {DemonlordActor} from './actor/actor.js' +import {DemonlordToken} from './actor/token.js' import {DemonlordItem} from './item/item.js' import {ActionTemplate} from './pixi/action-template.js' import {registerSettings} from './settings.js' @@ -52,6 +53,7 @@ Hooks.once('init', async function () { // Define custom Entity classes CONFIG.DL = DL CONFIG.Actor.documentClass = DemonlordActor + CONFIG.Token.objectClass = DemonlordToken CONFIG.Item.documentClass = DemonlordItem DocumentSheetConfig.registerSheet(ActiveEffect, "demonlord", DLActiveEffectConfig, {makeDefault: true}) CONFIG.ui.combat = DLCombatTracker diff --git a/src/module/utils/utils.js b/src/module/utils/utils.js index 31f7ca6e..8481563f 100644 --- a/src/module/utils/utils.js +++ b/src/module/utils/utils.js @@ -48,6 +48,14 @@ export async function enrichHTMLUnrolled(content, {rollData, secrets, rolls, ent return pcontent; } +/** Maps a number from a given range to an equivalent number of another range */ +export function MapRange(num, inMin, inMax, outMin, outMax) { + if (inMin === inMax || outMin === outMax) + return 0; + const mapped = ((num - inMin) * (outMax - outMin)) / (inMax - inMin) + outMin; + return Math.clamped(mapped, outMin, outMax); +} + // ----------------------------------------------- From 6507f82361719308004db4702d8e8b00e8187ef9 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Sun, 5 Nov 2023 22:44:54 +0000 Subject: [PATCH 02/12] Enrich End of the Round dialog descriptions --- src/module/dialog/endofround.js | 15 +++++++++++---- src/templates/dialogs/endofround-dialog.hbs | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/module/dialog/endofround.js b/src/module/dialog/endofround.js index 112fac51..09cc826a 100644 --- a/src/module/dialog/endofround.js +++ b/src/module/dialog/endofround.js @@ -22,25 +22,32 @@ export class DLEndOfRound extends FormApplication { * Construct and return the data object used to render the HTML template for this form application. * @return {Object} */ - getData() { + async getData() { let creatures = {} const currentCombat = game.combat const combatants = Array.from(currentCombat.combatants?.values()) || [] - combatants + await combatants .filter(combatant => !combatant.defeated && combatant.actor.type === 'creature') .filter(combatant => combatant.actor.items.filter(i => i.type === 'endoftheround').length > 0) .sort((a, b) => (a.initiative > b.initiative ? -1 : 1)) - .forEach((combatant, index) => { + .forEach(async (combatant, index) => { creatures[index] = { tokenActorId: combatant.token.actorId, initiative: combatant.initiative, actor: combatant.actor, token: combatant.token, - endOfRoundEffects: combatant.actor.items.filter(i => i.type === 'endoftheround'), + endOfRoundEffects: await combatant.actor.items.filter(i => i.type === 'endoftheround'), } }) + // Enrich descriptions + for (const creature of Object.values(creatures)) { + creature.endOfRoundEffects.forEach(async e => { + e.system.enrichedDescription = await TextEditor.enrichHTML(e.system.description) + }) + } + return { creatures } } /* -------------------------------------------- */ diff --git a/src/templates/dialogs/endofround-dialog.hbs b/src/templates/dialogs/endofround-dialog.hbs index 042ae920..75fb1e77 100644 --- a/src/templates/dialogs/endofround-dialog.hbs +++ b/src/templates/dialogs/endofround-dialog.hbs @@ -17,7 +17,7 @@ {{#each creature.endOfRoundEffects as |endOfRoundEffect id|}}
  • {{endOfRoundEffect.name}}
    -
    {{{endOfRoundEffect.system.description}}}
    +
    {{{endOfRoundEffect.system.enrichedDescription}}}
  • {{/each}} From e2349ff6f415e0e7d7e42a2b00ad2e5b6dc195b4 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 16:22:18 +0000 Subject: [PATCH 03/12] Correctly show input boons in chat message --- src/lang/en.json | 1 + src/module/actor/actor.js | 12 ++++++------ src/module/chat/effect-messages.js | 13 +++++++++---- src/module/chat/roll-messages.js | 21 ++++++++++----------- 4 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/lang/en.json b/src/lang/en.json index a0e20e5a..26799fce 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -357,6 +357,7 @@ "DL.LanguagesReadShort": "R", "DL.LanguagesWriteShort": "W", "DL.LanguagesSpeakShort": "S", + "DL.DialogInput": "Dialog Input", "DL.DialogChallengeRoll": "Challenge Roll: ", "DL.DialogAttackRoll": "Attack Roll: ", "DL.DialogTalentRoll": "Talent Roll: ", diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index f459686a..29f6c06c 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -366,7 +366,7 @@ export class DemonlordActor extends Actor { const attackRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) attackRoll.evaluate({async: false}) - postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute) + postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, inputBoons) const hitTargets = defendersTokens.filter(d => { const targetNumber = @@ -416,7 +416,7 @@ export class DemonlordActor extends Actor { const challengeRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) challengeRoll.evaluate({async: false}) - postAttributeToChat(this, attribute, challengeRoll) + postAttributeToChat(this, attribute, challengeRoll, inputBoons) } rollChallenge(attribute) { @@ -486,7 +486,7 @@ export class DemonlordActor extends Actor { itemId: talent.id, }) - postTalentToChat(this, talent, attackRoll, target?.actor) + postTalentToChat(this, talent, attackRoll, target?.actor, inputBoons) } /* -------------------------------------------- */ @@ -556,7 +556,7 @@ export class DemonlordActor extends Actor { itemId: spell.id, }) - postSpellToChat(this, spell, attackRoll, target?.actor) + postSpellToChat(this, spell, attackRoll, target?.actor, inputBoons) } /* -------------------------------------------- */ @@ -590,7 +590,7 @@ export class DemonlordActor extends Actor { let attackRoll = null if (!itemData?.action?.attack) { - postItemToChat(this, item, null, null) + postItemToChat(this, item, null, null, null) return } else { const attackAttribute = itemData.action.attack.toLowerCase() @@ -610,7 +610,7 @@ export class DemonlordActor extends Actor { attackRoll.evaluate({async: false}) } - postItemToChat(this, item, attackRoll, target?.actor) + postItemToChat(this, item, attackRoll, target?.actor, inputBoons) } /* -------------------------------------------- */ diff --git a/src/module/chat/effect-messages.js b/src/module/chat/effect-messages.js index 8f4081c8..5a00390a 100644 --- a/src/module/chat/effect-messages.js +++ b/src/module/chat/effect-messages.js @@ -44,7 +44,8 @@ const changeListToMsg = (m, keys, title, f=plusify) => { if (m.has(key)) changes.push(m.get(key)) }) - return changes.flat(Infinity).reduce((acc, change) => acc + _toMsg(change.name, f(change.value)), title) + if (changes.length > 0) return changes.flat(Infinity).reduce((acc, change) => acc + _toMsg(change.name, f(change.value)), title) + return '' } /* -------------------------------------------- */ @@ -61,13 +62,14 @@ const changeListToMsg = (m, keys, title, f=plusify) => { * @param defenseAttribute * @returns {*} */ -export function buildAttackEffectsMessage(attacker, defender, item, attackAttribute, defenseAttribute) { +export function buildAttackEffectsMessage(attacker, defender, item, attackAttribute, defenseAttribute, inputBoons) { const attackerEffects = attacker.getEmbeddedCollection('ActiveEffect').filter(effect => !effect.disabled) let m = _remapEffects(attackerEffects) let defenderBoons = (defender?.system.bonuses.defense.boons[defenseAttribute] || 0) + (defender?.system.bonuses.defense.boons.all || 0) const defenderString = defender?.name + ' [' + game.i18n.localize('DL.SpellTarget') + ']' let otherBoons = '' + let inputBoonsMsg = (inputBoons && inputBoons !== "0") ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' let itemBoons switch (item.type) { case 'spell': @@ -98,6 +100,7 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib return ( boonsMsg + + inputBoonsMsg + changeToMsg(m, 'system.bonuses.attack.damage', 'DL.TalentExtraDamage') + changeToMsg(m, 'system.bonuses.attack.plus20Damage', 'DL.TalentExtraDamage20plus') ) @@ -112,11 +115,13 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib * @param attribute * @returns {string} */ -export function buildAttributeEffectsMessage(actor, attribute) { +export function buildAttributeEffectsMessage(actor, attribute, inputBoons) { const actorEffects = actor?.getEmbeddedCollection('ActiveEffect').filter(effect => !effect.disabled) let m = _remapEffects(actorEffects) + let inputBoonsMsg = (inputBoons && inputBoons !== "0") ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' let result = '' - result += changeListToMsg(m, [`system.bonuses.challenge.boons.${attribute}`, 'system.bonuses.challenge.boons.all' ], 'DL.TalentChallengeBoonsBanes') + result += changeListToMsg(m, [`system.bonuses.challenge.boons.${attribute}`, 'system.bonuses.challenge.boons.all' ], 'DL.TalentChallengeBoonsBanes') + + inputBoonsMsg return result } diff --git a/src/module/chat/roll-messages.js b/src/module/chat/roll-messages.js index d437eace..26077631 100644 --- a/src/module/chat/roll-messages.js +++ b/src/module/chat/roll-messages.js @@ -10,7 +10,7 @@ import {buildActorInfo, formatDice, getChatBaseData} from './base-messages' * @param attackAttribute string (lowercase) * @param defenseAttribute stromg (lowercase) */ -export function postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute) { +export function postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, inputBoons) { const itemData = item.system const rollMode = game.settings.get('core', 'rollMode') @@ -75,7 +75,7 @@ export function postAttackToChat(attacker, defender, item, attackRoll, attackAtt data['isPlus20Roll'] = plus20 data['hasTarget'] = targetNumber !== undefined data['effects'] = attacker.system.bonuses.attack.extraEffect - data['attackEffects'] = buildAttackEffectsMessage(attacker, defender, item, attackAttribute, defenseAttribute) + data['attackEffects'] = buildAttackEffectsMessage(attacker, defender, item, attackAttribute, defenseAttribute, inputBoons) data['armorEffects'] = '' // TODO data['afflictionEffects'] = '' //TODO data['ifBlindedRoll'] = rollMode === 'blindroll' @@ -104,7 +104,7 @@ export function postAttackToChat(attacker, defender, item, attackRoll, attackAtt * @param attribute string (lowercase) * @param challengeRoll Roll */ -export function postAttributeToChat(actor, attribute, challengeRoll) { +export function postAttributeToChat(actor, attribute, challengeRoll, inputBoons) { const rollMode = game.settings.get('core', 'rollMode') const voidRoll = actor.getAttribute(attribute)?.immune @@ -125,7 +125,6 @@ export function postAttributeToChat(actor, attribute, challengeRoll) { diceData: formatDice(challengeRoll), data: {}, } - const effects = buildAttributeEffectsMessage(actor, attribute) const data = templateData.data data['diceTotal'] = voidRoll ? '-' : diceTotal @@ -134,7 +133,7 @@ export function postAttributeToChat(actor, attribute, challengeRoll) { data['resultTextGM'] = resultTextGM data['resultBoxClass'] = resultBoxClass data['isCreature'] = actor.type === 'creature' - data['actionEffects'] = effects + data['actionEffects'] = buildAttributeEffectsMessage(actor, attribute, inputBoons) data['ifBlindedRoll'] = rollMode === 'blindroll' data['actorInfo'] = buildActorInfo(actor) @@ -160,7 +159,7 @@ export function postAttributeToChat(actor, attribute, challengeRoll) { * @param attackRoll Roll * @param target DemonlordActor */ -export function postTalentToChat(actor, talent, attackRoll, target) { +export function postTalentToChat(actor, talent, attackRoll, target, inputBoons) { const talentData = talent.system const rollMode = game.settings.get('core', 'rollMode') @@ -237,7 +236,7 @@ export function postTalentToChat(actor, talent, attackRoll, target) { data['hasTarget'] = targetNumber !== undefined data['pureDamage'] = talentData?.damage data['pureDamageType'] = talentData?.damagetype - data['attackEffects'] = buildAttackEffectsMessage(actor, target, talent, attackAttribute, defenseAttribute) + data['attackEffects'] = buildAttackEffectsMessage(actor, target, talent, attackAttribute, defenseAttribute, inputBoons) data['effects'] = buildTalentEffectsMessage(actor, talent) data['ifBlindedRoll'] = rollMode === 'blindroll' data['hasAreaTarget'] = talentData?.activatedEffect?.target?.type in CONFIG.DL.actionAreaShape @@ -270,7 +269,7 @@ export function postTalentToChat(actor, talent, attackRoll, target) { * @param attackRoll * @param target */ -export function postSpellToChat(actor, spell, attackRoll, target) { +export function postSpellToChat(actor, spell, attackRoll, target, inputBoons) { const spellData = spell.system const rollMode = game.settings.get('core', 'rollMode') @@ -362,7 +361,7 @@ export function postSpellToChat(actor, spell, attackRoll, target) { data['isPlus20Roll'] = plus20 data['effectdice'] = effectdice data['effects'] = '' // FIXME: what to put in here?? - data['attackEffects'] = buildAttackEffectsMessage(actor, target, spell, attackAttribute, defenseAttribute) + data['attackEffects'] = buildAttackEffectsMessage(actor, target, spell, attackAttribute, defenseAttribute, inputBoons) data['ifBlindedRoll'] = rollMode === 'blindroll' data['hasAreaTarget'] = spellData?.activatedEffect?.target?.type in CONFIG.DL.actionAreaShape data['itemEffects'] = spell.effects @@ -436,7 +435,7 @@ export async function postCorruptionToChat(actor, corruptionRoll) { } } -export const postItemToChat = (actor, item, attackRoll, target) => { +export const postItemToChat = (actor, item, attackRoll, target, inputBoons) => { const itemData = item.system const rollMode = game.settings.get('core', 'rollMode') @@ -519,7 +518,7 @@ export const postItemToChat = (actor, item, attackRoll, target) => { data['isPlus20Roll'] = plus20 data['hasTarget'] = targetNumber !== undefined data['effects'] = actor.system.bonuses.attack.extraEffect - data['attackEffects'] = buildAttackEffectsMessage(actor, target, item, attackAttribute, defenseAttribute) + data['attackEffects'] = buildAttackEffectsMessage(actor, target, item, attackAttribute, defenseAttribute, inputBoons) data['armorEffects'] = '' // TODO data['afflictionEffects'] = '' //TODO data['ifBlindedRoll'] = rollMode === 'blindroll' From 4fd1069af87ab0175dca158c8c034577d6fdffc9 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 16:37:31 +0000 Subject: [PATCH 04/12] Show roll modifiers individually in chat message --- src/module/actor/actor.js | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 29f6c06c..20afa1f3 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -309,8 +309,11 @@ export class DemonlordActor extends Actor { /* Rolls and Actions */ /* -------------------------------------------- */ - rollFormula(mod, boba, bobaRerolls) { - let rollFormula = '1d20' + plusify(mod) + rollFormula(mods, boba, bobaRerolls) { + let rollFormula = '1d20' + for (const mod of mods) { + rollFormula += plusify(mod) + } if (boba > 0 && parseInt(bobaRerolls) > 0) rollFormula += `+${boba}d6r1kh` else if (boba) rollFormula += plusify(boba) + 'd6kh' console.log(rollFormula) @@ -341,8 +344,7 @@ export class DemonlordActor extends Actor { // if !target -> ui.notifications.warn(Please select target) ?? // Attack modifier and Boons/Banes - const modifier = - (attacker.system?.attributes[attackAttribute]?.modifier || 0) + (parseInt(inputModifier) || 0) + const modifiers = [attacker.system?.attributes[attackAttribute]?.modifier || 0, parseInt(inputModifier) || 0] let boons = (parseInt(item.system.action.boonsbanes) || 0) + (parseInt(inputBoons) || 0) + @@ -363,7 +365,7 @@ export class DemonlordActor extends Actor { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) // Roll the attack - const attackRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) + const attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) attackRoll.evaluate({async: false}) postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, inputBoons) @@ -410,11 +412,11 @@ export class DemonlordActor extends Actor { rollAttribute(attribute, inputBoons, inputModifier) { attribute = attribute.label.toLowerCase() - const modifier = parseInt(inputModifier) + (this.getAttribute(attribute)?.modifier || 0) + const modifiers = [parseInt(inputModifier), this.getAttribute(attribute)?.modifier || 0] const boons = parseInt(inputBoons) + (this.system.bonuses.challenge.boons[attribute] || 0) + (this.system.bonuses.challenge.boons.all || 0) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - const challengeRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) + const challengeRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) challengeRoll.evaluate({async: false}) postAttributeToChat(this, attribute, challengeRoll, inputBoons) } @@ -466,7 +468,7 @@ export class DemonlordActor extends Actor { const attackAttribute = talentData.action.attack.toLowerCase() const defenseAttribute = talentData.action?.attack?.toLowerCase() - let modifier = parseInt(inputModifier) + (this.getAttribute(attackAttribute)?.modifier || 0) + let modifiers = [parseInt(inputModifier), this.getAttribute(attackAttribute)?.modifier || 0] let boons = (parseInt(inputBoons) || 0) + @@ -476,7 +478,7 @@ export class DemonlordActor extends Actor { if (targets.length === 1) boons -= ((target?.actor?.system.bonuses.defense.boons[defenseAttribute] || 0) + (target?.actor?.system.bonuses.defense.boons.all || 0)) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - attackRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) + attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) attackRoll.evaluate({async: false}) } @@ -543,10 +545,10 @@ export class DemonlordActor extends Actor { (target?.actor?.system.bonuses.defense.boons.all || 0) + (target?.actor?.system.bonuses.defense.boons.spell || 0) - const modifier = (parseInt(inputModifier) || 0) + this.getAttribute(attackAttribute).modifier || 0 + const modifiers = [parseInt(inputModifier) || 0, this.getAttribute(attackAttribute).modifier || 0] const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - attackRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) + attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) attackRoll.evaluate({async: false}) } @@ -596,7 +598,7 @@ export class DemonlordActor extends Actor { const attackAttribute = itemData.action.attack.toLowerCase() const defenseAttribute = itemData.action?.attack?.toLowerCase() - let modifier = parseInt(inputModifier) + (this.getAttribute(attackAttribute)?.modifier || 0) + let modifiers = [parseInt(inputModifier), (this.getAttribute(attackAttribute)?.modifier || 0)] let boons = parseInt(inputBoons) + @@ -606,7 +608,7 @@ export class DemonlordActor extends Actor { if (targets.length === 1) boons -= ((target?.actor?.system.bonuses.defense.boons[defenseAttribute] || 0) + (target?.actor?.system.bonuses.defense.boons.all || 0)) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) - attackRoll = new Roll(this.rollFormula(modifier, boons, boonsReroll), {}) + attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) attackRoll.evaluate({async: false}) } From acc984a9ee33edb1188cfe3def8ee37f065d8511 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 16:38:04 +0000 Subject: [PATCH 05/12] Make roll evaluation async --- src/module/actor/actor.js | 28 ++++++++++----------- src/module/actor/sheets/base-actor-sheet.js | 6 ++--- src/module/actor/sheets/character-sheet.js | 2 +- src/module/chat/chat-listeners.js | 4 +-- src/module/chat/roll-messages.js | 4 +-- src/module/macros/item-macros.js | 4 +-- src/module/macros/player-macros.js | 12 ++++----- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 20afa1f3..220f386c 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -326,7 +326,7 @@ export class DemonlordActor extends Actor { * @param inputBoons Number of boons/banes from the user dialog * @param inputModifier Attack modifier from the user dialog */ - rollAttack(item, inputBoons = 0, inputModifier = 0) { + async rollAttack(item, inputBoons = 0, inputModifier = 0) { const attacker = this const defendersTokens = tokenManager.targets const defender = defendersTokens[0]?.actor @@ -366,7 +366,7 @@ export class DemonlordActor extends Actor { // Roll the attack const attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) - attackRoll.evaluate({async: false}) + await attackRoll.evaluate() postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, inputBoons) @@ -391,7 +391,7 @@ export class DemonlordActor extends Actor { * @param itemID The id of the item * @param _options Additional options */ - rollWeaponAttack(itemID, _options = {event: null}) { + async rollWeaponAttack(itemID, _options = {event: null}) { const item = this.getEmbeddedDocument('Item', itemID) // If no attribute to roll, roll without modifiers and boons @@ -403,21 +403,21 @@ export class DemonlordActor extends Actor { // Check if actor is blocked by an affliction if (!DLAfflictions.isActorBlocked(this, 'action', attribute)) - launchRollDialog(game.i18n.localize('DL.DialogAttackRoll') + game.i18n.localize(item.name), html => - this.rollAttack(item, html.find('[id="boonsbanes"]').val(), html.find('[id="modifier"]').val()), + launchRollDialog(game.i18n.localize('DL.DialogAttackRoll') + game.i18n.localize(item.name), async html => + await this.rollAttack(item, html.find('[id="boonsbanes"]').val(), html.find('[id="modifier"]').val()), ) } /* -------------------------------------------- */ - rollAttribute(attribute, inputBoons, inputModifier) { + async rollAttribute(attribute, inputBoons, inputModifier) { attribute = attribute.label.toLowerCase() const modifiers = [parseInt(inputModifier), this.getAttribute(attribute)?.modifier || 0] const boons = parseInt(inputBoons) + (this.system.bonuses.challenge.boons[attribute] || 0) + (this.system.bonuses.challenge.boons.all || 0) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) const challengeRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) - challengeRoll.evaluate({async: false}) + await challengeRoll.evaluate() postAttributeToChat(this, attribute, challengeRoll, inputBoons) } @@ -425,8 +425,8 @@ export class DemonlordActor extends Actor { if (typeof attribute === 'string' || attribute instanceof String) attribute = this.getAttribute(attribute) if (!DLAfflictions.isActorBlocked(this, 'challenge', attribute.label)) - launchRollDialog(this.name + ': ' + game.i18n.localize('DL.DialogChallengeRoll').slice(0, -2), html => - this.rollAttribute(attribute, html.find('[id="boonsbanes"]').val(), html.find('[id="modifier"]').val()), + launchRollDialog(this.name + ': ' + game.i18n.localize('DL.DialogChallengeRoll').slice(0, -2), async html => + await this.rollAttribute(attribute, html.find('[id="boonsbanes"]').val(), html.find('[id="modifier"]').val()), ) } @@ -479,7 +479,7 @@ export class DemonlordActor extends Actor { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) - attackRoll.evaluate({async: false}) + await attackRoll.evaluate() } Hooks.call('DL.UseTalent', { @@ -549,7 +549,7 @@ export class DemonlordActor extends Actor { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) - attackRoll.evaluate({async: false}) + await attackRoll.evaluate() } Hooks.call('DL.UseSpell', { @@ -609,7 +609,7 @@ export class DemonlordActor extends Actor { const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) - attackRoll.evaluate({async: false}) + await attackRoll.evaluate() } postItemToChat(this, item, attackRoll, target?.actor, inputBoons) @@ -617,9 +617,9 @@ export class DemonlordActor extends Actor { /* -------------------------------------------- */ - rollCorruption() { + async rollCorruption() { const corruptionRoll = new Roll('1d20') - corruptionRoll.evaluate({async: false}) + await corruptionRoll.evaluate() postCorruptionToChat(this, corruptionRoll) } diff --git a/src/module/actor/sheets/base-actor-sheet.js b/src/module/actor/sheets/base-actor-sheet.js index 971ca7fa..f4f84739 100644 --- a/src/module/actor/sheets/base-actor-sheet.js +++ b/src/module/actor/sheets/base-actor-sheet.js @@ -333,9 +333,9 @@ export default class DLBaseActorSheet extends ActorSheet { }) // Rollable Attack - html.find('.attack-roll').click(ev => { + html.find('.attack-roll').click(async ev => { const id = $(ev.currentTarget).closest('[data-item-id]').data('itemId') - this.actor.rollWeaponAttack(id, {event: ev}) + await this.actor.rollWeaponAttack(id, {event: ev}) }) // Rollable Talent @@ -368,7 +368,7 @@ export default class DLBaseActorSheet extends ActorSheet { if (dataset.roll) { const roll = new Roll(dataset.roll, this.actor.system) const label = dataset.label ? `Rolling ${dataset.label}` : '' - roll.roll({async: false}).toMessage({ + await roll.roll().toMessage({ speaker: ChatMessage.getSpeaker({actor: this.actor}), flavor: label, }) diff --git a/src/module/actor/sheets/character-sheet.js b/src/module/actor/sheets/character-sheet.js index 62aa059c..9473a8da 100644 --- a/src/module/actor/sheets/character-sheet.js +++ b/src/module/actor/sheets/character-sheet.js @@ -157,7 +157,7 @@ export default class DLCharacterSheet extends DLBaseActorSheet { if (!this.options.editable) return // Corruption Roll - html.find('.corruption-roll').click(_ => this.actor.rollCorruption()) + html.find('.corruption-roll').click(async _ => await this.actor.rollCorruption()) // Edit HealthBar, Insanity and Corruption html.find('.bar-edit').click(async () => { diff --git a/src/module/chat/chat-listeners.js b/src/module/chat/chat-listeners.js index e2cdf73b..13d77f5c 100644 --- a/src/module/chat/chat-listeners.js +++ b/src/module/chat/chat-listeners.js @@ -61,7 +61,7 @@ async function _onChatRollDamage(event) { const itemId = item.dataset.itemId || li.closest('.demonlord').dataset.itemId const damageRoll = new Roll(damageformula, {}) - damageRoll.evaluate({async: false}) + await damageRoll.evaluate() let totalDamage = '' let totalDamageGM = '' @@ -254,7 +254,7 @@ async function _onChatMakeChallengeRoll(event) { const start = li.closest('.demonlord') const boonsbanesEntered = start.children[1].children[0].children[0].children[1]?.value - actor.rollAttribute(attribute, parseInt(boonsbanes) + parseInt(boonsbanesEntered), 0) + await actor.rollAttribute(attribute, parseInt(boonsbanes) + parseInt(boonsbanesEntered), 0) } /* -------------------------------------------- */ diff --git a/src/module/chat/roll-messages.js b/src/module/chat/roll-messages.js index 26077631..f4595111 100644 --- a/src/module/chat/roll-messages.js +++ b/src/module/chat/roll-messages.js @@ -269,7 +269,7 @@ export function postTalentToChat(actor, talent, attackRoll, target, inputBoons) * @param attackRoll * @param target */ -export function postSpellToChat(actor, spell, attackRoll, target, inputBoons) { +export async function postSpellToChat(actor, spell, attackRoll, target, inputBoons) { const spellData = spell.system const rollMode = game.settings.get('core', 'rollMode') @@ -308,7 +308,7 @@ export function postSpellToChat(actor, spell, attackRoll, target, inputBoons) { let effectdice = '' if (spellData?.effectdice && spellData?.effectdice !== '') { const effectRoll = new Roll(spellData.effectdice, {}) - effectRoll.evaluate({async: false}) + await effectRoll.evaluate() effectdice = effectRoll.total } diff --git a/src/module/macros/item-macros.js b/src/module/macros/item-macros.js index 39c54b9e..b876d640 100644 --- a/src/module/macros/item-macros.js +++ b/src/module/macros/item-macros.js @@ -65,7 +65,7 @@ async function _createDemonlordItemMacro(item, slot) { * @param {string} itemName * @return {Promise} */ -export function rollWeaponMacro(itemName) { +export async function rollWeaponMacro(itemName) { const speaker = ChatMessage.getSpeaker() let actor if (speaker.token) actor = game.actors.tokens[speaker.token] @@ -75,7 +75,7 @@ export function rollWeaponMacro(itemName) { return ui.notifications.warn(`Your controlled Actor does not have an item named ${itemName}`) } - return actor.rollWeaponAttack(item.id) //FIXME: boons banes damage bonus ignored? + return await actor.rollWeaponAttack(item.id) //FIXME: boons banes damage bonus ignored? } /** diff --git a/src/module/macros/player-macros.js b/src/module/macros/player-macros.js index 814515fe..0a054477 100644 --- a/src/module/macros/player-macros.js +++ b/src/module/macros/player-macros.js @@ -3,16 +3,16 @@ export function makeChallengeRollMacro() { // Player Tool // Lets you choose with attributes you want to make challenge roll for and add bones/banes. - function makeRoll(attributeName, boonsbanes) { + async function makeRoll(attributeName, boonsbanes) { var selected = canvas.tokens.controlled; if (selected.length === 0) { ui.notifications.info(game.i18n.localize('DL.DialogWarningActorsNotSelected')); } else { - selected.forEach(s => { + await Promise.all(selected.forEach(async s => { const a = s.actor - a.rollAttribute(a.getAttribute(attributeName), boonsbanes, 0) - }) + await a.rollAttribute(a.getAttribute(attributeName), boonsbanes, 0) + })) } } @@ -49,12 +49,12 @@ export function makeChallengeRollMacro() { }, }, default: "yes", - close: html => { + close: async html => { if (applyChanges) { let attribute = html.find('[name="attribute-type"]')[0].value || "none"; let boonsbanes = html.find('[name="boonsbanes"]')[0].value || "none"; - makeRoll(attribute, boonsbanes); + await makeRoll(attribute, boonsbanes); } } }).render(true); From d190fa1ed54ec165ade7bbf1a2ffbee75cf34bb8 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 21:05:53 +0000 Subject: [PATCH 06/12] Prevent failure during nested item creation --- src/module/item/sheets/base-item-sheet.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/module/item/sheets/base-item-sheet.js b/src/module/item/sheets/base-item-sheet.js index d212822d..6b756a01 100644 --- a/src/module/item/sheets/base-item-sheet.js +++ b/src/module/item/sheets/base-item-sheet.js @@ -418,10 +418,10 @@ export default class DLBaseItemSheet extends ItemSheet { data: {}, } - await this.createNestedItem(item, folderName) - item.sheet.render(true) + const newItem = await this.createNestedItem(item, folderName) + newItem.sheet.render(true) this.render() - return item + return newItem } // eslint-disable-next-line no-unused-vars From b266c703f400628db8a03dc47874a36ecce450e0 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 21:34:41 +0000 Subject: [PATCH 07/12] Restore extra damage from active effects for spells and items --- src/module/chat/roll-messages.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/module/chat/roll-messages.js b/src/module/chat/roll-messages.js index f4595111..dcd2f0cd 100644 --- a/src/module/chat/roll-messages.js +++ b/src/module/chat/roll-messages.js @@ -58,7 +58,7 @@ export function postAttackToChat(attacker, defender, item, attackRoll, attackAtt data['against'] = defenseAttribute ? game.i18n.localize(CONFIG.DL.attributes[defenseAttribute]?.toUpperCase()) : '' data['againstNumber'] = defenseAttributeImmune ? '-' : againstNumber data['againstNumberGM'] = defenseAttributeImmune ? '-' : (againstNumber === '?' ? targetNumber : againstNumber) - data['damageFormula'] = itemData.action.damage + attacker.system.bonuses.attack.damage + data['damageFormula'] = itemData.action?.damage + attacker.system.bonuses.attack.damage data['damageType'] = itemData.action.damagetype data['damageTypes'] = itemData.action.damagetypes data['damageExtra20PlusFormula'] = itemData.action.plus20damage ? item.system.action.plus20damage : attacker.system.bonuses.attack.plus20Damage @@ -329,7 +329,7 @@ export async function postSpellToChat(actor, spell, attackRoll, target, inputBoo data['against'] = defenseAttribute ? game.i18n.localize(CONFIG.DL.attributes[defenseAttribute]?.toUpperCase()) : '' data['againstNumber'] = defenseAttributeImmune ? '-' : againstNumber data['againstNumberGM'] = defenseAttributeImmune ? '-' : (againstNumber === '?' ? targetNumber : againstNumber) - data['damageFormula'] = spellData.action?.damage + data['damageFormula'] = spellData.action?.damage + actor.system.bonuses.attack.damage data['damageType'] = spellData.action?.damagetype data['damageTypes'] = spellData.action?.damagetypes data['damageExtra20PlusFormula'] = spellData.action?.plus20damage @@ -500,8 +500,7 @@ export const postItemToChat = (actor, item, attackRoll, target, inputBoons) => { data['against'] = defenseAttribute ? game.i18n.localize(CONFIG.DL.attributes[defenseAttribute]?.toUpperCase()) : '' data['againstNumber'] = defenseAttributeImmune ? '-' : againstNumber data['againstNumberGM'] = defenseAttributeImmune ? '-' : (againstNumber === '?' ? targetNumber : againstNumber) - //data['damageFormula'] = itemData.action?.damage + actor.system.bonuses.attack.damage - data['damageFormula'] = itemData.action?.damage + data['damageFormula'] = itemData.action?.damage + actor.system.bonuses.attack.damage data['damageType'] = itemData.action?.damagetype data['damageTypes'] = itemData.action?.damagetypes data['damageExtra20PlusFormula'] = itemData.action?.plus20damage ? item.system.action?.plus20damage : actor.system.bonuses.attack.plus20Damage From adc75d0c531564c0bb084ea43a53105fb306356d Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 21:38:04 +0000 Subject: [PATCH 08/12] Don't double-plusify --- src/module/utils/utils.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/module/utils/utils.js b/src/module/utils/utils.js index 8481563f..c9adc656 100644 --- a/src/module/utils/utils.js +++ b/src/module/utils/utils.js @@ -5,6 +5,7 @@ export function capitalize(string) { } export function plusify(x) { + if ((typeof x === 'string' || x instanceof String) && x[0] === '+') return x // Ignore plusified strings if (x == 0) return '' return x > 0 ? '+' + x : x } From 3bc483674efdd99df98b5550c4dd98e643b4cd14 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Tue, 7 Nov 2023 21:39:37 +0000 Subject: [PATCH 09/12] Convert input boons to integer --- src/module/chat/effect-messages.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/module/chat/effect-messages.js b/src/module/chat/effect-messages.js index 5a00390a..15f74d57 100644 --- a/src/module/chat/effect-messages.js +++ b/src/module/chat/effect-messages.js @@ -69,7 +69,7 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib let defenderBoons = (defender?.system.bonuses.defense.boons[defenseAttribute] || 0) + (defender?.system.bonuses.defense.boons.all || 0) const defenderString = defender?.name + ' [' + game.i18n.localize('DL.SpellTarget') + ']' let otherBoons = '' - let inputBoonsMsg = (inputBoons && inputBoons !== "0") ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' + let inputBoonsMsg = inputBoons ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' let itemBoons switch (item.type) { case 'spell': @@ -118,7 +118,7 @@ export function buildAttackEffectsMessage(attacker, defender, item, attackAttrib export function buildAttributeEffectsMessage(actor, attribute, inputBoons) { const actorEffects = actor?.getEmbeddedCollection('ActiveEffect').filter(effect => !effect.disabled) let m = _remapEffects(actorEffects) - let inputBoonsMsg = (inputBoons && inputBoons !== "0") ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' + let inputBoonsMsg = inputBoons ? _toMsg(game.i18n.localize('DL.DialogInput'), plusify(inputBoons)) : '' let result = '' result += changeListToMsg(m, [`system.bonuses.challenge.boons.${attribute}`, 'system.bonuses.challenge.boons.all' ], 'DL.TalentChallengeBoonsBanes') + inputBoonsMsg From 818e0bbb8402e3947238ff8bb5c41ee858d06279 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Wed, 8 Nov 2023 22:11:38 +0000 Subject: [PATCH 10/12] Finish inputBoons as int --- src/module/actor/actor.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 220f386c..1f6718cc 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -368,7 +368,7 @@ export class DemonlordActor extends Actor { const attackRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) await attackRoll.evaluate() - postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, inputBoons) + postAttackToChat(attacker, defender, item, attackRoll, attackAttribute, defenseAttribute, parseInt(inputBoons) || 0) const hitTargets = defendersTokens.filter(d => { const targetNumber = @@ -413,12 +413,12 @@ export class DemonlordActor extends Actor { async rollAttribute(attribute, inputBoons, inputModifier) { attribute = attribute.label.toLowerCase() const modifiers = [parseInt(inputModifier), this.getAttribute(attribute)?.modifier || 0] - const boons = parseInt(inputBoons) + (this.system.bonuses.challenge.boons[attribute] || 0) + (this.system.bonuses.challenge.boons.all || 0) + const boons = (parseInt(inputBoons) || 0) + (this.system.bonuses.challenge.boons[attribute] || 0) + (this.system.bonuses.challenge.boons.all || 0) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) const challengeRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) await challengeRoll.evaluate() - postAttributeToChat(this, attribute, challengeRoll, inputBoons) + postAttributeToChat(this, attribute, challengeRoll, parseInt(inputBoons) || 0) } rollChallenge(attribute) { @@ -488,7 +488,7 @@ export class DemonlordActor extends Actor { itemId: talent.id, }) - postTalentToChat(this, talent, attackRoll, target?.actor, inputBoons) + postTalentToChat(this, talent, attackRoll, target?.actor, parseInt(inputBoons) || 0) } /* -------------------------------------------- */ @@ -558,7 +558,7 @@ export class DemonlordActor extends Actor { itemId: spell.id, }) - postSpellToChat(this, spell, attackRoll, target?.actor, inputBoons) + postSpellToChat(this, spell, attackRoll, target?.actor, parseInt(inputBoons) || 0) } /* -------------------------------------------- */ @@ -601,7 +601,7 @@ export class DemonlordActor extends Actor { let modifiers = [parseInt(inputModifier), (this.getAttribute(attackAttribute)?.modifier || 0)] let boons = - parseInt(inputBoons) + + (parseInt(inputBoons) || 0) + (this.system.bonuses.attack[attackAttribute] || 0) + (this.system.bonuses.attack.boons.all || 0) + parseInt(itemData.action?.boonsbanes || 0) @@ -612,7 +612,7 @@ export class DemonlordActor extends Actor { await attackRoll.evaluate() } - postItemToChat(this, item, attackRoll, target?.actor, inputBoons) + postItemToChat(this, item, attackRoll, target?.actor, parseInt(inputBoons) || 0) } /* -------------------------------------------- */ From e6466dd97fc6b063bac668419ffbaa03cad35ecd Mon Sep 17 00:00:00 2001 From: juanferrer Date: Wed, 8 Nov 2023 22:31:26 +0000 Subject: [PATCH 11/12] Correctly identify localised rolled attribute --- src/module/actor/actor.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 1f6718cc..2e487e57 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -110,6 +110,7 @@ export class DemonlordActor extends Actor { attribute.min = 0 attribute.value = Math.min(attribute.max, Math.max(attribute.min, attribute.value)) attribute.label = game.i18n.localize(`DL.Attribute${capitalize(key)}`) + attribute.key = key } system.attributes.perception.label = game.i18n.localize(`DL.AttributePerception`) @@ -411,20 +412,19 @@ export class DemonlordActor extends Actor { /* -------------------------------------------- */ async rollAttribute(attribute, inputBoons, inputModifier) { - attribute = attribute.label.toLowerCase() - const modifiers = [parseInt(inputModifier), this.getAttribute(attribute)?.modifier || 0] - const boons = (parseInt(inputBoons) || 0) + (this.system.bonuses.challenge.boons[attribute] || 0) + (this.system.bonuses.challenge.boons.all || 0) + const modifiers = [parseInt(inputModifier), this.getAttribute(attribute.key)?.modifier || 0] + const boons = (parseInt(inputBoons) || 0) + (this.system.bonuses.challenge.boons[attribute.key] || 0) + (this.system.bonuses.challenge.boons.all || 0) const boonsReroll = parseInt(this.system.bonuses.rerollBoon1Dice) const challengeRoll = new Roll(this.rollFormula(modifiers, boons, boonsReroll), {}) await challengeRoll.evaluate() - postAttributeToChat(this, attribute, challengeRoll, parseInt(inputBoons) || 0) + postAttributeToChat(this, attribute.key, challengeRoll, parseInt(inputBoons) || 0) } rollChallenge(attribute) { if (typeof attribute === 'string' || attribute instanceof String) attribute = this.getAttribute(attribute) - if (!DLAfflictions.isActorBlocked(this, 'challenge', attribute.label)) + if (!DLAfflictions.isActorBlocked(this, 'challenge', attribute.key)) launchRollDialog(this.name + ': ' + game.i18n.localize('DL.DialogChallengeRoll').slice(0, -2), async html => await this.rollAttribute(attribute, html.find('[id="boonsbanes"]').val(), html.find('[id="modifier"]').val()), ) From a5378b0f55eff74c5992f2334cdba88d5d6db3a4 Mon Sep 17 00:00:00 2001 From: juanferrer Date: Thu, 9 Nov 2023 21:47:59 +0000 Subject: [PATCH 12/12] Prevent double application of defense changes from active effects --- src/module/actor/actor.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/actor/actor.js b/src/module/actor/actor.js index 2e487e57..dc087534 100644 --- a/src/module/actor/actor.js +++ b/src/module/actor/actor.js @@ -186,7 +186,7 @@ export class DemonlordActor extends Actor { // Final armor computation system.characteristics.defense += system.bonuses.armor.defense system.characteristics.defense = system.bonuses.armor.override || system.characteristics.defense - for (let change of effectChanges.filter(e => e.key.includes("defense"))) { + for (let change of effectChanges.filter(e => e.key.includes("defense") && !e.key.startsWith("system.characteristics"))) { const result = change.effect.apply(this, change) if (result !== null) this.overrides[change.key] = result }