diff --git a/module/BladesRollCollab.js b/module/BladesRollCollab.js index a8d97c5e..01bc7333 100644 --- a/module/BladesRollCollab.js +++ b/module/BladesRollCollab.js @@ -6,7 +6,7 @@ \* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ import U from "./core/utilities.js"; -import C, { BladesActorType, BladesItemType, RollType, RollModStatus, RollModCategory, ActionTrait, AttributeTrait, Position, Effect, Factor, RollResult, ConsequenceType } from "./core/constants.js"; +import C, { BladesActorType, BladesItemType, RollType, RollSubType, RollModStatus, RollModSection, ActionTrait, AttributeTrait, Position, Effect, Factor, RollResult, ConsequenceType } from "./core/constants.js"; import BladesActor from "./BladesActor.js"; import BladesItem from "./BladesItem.js"; import { ApplyTooltipListeners } from "./core/gsap.js"; @@ -39,7 +39,7 @@ export class BladesRollMod { } const catString = U.pullElement(pStrings, (v) => typeof v === "string" && /^cat/i.test(v)); const catVal = (typeof catString === "string" && catString.replace(/^.*:/, "")); - if (!catVal || !(catVal in RollModCategory)) { + if (!catVal || !(catVal in RollModSection)) { throw new Error(`RollMod Missing Category: '${modString}'`); } const posNegString = (U.pullElement(pStrings, (v) => typeof v === "string" && /^p/i.test(v)) || "posNeg:positive"); @@ -85,12 +85,18 @@ export class BladesRollMod { else if (/^a.{0,3}r?.{0,3}y/i.test(keyString)) { key = "autoRollTypes"; } + else if (/^p.{0,10}r?.{0,3}y/i.test(keyString)) { + key = "participantRollTypes"; + } else if (/^c.{0,10}r?.{0,3}tr/i.test(keyString)) { key = "conditionalRollTraits"; } else if (/^a.{0,3}r?.{0,3}tr/i.test(keyString)) { key = "autoRollTraits"; } + else if (/^p.{0,10}r?.{0,3}tr/i.test(keyString)) { + key = "participantRollTypes"; + } else { throw new Error(`Bad Roll Mod Key: ${keyString}`); } @@ -157,8 +163,10 @@ export class BladesRollMod { return [ ...this.conditionalRollTraits, ...this.autoRollTraits, + ...this.participantRollTraits, ...this.conditionalRollTypes, - ...this.autoRollTypes + ...this.autoRollTypes, + ...this.participantRollTypes ].length > 0; } get isInInactiveBlock() { @@ -187,22 +195,34 @@ export class BladesRollMod { }); return stressCost; } - setConditionalStatus() { + setConditionalStatus() { if (!this.isConditional) { return false; } - if (this.autoRollTypes.includes(this.rollInstance.rollType) - || this.autoRollTraits.includes(this.rollInstance.rollTrait)) { + const autoTypesOrTraitsApply = this.autoRollTypes.includes(this.rollInstance.rollType) + || this.autoRollTraits.includes(this.rollInstance.rollTrait); + if (autoTypesOrTraitsApply) { this.heldStatus = RollModStatus.ForcedOn; return false; } - if ((this.conditionalRollTypes.length === 0 || this.conditionalRollTypes.includes(this.rollInstance.rollType)) - && (this.conditionalRollTraits.length === 0 || this.conditionalRollTraits.includes(this.rollInstance.rollTrait))) { + const conditionalTypesOrTraitsApply = this.checkTypesOrTraits(this.conditionalRollTypes, this.conditionalRollTraits); + if (conditionalTypesOrTraitsApply) { + this.heldStatus = RollModStatus.ToggledOff; + return false; + } + const participantTypesOrTraitsApply = this.rollInstance.isParticipantRoll + && this.checkTypesOrTraits(this.participantRollTypes, this.participantRollTraits); + if (participantTypesOrTraitsApply) { this.heldStatus = RollModStatus.ToggledOff; return false; } this.heldStatus = RollModStatus.Hidden; return true; + } + checkTypesOrTraits(types, traits) { + const typesApply = (!this.rollInstance.isParticipantRoll && types.length === 0) || types.includes(this.rollInstance.rollType); + const traitsApply = (!this.rollInstance.isParticipantRoll && traits.length === 0) || traits.includes(this.rollInstance.rollTrait); + return typesApply && traitsApply; } processKey(key) { const [thisKey, thisParam] = key.split(/-/) ?? []; @@ -328,7 +348,7 @@ export class BladesRollMod { return; } const consequenceType = this.rollInstance.rollConsequence.type; - if (!consequenceType || !consequenceType.startsWith("Harm")) { + if (!consequenceType?.startsWith("Harm")) { return; } const curLevel = [ConsequenceType.Harm1, ConsequenceType.Harm2, ConsequenceType.Harm3, ConsequenceType.Harm4] @@ -379,7 +399,7 @@ export class BladesRollMod { return this._sideString; } switch (this.category) { - case RollModCategory.roll: { + case RollModSection.roll: { if (this.name === "Assist") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.roll.Assist"); if (!docID) { @@ -389,7 +409,7 @@ export class BladesRollMod { } return undefined; } - case RollModCategory.position: { + case RollModSection.position: { if (this.name === "Setup") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.position.Setup"); if (!docID) { @@ -399,7 +419,7 @@ export class BladesRollMod { } return undefined; } - case RollModCategory.effect: { + case RollModSection.effect: { if (this.name === "Setup") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.effect.Setup"); if (!docID) { @@ -452,7 +472,7 @@ export class BladesRollMod { label = `${this.name} (To Act)`; } else { - const effect = this.category === RollModCategory.roll ? "+1d" : "+1 effect"; + const effect = this.category === RollModSection.roll ? "+1d" : "+1 effect"; label = `${this.name} (${effect})`; } } @@ -477,8 +497,10 @@ export class BladesRollMod { modType; conditionalRollTypes; autoRollTypes; + participantRollTypes; conditionalRollTraits; autoRollTraits; + participantRollTraits; category; rollInstance; constructor(modData, rollInstance) { @@ -496,8 +518,10 @@ export class BladesRollMod { this.modType = modData.modType; this.conditionalRollTypes = modData.conditionalRollTypes ?? []; this.autoRollTypes = modData.autoRollTypes ?? []; + this.participantRollTypes = modData.participantRollTypes ?? []; this.conditionalRollTraits = modData.conditionalRollTraits ?? []; this.autoRollTraits = modData.autoRollTraits ?? []; + this.participantRollTraits = modData.participantRollTraits ?? []; this.category = modData.category; } } @@ -699,7 +723,6 @@ class BladesRollCollab extends DocumentSheet { }); } static Initialize() { - return loadTemplates([ "systems/eunos-blades/templates/roll/roll-collab.hbs", "systems/eunos-blades/templates/roll/roll-collab-gm.hbs", @@ -727,7 +750,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Push-positive-roll", name: "PUSH", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.ToggledOff, posNeg: "positive", modType: "general", @@ -738,7 +761,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Bargain-positive-roll", name: "Bargain", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "general", @@ -749,7 +772,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Assist-positive-roll", name: "Assist", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -759,7 +782,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Setup-positive-position", name: "Setup", - category: RollModCategory.position, + category: RollModSection.position, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -769,7 +792,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Push-positive-effect", name: "PUSH", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.ToggledOff, posNeg: "positive", modType: "general", @@ -780,7 +803,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Setup-positive-effect", name: "Setup", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -790,7 +813,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Potency-positive-effect", name: "Potency", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "general", @@ -800,7 +823,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Potency-negative-effect", name: "Potency", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "negative", modType: "general", @@ -832,7 +855,7 @@ class BladesRollCollab extends DocumentSheet { [Factor.magnitude]: 0 }, docSelections: { - [RollModCategory.roll]: { + [RollModSection.roll]: { Assist: false, Group_1: false, Group_2: false, @@ -841,10 +864,10 @@ class BladesRollCollab extends DocumentSheet { Group_5: false, Group_6: false }, - [RollModCategory.position]: { + [RollModSection.position]: { Setup: false }, - [RollModCategory.effect]: { + [RollModSection.effect]: { Setup: false } }, @@ -964,7 +987,18 @@ class BladesRollCollab extends DocumentSheet { eLog.error("rollCollab", "[RenderRollCollab()] Invalid rollPrimary", { rollPrimaryData, config }); return; } - if (U.isInt(config.rollTrait)) { + let rollPrimary; + if (BladesRollPrimary.IsDoc(rollPrimaryData)) { + rollPrimary = rollPrimaryData; + } + else if (BladesRollPrimary.IsDoc(rollPrimaryData.rollPrimaryDoc)) { + rollPrimary = rollPrimaryData.rollPrimaryDoc; + } + if (flagUpdateData.rollType === RollType.IndulgeVice && BladesActor.IsType(rollPrimary, BladesActorType.pc)) { + const minAttrVal = Math.min(...Object.values(rollPrimary.attributes)); + flagUpdateData.rollTrait = U.objFindKey(rollPrimary.attributes, (val) => val === minAttrVal); + } + else if (U.isInt(config.rollTrait)) { flagUpdateData.rollTrait = config.rollTrait; } else if (!config.rollTrait) { @@ -981,14 +1015,6 @@ class BladesRollCollab extends DocumentSheet { flagUpdateData.rollTrait = U.lCase(config.rollTrait); break; } - case RollType.Downtime: { - if (!(U.lCase(config.rollTrait) in { ...ActionTrait, ...Factor })) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Downtime Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait); - break; - } case RollType.Fortune: { if (!(U.lCase(config.rollTrait) in { ...ActionTrait, ...AttributeTrait, ...Factor })) { eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`, config); @@ -1165,31 +1191,31 @@ class BladesRollCollab extends DocumentSheet { get finalPosition() { return Object.values(Position)[U.clampNum(Object.values(Position) .indexOf(this.initialPosition) - + this.getModsDelta(RollModCategory.position) + + this.getModsDelta(RollModSection.position) + (this.posEffectTrade === "position" ? 1 : 0) + (this.posEffectTrade === "effect" ? -1 : 0), [0, 2])]; } get finalEffect() { return Object.values(Effect)[U.clampNum(Object.values(Effect) .indexOf(this.initialEffect) - + this.getModsDelta(RollModCategory.effect) + + this.getModsDelta(RollModSection.effect) + (this.posEffectTrade === "effect" ? 1 : 0) + (this.posEffectTrade === "position" ? -1 : 0), [0, 4])]; } get finalResult() { - return this.getModsDelta(RollModCategory.result) + return this.getModsDelta(RollModSection.result) + (this.rData?.GMBoosts.Result ?? 0) + (this.tempGMBoosts.Result ?? 0); } get finalDicePool() { return Math.max(0, this.rollTraitData.value - + this.getModsDelta(RollModCategory.roll) + + this.getModsDelta(RollModSection.roll) + (this.rData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)); } get isRollingZero() { return Math.max(0, this.rollTraitData.value - + this.getModsDelta(RollModCategory.roll) + + this.getModsDelta(RollModSection.roll) + (this.rData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)) <= 0; } @@ -1364,9 +1390,9 @@ class BladesRollCollab extends DocumentSheet { .filter((mod) => !mod.isBasicPush) .forEach((mod) => { mod.heldStatus = RollModStatus.ForcedOff; }); } - [RollModCategory.roll, RollModCategory.effect].forEach((cat) => { + [RollModSection.roll, RollModSection.effect].forEach((cat) => { if (this.isPushed(cat)) { - if (cat === RollModCategory.roll && this.isPushed(cat, "positive")) { + if (cat === RollModSection.roll && this.isPushed(cat, "positive")) { const bargainMod = this.getRollModByID("Bargain-positive-roll"); if (bargainMod?.isVisible) { bargainMod.heldStatus = RollModStatus.ForcedOff; @@ -1398,6 +1424,10 @@ class BladesRollCollab extends DocumentSheet { } return false; } + get isParticipantRoll() { + return (this.rollType === RollType.Fortune && !game.user.isGM) + || (this.rollSubType === RollSubType.GroupParticipant); + } rollFactorPenaltiesNegated = {}; negateFactorPenalty(factor) { this.rollFactorPenaltiesNegated[factor] = true; @@ -1413,7 +1443,7 @@ class BladesRollCollab extends DocumentSheet { const harmPush = this.getRollModByID("Push-negative-roll"); const rollPush = this.getRollModByID("Push-positive-roll"); const effectPush = this.getRollModByID("Push-positive-effect"); - const negatePushCostMods = this.getActiveRollMods(RollModCategory.after, "positive") + const negatePushCostMods = this.getActiveRollMods(RollModSection.after, "positive") .filter((mod) => mod.effectKeys.includes("Negate-PushCost")); return ((harmPush?.isActive && harmPush?.stressCost) || 0) + ((rollPush?.isActive && rollPush?.stressCost) || 0) @@ -1593,9 +1623,9 @@ class BladesRollCollab extends DocumentSheet { teamworkDocs: game.actors.filter((actor) => BladesActor.IsType(actor, BladesActorType.pc)), rollTraitValOverride: this.rollTraitValOverride, rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated, - posRollMods: Object.fromEntries(Object.values(RollModCategory) + posRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "positive")])), - negRollMods: Object.fromEntries(Object.values(RollModCategory) + negRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "negative")])), hasInactiveConditionals: this.calculateHasInactiveConditionalsData(), rollFactors, @@ -1626,16 +1656,16 @@ class BladesRollCollab extends DocumentSheet { return { rollEffects: Object.values(Effect), rollEffectFinal: finalEffect, - isAffectingAfter: this.getVisibleRollMods(RollModCategory.after).length > 0 - || (isGM && this.getRollMods(RollModCategory.after).length > 0) + isAffectingAfter: this.getVisibleRollMods(RollModSection.after).length > 0 + || (isGM && this.getRollMods(RollModSection.after).length > 0) }; } calculateResultData(isGM, finalResult) { return { rollResultFinal: finalResult, isAffectingResult: finalResult > 0 - || this.getVisibleRollMods(RollModCategory.result).length > 0 - || (isGM && this.getRollMods(RollModCategory.result).length > 0) + || this.getVisibleRollMods(RollModSection.result).length > 0 + || (isGM && this.getRollMods(RollModSection.result).length > 0) }; } calculateGMBoostsData(flagData) { @@ -1706,7 +1736,7 @@ class BladesRollCollab extends DocumentSheet { } calculateHasInactiveConditionalsData() { const hasInactive = {}; - for (const category of Object.values(RollModCategory)) { + for (const category of Object.values(RollModSection)) { hasInactive[category] = this.getRollMods(category).filter((mod) => mod.isInInactiveBlock).length > 0; } return hasInactive; @@ -1775,10 +1805,10 @@ class BladesRollCollab extends DocumentSheet { rollEffectFinal: finalEffect, rollResultFinal: finalResult, isAffectingResult: finalResult > 0 - || this.getVisibleRollMods(RollModCategory.result).length > 0 - || (isGM && this.getRollMods(RollModCategory.result).length > 0), - isAffectingAfter: this.getVisibleRollMods(RollModCategory.after).length > 0 - || (isGM && this.getRollMods(RollModCategory.after).length > 0), + || this.getVisibleRollMods(RollModSection.result).length > 0 + || (isGM && this.getRollMods(RollModSection.result).length > 0), + isAffectingAfter: this.getVisibleRollMods(RollModSection.after).length > 0 + || (isGM && this.getRollMods(RollModSection.after).length > 0), rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated, GMBoosts: { Dice: this.rData.GMBoosts.Dice ?? 0, @@ -1802,24 +1832,24 @@ class BladesRollCollab extends DocumentSheet { || (posEffectTrade === false && finalPosition !== Position.controlled && finalEffect !== Effect.zero), - posRollMods: Object.fromEntries(Object.values(RollModCategory) + posRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "positive")])), - negRollMods: Object.fromEntries(Object.values(RollModCategory) + negRollMods: Object.fromEntries(Object.values(RollModSection) .map((cat) => [cat, this.getRollMods(cat, "negative")])), hasInactiveConditionals: { - [RollModCategory.roll]: this.getRollMods(RollModCategory.roll) + [RollModSection.roll]: this.getRollMods(RollModSection.roll) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.position]: this.getRollMods(RollModCategory.position) + [RollModSection.position]: this.getRollMods(RollModSection.position) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.effect]: this.getRollMods(RollModCategory.effect) + [RollModSection.effect]: this.getRollMods(RollModSection.effect) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.result]: this.getRollMods(RollModCategory.result) + [RollModSection.result]: this.getRollMods(RollModSection.result) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.after]: this.getRollMods(RollModCategory.after) + [RollModSection.after]: this.getRollMods(RollModSection.after) .filter((mod) => mod.isInInactiveBlock) .length > 0 }, @@ -1991,10 +2021,10 @@ class BladesRollCollab extends DocumentSheet { }); break; } - case RollType.Downtime: { + case RollType.Fortune: { break; } - case RollType.Fortune: { + case RollType.IndulgeVice: { break; } default: throw new Error(`Unrecognized RollType: ${this.rollType}`); diff --git a/module/blades.js b/module/blades.js index 9e7491d3..805614ee 100644 --- a/module/blades.js +++ b/module/blades.js @@ -36,8 +36,8 @@ Object.assign(globalThis, { updateContacts, updateOps, updateFactions, - applyDescriptions: updateDescriptions, - applyRollEffects: updateRollMods, + updateDescriptions, + updateRollMods, BladesActor, BladesPCSheet, BladesCrewSheet, diff --git a/module/core/constants.js b/module/core/constants.js index d87aeeac..d3d84aa6 100644 --- a/module/core/constants.js +++ b/module/core/constants.js @@ -156,15 +156,17 @@ export var DowntimeAction; export var RollType; (function (RollType) { RollType["Action"] = "Action"; - RollType["Downtime"] = "Downtime"; RollType["Resistance"] = "Resistance"; RollType["Fortune"] = "Fortune"; + RollType["IndulgeVice"] = "Vice"; })(RollType || (RollType = {})); export var RollSubType; (function (RollSubType) { RollSubType["Incarceration"] = "Incarceration"; RollSubType["Engagement"] = "Engagement"; RollSubType["GatherInfo"] = "GatherInfo"; + RollSubType["GroupLead"] = "GroupLead"; + RollSubType["GroupParticipant"] = "GroupParticipant"; })(RollSubType || (RollSubType = {})); export var ConsequenceType; (function (ConsequenceType) { @@ -186,14 +188,14 @@ export var RollModStatus; RollModStatus["ForcedOn"] = "ForcedOn"; RollModStatus["Dominant"] = "Dominant"; })(RollModStatus || (RollModStatus = {})); -export var RollModCategory; -(function (RollModCategory) { - RollModCategory["roll"] = "roll"; - RollModCategory["position"] = "position"; - RollModCategory["effect"] = "effect"; - RollModCategory["result"] = "result"; - RollModCategory["after"] = "after"; -})(RollModCategory || (RollModCategory = {})); +export var RollModSection; +(function (RollModSection) { + RollModSection["roll"] = "roll"; + RollModSection["position"] = "position"; + RollModSection["effect"] = "effect"; + RollModSection["result"] = "result"; + RollModSection["after"] = "after"; +})(RollModSection || (RollModSection = {})); export var Position; (function (Position) { Position["desperate"] = "desperate"; diff --git a/module/core/helpers.js b/module/core/helpers.js index 220581b2..4a0701a1 100644 --- a/module/core/helpers.js +++ b/module/core/helpers.js @@ -5,7 +5,7 @@ |* ▌██████████████████░░░░░░░░░░░░░░░░░░ ░░░░░░░░░░░░░░░░░░███████████████████▐ *| \* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ -import U from "./utilities.js"; +import U from "./utilities.js"; import { SVGDATA } from "./constants.js"; export async function preloadHandlebarsTemplates() { @@ -33,15 +33,15 @@ export async function preloadHandlebarsTemplates() { } const handlebarHelpers = { - "randString": function (param1 = 10) { + randString(param1 = 10) { return U.randString(param1); }, - "test": function (param1, operator, param2) { + test(param1, operator, param2) { const stringMap = { "true": true, "false": false, "null": null, - "undefined": undefined + undefined }; if (["!", "not", "=??"].includes(String(param1))) { [operator, param1] = [String(param1), operator]; @@ -67,14 +67,12 @@ const handlebarHelpers = { return param1 || param2; } case "==": { - return param1 == param2; + return U.areFuzzyEqual(param1, param2); } case "===": { return param1 === param2; } - case "!=": { - return param1 != param2; - } + case "!=": case "!==": { return param1 !== param2; } @@ -113,7 +111,7 @@ const handlebarHelpers = { } } }, - "calc": function (...params) { + calc(...params) { const calcs = { "+": (p1, p2) => U.pInt(p1) + U.pInt(p2), "-": (p1, p2) => U.pInt(p1) - U.pInt(p2), @@ -130,11 +128,11 @@ const handlebarHelpers = { : params; return calcs[operator](param1, param2); }, - "isIn": function () { - const [testStr, ...contents] = arguments; + isIn(...args) { + const [testStr, ...contents] = args; return contents.includes(testStr); }, - "case": function (mode, str) { + case(mode, str) { switch (mode) { case "upper": return U.uCase(str); case "lower": return U.lCase(str); @@ -143,7 +141,7 @@ const handlebarHelpers = { default: return str; } }, - "count": function (param) { + count(param) { if (Array.isArray(param) || U.isList(param)) { return Object.values(param).filter((val) => val !== null && val !== undefined).length; } @@ -152,7 +150,7 @@ const handlebarHelpers = { } return param ? 1 : 0; }, - "forloop": (...args) => { + forloop: (...args) => { const options = args.pop(); let [from, to, stepSize] = args; from = U.pInt(from); @@ -162,19 +160,15 @@ const handlebarHelpers = { return ""; } let html = ""; - for (let i = parseInt(from || 0, 10); i <= parseInt(to || 0, 10); i++) { + for (let i = parseInt(from || 0, 10); i <= parseInt(to || 0, 10); i += stepSize) { html += options.fn(i); } return html; }, - "signNum": function (num) { + signNum(num) { return U.signNum(num); }, - "areEmpty": function (...args) { - args.pop(); - return !Object.values(args).flat().join(""); - }, - "compileSvg": function (...args) { + compileSvg(...args) { const [svgDotKey, svgPaths] = args; const svgData = getProperty(SVGDATA, svgDotKey); if (!svgData) { @@ -189,7 +183,7 @@ const handlebarHelpers = { "" ].join("\n"); }, - "eLog": function (...args) { + eLog(...args) { args.pop(); let dbLevel = 5; if ([0, 1, 2, 3, 4, 5].includes(args[0])) { @@ -197,8 +191,8 @@ const handlebarHelpers = { } eLog.hbsLog(...args, dbLevel); }, - "isTurfBlock": (name) => U.fuzzyMatch(name, "Turf"), - "getConnectorPartner": (index, direction) => { + isTurfBlock: (name) => U.fuzzyMatch(name, "Turf"), + getConnectorPartner: (index, direction) => { index = parseInt(`${index}`, 10); const partners = { 1: { right: 2, bottom: 6 }, @@ -224,7 +218,7 @@ const handlebarHelpers = { } return null; }, - "isTurfOnEdge": (index, direction) => { + isTurfOnEdge: (index, direction) => { index = parseInt(`${index}`, 10); const edges = { 1: ["top", "left"], @@ -248,93 +242,37 @@ const handlebarHelpers = { } return edges[index].includes(direction); }, - "multiboxes": function (selected, options) { + multiboxes(selected, options) { let html = options.fn(this); selected = [selected].flat(1); - selected.forEach((selected_value) => { - if (selected_value !== false) { - const escapedValue = RegExp.escape(Handlebars.escapeExpression(String(selected_value))); - const rgx = new RegExp(' value=\"' + escapedValue + '\"'); + selected.forEach((selectedVal) => { + if (selectedVal !== false) { + const escapedValue = RegExp.escape(Handlebars.escapeExpression(String(selectedVal))); + const rgx = new RegExp(` value="${escapedValue}"`); html = html.replace(rgx, "$& checked=\"checked\""); } }); return html; - }, - "noteq": (a, b, options) => (a !== b ? options.fn(this) : ""), - "repturf": (turfs_amount, options) => { - let html = options.fn(this), turfs_amount_int = parseInt(turfs_amount, 10); - if (turfs_amount_int > 6) { - turfs_amount_int = 6; + }, + repturf: (turfsAmount, options) => { + let html = options.fn(this), turfsAmountInt = parseInt(turfsAmount, 10); + if (turfsAmountInt > 6) { + turfsAmountInt = 6; } - for (let i = 13 - turfs_amount_int; i <= 12; i++) { - const rgx = new RegExp(' value=\"' + i + '\"'); + for (let i = 13 - turfsAmountInt; i <= 12; i++) { + const rgx = new RegExp(` value="${i}"`); html = html.replace(rgx, "$& disabled=\"disabled\""); } return html; - }, - "crew_vault_coins": (max_coins, options) => { - let html = options.fn(this); - for (let i = 1; i <= max_coins; i++) { - html += ""; - } - return html; - }, - "crew_experience": (actor, options) => { - let html = options.fn(this); - for (let i = 1; i <= 10; i++) { - html += ``; - } - return html; - }, - "html": (options) => { - const text = options.hash.text.replace(/\n/g, "
"); - return new Handlebars.SafeString(text); }, - // - "times_from_1": (n, block) => { - n = parseInt(n, 10); - let accum = ""; - for (let i = 1; i <= n; ++i) { - accum += block.fn(i); - } - return accum; - }, - // - "times_from_0": (n, block) => { - n = parseInt(n, 10); - let accum = ""; - for (let i = 0; i <= n; ++i) { - accum += block.fn(i); - } - return accum; - }, - "concat": function () { + concat(...args) { let outStr = ""; - for (const arg in arguments) { - if (typeof arguments[arg] !== "object") { - outStr += arguments[arg]; + for (const arg of args) { + if (typeof arg === "string") { + outStr += arg; } } return outStr; - }, - "selectOptionsWithLabel": (choices, options) => { - const localize = options.hash.localize ?? false; - let selected = options.hash.selected ?? null; - const blank = options.hash.blank || null; - selected = selected instanceof Array ? selected.map(String) : [String(selected)]; - const option = (key, object) => { - if (localize) { - object.label = game.i18n.localize(object.label); - } - const isSelected = selected.includes(key); - html += ``; - }; - let html = ""; - if (blank) { - option("", blank); - } - Object.entries(choices).forEach(e => option(...e)); - return new Handlebars.SafeString(html); } }; handlebarHelpers.eLog1 = function (...args) { handlebarHelpers.eLog(...[1, ...args.slice(0, 7)]); }; diff --git a/module/core/utilities.js b/module/core/utilities.js index 5601a1b8..d0633bdf 100644 --- a/module/core/utilities.js +++ b/module/core/utilities.js @@ -171,59 +171,59 @@ function assertNonNullType(val, type) { throw new Error(`Value ${valStr} is not a ${type.name}!`); } } -const areEqual = (...refs) => { - do { - const ref = refs.pop(); - if (refs.length && !checkEquality(ref, refs[0])) { - return false; - } - } while (refs.length); - return true; -}; -const checkEquality = (ref1, ref2) => { - if (typeof ref1 !== typeof ref2) { +const areFuzzyEqual = (val1, val2) => { + if ([null, undefined].includes(val1) && [null, undefined].includes(val2)) { + return true; + } + if ([null, undefined].includes(val1) || [null, undefined].includes(val2)) { return false; + } + if (typeof val1 === "number" && typeof val2 === "number") { + return val1 === val2; + } + if (typeof val1 === "boolean" && typeof val2 === "boolean") { + return val1 === val2; + } + if (typeof val1 === "string" && typeof val2 === "string") { + return val1 === val2; + } + if (typeof val1 === "number" && typeof val2 === "string") { + return val1 === Number(val2); } - if ([ref1, ref2].includes(null)) { - return ref1 === ref2; - } - if (typeof ref1 === "object") { - return checkObjectEquality(ref1, ref2); - } - else { - return ref1 === ref2; - } -}; -const checkObjectEquality = (obj1, obj2) => { - if (isArray(obj1)) { - return checkArrayEquality(obj1, obj2); - } - else if (isList(obj1)) { - return checkListEquality(obj1, obj2); - } - else { - return checkOtherObjectEquality(obj1, obj2); - } -}; -const checkArrayEquality = (arr1, arr2) => { - if (!isArray(arr2) || arr1.length !== arr2.length) { + if (typeof val1 === "string" && typeof val2 === "number") { + return Number(val1) === val2; + } + if (typeof val1 === "boolean" && typeof val2 === "object") { return false; } - return arr1.every((value, index) => checkEquality(value, arr2[index])); -}; -const checkListEquality = (list1, list2) => { - if (!isList(list2) || Object.keys(list1).length !== Object.keys(list2).length) { + if (typeof val1 === "object" && typeof val2 === "boolean") { return false; + } + if (typeof val1 === "boolean" && typeof val2 === "string") { + return (val1 && val2 !== "") || (!val1 && val2 === ""); } - return checkEquality(Object.keys(list1), Object.keys(list2)) && checkEquality(Object.values(list1), Object.values(list2)); -}; -const checkOtherObjectEquality = (obj1, obj2) => { - try { - return JSON.stringify(obj1) === JSON.stringify(obj2); - } - catch { + if (typeof val1 === "string" && typeof val2 === "boolean") { + return (val2 && val1 !== "") || (!val2 && val1 === ""); + } + if ((typeof val1 === "number" || typeof val1 === "string") && typeof val2 === "object") { return false; } + if (typeof val1 === "object" && (typeof val2 === "number" || typeof val2 === "string")) { + return false; + } + if (typeof val1 === "object" && typeof val2 === "object") { + return val1 === val2; + } + return false; +}; +const areEqual = (...refs) => { + do { + const ref = refs.pop(); + if (refs.length && !areFuzzyEqual(ref, refs[0])) { + return false; + } + } while (refs.length); + return true; }; const pFloat = (ref, sigDigits, isStrict = false) => { if (typeof ref === "string") { @@ -1000,6 +1000,25 @@ function objNullify(obj) { }); return obj; } +function objFreezeProps(data, ...keysOrSchema) { + const firstArg = keysOrSchema[0]; + if (firstArg instanceof Object && !Array.isArray(firstArg)) { + const schema = firstArg; + for (const key in schema) { + if (data[key] === undefined) { + throw new Error(`Missing value for ${key}`); + } + } + } + else { + for (const key of keysOrSchema) { + if (data[key] === undefined) { + throw new Error(`Missing value for ${String(key)}`); + } + } + } + return data; +} const getDynamicFunc = (funcName, func, context) => { if (typeof func === "function") { @@ -1177,7 +1196,7 @@ export default { isNumber, isSimpleObj, isList, isArray, isFunc, isInt, isFloat, isPosInt, isIterable, isHTMLCode, isRGBColor, isHexColor, isUndefined, isDefined, isEmpty, hasItems, isInstance, - areEqual, + areEqual, areFuzzyEqual, pFloat, pInt, radToDeg, degToRad, getKey, assertNonNullType, @@ -1206,7 +1225,8 @@ export default { subGroup, shuffle, remove, replace, partition, objClean, objSize, objMap, objFindKey, objFilter, objForEach, objCompact, - objClone, objMerge, objDiff, objExpand, objFlatten, objNullify, + objClone, objMerge, objDiff, objExpand, objFlatten, objNullify, + objFreezeProps, getDynamicFunc, withLog, gsap, get, set, getGSAngleDelta, diff --git a/module/data-import/data-import.js b/module/data-import/data-import.js index 3401fa1f..63c0e272 100644 --- a/module/data-import/data-import.js +++ b/module/data-import/data-import.js @@ -3100,7 +3100,7 @@ export const updateOps = async () => { const playbookObj = game.items.getName(op.playbook); if (!playbookObj || playbookObj.type !== BladesItemType.crew_playbook) { errorReport.push(`Favored Op ${op.name} has invalid playbook ${op.playbook}`); - return; + return undefined; } const item = await BladesItem.create({ name: op.name, @@ -3111,8 +3111,9 @@ export const updateOps = async () => { } }); if (BladesItem.IsType(item, BladesItemType.preferred_op)) { - item.addTag(playbookObj.name); + return item.addTag(playbookObj.name); } + return undefined; })); console.log(errorReport); }; @@ -3122,7 +3123,7 @@ export const updateContacts = async () => { const playbookObj = game.items.getName(ct.playbook); if (!BladesItem.IsType(playbookObj, BladesItemType.crew_playbook)) { errorReport.push(`Contact ${ct.name} has invalid playbook ${ct.playbook}`); - return; + return undefined; } const actor = await Actor.create({ name: ct.name, @@ -3132,12 +3133,12 @@ export const updateContacts = async () => { prompts: ct.hints?.join(" ") } }); - actor.addTag(playbookObj.name); + return actor.addTag(playbookObj.name); })); console.log(errorReport); }; const updateFactionData = async (factionData) => { - const faction = await game.actors.getName(factionData.name); + const faction = game.actors.getName(factionData.name); const updateData = {}; if (faction) { updateData["system.subtitle"] = factionData.subtitle ?? ""; @@ -3196,333 +3197,324 @@ const updateFactionData = async (factionData) => { } }; export const updateFactions = async () => { - Object.values(JSONDATA.FACTIONS).forEach(async (factionData) => { - updateFactionData(factionData); - }); + await Promise.all(Object.values(JSONDATA.FACTIONS).map(async (factionData) => updateFactionData(factionData))); console.log(problemLog); }; export const updateRollMods = async () => { - Object.entries(JSONDATA.ABILITIES.RollMods) - .forEach(async ([aName, eData]) => { - const abilityDoc = game.items.getName(aName); - if (!abilityDoc) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Not Found.`); - return; - } - const abilityEffects = Array.from(abilityDoc.effects ?? []); - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - const testChange = eData[0]; - if ((testChange.isMember && eData.some((change) => !change.isMember)) - || (!testChange.isMember && eData.some((change) => change.isMember))) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isMember' entries.`); - return; - } - if ((testChange.isCohort && eData.some((change) => !change.isCohort)) - || (!testChange.isCohort && eData.some((change) => change.isCohort))) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isCohort' entries.`); - return; - } - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; + await Promise.all([ + ...Object.entries(JSONDATA.ABILITIES.RollMods) + .map(async ([aName, eData]) => { + const abilityDoc = game.items.getName(aName); + if (!abilityDoc) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Not Found.`); + return undefined; } - const effectData = { - name: aName, - icon: abilityDoc.img ?? "", - changes: eData.map((change) => { - delete change.isMember; - return change; - }) - }; - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + const abilityEffects = Array.from(abilityDoc.effects ?? []); + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + const testChange = eData[0]; + if ((testChange.isMember && eData.some((change) => !change.isMember)) + || (!testChange.isMember && eData.some((change) => change.isMember))) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isMember' entries.`); } - else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` - }); + if ((testChange.isCohort && eData.some((change) => !change.isCohort)) + || (!testChange.isCohort && eData.some((change) => change.isCohort))) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isCohort' entries.`); } - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + const effectData = { + name: aName, + icon: abilityDoc.img ?? "", + changes: eData.map((change) => { + delete change.isMember; + return change; + }) + }; + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` + }); + } + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - const effectData = { - name: aName, - icon: abilityDoc.img ?? "", - changes: eData.map((change) => { - delete change.isCohort; - return change; - }) - }; - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: abilityDoc.img ?? "", + changes: eData.map((change) => { + delete change.isCohort; + return change; + }) + }; + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` + }); + } + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` - }); + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: abilityDoc.img ?? "", + changes: eData + }; + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple Active Effects`); - return; + }), + ...Object.entries(JSONDATA.CREW_ABILITIES.RollMods) + .map(async ([aName, eData]) => { + const crewAbilityDoc = game.items.getName(aName); + if (!crewAbilityDoc) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Not Found.`); + return undefined; } - const effectData = { - name: aName, - icon: abilityDoc.img ?? "", - changes: eData - }; - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + const abilityEffects = Array.from(crewAbilityDoc.effects ?? []); + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + const testChange = eData[0]; + if ((testChange.isMember && eData.some((change) => !change.isMember)) + || (!testChange.isMember && eData.some((change) => change.isMember))) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isMember' entries.`); } - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); - Object.entries(JSONDATA.CREW_ABILITIES.RollMods) - .forEach(async ([aName, eData]) => { - const crewAbilityDoc = game.items.getName(aName); - if (!crewAbilityDoc) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Not Found.`); - return; - } - const abilityEffects = Array.from(crewAbilityDoc.effects ?? []); - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - const testChange = eData[0]; - if ((testChange.isMember && eData.some((change) => !change.isMember)) - || (!testChange.isMember && eData.some((change) => change.isMember))) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isMember' entries.`); - return; - } - if ((testChange.isCohort && eData.some((change) => !change.isCohort)) - || (!testChange.isCohort && eData.some((change) => change.isCohort))) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isCohort' entries.`); - return; - } - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; - } - const effectData = { - name: aName, - icon: crewAbilityDoc.img ?? "", - changes: eData.map((change) => { - delete change.isMember; - return change; - }) - }; - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } - else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` - }); + if ((testChange.isCohort && eData.some((change) => !change.isCohort)) + || (!testChange.isCohort && eData.some((change) => change.isCohort))) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isCohort' entries.`); } - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + const effectData = { + name: aName, + icon: crewAbilityDoc.img ?? "", + changes: eData.map((change) => { + delete change.isMember; + return change; + }) + }; + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` + }); + } + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - const effectData = { - name: aName, - icon: crewAbilityDoc.img ?? "", - changes: eData.map((change) => { - delete change.isCohort; - return change; - }) - }; - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: crewAbilityDoc.img ?? "", + changes: eData.map((change) => { + delete change.isCohort; + return change; + }) + }; + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` + }); + } + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` - }); + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: crewAbilityDoc.img ?? "", + changes: eData + }; + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple Active Effects`); - return; + }), + ...Object.entries(JSONDATA.CREW_UPGRADES.RollMods) + .map(async ([aName, eData]) => { + const crewUpgradeDoc = game.items.getName(aName); + if (!crewUpgradeDoc) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Not Found.`); + return undefined; } - const effectData = { - name: aName, - icon: crewAbilityDoc.img ?? "", - changes: eData - }; - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + const abilityEffects = Array.from(crewUpgradeDoc.effects ?? []); + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + const testChange = eData[0]; + if ((testChange.isMember && eData.some((change) => !change.isMember)) + || (!testChange.isMember && eData.some((change) => change.isMember))) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isMember' entries.`); } - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); - Object.entries(JSONDATA.CREW_UPGRADES.RollMods) - .forEach(async ([aName, eData]) => { - const crewUpgradeDoc = game.items.getName(aName); - if (!crewUpgradeDoc) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Not Found.`); - return; - } - const abilityEffects = Array.from(crewUpgradeDoc.effects ?? []); - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - const testChange = eData[0]; - if ((testChange.isMember && eData.some((change) => !change.isMember)) - || (!testChange.isMember && eData.some((change) => change.isMember))) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isMember' entries.`); - return; - } - if ((testChange.isCohort && eData.some((change) => !change.isCohort)) - || (!testChange.isCohort && eData.some((change) => change.isCohort))) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isCohort' entries.`); - return; - } - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; + if ((testChange.isCohort && eData.some((change) => !change.isCohort)) + || (!testChange.isCohort && eData.some((change) => change.isCohort))) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isCohort' entries.`); } - const effectData = { - name: aName, - icon: crewUpgradeDoc.img ?? "", - changes: eData.map((change) => { - delete change.isMember; - return change; - }) - }; - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + const effectData = { + name: aName, + icon: crewUpgradeDoc.img ?? "", + changes: eData.map((change) => { + delete change.isMember; + return change; + }) + }; + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` + }); + } + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` - }); - } - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; - } - const effectData = { - name: aName, - icon: crewUpgradeDoc.img ?? "", - changes: eData.map((change) => { - delete change.isCohort; - return change; - }) - }; - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); + else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: crewUpgradeDoc.img ?? "", + changes: eData.map((change) => { + delete change.isCohort; + return change; + }) + }; + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` + }); + } + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` - }); - } - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple Active Effects`); - return; + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple Active Effects`); + return undefined; + } + const effectData = { + name: aName, + icon: crewUpgradeDoc.img ?? "", + changes: eData + }; + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0]; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); } - const effectData = { - name: aName, - icon: crewUpgradeDoc.img ?? "", - changes: eData - }; - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0]; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); + }) + ]); }; export const updateDescriptions = async () => { - Object.entries({ + return Promise.all(Object.entries({ ...JSONDATA.ABILITIES.Descriptions, ...JSONDATA.CREW_ABILITIES.Descriptions, ...JSONDATA.CREW_UPGRADES.Descriptions }) - .forEach(async ([aName, desc]) => { + .map(async ([aName, desc]) => { const itemDoc = game.items.getName(aName); if (!itemDoc) { eLog.error("applyRollEffects", `ApplyDescriptions: Item Doc ${aName} Not Found.`); - return; + return undefined; } - itemDoc.update({ "system.notes": desc }); - }); + return itemDoc.update({ "system.notes": desc }); + })); }; //# sourceMappingURL=data-import.js.map //# sourceMappingURL=data-import.js.map diff --git a/module/documents/actors/BladesPC.js b/module/documents/actors/BladesPC.js index 398f26d3..cdecd644 100644 --- a/module/documents/actors/BladesPC.js +++ b/module/documents/actors/BladesPC.js @@ -6,7 +6,7 @@ \* ****▌███████████████████████████████████████████████████████████████████████████▐**** */ import BladesItem from "../../BladesItem.js"; -import C, { AttributeTrait, Harm, BladesActorType, BladesItemType, Tag, RollModCategory, Factor, RollModStatus } from "../../core/constants.js"; +import C, { AttributeTrait, Harm, BladesActorType, BladesItemType, Tag, RollModSection, Factor, RollModStatus } from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActor from "../../BladesActor.js"; import { BladesRollMod } from "../../BladesRollCollab.js"; @@ -34,7 +34,7 @@ class BladesPC extends BladesActor { return game.users?.find((user) => user.character?.id === this?.id) || null; } async clearLoadout() { - this.update({ "system.loadout.selected": "" }); + await this.update({ "system.loadout.selected": "" }); this.updateEmbeddedDocuments("Item", [ ...this.activeSubItems.filter((item) => BladesItem.IsType(item, BladesItemType.gear) && !item.hasTag(Tag.System.Archived)) .map((item) => ({ @@ -182,7 +182,7 @@ class BladesPC extends BladesActor { if (!BladesActor.IsType(this, BladesActorType.pc)) { return; } - this.update({ "system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max) }); + await this.update({ "system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max) }); } get rollFactors() { const factorData = { @@ -216,7 +216,7 @@ class BladesPC extends BladesActor { get rollPrimaryImg() { return this.img; } get rollModsData() { const rollModsData = BladesRollMod.ParseDocRollMods(this); - [[/1d/, RollModCategory.roll], [/Less Effect/, RollModCategory.effect]].forEach(([effectPat, effectCat]) => { + [[/1d/, RollModSection.roll], [/Less Effect/, RollModSection.effect]].forEach(([effectPat, effectCat]) => { const { one: harmConditionOne, two: harmConditionTwo } = Object.values(this.system.harm) .find((harmData) => effectPat.test(harmData.effect)) ?? {}; const harmString = U.objCompact([harmConditionOne, harmConditionTwo === "" ? null : harmConditionTwo]).join(" & "); @@ -230,9 +230,9 @@ class BladesPC extends BladesActor { modType: "harm", value: 1, tooltip: [ - `

${effectCat === RollModCategory.roll ? Harm.Impaired : Harm.Weakened} (Harm)

`, + `

${effectCat === RollModSection.roll ? Harm.Impaired : Harm.Weakened} (Harm)

`, `

${harmString}

`, - effectCat === RollModCategory.roll + effectCat === RollModSection.roll ? "

If your injuries apply to the situation at hand, you suffer −1d to your roll.

" : "

If your injuries apply to the situation at hand, you suffer −1 effect." ].join("") @@ -245,7 +245,7 @@ class BladesPC extends BladesActor { id: "Push-negative-roll", name: "PUSH", sideString: harmCondition.trim(), - category: RollModCategory.roll, + category: RollModSection.roll, posNeg: "negative", base_status: RollModStatus.ToggledOn, modType: "harm", diff --git a/module/sheets/actor/BladesPCSheet.js b/module/sheets/actor/BladesPCSheet.js index e560b23e..fc167992 100644 --- a/module/sheets/actor/BladesPCSheet.js +++ b/module/sheets/actor/BladesPCSheet.js @@ -271,7 +271,7 @@ class BladesPCSheet extends BladesActorSheet { super._onAdvanceClick(event); const action = $(event.currentTarget).data("action").replace(/^advance-/, ""); if (action in AttributeTrait) { - this.actor.advanceAttribute(action); + await this.actor.advanceAttribute(action); } } activateListeners(html) { diff --git a/templates/items/clock_keeper-sheet.hbs b/templates/items/clock_keeper-sheet.hbs index 4803bf9a..4f4f4a9d 100644 --- a/templates/items/clock_keeper-sheet.hbs +++ b/templates/items/clock_keeper-sheet.hbs @@ -69,9 +69,9 @@ @@ -85,9 +85,9 @@ - {{#times_from_1 keyData.numClocks}} + {{#forloop 1 keyData.numClocks}} {{> "systems/eunos-blades/templates/parts/clock-sheet-row.hbs" keyID=keyID keyData=keyData clockData=(lookup keyData.clocks this) clockNum=this}} - {{/times_from_1}} + {{/forloop}} {{/each}} diff --git a/templates/parts/clock-sheet-row.hbs b/templates/parts/clock-sheet-row.hbs index b568753c..c39f9c70 100644 --- a/templates/parts/clock-sheet-row.hbs +++ b/templates/parts/clock-sheet-row.hbs @@ -55,9 +55,9 @@ diff --git a/templates/parts/turf-list.hbs b/templates/parts/turf-list.hbs index d34bbb94..e08b0ac3 100644 --- a/templates/parts/turf-list.hbs +++ b/templates/parts/turf-list.hbs @@ -38,10 +38,10 @@ {{/if}} {{/if}} {{#unless ../can_edit}} - {{#noteq id "8"}} + {{#if (test id "!=" "8")}} - {{/noteq}} + {{/if}} {{/unless}} {{#if (eq id "5")}} diff --git a/ts/@types/blades-general-types.d.ts b/ts/@types/blades-general-types.d.ts index 287c6970..7d62d050 100644 --- a/ts/@types/blades-general-types.d.ts +++ b/ts/@types/blades-general-types.d.ts @@ -42,6 +42,12 @@ declare global { // Represents an allowed gender key type Gender = "M"|"F"|"U"|"X"; + // Represents an allowed direction + type Direction = "top"|"bottom"|"left"|"right"; + + // Represents an allowed string case + type StringCase = "upper" | "lower" | "sentence" | "title"; + // Represents HTML code as a string type HTMLCode = string; diff --git a/ts/@types/blades-roll.d.ts b/ts/@types/blades-roll.d.ts index ab0524f0..d3f0a611 100644 --- a/ts/@types/blades-roll.d.ts +++ b/ts/@types/blades-roll.d.ts @@ -1,4 +1,4 @@ -import {BladesActorType, BladesItemType, RollType, RollSubType, ConsequenceType, RollModStatus, RollModCategory, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor} from "../core/constants.js"; +import {BladesActorType, BladesItemType, RollType, RollSubType, ConsequenceType, RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor} from "../core/constants.js"; import BladesActor from "../BladesActor.js"; import BladesItem from "../BladesItem.js"; import {BladesRollMod} from "../BladesRollCollab.js"; @@ -16,8 +16,9 @@ declare global { rollTrait?: RollTrait } - export type ConsequenceData = { + export interface ConsequenceData extends Partial { type: ConsequenceType, + attribute: AttributeTrait, label?: string } @@ -58,7 +59,7 @@ declare global { GMBoosts: Partial>, GMOppBoosts: Partial>, docSelections: { - [RollModCategory.roll]: { + [RollModSection.roll]: { Assist: string|false, Group_1: string|false, Group_2: string|false, @@ -67,10 +68,10 @@ declare global { Group_5: string|false, Group_6: string|false, }, - [RollModCategory.position]: { + [RollModSection.position]: { Setup: string|false }, - [RollModCategory.effect]: { + [RollModSection.effect]: { Setup: string|false } } @@ -110,9 +111,9 @@ declare global { canTradePosition: boolean, canTradeEffect: boolean, - posRollMods: Record, - negRollMods: Record, - hasInactiveConditionals: Record, + posRollMods: Record, + negRollMods: Record, + hasInactiveConditionals: Record, rollFactors: Record<"source"|"opposition", Partial>>, @@ -123,6 +124,7 @@ declare global { export type PartialSheetData = Partial & FlagData; + export type AnyRollType = RollType|RollSubType|DowntimeAction; export type RollTrait = ActionTrait|AttributeTrait|Factor|number; export interface FactorData extends NamedValueMax { @@ -150,11 +152,13 @@ declare global { posNeg: "positive"|"negative", isOppositional?: boolean, modType: BladesItemType|"general"|"harm"|"teamwork", - conditionalRollTypes?: Array, - autoRollTypes?: Array, + conditionalRollTypes?: AnyRollType[], + autoRollTypes?: AnyRollType[], + participantRollTypes?: AnyRollType[], conditionalRollTraits?: RollTrait[], autoRollTraits?: RollTrait[], - category: RollModCategory + participantRollTraits?: RollTrait[], + category: RollModSection } export type PrimaryDoc = diff --git a/ts/BladesRollCollab.ts b/ts/BladesRollCollab.ts index f15cafd9..38e5b456 100644 --- a/ts/BladesRollCollab.ts +++ b/ts/BladesRollCollab.ts @@ -1,6 +1,6 @@ // #region IMPORTS ~ import U from "./core/utilities.js"; -import C, {BladesActorType, BladesItemType, RollType, RollSubType, RollModStatus, RollModCategory, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor, RollResult, ConsequenceType} from "./core/constants.js"; +import C, {BladesActorType, BladesItemType, RollType, RollSubType, RollModStatus, RollModSection, ActionTrait, DowntimeAction, AttributeTrait, Position, Effect, Factor, RollResult, ConsequenceType} from "./core/constants.js"; import BladesActor from "./BladesActor.js"; import BladesPC from "./documents/actors/BladesPC.js"; import BladesItem from "./BladesItem.js"; @@ -38,8 +38,8 @@ export class BladesRollMod { const nameVal = (typeof nameString === "string" && nameString.replace(/^.*:/, "")); if (!nameVal) { throw new Error(`RollMod Missing Name: '${modString}'`) } const catString = U.pullElement(pStrings, (v) => typeof v === "string" && /^cat/i.test(v)); - const catVal = (typeof catString === "string" && catString.replace(/^.*:/, "")) as RollModCategory|false; - if (!catVal || !(catVal in RollModCategory)) { throw new Error(`RollMod Missing Category: '${modString}'`) } + const catVal = (typeof catString === "string" && catString.replace(/^.*:/, "")) as RollModSection|false; + if (!catVal || !(catVal in RollModSection)) { throw new Error(`RollMod Missing Category: '${modString}'`) } const posNegString = (U.pullElement(pStrings, (v) => typeof v === "string" && /^p/i.test(v)) || "posNeg:positive"); const posNegVal = posNegString.replace(/^.*:/, "") as "positive"|"negative"; @@ -67,8 +67,10 @@ export class BladesRollMod { if (/^ty/i.test(keyString)) { key = "modType" } else if (/^c.{0,10}r?.{0,3}ty/i.test(keyString)) {key = "conditionalRollTypes"} else if (/^a.{0,3}r?.{0,3}y/i.test(keyString)) {key = "autoRollTypes"} else + if (/^p.{0,10}r?.{0,3}y/i.test(keyString)) {key = "participantRollTypes"} else if (/^c.{0,10}r?.{0,3}tr/i.test(keyString)) {key = "conditionalRollTraits"} else - if (/^a.{0,3}r?.{0,3}tr/i.test(keyString)) {key = "autoRollTraits"} else { + if (/^a.{0,3}r?.{0,3}tr/i.test(keyString)) {key = "autoRollTraits"} else + if (/^p.{0,10}r?.{0,3}tr/i.test(keyString)) {key = "participantRollTypes"} else { throw new Error(`Bad Roll Mod Key: ${keyString}`); } @@ -140,8 +142,10 @@ export class BladesRollMod { return [ ...this.conditionalRollTraits, ...this.autoRollTraits, + ...this.participantRollTraits, ...this.conditionalRollTypes, - ...this.autoRollTypes + ...this.autoRollTypes, + ...this.participantRollTypes ].length > 0; } @@ -173,28 +177,54 @@ export class BladesRollMod { return stressCost; } + /** + * Sets the conditional status of the roll instance. + * @returns {boolean} - Returns false if the status is ForcedOn or ToggledOff, true if the status is Hidden. + */ setConditionalStatus(): boolean { + // If the roll instance is not conditional, return false if (!this.isConditional) { return false } - // If ANY auto-Traits/Types apply, ForceOn - if (this.autoRollTypes.includes(this.rollInstance.rollType) - || this.autoRollTraits.includes(this.rollInstance.rollTrait)) { + // If any auto-Traits/Types apply, set status to ForcedOn and return false + const autoTypesOrTraitsApply = this.autoRollTypes.includes(this.rollInstance.rollType) + || this.autoRollTraits.includes(this.rollInstance.rollTrait); + if (autoTypesOrTraitsApply) { this.heldStatus = RollModStatus.ForcedOn; return false; } - // If BOTH conditionalTypes and conditionalTraits apply, held = ToggledOff - if ((this.conditionalRollTypes.length === 0 || this.conditionalRollTypes.includes(this.rollInstance.rollType)) - && (this.conditionalRollTraits.length === 0 || this.conditionalRollTraits.includes(this.rollInstance.rollTrait))) { + // If any conditionalTypes apply and any conditionalTraits apply, set status to ToggledOff and return false + const conditionalTypesOrTraitsApply = this.checkTypesOrTraits(this.conditionalRollTypes, this.conditionalRollTraits); + if (conditionalTypesOrTraitsApply) { this.heldStatus = RollModStatus.ToggledOff; return false; } - // OTHERWISE, return HIDDEN + // If this is a participant roll and any participantTypes apply and any participantTraits apply, set status to ToggledOff and return false + const participantTypesOrTraitsApply = this.rollInstance.isParticipantRoll + && this.checkTypesOrTraits(this.participantRollTypes, this.participantRollTraits); + if (participantTypesOrTraitsApply) { + this.heldStatus = RollModStatus.ToggledOff; + return false; + } + + // If none of the above conditions apply, set status to Hidden and return true this.heldStatus = RollModStatus.Hidden; return true; } + /** + * Checks if any types or traits apply to the roll instance. + * @param {AnyRollType[]} types - The types to check. + * @param {RollTrait[]} traits - The traits to check. + * @returns {boolean} - Returns true if any types or traits apply, false otherwise. + */ + private checkTypesOrTraits(types: BladesRollCollab.AnyRollType[], traits: BladesRollCollab.RollTrait[]): boolean { + const typesApply = (!this.rollInstance.isParticipantRoll && types.length === 0) || types.includes(this.rollInstance.rollType); + const traitsApply = (!this.rollInstance.isParticipantRoll && traits.length === 0) || traits.includes(this.rollInstance.rollTrait); + return typesApply && traitsApply; + } + /** * Helper function to process each key * @param {string} key - The key to process @@ -322,7 +352,7 @@ export class BladesRollMod { HarmLevel: () => { if (!this.rollInstance.rollConsequence) { return } const consequenceType = this.rollInstance.rollConsequence.type; - if (!consequenceType || !consequenceType.startsWith("Harm")) { return } + if (!consequenceType?.startsWith("Harm")) { return } const curLevel = [ConsequenceType.Harm1, ConsequenceType.Harm2, ConsequenceType.Harm3, ConsequenceType.Harm4] .findIndex((cType) => cType === consequenceType) + 1; if (curLevel > 1) { @@ -369,7 +399,7 @@ export class BladesRollMod { get sideString(): string | undefined { if (this._sideString) { return this._sideString } switch (this.category) { - case RollModCategory.roll: { + case RollModSection.roll: { if (this.name === "Assist") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.roll.Assist") as MaybeStringOrFalse; if (!docID) { return undefined } @@ -377,7 +407,7 @@ export class BladesRollMod { } return undefined; } - case RollModCategory.position: { + case RollModSection.position: { if (this.name === "Setup") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.position.Setup") as MaybeStringOrFalse; if (!docID) { return undefined } @@ -385,7 +415,7 @@ export class BladesRollMod { } return undefined; } - case RollModCategory.effect: { + case RollModSection.effect: { if (this.name === "Setup") { const docID = this.rollInstance.document.getFlag("eunos-blades", "rollCollab.docSelections.effect.Setup") as MaybeStringOrFalse; if (!docID) { return undefined } @@ -436,7 +466,7 @@ export class BladesRollMod { if (this.posNeg === "negative") { label = `${this.name} (To Act)`; } else { - const effect = this.category === RollModCategory.roll ? "+1d" : "+1 effect"; + const effect = this.category === RollModSection.roll ? "+1d" : "+1 effect"; label = `${this.name} (${effect})`; } } @@ -461,11 +491,13 @@ export class BladesRollMod { posNeg: "positive" | "negative"; isOppositional: boolean; modType: BladesRollCollab.ModType; - conditionalRollTypes: Array; - autoRollTypes: Array; + conditionalRollTypes: BladesRollCollab.AnyRollType[]; + autoRollTypes: BladesRollCollab.AnyRollType[]; + participantRollTypes: BladesRollCollab.AnyRollType[]; conditionalRollTraits: BladesRollCollab.RollTrait[]; autoRollTraits: BladesRollCollab.RollTrait[]; - category: RollModCategory; + participantRollTraits: BladesRollCollab.RollTrait[]; + category: RollModSection; rollInstance: BladesRollCollab; @@ -484,8 +516,10 @@ export class BladesRollMod { this.modType = modData.modType; this.conditionalRollTypes = modData.conditionalRollTypes ?? []; this.autoRollTypes = modData.autoRollTypes ?? []; + this.participantRollTypes = modData.participantRollTypes ?? []; this.conditionalRollTraits = modData.conditionalRollTraits ?? []; this.autoRollTraits = modData.autoRollTraits ?? []; + this.participantRollTraits = modData.participantRollTraits ?? []; this.category = modData.category; } } @@ -703,28 +737,6 @@ class BladesRollCollab extends DocumentSheet { } static Initialize() { - // Hooks.on("preUpdateUser", async (user: User, updateData: Record) => { - // const flatData = flattenObject(updateData); - // const docSelectKeys = Object.keys(flatData) - // .filter((key) => /docSelections/.test(key) && flatData[key] !== false); - // if (docSelectKeys.length > 0) { - // docSelectKeys.forEach((key) => { - // const [_, category, name] = key.match(/docSelections\.(.*?)\.(.*)/) as [never, RollModCategory, string]; - // const rollMods = ((user.getFlag("eunos-blades", "rollCollab.rollMods") ?? []) as BladesRollCollab.RollModData[]); - // const rollMod = U.pullElement(rollMods, (mod: BladesRollCollab.RollModData | undefined) => mod?.name === name && mod?.category === category); - // if (!rollMod || !rollMod.tooltip) { return } - // const curSidestring = rollMod.sideString; - // const newSidestring = (BladesActor.Get(flatData[key]) ?? BladesItem.Get(flatData[key]) ?? {name: ""}).name; - // if (newSidestring === null) { return } - // // if (curSidestring === newSidestring) { return } - // rollMod.tooltip = rollMod.tooltip.replace(new RegExp(curSidestring || "%DOC_NAME%", "g"), newSidestring); - // rollMod.sideString = newSidestring; - // rollMods.push(rollMod); - // user.setFlag("eunos-blades", "rollCollab.rollMods", rollMods); - // }); - // } - - // }); return loadTemplates([ "systems/eunos-blades/templates/roll/roll-collab.hbs", "systems/eunos-blades/templates/roll/roll-collab-gm.hbs", @@ -753,7 +765,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Push-positive-roll", name: "PUSH", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.ToggledOff, posNeg: "positive", modType: "general", @@ -764,7 +776,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Bargain-positive-roll", name: "Bargain", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "general", @@ -775,7 +787,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Assist-positive-roll", name: "Assist", - category: RollModCategory.roll, + category: RollModSection.roll, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -785,7 +797,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Setup-positive-position", name: "Setup", - category: RollModCategory.position, + category: RollModSection.position, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -795,7 +807,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Push-positive-effect", name: "PUSH", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.ToggledOff, posNeg: "positive", modType: "general", @@ -806,7 +818,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Setup-positive-effect", name: "Setup", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "teamwork", @@ -816,7 +828,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Potency-positive-effect", name: "Potency", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "positive", modType: "general", @@ -826,7 +838,7 @@ class BladesRollCollab extends DocumentSheet { { id: "Potency-negative-effect", name: "Potency", - category: RollModCategory.effect, + category: RollModSection.effect, base_status: RollModStatus.Hidden, posNeg: "negative", modType: "general", @@ -859,7 +871,7 @@ class BladesRollCollab extends DocumentSheet { [Factor.magnitude]: 0 }, docSelections: { - [RollModCategory.roll]: { + [RollModSection.roll]: { Assist: false, Group_1: false, Group_2: false, @@ -868,10 +880,10 @@ class BladesRollCollab extends DocumentSheet { Group_5: false, Group_6: false }, - [RollModCategory.position]: { + [RollModSection.position]: { Setup: false }, - [RollModCategory.effect]: { + [RollModSection.effect]: { Setup: false } }, @@ -964,7 +976,6 @@ class BladesRollCollab extends DocumentSheet { static async RenderRollCollab({userID, rollID}: { userID: string, rollID: string }) { const user = game.users.get(userID); // as User & {flags: {["eunos-blades"]: {rollCollab: BladesRollCollab.FlagData}}}; if (!user) { return } - // BladesRollCollab.Current[rollID] = new BladesRollCollab(user, rollID); await BladesRollCollab.Current[rollID]._render(true); } @@ -974,6 +985,7 @@ class BladesRollCollab extends DocumentSheet { delete BladesRollCollab.Current[rollID]; } + static async NewRoll(config: BladesRollCollab.Config) { if (game.user.isGM && BladesActor.IsType(config.rollPrimary, BladesActorType.pc)) { const rSource: BladesPC = config.rollPrimary; @@ -1003,7 +1015,18 @@ class BladesRollCollab extends DocumentSheet { eLog.error("rollCollab", "[RenderRollCollab()] Invalid rollPrimary", {rollPrimaryData, config}); return; } - if (U.isInt(config.rollTrait)) { + + let rollPrimary: BladesRollCollab.PrimaryDoc|undefined; + if (BladesRollPrimary.IsDoc(rollPrimaryData)) { + rollPrimary = rollPrimaryData; + } else if (BladesRollPrimary.IsDoc(rollPrimaryData.rollPrimaryDoc)) { + rollPrimary = rollPrimaryData.rollPrimaryDoc; + } + + if (flagUpdateData.rollType === RollType.IndulgeVice && BladesActor.IsType(rollPrimary, BladesActorType.pc)) { + const minAttrVal = Math.min(...Object.values(rollPrimary.attributes)); + flagUpdateData.rollTrait = U.objFindKey(rollPrimary.attributes, (val: number) => val === minAttrVal) as AttributeTrait; + } else if (U.isInt(config.rollTrait)) { flagUpdateData.rollTrait = config.rollTrait; } else if (!config.rollTrait) { eLog.error("rollCollab", "[RenderRollCollab()] No RollTrait in Config", config); @@ -1018,14 +1041,6 @@ class BladesRollCollab extends DocumentSheet { flagUpdateData.rollTrait = U.lCase(config.rollTrait) as ActionTrait | Factor; break; } - case RollType.Downtime: { - if (!(U.lCase(config.rollTrait) in {...ActionTrait, ...Factor})) { - eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Downtime Roll: ${config.rollTrait}`, config); - return; - } - flagUpdateData.rollTrait = U.lCase(config.rollTrait) as ActionTrait | Factor; - break; - } case RollType.Fortune: { if (!(U.lCase(config.rollTrait) in {...ActionTrait, ...AttributeTrait, ...Factor})) { eLog.error("rollCollab", `[RenderRollCollab()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`, config); @@ -1217,7 +1232,7 @@ class BladesRollCollab extends DocumentSheet { return Object.values(Position)[U.clampNum( Object.values(Position) .indexOf(this.initialPosition) - + this.getModsDelta(RollModCategory.position) + + this.getModsDelta(RollModSection.position) + (this.posEffectTrade === "position" ? 1 : 0) + (this.posEffectTrade === "effect" ? -1 : 0), [0, 2] @@ -1227,26 +1242,26 @@ class BladesRollCollab extends DocumentSheet { return Object.values(Effect)[U.clampNum( Object.values(Effect) .indexOf(this.initialEffect) - + this.getModsDelta(RollModCategory.effect) + + this.getModsDelta(RollModSection.effect) + (this.posEffectTrade === "effect" ? 1 : 0) + (this.posEffectTrade === "position" ? -1 : 0), [0, 4] )]; } get finalResult(): number { - return this.getModsDelta(RollModCategory.result) + return this.getModsDelta(RollModSection.result) + (this.rData?.GMBoosts.Result ?? 0) + (this.tempGMBoosts.Result ?? 0); } get finalDicePool(): number { return Math.max(0, this.rollTraitData.value - + this.getModsDelta(RollModCategory.roll) + + this.getModsDelta(RollModSection.roll) + (this.rData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)); } get isRollingZero(): boolean { return Math.max(0, this.rollTraitData.value - + this.getModsDelta(RollModCategory.roll) + + this.getModsDelta(RollModSection.roll) + (this.rData.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)) <= 0; } @@ -1397,7 +1412,7 @@ class BladesRollCollab extends DocumentSheet { this.rollTraitValOverride = Math.max(...Object.values(rollPrimaryDoc.actions)); } } else { - const [targetName, targetCat, targetPosNeg] = thisTarget?.split(/,/) as [string, RollModCategory | undefined, "positive" | "negative" | undefined] | undefined ?? []; + const [targetName, targetCat, targetPosNeg] = thisTarget?.split(/,/) as [string, RollModSection | undefined, "positive" | "negative" | undefined] | undefined ?? []; if (!targetName) { throw new Error(`No targetName found in thisTarget: ${thisTarget}.`)} let targetMod = this.getRollModByName(targetName) ?? this.getRollModByName(targetName, targetCat ?? mod.category); @@ -1432,10 +1447,10 @@ class BladesRollCollab extends DocumentSheet { } // ... BY CATEGORY ... - [RollModCategory.roll, RollModCategory.effect].forEach((cat) => { + [RollModSection.roll, RollModSection.effect].forEach((cat) => { if (this.isPushed(cat)) { // ... if pushed by positive mod, Force Off any visible Bargain - if (cat === RollModCategory.roll && this.isPushed(cat, "positive")) { + if (cat === RollModSection.roll && this.isPushed(cat, "positive")) { const bargainMod = this.getRollModByID("Bargain-positive-roll"); if (bargainMod?.isVisible) { bargainMod.heldStatus = RollModStatus.ForcedOff; @@ -1475,6 +1490,11 @@ class BladesRollCollab extends DocumentSheet { return false; } + get isParticipantRoll() { + return (this.rollType === RollType.Fortune && !game.user.isGM) + || (this.rollSubType === RollSubType.GroupParticipant); + } + rollFactorPenaltiesNegated: Partial> = {}; negateFactorPenalty(factor: Factor) { this.rollFactorPenaltiesNegated[factor] = true; @@ -1482,9 +1502,9 @@ class BladesRollCollab extends DocumentSheet { tempGMBoosts: Partial> = {}; - isPushed(cat?: RollModCategory, posNeg?: "positive"|"negative"): boolean { return this.getActiveBasicPushMods(cat, posNeg).length > 0 } - hasOpenPush(cat?: RollModCategory, posNeg?: "positive"|"negative"): boolean { return this.isPushed(cat) && this.getOpenPushMods(cat, posNeg).length > 0 } - isForcePushed(cat?: RollModCategory, posNeg?: "positive"|"negative"): boolean { return this.isPushed(cat) && this.getForcedPushMods(cat, posNeg).length > 0 } + isPushed(cat?: RollModSection, posNeg?: "positive"|"negative"): boolean { return this.getActiveBasicPushMods(cat, posNeg).length > 0 } + hasOpenPush(cat?: RollModSection, posNeg?: "positive"|"negative"): boolean { return this.isPushed(cat) && this.getOpenPushMods(cat, posNeg).length > 0 } + isForcePushed(cat?: RollModSection, posNeg?: "positive"|"negative"): boolean { return this.isPushed(cat) && this.getForcedPushMods(cat, posNeg).length > 0 } get rollCosts(): number { @@ -1492,7 +1512,7 @@ class BladesRollCollab extends DocumentSheet { const harmPush = this.getRollModByID("Push-negative-roll"); const rollPush = this.getRollModByID("Push-positive-roll"); const effectPush = this.getRollModByID("Push-positive-effect"); - const negatePushCostMods = this.getActiveRollMods(RollModCategory.after, "positive") + const negatePushCostMods = this.getActiveRollMods(RollModSection.after, "positive") .filter((mod) => mod.effectKeys.includes("Negate-PushCost")); return ((harmPush?.isActive && harmPush?.stressCost) || 0) + ((rollPush?.isActive && rollPush?.stressCost) || 0) @@ -1506,7 +1526,7 @@ class BladesRollCollab extends DocumentSheet { .flat(); } - getRollModByName(name: string, cat?: RollModCategory, posNeg?: "positive" | "negative"): BladesRollMod | undefined { + getRollModByName(name: string, cat?: RollModSection, posNeg?: "positive" | "negative"): BladesRollMod | undefined { const modMatches = this.rollMods.filter((rollMod) => { if (U.lCase(rollMod.name) !== U.lCase(name)) { return false; @@ -1526,51 +1546,51 @@ class BladesRollCollab extends DocumentSheet { return modMatches[0]; } getRollModByID(id: string) { return this.rollMods.find((rollMod) => rollMod.id === id) } - getRollMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getRollMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.rollMods.filter((rollMod) => (!cat || rollMod.category === cat) && (!posNeg || rollMod.posNeg === posNeg)); } - getVisibleRollMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getVisibleRollMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isVisible); } - getActiveRollMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getActiveRollMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isActive); } - getVisibleInactiveRollMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getVisibleInactiveRollMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getVisibleRollMods(cat, posNeg).filter((rollMod) => !rollMod.isActive); } - getPushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getPushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getRollMods(cat, posNeg).filter((rollMod) => rollMod.isPush); } - getVisiblePushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getVisiblePushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getPushMods(cat, posNeg).filter((rollMod) => rollMod.isVisible); } - getActivePushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getActivePushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getVisiblePushMods(cat, posNeg).filter((rollMod) => rollMod.isActive); } - getActiveBasicPushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getActiveBasicPushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getActivePushMods(cat, posNeg).filter((rollMod) => rollMod.isBasicPush); } - getInactivePushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getInactivePushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getVisiblePushMods(cat, posNeg).filter((rollMod) => !rollMod.isActive); } - getInactiveBasicPushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getInactiveBasicPushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getInactivePushMods(cat, posNeg).filter((rollMod) => rollMod.isBasicPush); } - getForcedPushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getForcedPushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getActivePushMods(cat, posNeg) .filter((rollMod) => rollMod.isBasicPush && rollMod.status === RollModStatus.ForcedOn); } - getOpenPushMods(cat?: RollModCategory, posNeg?: "positive" | "negative") { + getOpenPushMods(cat?: RollModSection, posNeg?: "positive" | "negative") { return this.getActivePushMods(cat, posNeg) .filter((rollMod) => rollMod.isBasicPush && rollMod.status === RollModStatus.ToggledOn); } - getModsDelta = (cat: RollModCategory) => { + getModsDelta = (cat: RollModSection) => { return U.sum([ ...this.getActiveRollMods(cat, "positive").map((mod) => mod.value), ...this.getActiveRollMods(cat, "negative").map((mod) => -mod.value) @@ -1740,10 +1760,10 @@ class BladesRollCollab extends DocumentSheet { rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated, - posRollMods: Object.fromEntries(Object.values(RollModCategory) - .map((cat) => [cat, this.getRollMods(cat, "positive")])) as Record, - negRollMods: Object.fromEntries(Object.values(RollModCategory) - .map((cat) => [cat, this.getRollMods(cat, "negative")])) as Record, + posRollMods: Object.fromEntries(Object.values(RollModSection) + .map((cat) => [cat, this.getRollMods(cat, "positive")])) as Record, + negRollMods: Object.fromEntries(Object.values(RollModSection) + .map((cat) => [cat, this.getRollMods(cat, "negative")])) as Record, hasInactiveConditionals: this.calculateHasInactiveConditionalsData(), rollFactors, @@ -1780,8 +1800,8 @@ class BladesRollCollab extends DocumentSheet { return { rollEffects: Object.values(Effect), rollEffectFinal: finalEffect, - isAffectingAfter: this.getVisibleRollMods(RollModCategory.after).length > 0 - || (isGM && this.getRollMods(RollModCategory.after).length > 0) + isAffectingAfter: this.getVisibleRollMods(RollModSection.after).length > 0 + || (isGM && this.getRollMods(RollModSection.after).length > 0) }; } @@ -1789,8 +1809,8 @@ class BladesRollCollab extends DocumentSheet { return { rollResultFinal: finalResult, isAffectingResult: finalResult > 0 - || this.getVisibleRollMods(RollModCategory.result).length > 0 - || (isGM && this.getRollMods(RollModCategory.result).length > 0) + || this.getVisibleRollMods(RollModSection.result).length > 0 + || (isGM && this.getRollMods(RollModSection.result).length > 0) }; } @@ -1883,11 +1903,11 @@ class BladesRollCollab extends DocumentSheet { /** * Calculate data on whether there are any inactive conditionals. -* @returns {Record} - Data on inactive conditionals. +* @returns {Record} - Data on inactive conditionals. */ - private calculateHasInactiveConditionalsData(): Record { - const hasInactive = {} as Record; - for (const category of Object.values(RollModCategory)) { + private calculateHasInactiveConditionalsData(): Record { + const hasInactive = {} as Record; + for (const category of Object.values(RollModSection)) { hasInactive[category] = this.getRollMods(category).filter((mod) => mod.isInInactiveBlock).length > 0; } return hasInactive; @@ -1981,10 +2001,10 @@ class BladesRollCollab extends DocumentSheet { rollEffectFinal: finalEffect, rollResultFinal: finalResult, isAffectingResult: finalResult > 0 - || this.getVisibleRollMods(RollModCategory.result).length > 0 - || (isGM && this.getRollMods(RollModCategory.result).length > 0), - isAffectingAfter: this.getVisibleRollMods(RollModCategory.after).length > 0 - || (isGM && this.getRollMods(RollModCategory.after).length > 0), + || this.getVisibleRollMods(RollModSection.result).length > 0 + || (isGM && this.getRollMods(RollModSection.result).length > 0), + isAffectingAfter: this.getVisibleRollMods(RollModSection.after).length > 0 + || (isGM && this.getRollMods(RollModSection.after).length > 0), rollFactorPenaltiesNegated: this.rollFactorPenaltiesNegated, @@ -2016,24 +2036,24 @@ class BladesRollCollab extends DocumentSheet { && finalEffect !== Effect.zero ), - posRollMods: Object.fromEntries(Object.values(RollModCategory) - .map((cat) => [cat, this.getRollMods(cat, "positive")])) as Record, - negRollMods: Object.fromEntries(Object.values(RollModCategory) - .map((cat) => [cat, this.getRollMods(cat, "negative")])) as Record, + posRollMods: Object.fromEntries(Object.values(RollModSection) + .map((cat) => [cat, this.getRollMods(cat, "positive")])) as Record, + negRollMods: Object.fromEntries(Object.values(RollModSection) + .map((cat) => [cat, this.getRollMods(cat, "negative")])) as Record, hasInactiveConditionals: { - [RollModCategory.roll]: this.getRollMods(RollModCategory.roll) + [RollModSection.roll]: this.getRollMods(RollModSection.roll) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.position]: this.getRollMods(RollModCategory.position) + [RollModSection.position]: this.getRollMods(RollModSection.position) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.effect]: this.getRollMods(RollModCategory.effect) + [RollModSection.effect]: this.getRollMods(RollModSection.effect) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.result]: this.getRollMods(RollModCategory.result) + [RollModSection.result]: this.getRollMods(RollModSection.result) .filter((mod) => mod.isInInactiveBlock) .length > 0, - [RollModCategory.after]: this.getRollMods(RollModCategory.after) + [RollModSection.after]: this.getRollMods(RollModSection.after) .filter((mod) => mod.isInInactiveBlock) .length > 0 }, @@ -2234,12 +2254,11 @@ class BladesRollCollab extends DocumentSheet { break; } - case RollType.Downtime: { - + case RollType.Fortune: { break; } - case RollType.Fortune: { + case RollType.IndulgeVice: { break; } diff --git a/ts/blades.ts b/ts/blades.ts index fdab71ff..6f2b2ff4 100644 --- a/ts/blades.ts +++ b/ts/blades.ts @@ -41,8 +41,8 @@ let socket: Socket; //~ SocketLib interface updateContacts, updateOps, updateFactions, - applyDescriptions: updateDescriptions, - applyRollEffects: updateRollMods, + updateDescriptions, + updateRollMods, BladesActor, BladesPCSheet, BladesCrewSheet, @@ -105,12 +105,6 @@ Hooks.once("init", async () => { Hooks.once("ready", () => { initCanvasStyles(); initTinyMCEStyles(); - // BladesRollCollab.NewRoll({ - // rollPrimary: U.randElem(BladesActor.GetTypeWithTags(BladesActorType.pc)), - // rollType: RollType.Action, - // rollTrait: U.randElem(Object.values(Action)) - // }); - // DebugPC(); }); // #endregion ▄▄▄▄▄ SYSTEM INITIALIZATION ▄▄▄▄▄ diff --git a/ts/core/constants.ts b/ts/core/constants.ts index 93712723..401421ff 100644 --- a/ts/core/constants.ts +++ b/ts/core/constants.ts @@ -143,15 +143,17 @@ export enum DowntimeAction { export enum RollType { Action = "Action", - Downtime = "Downtime", Resistance = "Resistance", - Fortune = "Fortune" + Fortune = "Fortune", + IndulgeVice = "Vice" } export enum RollSubType { Incarceration = "Incarceration", Engagement = "Engagement", - GatherInfo = "GatherInfo" + GatherInfo = "GatherInfo", + GroupLead = "GroupLead", + GroupParticipant = "GroupParticipant" } export enum ConsequenceType { @@ -174,7 +176,7 @@ export enum RollModStatus { Dominant = "Dominant" } -export enum RollModCategory { +export enum RollModSection { roll = "roll", position = "position", effect = "effect", diff --git a/ts/core/helpers.ts b/ts/core/helpers.ts index feb9a403..77fac228 100644 --- a/ts/core/helpers.ts +++ b/ts/core/helpers.ts @@ -1,11 +1,7 @@ // #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ import U from "./utilities.js"; -import type {ItemData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/module.mjs"; -import type BladesActor from "../BladesActor.js"; -import type BladesItem from "../BladesItem.js"; import {HbsSvgData, SVGDATA} from "./constants.js"; -import type {ItemDataConstructorData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/itemData.js"; // #endregion ▮▮▮▮[IMPORTS]▮▮▮▮ // #region ░░░░░░░[Templates]░░░░ Preload Partials, Components & Overlay Templates ░░░░░░░ ~ @@ -49,15 +45,15 @@ export async function preloadHandlebarsTemplates() { // #region ████████ Handlebars: Handlebar Helpers Definitions ████████ ~ const handlebarHelpers: Record = { - "randString": function(param1 = 10) { + randString(param1 = 10) { return U.randString(param1); }, - "test": function(param1: unknown, operator: string, param2: unknown) { + test(param1: unknown, operator: string, param2: unknown) { const stringMap = { "true": true, "false": false, "null": null, - "undefined": undefined + undefined }; if (["!", "not", "=??"].includes(String(param1))) { [operator, param1] = [String(param1), operator]; @@ -70,12 +66,12 @@ const handlebarHelpers: Record = { } switch (operator) { case "!": case "not": { return !param1 } - case "=??": { return [undefined, null].includes(param1 as any) } + case "=??": { return ([undefined, null] as unknown[]).includes(param1) } case "&&": { return param1 && param2 } case "||": { return param1 || param2 } - case "==": { return param1 == param2 } // eslint-disable-line eqeqeq + case "==": { return U.areFuzzyEqual(param1, param2) } case "===": { return param1 === param2 } - case "!=": { return param1 != param2 } // eslint-disable-line eqeqeq + case "!=": case "!==": { return param1 !== param2 } case ">": { return typeof param1 === "number" && typeof param2 === "number" && param1 > param2 } case "<": { return typeof param1 === "number" && typeof param2 === "number" && param1 < param2 } @@ -98,7 +94,7 @@ const handlebarHelpers: Record = { default: { return false } } }, - "calc": function(...params: unknown[]) { + calc(...params: unknown[]) { const calcs: Record) => number|string> = { "+": (p1, p2) => U.pInt(p1) + U.pInt(p2), "-": (p1, p2) => U.pInt(p1) - U.pInt(p2), @@ -115,12 +111,11 @@ const handlebarHelpers: Record = { : params; return calcs[operator as KeyOf](param1 as string|number, param2 as string|number); }, - "isIn": function() { - const [testStr, ...contents] = arguments; + isIn(...args: unknown[]) { + const [testStr, ...contents] = args; return contents.includes(testStr); }, - "case": function(mode: "upper" | "lower" | "sentence" | "title", str: string) { - // return U[`${mode.charAt(0)}Case`](str); + case(mode: StringCase, str: string) { switch (mode) { case "upper": return U.uCase(str); case "lower": return U.lCase(str); @@ -129,7 +124,7 @@ const handlebarHelpers: Record = { default: return str; } }, - "count": function(param: unknown): number { + count(param: unknown): number { if (Array.isArray(param) || U.isList(param)) { return Object.values(param).filter((val: unknown) => val !== null && val !== undefined).length; } else if (typeof param === "string") { @@ -138,7 +133,7 @@ const handlebarHelpers: Record = { return param ? 1 : 0; }, // For loop: {{#for [from = 0, to, stepSize = 1]}}{{/for}} - "forloop": (...args) => { + forloop: (...args) => { const options = args.pop(); let [from, to, stepSize] = args; from = U.pInt(from); @@ -146,19 +141,15 @@ const handlebarHelpers: Record = { stepSize = U.pInt(stepSize) || 1; if (from > to) { return "" } let html = ""; - for (let i = parseInt(from || 0, 10); i <= parseInt(to || 0, 10); i++) { + for (let i = parseInt(from || 0, 10); i <= parseInt(to || 0, 10); i += stepSize) { html += options.fn(i); } return html; }, - "signNum": function(num: number) { + signNum(num: number) { return U.signNum(num); }, - "areEmpty": function(...args) { - args.pop(); - return !Object.values(args).flat().join(""); - }, - "compileSvg": function(...args): string { + compileSvg(...args): string { const [svgDotKey, svgPaths]: [string, string] = args as [string, string]; const svgData = getProperty(SVGDATA, svgDotKey) as HbsSvgData|undefined; if (!svgData) { return "" } @@ -171,7 +162,7 @@ const handlebarHelpers: Record = { "" ].join("\n"); }, - "eLog": function(...args) { + eLog(...args) { args.pop(); let dbLevel = 5; if ([0,1,2,3,4,5].includes(args[0])) { @@ -180,9 +171,9 @@ const handlebarHelpers: Record = { eLog.hbsLog(...args, dbLevel); }, // Does the name of this turf block represent a standard 'Turf' claim? - "isTurfBlock": (name: string): boolean => U.fuzzyMatch(name, "Turf"), + isTurfBlock: (name: string): boolean => U.fuzzyMatch(name, "Turf"), // Which other connection does this connector overlap with? - "getConnectorPartner": (index: number|string, direction: "right"|"left"|"top"|"bottom"): string|null => { + getConnectorPartner: (index: number|string, direction: Direction): string|null => { index = parseInt(`${index}`, 10); const partners: Record> = { 1: {right: 2, bottom: 6}, @@ -202,12 +193,12 @@ const handlebarHelpers: Record = { 15: {top: 10, left: 14} }; const partnerDir = {left: "right", right: "left", top: "bottom", bottom: "top"}[direction]; - const partnerNum = partners[index as keyof typeof partners][direction] ?? 0; + const partnerNum = partners[index ][direction] ?? 0; if (partnerNum) { return `${partnerNum}-${partnerDir}` } return null; }, // Is the value Turf side. - "isTurfOnEdge": (index: number|string, direction: string): boolean => { + isTurfOnEdge: (index: number|string, direction: string): boolean => { index = parseInt(`${index}`, 10); const edges: Record = { 1: ["top", "left"], @@ -227,124 +218,49 @@ const handlebarHelpers: Record = { 15: ["right", "bottom"] }; if (!(index in edges)) { return true } - return edges[index as keyof typeof edges].includes(direction); + return edges[index ].includes(direction); }, // Multiboxes - "multiboxes": function(selected, options) { + multiboxes(selected, options) { let html = options.fn(this); selected = [selected].flat(1); - selected.forEach((selected_value: boolean|string) => { - if (selected_value !== false) { - const escapedValue = RegExp.escape(Handlebars.escapeExpression(String(selected_value))); - const rgx = new RegExp(' value=\"' + escapedValue + '\"'); + selected.forEach((selectedVal: boolean|string) => { + if (selectedVal !== false) { + const escapedValue = RegExp.escape(Handlebars.escapeExpression(String(selectedVal))); + const rgx = new RegExp(` value="${escapedValue}"`); html = html.replace(rgx, "$& checked=\"checked\""); } }); return html; }, - // NotEquals handlebar. - "noteq": (a, b, options) => (a !== b ? options.fn(this) : ""), - // ReputationTurf handlebar. - "repturf": (turfs_amount, options) => { + repturf: (turfsAmount, options) => { let html = options.fn(this), - turfs_amount_int = parseInt(turfs_amount, 10); + turfsAmountInt = parseInt(turfsAmount, 10); // Can't be more than 6. - if (turfs_amount_int > 6) { - turfs_amount_int = 6; + if (turfsAmountInt > 6) { + turfsAmountInt = 6; } - for (let i = 13 - turfs_amount_int; i <= 12; i++) { - const rgx = new RegExp(' value=\"' + i + '\"'); + for (let i = 13 - turfsAmountInt; i <= 12; i++) { + const rgx = new RegExp(` value="${i}"`); html = html.replace(rgx, "$& disabled=\"disabled\""); } return html; }, - "crew_vault_coins": (max_coins, options) => { - let html = options.fn(this); - for (let i = 1; i <= max_coins; i++) { - html += ""; - } - return html; - }, - "crew_experience": (actor, options) => { - let html = options.fn(this); - for (let i = 1; i <= 10; i++) { - html += ``; - } - return html; - }, - // Enrich the HTML replace /n with
- "html": (options) => { - const text = options.hash.text.replace(/\n/g, "
"); - return new Handlebars.SafeString(text); - }, - // "N Times" loop for handlebars. - // Block is executed N times starting from n=1. - // - // Usage: - // {{#times_from_1 10}} - // {{this}} - // {{/times_from_1}} - "times_from_1": (n, block) => { - n = parseInt(n, 10); - let accum = ""; - for (let i = 1; i <= n; ++i) { - accum += block.fn(i); - } - return accum; - }, - // "N Times" loop for handlebars. - // Block is executed N times starting from n=0. - // - // Usage: - // {{#times_from_0 10}} - // {{this}} - // {{/times_from_0}} - "times_from_0": (n, block) => { - n = parseInt(n, 10); - let accum = ""; - for (let i = 0; i <= n; ++i) { - accum += block.fn(i); - } - return accum; - }, // Concat helper // Usage: (concat 'first 'second') - "concat": function() { + concat(...args: unknown[]) { let outStr = ""; - for(const arg in arguments){ - if(typeof arguments[arg]!=="object"){ - outStr += arguments[arg]; + for(const arg of args){ + if(typeof arg === "string"){ + outStr += arg; } } return outStr; - }, - /** - * Takes label from Selected option instead of just plain value. - */ - "selectOptionsWithLabel": (choices: any[], options) => { - const localize = options.hash.localize ?? false; - let selected = options.hash.selected ?? null; - const blank = options.hash.blank || null; - selected = selected instanceof Array ? selected.map(String) : [String(selected)]; - - // Create an option - const option = (key: string, object: Record) => { - if ( localize ) {object.label = game.i18n.localize(object.label)} - const isSelected = selected.includes(key); - html += ``; - }; - - // Create the options - let html = ""; - if ( blank ) {option("", blank)} - Object.entries(choices).forEach(e => option(...e)); - - return new Handlebars.SafeString(html); } }; diff --git a/ts/core/utilities.ts b/ts/core/utilities.ts index c590c23d..2a9921a7 100644 --- a/ts/core/utilities.ts +++ b/ts/core/utilities.ts @@ -196,53 +196,62 @@ function assertNonNullType(val: unknown, type: (new(...args: unknown[]) => T) throw new Error(`Value ${valStr} is not a ${type.name}!`); } } +/** + * Checks if two values are "fuzzy" equal, simulating the behavior of the "==" operator. + * This function does not use the "==" operator directly to comply with linting rules. + * + * @param {unknown} val1 - The first value to compare. + * @param {unknown} val2 - The second value to compare. + * @returns {boolean} True if the values are "fuzzy" equal, false otherwise. + */ +const areFuzzyEqual = (val1: unknown, val2: unknown): boolean => { + // If both values are null or undefined, they are considered equal + if (([null, undefined] as unknown[]).includes(val1) && ([null, undefined] as unknown[]).includes(val2)) { return true } + + // If only one of the values is null or undefined, they are not equal + if (([null, undefined] as unknown[]).includes(val1) || ([null, undefined] as unknown[]).includes(val2)) { return false } + + // If both values are numbers, they are considered equal if they are numerically equal + if (typeof val1 === "number" && typeof val2 === "number") { return val1 === val2 } + + // If both values are booleans, they are considered equal if they are both true or both false + if (typeof val1 === "boolean" && typeof val2 === "boolean") { return val1 === val2 } + + // If both values are strings, they are considered equal if they are identical + if (typeof val1 === "string" && typeof val2 === "string") { return val1 === val2 } + + // If one value is a number and the other is a string, they are considered equal if the string can be converted to the number + if (typeof val1 === "number" && typeof val2 === "string") { return val1 === Number(val2) } + if (typeof val1 === "string" && typeof val2 === "number") { return Number(val1) === val2 } + + // If one value is a boolean and the other is a non-null object, they are not equal + if (typeof val1 === "boolean" && typeof val2 === "object") { return false } + if (typeof val1 === "object" && typeof val2 === "boolean") { return false } + + // If one value is a boolean and the other is a string, they are considered equal if the boolean is true and the string is not empty, or if the boolean is false and the string is empty + if (typeof val1 === "boolean" && typeof val2 === "string") { return (val1 && val2 !== "") || (!val1 && val2 === "") } + if (typeof val1 === "string" && typeof val2 === "boolean") { return (val2 && val1 !== "") || (!val2 && val1 === "") } + + // If one value is a number or a string and the other is an object, they are not equal + if ((typeof val1 === "number" || typeof val1 === "string") && typeof val2 === "object") { return false } + if (typeof val1 === "object" && (typeof val2 === "number" || typeof val2 === "string")) { return false } + + // If both values are objects, they are considered equal if they are identical + if (typeof val1 === "object" && typeof val2 === "object") { return val1 === val2 } + + // If none of the above conditions are met, the values are not equal + return false; +}; + const areEqual = (...refs: unknown[]) => { do { const ref = refs.pop(); - if (refs.length && !checkEquality(ref, refs[0])) { + if (refs.length && !areFuzzyEqual(ref, refs[0])) { return false; } } while (refs.length); return true; }; - -const checkEquality = (ref1: unknown, ref2: unknown): boolean => { - if (typeof ref1 !== typeof ref2) {return false} - if ([ref1, ref2].includes(null)) {return ref1 === ref2} - if (typeof ref1 === "object") { - return checkObjectEquality(ref1, ref2); - } else { - return ref1 === ref2; - } -}; - -const checkObjectEquality = (obj1: unknown, obj2: unknown): boolean => { - if (isArray(obj1)) { - return checkArrayEquality(obj1, obj2 as unknown[]); - } else if (isList(obj1)) { - return checkListEquality(obj1, obj2 as Record); - } else { - return checkOtherObjectEquality(obj1, obj2); - } -}; - -const checkArrayEquality = (arr1: unknown[], arr2: unknown[]): boolean => { - if (!isArray(arr2) || arr1.length !== arr2.length) {return false} - return arr1.every((value, index) => checkEquality(value, arr2[index])); -}; - -const checkListEquality = (list1: Record, list2: Record): boolean => { - if (!isList(list2) || Object.keys(list1).length !== Object.keys(list2).length) {return false} - return checkEquality(Object.keys(list1), Object.keys(list2)) && checkEquality(Object.values(list1), Object.values(list2)); -}; - -const checkOtherObjectEquality = (obj1: unknown, obj2: unknown): boolean => { - try { - return JSON.stringify(obj1) === JSON.stringify(obj2); - } catch { - return false; - } -}; const pFloat = (ref: unknown, sigDigits?: posInt, isStrict = false): number => { if (typeof ref === "string") { ref = parseFloat(ref); @@ -1118,6 +1127,38 @@ function objNullify(obj: T): Record, null> | null[] | T { return obj; } + +/** + * This function freezes the properties of an object based on a provided schema or keys. + * If a property is missing, it throws an error. + * @param {Partial} data - The object whose properties are to be frozen. + * @param {...Array | [T]} keysOrSchema - The keys or schema to freeze the properties. + * @returns {T} - The object with frozen properties. + * @throws {Error} - Throws an error if a property is missing. + */ +function objFreezeProps(data: Partial, ...keysOrSchema: Array | [T]): T { + const firstArg = keysOrSchema[0]; + + // If the first argument is an object and not an array, treat it as a schema + if (firstArg instanceof Object && !Array.isArray(firstArg)) { + const schema = firstArg as T; + for (const key in schema) { + if (data[key as keyof T] === undefined) { + throw new Error(`Missing value for ${key}`); + } + } + } else { + // If the first argument is not an object or is an array, treat it as an array of keys + for (const key of keysOrSchema as Array) { + if (data[key] === undefined) { + throw new Error(`Missing value for ${String(key)}`); + } + } + } + + // Return the data as type T + return data as T; +} // #endregion ▄▄▄▄▄ OBJECTS ▄▄▄▄▄ // #region ████████ FUNCTIONS: Function Wrapping, Queuing, Manipulation ████████ ~ @@ -1334,7 +1375,7 @@ export default { isNumber, isSimpleObj, isList, isArray, isFunc, isInt, isFloat, isPosInt, isIterable, isHTMLCode, isRGBColor, isHexColor, isUndefined, isDefined, isEmpty, hasItems, isInstance, - areEqual, + areEqual, areFuzzyEqual, pFloat, pInt, radToDeg, degToRad, getKey, assertNonNullType, @@ -1380,6 +1421,7 @@ export default { remove, replace, partition, objClean, objSize, objMap, objFindKey, objFilter, objForEach, objCompact, objClone, objMerge, objDiff, objExpand, objFlatten, objNullify, + objFreezeProps, // ████████ FUNCTIONS: Function Wrapping, Queuing, Manipulation ████████ getDynamicFunc, withLog, diff --git a/ts/data-import/data-import.ts b/ts/data-import/data-import.ts index f63798eb..2dfaf954 100644 --- a/ts/data-import/data-import.ts +++ b/ts/data-import/data-import.ts @@ -3144,7 +3144,7 @@ export const updateOps = async () => { const playbookObj = game.items.getName(op.playbook); if (!playbookObj || playbookObj.type !== BladesItemType.crew_playbook) { errorReport.push(`Favored Op ${op.name} has invalid playbook ${op.playbook}`); - return; + return undefined; } const item = await BladesItem.create({ name: op.name, @@ -3155,8 +3155,9 @@ export const updateOps = async () => { } }) as BladesItemOfType; if (BladesItem.IsType(item, BladesItemType.preferred_op)) { - item.addTag(playbookObj.name as Playbook); + return item.addTag(playbookObj.name as Playbook); } + return undefined; })); console.log(errorReport); @@ -3168,7 +3169,7 @@ export const updateContacts = async () => { const playbookObj = game.items.getName(ct.playbook); if (!BladesItem.IsType(playbookObj, BladesItemType.crew_playbook)) { errorReport.push(`Contact ${ct.name} has invalid playbook ${ct.playbook}`); - return; + return undefined; } const actor: BladesNPC = await Actor.create({ name: ct.name, @@ -3178,14 +3179,14 @@ export const updateContacts = async () => { prompts: ct.hints?.join(" ") } as Partial }) as BladesNPC; - actor.addTag(playbookObj.name as Playbook); + return actor.addTag(playbookObj.name as Playbook); })); console.log(errorReport); }; const updateFactionData = async (factionData: FactionData) => { - const faction = await game.actors.getName(factionData.name) as BladesFaction|undefined; + const faction = game.actors.getName(factionData.name) as BladesFaction|undefined; const updateData: Record = {}; if (faction) { updateData["system.subtitle"] = factionData.subtitle ?? ""; @@ -3242,50 +3243,52 @@ const updateFactionData = async (factionData: FactionData) => { }; export const updateFactions = async () => { - Object.values(JSONDATA.FACTIONS).forEach(async (factionData) => { - updateFactionData(factionData); - }); + await Promise.all(Object.values(JSONDATA.FACTIONS).map(async (factionData) => updateFactionData(factionData))); console.log(problemLog); }; export const updateRollMods = async () => { - Object.entries(JSONDATA.ABILITIES.RollMods) - .forEach(async ([aName, eData]) => { - // Get ability doc - const abilityDoc = game.items.getName(aName); - if (!abilityDoc) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Not Found.`); - return; - } + await Promise.all([ + ...Object.entries(JSONDATA.ABILITIES.RollMods) + .map(async ([aName, eData]) => { + // Get ability doc + const abilityDoc = game.items.getName(aName); + if (!abilityDoc) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Not Found.`); + return undefined; + } - // Get active effects on abilityDoc - const abilityEffects = Array.from(abilityDoc.effects ?? []) as BladesActiveEffect[]; + // Get active effects on abilityDoc + const abilityEffects = Array.from(abilityDoc.effects ?? []) as BladesActiveEffect[]; - // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - // Confirm eData.isMember and eData.isCohort are consistent across all changes. - const testChange = eData[0]; - if ( - (testChange.isMember && eData.some((change) => !change.isMember)) + // Confirm eData.isMember and eData.isCohort are consistent across all changes. + const testChange = eData[0]; + if ( + (testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember)) - ) { eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isMember' entries.`); return } - if ( - (testChange.isCohort && eData.some((change) => !change.isCohort)) + ) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isMember' entries.`); + } + if ( + (testChange.isCohort && eData.some((change) => !change.isCohort)) || (!testChange.isCohort && eData.some((change) => change.isCohort)) - ) { eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isCohort' entries.`); return } - - // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; + ) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} has inconsistent 'isCohort' entries.`); } - // Initialize new effect data - const effectData: { + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3298,32 +3301,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` + }); + } - // Create new ActiveEffect - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; - } + // Create new ActiveEffect + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3336,32 +3339,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Scoundrel Ability)` + }); + } - // Create new ActiveEffect - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple Active Effects`); - return; - } + // Create new ActiveEffect + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else { + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Ability ${aName} Has Multiple Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3371,56 +3374,59 @@ export const updateRollMods = async () => { changes: eData }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } - // Create new ActiveEffect - await abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); - Object.entries(JSONDATA.CREW_ABILITIES.RollMods) - .forEach(async ([aName, eData]) => { - // Get crew ability doc - const crewAbilityDoc = game.items.getName(aName); - if (!crewAbilityDoc) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Not Found.`); - return; - } + // Create new ActiveEffect + return abilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } + }), + ...Object.entries(JSONDATA.CREW_ABILITIES.RollMods) + .map(async ([aName, eData]) => { + // Get crew ability doc + const crewAbilityDoc = game.items.getName(aName); + if (!crewAbilityDoc) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Not Found.`); + return undefined; + } - // Get active effects on crewAbilityDoc - const abilityEffects = Array.from(crewAbilityDoc.effects ?? []) as BladesActiveEffect[]; + // Get active effects on crewAbilityDoc + const abilityEffects = Array.from(crewAbilityDoc.effects ?? []) as BladesActiveEffect[]; - // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - // Confirm eData.isMember and eData.isCohort are consistent across all changes. - const testChange = eData[0]; - if ( - (testChange.isMember && eData.some((change) => !change.isMember)) + // Confirm eData.isMember and eData.isCohort are consistent across all changes. + const testChange = eData[0]; + if ( + (testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember)) - ) { eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isMember' entries.`); return } - if ( - (testChange.isCohort && eData.some((change) => !change.isCohort)) + ) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isMember' entries.`); + } + if ( + (testChange.isCohort && eData.some((change) => !change.isCohort)) || (!testChange.isCohort && eData.some((change) => change.isCohort)) - ) { eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isCohort' entries.`); return } - - // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; + ) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} has inconsistent 'isCohort' entries.`); } - // Initialize new effect data - const effectData: { + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3433,32 +3439,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` + }); + } - // Create new ActiveEffect - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; - } + // Create new ActiveEffect + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3471,32 +3477,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Ability)` + }); + } - // Create new ActiveEffect - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple Active Effects`); - return; - } + // Create new ActiveEffect + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else { + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Ability ${aName} Has Multiple Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3506,56 +3512,59 @@ export const updateRollMods = async () => { changes: eData }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } - // Create new ActiveEffect - await crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); - Object.entries(JSONDATA.CREW_UPGRADES.RollMods) - .forEach(async ([aName, eData]) => { - // Get crew upgrade doc - const crewUpgradeDoc = game.items.getName(aName); - if (!crewUpgradeDoc) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Not Found.`); - return; - } + // Create new ActiveEffect + return crewAbilityDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } + }), + ...Object.entries(JSONDATA.CREW_UPGRADES.RollMods) + .map(async ([aName, eData]) => { + // Get crew upgrade doc + const crewUpgradeDoc = game.items.getName(aName); + if (!crewUpgradeDoc) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Not Found.`); + return undefined; + } - // Get active effects on crewAbilityDoc - const abilityEffects = Array.from(crewUpgradeDoc.effects ?? []) as BladesActiveEffect[]; + // Get active effects on crewUpgradeDoc + const abilityEffects = Array.from(crewUpgradeDoc.effects ?? []) as BladesActiveEffect[]; - // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects - const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); - const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); - const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); + // Separate out 'APPLYTOMEMBERS' and 'APPLYTOCOHORTS' ActiveEffects + const toMemberEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOMEMBERS")); + const toCohortEffects = abilityEffects.filter((effect) => effect.changes.some((change) => change.key === "APPLYTOCOHORTS")); + const standardEffects = abilityEffects.filter((effect) => effect.changes.every((change) => !["APPLYTOMEMBERS", "APPLYTOCOHORTS"].includes(change.key))); - // Confirm eData.isMember and eData.isCohort are consistent across all changes. - const testChange = eData[0]; - if ( - (testChange.isMember && eData.some((change) => !change.isMember)) + // Confirm eData.isMember and eData.isCohort are consistent across all changes. + const testChange = eData[0]; + if ( + (testChange.isMember && eData.some((change) => !change.isMember)) || (!testChange.isMember && eData.some((change) => change.isMember)) - ) { eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isMember' entries.`); return } - if ( - (testChange.isCohort && eData.some((change) => !change.isCohort)) + ) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isMember' entries.`); + } + if ( + (testChange.isCohort && eData.some((change) => !change.isCohort)) || (!testChange.isCohort && eData.some((change) => change.isCohort)) - ) { eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isCohort' entries.`); return } - - // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc - if (testChange.isMember) { - if (toMemberEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); - return; + ) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} has inconsistent 'isCohort' entries.`); } - // Initialize new effect data - const effectData: { + // If eData.isMember or eData.isCohort, first see if there already is such an effect on the doc + if (testChange.isMember) { + if (toMemberEffects.length > 1) { + return eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOMEMBERS' Active Effects`); + } + + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3568,32 +3577,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toMemberEffects.length === 1) { - const abilityEffect = toMemberEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOMEMBERS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toMemberEffects.length === 1) { + const abilityEffect = toMemberEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOMEMBERS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` + }); + } - // Create new ActiveEffect - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else if (testChange.isCohort) { - if (toCohortEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); - return; - } + // Create new ActiveEffect + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else if (testChange.isCohort) { + if (toCohortEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple 'APPLYTOCOHORTS' Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3606,32 +3615,32 @@ export const updateRollMods = async () => { }) }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (toCohortEffects.length === 1) { - const abilityEffect = toCohortEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } else { - effectData.changes.unshift({ - key: "APPLYTOCOHORTS", - mode: 0, - priority: null, - value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` - }); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (toCohortEffects.length === 1) { + const abilityEffect = toCohortEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } else { + effectData.changes.unshift({ + key: "APPLYTOCOHORTS", + mode: 0, + priority: null, + value: `${aName.replace(/\s*\([^()]*? (Ability|Upgrade)\)\s*$/, "")} (Crew Upgrade)` + }); + } - // Create new ActiveEffect - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } else { - if (standardEffects.length > 1) { - eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple Active Effects`); - return; - } + // Create new ActiveEffect + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } else { + if (standardEffects.length > 1) { + eLog.error("updateRollMods", `updateRollMods: Crew Upgrade ${aName} Has Multiple Active Effects`); + return undefined; + } - // Initialize new effect data - const effectData: { + // Initialize new effect data + const effectData: { name: string, icon: string, changes: Array> @@ -3641,35 +3650,36 @@ export const updateRollMods = async () => { changes: eData }; - // Derive new effect data from existing effect, if any, then delete existing effect - if (standardEffects.length === 1) { - const abilityEffect = standardEffects[0] as BladesActiveEffect; - effectData.name = abilityEffect.name ?? effectData.name; - effectData.icon = abilityEffect.icon ?? effectData.icon; - effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); - await abilityEffect.delete(); - } + // Derive new effect data from existing effect, if any, then delete existing effect + if (standardEffects.length === 1) { + const abilityEffect = standardEffects[0] ; + effectData.name = abilityEffect.name ?? effectData.name; + effectData.icon = abilityEffect.icon ?? effectData.icon; + effectData.changes.unshift(...abilityEffect.changes.filter((change) => change.key !== "system.roll_mods")); + await abilityEffect.delete(); + } - // Create new ActiveEffect - await crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); - } - }); + // Create new ActiveEffect + return crewUpgradeDoc.createEmbeddedDocuments("ActiveEffect", [effectData]); + } + }) + ]); }; export const updateDescriptions = async () => { - Object.entries({ + return Promise.all(Object.entries({ ...JSONDATA.ABILITIES.Descriptions, ...JSONDATA.CREW_ABILITIES.Descriptions, ...JSONDATA.CREW_UPGRADES.Descriptions }) - .forEach(async ([aName, desc]) => { + .map(async ([aName, desc]) => { const itemDoc = game.items.getName(aName); if (!itemDoc) { eLog.error("applyRollEffects", `ApplyDescriptions: Item Doc ${aName} Not Found.`); - return; + return undefined; } // Update system.notes - itemDoc.update({"system.notes": desc}); - }); + return itemDoc.update({"system.notes": desc}); + })); }; \ No newline at end of file diff --git a/ts/documents/actors/BladesPC.ts b/ts/documents/actors/BladesPC.ts index cd2a83ac..c6bc0946 100644 --- a/ts/documents/actors/BladesPC.ts +++ b/ts/documents/actors/BladesPC.ts @@ -1,5 +1,5 @@ import BladesItem from "../../BladesItem.js"; -import C, {Playbook, AttributeTrait, ActionTrait, Harm, BladesActorType, BladesItemType, Tag, RollModCategory, Factor, RollModStatus} from "../../core/constants.js"; +import C, {Playbook, AttributeTrait, ActionTrait, Harm, BladesActorType, BladesItemType, Tag, RollModSection, Factor, RollModStatus} from "../../core/constants.js"; import U from "../../core/utilities.js"; import BladesActor from "../../BladesActor.js"; import BladesCrew from "./BladesCrew.js"; @@ -39,7 +39,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, return game.users?.find((user) => user.character?.id === this?.id) || null; } async clearLoadout() { - this.update({"system.loadout.selected": ""}); + await this.update({"system.loadout.selected": ""}); this.updateEmbeddedDocuments( "Item", [ @@ -162,7 +162,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, this.system.trauma.checked, // @ts-ignore Compiler linter mismatch. (_v: unknown, traumaName: string): boolean => Boolean(traumaName in this.system.trauma.active && this.system.trauma.active[traumaName]) - ) as Record; + ); } get currentLoad(): number { if (!BladesActor.IsType(this, BladesActorType.pc)) { return 0 } @@ -178,7 +178,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, async addStash(amount: number): Promise { if (!BladesActor.IsType(this, BladesActorType.pc)) { return } - this.update({"system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max)}); + await this.update({"system.stash.value": Math.min(this.system.stash.value + amount, this.system.stash.max)}); } // #endregion @@ -224,7 +224,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, const rollModsData = BladesRollMod.ParseDocRollMods(this); // Add roll mods from harm - [[/1d/, RollModCategory.roll] as const, [/Less Effect/, RollModCategory.effect] as const].forEach(([effectPat, effectCat]) => { + [[/1d/, RollModSection.roll] as const, [/Less Effect/, RollModSection.effect] as const].forEach(([effectPat, effectCat]) => { const {one: harmConditionOne, two: harmConditionTwo} = Object.values(this.system.harm) .find((harmData) => effectPat.test(harmData.effect)) ?? {}; const harmString = U.objCompact([harmConditionOne, harmConditionTwo === "" ? null : harmConditionTwo]).join(" & "); @@ -238,9 +238,9 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, modType: "harm", value: 1, tooltip: [ - `

${effectCat === RollModCategory.roll ? Harm.Impaired : Harm.Weakened} (Harm)

`, + `

${effectCat === RollModSection.roll ? Harm.Impaired : Harm.Weakened} (Harm)

`, `

${harmString}

`, - effectCat === RollModCategory.roll + effectCat === RollModSection.roll ? "

If your injuries apply to the situation at hand, you suffer −1d to your roll.

" : "

If your injuries apply to the situation at hand, you suffer −1 effect." ].join("") @@ -253,7 +253,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, id: "Push-negative-roll", name: "PUSH", sideString: harmCondition.trim(), - category: RollModCategory.roll, + category: RollModSection.roll, posNeg: "negative", base_status: RollModStatus.ToggledOn, modType: "harm", diff --git a/ts/sheets/actor/BladesPCSheet.ts b/ts/sheets/actor/BladesPCSheet.ts index 8bdd02b7..d0da1b65 100644 --- a/ts/sheets/actor/BladesPCSheet.ts +++ b/ts/sheets/actor/BladesPCSheet.ts @@ -290,7 +290,7 @@ class BladesPCSheet extends BladesActorSheet { super._onAdvanceClick(event); const action = $(event.currentTarget).data("action").replace(/^advance-/, ""); if (action in AttributeTrait) { - this.actor.advanceAttribute(action); + await this.actor.advanceAttribute(action); } }