Skip to content

Commit

Permalink
Merge pull request #838 from Muttley/837-bug-ac-displaying-incorrectly
Browse files Browse the repository at this point in the history
Fixes #837
  • Loading branch information
Muttley authored Jul 9, 2024
2 parents cd7898a + 07895d3 commit 6a0a678
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 131 deletions.
227 changes: 111 additions & 116 deletions system/src/documents/ActorSD.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,6 @@ export default class ActorSD extends Actor {

_preparePlayerData() {
this._populatePlayerModifiers();
this.updateArmorClass();
}


Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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.system.attributes.ac.value = newArmorClass;
}


async useAbility(itemId, options={}) {
const item = this.items.get(itemId);
const abilityDescription = await TextEditor.enrichHTML(
Expand Down
10 changes: 5 additions & 5 deletions system/src/documents/__tests__/documents-actor.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -427,7 +427,7 @@ export default ({ describe, it, after, before, expect }) => {
});
});

describe("updateArmorClass()", () => {
describe("getArmorClass()", () => {
let actor = {};

before(async () => {
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand All @@ -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);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
Expand Down
8 changes: 1 addition & 7 deletions system/src/sheets/PlayerSheetSD.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down

0 comments on commit 6a0a678

Please sign in to comment.