From 0ef4359fe82b4413904e2ee79e612684e78a20e3 Mon Sep 17 00:00:00 2001 From: aeauseth Date: Tue, 8 Oct 2024 11:47:35 -0700 Subject: [PATCH 1/5] Talent/Skill/Perk as Powers now toggle #1288 --- module/item/item-attack-application.mjs | 313 ++++++++++--------- scss/components/_actor-sheet.scss | 2 +- templates/attack/item-attack-application.hbs | 30 +- 3 files changed, 188 insertions(+), 157 deletions(-) diff --git a/module/item/item-attack-application.mjs b/module/item/item-attack-application.mjs index b2618b7c0..218b53be5 100644 --- a/module/item/item-attack-application.mjs +++ b/module/item/item-attack-application.mjs @@ -80,153 +80,169 @@ export class ItemAttackFormApplication extends FormApplication { const data = this.data; const item = data.item; - //data.targets = game.user.targets; - data.targets = Array.from(game.user.targets); - - if (data.targets.length === 0 && item.system.XMLID === "MINDSCAN" && game.user.isGM) { - data.targets = foundry.utils - .deepClone(canvas.tokens.controlled) - .filter((t) => t.actor?.id != item.actor?.id); - } - - // Initialize aim to the default option values - this.data.aim ??= "none"; - this.data.aimSide ??= "none"; - - data.ocvMod ??= item.system.ocv; - data.dcvMod ??= item.system.dcv; - data.omcvMod ??= item.system.ocv; //TODO: May need to make a distinction between OCV/OMCV - data.dmcvMod ??= item.system.dcv; - data.effectiveStr ??= data.str; - data.effectiveLevels ??= data.item.system.LEVELS; - - data.hitLoc = []; - data.useHitLoc = false; - const aoe = item.AoeAttackParameters({ levels: data.effectiveLevels }); // getAoeModifier(); - if (game.settings.get(HEROSYS.module, "hit locations") && !item.system.noHitLocations && !aoe) { - for (const key of Object.keys(CONFIG.HERO.hitLocations)) { - data.hitLoc.push({ key: key, label: key }); + try { + //data.targets = game.user.targets; + data.targets = Array.from(game.user.targets); + + if (data.targets.length === 0 && item.system.XMLID === "MINDSCAN" && game.user.isGM) { + data.targets = foundry.utils + .deepClone(canvas.tokens.controlled) + .filter((t) => t.actor?.id != item.actor?.id); } - data.useHitLoc = true; - } - // Allow targeting of ENTANGLES & FOCI - // if (data.targets.length === 1) { - // for (const entry of data.targets[0].actor?.targetableItems) { - // data.hitLoc.push({ key: entry.uuid, label: entry.name }); //disabled: true - // data.useHitLoc = true; - // } - // } - if (data.useHitLoc) { - data.hitLoc = [{ key: "none", label: "None" }, ...data.hitLoc]; - } + // Initialize aim to the default option values + this.data.aim ??= "none"; + this.data.aimSide ??= "none"; + + data.ocvMod ??= item.system.ocv; + data.dcvMod ??= item.system.dcv; + data.omcvMod ??= item.system.ocv; //TODO: May need to make a distinction between OCV/OMCV + data.dmcvMod ??= item.system.dcv; + data.effectiveStr ??= data.str; + data.effectiveLevels ??= data.item.system.LEVELS; + + // Is there an ENTANGLE on any of the targets + // If so assume we are targeting the entangle + const entangles = []; + for (const target of data.targets) { + const ae = target.actor?.temporaryEffects.find((o) => o.flags.XMLID === "ENTANGLE"); + if (ae) { + entangles.push(ae); + } + } + data.entangleExists = true; + data.targetEntangle ??= true; + + data.hitLoc = []; + data.useHitLoc = false; + const aoe = item.AoeAttackParameters({ levels: data.effectiveLevels }); // getAoeModifier(); + if (game.settings.get(HEROSYS.module, "hit locations") && !item.system.noHitLocations && !aoe) { + for (const key of Object.keys(CONFIG.HERO.hitLocations)) { + data.hitLoc.push({ key: key, label: key }); + } + data.useHitLoc = true; + } - if (aoe) { - data.aoeText = aoe.OPTION_ALIAS; - // if (!item.system.areaOfEffect) { - // ui.notifications.error(`${item.system.ALIAS || item.name} has invalid AOE definition.`); + // Allow targeting of ENTANGLES & FOCI + // if (data.targets.length === 1) { + // for (const entry of data.targets[0].actor?.targetableItems) { + // data.hitLoc.push({ key: entry.uuid, label: entry.name }); //disabled: true + // data.useHitLoc = true; + // } // } - const levels = aoe.value; //item.system.areaOfEffect.value; //parseInt(aoe.LEVELS) || parseInt(aoe.levels); - if (levels) { - data.aoeText += ` (${levels}${getSystemDisplayUnits(item.actor.is5e)})`; + if (data.useHitLoc) { + data.hitLoc = [{ key: "none", label: "None" }, ...data.hitLoc]; } - if (this.getAoeTemplate() || game.user.targets.size > 0) { - data.noTargets = false; + if (aoe) { + data.aoeText = aoe.OPTION_ALIAS; + // if (!item.system.areaOfEffect) { + // ui.notifications.error(`${item.system.ALIAS || item.name} has invalid AOE definition.`); + // } + const levels = aoe.value; //item.system.areaOfEffect.value; //parseInt(aoe.LEVELS) || parseInt(aoe.levels); + if (levels) { + data.aoeText += ` (${levels}${getSystemDisplayUnits(item.actor.is5e)})`; + } + + if (this.getAoeTemplate() || game.user.targets.size > 0) { + data.noTargets = false; + } else { + data.noTargets = true; + } } else { - data.noTargets = true; + data.noTargets = game.user.targets.size === 0; + data.aoeText = null; } - } else { - data.noTargets = game.user.targets.size === 0; - data.aoeText = null; - } - // Boostable Charges - if (item.system.charges?.value > 1) { - data.boostableCharges = item.system.charges.value - 1; - } + // Boostable Charges + if (item.system.charges?.value > 1) { + data.boostableCharges = item.system.charges.value - 1; + } - // MINDSCAN - if (item.system.XMLID === "MINDSCAN") { - data.mindScanChoices = CONFIG.HERO.mindScanChoices; + // MINDSCAN + if (item.system.XMLID === "MINDSCAN") { + data.mindScanChoices = CONFIG.HERO.mindScanChoices; - data.mindScanFamiliarOptions = []; - data.mindScanFamiliarOptions.push({ - label: `+0`, - key: 0, - }); - for (let i = 1; i <= 5; i++) { + data.mindScanFamiliarOptions = []; data.mindScanFamiliarOptions.push({ - label: `+${i} Familiar mind`, - key: i, + label: `+0`, + key: 0, }); - } - for (let i = 1; i <= 5; i++) { - data.mindScanFamiliarOptions.push({ - label: `${-i} Unfamiliar mind`, - key: -i, - }); - } - } - - // Combat Skill Levels - // data.cslChoices = null; - // data.csl = null; - // data.cslSkill = null; - const csls = CombatSkillLevelsForAttack(item); - data.csls = undefined; - for (const csl of csls) { - let entry = {}; - if (csl && csl.skill) { - entry.cslSkill = csl.skill; - let mental = csl.skill.system.XMLID === "MENTAL_COMBAT_LEVELS"; - let _ocv = mental ? "omcv" : "ocv"; - let _dcv = mental ? "dmcv" : "dcv"; - entry.cslChoices = { [_ocv]: _ocv }; - if (csl.skill.system.OPTION != "SINGLE") { - entry.cslChoices[_dcv] = _dcv; - entry.cslChoices.dc = "dc"; + for (let i = 1; i <= 5; i++) { + data.mindScanFamiliarOptions.push({ + label: `+${i} Familiar mind`, + key: i, + }); } - - // CSL radioBoxes names - entry.csl = []; - for (let c = 0; c < parseInt(csl.skill.system.LEVELS || 0); c++) { - entry.csl.push({ - name: `${csl.skill.id}.system.csl.${c}`, - value: csl.skill.system.csl ? csl.skill.system.csl[c] : "undefined", + for (let i = 1; i <= 5; i++) { + data.mindScanFamiliarOptions.push({ + label: `${-i} Unfamiliar mind`, + key: -i, }); } + } - data.csls ??= []; - data.csls.push(entry); + // Combat Skill Levels + // data.cslChoices = null; + // data.csl = null; + // data.cslSkill = null; + const csls = CombatSkillLevelsForAttack(item); + data.csls = undefined; + for (const csl of csls) { + let entry = {}; + if (csl && csl.skill) { + entry.cslSkill = csl.skill; + let mental = csl.skill.system.XMLID === "MENTAL_COMBAT_LEVELS"; + let _ocv = mental ? "omcv" : "ocv"; + let _dcv = mental ? "dmcv" : "dcv"; + entry.cslChoices = { [_ocv]: _ocv }; + if (csl.skill.system.OPTION != "SINGLE") { + entry.cslChoices[_dcv] = _dcv; + entry.cslChoices.dc = "dc"; + } + + // CSL radioBoxes names + entry.csl = []; + for (let c = 0; c < parseInt(csl.skill.system.LEVELS || 0); c++) { + entry.csl.push({ + name: `${csl.skill.id}.system.csl.${c}`, + value: csl.skill.system.csl ? csl.skill.system.csl[c] : "undefined", + }); + } + + data.csls ??= []; + data.csls.push(entry); + } } - } - // DEADLYBLOW - const DEADLYBLOW = item.actor.items.find((o) => o.system.XMLID === "DEADLYBLOW"); - if (DEADLYBLOW) { - item.system.conditionalAttacks ??= {}; - item.system.conditionalAttacks[DEADLYBLOW.id] ??= { - ...DEADLYBLOW, - id: DEADLYBLOW.id, - }; - item.system.conditionalAttacks[DEADLYBLOW.id].checked ??= true; - } + // DEADLYBLOW + const DEADLYBLOW = item.actor.items.find((o) => o.system.XMLID === "DEADLYBLOW"); + if (DEADLYBLOW) { + item.system.conditionalAttacks ??= {}; + item.system.conditionalAttacks[DEADLYBLOW.id] ??= { + ...DEADLYBLOW, + id: DEADLYBLOW.id, + }; + item.system.conditionalAttacks[DEADLYBLOW.id].checked ??= true; + } - data.action = Attack.getActionInfo( - data.item, - data.targets, - data.formData, // use formdata to include player options from the form - ); - // the title seems to be fixed when the form is initialized, - // and doesn't change afterwards even if we come through here again - // todo: figure out how to adjust the title when we want it to - if (data.action.maneuver.isMultipleAttack) { - this.options.title = `${this.data?.item?.actor?.name} multiple attack.`; - } else if (data.action.maneuver.isHaymakerAttack) { - this.options.title = `${this.data?.item?.actor?.name} haymaker attack.`; - } else { - this.options.title = `${this.data?.item?.actor?.name} select attack options and roll to hit`; + data.action = Attack.getActionInfo( + data.item, + data.targets, + data.formData, // use formdata to include player options from the form + ); + // the title seems to be fixed when the form is initialized, + // and doesn't change afterwards even if we come through here again + // todo: figure out how to adjust the title when we want it to + if (data.action.maneuver.isMultipleAttack) { + this.options.title = `${this.data?.item?.actor?.name} multiple attack.`; + } else if (data.action.maneuver.isHaymakerAttack) { + this.options.title = `${this.data?.item?.actor?.name} haymaker attack.`; + } else { + this.options.title = `${this.data?.item?.actor?.name} select attack options and roll to hit`; + } + } catch (ex) { + console.error(ex); } return data; } @@ -261,6 +277,14 @@ export class ItemAttackFormApplication extends FormApplication { } async _updateObject(event, formData) { + // Take all the data we updated in the form and apply it. + this.data = { ...this.data, ...formData }; + + // We may need to tweak the results + this.render(); + + return; + // changes to the form pass through here if (event.submitter?.name === "roll") { canvas.tokens.activate(); @@ -314,26 +338,21 @@ export class ItemAttackFormApplication extends FormApplication { this._updateCsl(event, formData); - this.data.aim = formData.aim; - this.data.aimSide = formData.aimSide; - - this.data.ocvMod = formData.ocvMod; - this.data.dcvMod = formData.dcvMod; - this.data.omcvMod = formData.omcvMod; - this.data.dmcvMod = formData.dmcvMod; - - this.data.effectiveStr = formData.effectiveStr; - this.data.effectiveLevels = formData.effectiveLevels; - - this.data.mindScanMinds = formData.mindScanMinds; - this.data.mindScanFamiliar = formData.mindScanFamiliar; - - this.data.boostableCharges = Math.max( - 0, - Math.min(parseInt(formData.boostableCharges), this.data.item.charges?.value - 1), - ); - - this.data.velocity = parseInt(formData.velocity || 0); + // this.data.aim = formData.aim; + // this.data.aimSide = formData.aimSide; + // this.data.ocvMod = formData.ocvMod; + // this.data.dcvMod = formData.dcvMod; + // this.data.omcvMod = formData.omcvMod; + // this.data.dmcvMod = formData.dmcvMod; + // this.data.effectiveStr = formData.effectiveStr; + // this.data.effectiveLevels = formData.effectiveLevels; + // this.data.mindScanMinds = formData.mindScanMinds; + // this.data.mindScanFamiliar = formData.mindScanFamiliar; + // this.data.boostableCharges = Math.max( + // 0, + // Math.min(parseInt(formData.boostableCharges), this.data.item.charges?.value - 1), + // ); + //this.data.velocity = parseInt(formData.velocity || 0); // const aoe = this.data.item.AoeAttackParameters({ levels: this.data.effectiveLevels }); // getAoeModifier(); // if (aoe) { diff --git a/scss/components/_actor-sheet.scss b/scss/components/_actor-sheet.scss index b9100304b..5815b6006 100644 --- a/scss/components/_actor-sheet.scss +++ b/scss/components/_actor-sheet.scss @@ -423,7 +423,7 @@ form div.attack-card div.description { padding-left: 5px; } - #item-attack-form-application .noflexwrap input[type="text"] +#item-attack-form-application .noflexwrap input[type="text"], #item-attack-form-application .noflexwrap input[type="number"] { width: auto; margin-right: 5px; diff --git a/templates/attack/item-attack-application.hbs b/templates/attack/item-attack-application.hbs index 21d80b8fc..da1a03a5f 100644 --- a/templates/attack/item-attack-application.hbs +++ b/templates/attack/item-attack-application.hbs @@ -117,23 +117,30 @@ {{/each}} + {{!ENTANGLE}} + {{#if entangleExists}} +
+ + +
+ + {{/if}} + {{!-- Aim (hit location) form group --}} -
+ {{#if useHitLoc}} +
- {{!-- --}}
+ {{/if}} {{!-- Aim side (hit location side) form group --}} -
+ {{#if hitLocSide}} +
+ {{/if}} {{!-- Penalty skill levels --}} -
+ {{#if PENALTY_SKILL_LEVELS}} +
{{PENALTY_SKILL_LEVELS.name}} {{#if PENALTY_SKILL_LEVELS.system.SFX}} ({{PENALTY_SKILL_LEVELS.system.SFX}}){{/if}} : {{PENALTY_SKILL_LEVELS.system.description}} @@ -154,6 +163,7 @@
+ {{/if}} {{#if mindScanChoices}}
@@ -190,6 +200,7 @@
+ {{!-- {{numberInput effectiveLevels name="effectiveLevels" step=1 min=1 max=999}} --}}
@@ -201,6 +212,7 @@
+ {{!-- {{numberInput effectiveStr name="effectiveStr" step=1 min=1 max=999}} --}}
From 181e22aaf58a60c0bdd7eca294edb33e9d417100 Mon Sep 17 00:00:00 2001 From: aeauseth Date: Tue, 8 Oct 2024 11:47:55 -0700 Subject: [PATCH 2/5] Talent/Skill/Perks as powers now toggle #1288 --- CHANGELOG.md | 6 +++++- module/herosystem6e.mjs | 8 ++++---- module/item/item.mjs | 5 +++++ scss/components/_actor-sheet.scss | 2 +- 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7046e60c9..c97eb395a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,10 @@ # Releases -## Version 3.0.100 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) +## Version 3.0.101 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) +- We not longer test/verify this system with FoundryVTT V11. We encourage you upgrade tou FoundryVTT v12. +- Talent/Skill/Perks as powers now toggle [#1288](https://github.com/dmdorman/hero6e-foundryvtt/issues/1230) + +## Version 3.0.100 - Added Effect Panel showing more details about effects on a token. - Initial support for MULTIPLE ATTACK maneuver. - Fixed issue with powers sometimes uploading out of order. [#1138](https://github.com/dmdorman/hero6e-foundryvtt/issues/1138) diff --git a/module/herosystem6e.mjs b/module/herosystem6e.mjs index 514fffcff..da6da73bc 100644 --- a/module/herosystem6e.mjs +++ b/module/herosystem6e.mjs @@ -182,10 +182,10 @@ Hooks.on("canvasReady", () => { }); Hooks.once("ready", async function () { - // if (game.settings.get(game.system.id, "alphaTesting")) { - // CONFIG.compatibility.mode = 0; - // CONFIG.debug.combat = true; - // } + if (game.settings.get(game.system.id, "alphaTesting")) { + CONFIG.compatibility.mode = 0; + //CONFIG.debug.combat = true; + } // Wait to register hotbar drop hook on ready so that modules could register earlier if they want to Hooks.on("hotbarDrop", (bar, data, slot) => createHeroSystem6eMacro(bar, data, slot)); diff --git a/module/item/item.mjs b/module/item/item.mjs index 01aa6f81a..55dad5399 100644 --- a/module/item/item.mjs +++ b/module/item/item.mjs @@ -1154,6 +1154,11 @@ export class HeroSystem6eItem extends Item { item.system.showToggle = true; } + // Talent/Skill/Perk as Powers are technically toggleable + if (item.type === "power" && ["talent", "skill", "perk"].find((o) => item.#baseInfo?.type.includes(o))) { + item.system.showToggle = true; + } + // Endurance item.system.endEstimate = parseInt(item.system.end) || 0; diff --git a/scss/components/_actor-sheet.scss b/scss/components/_actor-sheet.scss index 5815b6006..4539abee0 100644 --- a/scss/components/_actor-sheet.scss +++ b/scss/components/_actor-sheet.scss @@ -423,7 +423,7 @@ form div.attack-card div.description { padding-left: 5px; } -#item-attack-form-application .noflexwrap input[type="text"], #item-attack-form-application .noflexwrap input[type="number"] +t#item-attack-form-application .noflexwrap input[type="text"], #item-attack-form-application .noflexwrap input[type="number"] { width: auto; margin-right: 5px; From 0caa2a6b9d70bca68d2d51b99e9bb24fb0b83b39 Mon Sep 17 00:00:00 2001 From: aeauseth Date: Tue, 8 Oct 2024 22:39:26 -0700 Subject: [PATCH 3/5] Improved attack-item-application & PSLs. Prep for entangle targeting. --- CHANGELOG.md | 1 + module/config.mjs | 2 + module/item/item-attack-application.mjs | 121 +++++++++++-------- module/item/item-attack.mjs | 38 +++--- module/item/item.mjs | 8 ++ module/utility/damage.mjs | 16 +++ scss/components/_actor-sheet.scss | 7 +- templates/attack/item-attack-application.hbs | 88 +++++++++----- 8 files changed, 185 insertions(+), 96 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c97eb395a..1a1684c10 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## Version 3.0.101 [Hero System 6e (Unofficial) v2](https://github.com/dmdorman/hero6e-foundryvtt) - We not longer test/verify this system with FoundryVTT V11. We encourage you upgrade tou FoundryVTT v12. - Talent/Skill/Perks as powers now toggle [#1288](https://github.com/dmdorman/hero6e-foundryvtt/issues/1230) +- Improved Penalty Skill Levels. You can now have more than one PSL for different attacks. ## Version 3.0.100 - Added Effect Panel showing more details about effects on a token. diff --git a/module/config.mjs b/module/config.mjs index 2980dc70d..1566ab3b9 100644 --- a/module/config.mjs +++ b/module/config.mjs @@ -1969,6 +1969,7 @@ function addPower(powerDescription6e, powerOverrideFor5e) { duration: "constant", target: "self only", costEnd: false, + refreshAttackDialogWhenChanged: true, range: HERO.RANGE_TYPES.SELF, editOptions: { showAttacks: true, @@ -2455,6 +2456,7 @@ function addPower(powerDescription6e, powerOverrideFor5e) { target: "self only", range: HERO.RANGE_TYPES.SELF, costEnd: false, + refreshAttackDialogWhenChanged: true, editOptions: { showAttacks: true, editableOption_ALIAS: true, diff --git a/module/item/item-attack-application.mjs b/module/item/item-attack-application.mjs index 218b53be5..c041ba062 100644 --- a/module/item/item-attack-application.mjs +++ b/module/item/item-attack-application.mjs @@ -1,4 +1,4 @@ -import { CombatSkillLevelsForAttack } from "../utility/damage.mjs"; +import { CombatSkillLevelsForAttack, PenaltySkillLevelsForAttack } from "../utility/damage.mjs"; import { processAttackOptions } from "../item/item-attack.mjs"; import { convertSystemUnitsToMetres, getSystemDisplayUnits } from "../utility/units.mjs"; import { HEROSYS } from "../herosystem6e.mjs"; @@ -21,44 +21,52 @@ export class ItemAttackFormApplication extends FormApplication { this.data = data; this.options.title = `${this.data?.item?.actor?.name} roll to hit`; - Hooks.on( - "updateItem", - function (item, changes, options, userId) { - if (!this.rendered) return; + // const _updateItem = async function (item, changes, options, userId) { + // if (!this.rendered) return; - if (item.id === this.data.item.id) { - this.updateItem(item, changes, options, userId); - } + // if (item.id === this.data.item.id) { + // this.updateItem(item, changes, options, userId); + // } - const cslSkill = CombatSkillLevelsForAttack(this.data.item).skill; - if (cslSkill && item.id === cslSkill.id) { - this.updateItem(item, changes, options, userId); - } - if (!cslSkill && data.cslSkill) { - this.updateItem(item, changes, options, userId); - } - }.bind(this), - ); + // const cslSkill = CombatSkillLevelsForAttack(this.data.item).skill; + // if (cslSkill && item.id === cslSkill.id) { + // this.updateItem(item, changes, options, userId); + // } + // if (!cslSkill && data.cslSkill) { + // this.updateItem(item, changes, options, userId); + // } + // }; + // Hooks.on("updateItem", _updateItem.bind(this)); - Hooks.on( - "targetToken", - async function (...args) { - window.setTimeout(() => this.updateItem(...args), 1); - // await this.updateItem(...args); - }.bind(this), - ); + const _targetToken = async function () { + this.refresh(); + }; + Hooks.on("targetToken", _targetToken.bind(this)); - Hooks.on( - "controlToken", - async function (...args) { - window.setTimeout(() => this.updateItem(...args), 1); - // await this.updateItem(...args); - }.bind(this), - ); + const _controlToken = async function () { + this.refresh(); + }; + Hooks.on("controlToken", _controlToken.bind(this)); + + // If CSLs change on the Actor we need to know + const _updateItem = async function (item) { + //, changes, options, userId) { + if (this.data.actor.id === item.actor.id && item.baseInfo?.refreshAttackDialogWhenChanged) { + this.refresh(); + } + }; + Hooks.on("updateItem", _updateItem.bind(this)); } - async updateItem() { - await this.render(); + // async close(options = {}) { + // Hooks.off("targetToken", this._targetToken); + // Hooks.off("controlToken", this._controlToken); + // Hooks.off("updateItem", this._updateItem); + // return super.close(options); + // } + + refresh() { + foundry.utils.debounce(this.render(), 100); } static get defaultOptions() { @@ -94,12 +102,18 @@ export class ItemAttackFormApplication extends FormApplication { this.data.aim ??= "none"; this.data.aimSide ??= "none"; - data.ocvMod ??= item.system.ocv; - data.dcvMod ??= item.system.dcv; - data.omcvMod ??= item.system.ocv; //TODO: May need to make a distinction between OCV/OMCV - data.dmcvMod ??= item.system.dcv; - data.effectiveStr ??= data.str; - data.effectiveLevels ??= data.item.system.LEVELS; + // We are using the numberInput handlebar helper which requires NUMBERS, thus the parseInt + // Set the initial values on the form + data.ocvMod ??= parseInt(item.system.ocv); + data.dcvMod ??= parseInt(item.system.dcv); + data.omcvMod ??= parseInt(item.system.ocv); //TODO: May need to make a distinction between OCV/OMCV + data.dmcvMod ??= parseInt(item.system.dcv); + data.effectiveStr ??= parseInt(data.str); + data.effectiveLevels ??= parseInt(data.item.system.LEVELS); + + // Penalty Skill Levels + // Currently only supports range PSL + data.psls = PenaltySkillLevelsForAttack(item).filter((o) => o.system.penalty === "range"); // Is there an ENTANGLE on any of the targets // If so assume we are targeting the entangle @@ -110,7 +124,7 @@ export class ItemAttackFormApplication extends FormApplication { entangles.push(ae); } } - data.entangleExists = true; + data.entangleExists = entangles.length > 0 ? true : false; data.targetEntangle ??= true; data.hitLoc = []; @@ -277,15 +291,26 @@ export class ItemAttackFormApplication extends FormApplication { } async _updateObject(event, formData) { - // Take all the data we updated in the form and apply it. - this.data = { ...this.data, ...formData }; - - // We may need to tweak the results - this.render(); + // CSL & PSL format is non-standard, need to deal with those + const extendedFormData = foundry.utils.expandObject(formData); + const updates = []; + for (const key of Object.keys(extendedFormData)) { + if (key.length === 16) { + const extendedItem = this.data.actor.items.find((o) => o.id === key); + if (extendedItem) { + updates.push({ _id: key, ...extendedFormData[key] }); + delete extendedFormData[key]; + } + } + } + if (updates) { + const actualUpdates = await this.data.actor.updateEmbeddedDocuments("Item", updates); + console.log(actualUpdates); + } - return; + // Take all the data we updated in the form and apply it. + this.data = foundry.utils.mergeObject(this.data, extendedFormData); - // changes to the form pass through here if (event.submitter?.name === "roll") { canvas.tokens.activate(); await this.close(); @@ -336,7 +361,7 @@ export class ItemAttackFormApplication extends FormApplication { // collect the changed data; all of these changes can go into get data this.data.formData = { ...this.data.formData, ...formData }; - this._updateCsl(event, formData); + //this._updateCsl(event, formData); // this.data.aim = formData.aim; // this.data.aimSide = formData.aimSide; diff --git a/module/item/item-attack.mjs b/module/item/item-attack.mjs index b701d34b0..c84d51175 100644 --- a/module/item/item-attack.mjs +++ b/module/item/item-attack.mjs @@ -3,7 +3,12 @@ import { getPowerInfo, getCharacteristicInfoArrayForActor } from "../utility/uti import { determineDefense } from "../utility/defense.mjs"; import { HeroSystem6eActorActiveEffects } from "../actor/actor-active-effects.mjs"; import { RoundFavorPlayerDown, RoundFavorPlayerUp } from "../utility/round.mjs"; -import { calculateDiceFormulaParts, CombatSkillLevelsForAttack, convertToDcFromItem } from "../utility/damage.mjs"; +import { + calculateDiceFormulaParts, + CombatSkillLevelsForAttack, + PenaltySkillLevelsForAttack, + convertToDcFromItem, +} from "../utility/damage.mjs"; import { performAdjustment, renderAdjustmentChatCards } from "../utility/adjustment.mjs"; import { getRoundedDownDistanceInSystemUnits, getSystemDisplayUnits } from "../utility/units.mjs"; import { HeroSystem6eItem, RequiresASkillRollCheck } from "../item/item.mjs"; @@ -118,12 +123,6 @@ export async function AttackOptions(item) { //data.hitLoc = CONFIG.HERO.hitLocations; data.hitLocSide = game.settings.get(HEROSYS.module, "hitLocTracking") === "all" ? CONFIG.HERO.hitLocationSide : null; - - // Penalty Skill Levels - const PENALTY_SKILL_LEVELS = actor.items.find((o) => o.system.XMLID === "PENALTY_SKILL_LEVELS"); - if (PENALTY_SKILL_LEVELS) { - data.PENALTY_SKILL_LEVELS = PENALTY_SKILL_LEVELS; - } } await new ItemAttackFormApplication(data).render(true); @@ -196,8 +195,8 @@ export async function AttackAoeToHit(item, options) { const rangePenalty = -calculateRangePenaltyFromDistanceInMetres(distanceToken); // PENALTY_SKILL_LEVELS (range) - const pslRange = actor.items.find( - (o) => o.system.XMLID === "PENALTY_SKILL_LEVELS" && o.system.penalty === "range", + const pslRange = PenaltySkillLevelsForAttack(item).find( + (o) => o.system.penalty === "range" && o.system.checked, ); if (pslRange) { const pslValue = Math.min(parseInt(pslRange.system.LEVELS), -rangePenalty); @@ -370,7 +369,7 @@ export async function AttackToHit(item, options) { // There are no range penalties if this is a line of sight power or it has been bought with // no range modifiers. if ( - targets.size > 0 && + targets.length > 0 && !( item.system.range === CONFIG.HERO.RANGE_TYPES.LINE_OF_SIGHT || item.system.range === CONFIG.HERO.RANGE_TYPES.SPECIAL || @@ -385,13 +384,13 @@ export async function AttackToHit(item, options) { ui.notifications.warn(`${actor.name} has no token in this scene. Range penalties will be ignored.`); } - const target = targets.first(); + const target = targets[0]; const distance = token ? calculateDistanceBetween(token, target) : 0; const rangePenalty = -calculateRangePenaltyFromDistanceInMetres(distance); // PENALTY_SKILL_LEVELS (range) - const pslRange = actor.items.find( - (o) => o.system.XMLID === "PENALTY_SKILL_LEVELS" && o.system.penalty === "range", + const pslRange = PenaltySkillLevelsForAttack(item).find( + (o) => o.system.penalty === "range" && o.system.checked, ); if (pslRange) { const pslValue = Math.min(parseInt(pslRange.system.LEVELS), -rangePenalty); @@ -514,13 +513,12 @@ export async function AttackToHit(item, options) { // Penalty Skill Levels if (options.usePsl) { - const PENALTY_SKILL_LEVELS = actor.items.find((o) => o.system.XMLID === "PENALTY_SKILL_LEVELS"); - if (PENALTY_SKILL_LEVELS) { - let pslValue = Math.min( - PENALTY_SKILL_LEVELS.system.LEVELS, - Math.abs(CONFIG.HERO.hitLocations[options.aim][3]), - ); - heroRoller.addNumber(pslValue, PENALTY_SKILL_LEVELS.name); + const pslHit = PenaltySkillLevelsForAttack(item).find( + (o) => o.system.penalty === "hitLocation" && o.system.checked, + ); + if (pslHit) { + let pslValue = Math.min(pslHit.system.LEVELS, Math.abs(CONFIG.HERO.hitLocations[options.aim][3])); + heroRoller.addNumber(pslValue, pslHit.name); } } } diff --git a/module/item/item.mjs b/module/item/item.mjs index 55dad5399..538e474b6 100644 --- a/module/item/item.mjs +++ b/module/item/item.mjs @@ -1736,6 +1736,14 @@ export class HeroSystem6eItem extends Item { } } } + + if (this.system.XMLID === "PENALTY_SKILL_LEVELS" && !this.system.penalty) { + if (this.system.OPTION_ALIAS.match(/range/i)) { + this.system.penalty ??= "range"; + } else if (this.system.OPTION_ALIAS.match(/hit/i) || this.system.OPTION_ALIAS.match(/location/i)) { + this.system.penalty ??= "hitLocation"; + } + } } // DESCRIPTION diff --git a/module/utility/damage.mjs b/module/utility/damage.mjs index 08684f791..cc37393dd 100644 --- a/module/utility/damage.mjs +++ b/module/utility/damage.mjs @@ -594,3 +594,19 @@ export function CombatSkillLevelsForAttack(item) { return results; } + +export function PenaltySkillLevelsForAttack(item) { + let results = []; + + // Guard + if (!item.actor) return results; + + const psls = item.actor.items.filter( + (o) => + ["PENALTY_SKILL_LEVELS"].includes(o.system.XMLID) && + (o.system.ADDER || []).find((p) => p.ALIAS === item.system.ALIAS || p.ALIAS === item.name) && + o.system.active != false, + ); + + return psls; +} diff --git a/scss/components/_actor-sheet.scss b/scss/components/_actor-sheet.scss index 4539abee0..2a48f8c5b 100644 --- a/scss/components/_actor-sheet.scss +++ b/scss/components/_actor-sheet.scss @@ -423,9 +423,14 @@ form div.attack-card div.description { padding-left: 5px; } -t#item-attack-form-application .noflexwrap input[type="text"], #item-attack-form-application .noflexwrap input[type="number"] +#item-attack-form-application .noflexwrap input[type="text"], #item-attack-form-application .noflexwrap input[type="number"] { width: auto; margin-right: 5px; margin-left: -4px; //* needed to align to previous rows, not sure why **// +} + +#item-attack-form-application input[type="checkbox"] +{ + margin-left: -2px; } \ No newline at end of file diff --git a/templates/attack/item-attack-application.hbs b/templates/attack/item-attack-application.hbs index da1a03a5f..c40d77aa2 100644 --- a/templates/attack/item-attack-application.hbs +++ b/templates/attack/item-attack-application.hbs @@ -1,4 +1,4 @@ -{{ log 'HEROSYS ITEM ATTACK APPLICATION CARD item-attack-application' this }} +{{!-- {{ log 'HEROSYS ITEM ATTACK APPLICATION CARD item-attack-application' this }} --}}
@@ -120,9 +120,11 @@ {{!ENTANGLE}} {{#if entangleExists}}
- - + +
+ +
{{/if}} @@ -152,46 +154,53 @@ {{/if}} {{!-- Penalty skill levels --}} - {{#if PENALTY_SKILL_LEVELS}} -
- {{PENALTY_SKILL_LEVELS.name}} - {{#if PENALTY_SKILL_LEVELS.system.SFX}} ({{PENALTY_SKILL_LEVELS.system.SFX}}){{/if}} - : {{PENALTY_SKILL_LEVELS.system.description}} -
+ {{!-- {{#each PENALTY_SKILL_LEVELS}} +
+ {{PENALTY_SKILL_LEVELS.name}} + {{#if PENALTY_SKILL_LEVELS.system.SFX}} ({{PENALTY_SKILL_LEVELS.system.SFX}}){{/if}} + : {{PENALTY_SKILL_LEVELS.system.description}} +
+ +
+ + +
+ {{/if}} --}} + -
- - -
- {{/if}} {{#if mindScanChoices}}
- + {{!-- --}} + {{numberInput omcvMod name="omcvMod" step=1 min=-99 max=99}}
- + {{!-- --}} + {{numberInput dmcvMod name="dmcvMod" step=1 min=-99 max=99}}
{{else}}
- + {{!-- --}} + {{numberInput ocvMod name="ocvMod" step=1 min=-99 max=99}}
- + {{!-- --}} + {{numberInput dcvMod name="dcvMod" step=1 min=-99 max=99}}
{{/if}} {{#if showVelocity}}
- + {{!-- --}} + {{numberInput velocity name="velocity" step=1 min=0 max=999 title="Typically you assume a starting velocity of 0, accelerate up to half your full move, then decelerate back to a 0 velocity. A character can accelerate at a rate of 5m per meter. If the Drag Ruler module is enabled, combat has started, and token has moved on its phase, then a velocity estimate is provided. A simplistic solution is to assume velocity is equal to the movement speed."}}
{{/if}} @@ -199,8 +208,8 @@
- - {{!-- {{numberInput effectiveLevels name="effectiveLevels" step=1 min=1 max=999}} --}} + {{!-- --}} + {{numberInput effectiveLevels name="effectiveLevels" step=1 min=1 max=999}}
@@ -211,8 +220,8 @@
- - {{!-- {{numberInput effectiveStr name="effectiveStr" step=1 min=1 max=999}} --}} + {{!-- --}} + {{numberInput effectiveStr name="effectiveStr" step=1 min=1 max=999}}
@@ -221,8 +230,8 @@ {{#if boostableCharges}}
- - + {{!-- --}} + {{numberInput boostableCharges name="boostableCharges" step=1 min=0 max=999}}
{{/if}} @@ -242,6 +251,31 @@
{{/if}} + {{!-- Penalty Skill Levels --}} + {{#if psls.length}} +
+ +
+ + {{#each psls}} + + + + + {{/each}} +
+ + + {{!-- {{this.name}}: --}} + {{this.system.description}} +
+
+
+ {{/if}} + {{#each csls}}