diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a7845fa..390a4a79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ ## Version 4.0.7 (So far...) [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) +- Conditional DAMAGEREDUCTION and DAMAGENEGATION defenses now prompt for applicability. [#1478](https://github.com/dmdorman/hero6e-foundryvtt/issues/1478) +- Conditional defenses with special effects will attempt to automatically determine if it applies. GM still has to confirm conditional effects. + ## Version 4.0.6 - Migrations are no longer supported from versions before 3.0.76. Migrate through any version prior to 4.0.6 if you find your world in this situation. diff --git a/module/actor/actor-sheet.mjs b/module/actor/actor-sheet.mjs index f7481d13..a3f4d7e8 100644 --- a/module/actor/actor-sheet.mjs +++ b/module/actor/actor-sheet.mjs @@ -4,7 +4,7 @@ import { HeroSystem6eActor } from "./actor.mjs"; import { HeroSystem6eItem } from "../item/item.mjs"; import { userInteractiveVerifyOptionallyPromptThenSpendResources } from "../item/item-attack.mjs"; -import { determineDefense, getActorDefensesVsAttack } from "../utility/defense.mjs"; +import { getActorDefensesVsAttack } from "../utility/defense.mjs"; import { presenceAttackPopOut } from "../utility/presence-attack.mjs"; import { onManageActiveEffect } from "../utility/effects.mjs"; import { getPowerInfo, getCharacteristicInfoArrayForActor, whisperUserTargetsForActor } from "../utility/util.mjs"; @@ -54,7 +54,7 @@ export class HeroSystemActorSheet extends ActorSheet { } // Items returned by the super have been neutered, we want the full class so we can use parentItem and childItem getters. - data.items = Array.from(data.items).sort((a, b) => (a.sort || 0) - (b.sort || 0)); + data.items = Array.from(data.actor.items).sort((a, b) => (a.sort || 0) - (b.sort || 0)); // const equipmentWeightPercentage = // parseInt(game.settings.get(game.system.id, "equipmentWeightPercentage")) / 100.0; @@ -383,22 +383,12 @@ export class HeroSystemActorSheet extends ActorSheet { ); await drainAttack._postUpload(); - let { - defenseValue: _defenseValuePOWD, - // resistantValue: - // _resistantValuePOWD /*impenetrableValuePOWD*/ /*damageReductionValuePOWD*/ /*damageNegationValuePOWD*/ /*knockbackResistancePOWD*/, - // defenseTags: defenseTagsPOWD, - } = determineDefense(this.actor, drainAttack, { suppressDeprecationWarn: true }); - // New POWERDEFENSE const { defenseValue: defenseValuePOWD, resistantValue: resistantValuePOWD, defenseTags: defenseTagsPOWD, } = getActorDefensesVsAttack(this.actor, drainAttack); - if (_defenseValuePOWD != defenseValuePOWD) { - console.warn("POWERDEFENSE Defense mismatch", _defenseValuePOWD, defenseValuePOWD); - } defense.POWD = defenseValuePOWD; for (const tag of defenseTagsPOWD.filter((o) => o.operation === "add" && !o.options?.resistant)) { defense.POWDtags = `${defense.POWDtags || ""}${tag.value.signedString()} ${tag.name} ${ diff --git a/module/actor/actor.mjs b/module/actor/actor.mjs index 59d2fa6b..71739a09 100644 --- a/module/actor/actor.mjs +++ b/module/actor/actor.mjs @@ -2136,7 +2136,7 @@ export class HeroSystem6eActor extends Actor { // Characteristics for (const key of Object.keys(this.system.characteristics)) { let newValue = parseInt(this.system?.[key.toUpperCase()]?.LEVELS || 0); - newValue += this.getCharacteristicBase(key); + newValue += this.getCharacteristicBase(key) || 0; // 5e will have empty base for ocv/dcv and other figured characteristics if (this.system.is5e && key === "spd") { // SPD is always an integer, but in 5e due to figured characteristics, the base can be fractional. newValue = Math.floor(newValue); @@ -2156,6 +2156,7 @@ export class HeroSystem6eActor extends Actor { } if ( this.system.characteristics[key].value !== this.system.characteristics[key.toLowerCase()].max && + this.system.characteristics[key.toLowerCase()].max !== null && overrideValues ) { if (this.id) { diff --git a/module/config.mjs b/module/config.mjs index 8d2f895e..794f1271 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -4685,7 +4685,19 @@ function addPower(powerDescription6e, powerOverrideFor5e) { key: "DARKNESS", type: ["sense-affecting", "attack", "standard"], behaviors: ["attack"], - costPerLevel: costPerLevelFixedValue(0), + costPerLevel: function (item) { + switch (item?.system?.OPTIONID) { + case "SIGHTGROUP": + return 5; // Targeting sense gruop + case "HEARINGGROUP": + case "MENTALGROUP": + case "RADIOGROUP": + case "SMELLGROUP": + case "TOUCHGROUP": + return 3; // Non-targeting sense group + } + return 0; + }, duration: "constant", range: HERO.RANGE_TYPES.STANDARD, costEnd: true, diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index 09fdc77f..ae1a1059 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -1,6 +1,6 @@ import { HEROSYS } from "../herosystem6e.mjs"; import { getPowerInfo, getCharacteristicInfoArrayForActor, whisperUserTargetsForActor } from "../utility/util.mjs"; -import { determineDefense, getActorDefensesVsAttack } from "../utility/defense.mjs"; +import { getActorDefensesVsAttack, defenseConditionalCheckedByDefault } from "../utility/defense.mjs"; import { HeroSystem6eActorActiveEffects } from "../actor/actor-active-effects.mjs"; import { RoundFavorPlayerDown, RoundFavorPlayerUp } from "../utility/round.mjs"; import { @@ -1269,16 +1269,6 @@ async function _rollApplyKnockback(token, knockbackDice) { let ignoreDefenseIds = []; let defense = ""; - // Old Defense Stuff - let { - defenseValue: _defenseValue, - // resistantValue: _resistantValue, - // impenetrableValue: _impenetrableValue, - // damageReductionValue: _damageReductionValue, - damageNegationValue: _damageNegationValue, - knockbackResistanceValue: _knockbackResistanceValue, - //defenseTags: _defenseTags, - } = determineDefense(token.actor, pdAttack, { ignoreDefenseIds }); // New Defense Stuff let { @@ -1291,16 +1281,6 @@ async function _rollApplyKnockback(token, knockbackDice) { defenseTags, } = getActorDefensesVsAttack(token.actor, pdAttack, { ignoreDefenseIds }); - if (damageNegationValue != _damageNegationValue) { - console.warn("damageNegationValue mismatch", damageNegationValue, _damageNegationValue); - } - if (defenseValue != _defenseValue) { - console.warn("defenseValue mismatch", defenseValue, _defenseValue); - } - if (knockbackResistanceValue != _knockbackResistanceValue) { - console.warn("knockbackResistanceValue mismatch", knockbackResistanceValue, _knockbackResistanceValue); - } - if (damageNegationValue > 0) { defense += "Damage Negation " + damageNegationValue + "DC(s); "; } @@ -1758,7 +1738,7 @@ export async function _onRollMindScanEffectRoll(event) { damageNegationValue, knockbackResistanceValue, defenseTags, - } = determineDefense(token.actor, item, { ignoreDefenseIds }); + } = getActorDefensesVsAttack(token.actor, item, { ignoreDefenseIds }); if (damageNegationValue > 0) { defense += "Damage Negation " + damageNegationValue + "DC(s); "; } @@ -1970,7 +1950,6 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { } // Check for conditional defenses - let ignoreDefenseIds = []; let conditionalDefenses = token.actor.items.filter( (o) => @@ -1982,9 +1961,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { // Remove conditional defenses that provide no defense if (!game.settings.get(HEROSYS.module, "ShowAllConditionalDefenses")) { - conditionalDefenses = conditionalDefenses.filter( - (defense) => defense.getDefense(token.actor, item).defenseTotalValue > 0, - ); + conditionalDefenses = conditionalDefenses.filter((defense) => defense.getDefense(token.actor, item)); } // AVAD Life Support @@ -2031,7 +2008,7 @@ export async function _onApplyDamageToSpecificToken(event, tokenId) { const option = { id: defense.id, name: defense.name, - checked: !avad && defense.getDefense(token.actor, item).defenseTotalValue > 0, + checked: !avad && defenseConditionalCheckedByDefault(defense, item), conditions: "", }; diff --git a/module/item/item.mjs b/module/item/item.mjs index ecafa6f3..7aed7dfb 100644 --- a/module/item/item.mjs +++ b/module/item/item.mjs @@ -23,7 +23,7 @@ import { HeroRoller } from "../utility/dice.mjs"; import { HeroSystem6eActorActiveEffects } from "../actor/actor-active-effects.mjs"; import { Attack } from "../utility/attack.mjs"; import { activateSpecialVision, removeSpecialVisions } from "../utility/vision.mjs"; -import { determineDefense } from "../utility/defense.mjs"; +import { getItemDefenseVsAttack } from "../utility/defense.mjs"; import { overrideCanAct } from "../settings/settings-helpers.mjs"; export function initializeItemHandlebarsHelpers() { @@ -433,6 +433,9 @@ export class HeroSystem6eItem extends Item { } } + // Perceivability + content += ` ${this.perceivability}.`; + if (this.system.end) { content += ` Estimated End: ${this.system.end}.`; } @@ -2221,6 +2224,7 @@ export class HeroSystem6eItem extends Item { get parentItem() { const parentId = this.system?.PARENTID; if (!parentId) return null; + if (!this.system?.ID) return null; const items = this.actor?.items || game.items; return items.find((item) => item.system?.ID === parentId) || null; @@ -2241,6 +2245,9 @@ export class HeroSystem6eItem extends Item { // } // game.packs.get(this.pack).index.contents + // Super old items may not have an ID + if (!this.system?.ID) return []; + const items = this.actor?.items || (this.pack ? [] : game.items); const children = items @@ -4838,7 +4845,8 @@ export class HeroSystem6eItem extends Item { } getDefense(targetActor, attackItem) { - return determineDefense(targetActor, attackItem, { only: this }); + //return determineDefense(targetActor, attackItem, { only: this }); + return getItemDefenseVsAttack(this, attackItem); } get attackDefenseVs() { @@ -4942,6 +4950,10 @@ export class HeroSystem6eItem extends Item { return false; } + get perceivability() { + return this.baseInfo.perceivability; + } + get weightKg() { const equipmentWeightPercentage = parseInt(game.settings.get(game.system.id, "equipmentWeightPercentage")) / 100.0; diff --git a/module/testing/testing-defense.mjs b/module/testing/testing-defense.mjs index d38d0d83..9fe4c60c 100644 --- a/module/testing/testing-defense.mjs +++ b/module/testing/testing-defense.mjs @@ -1,6 +1,6 @@ import { HeroSystem6eActor } from "../actor/actor.mjs"; import { HeroSystem6eItem } from "../item/item.mjs"; -import { determineDefense } from "../utility/defense.mjs"; +import { getActorDefensesVsAttack } from "../utility/defense.mjs"; export function registerDefenseTests(quench) { quench.registerBatch( @@ -38,7 +38,7 @@ export function registerDefenseTests(quench) { }); await itemAttack._postUpload(); - const defense = determineDefense(actor, itemAttack); + const defense = getActorDefensesVsAttack(actor, itemAttack); assert.equal(defense.resistantValue, 1); }); @@ -71,7 +71,7 @@ export function registerDefenseTests(quench) { }); await itemAttack._postUpload(); - const defense = determineDefense(actor, itemAttack); + const defense = getActorDefensesVsAttack(actor, itemAttack); assert.equal(defense.resistantValue, 2); }); @@ -105,7 +105,7 @@ export function registerDefenseTests(quench) { }); await itemAttack._postUpload(); - const defense = determineDefense(actor, itemAttack); + const defense = getActorDefensesVsAttack(actor, itemAttack); assert.equal(defense.resistantValue, 3); }); @@ -140,7 +140,7 @@ export function registerDefenseTests(quench) { await itemAttack._postUpload(); - const defense = determineDefense(actor, itemAttack); + const defense = getActorDefensesVsAttack(actor, itemAttack); assert.equal(defense.resistantValue, 4); }); }); diff --git a/module/utility/defense.mjs b/module/utility/defense.mjs index 588c44d4..490fecea 100644 --- a/module/utility/defense.mjs +++ b/module/utility/defense.mjs @@ -1,6 +1,3 @@ -import { HEROSYS } from "../herosystem6e.mjs"; -import { getPowerInfo } from "../utility/util.mjs"; -// import { distanceWithActorUnits } from "../utility/units.mjs"; import { RoundFavorPlayerUp } from "./round.mjs"; //export function createDefenseProfile @@ -252,661 +249,44 @@ export function getActorDefensesVsAttack(targetActor, attackItem, options = {}) } } actorDefenses.defenseTotalValue = actorDefenses.defenseValue + actorDefenses.resistantValue; + return actorDefenses; } -export function determineDefense(targetActor, attackItem, options) { - if (!options?.suppressDeprecationWarn) { - console.warn("deprecated 'determineDefense' function, refactor to use 'getActorDefensesVsAttack'"); - } - - if (!attackItem.findModsByXmlid) { - console.error("Invalid attackItem", attackItem); - } - - //console.log(attackItem.attackDefenseVs); - getActorDefensesVsAttack(targetActor, attackItem, options); - - //const avad = attackItem.findModsByXmlid("AVAD"); - //const attackType = avad ? "avad" : attackItem.system.class; - let attackType = attackItem.system.class; - if (attackType === "avad") { - switch (attackItem.system.INPUT) { - case "PD": - attackType = "physical"; - break; - case "ED": - attackType = "energy"; - break; - case "MD": - attackType = "mental"; - break; - default: - console.error("Unknown attackType"); - } - } - - if (!["physical", "energy", "mental"].includes(attackType) && attackItem.baseInfo?.type.includes("mental")) { - attackType = "mental"; - } - - const piercing = parseInt(attackItem.system.piercing) || attackItem.findModsByXmlid("ARMORPIERCING") || 0; - const penetrating = parseInt(attackItem.system.penetrating) || attackItem.findModsByXmlid("PENETRATING") || 0; - - // The defenses that are active - const activeDefenses = options?.only - ? [options.only] - : targetActor.items.filter( - (o) => - (o.system.subType === "defense" || o.type === "defense" || o.baseInfo?.type?.includes("defense")) && - (o.isActive || o.effects.find(() => true)?.disabled === false) && - !(options?.ignoreDefenseIds || []).includes(o.id), - ); - - let PD = options?.only ? 0 : parseInt(targetActor.system.characteristics.pd.value); // physical defense - let ED = options?.only ? 0 : parseInt(targetActor.system.characteristics.ed.value); // energy defense - let MD = 0; // mental defense - let POWD = 0; // power defense - let rPD = 0; // resistant physical defense - let rED = 0; // resistant energy defense - let rMD = 0; // resistant mental defense (a silly but possible thing) - let rPOWD = 0; // resistant power defense (a silly but possible thing) - let DRP = 0; // damage reduction physical - let DRE = 0; // damage reduction energy - let DRM = 0; // damage reduction mental - let DNP = 0; // damage negation physical - let DNE = 0; // damage negation energy - let DNM = 0; // damage negation mental - let knockbackResistanceValue = 0; - - // Check if we are supposed to ignore PD/ED (eg AVAD) - if (options?.ignoreDefenseIds?.includes("PD")) { - PD = 0; - } - if (options?.ignoreDefenseIds?.includes("ED")) { - ED = 0; - } - - // DAMAGERESISTANCE (converts PD to rPD) - for (const item of activeDefenses.filter((o) => o.system.XMLID == "DAMAGERESISTANCE")) { - const pdLevels = Math.min(PD, parseInt(item.system.PDLEVELS) || 0); - PD -= pdLevels; - rPD += pdLevels; - const edLevels = Math.min(ED, parseInt(item.system.EDLEVELS) || 0); - ED -= edLevels; - rED += edLevels; - const mdLevels = Math.min(MD, parseInt(item.system.MDLEVELS) || 0); - MD -= mdLevels; - rMD += mdLevels; - - // TODO: - // Characters can also purchase Damage Resistance - // for Mental Defense, Flash Defense, Power - // Defense, or similar Defense Powers to make them - // Resistant.: - } - - // PD bought as resistant - for (const item of activeDefenses.filter((o) => o.system.XMLID == "PD")) { - if (item.findModsByXmlid("RESISTANT")) { - const levels = parseInt(item.system.value) || 0; - PD -= levels; - rPD += levels; - - if (item.system.ADD_MODIFIERS_TO_BASE) { - PD -= targetActor.system.characteristics.pd.core; - rPD += targetActor.system.characteristics.pd.core; - } - } - } - - // ED bought as resistant - for (const item of activeDefenses.filter((o) => o.system.XMLID == "ED")) { - if (item.findModsByXmlid("RESISTANT")) { - const levels = parseInt(item.system.value) || 0; - ED -= levels; - rED += levels; - } - - if (item.system.ADD_MODIFIERS_TO_BASE) { - ED -= targetActor.system.characteristics.ed.core; - rED += targetActor.system.characteristics.ed.core; - } - } - - // PD bought as hardened - for (const item of activeDefenses.filter((o) => o.system.XMLID == "PD" && o.isActive)) { - if (item.findModsByXmlid("HARDENED")) { - const levels = parseInt(item.system.value) || 0; - PD -= levels; - } - } - - // ED bought as hardened - for (const item of activeDefenses.filter((o) => o.system.XMLID == "ED" && o.isActive)) { - if (item.findModsByXmlid("HARDENED")) { - const levels = parseInt(item.system.value) || 0; - ED -= levels; - } - } - - // Bases & Vehicles have resistant PD & ED - if (["base2", "vehicle"].includes(targetActor?.type)) { - rPD += PD; - PD = 0; - rED += ED; - ED = 0; - } - - // Impenetrable (defense vs penetrating) - let impenetrableValue = 0; - - // tags (defenses) will be displayed on apply damage card - let defenseTags = []; - - switch (attackType) { - case "physical": - if (PD > 0) - defenseTags.push({ - name: "PD", - value: PD, - resistant: false, - title: "Natural PD", - }); - if (rPD > 0) - defenseTags.push({ - name: "rPD", - value: rPD, - resistant: true, - title: "resistant PD", - }); - break; - case "energy": - if (ED > 0) - defenseTags.push({ - name: "ED", - value: ED, - resistant: false, - title: "Natural ED", - }); - if (rED > 0) - defenseTags.push({ - name: "rED", - value: rED, - resistant: true, - title: "resistant ED", - }); - break; - case "mental": - case "adjustment": - case "transform": - break; - } - - // Armor Piercing of natural PD and ED - // Notice the tag (above) shows full defenses, but we half it here - if (piercing) { - PD = Math.round(PD / 2); - ED = Math.round(ED / 2); - rPD = Math.round(rPD / 2); - rED = Math.round(rED / 2); - } - - // TODO: These should be set at creation time rather than each use. - for (const activeDefense of activeDefenses) { - const xmlid = activeDefense.system.XMLID; - let value = parseInt(activeDefense.system.value) || 0; - let tagXmlIds = []; - - // PD & ED - if (xmlid === "PD") { - // Making sure this isn't an AE that is already included in the natural PD numbers - if (activeDefense.effects.size > 0) { - PD -= parseInt(activeDefense.system.LEVELS); - } - activeDefense.system.defenseType = "pd"; - } else if (xmlid === "ED") { - // Making sure this isn't an AE that is already included in the natural ED numbers - if (activeDefense.effects.size > 0) { - ED -= parseInt(activeDefense.system.LEVELS); - } - activeDefense.system.defenseType = "ed"; - } else if (["FORCEFIELD", "ARMOR"].includes(xmlid)) { - //"FORCEWALL" not typically a defense on a character. Englobing not supported yet. - // Resistant Defenses - switch (attackType) { - case "physical": - value = parseInt(activeDefense.system.PDLEVELS) || 0; - activeDefense.system.defenseType = "pd"; - activeDefense.system.resistant = true; - break; - - case "energy": - value = parseInt(activeDefense.system.EDLEVELS) || 0; - activeDefense.system.defenseType = "ed"; - activeDefense.system.resistant = true; - break; - - case "mental": - value = parseInt(activeDefense.system.MDLEVELS) || 0; - activeDefense.system.defenseType = "md"; - activeDefense.system.resistant = true; - break; - - case "adjustment": - case "transform": - value = parseInt(activeDefense.system.POWDLEVELS) || 0; - activeDefense.system.defenseType = "powd"; - activeDefense.system.resistant = true; - break; - } - } else if (["POWERDEFENSE"].includes(xmlid)) { - switch (attackType) { - case "adjustment": - activeDefense.system.defenseType = "powd"; - break; - case "transform": - activeDefense.system.defenseType = "powd"; - break; - } - } else if (["MENTALDEFENSE"].includes(xmlid)) { - switch (attackType) { - case "mental": - activeDefense.system.defenseType = "md"; - break; - } - } else if (["COMBAT_LUCK"].includes(xmlid)) { - switch (attackType) { - case "physical": - activeDefense.system.defenseType = "pd"; - value = (parseInt(activeDefense.system.value) || 0) * 3; - activeDefense.system.resistant = true; - activeDefense.system.hardened = 1; - if (!activeDefense.is5e) { - activeDefense.system.impenetrable = 1; - } - break; +export function defenseConditionalCheckedByDefault(defenseItem, attackingItem) { + // Does attacking item have a special effect + if (!attackingItem.system.SFX) return false; - case "energy": - activeDefense.system.defenseType = "ed"; - value = (parseInt(activeDefense.system.value) || 0) * 3; - activeDefense.system.resistant = true; - activeDefense.system.hardened = 1; - if (!activeDefense.is5e) { - activeDefense.system.impenetrable = 1; + // Double check to make sure defense is conditional + const conditionals = (defenseItem.system.MODIFIER || []).filter((p) => + ["ONLYAGAINSTLIMITEDTYPE", "CONDITIONALPOWER"].includes(p.XMLID), + ); + if (conditionals.length === 0) return false; + + // Loop thru all conditionals to find some sort of a match + for (const condition of conditionals) { + switch (condition.XMLID) { + case "ONLYAGAINSTLIMITEDTYPE": + // Only Works Against Cold + for (const sfx of attackingItem.system.SFX.split("/")) { + if (condition.ALIAS.match(new RegExp(sfx, "i"))) { + return true; } - break; - } - } - - if (!value && ["DAMAGEREDUCTION"].includes(xmlid) && activeDefense.system.INPUT.toLowerCase() == attackType) { - value = parseInt(activeDefense.system.OPTIONID.match(/\d+/)) || 0; - activeDefense.system.resistant = activeDefense.system.OPTIONID.match(/RESISTANT/) ? true : false; - switch (attackType) { - case "physical": - activeDefense.system.defenseType = "drp"; - break; - - case "energy": - activeDefense.system.defenseType = "dre"; - break; - - case "mental": - activeDefense.system.defenseType = "drm"; - break; - } - } - - if (!value && ["DAMAGENEGATION"].includes(xmlid)) { - switch (attackType) { - case "physical": - activeDefense.system.defenseType = "dnp"; - value = parseInt(activeDefense.system.ADDER.find((o) => o.XMLID == "PHYSICAL")?.LEVELS) || 0; - break; - - case "energy": - activeDefense.system.defenseType = "dne"; - value = parseInt(activeDefense.system.ADDER.find((o) => o.XMLID == "ENERGY")?.LEVELS) || 0; - break; - - case "mental": - activeDefense.system.defenseType = "dnm"; - value = parseInt(activeDefense.system.ADDER.find((o) => o.XMLID == "MENTAL")?.LEVELS) || 0; - break; - } - } - - // Knockback Resistance - if (game.settings.get(HEROSYS.module, "knockback") && attackItem.system.knockbackMultiplier) { - if (["KBRESISTANCE", "DENSITYINCREASE"].includes(xmlid)) { - let _value = value * (targetActor.system.is5e ? 1 : 2); - knockbackResistanceValue += _value; - defenseTags.push({ - value: _value, - name: "KB" + tagXmlIds, - title: activeDefense.name, - }); - } - - if (["GROWTH"].includes(xmlid)) { - const configPowerInfo = getPowerInfo({ item: activeDefense }); - const details = configPowerInfo?.details(activeDefense) || {}; - let _value = details.kb; - knockbackResistanceValue += _value; - defenseTags.push({ - value: _value, - name: "KB" + tagXmlIds, - title: activeDefense.name, - }); - } - - if (["SHRINKING"].includes(xmlid)) { - let _value = -value * (targetActor.system.is5e ? 3 : 6); - knockbackResistanceValue += _value; - defenseTags.push({ - value: _value, - name: "KB" + tagXmlIds, - title: activeDefense.name, - }); - } - } - - let valueAp = value; - let valueImp = 0; - - // Hardened - const hardened = - (activeDefense.system.hardened || 0) + parseInt(activeDefense.findModsByXmlid("HARDENED")?.LEVELS || 0); - if (hardened > 0) { - tagXmlIds.push(`h${hardened}`); - } - - // Impenetrable - const impenetrable = - (activeDefense.system.impenetrable || 0) + - parseInt(activeDefense.findModsByXmlid("IMPENETRABLE")?.LEVELS || 0); - if (impenetrable > 0) { - tagXmlIds.push(`i${impenetrable}`); - } - - // Armor Piercing - if (piercing > hardened) { - valueAp = Math.round(valueAp / 2); - } - - // Penetrating (defense is hardened in 5e and impenetrable in 6e) - if ((activeDefense.is5e && penetrating <= hardened) || (!activeDefense.is5e && penetrating <= impenetrable)) { - valueImp = valueAp; - } - - const protectionType = (activeDefense.system.resistant ? "r" : "") + activeDefense.system.defenseType; - switch (protectionType) { - case "pd": // Physical Defense - PD += valueAp; - if (attackType === "physical" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "PD" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "ed": // Energy Defense - ED += valueAp; - if (attackType === "energy" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "ED" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "md": // Mental Defense - MD += valueAp; - if (attackType === "mental" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "MD" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - impenetrableValue += valueImp; } break; - - case "powd": // Power Defense - POWD += valueAp; - if (["adjustment", "transform", "avad"].includes(attackType)) { - if (valueAp > 0) - defenseTags.push({ - name: "POWD" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "rpd": // Resistant PD - rPD += valueAp; - if (attackType === "physical" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "rPD" + tagXmlIds, - value: value, - resistant: true, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "red": // Resistant ED - rED += valueAp; - if (attackType === "energy" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "rED" + tagXmlIds, - value: value, - resistant: true, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "rmd": // Resistant MD - rMD += valueAp; - if (attackType === "mental" || attackType === "avad") { - if (valueAp > 0) - defenseTags.push({ - name: "rMD" + tagXmlIds, - value: value, - resistant: true, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "rpowd": // Resistant Power Defense - rPOWD += valueAp; - if (["adjustment", "transform", "avad"].includes(attackType)) { - if (valueAp > 0) - defenseTags.push({ - name: "rPOWD" + tagXmlIds, - value: value, - resistant: true, - title: activeDefense.name, - }); - impenetrableValue += valueImp; - } - break; - - case "drp": // Damage Reduction Physical - case "rdrp": - if (value > 0) - defenseTags.push({ - name: "drp" + tagXmlIds, - value: `${activeDefense.system.resistant ? "r" : ""}${value}%`, - resistant: activeDefense.system.resistant, - title: activeDefense.name, - }); - DRP = Math.max(DRP, value); - break; - - case "dre": // Damage Reduction Energy - case "rdre": - if (value > 0) - defenseTags.push({ - name: "dre" + tagXmlIds, - value: `${activeDefense.system.resistant ? "r" : ""}${value}%`, - resistant: activeDefense.system.resistant, - title: activeDefense.name, - }); - DRE = Math.max(DRE, value); - break; - - case "drm": // Damage Reduction Mental - case "rdrm": - if (value > 0) - defenseTags.push({ - name: "drm" + tagXmlIds, - value: `${activeDefense.system.resistant ? "r" : ""}${value}%`, - resistant: activeDefense.system.resistant, - title: activeDefense.name, - }); - DRM = Math.max(DRM, value); - break; - - case "dnp": // Damage Negation Physical - if (value > 0) - defenseTags.push({ - name: "dnp" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - DNP += value; - break; - - case "dne": // Damage Negation Energy - if (value > 0) - defenseTags.push({ - name: "dne" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - DNE += value; - break; - - case "dnm": // Damage Negation Mental - if (value > 0) - defenseTags.push({ - name: "dnm" + tagXmlIds, - value: value, - resistant: false, - title: activeDefense.name, - }); - DNM += value; - break; - - case "kbr": // Knockback Resistance - knockbackResistanceValue += value; - if (attackType != "mental" && game.settings.get(HEROSYS.module, "knockback")) { - defenseTags.push({ - name: "KB Resistance" + tagXmlIds, - value: value, - title: activeDefense.name, - }); + case "CONDITIONALPOWER": + // Power does not work in Very Uncommon Circumstances + for (const sfx of attackingItem.system.SFX.split("/")) { + if (condition.ALIAS.match(new RegExp(sfx, "i"))) { + return false; + } } - break; + return true; default: - // TODO: Mostly likely this is flash defense missing - // if (game.settings.get(game.system.id, "alphaTesting")) { - // const warnMessage = `${activeDefense.name}: ${activeDefense.system.defenseType} not yet supported!`; - // ui.notifications.warn(warnMessage); - // HEROSYS.log(false, warnMessage); - // } - break; + console.warn("Unknown conditional", condition); } } - let defenseValue = 0; - let resistantValue = 0; - let damageReductionValue = 0; - let damageNegationValue = 0; - switch (attackType) { - case "physical": - defenseValue = PD; - resistantValue = rPD; - //impenetrableValue = Math.max(PD, rPD); - damageReductionValue = DRP; - damageNegationValue = DNP; - break; - - case "energy": - defenseValue = ED; - resistantValue = rED; - //impenetrableValue = Math.max(ED, rED); - damageReductionValue = DRE; - damageNegationValue = DNE; - break; - - case "mental": - defenseValue = MD; - resistantValue = rMD; - //impenetrableValue = Math.max(MD, rMD); - damageReductionValue = DRM; - damageNegationValue = DNM; - break; - - case "adjustment": - defenseValue = POWD; - resistantValue = rPOWD; - //impenetrableValue = Math.max(POWD, rPOWD); - damageReductionValue = DRM; - damageNegationValue = DNM; - break; - - case "transform": - defenseValue = POWD; - resistantValue = rPOWD; - //impenetrableValue = Math.max(POWD, rPOWD); - damageReductionValue = DRM; - damageNegationValue = DNM; - break; - - case "avad": - defenseValue = PD + ED + MD + POWD; - resistantValue = rPD + rED + rMD + rPOWD; - //impenetrableValue = Math.max(PD, rPD) + Math.max(ED, rED) + Math.max(MD, rMD) + Math.max(POWD, rPOWD); - damageReductionValue = DRM; - damageNegationValue = DNM; - defenseTags; - break; - } - - return { - defenseValue, - resistantValue, - impenetrableValue, - damageReductionValue, - damageNegationValue, - knockbackResistanceValue, - defenseTags, - defenseTotalValue: defenseValue + resistantValue, - }; + return false; } diff --git a/templates/actor/actor-sheet-partial-powers-item.hbs b/templates/actor/actor-sheet-partial-powers-item.hbs index fd3e4030..93e93430 100644 --- a/templates/actor/actor-sheet-partial-powers-item.hbs +++ b/templates/actor/actor-sheet-partial-powers-item.hbs @@ -24,7 +24,8 @@ height="24" /> {{/if}} - {{#if (eq this.system.XMLID "COMPOUNDPOWER")}} + + {{#if (eq this.system.XMLID "COMPOUNDPOWER")}} {{this.compoundCost}} {{else}} {{#if (eq this.parentItem.system.XMLID "COMPOUNDPOWER")}}