From 0e6b093a1bdd58c7f24fe630aae5aaba15ab09cd Mon Sep 17 00:00:00 2001 From: Prototype Date: Mon, 8 Jul 2024 09:27:53 -0700 Subject: [PATCH 1/2] Fixes #837 --- system/src/documents/ActorSD.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system/src/documents/ActorSD.mjs b/system/src/documents/ActorSD.mjs index ca4925c2..6c4892fc 100644 --- a/system/src/documents/ActorSD.mjs +++ b/system/src/documents/ActorSD.mjs @@ -1291,7 +1291,7 @@ export default class ActorSD extends Actor { newArmorClass += parseInt(this.system.bonuses.acBonus, 10); } - this.system.attributes.ac.value = newArmorClass; + this.update({"system.attributes.ac.value": newArmorClass}); } From 07895d33c76489b6ed46deaebbb5a3a221388407 Mon Sep 17 00:00:00 2001 From: Prototype Date: Mon, 8 Jul 2024 12:31:06 -0700 Subject: [PATCH 2/2] armorClass funtion cleanup --- system/src/documents/ActorSD.mjs | 227 +++++++++--------- .../__tests__/documents-actor.test.mjs | 10 +- ...ts-item-effect-predefined-effects.test.mjs | 4 +- .../e2e-documents-item-effect.test.mjs | 2 +- system/src/sheets/PlayerSheetSD.mjs | 8 +- 5 files changed, 120 insertions(+), 131 deletions(-) diff --git a/system/src/documents/ActorSD.mjs b/system/src/documents/ActorSD.mjs index 6c4892fc..84b4d7aa 100644 --- a/system/src/documents/ActorSD.mjs +++ b/system/src/documents/ActorSD.mjs @@ -222,7 +222,6 @@ export default class ActorSD extends Actor { _preparePlayerData() { this._populatePlayerModifiers(); - this.updateArmorClass(); } @@ -712,7 +711,117 @@ export default class ActorSD extends Actor { } - getArmorClass() { + async getArmorClass() { + const dexModifier = this.abilityModifier("dex"); + + let baseArmorClass = shadowdark.defaults.BASE_ARMOR_CLASS; + baseArmorClass += dexModifier; + + for (const attribute of this.system.bonuses?.acBonusFromAttribute ?? []) { + const attributeBonus = this.abilityModifier(attribute); + baseArmorClass += attributeBonus > 0 ? attributeBonus : 0; + } + + let newArmorClass = baseArmorClass; + let shieldBonus = 0; + + const acOverride = this.system.attributes.ac?.override ?? null; + if (Number.isInteger(acOverride)) { + // AC is being overridden by an effect so we just use that value + // and ignore everything else + newArmorClass = acOverride; + } + else { + let armorMasteryBonus = 0; + + const equippedArmorItems = this.items.filter( + item => item.type === "Armor" && item.system.equipped + ); + const equippedArmor = []; + const equippedShields = []; + + for (const item of equippedArmorItems) { + if (await item.isAShield()) { + equippedShields.push(item); + } + else { + equippedArmor.push(item); + } + } + + if (equippedShields.length > 0) { + const firstShield = equippedShields[0]; + shieldBonus = firstShield.system.ac.modifier; + + armorMasteryBonus = this.system.bonuses.armorMastery.filter( + a => a === firstShield.name.slugify() + || a === firstShield.system.baseArmor + ).length; + } + + if (equippedArmor.length > 0) { + newArmorClass = 0; + + let bestAttributeBonus = null; + let baseArmorClassApplied = false; + + for (const armor of equippedArmor) { + + // Check if armor mastery should apply to the AC. Multiple + // mastery levels should stack + // + const masteryLevels = this.system.bonuses.armorMastery.filter( + a => a === armor.name.slugify() + || a === armor.system.baseArmor + ); + armorMasteryBonus += masteryLevels.length; + + if (armor.system.ac.base > 0) baseArmorClassApplied = true; + + newArmorClass += armor.system.ac.base; + newArmorClass += armor.system.ac.modifier; + + const attribute = armor.system.ac.attribute; + if (attribute) { + const attributeBonus = this.abilityModifier(attribute); + if (bestAttributeBonus === null) { + bestAttributeBonus = attributeBonus; + } + else { + bestAttributeBonus = + attributeBonus > bestAttributeBonus + ? attributeBonus + : bestAttributeBonus; + } + } + } + + if (!baseArmorClassApplied) { + // None of the armor we're wearing has a base value, only + // bonuses so we will use the default base class of + // 10+DEX to allow for unarmored characters wearing Bracers + // of defense (as an example) + // + newArmorClass += baseArmorClass; + } + + newArmorClass += bestAttributeBonus; + newArmorClass += armorMasteryBonus; + newArmorClass += shieldBonus; + } + else if (shieldBonus <= 0) { + newArmorClass += this.system.bonuses.unarmoredAcBonus ?? 0; + } + else { + newArmorClass += shieldBonus; + } + + // Add AC from bonus effects + newArmorClass += parseInt(this.system.bonuses.acBonus, 10); + } + + this.update({"system.attributes.ac.value": newArmorClass}); + return this.system.attributes.ac.value; } @@ -1181,120 +1290,6 @@ export default class ActorSD extends Actor { await this.changeLightSettings(lightData); } - - async updateArmorClass() { - const dexModifier = this.abilityModifier("dex"); - - let baseArmorClass = shadowdark.defaults.BASE_ARMOR_CLASS; - baseArmorClass += dexModifier; - - for (const attribute of this.system.bonuses?.acBonusFromAttribute ?? []) { - const attributeBonus = this.abilityModifier(attribute); - baseArmorClass += attributeBonus > 0 ? attributeBonus : 0; - } - - let newArmorClass = baseArmorClass; - let shieldBonus = 0; - - const acOverride = this.system.attributes.ac?.override ?? null; - if (Number.isInteger(acOverride)) { - // AC is being overridden by an effect so we just use that value - // and ignore everything else - newArmorClass = acOverride; - } - else { - let armorMasteryBonus = 0; - - const equippedArmorItems = this.items.filter( - item => item.type === "Armor" && item.system.equipped - ); - const equippedArmor = []; - const equippedShields = []; - - for (const item of equippedArmorItems) { - if (await item.isAShield()) { - equippedShields.push(item); - } - else { - equippedArmor.push(item); - } - } - - if (equippedShields.length > 0) { - const firstShield = equippedShields[0]; - shieldBonus = firstShield.system.ac.modifier; - - armorMasteryBonus = this.system.bonuses.armorMastery.filter( - a => a === firstShield.name.slugify() - || a === firstShield.system.baseArmor - ).length; - } - - if (equippedArmor.length > 0) { - newArmorClass = 0; - - let bestAttributeBonus = null; - let baseArmorClassApplied = false; - - for (const armor of equippedArmor) { - - // Check if armor mastery should apply to the AC. Multiple - // mastery levels should stack - // - const masteryLevels = this.system.bonuses.armorMastery.filter( - a => a === armor.name.slugify() - || a === armor.system.baseArmor - ); - armorMasteryBonus += masteryLevels.length; - - if (armor.system.ac.base > 0) baseArmorClassApplied = true; - - newArmorClass += armor.system.ac.base; - newArmorClass += armor.system.ac.modifier; - - const attribute = armor.system.ac.attribute; - if (attribute) { - const attributeBonus = this.abilityModifier(attribute); - if (bestAttributeBonus === null) { - bestAttributeBonus = attributeBonus; - } - else { - bestAttributeBonus = - attributeBonus > bestAttributeBonus - ? attributeBonus - : bestAttributeBonus; - } - } - } - - if (!baseArmorClassApplied) { - // None of the armor we're wearing has a base value, only - // bonuses so we will use the default base class of - // 10+DEX to allow for unarmored characters wearing Bracers - // of defense (as an example) - // - newArmorClass += baseArmorClass; - } - - newArmorClass += bestAttributeBonus; - newArmorClass += armorMasteryBonus; - newArmorClass += shieldBonus; - } - else if (shieldBonus <= 0) { - newArmorClass += this.system.bonuses.unarmoredAcBonus ?? 0; - } - else { - newArmorClass += shieldBonus; - } - - // Add AC from bonus effects - newArmorClass += parseInt(this.system.bonuses.acBonus, 10); - } - - this.update({"system.attributes.ac.value": newArmorClass}); - } - - async useAbility(itemId, options={}) { const item = this.items.get(itemId); const abilityDescription = await TextEditor.enrichHTML( diff --git a/system/src/documents/__tests__/documents-actor.test.mjs b/system/src/documents/__tests__/documents-actor.test.mjs index 5edb5b18..f686c3bf 100644 --- a/system/src/documents/__tests__/documents-actor.test.mjs +++ b/system/src/documents/__tests__/documents-actor.test.mjs @@ -359,7 +359,7 @@ export default ({ describe, it, after, before, expect }) => { }); describe("getArmorClass()", () => { - // Tested under updateArmorClass() + // Tested under getArmorClass() it("returns the correct armor class", async () => { const actor = await createMockActor("Player"); await actor.update({ @@ -427,7 +427,7 @@ export default ({ describe, it, after, before, expect }) => { }); }); - describe("updateArmorClass()", () => { + describe("getArmorClass()", () => { let actor = {}; before(async () => { @@ -438,7 +438,7 @@ export default ({ describe, it, after, before, expect }) => { }); it("calculates the correct AC with no armor equipped", async () => { - await actor.updateArmorClass(); + await actor.getArmorClass(); await waitForInput(); expect(await actor.getArmorClass()).equal(10 + 4); }); @@ -453,7 +453,7 @@ export default ({ describe, it, after, before, expect }) => { "system.equipped": true, }, ]); - await actor.updateArmorClass(); + await actor.getArmorClass(); await waitForInput(); expect(await actor.getArmorClass()).equal(11 + 4 + 2); }); @@ -468,7 +468,7 @@ export default ({ describe, it, after, before, expect }) => { "system.equipped": true, }, ]); - await actor.updateArmorClass(); + await actor.getArmorClass(); await waitForInput(); expect(await actor.getArmorClass()).equal(11 + 4 + 2 + 3); }); diff --git a/system/src/documents/__tests__/documents-item-effect-predefined-effects.test.mjs b/system/src/documents/__tests__/documents-item-effect-predefined-effects.test.mjs index 21e7add5..687cd7de 100644 --- a/system/src/documents/__tests__/documents-item-effect-predefined-effects.test.mjs +++ b/system/src/documents/__tests__/documents-item-effect-predefined-effects.test.mjs @@ -51,7 +51,7 @@ export default ({ describe, it, before, after, afterEach, expect }) => { before(async () => { _p = await createMockPlayer(); - await _p.updateArmorClass(); + await _p.getArmorClass(); await waitForInput(); }); @@ -142,7 +142,7 @@ export default ({ describe, it, before, after, afterEach, expect }) => { expect(_pe[0]).is.not.undefined; expect(_p.items.size).equal(1); - await _p.updateArmorClass(); + await _p.getArmorClass(); await waitForInput(); expect(_p.system.attributes.ac.value).equal(11); diff --git a/system/src/documents/__tests__/e2e-documents-item-effect.test.mjs b/system/src/documents/__tests__/e2e-documents-item-effect.test.mjs index 0ab6d39d..51c89683 100644 --- a/system/src/documents/__tests__/e2e-documents-item-effect.test.mjs +++ b/system/src/documents/__tests__/e2e-documents-item-effect.test.mjs @@ -43,7 +43,7 @@ export default ({ describe, it, before, after, afterEach, expect }) => { before(async () => { _p = await createMockPlayer(); - await _p.updateArmorClass(); + await _p.getArmorClass(); await waitForInput(); }); diff --git a/system/src/sheets/PlayerSheetSD.mjs b/system/src/sheets/PlayerSheetSD.mjs index 59f65c99..0d6a524f 100644 --- a/system/src/sheets/PlayerSheetSD.mjs +++ b/system/src/sheets/PlayerSheetSD.mjs @@ -164,8 +164,7 @@ export default class PlayerSheetSD extends ActorSheetSD { context.xpNextLevel = context.system.level.value * 10; context.levelUp = (context.system.level.xp >= context.xpNextLevel); - // await this.actor.updateArmorClass(); - // context.armorClass = this.actor.getArmorClass(); + context.system.attributes.ac.value = await this.actor.getArmorClass(); context.isSpellcaster = await this.actor.isSpellcaster(); context.canUseMagicItems = await this.actor.canUseMagicItems(); @@ -646,11 +645,6 @@ export default class PlayerSheetSD extends ActorSheetSD { }, ]); - if (item.type === "Armor") await this.actor.updateArmorClass(); - - this.actor.update({ - "system.attributes.ac.value": this.actor.getArmorClass(), - }); } async _onToggleStashed(event) {