From 0be56cafa8c333701dd5bbe1bd8908d9216f0397 Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Mon, 12 Jul 2021 21:32:22 -0700 Subject: [PATCH 01/88] rewrite accdiff dialog as a FormApplication, so it has a data model --- src/module/helpers/acc_diff.ts | 243 +++++++++++++++++++----------- src/module/macros.ts | 168 +++++++++------------ src/templates/window/acc_diff.hbs | 218 ++++++++++++++------------- 3 files changed, 343 insertions(+), 286 deletions(-) diff --git a/src/module/helpers/acc_diff.ts b/src/module/helpers/acc_diff.ts index 9209c46fa..7fcef78d5 100644 --- a/src/module/helpers/acc_diff.ts +++ b/src/module/helpers/acc_diff.ts @@ -1,101 +1,166 @@ import { TagInstance } from "machine-mind"; +import { LancerActor, LancerActorType } from '../actor/lancer-actor'; +import { gentle_merge } from '../helpers/commons'; -export type AccDiffFlag = "ACCURATE" | "INACCURATE" | "SOFT_COVER" | "HARD_COVER" | "SEEKING"; -export const AccDiffRegistry: Record = { - ACCURATE: 1, - INACCURATE: -1, - SOFT_COVER: -1, - HARD_COVER: -2, - SEEKING: 0, -}; - -export function calcAccDiff() { - let acc = 0; - let diff = 0; - let flags: AccDiffFlag[] = []; - document - .querySelectorAll("[data-acc]:checked:not([disabled])") - .forEach(ele => flags.push(ele.getAttribute("data-acc") as AccDiffFlag)); - document - .querySelectorAll("[data-diff]:checked:not([disabled])") - .forEach(ele => flags.push(ele.getAttribute("data-diff") as AccDiffFlag)); - - const isSeeking = flags.includes("SEEKING"); - for (let flag of flags) { - switch (flag) { - case "SOFT_COVER": - case "HARD_COVER": - diff += isSeeking ? 0 : AccDiffRegistry[flag]; - break; - case "ACCURATE": - acc += 1; - break; - case "INACCURATE": - diff -= 1; - break; - case "SEEKING": - break; - } +export type AccDiffFormData = { + title: string, + baseUntypedAccDiff: { accuracy: number, difficulty: number }, + baseAccDiff: { accuracy: number, difficulty: number }, + targetedAccDiffs: { target: LancerActor, accuracy: number, difficulty: number }[], + accurate: boolean, + inaccurate: boolean, + softCover: boolean, + hardCover: boolean, + seeking: boolean, + seekingDisabled?: string, + totalIcon?: { + value: number, + color: string, + className: string } - return [acc, -diff]; } -function calcManualAccDiff() { - const acc = parseInt((document.querySelector(`#accdiff-other-acc`) as HTMLInputElement)?.value); - const diff = parseInt((document.querySelector(`#accdiff-other-diff`) as HTMLInputElement)?.value); - return [acc, diff]; -} +export class AccDiffForm extends FormApplication { + data: AccDiffFormData; + resolve: ((data: AccDiffFormData) => void) | null = null; + reject: ((v: void) => void) | null = null; + promise: Promise; + + static get defaultOptions() { + return mergeObject(super.defaultOptions, { + template: "systems/lancer/templates/window/acc_diff.hbs", + resizable: false, + submitOnChange: false, + submitOnClose: false, + closeOnSubmit: true, + }); + } + + constructor(data: AccDiffFormData) { + super(data, { title: data.title }); + this.data = data; + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }) + } + + static formDataFromParams( + tags?: TagInstance[], + title?: string, + targets?: LancerActor[], + starting?: [number, number] + ): AccDiffFormData { + let baseAccDiff = { + accuracy: starting ? starting[0] : 0, + difficulty: starting ? starting[1] : 0 + }; + + let ret: AccDiffFormData = { + title: title ? `${title} - Accuracy and Difficulty` : "Accuracy and Difficulty", + baseUntypedAccDiff: baseAccDiff, + baseAccDiff: baseAccDiff, // this'll get overwritten by getData soon -export function tagsToFlags(tags: TagInstance[]): AccDiffFlag[] { - const ret: AccDiffFlag[] = []; - tags.forEach(tag => { - switch (tag.Tag.LID) { - case "tg_accurate": - ret.push("ACCURATE"); - break; - case "tg_inaccurate": - ret.push("INACCURATE"); - break; - case "tg_seeking": - ret.push("SEEKING"); - break; + targetedAccDiffs: (targets || []).map(t => ({ + target: t, + accuracy: 0, + difficulty: 0 + })), + + accurate: false, + inaccurate: false, + softCover: false, + hardCover: false, + seeking: false + }; + + for (let tag of (tags || [])) { + switch (tag.Tag.LID) { + case "tg_accurate": + ret.accurate = true; + break; + case "tg_inaccurate": + ret.inaccurate = true; + break; + case "tg_seeking": + ret.seeking = true; + break; + } } - }); - return ret; -} -// DOM Manipulation -export function toggleCover(toggle: boolean) { - const ret = document.querySelectorAll('[data-accdiff="SOFT_COVER"],[data-accdiff="HARD_COVER"]'); - ret.forEach(ele => (toggle ? ele.removeAttribute("disabled") : ele.setAttribute("disabled", "true"))); -} + return ret; + } + + static fromData( + tags?: TagInstance[], + title?: string, + targets?: LancerActor[], + starting?: [number, number]) { + return new AccDiffForm(AccDiffForm.formDataFromParams(tags, title, targets, starting)); + } -export function updateTotals() { - const flags = calcAccDiff(); - const other = calcManualAccDiff(); - const totalAcc = flags[0] + other[0]; - const totalDiff = flags[1] + other[1]; - const fullTotal = totalAcc - totalDiff; - - // SEPARATE SUBTOTALS - // const accEle = document.querySelector("#accdiff-total-acc"); - // const diffEle = document.querySelector("#accdiff-total-diff"); - // accEle && (accEle.innerHTML = String(totalAcc)); - // diffEle && (diffEle.innerHTML = String(totalDiff)); - - // SINGLE TOTAL - const accEle = document.querySelector("#accdiff-total"); - if (accEle) { - accEle.innerHTML = String(Math.abs(fullTotal)); - - // Change color based on result. - const color = fullTotal > 0 ? "#017934" : fullTotal < 0 ? "#9c0d0d" : "#443c3c"; - accEle.parentElement!.style.backgroundColor = color; - - // Change icon based on result. - fullTotal > 0 && accEle.nextElementSibling?.classList.replace("cci-difficulty", "cci-accuracy"); - fullTotal < 0 && accEle.nextElementSibling?.classList.replace("cci-accuracy", "cci-difficulty"); + getData(): AccDiffFormData { + let ret = this.data; + + ret.baseAccDiff = { + accuracy: ret.baseUntypedAccDiff.accuracy + (ret.accurate ? 1 : 0), + difficulty: ret.baseUntypedAccDiff.difficulty + (ret.inaccurate ? 1 : 0) + }; + if (!ret.seeking && (ret.hardCover || ret.softCover)) { + ret.baseAccDiff.difficulty += ret.hardCover ? 2 : 1; + } + + ret.seekingDisabled = ret.seeking ? "disabled" : ""; + + let total = ret.baseAccDiff.accuracy - ret.baseAccDiff.difficulty; + + ret.totalIcon = { + value: Math.abs(total), + color: total > 0 ? "#017934" : total < 0 ? "#9c0d0d" : "#443c3c", + className: total >= 0 ? "cci-accuracy" : "cci-difficulty" + }; + + return ret; + } + + activateListeners(html: JQuery) { + super.activateListeners(html); + html.find(".cancel").on("click", async (_ev) => { + return this.close(); + }); } - return fullTotal; + async _onChangeInput(_e: Event) { + // @ts-ignore .8 -- FormApplication._onChangeInput does exist + await super._onChangeInput(_e); + // @ts-ignore .8 -- FormApplication._getSubmitData does exist + let data = this._getSubmitData(null); + await this._updateObject(_e, data); + } + + _updateObject(ev: Event, formData: any) { + gentle_merge(this.data, formData); + this.render(); + + if (ev.type == "submit") { + if (this.resolve) { + this.resolve(this.data); + this.reject = null; + } + return this.promise; + } else { + return Promise.resolve(this.data); + } + } + + // FormApplication.close() does take an options hash + // @ts-ignore .8 + close(options: any = {}) { + if (this.reject) { + this.reject(); + this.resolve = null; + }; + // @ts-ignore .8 + return super.close(options); + } } diff --git a/src/module/macros.ts b/src/module/macros.ts index ebd413f2e..d2a5b01ff 100644 --- a/src/module/macros.ts +++ b/src/module/macros.ts @@ -50,7 +50,7 @@ import { buildActionHTML, buildDeployableHTML, buildSystemHTML } from "./helpers import { ActivationOptions, StabOptions1, StabOptions2 } from "./enums"; import { applyCollapseListeners, uuid4 } from "./helpers/collapse"; import { checkForHit, getTargets } from "./helpers/automation/targeting"; -import { AccDiffFlag, tagsToFlags, toggleCover, updateTotals } from "./helpers/acc_diff"; +import { AccDiffForm, AccDiffFormData } from "./helpers/acc_diff"; import { is_overkill } from "machine-mind/dist/funcs"; const lp = LANCER.log_prefix; @@ -450,23 +450,48 @@ function getMacroActorItem(a: string, i: string): { actor: Actor | null; item: I return result; } -async function buildAttackRollString( +function rollStr(bonus: number, obj: { accuracy: number, difficulty: number }): string { + let total = obj.accuracy - obj.difficulty; + let modStr = ""; + if (total != 0) { + let sign = total > 0 ? "+" : "-"; + let abs = Math.abs(total); + let roll = abs == 1 ? "1d6" : `${abs}d6kh1`; + modStr = ` ${sign} ${roll}`; + } + return `1d20+${bonus}${modStr}`; +} + +type AttackRoll = { + roll: string, + targeted: { target: LancerActor, roll: string }[] +} + +async function buildAttackRollStrings( title: string, - flags: AccDiffFlag[], + tags: TagInstance[], bonus: number, + targets: LancerActor[], starting?: [number, number] // initial [accuracy, difficulty] -): Promise { - let abort: boolean = false; - let acc = 0; - await promptAccDiffModifier(flags, title, starting).then( - resolve => (acc = resolve), - reject => (abort = reject) - ); - if (abort) return null; - - // Do the attack rolling - let acc_str = acc != 0 ? ` + ${acc}d6kh1` : ""; - return `1d20+${bonus}${acc_str}`; +): Promise { + try { + let accdiff = await promptAccDiffModifiers(tags, title, targets, starting); + let res: AttackRoll = { + roll: rollStr(bonus, accdiff.baseAccDiff), + targeted: accdiff.targetedAccDiffs.map(tad => ({ + target: tad.target, + roll: rollStr(bonus, { + accuracy: tad.accuracy + accdiff.baseAccDiff.accuracy, + difficulty: tad.difficulty + accdiff.baseAccDiff.difficulty + }) + })) + }; + + return res; + } catch (e) { + // an error occurred during the prompt; probably the user cancelling + return null; + } } export async function prepareStatMacro(a: string, statKey: string) { @@ -503,8 +528,8 @@ async function rollStatMacro(actor: Actor, data: LancerStatMacroData) { // Get accuracy/difficulty with a prompt let acc: number = 0; let abort: boolean = false; - await promptAccDiffModifier().then( - resolve => (acc = resolve), + await promptAccDiffModifiers().then( + accdiff => (acc = accdiff.baseAccDiff.accuracy - accdiff.baseAccDiff.difficulty), () => (abort = true) ); if (abort) return Promise.resolve(); @@ -690,14 +715,16 @@ async function prepareAttackMacro({ } } - // Build attack string before deducting charge. - const atk_str = await buildAttackRollString( + // Build attack rolls before deducting charge. + const targets = getTargets(); + const atkRolls = await buildAttackRollStrings( mData.title, - tagsToFlags(mData.tags), + mData.tags, mData.grit, - mData.acc > 0 ? [mData.acc, 0] : [0, -mData.acc] + targets, + mData.acc > 0 ? [mData.acc, 0] : [0, -mData.acc], ); - if (!atk_str) return; + if (!atkRolls) return; // Deduct charge if LOADING weapon. if ( @@ -712,16 +739,13 @@ async function prepareAttackMacro({ await itemEnt.writeback(); } - await rollAttackMacro(actor, atk_str, mData); + await rollAttackMacro(actor, atkRolls, mData); } -async function rollAttackMacro(actor: Actor, atk_str: string | null, data: LancerAttackMacroData) { - if (!atk_str) return; - +async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerAttackMacroData) { // IS SMART? const isSmart = data.tags.findIndex(tag => tag.Tag.LID === "tg_smart") > -1; // CHECK TARGETS - const targets = getTargets(); let hits: { token: { name: string; img: string }; total: string; @@ -729,10 +753,11 @@ async function rollAttackMacro(actor: Actor, atk_str: string | null, data: Lance crit: boolean; }[] = []; let attacks: { roll: Roll; tt: HTMLElement | JQuery }[] = []; - if (game.settings.get(LANCER.sys_name, LANCER.setting_automation_attack) && targets.length > 0) { - for (const target of targets) { + if (game.settings.get(LANCER.sys_name, LANCER.setting_automation_attack) && atkRolls.targeted.length > 0) { + for (const targetingData of atkRolls.targeted) { + let target = targetingData.target; // @ts-ignore .8 - let attack_roll = await new Roll(atk_str!).evaluate({ async: true }); + let attack_roll = await new Roll(targetingData.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); attacks.push({ roll: attack_roll, tt: attack_tt }); @@ -748,7 +773,7 @@ async function rollAttackMacro(actor: Actor, atk_str: string | null, data: Lance } } else { // @ts-ignore .8 - let attack_roll = await new Roll(atk_str).evaluate({ async: true }); + let attack_roll = await new Roll(atkRolls.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); attacks.push({ roll: attack_roll, tt: attack_tt }); } @@ -1084,11 +1109,12 @@ export async function prepareTechMacro(a: string, t: string) { } async function rollTechMacro(actor: Actor, data: LancerTechMacroData) { - let atk_str = await buildAttackRollString(data.title, tagsToFlags(data.tags), data.t_atk); - if (!atk_str) return; + const targets = getTargets(); + + let atkRolls = await buildAttackRollStrings(data.title, data.tags, data.t_atk, targets); + if (!atkRolls) return; // CHECK TARGETS - const targets = getTargets(); let hits: { token: { name: string; img: string }; total: string; @@ -1097,9 +1123,10 @@ async function rollTechMacro(actor: Actor, data: LancerTechMacroData) { }[] = []; let attacks: { roll: Roll; tt: HTMLElement | JQuery }[] = []; if (game.settings.get(LANCER.sys_name, LANCER.setting_automation_attack) && targets.length > 0) { - for (const target of targets) { + for (const targetingData of atkRolls.targeted) { + let target = targetingData.target; // @ts-ignore .8 - let attack_roll = await new Roll(atk_str!).evaluate({ async: true }); + let attack_roll = await new Roll(targetingData.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); attacks.push({ roll: attack_roll, tt: attack_tt }); @@ -1115,7 +1142,7 @@ async function rollTechMacro(actor: Actor, data: LancerTechMacroData) { } } else { // @ts-ignore .8 - let attack_roll = await new Roll(atk_str).evaluate({ async: true }); + let attack_roll = await new Roll(atkRolls.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); attacks.push({ roll: attack_roll, tt: attack_tt }); } @@ -1134,64 +1161,15 @@ async function rollTechMacro(actor: Actor, data: LancerTechMacroData) { return await renderMacroTemplate(actor, template, templateData); } -export async function promptAccDiffModifier(flags?: AccDiffFlag[], title?: string, starting?: [number, number]) { - let template = await renderTemplate(`systems/lancer/templates/window/acc_diff.hbs`, {}); - return new Promise((resolve, reject) => { - new Dialog({ - title: title ? `${title} - Accuracy and Difficulty` : "Accuracy and Difficulty", - content: template, - buttons: { - submit: { - icon: '', - label: "Submit", - callback: async dialog => { - let total = updateTotals(); - console.log(`${lp} Dialog returned a modifier of ${total}d6`); - resolve(total); - }, - }, - cancel: { - icon: '', - label: "Cancel", - callback: async () => { - reject(true); - }, - }, - }, - default: "submit", - render: (_html: any) => { - if (flags) { - for (let flag of flags) { - const ret = document.querySelector(`[data-acc="${flag}"],[data-diff="${flag}"]`); - ret && ((ret as HTMLInputElement).checked = true); - } - } - - if (flags?.includes("SEEKING")) { - toggleCover(false); - } - updateTotals(); - - if (starting) { - $("#accdiff-other-acc").val(starting[0]); - $("#accdiff-other-diff").val(starting[1]); - updateTotals(); - } - - // LISTENERS - $("[data-acc],[data-diff]").on("click", e => { - if (e.currentTarget.dataset.acc === "SEEKING") { - toggleCover(!(e.currentTarget as HTMLInputElement).checked); - } - updateTotals(); - }); - $(".accdiff-grid button.dec-set").on("click", _e => { - updateTotals(); - }); - }, - close: () => reject(true), - } as DialogData).render(true); - }); +export function promptAccDiffModifiers( + tags?: TagInstance[], + title?: string, + targets?: LancerActor[], + starting?: [number, number] +): Promise { + let form = AccDiffForm.fromData(tags, title, targets, starting); + form.render(true); + return form.promise; } export async function prepareOverchargeMacro(a: string) { diff --git a/src/templates/window/acc_diff.hbs b/src/templates/window/acc_diff.hbs index 9a9fda2a6..88e21585b 100644 --- a/src/templates/window/acc_diff.hbs +++ b/src/templates/window/acc_diff.hbs @@ -1,107 +1,121 @@ -
-
-
-

- - Accuracy -

- - -
-
-

- - Difficulty -

- - - -
-
- -
-
- - - +
+
+
+
+

+ + Accuracy +

+ + +
+
+

+ + Difficulty +

+ + + +
-
- - - + +
+
+ + + +
+
+ + + +
-
- -
-
-
- - 0 - - + +
+
+
+ + {{totalIcon.value}} + + +
-
\ No newline at end of file +
+ + +
+ \ No newline at end of file From 28ca3a245f9b8f9f4916cbfbfc4d6d9723f017b4 Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Mon, 12 Jul 2021 21:33:41 -0700 Subject: [PATCH 02/88] not sure why this collapse was there before the only practical upshot of this collapse is that sometimes tech attacks wouldn't expand to show some of the attack rolls. I've adjusted it to match the regular attack rolls --- src/templates/chat/tech-attack-card.hbs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/templates/chat/tech-attack-card.hbs b/src/templates/chat/tech-attack-card.hbs index 2d1fc77f7..d4b4aeadf 100644 --- a/src/templates/chat/tech-attack-card.hbs +++ b/src/templates/chat/tech-attack-card.hbs @@ -8,10 +8,7 @@ {{localize "lancer.chat-card.title.attack"}}
{{#each attacks as |attack key|}} -
+
{{ attack.roll.formula }} From 0542ac8bc7c8beb140c0c6aebd93df7aa2ff6d7a Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Mon, 12 Jul 2021 21:56:11 -0700 Subject: [PATCH 03/88] factor out common code between tech and weapon attacks + take the opportunity to de-serialise operations a tiny bit --- src/module/macros.ts | 100 +++++++++++++++++++------------------------ 1 file changed, 44 insertions(+), 56 deletions(-) diff --git a/src/module/macros.ts b/src/module/macros.ts index d2a5b01ff..13cf7a076 100644 --- a/src/module/macros.ts +++ b/src/module/macros.ts @@ -742,41 +742,60 @@ async function prepareAttackMacro({ await rollAttackMacro(actor, atkRolls, mData); } -async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerAttackMacroData) { - // IS SMART? - const isSmart = data.tags.findIndex(tag => tag.Tag.LID === "tg_smart") > -1; - // CHECK TARGETS - let hits: { - token: { name: string; img: string }; - total: string; - hit: boolean; - crit: boolean; - }[] = []; - let attacks: { roll: Roll; tt: HTMLElement | JQuery }[] = []; +type AttackResult = { + roll: Roll, + tt: HTMLElement | JQuery +} + +type HitResult = { + token: { name: string, img: string }, + total: string, + hit: boolean, + crit: boolean +} + +async function checkTargets(atkRolls: AttackRoll, isSmart: boolean): Promise<{ + attacks: AttackResult[], + hits: HitResult[] +}> { if (game.settings.get(LANCER.sys_name, LANCER.setting_automation_attack) && atkRolls.targeted.length > 0) { - for (const targetingData of atkRolls.targeted) { + let data = await Promise.all(atkRolls.targeted.map(async targetingData => { let target = targetingData.target; // @ts-ignore .8 let attack_roll = await new Roll(targetingData.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); - attacks.push({ roll: attack_roll, tt: attack_tt }); + return { + attack: { roll: attack_roll, tt: attack_tt }, + hit: { + token: { + name: target.token ? target.token.data.name : target.data.name, + img: target.token ? target.token.data.img : target.data.img, + }, + total: String(attack_roll._total).padStart(2, "0"), + hit: await checkForHit(isSmart, attack_roll, target), + crit: attack_roll._total >= 20, + } + } + })); - hits.push({ - token: { - name: target.token ? target.token.data.name : target.data.name, - img: target.token ? target.token.data.img : target.data.img, - }, - total: String(attack_roll._total).padStart(2, "0"), - hit: await checkForHit(isSmart, attack_roll, target), - crit: attack_roll._total >= 20, - }); - } + return { + attacks: data.map(d => d.attack), + hits: data.map(d => d.hit) + }; } else { // @ts-ignore .8 let attack_roll = await new Roll(atkRolls.roll).evaluate({ async: true }); const attack_tt = await attack_roll.getTooltip(); - attacks.push({ roll: attack_roll, tt: attack_tt }); + return { + attacks: [{ roll: attack_roll, tt: attack_tt }], + hits: [] + } } +} + +async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerAttackMacroData) { + const isSmart = data.tags.findIndex(tag => tag.Tag.LID === "tg_smart") > -1; + const { attacks, hits } = await checkTargets(atkRolls, isSmart); // Iterate through damage types, rolling each let damage_results: Array<{ @@ -1114,38 +1133,7 @@ async function rollTechMacro(actor: Actor, data: LancerTechMacroData) { let atkRolls = await buildAttackRollStrings(data.title, data.tags, data.t_atk, targets); if (!atkRolls) return; - // CHECK TARGETS - let hits: { - token: { name: string; img: string }; - total: string; - hit: boolean; - crit: boolean; - }[] = []; - let attacks: { roll: Roll; tt: HTMLElement | JQuery }[] = []; - if (game.settings.get(LANCER.sys_name, LANCER.setting_automation_attack) && targets.length > 0) { - for (const targetingData of atkRolls.targeted) { - let target = targetingData.target; - // @ts-ignore .8 - let attack_roll = await new Roll(targetingData.roll).evaluate({ async: true }); - const attack_tt = await attack_roll.getTooltip(); - attacks.push({ roll: attack_roll, tt: attack_tt }); - - hits.push({ - token: { - name: target.token ? target.token.data.name : target.data.name, - img: target.token ? target.token.data.img : target.data.img, - }, - total: String(attack_roll._total).padStart(2, "0"), - hit: await checkForHit(true, attack_roll, target), - crit: attack_roll._total >= 20, - }); - } - } else { - // @ts-ignore .8 - let attack_roll = await new Roll(atkRolls.roll).evaluate({ async: true }); - const attack_tt = await attack_roll.getTooltip(); - attacks.push({ roll: attack_roll, tt: attack_tt }); - } + const { attacks, hits } = await checkTargets(atkRolls, true); // true = all tech attacks are "smart" // Output const templateData = { From f46b876ce6d9e4a39e252156a91a65b5c757643d Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Tue, 13 Jul 2021 14:31:23 -0700 Subject: [PATCH 04/88] implement multi-target acc/diff and cover in the acc_diff form subtasks: * separate out {{std-cover-input}} and {{accdiff-total-display}} * rejigger accdiff's data model (again) (we use get total() functions now) * messy UI work for the multi-target section of the acc_diff form --- src/lancer.scss | 79 ++++++++++++++++++ src/lancer.ts | 4 + src/module/helpers/acc_diff.ts | 131 +++++++++++++++++------------- src/module/helpers/commons.ts | 68 ++++++++++++++++ src/module/macros.ts | 14 ++-- src/templates/window/acc_diff.hbs | 108 +++++++++++++++++------- 6 files changed, 310 insertions(+), 94 deletions(-) diff --git a/src/lancer.scss b/src/lancer.scss index 5b916a46f..df9517030 100644 --- a/src/lancer.scss +++ b/src/lancer.scss @@ -2094,6 +2094,39 @@ a.action { border-top: 1px solid #782e22; } +.accdiff-base-cover { + margin-top: 12px; + margin-bottom: 4px; + font-size: 0.9em; + user-select: none; + padding-left: 5px; + line-height: 2; +} + +.accdiff-targeted-cover label span { + opacity: 0; + position: fixed; + width: 0; + visibility: hidden; +} + +.accdiff-targeted-cover label > i { + font-size: 16px; + vertical-align: top; +} + +.accdiff-base-cover label > i { + vertical-align: baseline; +} + +// there's a very specific foundry rule that adds some margin here +// because it assumes all icons in buttons are followed by text, I think +.accdiff-target-row button > i { + margin-inline-end: 0 !important; +} + + + // V-IMPORTS .white--text { color: #fff !important; @@ -2177,3 +2210,49 @@ div[data-tab="system"] > .settings-list > div:nth-last-child(-n + 4) { max-width: 60%; align-self: center; } + +.accdiff-target-row .lancer-hit-thumb { + margin-right: 0px; + margin-left: 4px; + margin-bottom: 4px; +} + +.lancer-cover-radio input[type="radio"] { + opacity: 0; + position: fixed; + width: 0; +} + +.lancer-cover-radio label { + display: inline-block; + padding-left: 5px; +} +.lancer-cover-radio.flexrow label { + padding: 0px; +} + + +.lancer-cover-radio:not(.disabled) label:hover { + background-color: #dfd; +} + +.lancer-cover-radio input[type="radio"]:checked + label { + background-color: #ddd; + .card & { + background-color: #aaa; + } +} + +.lancer-cover-radio.disabled { + opacity: 0.6; +} + +.cci-no-cover:before { + content: '\25cb'; +} +.cci-soft-cover:before { + content: '\25d1'; +} +.cci-hard-cover:before { + content: '\25cf'; +} diff --git a/src/lancer.ts b/src/lancer.ts index 5b3bf4579..6e7c1fec4 100644 --- a/src/lancer.ts +++ b/src/lancer.ts @@ -65,6 +65,8 @@ import { std_password_input, std_num_input, std_checkbox, + std_cover_input, + accdiff_total_display, } from "./module/helpers/commons"; import { weapon_size_selector, @@ -386,6 +388,8 @@ Hooks.once("init", async function () { Handlebars.registerHelper("std-num-input", std_num_input); Handlebars.registerHelper("std-checkbox", std_checkbox); Handlebars.registerHelper("action-button", action_button); + Handlebars.registerHelper("std-cover-input", std_cover_input); + Handlebars.registerHelper("accdiff-total-display", accdiff_total_display); // ------------------------------------------------------------------------ // Tag helpers diff --git a/src/module/helpers/acc_diff.ts b/src/module/helpers/acc_diff.ts index 7fcef78d5..6cef9e59d 100644 --- a/src/module/helpers/acc_diff.ts +++ b/src/module/helpers/acc_diff.ts @@ -2,22 +2,39 @@ import { TagInstance } from "machine-mind"; import { LancerActor, LancerActorType } from '../actor/lancer-actor'; import { gentle_merge } from '../helpers/commons'; +enum Cover { + None = 0, + Soft = 1, + Hard = 2 +} + export type AccDiffFormData = { title: string, - baseUntypedAccDiff: { accuracy: number, difficulty: number }, - baseAccDiff: { accuracy: number, difficulty: number }, - targetedAccDiffs: { target: LancerActor, accuracy: number, difficulty: number }[], - accurate: boolean, - inaccurate: boolean, - softCover: boolean, - hardCover: boolean, - seeking: boolean, - seekingDisabled?: string, - totalIcon?: { - value: number, - color: string, - className: string - } + base: { + untyped: { + accuracy: number, + difficulty: number, + }, + accuracy: number, + difficulty: number, + cover: Cover, + accurate: boolean, + inaccurate: boolean, + seeking: boolean, + total: number + }, + targets: { + target: LancerActor, + accuracy: number, + difficulty: number, + cover: Cover, + total: number + }[], +} + +type AccDiffFormView = { + baseCoverDisabled: boolean, + hasTargets: boolean } export class AccDiffForm extends FormApplication { @@ -51,39 +68,53 @@ export class AccDiffForm extends FormApplication { targets?: LancerActor[], starting?: [number, number] ): AccDiffFormData { - let baseAccDiff = { - accuracy: starting ? starting[0] : 0, - difficulty: starting ? starting[1] : 0 - }; - let ret: AccDiffFormData = { title: title ? `${title} - Accuracy and Difficulty` : "Accuracy and Difficulty", - baseUntypedAccDiff: baseAccDiff, - baseAccDiff: baseAccDiff, // this'll get overwritten by getData soon - - targetedAccDiffs: (targets || []).map(t => ({ - target: t, - accuracy: 0, - difficulty: 0 - })), - - accurate: false, - inaccurate: false, - softCover: false, - hardCover: false, - seeking: false + base: { + accurate: false, + inaccurate: false, + cover: Cover.None, + seeking: false, + untyped: { + accuracy: starting ? starting[0] : 0, + difficulty: starting ? starting[1] : 0 + }, + get accuracy() { + return this.untyped.accuracy + (this.accurate ? 1 : 0); + }, + get difficulty() { + return this.untyped.difficulty + + (this.inaccurate ? 1 : 0) + + (this.seeking ? 0 : this.cover) + }, + get total() { + return this.accuracy - this.difficulty; + } + }, + targets: [] }; + ret.targets = (targets || []).map(t => ({ + target: t, + accuracy: 0, + difficulty: 0, + cover: Cover.None, + get total() { + return this.accuracy - this.difficulty + + ret.base.total - (ret.base.seeking ? 0 : this.cover); + } + })); + for (let tag of (tags || [])) { switch (tag.Tag.LID) { case "tg_accurate": - ret.accurate = true; + ret.base.accurate = true; break; case "tg_inaccurate": - ret.inaccurate = true; + ret.base.inaccurate = true; break; case "tg_seeking": - ret.seeking = true; + ret.base.seeking = true; break; } } @@ -99,28 +130,18 @@ export class AccDiffForm extends FormApplication { return new AccDiffForm(AccDiffForm.formDataFromParams(tags, title, targets, starting)); } - getData(): AccDiffFormData { - let ret = this.data; + getData(): AccDiffFormData & AccDiffFormView { + let ret: AccDiffFormData = this.data as AccDiffFormData; - ret.baseAccDiff = { - accuracy: ret.baseUntypedAccDiff.accuracy + (ret.accurate ? 1 : 0), - difficulty: ret.baseUntypedAccDiff.difficulty + (ret.inaccurate ? 1 : 0) - }; - if (!ret.seeking && (ret.hardCover || ret.softCover)) { - ret.baseAccDiff.difficulty += ret.hardCover ? 2 : 1; + let view: AccDiffFormView = { + baseCoverDisabled: ret.base.seeking || ret.targets.length > 0, + hasTargets: ret.targets.length > 0 } - ret.seekingDisabled = ret.seeking ? "disabled" : ""; - - let total = ret.baseAccDiff.accuracy - ret.baseAccDiff.difficulty; - - ret.totalIcon = { - value: Math.abs(total), - color: total > 0 ? "#017934" : total < 0 ? "#9c0d0d" : "#443c3c", - className: total >= 0 ? "cci-accuracy" : "cci-difficulty" - }; - - return ret; + return mergeObject( + ret as AccDiffFormData & AccDiffFormView, + view as AccDiffFormData & AccDiffFormView + ); } activateListeners(html: JQuery) { diff --git a/src/module/helpers/commons.ts b/src/module/helpers/commons.ts index f6ae289ed..313e3bf50 100644 --- a/src/module/helpers/commons.ts +++ b/src/module/helpers/commons.ts @@ -505,6 +505,74 @@ async function control_structs(key: string, on: LiveEntryTypes): Prom return [false, null]; } +export function std_cover_input(path: string, options: HelperOptions) { + let container_classes: string = options.hash["classes"] || ""; + let label: string = options.hash["label"] || ""; + let label_classes: string = options.hash["label_classes"] || ""; + + let value: string | undefined = options.hash["value"]; + if (value == undefined) { + // Resolve + value = (resolve_helper_dotpath(options, path) as number)?.toString() ?? 0; + } + + let checked = ["0", "1", "2"].map(v => value == v ? "checked='true'" : ""); + let disabled: boolean = options.hash["disabled"] ? resolve_helper_dotpath(options, options.hash["disabled"]) : false; + let disabledStr = disabled ? "disabled" : ""; + + let inputs = [ + ` + `, + ` + `, + ` + ` + ]; + + if (label) { + return `` + } else { + return `
${inputs.join(" ")}
`; + } +} + +export function accdiff_total_display(path: string, options: HelperOptions) { + let container_classes: string = options.hash["classes"] || ""; + let id: string = options.hash["id"] || ""; + let value: string | undefined = options.hash["value"]; + let total = 0; + if (value == undefined) { + // Resolve + total = (resolve_helper_dotpath(options, path) as number) ?? 0; + } + + let abs = Math.abs(total); + let color = total > 0 ? "#017934" : total < 0 ? "#9c0d0d" : "#443c3c"; + let icon = total >= 0 ? "cci-accuracy" : "cci-difficulty" + + return ` +
+
+
+ ${abs} + +
+
+
` +} + // Our standardized functions for making simple key-value input pair // Todo - these could on the whole be a bit fancier, yeah? diff --git a/src/module/macros.ts b/src/module/macros.ts index 13cf7a076..a1a2f0986 100644 --- a/src/module/macros.ts +++ b/src/module/macros.ts @@ -450,8 +450,7 @@ function getMacroActorItem(a: string, i: string): { actor: Actor | null; item: I return result; } -function rollStr(bonus: number, obj: { accuracy: number, difficulty: number }): string { - let total = obj.accuracy - obj.difficulty; +function rollStr(bonus: number, total: number): string { let modStr = ""; if (total != 0) { let sign = total > 0 ? "+" : "-"; @@ -477,13 +476,10 @@ async function buildAttackRollStrings( try { let accdiff = await promptAccDiffModifiers(tags, title, targets, starting); let res: AttackRoll = { - roll: rollStr(bonus, accdiff.baseAccDiff), - targeted: accdiff.targetedAccDiffs.map(tad => ({ + roll: rollStr(bonus, accdiff.base.total), + targeted: accdiff.targets.map(tad => ({ target: tad.target, - roll: rollStr(bonus, { - accuracy: tad.accuracy + accdiff.baseAccDiff.accuracy, - difficulty: tad.difficulty + accdiff.baseAccDiff.difficulty - }) + roll: rollStr(bonus, tad.total) })) }; @@ -529,7 +525,7 @@ async function rollStatMacro(actor: Actor, data: LancerStatMacroData) { let acc: number = 0; let abort: boolean = false; await promptAccDiffModifiers().then( - accdiff => (acc = accdiff.baseAccDiff.accuracy - accdiff.baseAccDiff.difficulty), + accdiff => (acc = accdiff.base.total), () => (abort = true) ); if (abort) return Promise.resolve(); diff --git a/src/templates/window/acc_diff.hbs b/src/templates/window/acc_diff.hbs index 88e21585b..4070bf013 100644 --- a/src/templates/window/acc_diff.hbs +++ b/src/templates/window/acc_diff.hbs @@ -1,4 +1,4 @@ -
+
@@ -8,12 +8,12 @@
@@ -24,19 +24,13 @@ - - + {{{std-cover-input "base.cover" + classes="accdiff-base-cover flexcol" + disabled="baseCoverDisabled" + }}}
- -
-
-
- - {{totalIcon.value}} - - -
+ {{#unless hasTargets}} + + {{{accdiff-total-display "base.total" + id="accdiff-total" + classes="accdiff-grid accdiff-weight" + }}} + {{/unless}} + {{#if hasTargets}} + -
+ {{/if}}
`; @@ -366,8 +396,9 @@ export class LancerActor extends Actor { } else { if(result === 1 && remStruct === 2) { let macroData = encodeMacroData({ - command: `game.lancer.prepareStatMacro("${ent.RegistryID}","mm.Hull");`, title: "Hull", + fn: "prepareStatMacro", + args: [ent.RegistryID, "mm.Hull"] }); secondaryRoll = ``; @@ -375,33 +406,9 @@ export class LancerActor extends Actor { let macroData = encodeMacroData({ // TODO: Should create a "prepareRollMacro" or something to handle generic roll-based macros // Since we can't change prepareTextMacro too much or break everyone's macros - command: ` - let roll = new Roll('1d6').evaluate({async: false}); - let result = roll.total; - if(result<=3) { - game.lancer.prepareTextMacro("${ent.RegistryID}","Destroy Weapons",\` -
-
-
- \${ roll.formula } - \${ result } -
-
-
- On a 1–3, all weapons on one mount of your choice are destroyed\`); - } else { - game.lancer.prepareTextMacro("${ent.RegistryID}","Destroy Systems",\` -
-
-
- \${ roll.formula } - \${ result } -
-
-
- On a 4–6, a system of your choice is destroyed\`); - }`, title: "Roll for Destruction", + fn: "prepareStructureSecondaryRollMacro", + args: [ent.RegistryID] }); secondaryRoll = ``; diff --git a/src/module/helpers/actor.ts b/src/module/helpers/actor.ts index eed7eb4db..4f8532361 100644 --- a/src/module/helpers/actor.ts +++ b/src/module/helpers/actor.ts @@ -53,8 +53,12 @@ export function stat_view_card( let data_val = resolve_helper_dotpath(options, data_path); let macro_button: string | undefined; let macroData = encodeMacroData({ - command: `game.lancer.prepareStatMacro("${options.data.root.data._id}","${data_path}");`, title: title, + fn: "prepareStatMacro", + args: [ + options.data.root.data._id, + data_path + ] }); if (options.rollable) macro_button = ``; @@ -126,8 +130,12 @@ export function clicker_stat_card( ): string { let button = ""; let macroData = encodeMacroData({ - command: `game.lancer.prepareStatMacro("${options.data.root.data._id}","${data_path}");`, title: title, + fn: "prepareStatMacro", + args: [ + options.data.root.data._id, + data_path + ] }); if (roller) button = ``; diff --git a/src/module/helpers/loadout.ts b/src/module/helpers/loadout.ts index 54f967779..f2c1731ff 100644 --- a/src/module/helpers/loadout.ts +++ b/src/module/helpers/loadout.ts @@ -265,12 +265,11 @@ function frame_active(actor: LancerActor, core: CoreSystem): str }).join(""); // Should find a better way to do this... - - let coreMacroData: LancerMacroData = { - command: `game.lancer.prepareCoreActiveMacro("${actor.id}")`, title: `${actor.name} | CORE POWER`, iconPath: `systems/lancer/assets/icons/macro-icons/corebonus.svg`, + fn: "prepareCoreActiveMacro", + args: [actor.id] }; return ` diff --git a/src/module/helpers/refs.ts b/src/module/helpers/refs.ts index 575d4086c..cbaa1f1b4 100644 --- a/src/module/helpers/refs.ts +++ b/src/module/helpers/refs.ts @@ -314,7 +314,11 @@ export function editable_mm_ref_list_item( let macroData: LancerMacroData = { iconPath: `systems/lancer/assets/icons/macro-icons/mech_system.svg`, title: sys.Name, - command: `game.lancer.prepareItemMacro("${sys.Flags.orig_doc.actor?.id ?? ""}", "${sys.Flags.orig_doc.id}")`, + fn: "prepareItemMacro", + args: [ + sys.Flags.orig_doc.actor?.id ?? "", + sys.Flags.orig_doc.id + ] }; let limited = ""; diff --git a/src/module/interfaces.d.ts b/src/module/interfaces.d.ts index b8a515bdf..6299381bd 100644 --- a/src/module/interfaces.d.ts +++ b/src/module/interfaces.d.ts @@ -148,7 +148,8 @@ declare interface LancerOverchargeMacroData { } declare interface LancerMacroData { - command: string; + fn: string; + args: any[]; iconPath?: string; title: string; } diff --git a/src/module/macros.ts b/src/module/macros.ts index c81fa6e22..530a8a938 100644 --- a/src/module/macros.ts +++ b/src/module/macros.ts @@ -56,20 +56,34 @@ import { is_overkill } from "machine-mind/dist/funcs"; const lp = LANCER.log_prefix; -export function encodeMacroData(macroData: LancerMacroData): string { - return btoa(encodeURI(JSON.stringify(macroData))); +export function encodeMacroData(data: LancerMacroData): string { + return btoa(encodeURI(JSON.stringify(data))); } -export async function runEncodedMacro(el: JQuery) { - let encoded = el.attr("data-macro"); +export async function runEncodedMacro(el: HTMLElement) { + let encoded = el.attributes.getNamedItem('data-macro')?.nodeValue; + if (!encoded) { + console.warn("No macro data available"); + return; + } - if (!encoded) throw Error("No macro data available"); let data: LancerMacroData = JSON.parse(decodeURI(atob(encoded))); - let command = data.command; + const whitelist = [ + "prepareEncodedAttackMacro", + "prepareStatMacro", + "prepareItemMacro", + "prepareCoreActiveMacro", + "prepareStructureSecondaryRollMacro", + ]; + + if (whitelist.indexOf(data.fn) < 0) { + console.error("Attempting to call unwhitelisted function via encoded macro: " + data.fn); + return; + } - // Some might say eval is bad, but it's no worse than what we can already do with macros - eval(command); + let fn = game.lancer[data.fn]; + return fn.apply(null, data.args) } export async function onHotbarDrop(_bar: any, data: any, slot: number) { @@ -735,12 +749,16 @@ async function prepareAttackMacro({ await itemEnt.writeback(); } - let rerollMacro = `prepareEncodedAttackMacro( -${JSON.stringify(actor.data.data.derived.mm.as_ref())}, -"${item.id}", -${JSON.stringify(options)}, -${JSON.stringify(promptedData.toObject())} -)`; + let rerollMacro = { + title: "Reroll attack", + fn: "prepareEncodedAttackMacro", + args: [ + actor.data.data.derived.mm.as_ref(), + item.id, + options, + promptedData.toObject() + ] + }; await rollAttackMacro(actor, atkRolls, mData, rerollMacro); } @@ -793,7 +811,7 @@ async function checkTargets(atkRolls: AttackRoll, isSmart: boolean): Promise<{ } } -async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerAttackMacroData, rerollMacro: string) { +async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerAttackMacroData, rerollMacro: LancerMacroData) { const isSmart = data.tags.findIndex(tag => tag.Tag.LID === "tg_smart") > -1; const { attacks, hits } = await checkTargets(atkRolls, isSmart); @@ -933,7 +951,7 @@ async function rollAttackMacro(actor: Actor, atkRolls: AttackRoll, data: LancerA effect: data.effect ? data.effect : null, on_hit: data.on_hit ? data.on_hit : null, tags: data.tags, - rerollMacroData: encodeMacroData({ command: rerollMacro, title: "Reroll Attack" }) + rerollMacroData: encodeMacroData(rerollMacro) }; console.debug(templateData); From 99800d47cb1236d51c14b8e28844ab1011488983 Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Tue, 13 Jul 2021 23:36:25 -0700 Subject: [PATCH 15/88] bugfix: structure damage macro was broken --- src/lang/en.json | 1 + src/templates/chat/structure-card.hbs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lang/en.json b/src/lang/en.json index 3ccb071ba..0818fb173 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -96,6 +96,7 @@ "effect": "// EFFECT //", "charge-roll": "// CHARGE ROLL //", "changes": "// CHANGES //", + "structure": "// STRUCTURE DAMAGE //", "overheating": "// OVERHEATING //", "check": "// CHECK :: {check} //", "system": "// SYSTEM :: {system} //" diff --git a/src/templates/chat/structure-card.hbs b/src/templates/chat/structure-card.hbs index 363063449..bd8486e9d 100644 --- a/src/templates/chat/structure-card.hbs +++ b/src/templates/chat/structure-card.hbs @@ -1,5 +1,5 @@
-
{{localize "lancer.chat-card.title" title="STRUCTURE DAMAGE"}}
+
{{localize "lancer.chat-card.title.structure" title="STRUCTURE DAMAGE"}}
{{localize "lancer.chat-card.remaining" total=val max=max}}
{{#if roll}}
From a6db06ee9365e56ba91b5596bdeea5b93714b291 Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Wed, 14 Jul 2021 00:26:50 -0700 Subject: [PATCH 16/88] the 1-target case looks nicer without the duplicated UI --- src/module/helpers/acc_diff.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/module/helpers/acc_diff.ts b/src/module/helpers/acc_diff.ts index dc1ca2a9a..c559b461a 100644 --- a/src/module/helpers/acc_diff.ts +++ b/src/module/helpers/acc_diff.ts @@ -213,7 +213,7 @@ export class AccDiffForm extends ReactiveForm { getViewModel(data: AccDiffData): AccDiffView { let ret = data as AccDiffView; // view elements haven't been set yet - ret.hasTargets = ret.targets.length > 0; + ret.hasTargets = ret.targets.length > 1; ret.baseCoverDisabled = ret.base.seeking || ret.hasTargets; return ret } From fd66efffb295746c75ba8ff51d555db961603efb Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Wed, 14 Jul 2021 09:22:29 -0700 Subject: [PATCH 17/88] =?UTF-8?q?woops=20=E2=80=94=20drop=20handler=20also?= =?UTF-8?q?=20depends=20on=20the=20old=20LancerMacroData=20defn?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/module/macros.ts | 46 +++++++++++++++++++++++++++----------------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/src/module/macros.ts b/src/module/macros.ts index 530a8a938..13ea9f942 100644 --- a/src/module/macros.ts +++ b/src/module/macros.ts @@ -56,28 +56,34 @@ import { is_overkill } from "machine-mind/dist/funcs"; const lp = LANCER.log_prefix; +const encodedMacroWhitelist = [ + "prepareEncodedAttackMacro", + "prepareStatMacro", + "prepareItemMacro", + "prepareCoreActiveMacro", + "prepareStructureSecondaryRollMacro", +]; + export function encodeMacroData(data: LancerMacroData): string { return btoa(encodeURI(JSON.stringify(data))); } -export async function runEncodedMacro(el: HTMLElement) { - let encoded = el.attributes.getNamedItem('data-macro')?.nodeValue; - if (!encoded) { - console.warn("No macro data available"); - return; - } +export async function runEncodedMacro(el: HTMLElement | LancerMacroData) { + let data: LancerMacroData | null = null; - let data: LancerMacroData = JSON.parse(decodeURI(atob(encoded))); + if (el instanceof HTMLElement) { + let encoded = el.attributes.getNamedItem('data-macro')?.nodeValue; + if (!encoded) { + console.warn("No macro data available"); + return; + } - const whitelist = [ - "prepareEncodedAttackMacro", - "prepareStatMacro", - "prepareItemMacro", - "prepareCoreActiveMacro", - "prepareStructureSecondaryRollMacro", - ]; + data = JSON.parse(decodeURI(atob(encoded))) as LancerMacroData; + } else { + data = el as LancerMacroData; + } - if (whitelist.indexOf(data.fn) < 0) { + if (encodedMacroWhitelist.indexOf(data.fn) < 0) { console.error("Attempting to call unwhitelisted function via encoded macro: " + data.fn); return; } @@ -95,9 +101,13 @@ export async function onHotbarDrop(_bar: any, data: any, slot: number) { let img = "systems/lancer/assets/icons/macro-icons/d20-framed.svg"; // Grab new encoded data ASAP - if (data.command && data.title) { - (command = data.command), - (img = data.iconPath ? data.iconPath : `systems/lancer/assets/icons/macro-icons/generic_item.svg`); + if (data.fn && data.args && data.title) { // i.e., data instanceof LancerMacroData + if (encodedMacroWhitelist.indexOf(data.fn) < 0) { + ui.notifications.error("You are trying to drop an invalid macro"); + return; + } + command = `game.lancer.${data.fn}.apply(null, ${JSON.stringify(data.args)})`; + img = data.iconPath ? data.iconPath : `systems/lancer/assets/icons/macro-icons/generic_item.svg`; title = data.title; } else if (data.pack) { // If we have a source pack, it's dropped from a compendium and there's no processing for us to do From 5dfcdd3a7ce0f5ef413a1271708c08d2f155ce7a Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Wed, 14 Jul 2021 09:23:13 -0700 Subject: [PATCH 18/88] marking this as lancer-macro does nothing right now, but in prep if/when we choose to extend lancer-actor-sheet's EncodedMacroHandler to the whole app, we will be _ready_ --- src/templates/chat/attack-card.hbs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/templates/chat/attack-card.hbs b/src/templates/chat/attack-card.hbs index 52334329e..a5a1fc23a 100644 --- a/src/templates/chat/attack-card.hbs +++ b/src/templates/chat/attack-card.hbs @@ -2,7 +2,7 @@
{{ title }} - +
From 6c21fc17a8d398540cbb4696a3b79ffd30e43b12 Mon Sep 17 00:00:00 2001 From: Sohum Banerjea Date: Wed, 14 Jul 2021 10:52:57 -0700 Subject: [PATCH 19/88] restructure accdiff datamodel: separate weapon & default base target --- src/module/helpers/acc_diff.ts | 119 +++++++++++++++--------------- src/templates/window/acc_diff.hbs | 14 ++-- 2 files changed, 66 insertions(+), 67 deletions(-) diff --git a/src/module/helpers/acc_diff.ts b/src/module/helpers/acc_diff.ts index c559b461a..c64a2e36a 100644 --- a/src/module/helpers/acc_diff.ts +++ b/src/module/helpers/acc_diff.ts @@ -7,76 +7,67 @@ enum Cover { Hard = 2 } -type AccDiffBaseSerialized = { - untyped: { accuracy: number, difficulty: number }, - cover: Cover, +type AccDiffWeapon = { accurate: boolean, inaccurate: boolean, - seeking: boolean + seeking: boolean, } -type AccDiffTargetSerialized = { - target: string, +type AccDiffWeaponSerialized = AccDiffWeapon; + +type AccDiffBaseSerialized = { accuracy: number, difficulty: number, cover: Cover } +type AccDiffTargetSerialized = AccDiffBaseSerialized & { + target: string +} + export type AccDiffDataSerialized = { title: string, + weapon: AccDiffWeaponSerialized, base: AccDiffBaseSerialized, targets: AccDiffTargetSerialized[], } class AccDiffBase { - untyped: { accuracy: number, difficulty: number }; + accuracy: number; + difficulty: number; cover: Cover; - accurate: boolean; - inaccurate: boolean; - seeking: boolean; + #weapon: AccDiffWeapon; - constructor(obj: AccDiffBaseSerialized) { - this.untyped = obj.untyped; + constructor(obj: AccDiffBaseSerialized, weapon: AccDiffWeapon) { + this.accuracy = obj.accuracy; + this.difficulty = obj.difficulty; this.cover = obj.cover; - this.accurate = obj.accurate; - this.inaccurate = obj.inaccurate; - this.seeking = obj.seeking; + this.#weapon = weapon; } - static fromObject(obj: AccDiffBaseSerialized): AccDiffBase { - return new AccDiffBase(obj); + static fromObject(obj: AccDiffBaseSerialized, extra: { weapon: AccDiffWeapon }): AccDiffBase { + return new AccDiffBase(obj, extra.weapon); } toObject(): AccDiffBaseSerialized { return { - untyped: this.untyped, + accuracy: this.accuracy, + difficulty: this.difficulty, cover: this.cover, - accurate: this.accurate, - inaccurate: this.inaccurate, - seeking: this.seeking } } - get accuracy() { - return this.untyped.accuracy + (this.accurate ? 1 : 0); - } - - get difficulty() { - return this.untyped.difficulty - + (this.inaccurate ? 1 : 0) - + (this.seeking ? 0 : this.cover) - } - get total() { - return this.accuracy - this.difficulty; + return this.accuracy - this.difficulty + + (this.#weapon.accurate ? 1 : 0) + - (this.#weapon.inaccurate ? 1 : 0) + - (this.#weapon.seeking ? 0 : this.cover) + } } -class AccDiffTarget { +class AccDiffTarget extends AccDiffBase { target: Token; - accuracy: number; - difficulty: number; - cover: Cover; #base: AccDiffBase; constructor(obj: { @@ -84,11 +75,9 @@ class AccDiffTarget { accuracy: number, difficulty: number, cover: Cover - }, base: AccDiffBase) { + }, base: AccDiffBase, weapon: AccDiffWeapon) { + super(obj, weapon); this.target = obj.target; - this.accuracy = obj.accuracy; - this.difficulty = obj.difficulty; - this.cover = obj.cover; this.#base = base; } @@ -101,7 +90,8 @@ class AccDiffTarget { } } - static fromObject(obj: AccDiffTargetSerialized, base: AccDiffBase): AccDiffTarget { + static fromObject(obj: AccDiffTargetSerialized, + extra: { base: AccDiffBase, weapon: AccDiffWeapon }): AccDiffTarget { let target = canvas.scene.tokens.get(obj.target); if (!target) { ui.notifications.error("Trying to access tokens from a different scene!"); @@ -112,26 +102,29 @@ class AccDiffTarget { accuracy: obj.accuracy, difficulty: obj.difficulty, cover: obj.cover - }, base) + }, extra.base, extra.weapon) } get total() { - return this.accuracy - this.difficulty - + this.#base.total - (this.#base.seeking ? 0 : this.cover); + // the only thing we actually use base for is the untyped bonuses + return super.total + this.#base.accuracy - this.#base.difficulty; } } export class AccDiffData { title: string; + weapon: AccDiffWeapon; base: AccDiffBase; targets: AccDiffTarget[]; constructor(obj: { title: string, base: AccDiffBase, + weapon: AccDiffWeapon, targets: AccDiffTarget[] }) { this.title = obj.title; + this.weapon = obj.weapon; this.base = obj.base; this.targets = obj.targets; } @@ -139,15 +132,20 @@ export class AccDiffData { toObject(): AccDiffDataSerialized { return { title: this.title, + weapon: this.weapon, base: this.base.toObject(), targets: this.targets.map(t => t.toObject()) } } static fromObject(obj: AccDiffDataSerialized): AccDiffData { - let base = AccDiffBase.fromObject(obj.base); - let targets = obj.targets.map(t => AccDiffTarget.fromObject(t, base)); - return new AccDiffData({ title: obj.title, base, targets }); + let base = AccDiffBase.fromObject(obj.base, obj); + let targets = obj.targets.map(t => AccDiffTarget.fromObject(t, { base, weapon: obj.weapon })); + return new AccDiffData({ + title: obj.title, + weapon: obj.weapon, + base, targets + }); } static fromParams( @@ -156,40 +154,41 @@ export class AccDiffData { targets?: Token[], starting?: [number, number] ): AccDiffData { - let base = AccDiffBase.fromObject({ + let weapon = { accurate: false, inaccurate: false, - cover: Cover.None, seeking: false, - untyped: { - accuracy: starting ? starting[0] : 0, - difficulty: starting ? starting[1] : 0 - }, - }); + }; for (let tag of (tags || [])) { switch (tag.Tag.LID) { case "tg_accurate": - base.accurate = true; + weapon.accurate = true; break; case "tg_inaccurate": - base.inaccurate = true; + weapon.inaccurate = true; break; case "tg_seeking": - base.seeking = true; + weapon.seeking = true; break; } } + let base = new AccDiffBase({ + cover: Cover.None, + accuracy: starting ? starting[0] : 0, + difficulty: starting ? starting[1] : 0, + }, weapon); + return new AccDiffData({ title: title ? `${title} - Accuracy and Difficulty` : "Accuracy and Difficulty", - base: base, + weapon, base, targets: (targets || []).map(t => new AccDiffTarget({ target: t, accuracy: 0, difficulty: 0, cover: Cover.None - }, base)) + }, base, weapon)) }); } } @@ -214,7 +213,7 @@ export class AccDiffForm extends ReactiveForm { getViewModel(data: AccDiffData): AccDiffView { let ret = data as AccDiffView; // view elements haven't been set yet ret.hasTargets = ret.targets.length > 1; - ret.baseCoverDisabled = ret.base.seeking || ret.hasTargets; + ret.baseCoverDisabled = ret.weapon.seeking || ret.hasTargets; return ret } } diff --git a/src/templates/window/acc_diff.hbs b/src/templates/window/acc_diff.hbs index 222bb01d4..3d6e54f31 100644 --- a/src/templates/window/acc_diff.hbs +++ b/src/templates/window/acc_diff.hbs @@ -8,12 +8,12 @@
@@ -24,7 +24,7 @@ {{{std-cover-input "base.cover" @@ -50,8 +50,8 @@ class="difficulty lancer-invisible-input dec-set" style="max-width: 2em;text-align: center;font-size: 1.2em" type="number" - name="base.untyped.accuracy" - value="{{base.untyped.accuracy}}" + name="base.accuracy" + value="{{base.accuracy}}" min="0" />
{{#unless hasTargets}} - {{{accdiff-total-display "base.total" - id="accdiff-total" - classes="accdiff-grid accdiff-weight" - }}} + {{#if hasExactlyOneTarget}} +
+
+ +
+ {{{accdiff-total-display "targets.0.total" + id="accdiff-total-0" + classes="accdiff-grid accdiff-weight" + }}} +
+ {{else}} + {{{accdiff-total-display "base.total" + id="accdiff-total" + classes="accdiff-grid accdiff-weight" + }}} + {{/if}} {{/unless}} {{#if hasTargets}}