From bd045b21e6dc1145c4e0701e1e0885d2e4969724 Mon Sep 17 00:00:00 2001 From: Eunomiac Date: Tue, 13 Feb 2024 04:37:45 -0500 Subject: [PATCH] It might work, now have to fix templates --- css/style.min.css | 2 +- module/BladesActor.js | 2 +- module/BladesItem.js | 2 +- module/blades.js | 55 +- module/classes/BladesClocks.js | 10 +- module/classes/BladesConsequence.js | 4 +- module/classes/BladesRoll.js | 650 ++++++++++++-------- module/classes/BladesRollTemp.js | 4 +- module/classes/BladesTargetLink.js | 40 +- module/core/debug.js | 71 +++ module/documents/actors/BladesCrew.js | 4 +- module/documents/actors/BladesFaction.js | 3 + module/documents/actors/BladesNPC.js | 5 +- module/documents/actors/BladesPC.js | 11 +- ts/@types/blades-roll.d.ts | 22 +- ts/BladesActor.ts | 2 +- ts/BladesItem.ts | 2 +- ts/blades.ts | 111 ++-- ts/classes/BladesClocks.ts | 15 +- ts/classes/BladesConsequence.ts | 10 +- ts/classes/BladesRoll.ts | 729 +++++++++++++---------- ts/classes/BladesRollTemp.ts | 4 +- ts/classes/BladesTargetLink.ts | 67 ++- ts/core/debug.ts | 123 ++++ ts/documents/actors/BladesCrew.ts | 9 +- ts/documents/actors/BladesFaction.ts | 6 + ts/documents/actors/BladesNPC.ts | 6 + ts/documents/actors/BladesPC.ts | 18 +- 28 files changed, 1234 insertions(+), 753 deletions(-) create mode 100644 module/core/debug.js create mode 100644 ts/core/debug.ts diff --git a/css/style.min.css b/css/style.min.css index 4c778fe1..263a587b 100644 --- a/css/style.min.css +++ b/css/style.min.css @@ -18184,7 +18184,7 @@ template { :root body.vtt.game.system-eunos-blades #hotbar #chat #chat-log *, :root body.vtt.game.system-eunos-blades #players #chat #chat-log, :root body.vtt.game.system-eunos-blades #players #chat #chat-log * { - --font-primary: "Beaufort", ; + --font-primary: "Beaufort", serif, ; } :root body.vtt.game.system-eunos-blades #interface #chat .flexrow.jump-to-bottom, :root body.vtt.game.system-eunos-blades #controls #chat .flexrow.jump-to-bottom, diff --git a/module/BladesActor.js b/module/BladesActor.js index f13ed8c2..b22bf70e 100644 --- a/module/BladesActor.js +++ b/module/BladesActor.js @@ -745,7 +745,7 @@ class BladesActor extends Actor { get isSubActor() { return this.parentActor !== undefined; } // #endregion // #region BladesRoll Implementation ~ - get rollModsSchemaSet() { + get rollPrimaryModsSchemaSet() { return BladesRollMod.ParseDocModsToSchemaSet(this); } get rollFactors() { diff --git a/module/BladesItem.js b/module/BladesItem.js index e5c2b4a4..0838f955 100644 --- a/module/BladesItem.js +++ b/module/BladesItem.js @@ -183,7 +183,7 @@ class BladesItem extends Item { return this.type; } get rollPrimaryImg() { return this.img; } - get rollModsSchemaSet() { + get rollPrimaryModsSchemaSet() { // Add roll mods from COHORT harm return BladesRollMod.ParseDocModsToSchemaSet(this); } diff --git a/module/blades.js b/module/blades.js index d58d1411..8f0e40c1 100644 --- a/module/blades.js +++ b/module/blades.js @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ // #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ -import C, { ActionTrait, AttributeTrait, RollType, ConsequenceType } from "./core/constants.js"; +import C, { ActionTrait, RollType } from "./core/constants.js"; import registerSettings, { initTinyMCEStyles, initCanvasStyles, initDOMStyles } from "./core/settings.js"; import { registerHandlebarHelpers, preloadHandlebarsTemplates } from "./core/helpers.js"; import BladesChat from "./classes/BladesChat.js"; @@ -18,15 +18,16 @@ import BladesPCSheet from "./sheets/actor/BladesPCSheet.js"; import BladesCrewSheet from "./sheets/actor/BladesCrewSheet.js"; import BladesNPCSheet from "./sheets/actor/BladesNPCSheet.js"; import BladesFactionSheet from "./sheets/actor/BladesFactionSheet.js"; -import BladesRoll, { BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, BladesActionRoll, BladesResistanceRoll } from "./classes/BladesRoll.js"; +import BladesRoll, { BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, BladesActionRoll, BladesEngagementRoll, BladesFortuneRoll, BladesIncarcerationRoll, BladesIndulgeViceRoll, BladesInlineResistanceRoll, BladesResistanceRoll } from "./classes/BladesRoll.js"; import BladesDialog from "./classes/BladesDialog.js"; import BladesAI, { AGENTS, AIAssistant } from "./core/ai.js"; import BladesActiveEffect from "./documents/BladesActiveEffect.js"; import BladesGMTrackerSheet from "./sheets/item/BladesGMTrackerSheet.js"; import BladesClockKeeperSheet from "./sheets/item/BladesClockKeeperSheet.js"; -CONFIG.debug.logging = true; /* DEVCODE*/ -Object.assign(globalThis, { eLog: logger }); +import BladesDebug from "./core/debug.js"; +CONFIG.debug.logging = true; +Object.assign(globalThis, { eLog: logger, BladesDebug }); Handlebars.registerHelper("eLog", logger.hbsLog); /* !DEVCODE*/ let socket; // ~ SocketLib interface @@ -59,45 +60,6 @@ class GlobalGetter { }; BladesActionRoll.New(conf); } - async newResistanceRoll() { - const pc = game.actors.getName("Alistair"); - if (!pc?.id) { - return; - } - const csq = await BladesConsequence.Create({ - target: pc, - targetFlagKey: "rollConsequence", - name: "Shattered Knee", - isScopingById: true, - type: ConsequenceType.ProwessHarm3, - primaryID: pc.uuid, - attribute: AttributeTrait.prowess, - attributeVal: 3, - resistSchema: { - name: "Banged Knee", - type: ConsequenceType.ProwessHarm2, - primaryID: pc.uuid, - canResistWithSpecial: true, - resistWithSpecialNegates: true, - specialFooterMsg: "Ability: Spend to Fully Negate." - }, - canResistWithRoll: true, - canResistWithSpecial: true, - resistWithSpecialNegates: true, - specialFooterMsg: "Ability: Spend to Fully Negate." - }); - const conf = { - target: pc, - targetFlagKey: "rollCollab", - rollType: RollType.Resistance, - rollUserID: game.users.find((user) => user.character?.name === "Alistair")?.id, - rollPrimaryData: pc, - resistanceData: { - consequence: csq.data - } - }; - BladesResistanceRoll.New(conf); - } } // #region Globals: Exposing Functionality to Global Scope ~ /* DEVCODE*/ Object.assign(globalThis, { @@ -126,6 +88,13 @@ class GlobalGetter { BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, + BladesActionRoll, + BladesEngagementRoll, + BladesFortuneRoll, + BladesIncarcerationRoll, + BladesIndulgeViceRoll, + BladesInlineResistanceRoll, + BladesResistanceRoll, BladesChat, BladesConsequence, G, diff --git a/module/classes/BladesClocks.js b/module/classes/BladesClocks.js index 0ac7cd34..c600e5c1 100644 --- a/module/classes/BladesClocks.js +++ b/module/classes/BladesClocks.js @@ -64,7 +64,7 @@ class BladesClockKey extends BladesTargetLink { // If no clocks provided, add one default clock. clocksInitialData.push({}); } - // Generate a local-only TargetLink nstance, to assist in deriving values for the clocks data + // Generate a local-only TargetLink instance, to assist in deriving values for the clocks data const tempLink = new BladesTargetLink(config); // Generate the targetKey or targetFlagKey for each clockData if (tempLink.targetKeyPrefix) { @@ -316,12 +316,10 @@ class BladesClockKey extends BladesTargetLink { } return options; } - // #endregion - // #region ~~~ CONSTRUCTOR & CLOCK CONFIG PARSER ~~~ - constructor(data) { - super(data); + constructor(dataOrConfig) { + super(dataOrConfig); game.eunoblades.ClockKeys.set(this.id, this); - Object.values(data.clocksData).forEach((clockData) => new BladesClock(clockData)); + Object.values(dataOrConfig.clocksData ?? {}).forEach((clockData) => new BladesClock(clockData)); } // parseClockConfig(config: BladesClock.Config, indexOverride?: ClockIndex): BladesClock.Data { // if (this.size === 6) {throw new Error("Cannot add a clock to a clock key with 6 clocks.");} diff --git a/module/classes/BladesConsequence.js b/module/classes/BladesConsequence.js index 3aad651e..3e106bb4 100644 --- a/module/classes/BladesConsequence.js +++ b/module/classes/BladesConsequence.js @@ -161,9 +161,9 @@ class BladesConsequence extends BladesTargetLink { if (this._consequenceNone) { return this._consequenceNone; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const { id, ...linkData } = this.linkData; - return BladesConsequence.Create({ ...linkData, ...BladesConsequence.PartialNoneSchema }) - .then((csq) => this._consequenceNone = csq); + return BladesConsequence.Create({ ...linkData, ...BladesConsequence.PartialNoneSchema }).then((csq) => this._consequenceNone = csq); } get parentConsequence() { if (!this.parentCsqID) { diff --git a/module/classes/BladesRoll.js b/module/classes/BladesRoll.js index 2693719d..bfaf7649 100644 --- a/module/classes/BladesRoll.js +++ b/module/classes/BladesRoll.js @@ -620,13 +620,13 @@ class BladesRollPrimary { // #region Static Methods ~ static IsValidData(data) { if (BladesRollPrimary.IsDoc(data)) { - return true; + return false; } return U.isList(data) && typeof data.rollPrimaryName === "string" && typeof data.rollPrimaryType === "string" && typeof data.rollPrimaryImg === "string" - && Array.isArray(data.rollModsData) + && Array.isArray(data.rollPrimaryModsSchemaSet) && U.isList(data.rollFactors) && (!data.rollPrimaryID || typeof data.rollPrimaryID === "string") && (!data.rollPrimaryDoc || BladesRollPrimary.IsDoc(data.rollPrimaryDoc)); @@ -645,6 +645,16 @@ class BladesRollPrimary { return BladesActor.IsType(doc, BladesActorType.pc, BladesActorType.crew) || BladesItem.IsType(doc, BladesItemType.cohort_expert, BladesItemType.cohort_gang, BladesItemType.gm_tracker); } + static GetDataFromDoc(doc) { + return { + rollPrimaryID: doc.id, + rollPrimaryName: doc.name, + rollPrimaryType: doc.type, + rollPrimaryImg: doc.img, + rollPrimaryModsSchemaSet: doc.rollPrimaryModsSchemaSet, + rollFactors: doc.rollFactors + }; + } static BuildData(config) { if (BladesRollPrimary.IsValidData(config.rollPrimaryData)) { return config.rollPrimaryData; @@ -665,7 +675,7 @@ class BladesRollPrimary { rollPrimaryName: rollPrimary.rollPrimaryName, rollPrimaryType: rollPrimary.rollPrimaryType, rollPrimaryImg: rollPrimary.rollPrimaryImg, - rollPrimaryModsSchemaSet: rollPrimary.rollModsSchemaSet, + rollPrimaryModsSchemaSet: rollPrimary.rollPrimaryModsSchemaSet, rollFactors: rollPrimary.rollFactors }; } @@ -816,7 +826,7 @@ class BladesRollPrimary { rollPrimaryName: primaryDoc.rollPrimaryName, rollPrimaryType: primaryDoc.rollPrimaryType, rollPrimaryImg: primaryDoc.rollPrimaryImg, - rollPrimaryModsSchemaSet: primaryDoc.rollModsSchemaSet, + rollPrimaryModsSchemaSet: primaryDoc.rollPrimaryModsSchemaSet, rollFactors: primaryDoc.rollFactors }; } @@ -1146,8 +1156,7 @@ class BladesRoll extends BladesTargetLink { }); } } - data.rollModsData = this.GetRollModsDataSet(parentRollInst, Object.values(data.rollModsData ?? {})); - return BladesTargetLink.ParseConfigToData(data); + return super.ParseConfigToData(data); } static ApplySchemaDefaults(schemaData) { // Ensure all properties of Schema are provided @@ -1195,41 +1204,6 @@ class BladesRoll extends BladesTargetLink { /* Subclass overrides determine default roll mods. */ return []; } - static GetRollModsDataSet(rollInst, rollModsSchemaSet) { - const { linkData } = rollInst; - const modLinkConfig = { - targetID: linkData.targetID, - targetKey: "targetKey" in linkData - ? "rollModsData" - : undefined, - targetFlagKey: "targetFlagKey" in linkData - ? "rollModsData" - : undefined, - isScopingById: true - }; - const compiledModSchemaSets = [...rollModsSchemaSet]; - // Add roll mods on rollPrimary - if (rollInst.rollPrimary) { - compiledModSchemaSets.push(...rollInst.rollPrimary.rollPrimaryModsSchemaSet - .filter((pSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== pSchema.key))); - } - // Add roll mods on rollOpposition - if (rollInst.rollOpposition?.rollOppModsSchemaSet) { - compiledModSchemaSets.push(...rollInst.rollOpposition.rollOppModsSchemaSet - .filter((oSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== oSchema.key))); - } - // Add default roll mods - compiledModSchemaSets.push(...this.DefaultRollModSchemaSet - .filter((dSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== dSchema.key))); - return Object.fromEntries(compiledModSchemaSets - .map((modSchema) => { - const modData = BladesTargetLink.ParseConfigToData({ - ...BladesRollMod.ApplySchemaDefaults(modSchema), - ...modLinkConfig - }); - return [modData.id, modData]; - })); - } static GetDieClass(rollType, rollResult, dieVal, dieIndex) { switch (rollType) { case RollType.Resistance: { @@ -1399,34 +1373,87 @@ class BladesRoll extends BladesTargetLink { } static BuildLinkConfig(config) { // Prepare partial target link config - const partialLinkConfig = { - target: "target" in config ? config.target : undefined, - targetID: "targetID" in config ? config.targetID : undefined, - targetKey: "targetKey" in config ? config.targetKey : undefined, - targetFlagKey: "targetFlagKey" in config ? config.targetFlagKey : undefined - }; - // If neither target nor targetID are provided, set target to rollUser or currentUser - if (!partialLinkConfig.target && !partialLinkConfig.targetID) { - const rollUser = game.users.get(config.rollUserID ?? game.user.id); - if (!rollUser) { - throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollUserID, target, or targetID in the config object."); + const partialLinkConfig = {}; + if ("targetKey" in config && config.targetKey) { + partialLinkConfig.targetKey = config.targetKey; + } + else if ("targetFlagKey" in config && config.targetFlagKey) { + partialLinkConfig.targetFlagKey = config.targetFlagKey; + } + if ("target" in config) { + if (U.isDocUUID(config.target)) { + partialLinkConfig.targetID = config.target; + } + else if (U.isDocID(config.target)) { + const confTarget = game.actors.get(config.target) + ?? game.items.get(config.target) + ?? game.messages.get(config.target) + ?? game.users.get(config.target); + if (confTarget) { + partialLinkConfig.targetID = confTarget.uuid; + } + else { + throw new Error(`[BladesRoll.BuildLinkConfig] No target found with id ${config.target}.`); + } + } + else { + partialLinkConfig.targetID = config.target.uuid; } - partialLinkConfig.target = rollUser; + } + else if ("targetID" in config) { + partialLinkConfig.targetID = config.targetID; + } + else { + throw new Error("[BladesRoll.BuildLinkConfig] You must provide a valid target or targetID in the config object."); } // If neither targetKey nor targetFlagKey are provided, set targetFlagKey to 'rollCollab'. if (!partialLinkConfig.targetKey && !partialLinkConfig.targetFlagKey) { partialLinkConfig.targetFlagKey = "rollCollab"; } // Build target link config - return BladesTargetLink.BuildLinkConfig(partialLinkConfig); + if (BladesTargetLink.IsValidConfig(partialLinkConfig)) { + return BladesTargetLink.BuildLinkConfig(partialLinkConfig); + } + throw new Error("[BladesRoll.BuildLinkConfig] Invalid link config."); } /** - * This static method accepts a partial version of the config options required - * to build a BladesRoll instance, sets the requisite flags on the storage - * document, then sends out a socket call to the relevant users to construct - * and display the roll instance. + * Asynchronously creates a new instance of `BladesRoll` or its subclasses. + * + * This generic static method is designed to facilitate the creation of roll instances with + * configurations specific to the type of roll being created. It ensures that the correct type + * of roll instance is returned based on the class it's called on, allowing for a flexible and + * type-safe creation process that can be extended to subclasses of `BladesRoll`. + * + * @template C The class on which `New` is called. This class must extend `BladesRoll` and + * must be constructible with a configuration object that is either a `BladesRoll.Config` or + * a combination of `BladesTargetLink.Data` and a partial `BladesRoll.Schema`. This ensures + * that any subclass of `BladesRoll` can use this method to create instances of itself while + * applying any class-specific configurations or behaviors. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance. + * This configuration includes all necessary data to initialize the roll, such as user permissions, + * roll type, and any modifications or additional data required for the roll's operation. + * + * @returns {Promise>} A promise that resolves to an instance of the class + * from which `New` was called. This allows for the dynamic creation of roll instances based + * on the subclass calling the method, ensuring that the returned instance is of the correct type. * - * @param {BladesRoll.Config} config The configuration object for the new roll. + * @example + * // Assuming `MyCustomRoll` is a subclass of `BladesRoll` + * MyCustomRoll.New(myConfig).then(instance => { + * // `instance` is of type `MyCustomRoll` + * }); + * + * @remarks + * - The method performs several key operations as part of the roll instance creation process: + * 1. Builds link configuration based on the provided config. + * 2. Prepares roll user flag data to determine permissions for different users. + * 3. Validates that a roll type is defined in the config, throwing an error if not. + * 4. Logs the roll data for debugging or auditing purposes. + * 5. Constructs and initializes the roll instance, including setting up roll modifications + * and sending out socket calls to inform all users about the roll. + * - This method is central to the dynamic and flexible creation of roll instances within the + * system, allowing for easy extension and customization in subclasses of `BladesRoll`. */ static async New(config) { // Build link config @@ -1440,18 +1467,139 @@ class BladesRoll extends BladesTargetLink { // Log the roll data eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll()", { config }); // Construct and initialize the BladesRoll/BladesTargetLink instance - const rollInst = await BladesRoll.Create({ ...config, ...linkConfig }); + const rollInst = await this.Create({ ...config, ...linkConfig }); + if (!rollInst.isInitPromiseResolved) { + eLog.checkLog3("bladesRoll", "BladesRoll Init Promise NOT Resolved After Awaiting Create"); + await U.waitFor(rollInst.initPromise); + } + else { + eLog.checkLog3("bladesRoll", "BladesRoll Init Promise Resolved After Awaiting Create"); + } // Send out socket calls to all users to see the roll. rollInst.constructRollCollab_SocketCall(rollInst.linkData); return rollInst; } + async initTargetLink() { + this.initialSchema.rollModsData = this.rollModsDataSet; + super.initTargetLink(); + } + get rollModsSchemaSets() { + const compiledModSchemaSets = []; + // Add roll mods on rollPrimary + if (this.rollPrimary) { + compiledModSchemaSets.push(...this.rollPrimary.rollPrimaryModsSchemaSet + .filter((pSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== pSchema.key))); + } + // Add roll mods on rollOpposition + if (this.rollOpposition?.rollOppModsSchemaSet) { + compiledModSchemaSets.push(...this.rollOpposition.rollOppModsSchemaSet + .filter((oSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== oSchema.key))); + } + // Add default roll mods + compiledModSchemaSets.push(...this.constructor.DefaultRollModSchemaSet + .filter((dSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== dSchema.key))); + // If this is a downtime action roll, add default downtime action roll mods + if (this.rollDowntimeAction) { + compiledModSchemaSets.push({ + key: "HelpFromFriend-positive-roll", + name: "Help From a Friend", + section: RollModSection.position, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" + }); + if (this.rollDowntimeAction !== DowntimeAction.IndulgeVice) { + compiledModSchemaSets.push({ + key: "CanBuyResultLevel-positive-after", + name: "Buying Result Level", + section: RollModSection.after, + base_status: RollModStatus.ForcedOn, + posNeg: "positive", + modType: RollModType.general, + value: 0, + effectKeys: [], + tooltip: "

Buying Result Level

After your roll, you can increase the result level by one for each Coin you spend.

" + }); + } + if (this.rollDowntimeAction === DowntimeAction.AcquireAsset) { + compiledModSchemaSets.push({ + key: "RepeatPurchase-positive-roll", + name: "Repeat Purchase", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Repeat Purchase Bonus

Add +1d if you have previously acquired this asset or service with a Acquire Asset Downtime activity.

" + }, { + key: "RestrictedItem-negative-after", + name: "Restricted", + section: RollModSection.after, + base_status: RollModStatus.Hidden, + posNeg: "negative", + modType: RollModType.general, + value: 0, + effectKeys: ["Cost-Heat2"], + tooltip: "

Restricted

Whether contraband goods or dangerous materials, this Acquire Asset Downtime activity will add +2 Heat to your crew.

" + }); + } + } + return compiledModSchemaSets; + } + get rollModsDataSet() { + const { linkData } = this; + const modLinkConfig = { + targetID: linkData.targetID, + isScopingById: true, + ...("targetKey" in linkData + ? { targetKey: `${this.targetKeyPrefix}.rollModsData` } + : {}), + ...("targetFlagKey" in linkData + ? { targetFlagKey: `${this.targetFlagKeyPrefix}.rollModsData` } + : {}) + }; + return Object.fromEntries(this.rollModsSchemaSets + .map((modSchema) => { + const modData = BladesTargetLink.ParseConfigToData({ + ...BladesRollMod.ApplySchemaDefaults(modSchema), + ...modLinkConfig + }); + return [modData.id, modData]; + })); + } // #endregion // #region SOCKET CALLS & RESPONSES ~ + static GetRollSubClass(linkData) { + const targetLink = new BladesTargetLink(linkData); + switch (targetLink.data.rollType) { + case RollType.Action: return BladesActionRoll; + case RollType.Fortune: { + if (targetLink.data.rollSubType === RollSubType.Engagement) { + return BladesEngagementRoll; + } + else if (targetLink.data.rollSubType === RollSubType.Incarceration) { + return BladesIncarcerationRoll; + } + return BladesFortuneRoll; + } + case RollType.Resistance: { + if (targetLink.data.isInlineResistanceRoll) { + return BladesInlineResistanceRoll; + } + return BladesResistanceRoll; + } + case RollType.IndulgeVice: return BladesIndulgeViceRoll; + } + } constructRollCollab_SocketCall(linkData) { socketlib.system.executeForEveryone("constructRollCollab_SocketCall", linkData); } static constructRollCollab_SocketResponse(linkData) { - const rollInst = new BladesRoll(linkData); + const rollInst = new (this.GetRollSubClass(linkData))(linkData); eLog.checkLog3("rollCollab", "constructRollCollab_SocketResponse()", { params: { linkData }, rollInst }); this.renderRollCollab_SocketResponse(rollInst.id); } @@ -1465,7 +1613,7 @@ class BladesRoll extends BladesTargetLink { } async renderRollCollab() { this.prepareRollParticipantData(); - const html = await renderTemplate("systems/eunoblades/templates/rolls/roll-collab.hbs", this.context); + const html = await renderTemplate(this.collabTemplate, this.context); this.elem$.html(html); this.activateListeners(); } @@ -1564,13 +1712,7 @@ class BladesRoll extends BladesTargetLink { return this._rollPrimary; } get rollPrimaryDoc() { - if (BladesRollPrimary.IsDoc(this.rollPrimaryDoc)) { - return this.rollPrimaryDoc; - } - if (BladesRollPrimary.IsDoc(this.rollPrimary)) { - return this.rollPrimary; - } - return undefined; + return this.rollPrimary.rollPrimaryDoc; } get rollOpposition() { if (!this._rollOpposition && BladesRollOpposition.IsValidData(this.data.rollOppData)) { @@ -1949,7 +2091,6 @@ class BladesRoll extends BladesTargetLink { this.rollTraitValOverride = undefined; this.rollFactorPenaltiesNegated = {}; this.tempGMBoosts = {}; - this.rollMods = Object.values(this.data.rollModsData).map((modData) => new BladesRollMod(modData, this)); // ESLINT DISABLE: Dev Code. // eslint-disable-next-line @typescript-eslint/no-explicit-any const initReport = {}; @@ -1989,7 +2130,7 @@ class BladesRoll extends BladesTargetLink { }; watchMod("INITIAL"); /* *** PASS ZERO: ROLLTYPE VALIDATION PASS *** */ - this.rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); + this._rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); watchMod("ROLLTYPE VALIDATION"); /* *** PASS ONE: DISABLE PASS *** */ // ... Conditional Status Pass @@ -2244,11 +2385,10 @@ class BladesRoll extends BladesTargetLink { } get rollMods() { if (!this._rollMods) { - throw new Error("[get rollMods] No roll mods found!"); + this._rollMods = Object.values(this.data.rollModsData).map((modData) => new BladesRollMod(modData, this)); } return [...this._rollMods].sort((modA, modB) => this.compareMods(modA, modB)); } - set rollMods(val) { this._rollMods = val; } canResistWithArmor(csq) { if (!this.rollPrimary.hasArmor) { return false; @@ -2296,193 +2436,6 @@ class BladesRoll extends BladesTargetLink { this.rollMods.forEach((rollMod) => rollMod.applyRollModEffectKeys()); return this.getTemplateContext(); } - getFortuneRollModsSchemaSet() { - const modsData = []; - if (this.rollSubType === RollSubType.Engagement) { - modsData.push({ - key: "BoldPlan-positive-roll", - name: "Bold Plan", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "ComplexPlan-negative-roll", - name: "Complex Plan", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "ExploitWeakness-positive-roll", - name: "Exploiting a Weakness", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "WellDefended-negative-roll", - name: "Well-Defended", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "HelpFromFriend-positive-roll", - name: "Help From a Friend", - section: RollModSection.position, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" - }); - modsData.push({ - key: "EnemyInterference-negative-roll", - name: "Enemy Interference", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - } - return modsData; - } - getDowntimeActionRollModsSchemaSet() { - const modsData = []; - modsData.push({ - key: "HelpFromFriend-positive-roll", - name: "Help From a Friend", - section: RollModSection.position, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" - }); - if (this.rollDowntimeAction !== DowntimeAction.IndulgeVice) { - modsData.push({ - key: "CanBuyResultLevel-positive-after", - name: "Buying Result Level", - section: RollModSection.after, - base_status: RollModStatus.ForcedOn, - posNeg: "positive", - modType: RollModType.general, - value: 0, - effectKeys: [], - tooltip: "

Buying Result Level

After your roll, you can increase the result level by one for each Coin you spend.

" - }); - } - switch (this.rollDowntimeAction) { - case DowntimeAction.AcquireAsset: { - modsData.push({ - key: "RepeatPurchase-positive-roll", - name: "Repeat Purchase", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Repeat Purchase Bonus

Add +1d if you have previously acquired this asset or service with a Acquire Asset Downtime activity.

" - }); - modsData.push({ - key: "RestrictedItem-negative-after", - name: "Restricted", - section: RollModSection.after, - base_status: RollModStatus.Hidden, - posNeg: "negative", - modType: RollModType.general, - value: 0, - effectKeys: ["Cost-Heat2"], - tooltip: "

Restricted

Whether contraband goods or dangerous materials, this Acquire Asset Downtime activity will add +2 Heat to your crew.

" - }); - break; - } - default: break; - } - /* - modsData.push({ - id: "--", - name: "", - section: RollModSection, - base_status: RollModStatus, - posNeg: "", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }) - */ - return modsData; - } - /** - * Gets the roll modifications data. - * @returns {BladesRollMod.Data[]} The roll modifications data. - */ - getRollModsData() { - const defaultMods = []; - if (this.rollType === RollType.Fortune) { - defaultMods.push(...this.getFortuneRollModsSchemaSet()); - } - if (this.rollDowntimeAction) { - defaultMods.push(...this.getDowntimeActionRollModsSchemaSet()); - } - if (this.rollType === RollType.Action) { - if (this.rollPrimary.isWorsePosition) { - defaultMods.push({ - key: "WorsePosition-negative-position", - name: "Worse Position", - section: RollModSection.position, - base_status: RollModStatus.ForcedOn, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Worse Position

A Consequence on a previous roll has worsened your Position.

" - }); - } - } - if (this.rollType === RollType.Action - && this.acceptedConsequences.some((csq) => csq.type === ConsequenceType.ReducedEffect)) { - defaultMods.push({ - key: "ReducedEffect-negative-effect", - name: "Reduced Effect", - section: RollModSection.effect, - base_status: RollModStatus.ForcedOn, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Reduced Effect

A Consequence has worsened your Effect.

" - }); - } - return Object.values(BladesRoll.GetRollModsDataSet(this, [ - ...defaultMods, - ...this.rollOpposition?.rollOppModsSchemaSet ?? [] - ])); - } /** * Determines if the user is a game master. * @returns {boolean} Whether the user is a GM. @@ -3539,6 +3492,21 @@ class BladesActionRoll extends BladesRoll { } ]; } + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ static async New(config) { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -3546,9 +3514,42 @@ class BladesActionRoll extends BladesRoll { ...config, ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesActionRoll. + const rollInst = await super.New(parsedConfig); return rollInst; } + get rollModsSchemaSets() { + const rollModSchemaSets = super.rollModsSchemaSets; + // Add additional conditional roll mods based on effects of previous consequences. + if (this.rollPrimary.isWorsePosition) { + rollModSchemaSets.push({ + key: "WorsePosition-negative-position", + name: "Worse Position", + section: RollModSection.position, + base_status: RollModStatus.ForcedOn, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Worse Position

A Consequence on a previous roll has worsened your Position.

" + }); + } + if (this.acceptedConsequences.some((csq) => csq.type === ConsequenceType.ReducedEffect)) { + rollModSchemaSets.push({ + key: "ReducedEffect-negative-effect", + name: "Reduced Effect", + section: RollModSection.effect, + base_status: RollModStatus.ForcedOn, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Reduced Effect

A Consequence has worsened your Effect.

" + }); + } + return rollModSchemaSets; + } get collabTemplate() { return `systems/eunos-blades/templates/roll/roll-collab-action${game.user.isGM ? "-gm" : ""}.hbs`; } @@ -3624,6 +3625,21 @@ class BladesResistanceRoll extends BladesRoll { eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [1]", { config }); return config; } + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ static async New(config) { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -3631,7 +3647,9 @@ class BladesResistanceRoll extends BladesRoll { ...config, ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesResistanceRoll. + const rollInst = await super.New(parsedConfig); return rollInst; } get collabTemplate() { @@ -3675,6 +3693,21 @@ class BladesFortuneRoll extends BladesRoll { } return config; } + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ static async New(config) { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -3682,7 +3715,9 @@ class BladesFortuneRoll extends BladesRoll { ...config, ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesFortuneRoll. + const rollInst = await super.New(parsedConfig); return rollInst; } } @@ -3701,6 +3736,21 @@ class BladesIndulgeViceRoll extends BladesRoll { config.rollDowntimeAction = DowntimeAction.IndulgeVice; return config; } + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ static async New(config) { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -3708,7 +3758,9 @@ class BladesIndulgeViceRoll extends BladesRoll { ...config, ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesIndulgeViceRoll. + const rollInst = await super.New(parsedConfig); return rollInst; } get collabTemplate() { @@ -3730,6 +3782,76 @@ class BladesIndulgeViceRoll extends BladesRoll { } } class BladesEngagementRoll extends BladesFortuneRoll { + static get DefaultRollModSchemaSet() { + return [ + { + key: "BoldPlan-positive-roll", + name: "Bold Plan", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "ComplexPlan-negative-roll", + name: "Complex Plan", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "ExploitWeakness-positive-roll", + name: "Exploiting a Weakness", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "WellDefended-negative-roll", + name: "Well-Defended", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "HelpFromFriend-positive-roll", + name: "Help From a Friend", + section: RollModSection.position, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" + }, + { + key: "EnemyInterference-negative-roll", + name: "Enemy Interference", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + } + ]; + } get chatTemplate() { return "systems/eunos-blades/templates/chat/roll-result/fortune-engagement.hbs"; } diff --git a/module/classes/BladesRollTemp.js b/module/classes/BladesRollTemp.js index c56773a6..b8b6b9fe 100644 --- a/module/classes/BladesRollTemp.js +++ b/module/classes/BladesRollTemp.js @@ -668,7 +668,7 @@ class BladesRollPrimary { rollPrimaryName: rollPrimary.rollPrimaryName, rollPrimaryType: rollPrimary.rollPrimaryType, rollPrimaryImg: rollPrimary.rollPrimaryImg, - rollPrimaryModsSchemaSet: rollPrimary.rollModsSchemaSet, + rollPrimaryModsSchemaSet: rollPrimary.rollPrimaryModsSchemaSet, rollFactors: rollPrimary.rollFactors }; } @@ -685,7 +685,7 @@ class BladesRollPrimary { get rollPrimaryName() { return this.rollPrimaryDoc.rollPrimaryName; } get rollPrimaryType() { return this.rollPrimaryDoc.rollPrimaryType; } get rollPrimaryImg() { return this.rollPrimaryDoc.rollPrimaryImg; } - get rollPrimaryModsSchemaSet() { return this.rollPrimaryDoc.rollModsSchemaSet; } + get rollPrimaryModsSchemaSet() { return this.rollPrimaryDoc.rollPrimaryModsSchemaSet; } get rollFactors() { return this.rollPrimaryDoc.rollFactors; } get isWorsePosition() { if (this.rollPrimaryDoc) { diff --git a/module/classes/BladesTargetLink.js b/module/classes/BladesTargetLink.js index e07f3942..4aded4ae 100644 --- a/module/classes/BladesTargetLink.js +++ b/module/classes/BladesTargetLink.js @@ -6,12 +6,14 @@ import { BladesItem } from "../documents/BladesItemProxy.js"; import BladesChat from "./BladesChat.js"; class BladesTargetLink { // #region STATIC METHODS ~ - static ValidTargetClasses = [ - BladesActor, - BladesItem, - BladesChat, - User - ]; + static get ValidTargetClasses() { + return [ + BladesActor, + BladesItem, + BladesChat, + User + ]; + } static IsValidConfig(ref) { return U.isSimpleObj(ref) && (U.isDocID(ref.target) @@ -27,8 +29,8 @@ class BladesTargetLink { && U.isDocID(ref.id) && U.isDocUUID(ref.targetID) && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) - && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)) - && (typeof ref.isScopingById === "boolean"); + && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); + // && (typeof ref.isScopingById === "boolean"); } static #ParseChildLinkData(childData, parentLinkData) { if (!parentLinkData) { @@ -132,14 +134,14 @@ class BladesTargetLink { ...partialSchema, targetID: U.parseDocRefToUUID("target" in fullConfig ? fullConfig.target : fullConfig.targetID), targetKey: fullConfig.targetKey - }); + }, parentLinkData); } return this.ParseConfigToData({ id: randomID(), ...partialSchema, targetID: U.parseDocRefToUUID("target" in fullConfig ? fullConfig.target : fullConfig.targetID), targetFlagKey: fullConfig.targetFlagKey - }); + }, parentLinkData); } /** * This static method parses the provided data into a format suitable for BladesTargetLink. @@ -157,7 +159,7 @@ class BladesTargetLink { if (this.IsValidData(data)) { return this.#ParseChildLinkData(data, parentLinkData); } - return this.#ParseConfigToData(data); + return this.#ParseConfigToData(data, parentLinkData); } static PartitionSchemaData(dataOrConfig) { const { id, target, targetID, targetKey, targetFlagKey, isScopingById, ...schemaData } = dataOrConfig; @@ -198,7 +200,7 @@ class BladesTargetLink { partialSchema }; } - static #ApplySchemaDefaults(schemaData) { + static _ApplySchemaDefaults(schemaData) { return this.ApplySchemaDefaults(schemaData); } /** @@ -330,10 +332,11 @@ class BladesTargetLink { constructor(dataOrConfig, parentLinkData) { let linkData; let schema; + const subclassConstructor = this.constructor; // First, we construct the link data from the config or data object. - if (BladesTargetLink.IsValidData(dataOrConfig)) { + if (subclassConstructor.IsValidData(dataOrConfig)) { // If a simple link data object was provided, acquire the schema from the source document - ({ linkData } = BladesTargetLink.PartitionSchemaData(dataOrConfig)); + ({ linkData } = subclassConstructor.PartitionSchemaData(dataOrConfig)); const target = fromUuidSync(linkData.targetID); if (!target) { throw new Error(`[new BladesTargetLink()] Unable to resolve target from uuid '${linkData.targetID}'`); @@ -353,9 +356,9 @@ class BladesTargetLink { const parsedData = BladesTargetLink.#ParseConfigToData(dataOrConfig, parentLinkData); // Next we separate the linkData and the schemaData from the parsedData object. let partialSchema; - ({ linkData, partialSchema } = BladesTargetLink.PartitionSchemaData(parsedData)); + ({ linkData, partialSchema } = subclassConstructor.PartitionSchemaData(parsedData)); // And apply any schema defaults to the provided schema data. - schema = BladesTargetLink.#ApplySchemaDefaults(partialSchema); + schema = subclassConstructor._ApplySchemaDefaults(partialSchema); } this._id = linkData.id; this._targetID = linkData.targetID; @@ -451,7 +454,7 @@ class BladesTargetLink { }).catch(reject); } else if (this.targetFlagKeyPrefix) { - const updateData = mergeObject(this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix), data); + const updateData = mergeObject((this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) ?? {}), data); this.target.setFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix, updateData).then(() => { this.isInitPromiseResolved = true; resolve(); @@ -461,6 +464,7 @@ class BladesTargetLink { reject(); } }); + return this.initPromise; } async #updateTargetViaMerge(updateData, waitFor) { await U.waitFor(waitFor); @@ -471,7 +475,7 @@ class BladesTargetLink { } else if (this.targetFlagKeyPrefix) { // We must retrieve the existing flag data, flattenObject it, then merge it with updateData - const existingFlagData = this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix); + const existingFlagData = this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) ?? {}; const flattenedFlagData = flattenObject(existingFlagData); const mergedFlagData = mergeObject(flattenedFlagData, updateData); return this.target.setFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix, mergedFlagData); diff --git a/module/core/debug.js b/module/core/debug.js new file mode 100644 index 00000000..fdec4ea5 --- /dev/null +++ b/module/core/debug.js @@ -0,0 +1,71 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ +import { RollPermissions, ActionTrait, RollPhase, Effect, RollType, Position } from "../core/constants.js"; +import { BladesPC, BladesNPC } from "../documents/BladesActorProxy.js"; +import { BladesRollPrimary } from "../classes/BladesRoll.js"; +// #endregion ▮▮▮▮[IMPORTS]▮▮▮▮ +class BladesDebug { + static async GetSampleSchemas() { + // Documents + const SAMPLE_USER_NAME = "Alistair"; + const SAMPLE_PC_NAME = "Alistair"; + const SAMPLE_NPC_NAME = "Setarra"; + const sampleUser = game.users.getName(SAMPLE_USER_NAME); + if (!sampleUser) { + throw new Error(`Sample user with name "${SAMPLE_USER_NAME}" not found.`); + } + const samplePC = game.actors.getName(SAMPLE_PC_NAME); + if (!BladesPC.IsType(samplePC)) { + throw new Error(`Sample BladesPC with name "${SAMPLE_PC_NAME}" not found.`); + } + const sampleNPC = game.actors.getName(SAMPLE_NPC_NAME); + if (!BladesNPC.IsType(sampleNPC)) { + throw new Error(`Sample BladesNPC with name "${SAMPLE_NPC_NAME}" not found or is not a valid BladesNPC.`); + } + // BladesActionRoll + const BladesActionRoll_Schema = { + rollType: RollType.Action, + // rollSubType: RollSubType.GatherInfo, + // rollPrompt: "Gathering Information", + rollTrait: ActionTrait.skirmish, + // rollUserID: sampleUser.id, + // rollDowntimeAction: DowntimeAction.AcquireAsset, + // rollClockKey: U.getLast(game.eunoblades.ClockKeys.contents)?.id, + rollPrimaryData: BladesRollPrimary.GetDataFromDoc(samplePC), + // rollOppData: sampleNPC, + // rollParticipantData: {}, + // consequenceData: {}, + // resistanceData: { + // consequence: {} + // }, + rollModsData: {}, + rollPositionInitial: Position.risky, + rollEffectInitial: Effect.standard, + rollPosEffectTrade: false, + rollPhase: RollPhase.Collaboration, + GMBoosts: {}, + GMOppBoosts: {}, + GMOverrides: {}, + rollFactorToggles: { + source: {}, + opposition: {} + }, + userPermissions: { + [sampleUser.id]: RollPermissions.Primary + } + // rollPositionFinal: Position.risky, + // rollEffectFinal: Effect.standard, + // rollResult: RollResult.success, + // rollResultDelta: 0, + // rollResultFinal: RollResult.success, + // rollTraitVerb: "skirmishes", + // rollTraitPastVerb: "skirmished", + // finalDiceData: [], + // isInlineResistanceRoll: false + }; + return { + BladesActionRoll_Schema + }; + } +} +export default BladesDebug; diff --git a/module/documents/actors/BladesCrew.js b/module/documents/actors/BladesCrew.js index 677a8a93..36eb8503 100644 --- a/module/documents/actors/BladesCrew.js +++ b/module/documents/actors/BladesCrew.js @@ -13,6 +13,9 @@ class BladesCrew extends BladesActor { } // #endregion // #region Static Overrides: Create ~ + // static override IsType(doc: unknown): doc is BladesActorOfType { + // return super.IsType(doc, BladesActorType.crew); + // } static IsType(doc) { return super.IsType(doc, BladesActorType.crew); } @@ -124,7 +127,6 @@ class BladesCrew extends BladesActor { } // #endregion // #region BladesRoll Implementation - get rollPrimaryModsSchemaSet() { return this.rollModsSchemaSet; } // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() { return this.id; } get rollParticipantDoc() { return this; } diff --git a/module/documents/actors/BladesFaction.js b/module/documents/actors/BladesFaction.js index 0cec76a4..fd107e97 100644 --- a/module/documents/actors/BladesFaction.js +++ b/module/documents/actors/BladesFaction.js @@ -14,6 +14,9 @@ class BladesFaction extends BladesActor { return new Collection(super.GetTypeWithTags(BladesActorType.faction) .map((faction) => [faction.id, faction])); } + static IsType(doc) { + return super.IsType(doc, BladesActorType.faction); + } // #region BladesRoll Implementation // #region BladesRoll.OppositionDoc Implementation get rollOppID() { return this.id; } diff --git a/module/documents/actors/BladesNPC.js b/module/documents/actors/BladesNPC.js index 5286bb13..c6723631 100644 --- a/module/documents/actors/BladesNPC.js +++ b/module/documents/actors/BladesNPC.js @@ -1,4 +1,4 @@ -import { Factor } from "../../core/constants.js"; +import { BladesActorType, Factor } from "../../core/constants.js"; import BladesActor from "../../BladesActor.js"; import BladesNPCSheet from "../../sheets/actor/BladesNPCSheet.js"; class BladesNPC extends BladesActor { @@ -9,6 +9,9 @@ class BladesNPC extends BladesActor { return loadTemplates(["systems/eunos-blades/templates/npc-sheet.hbs"]); } // #endregion + static IsType(doc) { + return super.IsType(doc, BladesActorType.npc); + } // #region BladesRoll Implementation get rollFactors() { const factorData = super.rollFactors; diff --git a/module/documents/actors/BladesPC.js b/module/documents/actors/BladesPC.js index 95505060..55fd7eb4 100644 --- a/module/documents/actors/BladesPC.js +++ b/module/documents/actors/BladesPC.js @@ -28,6 +28,9 @@ class BladesPC extends BladesActor { } // #endregion // #region Static Overrides: Create, get All ~ + // static override IsType(doc: unknown): doc is BladesActorOfType { + // return super.IsType(doc, BladesActorType.pc); + // } static IsType(doc) { return super.IsType(doc, BladesActorType.pc); } @@ -412,7 +415,7 @@ class BladesPC extends BladesActor { // #endregion // #region BladesRoll.PrimaryDoc Implementation get rollPrimaryModsSchemaSet() { - const rollModsData = this.rollModsSchemaSet; + const rollModsSchemaSet = super.rollPrimaryModsSchemaSet; // Add roll mods from harm [ [/1d/, RollModSection.roll], @@ -422,7 +425,7 @@ class BladesPC extends BladesActor { .find((harmData) => effectPat.test(harmData.effect)) ?? {}; const harmString = U.objCompact([harmConditionOne, harmConditionTwo === "" ? null : harmConditionTwo]).join(" & "); if (harmString.length > 0) { - rollModsData.push({ + rollModsSchemaSet.push({ key: `Harm-negative-${effectCat}`, name: harmString, section: effectCat, @@ -443,7 +446,7 @@ class BladesPC extends BladesActor { const { one: harmCondition } = Object.values(this.system.harm) .find((harmData) => /Need Help/.test(harmData.effect)) ?? {}; if (harmCondition && harmCondition.trim() !== "") { - rollModsData.push({ + rollModsSchemaSet.push({ key: "Push-negative-roll", name: "PUSH", sideString: harmCondition.trim(), @@ -460,7 +463,7 @@ class BladesPC extends BladesActor { ].join("") }); } - return rollModsData; + return rollModsSchemaSet; } async applyHarm(num, name) { if (num === 4) { diff --git a/ts/@types/blades-roll.d.ts b/ts/@types/blades-roll.d.ts index 88838d28..f84ac419 100644 --- a/ts/@types/blades-roll.d.ts +++ b/ts/@types/blades-roll.d.ts @@ -50,20 +50,20 @@ declare global { rollSubType?: RollSubType, rollPrompt?: string; rollUserID?: IDString, - rollTrait?: RollTrait, + rollTrait: RollTrait, rollDowntimeAction?: DowntimeAction, rollClockKey?: IDString, - rollPrimaryData?: PrimaryData; + rollPrimaryData: PrimaryData; rollOppData?: OppositionData; rollParticipantData?: RollParticipantDataSet, - participantRollTo?: string, - resistanceRollTo?: { - id: string, - userID: string, - consequenceID: string - }, + // participantRollTo?: string, + // resistanceRollTo?: { + // id: string, + // userID: string, + // consequenceID: string + // }, consequenceData?: Partial> >, - userPermissions: Record, - rollPositionFinal?: Position, rollEffectFinal?: Effect, rollResult?: number|false|RollResult, - rollResultDelta: number, + rollResultDelta?: number, rollResultFinal?: number|false|RollResult, rollTraitVerb?: string, rollTraitPastVerb?: string, @@ -106,7 +104,7 @@ declare global { isInlineResistanceRoll?: boolean, - userPermissions?: Record + userPermissions: Record } export type Config = BladesTargetLink.Config & Partial; diff --git a/ts/BladesActor.ts b/ts/BladesActor.ts index 9ccdf47a..22e120c7 100644 --- a/ts/BladesActor.ts +++ b/ts/BladesActor.ts @@ -807,7 +807,7 @@ class BladesActor extends Actor implements BladesDocument { // #endregion // #region BladesRoll Implementation ~ - get rollModsSchemaSet(): BladesRollMod.Schema[] { + get rollPrimaryModsSchemaSet(): BladesRollMod.Schema[] { return BladesRollMod.ParseDocModsToSchemaSet(this); } diff --git a/ts/BladesItem.ts b/ts/BladesItem.ts index e8fcaddf..5d3d85f9 100644 --- a/ts/BladesItem.ts +++ b/ts/BladesItem.ts @@ -220,7 +220,7 @@ class BladesItem extends Item implements BladesDocument, get rollPrimaryImg() { return this.img; } - get rollModsSchemaSet(): BladesRollMod.Schema[] { + get rollPrimaryModsSchemaSet(): BladesRollMod.Schema[] { // Add roll mods from COHORT harm return BladesRollMod.ParseDocModsToSchemaSet(this); } diff --git a/ts/blades.ts b/ts/blades.ts index 9d9c18ee..823794cc 100644 --- a/ts/blades.ts +++ b/ts/blades.ts @@ -1,6 +1,15 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ // #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ -import C, {ActionTrait, ClockColor, ClockKeyDisplayMode, AttributeTrait, RollType, ConsequenceType, Position, RollResult} from "./core/constants"; +import C, { + ActionTrait, + ClockColor, + ClockKeyDisplayMode, + AttributeTrait, + RollType, + ConsequenceType, + Position, + RollResult +} from "./core/constants"; import registerSettings, {initTinyMCEStyles, initCanvasStyles, initDOMStyles} from "./core/settings"; import {registerHandlebarHelpers, preloadHandlebarsTemplates} from "./core/helpers"; import BladesChat from "./classes/BladesChat"; @@ -34,7 +43,19 @@ import BladesPCSheet from "./sheets/actor/BladesPCSheet"; import BladesCrewSheet from "./sheets/actor/BladesCrewSheet"; import BladesNPCSheet from "./sheets/actor/BladesNPCSheet"; import BladesFactionSheet from "./sheets/actor/BladesFactionSheet"; -import BladesRoll, {BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, BladesActionRoll, BladesEngagementRoll, BladesFortuneRoll, BladesIncarcerationRoll, BladesIndulgeViceRoll, BladesInlineResistanceRoll, BladesResistanceRoll} from "./classes/BladesRoll"; +import BladesRoll, { + BladesRollMod, + BladesRollPrimary, + BladesRollOpposition, + BladesRollParticipant, + BladesActionRoll, + BladesEngagementRoll, + BladesFortuneRoll, + BladesIncarcerationRoll, + BladesIndulgeViceRoll, + BladesInlineResistanceRoll, + BladesResistanceRoll +} from "./classes/BladesRoll"; import BladesDialog from "./classes/BladesDialog"; import BladesAI, {AGENTS, AIAssistant} from "./core/ai"; @@ -42,9 +63,10 @@ import BladesActiveEffect from "./documents/BladesActiveEffect"; import BladesGMTrackerSheet from "./sheets/item/BladesGMTrackerSheet"; import BladesClockKeeperSheet from "./sheets/item/BladesClockKeeperSheet"; -CONFIG.debug.logging = true; /* DEVCODE*/ -Object.assign(globalThis, {eLog: logger}); +import BladesDebug from "./core/debug"; +CONFIG.debug.logging = true; +Object.assign(globalThis, {eLog: logger, BladesDebug}); Handlebars.registerHelper("eLog", logger.hbsLog); /* !DEVCODE*/ @@ -92,43 +114,43 @@ class GlobalGetter { BladesActionRoll.New(conf); } - async newResistanceRoll() { - const pc = game.actors.getName("Alistair") as BladesPC|undefined; - if (!pc?.id) { return; } - const csq = await BladesConsequence.Create({ - target: pc, - targetFlagKey: "rollConsequence" as TargetFlagKey, - name: "Shattered Knee", - isScopingById: true, - type: ConsequenceType.ProwessHarm3, - primaryID: pc.uuid, - attribute: AttributeTrait.prowess, - attributeVal: 3, - resistSchema: { - name: "Banged Knee", - type: ConsequenceType.ProwessHarm2, - primaryID: pc.uuid, - canResistWithSpecial: true, - resistWithSpecialNegates: true, - specialFooterMsg: "Ability: Spend to Fully Negate." - }, - canResistWithRoll: true, - canResistWithSpecial: true, - resistWithSpecialNegates: true, - specialFooterMsg: "Ability: Spend to Fully Negate." - }); - const conf: BladesRoll.Config = { - target: pc, - targetFlagKey: "rollCollab" as TargetFlagKey, - rollType: RollType.Resistance, - rollUserID: game.users.find((user) => user.character?.name === "Alistair")?.id as IDString, - rollPrimaryData: pc, - resistanceData: { - consequence: csq.data - } - }; - BladesResistanceRoll.New(conf); - } + // async newResistanceRoll() { + // const pc = game.actors.getName("Alistair") as BladesPC|undefined; + // if (!pc?.id) { return; } + // const csq = await BladesConsequence.Create({ + // target: pc, + // targetFlagKey: "rollConsequence" as TargetFlagKey, + // name: "Shattered Knee", + // isScopingById: true, + // type: ConsequenceType.ProwessHarm3, + // primaryID: pc.uuid, + // attribute: AttributeTrait.prowess, + // attributeVal: 3, + // resistSchema: { + // name: "Banged Knee", + // type: ConsequenceType.ProwessHarm2, + // primaryID: pc.uuid, + // canResistWithSpecial: true, + // resistWithSpecialNegates: true, + // specialFooterMsg: "Ability: Spend to Fully Negate." + // }, + // canResistWithRoll: true, + // canResistWithSpecial: true, + // resistWithSpecialNegates: true, + // specialFooterMsg: "Ability: Spend to Fully Negate." + // }); + // const conf: BladesRoll.Config = { + // target: pc, + // targetFlagKey: "rollCollab" as TargetFlagKey, + // rollType: RollType.Resistance, + // rollUserID: game.users.find((user) => user.character?.name === "Alistair")?.id as IDString, + // rollPrimaryData: pc, + // resistanceData: { + // consequence: csq.data + // } + // }; + // BladesResistanceRoll.New(conf); + // } } @@ -161,6 +183,13 @@ class GlobalGetter { BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, + BladesActionRoll, + BladesEngagementRoll, + BladesFortuneRoll, + BladesIncarcerationRoll, + BladesIndulgeViceRoll, + BladesInlineResistanceRoll, + BladesResistanceRoll, BladesChat, BladesConsequence, G, diff --git a/ts/classes/BladesClocks.ts b/ts/classes/BladesClocks.ts index d576c7d9..76284419 100644 --- a/ts/classes/BladesClocks.ts +++ b/ts/classes/BladesClocks.ts @@ -121,7 +121,7 @@ class BladesClockKey extends BladesTargetLink implements clocksInitialData.push({}); } - // Generate a local-only TargetLink nstance, to assist in deriving values for the clocks data + // Generate a local-only TargetLink instance, to assist in deriving values for the clocks data const tempLink = new BladesTargetLink(config); // Generate the targetKey or targetFlagKey for each clockData @@ -162,10 +162,7 @@ class BladesClockKey extends BladesTargetLink implements // Create and initialize the target link - const clockKeyLink = await super.Create< - BladesTargetLink, - BladesClockKey.Schema - >(tempLink.data); + const clockKeyLink = await super.Create(tempLink.data); // Instantiate the ClockKey const clockKey = new BladesClockKey(clockKeyLink.data); @@ -410,10 +407,12 @@ class BladesClockKey extends BladesTargetLink implements // #region ~~~ CONSTRUCTOR & CLOCK CONFIG PARSER ~~~ - constructor(data: BladesClockKey.Data) { - super(data); + constructor(config: BladesTargetLink.Config & Partial) + constructor(data: BladesClockKey.Data) + constructor(dataOrConfig: BladesClockKey.Data | (BladesTargetLink.Config & Partial)) { + super(dataOrConfig); game.eunoblades.ClockKeys.set(this.id, this); - Object.values(data.clocksData).forEach((clockData) => new BladesClock(clockData)); + Object.values(dataOrConfig.clocksData ?? {}).forEach((clockData) => new BladesClock(clockData)); } // parseClockConfig(config: BladesClock.Config, indexOverride?: ClockIndex): BladesClock.Data { diff --git a/ts/classes/BladesConsequence.ts b/ts/classes/BladesConsequence.ts index d27f8740..d3c97748 100644 --- a/ts/classes/BladesConsequence.ts +++ b/ts/classes/BladesConsequence.ts @@ -154,9 +154,15 @@ class BladesConsequence extends BladesTargetLink { _consequenceNone?: BladesConsequence; get consequenceNone(): Promise|BladesConsequence { if (this._consequenceNone) { return this._consequenceNone; } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const {id, ...linkData} = this.linkData; - return BladesConsequence.Create({...linkData, ...BladesConsequence.PartialNoneSchema}) - .then((csq) => this._consequenceNone = csq); + return BladesConsequence.Create< + new( + dataConfigOrSchema: BladesTargetLink.Config & Partial, + parentCsq?: BladesConsequence.Data + ) => BladesConsequence, + BladesConsequence.Schema + >({...linkData, ...BladesConsequence.PartialNoneSchema}).then((csq) => this._consequenceNone = csq); } get parentConsequence(): BladesConsequence|undefined { if (!this.parentCsqID) { return undefined; } diff --git a/ts/classes/BladesRoll.ts b/ts/classes/BladesRoll.ts index 47017c9f..eabdeada 100644 --- a/ts/classes/BladesRoll.ts +++ b/ts/classes/BladesRoll.ts @@ -656,12 +656,12 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { // #region Static Methods ~ static IsValidData(data: unknown): data is BladesRoll.PrimaryData { - if (BladesRollPrimary.IsDoc(data)) {return true;} + if (BladesRollPrimary.IsDoc(data)) {return false;} return U.isList(data) && typeof data.rollPrimaryName === "string" && typeof data.rollPrimaryType === "string" && typeof data.rollPrimaryImg === "string" - && Array.isArray(data.rollModsData) + && Array.isArray(data.rollPrimaryModsSchemaSet) && U.isList(data.rollFactors) && (!data.rollPrimaryID || typeof data.rollPrimaryID === "string") && (!data.rollPrimaryDoc || BladesRollPrimary.IsDoc(data.rollPrimaryDoc)); @@ -683,6 +683,17 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { || BladesItem.IsType(doc, BladesItemType.cohort_expert, BladesItemType.cohort_gang, BladesItemType.gm_tracker); } + static GetDataFromDoc(doc: BladesRoll.PrimaryDoc): BladesRoll.PrimaryData { + return { + rollPrimaryID: doc.id, + rollPrimaryName: doc.name, + rollPrimaryType: doc.type as BladesRoll.PrimaryType, + rollPrimaryImg: doc.img, + rollPrimaryModsSchemaSet: doc.rollPrimaryModsSchemaSet, + rollFactors: doc.rollFactors + }; + } + static BuildData(config: BladesRoll.Config|BladesRoll.Schema): BladesRoll.PrimaryData { if (BladesRollPrimary.IsValidData(config.rollPrimaryData)) { return config.rollPrimaryData; @@ -702,7 +713,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { rollPrimaryName: rollPrimary.rollPrimaryName, rollPrimaryType: rollPrimary.rollPrimaryType, rollPrimaryImg: rollPrimary.rollPrimaryImg, - rollPrimaryModsSchemaSet: rollPrimary.rollModsSchemaSet, + rollPrimaryModsSchemaSet: rollPrimary.rollPrimaryModsSchemaSet, rollFactors: rollPrimary.rollFactors }; } @@ -879,7 +890,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { rollPrimaryName: primaryDoc.rollPrimaryName, rollPrimaryType: primaryDoc.rollPrimaryType, rollPrimaryImg: primaryDoc.rollPrimaryImg, - rollPrimaryModsSchemaSet: primaryDoc.rollModsSchemaSet, + rollPrimaryModsSchemaSet: primaryDoc.rollPrimaryModsSchemaSet, rollFactors: primaryDoc.rollFactors }; } @@ -1288,8 +1299,7 @@ class BladesRoll extends BladesTargetLink { }); } } - data.rollModsData = this.GetRollModsDataSet(parentRollInst, Object.values(data.rollModsData ?? {})); - return BladesTargetLink.ParseConfigToData(data) as BladesTargetLink.Data & Partial; + return super.ParseConfigToData(data) as BladesTargetLink.Data & Partial; } static override ApplySchemaDefaults( @@ -1342,60 +1352,11 @@ class BladesRoll extends BladesTargetLink { // }); // } - protected static get DefaultRollModSchemaSet(): BladesRollMod.Schema[] { + static get DefaultRollModSchemaSet(): BladesRollMod.Schema[] { /* Subclass overrides determine default roll mods. */ return []; } - static GetRollModsDataSet( - rollInst: BladesRoll, - rollModsSchemaSet: BladesRollMod.Schema[] - ): Record { - const {linkData} = rollInst; - const modLinkConfig = { - targetID: linkData.targetID, - targetKey: "targetKey" in linkData - ? "rollModsData" as TargetKey - : undefined, - targetFlagKey: "targetFlagKey" in linkData - ? "rollModsData" as TargetFlagKey - : undefined, - isScopingById: true - } as BladesTargetLink.Config; - - const compiledModSchemaSets = [...rollModsSchemaSet]; - - // Add roll mods on rollPrimary - if (rollInst.rollPrimary) { - compiledModSchemaSets.push(...rollInst.rollPrimary.rollPrimaryModsSchemaSet - .filter((pSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== pSchema.key)) - ); - } - - // Add roll mods on rollOpposition - if (rollInst.rollOpposition?.rollOppModsSchemaSet) { - compiledModSchemaSets.push(...rollInst.rollOpposition.rollOppModsSchemaSet - .filter((oSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== oSchema.key)) - ); - } - - // Add default roll mods - compiledModSchemaSets.push(...this.DefaultRollModSchemaSet - .filter((dSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== dSchema.key))); - - return Object.fromEntries(compiledModSchemaSets - .map((modSchema) => { - const modData = BladesTargetLink.ParseConfigToData( - { - ...BladesRollMod.ApplySchemaDefaults(modSchema), - ...modLinkConfig - } - ) as BladesRollMod.Data; - return [modData.id, modData]; - }) - ); - } - static GetDieClass(rollType: RollType, rollResult: number | false | RollResult, dieVal: number, dieIndex: number) { switch (rollType) { case RollType.Resistance: { @@ -1607,20 +1568,35 @@ class BladesRoll extends BladesTargetLink { static override BuildLinkConfig(config: BladesRoll.Config): BladesTargetLink.Config { // Prepare partial target link config - const partialLinkConfig = { - target: "target" in config ? config.target : undefined, - targetID: "targetID" in config ? config.targetID : undefined, - targetKey: "targetKey" in config ? config.targetKey : undefined, - targetFlagKey: "targetFlagKey" in config ? config.targetFlagKey : undefined - }; + const partialLinkConfig: Record = {}; + + + if ("targetKey" in config && config.targetKey) { + partialLinkConfig.targetKey = config.targetKey; + } else if ("targetFlagKey" in config && config.targetFlagKey) { + partialLinkConfig.targetFlagKey = config.targetFlagKey; + } - // If neither target nor targetID are provided, set target to rollUser or currentUser - if (!partialLinkConfig.target && !partialLinkConfig.targetID) { - const rollUser = game.users.get(config.rollUserID ?? game.user.id); - if (!rollUser) { - throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollUserID, target, or targetID in the config object."); + if ("target" in config) { + if (U.isDocUUID(config.target)) { + partialLinkConfig.targetID = config.target; + } else if (U.isDocID(config.target)) { + const confTarget = game.actors.get(config.target) + ?? game.items.get(config.target) + ?? game.messages.get(config.target) + ?? game.users.get(config.target); + if (confTarget) { + partialLinkConfig.targetID = confTarget.uuid; + } else { + throw new Error(`[BladesRoll.BuildLinkConfig] No target found with id ${config.target}.`); + } + } else { + partialLinkConfig.targetID = config.target.uuid; } - partialLinkConfig.target = rollUser; + } else if ("targetID" in config) { + partialLinkConfig.targetID = config.targetID; + } else { + throw new Error("[BladesRoll.BuildLinkConfig] You must provide a valid target or targetID in the config object."); } // If neither targetKey nor targetFlagKey are provided, set targetFlagKey to 'rollCollab'. @@ -1629,18 +1605,55 @@ class BladesRoll extends BladesTargetLink { } // Build target link config - return BladesTargetLink.BuildLinkConfig(partialLinkConfig as BladesTargetLink.Config); + if (BladesTargetLink.IsValidConfig(partialLinkConfig)) { + return BladesTargetLink.BuildLinkConfig(partialLinkConfig); + } + throw new Error("[BladesRoll.BuildLinkConfig] Invalid link config."); } /** - * This static method accepts a partial version of the config options required - * to build a BladesRoll instance, sets the requisite flags on the storage - * document, then sends out a socket call to the relevant users to construct - * and display the roll instance. + * Asynchronously creates a new instance of `BladesRoll` or its subclasses. + * + * This generic static method is designed to facilitate the creation of roll instances with + * configurations specific to the type of roll being created. It ensures that the correct type + * of roll instance is returned based on the class it's called on, allowing for a flexible and + * type-safe creation process that can be extended to subclasses of `BladesRoll`. + * + * @template C The class on which `New` is called. This class must extend `BladesRoll` and + * must be constructible with a configuration object that is either a `BladesRoll.Config` or + * a combination of `BladesTargetLink.Data` and a partial `BladesRoll.Schema`. This ensures + * that any subclass of `BladesRoll` can use this method to create instances of itself while + * applying any class-specific configurations or behaviors. * - * @param {BladesRoll.Config} config The configuration object for the new roll. + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance. + * This configuration includes all necessary data to initialize the roll, such as user permissions, + * roll type, and any modifications or additional data required for the roll's operation. + * + * @returns {Promise>} A promise that resolves to an instance of the class + * from which `New` was called. This allows for the dynamic creation of roll instances based + * on the subclass calling the method, ensuring that the returned instance is of the correct type. + * + * @example + * // Assuming `MyCustomRoll` is a subclass of `BladesRoll` + * MyCustomRoll.New(myConfig).then(instance => { + * // `instance` is of type `MyCustomRoll` + * }); + * + * @remarks + * - The method performs several key operations as part of the roll instance creation process: + * 1. Builds link configuration based on the provided config. + * 2. Prepares roll user flag data to determine permissions for different users. + * 3. Validates that a roll type is defined in the config, throwing an error if not. + * 4. Logs the roll data for debugging or auditing purposes. + * 5. Constructs and initializes the roll instance, including setting up roll modifications + * and sending out socket calls to inform all users about the roll. + * - This method is central to the dynamic and flexible creation of roll instances within the + * system, allowing for easy extension and customization in subclasses of `BladesRoll`. */ - static async New(config: BladesRoll.Config) { + static async New< + C extends typeof BladesRoll + &(new (dataOrConfig: BladesRoll.Config | BladesTargetLink.Data & Partial) => BladesRoll) + >(this: C, config: BladesRoll.Config) { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -1657,22 +1670,157 @@ class BladesRoll extends BladesTargetLink { eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll()", {config}); // Construct and initialize the BladesRoll/BladesTargetLink instance - const rollInst = await BladesRoll.Create({...config, ...linkConfig}); - + const rollInst = await this.Create({...config, ...linkConfig}); + if (!rollInst.isInitPromiseResolved) { + eLog.checkLog3("bladesRoll", "BladesRoll Init Promise NOT Resolved After Awaiting Create"); + await U.waitFor(rollInst.initPromise); + } else { + eLog.checkLog3("bladesRoll", "BladesRoll Init Promise Resolved After Awaiting Create"); + } // Send out socket calls to all users to see the roll. rollInst.constructRollCollab_SocketCall(rollInst.linkData); return rollInst; } + + override async initTargetLink() { + this.initialSchema.rollModsData = this.rollModsDataSet; + super.initTargetLink(); + } + + get rollModsSchemaSets(): BladesRollMod.Schema[] { + const compiledModSchemaSets: BladesRollMod.Schema[] = []; + + // Add roll mods on rollPrimary + if (this.rollPrimary) { + compiledModSchemaSets.push(...this.rollPrimary.rollPrimaryModsSchemaSet + .filter((pSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== pSchema.key)) + ); + } + + // Add roll mods on rollOpposition + if (this.rollOpposition?.rollOppModsSchemaSet) { + compiledModSchemaSets.push(...this.rollOpposition.rollOppModsSchemaSet + .filter((oSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== oSchema.key)) + ); + } + + // Add default roll mods + compiledModSchemaSets.push(...(this.constructor as typeof BladesRoll).DefaultRollModSchemaSet + .filter((dSchema) => compiledModSchemaSets.every((mSchema) => mSchema.key !== dSchema.key))); + + // If this is a downtime action roll, add default downtime action roll mods + if (this.rollDowntimeAction) { + compiledModSchemaSets.push({ + key: "HelpFromFriend-positive-roll", + name: "Help From a Friend", + section: RollModSection.position, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" + }); + if (this.rollDowntimeAction !== DowntimeAction.IndulgeVice) { + compiledModSchemaSets.push({ + key: "CanBuyResultLevel-positive-after", + name: "Buying Result Level", + section: RollModSection.after, + base_status: RollModStatus.ForcedOn, + posNeg: "positive", + modType: RollModType.general, + value: 0, + effectKeys: [], + tooltip: "

Buying Result Level

After your roll, you can increase the result level by one for each Coin you spend.

" + }); + } + if (this.rollDowntimeAction === DowntimeAction.AcquireAsset) { + compiledModSchemaSets.push( + { + key: "RepeatPurchase-positive-roll", + name: "Repeat Purchase", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Repeat Purchase Bonus

Add +1d if you have previously acquired this asset or service with a Acquire Asset Downtime activity.

" + }, + { + key: "RestrictedItem-negative-after", + name: "Restricted", + section: RollModSection.after, + base_status: RollModStatus.Hidden, + posNeg: "negative", + modType: RollModType.general, + value: 0, + effectKeys: ["Cost-Heat2"], + tooltip: "

Restricted

Whether contraband goods or dangerous materials, this Acquire Asset Downtime activity will add +2 Heat to your crew.

" + } + ); + } + } + + return compiledModSchemaSets; + } + + get rollModsDataSet(): Record { + const {linkData} = this; + const modLinkConfig = { + targetID: linkData.targetID, + isScopingById: true, + ...("targetKey" in linkData + ? {targetKey: `${this.targetKeyPrefix}.rollModsData` as TargetKey} + : {}), + ...("targetFlagKey" in linkData + ? {targetFlagKey: `${this.targetFlagKeyPrefix}.rollModsData` as TargetFlagKey} + : {}) + }; + return Object.fromEntries(this.rollModsSchemaSets + .map((modSchema) => { + const modData = BladesTargetLink.ParseConfigToData( + { + ...BladesRollMod.ApplySchemaDefaults(modSchema), + ...modLinkConfig + } + ) as BladesRollMod.Data; + return [modData.id, modData]; + }) + ); + } // #endregion // #region SOCKET CALLS & RESPONSES ~ + static GetRollSubClass(linkData: BladesTargetLink.Data) { + const targetLink = new BladesTargetLink(linkData); + switch (targetLink.data.rollType) { + case RollType.Action: return BladesActionRoll; + case RollType.Fortune: { + if (targetLink.data.rollSubType === RollSubType.Engagement) { + return BladesEngagementRoll; + } else if (targetLink.data.rollSubType === RollSubType.Incarceration) { + return BladesIncarcerationRoll; + } + return BladesFortuneRoll; + } + case RollType.Resistance: { + if (targetLink.data.isInlineResistanceRoll) { + return BladesInlineResistanceRoll; + } + return BladesResistanceRoll; + } + case RollType.IndulgeVice: return BladesIndulgeViceRoll; + } + } + constructRollCollab_SocketCall(linkData: BladesTargetLink.Data) { socketlib.system.executeForEveryone("constructRollCollab_SocketCall", linkData); } static constructRollCollab_SocketResponse(linkData: BladesTargetLink.Data) { - const rollInst = new BladesRoll(linkData); + const rollInst = new (this.GetRollSubClass(linkData))(linkData); eLog.checkLog3("rollCollab", "constructRollCollab_SocketResponse()", {params: {linkData}, rollInst}); this.renderRollCollab_SocketResponse(rollInst.id); } @@ -1687,7 +1835,7 @@ class BladesRoll extends BladesTargetLink { async renderRollCollab() { this.prepareRollParticipantData(); - const html = await renderTemplate("systems/eunoblades/templates/rolls/roll-collab.hbs", this.context); + const html = await renderTemplate(this.collabTemplate, this.context); this.elem$.html(html); this.activateListeners(); } @@ -1820,13 +1968,7 @@ class BladesRoll extends BladesTargetLink { } get rollPrimaryDoc(): BladesRoll.PrimaryDoc | undefined { - if (BladesRollPrimary.IsDoc(this.rollPrimaryDoc)) { - return this.rollPrimaryDoc; - } - if (BladesRollPrimary.IsDoc(this.rollPrimary)) { - return this.rollPrimary; - } - return undefined; + return this.rollPrimary.rollPrimaryDoc; } get rollOpposition(): BladesRollOpposition | undefined { @@ -2281,8 +2423,6 @@ class BladesRoll extends BladesTargetLink { this.rollFactorPenaltiesNegated = {}; this.tempGMBoosts = {}; - this.rollMods = Object.values(this.data.rollModsData).map((modData) => new BladesRollMod(modData, this)); - // ESLINT DISABLE: Dev Code. // eslint-disable-next-line @typescript-eslint/no-explicit-any const initReport: Record = {}; @@ -2322,7 +2462,7 @@ class BladesRoll extends BladesTargetLink { watchMod("INITIAL"); /* *** PASS ZERO: ROLLTYPE VALIDATION PASS *** */ - this.rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); + this._rollMods = this.rollMods.filter((rollMod) => rollMod.isValidForRollType()); watchMod("ROLLTYPE VALIDATION"); @@ -2603,12 +2743,12 @@ class BladesRoll extends BladesTargetLink { } get rollMods(): BladesRollMod[] { - if (!this._rollMods) {throw new Error("[get rollMods] No roll mods found!");} + if (!this._rollMods) { + this._rollMods = Object.values(this.data.rollModsData).map((modData) => new BladesRollMod(modData, this)); + } return [...this._rollMods].sort((modA: BladesRollMod, modB: BladesRollMod) => this.compareMods(modA, modB)); } - set rollMods(val: BladesRollMod[]) {this._rollMods = val;} - canResistWithArmor(csq: BladesConsequence) { if (!this.rollPrimary.hasArmor) {return false;} return csq.attribute === AttributeTrait.prowess; @@ -2671,203 +2811,6 @@ class BladesRoll extends BladesTargetLink { return this.getTemplateContext(); } - getFortuneRollModsSchemaSet(): BladesRollMod.Schema[] { - const modsData: BladesRollMod.Schema[] = []; - - if (this.rollSubType === RollSubType.Engagement) { - modsData.push({ - key: "BoldPlan-positive-roll", - name: "Bold Plan", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "ComplexPlan-negative-roll", - name: "Complex Plan", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "ExploitWeakness-positive-roll", - name: "Exploiting a Weakness", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "WellDefended-negative-roll", - name: "Well-Defended", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - modsData.push({ - key: "HelpFromFriend-positive-roll", - name: "Help From a Friend", - section: RollModSection.position, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" - }); - modsData.push({ - key: "EnemyInterference-negative-roll", - name: "Enemy Interference", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }); - } - - return modsData; - } - - getDowntimeActionRollModsSchemaSet(): BladesRollMod.Schema[] { - const modsData: BladesRollMod.Schema[] = []; - modsData.push({ - key: "HelpFromFriend-positive-roll", - name: "Help From a Friend", - section: RollModSection.position, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" - }); - if (this.rollDowntimeAction !== DowntimeAction.IndulgeVice) { - modsData.push({ - key: "CanBuyResultLevel-positive-after", - name: "Buying Result Level", - section: RollModSection.after, - base_status: RollModStatus.ForcedOn, - posNeg: "positive", - modType: RollModType.general, - value: 0, - effectKeys: [], - tooltip: "

Buying Result Level

After your roll, you can increase the result level by one for each Coin you spend.

" - }); - } - switch (this.rollDowntimeAction) { - case DowntimeAction.AcquireAsset: { - modsData.push({ - key: "RepeatPurchase-positive-roll", - name: "Repeat Purchase", - section: RollModSection.roll, - base_status: RollModStatus.ToggledOff, - posNeg: "positive", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Repeat Purchase Bonus

Add +1d if you have previously acquired this asset or service with a Acquire Asset Downtime activity.

" - }); - modsData.push({ - key: "RestrictedItem-negative-after", - name: "Restricted", - section: RollModSection.after, - base_status: RollModStatus.Hidden, - posNeg: "negative", - modType: RollModType.general, - value: 0, - effectKeys: ["Cost-Heat2"], - tooltip: "

Restricted

Whether contraband goods or dangerous materials, this Acquire Asset Downtime activity will add +2 Heat to your crew.

" - }); - break; - } - default: break; - } - - /* - modsData.push({ - id: "--", - name: "", - section: RollModSection, - base_status: RollModStatus, - posNeg: "", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

" - }) -*/ - return modsData; - } - - /** - * Gets the roll modifications data. - * @returns {BladesRollMod.Data[]} The roll modifications data. - */ - protected getRollModsData(): BladesRollMod.Data[] { - const defaultMods: BladesRollMod.Schema[] = []; - if (this.rollType === RollType.Fortune) { - defaultMods.push(...this.getFortuneRollModsSchemaSet()); - } - if (this.rollDowntimeAction) { - defaultMods.push(...this.getDowntimeActionRollModsSchemaSet()); - } - if (this.rollType === RollType.Action) { - if (this.rollPrimary.isWorsePosition) { - defaultMods.push({ - key: "WorsePosition-negative-position", - name: "Worse Position", - section: RollModSection.position, - base_status: RollModStatus.ForcedOn, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Worse Position

A Consequence on a previous roll has worsened your Position.

" - }); - } - } - if ( - this.rollType === RollType.Action - && this.acceptedConsequences.some((csq) => csq.type === ConsequenceType.ReducedEffect) - ) { - defaultMods.push({ - key: "ReducedEffect-negative-effect", - name: "Reduced Effect", - section: RollModSection.effect, - base_status: RollModStatus.ForcedOn, - posNeg: "negative", - modType: RollModType.general, - value: 1, - effectKeys: [], - tooltip: "

Reduced Effect

A Consequence has worsened your Effect.

" - }); - } - return Object.values(BladesRoll.GetRollModsDataSet( - this, - [ - ...defaultMods, - ...this.rollOpposition?.rollOppModsSchemaSet ?? [] - ])); - } - /** * Determines if the user is a game master. * @returns {boolean} Whether the user is a GM. @@ -3378,7 +3321,6 @@ class BladesRoll extends BladesTargetLink { } // #endregion - // #region *** ROLL COLLAB HTML ELEMENT *** get collabTemplate(): string { @@ -4043,8 +3985,25 @@ class BladesActionRoll extends BladesRoll { ]; } - static override async New(config: BladesRoll.Config): Promise { - + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ + static override async New( + this: C, + config: BladesRoll.Config + ): Promise> { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -4053,11 +4012,47 @@ class BladesActionRoll extends BladesRoll { ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesActionRoll. + const rollInst = await super.New(parsedConfig) as InstanceType; return rollInst; } + override get rollModsSchemaSets(): BladesRollMod.Schema[] { + const rollModSchemaSets: BladesRollMod.Schema[] = super.rollModsSchemaSets; + + // Add additional conditional roll mods based on effects of previous consequences. + if (this.rollPrimary.isWorsePosition) { + rollModSchemaSets.push({ + key: "WorsePosition-negative-position", + name: "Worse Position", + section: RollModSection.position, + base_status: RollModStatus.ForcedOn, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Worse Position

A Consequence on a previous roll has worsened your Position.

" + }); + } + if (this.acceptedConsequences.some((csq) => csq.type === ConsequenceType.ReducedEffect)) { + rollModSchemaSets.push({ + key: "ReducedEffect-negative-effect", + name: "Reduced Effect", + section: RollModSection.effect, + base_status: RollModStatus.ForcedOn, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Reduced Effect

A Consequence has worsened your Effect.

" + }); + } + + return rollModSchemaSets; + } + override get collabTemplate(): string { return `systems/eunos-blades/templates/roll/roll-collab-action${game.user.isGM ? "-gm" : ""}.hbs`; } @@ -4084,7 +4079,7 @@ class BladesActionRoll extends BladesRoll { return templateParts.join(""); } - override get rollResult(): RollResult|false { + override get rollResult(): RollResult|number|false { if (!this.isResolved) { return false; } if (this.isCritical) {return RollResult.critical;} if (this.isSuccess) {return RollResult.success;} @@ -4142,8 +4137,25 @@ class BladesResistanceRoll extends BladesRoll { return config as Schema; } - static override async New(config: BladesRoll.Config): Promise { - + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ + static override async New( + this: C, + config: BladesRoll.Config + ): Promise> { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -4152,7 +4164,9 @@ class BladesResistanceRoll extends BladesRoll { ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesResistanceRoll. + const rollInst = await super.New(parsedConfig) as InstanceType; return rollInst; } @@ -4204,8 +4218,25 @@ class BladesFortuneRoll extends BladesRoll { return config as Schema; } - static override async New(config: BladesRoll.Config): Promise { - + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ + static override async New( + this: C, + config: BladesRoll.Config + ): Promise> { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -4214,7 +4245,9 @@ class BladesFortuneRoll extends BladesRoll { ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesFortuneRoll. + const rollInst = await super.New(parsedConfig) as InstanceType; return rollInst; } @@ -4243,8 +4276,25 @@ class BladesIndulgeViceRoll extends BladesRoll { return config as Schema; } - static override async New(config: BladesRoll.Config): Promise { - + /** + * Asynchronously creates a new instance of this subclass of `BladesRoll`. + * + * Overrides the `New` static method from `BladesRoll`, applying subclass-specific configurations + * to the instance creation process. It ensures that the returned instance is correctly typed + * and configured for this subclass. + * + * @param {BladesRoll.Config} config The configuration object for creating a new roll instance, + * extended with any subclass-specific configurations or requirements. + * + * @returns {Promise>} A promise that resolves to an instance of this subclass. + * + * @see {@link BladesRoll.New} for the base method's functionality and the generic creation process + * for roll instances. + */ + static override async New( + this: C, + config: BladesRoll.Config + ): Promise> { // Build link config const linkConfig = this.BuildLinkConfig(config); @@ -4253,7 +4303,9 @@ class BladesIndulgeViceRoll extends BladesRoll { ...linkConfig }; - const rollInst = await this.Create(parsedConfig); + // Call super.New and cast the result appropriately. + // The cast to InstanceType is safe here because C is constrained to typeof BladesIndulgeViceRoll. + const rollInst = await super.New(parsedConfig) as InstanceType; return rollInst; } @@ -4281,6 +4333,77 @@ class BladesIndulgeViceRoll extends BladesRoll { class BladesEngagementRoll extends BladesFortuneRoll { + static override get DefaultRollModSchemaSet(): BladesRollMod.Schema[] { + return [ + { + key: "BoldPlan-positive-roll", + name: "Bold Plan", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "ComplexPlan-negative-roll", + name: "Complex Plan", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "ExploitWeakness-positive-roll", + name: "Exploiting a Weakness", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "WellDefended-negative-roll", + name: "Well-Defended", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + }, + { + key: "HelpFromFriend-positive-roll", + name: "Help From a Friend", + section: RollModSection.position, + base_status: RollModStatus.ToggledOff, + posNeg: "positive", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

Help From a Friend

Add +1d if you enlist the help of a friend or contact.

" + }, + { + key: "EnemyInterference-negative-roll", + name: "Enemy Interference", + section: RollModSection.roll, + base_status: RollModStatus.ToggledOff, + posNeg: "negative", + modType: RollModType.general, + value: 1, + effectKeys: [], + tooltip: "

" + } + ]; + } + override get chatTemplate(): string { return "systems/eunos-blades/templates/chat/roll-result/fortune-engagement.hbs"; } diff --git a/ts/classes/BladesRollTemp.ts b/ts/classes/BladesRollTemp.ts index 55c2314c..9546571d 100644 --- a/ts/classes/BladesRollTemp.ts +++ b/ts/classes/BladesRollTemp.ts @@ -705,7 +705,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { rollPrimaryName: rollPrimary.rollPrimaryName, rollPrimaryType: rollPrimary.rollPrimaryType, rollPrimaryImg: rollPrimary.rollPrimaryImg, - rollPrimaryModsSchemaSet: rollPrimary.rollModsSchemaSet, + rollPrimaryModsSchemaSet: rollPrimary.rollPrimaryModsSchemaSet, rollFactors: rollPrimary.rollFactors }; } @@ -727,7 +727,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryData { get rollPrimaryName(): string { return this.rollPrimaryDoc.rollPrimaryName; } get rollPrimaryType(): BladesRoll.PrimaryType { return this.rollPrimaryDoc.rollPrimaryType; } get rollPrimaryImg(): string { return this.rollPrimaryDoc.rollPrimaryImg; } - get rollPrimaryModsSchemaSet() { return this.rollPrimaryDoc.rollModsSchemaSet; } + get rollPrimaryModsSchemaSet() { return this.rollPrimaryDoc.rollPrimaryModsSchemaSet; } get rollFactors() { return this.rollPrimaryDoc.rollFactors; } get isWorsePosition(): boolean { diff --git a/ts/classes/BladesTargetLink.ts b/ts/classes/BladesTargetLink.ts index 18e55ed5..2cb52ebf 100644 --- a/ts/classes/BladesTargetLink.ts +++ b/ts/classes/BladesTargetLink.ts @@ -9,20 +9,23 @@ class BladesTargetLink { // #region STATIC METHODS ~ - static readonly ValidTargetClasses = [ - BladesActor, - BladesItem, - BladesChat, - User - ] as const; + static get ValidTargetClasses() { + return [ + BladesActor, + BladesItem, + BladesChat, + User + ] as const; + } static IsValidConfig(ref: unknown): ref is BladesTargetLink.Config { return U.isSimpleObj(ref) - && (U.isDocID(ref.target) - || U.isDocUUID(ref.target) - || U.isDocID(ref.targetID) - || U.isDocUUID(ref.targetID) - || this.ValidTargetClasses.some((cls) => ref.target instanceof cls) + && ( + U.isDocID(ref.target) + || U.isDocUUID(ref.target) + || U.isDocID(ref.targetID) + || U.isDocUUID(ref.targetID) + || this.ValidTargetClasses.some((cls) => ref.target instanceof cls) ) && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); @@ -33,8 +36,8 @@ class BladesTargetLink { && U.isDocID(ref.id) && U.isDocUUID(ref.targetID) && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) - && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)) - && (typeof ref.isScopingById === "boolean"); + && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); + // && (typeof ref.isScopingById === "boolean"); } static #ParseChildLinkData( @@ -146,14 +149,14 @@ class BladesTargetLink { ...partialSchema, targetID: U.parseDocRefToUUID("target" in fullConfig ? fullConfig.target : fullConfig.targetID), targetKey: fullConfig.targetKey - }); + }, parentLinkData); } return this.ParseConfigToData({ id: randomID() as IDString, ...partialSchema, targetID: U.parseDocRefToUUID("target" in fullConfig ? fullConfig.target : fullConfig.targetID), targetFlagKey: fullConfig.targetFlagKey - }); + }, parentLinkData); } /** @@ -173,7 +176,7 @@ class BladesTargetLink { parentLinkData?: BladesTargetLink.Data ): BladesTargetLink.Data & Partial { if (this.IsValidData(data)) { return this.#ParseChildLinkData(data, parentLinkData); } - return this.#ParseConfigToData(data); + return this.#ParseConfigToData(data, parentLinkData); } static PartitionSchemaData( @@ -235,7 +238,7 @@ class BladesTargetLink { }; } - static #ApplySchemaDefaults( + static _ApplySchemaDefaults( this: BladesTargetLink.StaticThisContext, schemaData: Partial ): Schema { @@ -271,17 +274,17 @@ class BladesTargetLink { * * @throws {Error} - Throws an error if the initialization of the target link fails. */ - static async Create, Schema>( - this: new ( - config: BladesTargetLink.Config & Partial, - parentLinkData?: BladesTargetLink.Data & Schema - ) => C, + static async Create, + parentLinkData?: BladesTargetLink.Data & Schema + ) => BladesTargetLink, Schema>( + this: C, config: BladesTargetLink.Config & Partial, parentLinkData?: BladesTargetLink.Data & Schema - ): Promise { + ): Promise> { const tLink = new this(config, parentLinkData); await tLink.initTargetLink(); - return tLink; + return tLink as InstanceType; } // #endregion @@ -400,10 +403,12 @@ class BladesTargetLink { let linkData: BladesTargetLink.Data; let schema: Schema; + const subclassConstructor = this.constructor as typeof BladesTargetLink; + // First, we construct the link data from the config or data object. - if (BladesTargetLink.IsValidData(dataOrConfig)) { + if (subclassConstructor.IsValidData(dataOrConfig)) { // If a simple link data object was provided, acquire the schema from the source document - ({linkData} = BladesTargetLink.PartitionSchemaData(dataOrConfig)); + ({linkData} = subclassConstructor.PartitionSchemaData(dataOrConfig)); const target = fromUuidSync(linkData.targetID); if (!target) { throw new Error(`[new BladesTargetLink()] Unable to resolve target from uuid '${linkData.targetID}'`); @@ -426,10 +431,10 @@ class BladesTargetLink { // Next we separate the linkData and the schemaData from the parsedData object. let partialSchema: Partial; - ({linkData, partialSchema} = BladesTargetLink.PartitionSchemaData(parsedData)); + ({linkData, partialSchema} = subclassConstructor.PartitionSchemaData(parsedData)); // And apply any schema defaults to the provided schema data. - schema = BladesTargetLink.#ApplySchemaDefaults(partialSchema); + schema = subclassConstructor._ApplySchemaDefaults(partialSchema); } this._id = linkData.id; @@ -529,7 +534,7 @@ class BladesTargetLink { }).catch(reject); } else if (this.targetFlagKeyPrefix) { const updateData = mergeObject( - this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) as Partial, + (this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) ?? {}) as Partial, data ); (this.target as BladesItem).setFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix, updateData).then(() => { @@ -540,6 +545,8 @@ class BladesTargetLink { reject(); } }); + + return this.initPromise; } async #updateTargetViaMerge(updateData: Record, waitFor?: Promise|gsapAnim) { @@ -550,7 +557,7 @@ class BladesTargetLink { return this.target.update(updateData, {render: false}); } else if (this.targetFlagKeyPrefix) { // We must retrieve the existing flag data, flattenObject it, then merge it with updateData - const existingFlagData = this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix); + const existingFlagData = this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) ?? {}; const flattenedFlagData = flattenObject(existingFlagData as BladesTargetLink.Data & Schema); const mergedFlagData = mergeObject(flattenedFlagData, updateData); diff --git a/ts/core/debug.ts b/ts/core/debug.ts new file mode 100644 index 00000000..7bd66e38 --- /dev/null +++ b/ts/core/debug.ts @@ -0,0 +1,123 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +// #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ +import C, {BladesActorType, BladesItemType, RollPermissions, ActionTrait, DowntimeAction, RollSubType, RollPhase, Effect, ClockColor, ClockKeyDisplayMode, AttributeTrait, RollType, ConsequenceType, Position, RollResult} from "../core/constants"; +import registerSettings, {initTinyMCEStyles, initCanvasStyles, initDOMStyles} from "../core/settings"; +import {registerHandlebarHelpers, preloadHandlebarsTemplates} from "../core/helpers"; +import BladesChat from "../classes/BladesChat"; +import U from "../core/utilities"; +import logger from "../core/logger"; +import G, {Initialize as GsapInitialize} from "../core/gsap"; +import BladesClockKey from "../classes/BladesClocks"; +import BladesDirector from "../classes/BladesDirector"; +import BladesConsequence from "../classes/BladesConsequence"; +import BladesScene from "../classes/BladesScene"; + + +import BladesActorProxy, { + BladesActor, + BladesPC, + BladesCrew, + BladesNPC, + BladesFaction +} from "../documents/BladesActorProxy"; +import BladesItemProxy, { + BladesItem, + BladesClockKeeper, + BladesGMTracker, + BladesLocation, + BladesScore, + BladesProject +} from "../documents/BladesItemProxy"; + +import BladesItemSheet from "../sheets/item/BladesItemSheet"; +import BladesPCSheet from "../sheets/actor/BladesPCSheet"; +import BladesCrewSheet from "../sheets/actor/BladesCrewSheet"; +import BladesNPCSheet from "../sheets/actor/BladesNPCSheet"; +import BladesFactionSheet from "../sheets/actor/BladesFactionSheet"; +import BladesRoll, {BladesRollMod, BladesRollPrimary, BladesRollOpposition, BladesRollParticipant, BladesActionRoll, BladesEngagementRoll, BladesFortuneRoll, BladesIncarcerationRoll, BladesIndulgeViceRoll, BladesInlineResistanceRoll, BladesResistanceRoll} from "../classes/BladesRoll"; + +import BladesDialog from "../classes/BladesDialog"; +import BladesAI, {AGENTS, AIAssistant} from "../core/ai"; +import BladesActiveEffect from "../documents/BladesActiveEffect"; +import BladesGMTrackerSheet from "../sheets/item/BladesGMTrackerSheet"; +import BladesClockKeeperSheet from "../sheets/item/BladesClockKeeperSheet"; +// #endregion ▮▮▮▮[IMPORTS]▮▮▮▮ + +class BladesDebug { + + static async GetSampleSchemas() { + // Documents + const SAMPLE_USER_NAME = "Alistair" as const; + const SAMPLE_PC_NAME = "Alistair" as const; + const SAMPLE_NPC_NAME = "Setarra" as const; + + const sampleUser = game.users.getName(SAMPLE_USER_NAME); + if (!sampleUser) { + throw new Error(`Sample user with name "${SAMPLE_USER_NAME}" not found.`); + } + const samplePC = game.actors.getName(SAMPLE_PC_NAME); + if (!BladesPC.IsType(samplePC)) { + throw new Error(`Sample BladesPC with name "${SAMPLE_PC_NAME}" not found.`); + } + const sampleNPC: BladesActor|BladesNPC|undefined = game.actors.getName(SAMPLE_NPC_NAME); + if (!BladesNPC.IsType(sampleNPC)) { + throw new Error(`Sample BladesNPC with name "${SAMPLE_NPC_NAME}" not found or is not a valid BladesNPC.`); + } + + // BladesActionRoll + const BladesActionRoll_Schema: BladesRoll.Schema = { + rollType: RollType.Action, + // rollSubType: RollSubType.GatherInfo, + // rollPrompt: "Gathering Information", + rollTrait: ActionTrait.skirmish, + // rollUserID: sampleUser.id, + // rollDowntimeAction: DowntimeAction.AcquireAsset, + // rollClockKey: U.getLast(game.eunoblades.ClockKeys.contents)?.id, + + rollPrimaryData: BladesRollPrimary.GetDataFromDoc(samplePC), + // rollOppData: sampleNPC, + // rollParticipantData: {}, + + // consequenceData: {}, + // resistanceData: { + // consequence: {} + // }, + + rollModsData: {}, + + rollPositionInitial: Position.risky, + rollEffectInitial: Effect.standard, + rollPosEffectTrade: false, + rollPhase: RollPhase.Collaboration, + + GMBoosts: {}, + GMOppBoosts: {}, + GMOverrides: {}, + rollFactorToggles: { + source: {}, + opposition: {} + }, + + userPermissions: { + [sampleUser.id]: RollPermissions.Primary + } + + // rollPositionFinal: Position.risky, + // rollEffectFinal: Effect.standard, + // rollResult: RollResult.success, + // rollResultDelta: 0, + // rollResultFinal: RollResult.success, + // rollTraitVerb: "skirmishes", + // rollTraitPastVerb: "skirmished", + // finalDiceData: [], + + // isInlineResistanceRoll: false + }; + + return { + BladesActionRoll_Schema + }; + } +} + +export default BladesDebug; diff --git a/ts/documents/actors/BladesCrew.ts b/ts/documents/actors/BladesCrew.ts index 8a1fd3a9..d4a2818e 100644 --- a/ts/documents/actors/BladesCrew.ts +++ b/ts/documents/actors/BladesCrew.ts @@ -22,7 +22,12 @@ class BladesCrew extends BladesActor implements BladesActorSubClass.Crew, // #endregion // #region Static Overrides: Create ~ - static override IsType(doc: unknown): doc is BladesActorOfType { + // static override IsType(doc: unknown): doc is BladesActorOfType { + // return super.IsType(doc, BladesActorType.crew); + // } + static override IsType( + doc: unknown + ): doc is BladesCrew & BladesActorOfType { return super.IsType(doc, BladesActorType.crew); } @@ -151,8 +156,6 @@ class BladesCrew extends BladesActor implements BladesActorSubClass.Crew, // #region BladesRoll Implementation - get rollPrimaryModsSchemaSet(): BladesRollMod.Schema[] { return this.rollModsSchemaSet; } - // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() {return this.id;} diff --git a/ts/documents/actors/BladesFaction.ts b/ts/documents/actors/BladesFaction.ts index 93975b8c..cbbe5e3b 100644 --- a/ts/documents/actors/BladesFaction.ts +++ b/ts/documents/actors/BladesFaction.ts @@ -22,6 +22,12 @@ class BladesFaction extends BladesActor implements BladesActorSubClass.Faction, ); } + static override IsType( + doc: unknown + ): doc is BladesFaction & BladesActorOfType { + return super.IsType(doc, BladesActorType.faction); + } + // #region BladesRoll Implementation // #region BladesRoll.OppositionDoc Implementation diff --git a/ts/documents/actors/BladesNPC.ts b/ts/documents/actors/BladesNPC.ts index c386f056..f54035c9 100644 --- a/ts/documents/actors/BladesNPC.ts +++ b/ts/documents/actors/BladesNPC.ts @@ -16,6 +16,12 @@ class BladesNPC extends BladesActor implements BladesActorSubClass.NPC, } // #endregion + static override IsType( + doc: unknown + ): doc is BladesNPC & BladesActorOfType { + return super.IsType(doc, BladesActorType.npc); + } + // #region BladesRoll Implementation override get rollFactors(): Partial> { diff --git a/ts/documents/actors/BladesPC.ts b/ts/documents/actors/BladesPC.ts index 5f3ea6dc..3bc43e66 100644 --- a/ts/documents/actors/BladesPC.ts +++ b/ts/documents/actors/BladesPC.ts @@ -35,7 +35,13 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, // #endregion // #region Static Overrides: Create, get All ~ - static override IsType(doc: unknown): doc is BladesActorOfType { + // static override IsType(doc: unknown): doc is BladesActorOfType { + // return super.IsType(doc, BladesActorType.pc); + // } + + static override IsType( + doc: unknown + ): doc is BladesPC & BladesActorOfType { return super.IsType(doc, BladesActorType.pc); } @@ -442,9 +448,9 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, // #endregion // #region BladesRoll.PrimaryDoc Implementation - get rollPrimaryModsSchemaSet(): BladesRollMod.Schema[] { + override get rollPrimaryModsSchemaSet(): BladesRollMod.Schema[] { - const rollModsData = this.rollModsSchemaSet; + const rollModsSchemaSet = super.rollPrimaryModsSchemaSet; // Add roll mods from harm [ @@ -455,7 +461,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, .find((harmData) => effectPat.test(harmData.effect)) ?? {}; const harmString = U.objCompact([harmConditionOne, harmConditionTwo === "" ? null : harmConditionTwo]).join(" & "); if (harmString.length > 0) { - rollModsData.push({ + rollModsSchemaSet.push({ key: `Harm-negative-${effectCat}`, name: harmString, section: effectCat, @@ -476,7 +482,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, const {one: harmCondition} = Object.values(this.system.harm) .find((harmData) => /Need Help/.test(harmData.effect)) ?? {}; if (harmCondition && harmCondition.trim() !== "") { - rollModsData.push({ + rollModsSchemaSet.push({ key: "Push-negative-roll", name: "PUSH", sideString: harmCondition.trim(), @@ -494,7 +500,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, }); } - return rollModsData; + return rollModsSchemaSet; }