diff --git a/templates/chat/roll-result/action-acquireasset.hbs b/templates/chat/roll-result/action-acquireasset.hbs index 1459292f..27376d40 100644 --- a/templates/chat/roll-result/action-acquireasset.hbs +++ b/templates/chat/roll-result/action-acquireasset.hbs @@ -2,7 +2,7 @@
+
{{rollType}} Roll
@@ -327,7 +327,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/templates/roll/partials/roll-collab-fortune-gm.hbs b/templates/roll/partials/roll-collab-fortune-gm.hbs index 8a33d576..d84df68a 100644 --- a/templates/roll/partials/roll-collab-fortune-gm.hbs +++ b/templates/roll/partials/roll-collab-fortune-gm.hbs @@ -1,4 +1,4 @@ -
+
{{rollType}} Roll
@@ -190,7 +190,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/templates/roll/partials/roll-collab-fortune.hbs b/templates/roll/partials/roll-collab-fortune.hbs index 123c6dda..4e49c41e 100644 --- a/templates/roll/partials/roll-collab-fortune.hbs +++ b/templates/roll/partials/roll-collab-fortune.hbs @@ -1,4 +1,4 @@ -
+
{{rollType}} Roll
@@ -323,7 +323,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/templates/roll/partials/roll-collab-indulgevice-gm.hbs b/templates/roll/partials/roll-collab-indulgevice-gm.hbs index 8a33d576..d84df68a 100644 --- a/templates/roll/partials/roll-collab-indulgevice-gm.hbs +++ b/templates/roll/partials/roll-collab-indulgevice-gm.hbs @@ -1,4 +1,4 @@ -
+
{{rollType}} Roll
@@ -190,7 +190,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/templates/roll/partials/roll-collab-indulgevice.hbs b/templates/roll/partials/roll-collab-indulgevice.hbs index 123c6dda..4e49c41e 100644 --- a/templates/roll/partials/roll-collab-indulgevice.hbs +++ b/templates/roll/partials/roll-collab-indulgevice.hbs @@ -1,4 +1,4 @@ -
+
{{rollType}} Roll
@@ -323,7 +323,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/templates/roll/partials/roll-collab-resistance.hbs b/templates/roll/partials/roll-collab-resistance.hbs index 1b1e63bb..51f44fb5 100644 --- a/templates/roll/partials/roll-collab-resistance.hbs +++ b/templates/roll/partials/roll-collab-resistance.hbs @@ -156,7 +156,7 @@
Waiting For GM ...
{{else if (test rollPhase "==" "AwaitingRoll")}} - + {{/if}} {{#if costData}} diff --git a/ts/@types/blades-general-types.d.ts b/ts/@types/blades-general-types.d.ts index 46a9e1d9..774d5674 100644 --- a/ts/@types/blades-general-types.d.ts +++ b/ts/@types/blades-general-types.d.ts @@ -2,6 +2,9 @@ import {AttributeTrait, ActionTrait, District} from "../core/constants"; import BladesItem from "../BladesItem"; import BladesActor from "../BladesActor"; import BladesChat from "../classes/BladesChat"; +import BladesClockKey, {BladesClock} from "../classes/BladesClocks"; +import BladesConsequence from "../classes/BladesConsequence"; +import BladesRoll, {BladesRollMod} from "../classes/BladesRoll"; import {gsap} from "gsap/all"; @@ -132,10 +135,13 @@ declare global { type gsapAnim = gsap.core.Tween | gsap.core.Timeline; // Represents a generic Blades document - type BladesDoc = BladesActor | BladesItem | BladesChat; + type BladesDoc = BladesActor | BladesItem; // Represents any Blades document sheet - type BladesSheet = BladesActorSheet | BladesItemSheet | BladesRoll; + type BladesSheet = BladesActorSheet | BladesItemSheet; + + // Represents any document that can be the target of a BladesTargetLink subclass. + type BladesLinkDoc = BladesDoc | BladesChat | User; // Represents a reference to a Blades document type DocRef = string | BladesDoc; diff --git a/ts/@types/blades-roll.d.ts b/ts/@types/blades-roll.d.ts index 24e7a86e..516f9325 100644 --- a/ts/@types/blades-roll.d.ts +++ b/ts/@types/blades-roll.d.ts @@ -9,6 +9,8 @@ declare global { namespace BladesRollMod { + // export type Value = string|number|string[]; + export type Schema = { key: string, name: string, @@ -43,22 +45,22 @@ declare global { namespace BladesRoll { - export interface Config extends BladesTargetLink.Config { - rollType: RollType, + export interface Config extends Partial { + rollType?: RollType, rollSubType?: RollSubType, - rollUserID: IDString, + rollUserID?: IDString, rollTrait?: RollTrait, rollDowntimeAction?: DowntimeAction, rollClockKey?: IDString|BladesClockKey, rollClockKeyID?: IDString, - rollPrimaryData: PrimaryDocData; + rollPrimaryData?: PrimaryDocData; rollOppData?: OppositionDocData; - rollParticipantData?: RollParticipantData, + rollParticipantData?: RollParticipantDataSet, participantRollTo?: string, resistanceRollTo?: { - rollID: string, + id: string, userID: string, consequenceID: string }, @@ -74,7 +76,9 @@ declare global { >>, resistanceData?: { consequence: BladesConsequence.Data - } + }, + + userPermissions?: Record } export interface Schema extends Omit { @@ -99,7 +103,7 @@ declare global { Partial> >, - userPermissions: Record, + userPermissions: Record, template?: string, finalPosition?: Position, @@ -319,7 +323,7 @@ declare global { export type ParticipantConstructorData = ParticipantSectionData & Partial; - export interface RollParticipantData { + export interface RollParticipantDataSet { [RollModSection.roll]?: { Assist?: ParticipantDocData & ParticipantSectionData, Group_1?: ParticipantDocData & ParticipantSectionData, diff --git a/ts/@types/blades-target-link.d.ts b/ts/@types/blades-target-link.d.ts index c08e0f6e..0feff9a0 100644 --- a/ts/@types/blades-target-link.d.ts +++ b/ts/@types/blades-target-link.d.ts @@ -5,6 +5,7 @@ import BladesNPC from "../documents/actors/BladesNPC"; import BladesFaction from "../documents/actors/BladesFaction"; import BladesCrew from "../documents/actors/BladesCrew"; import BladesTargetLink from "../classes/BladesTargetLink"; +import BladesChat from "../classes/BladesChat"; declare global { @@ -13,13 +14,11 @@ declare global { namespace BladesTargetLink { - export type UnknownSchema = Record; - - export type StaticThisContext = typeof BladesTargetLink + export type StaticThisContext = typeof BladesTargetLink & (new (data: Data & Schema) => BladesTargetLink & Subclass); export type Config = { - target?: IDString|UUIDString|BladesDoc, + target?: IDString|UUIDString|BladesDoc|BladesChat|User, targetID?: IDString|UUIDString, targetKey?: TargetKey, targetFlagKey?: TargetFlagKey @@ -33,10 +32,10 @@ declare global { } export type Instance = Data & { - target: BladesDoc + target: BladesDoc|BladesChat|User } - export interface Subclass extends Instance { + export interface Subclass extends Instance { data: Data & Schema, diff --git a/ts/@types/index.d.ts b/ts/@types/index.d.ts index 0ed98665..78f5616e 100644 --- a/ts/@types/index.d.ts +++ b/ts/@types/index.d.ts @@ -59,6 +59,7 @@ declare global { ClockKeeper: BladesClockKeeper, Director: BladesDirector, Tracker: BladesGMTracker, + Rolls: Collection, ClockKeys: Collection, Consequences: Collection, Tooltips: WeakMap @@ -97,14 +98,6 @@ declare global { } interface LenientGlobalVariableTypes { game: never } - interface FlagConfig { - User: { - [C.SYSTEM_ID]?: { - rollCollab?: BladesRoll.FlagData - } - }; - } - // GreenSock Accessor Object declare const gsap: gsap; type BladesTweenTarget = JQuery | gsap.TweenTarget; diff --git a/ts/blades.ts b/ts/blades.ts index 88187a15..1f571ee6 100644 --- a/ts/blades.ts +++ b/ts/blades.ts @@ -585,6 +585,7 @@ class GlobalGetter { Hooks.once("init", async () => { // Initialize Game object game.eunoblades = { + Rolls: new Collection(), ClockKeys: new Collection(), Consequences: new Collection(), Director: BladesDirector.getInstance(), diff --git a/ts/classes/BladesChat.ts b/ts/classes/BladesChat.ts index 882ea279..aa311dd1 100644 --- a/ts/classes/BladesChat.ts +++ b/ts/classes/BladesChat.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-unused-vars */ // #region IMPORTS ~ import {ApplyTooltipAnimations, ApplyConsequenceAnimations} from "../core/gsap"; -import {Position, Effect, RollResult} from "../core/constants"; +import C, {Position, Effect, RollResult} from "../core/constants"; import BladesRoll from "./BladesRoll"; import BladesConsequence from "./BladesConsequence"; @@ -14,36 +14,24 @@ namespace BladesChat { export interface Flags { template: string, - rollData?: BladesRoll.FlagData + rollData?: BladesRoll.Schema } } class BladesChat extends ChatMessage { - // static override defineSchema() { - // return Object.assign(super.defineSchema(), { - // csqData: new foundry.data.fields.ObjectField() - // }); - // } - static Initialize() { - // let lastMessageID: string|false = Array.from(game.messages).pop()?.id ?? ""; + Hooks.on("renderChatMessage", (msg: BladesChat, html: JQuery) => { ApplyTooltipAnimations(html); - if (msg.flags.rollData) { + const {rollData} = msg.flagData; + if (rollData) { ApplyConsequenceAnimations(html); BladesConsequence.ApplyChatListeners(html); } html.addClass("display-ok"); - // if (lastMessageID && _msg.id === lastMessageID) { - // setTimeout(() => { - // $(document).find("#chat .chat-message:not([class*='-roll'])") - // .remove(); - // }, 500); - // lastMessageID = false; - // } - }); + return loadTemplates([ "systems/eunos-blades/templates/chat/roll-result/action.hbs", "systems/eunos-blades/templates/chat/roll-result/action-clock.hbs", @@ -66,23 +54,29 @@ class BladesChat extends ChatMessage { static async ConstructRollOutput(rollInst: BladesRoll): Promise { const template = rollInst.resultChatTemplate; - const rollFlags = { - ...rollInst.flagData, + const rollData = { + ...rollInst.data, rollTraitVerb: rollInst.rollTraitVerb ?? "", rollTraitPastVerb: rollInst.rollTraitPastVerb ?? rollInst.rollTraitVerb ?? "" }; return await BladesChat.create({ speaker: rollInst.getSpeaker(BladesChat.getSpeaker()), - content: await renderTemplate(template, rollFlags), + content: await renderTemplate(template, rollData), type: CONST.CHAT_MESSAGE_TYPES.ROLL, flags: { - template, - rollData: rollFlags + "eunos-blades": { + template, + rollData + } } }) as BladesChat; } + get flagData() { + return this.flags["eunos-blades"]; + } + get allRollConsequences(): Record implements } as Schema; } - static override async Create( + static override async Create( config: BladesClockKey.Config & Partial, clockConfigs: Array> = [] ) { @@ -132,7 +132,7 @@ class BladesClockKey extends BladesTargetLink implements // Update the clock key with the new clock data await clockKey.updateTarget("clocksData", clocksData, ClockKeyUpdateAction.RenderAll); - return clockKey as BladesClockKey & BladesTargetLink.Subclass; + return clockKey as BladesClockKey & BladesTargetLink; } static GetFromElement(elem: HTMLElement | JQuery): BladesClockKey | undefined { @@ -207,16 +207,16 @@ class BladesClockKey extends BladesTargetLink implements } get isClockKeeperKey(): boolean { - return this.target.type === BladesItemType.clock_keeper; + return this.target instanceof BladesClockKeeper; } get isFactionKey(): boolean { - return this.target.type === BladesActorType.faction; + return this.target instanceof BladesFaction; } get isProjectKey(): boolean { - return this.target.type === BladesItemType.project; + return this.target instanceof BladesProject; } get isScoreKey(): boolean { - return this.target.type === BladesItemType.score; + return this.target instanceof BladesScore; } get visibleClocks(): BladesClock[] { return this.clocks.filter((clock) => clock.isVisible); @@ -385,7 +385,7 @@ class BladesClockKey extends BladesTargetLink implements config.index = indexOverride ?? this.size; // Parse config to full data object - const cData = BladesClock.ParseConfig(config as BladesClock.Config); + const cData = BladesClock.ParseConfig(config as BladesClock.Config); return cData; } @@ -977,8 +977,8 @@ class BladesClockKey extends BladesTargetLink implements this.postUpdateRender(postUpdateAction); } - override async updateTargetData( - val: T | null, + override async updateTargetData( + val: unknown, postUpdateAction: ClockKeyUpdateAction|boolean = false ) { await super.updateTargetData(val, true); @@ -1654,8 +1654,8 @@ class BladesClock extends BladesTargetLink implements Blades this.postUpdateRender(postUpdateAction); } - override async updateTargetData( - val: T | null, + override async updateTargetData( + val: Partial | null, postUpdateAction: ClockKeyUpdateAction|boolean = false ) { await super.updateTargetData(val, true); diff --git a/ts/classes/BladesConsequence.ts b/ts/classes/BladesConsequence.ts index fff009b8..621434b9 100644 --- a/ts/classes/BladesConsequence.ts +++ b/ts/classes/BladesConsequence.ts @@ -149,7 +149,7 @@ class BladesConsequence extends BladesTargetLink { return new BladesConsequence({ ...csqData, type: type as ConsequenceType, - rollID: msg.flags.rollID as IDString, + id: msg.flags.id as IDString, userID: msg.flags.rollUserID, primaryID: msg.flags.rollPrimaryData.rollPrimaryID as IDString, primaryType: msg.flags.rollPrimaryData.rollPrimaryType, @@ -164,7 +164,7 @@ class BladesConsequence extends BladesTargetLink { chatID: IDString; - rollID: IDString; + id: IDString; userID: IDString; @@ -203,7 +203,7 @@ class BladesConsequence extends BladesTargetLink { id: this.resistTo.id, chatID: this.chatMessage.id as IDString, userID: this.user.id as IDString, - rollID: this.rollID, + id: this.id, primaryID: this.primaryID, primaryType: this.primaryType, position: this.position, @@ -236,7 +236,7 @@ class BladesConsequence extends BladesTargetLink { const {id, targetID, targetKey, targetFlagKey} = {...parentCsq ?? {}, ...data} as BladesTargetLink.Data; super({id, targetID, targetKey, targetFlagKey}); const { - chatID, userID, rollID, primaryID, primaryType, + chatID, userID, id, primaryID, primaryType, position, effect, result } = {...parentCsq ?? {}, ...data} as BladesConsequence.Data; @@ -251,7 +251,7 @@ class BladesConsequence extends BladesTargetLink { eLog.checkLog3("bladesConsequence", "[new BladesConsequence]", { parentCsq, - id, chatID, userID, rollID, primaryID, primaryType, + id, chatID, userID, id, primaryID, primaryType, name, type, position, effect, result, @@ -262,7 +262,7 @@ class BladesConsequence extends BladesTargetLink { }); if (typeof id !== "string") { throw new Error("[new BladesConsequence] Missing 'id' in constructor data object."); } - if (typeof rollID !== "string") { throw new Error("[new BladesConsequence] Missing 'rollID' in constructor data object."); } + if (typeof id !== "string") { throw new Error("[new BladesConsequence] Missing 'id' in constructor data object."); } if (typeof chatID !== "string") { throw new Error("[new BladesConsequence] Missing 'chatID' in constructor data object."); } const chatMessage = game.messages.get(chatID); if (!(chatMessage instanceof BladesChat)) { throw new Error(`[new BladesConsequence] No chat message with id '${chatID}' found.`); } @@ -281,7 +281,7 @@ class BladesConsequence extends BladesTargetLink { if (!(typeof result === "string" && [RollResult.partial, RollResult.fail].includes(result))) { throw new Error("[new BladesConsequence] Missing 'result' in constructor data object."); } this._id = id; - this.rollID = rollID; + this.id = id; this.chatMessage = chatMessage; this.chatID = chatMessage.id; this.user = user; @@ -478,7 +478,7 @@ class BladesConsequence extends BladesTargetLink { // get rollFlagData(): BladesRoll.FlagData { // // Get rollPrimaryData from archived roll flags on user document. // let rollFlagData = this._user.getFlag(C.SYSTEM_ID, "rollCollab") as BladesRoll.FlagData; - // if (rollFlagData.rollID !== this._rollID) { + // if (rollFlagData.id !== this._rollID) { // rollFlagData = this._user.getFlag(C.SYSTEM_ID, `rollCollabArchive.${this._rollID}`) as BladesRoll.FlagData; // } // if (!rollFlagData) { throw new Error(`Unable to locate flag data for roll id '${this._rollID}'`); } diff --git a/ts/classes/BladesRoll.ts b/ts/classes/BladesRoll.ts index f702f3f3..7eaa5ab1 100644 --- a/ts/classes/BladesRoll.ts +++ b/ts/classes/BladesRoll.ts @@ -61,8 +61,6 @@ function isModStatus(str: unknown): str is RollModStatus { return typeof str === "string" && str in RollModStatus; } - - /** * Checks if the given section can contain BladesRollParticipant documents. * @param {RollModSection} section @@ -87,15 +85,16 @@ function isParticipantSubSection(subSection: string): subSection is BladesRoll.R // #endregion // #region Utility Functions ~ + // #endregion class BladesRollMod extends BladesTargetLink { static override ApplySchemaDefaults( schemaData: Partial - ) { + ): Schema { // Ensure all properties of Schema are provided - if (!schemaData.name) { throw new Error("name is required for BladesRollMod.Schema"); } + if (!schemaData.name) {throw new Error("name is required for BladesRollMod.Schema");} return { key: `${schemaData.name}-positive-roll`, modType: RollModType.general, @@ -112,7 +111,7 @@ class BladesRollMod extends BladesTargetLink { return [RollModStatus.ForcedOn, RollModStatus.ForcedOff, RollModStatus.Hidden]; } - private static getModData(mStrings: string[]): BladesRollMod.Schema { + private static getModData(mStrings: string[]): Partial { const nameString = U.pullElement(mStrings, (v) => typeof v === "string" && /^na/i.test(v)); const nameVal = (typeof nameString === "string" && nameString.replace(/^.*:/, "")); @@ -129,19 +128,18 @@ class BladesRollMod extends BladesTargetLink { const posNegString = (U.pullElement(mStrings, (v) => typeof v === "string" && /^p/i.test(v)) || "posNeg:positive"); const posNegVal = posNegString.replace(/^.*:/, "") as "positive" | "negative"; - const partialData: Partial = { + return { key: `${nameVal}-${posNegVal}-${catVal}`, name: nameVal, section: catVal, posNeg: posNegVal }; - - return this.ApplySchemaDefaults(partialData); } - private static getModParameterKeyVal(mString: string): Partial< - Record, string|number|string[]> - > { + private static getModParameterKeyVal(mString: string): Partial, + string | number | string[] + >> { const [keyString, valString] = mString.split(/:/) as [string, string]; let val: string | string[] = /\|/.test(valString) ? valString.split(/\|/) : valString; @@ -193,7 +191,7 @@ class BladesRollMod extends BladesTargetLink { return {[key]: valProcessed}; } - static ParseDocRollMods(doc: BladesDoc): BladesRollMod.Schema[] { + static ParseDocRollMods(doc: BladesDoc): Array> { if (doc instanceof BladesChat) { throw new Error("BladesRollMod.ParseDocRollMods cannot be called on a BladesChat document."); @@ -205,7 +203,7 @@ class BladesRollMod extends BladesTargetLink { return roll_mods .filter((elem) => elem && typeof elem === "string") .map((modString) => { - if (!modString) { return undefined; } + if (!modString) {return undefined;} const mStrings = modString.split(/@/); @@ -213,14 +211,14 @@ class BladesRollMod extends BladesTargetLink { mStrings.forEach((mString) => { Object.assign( - this.ApplySchemaDefaults(rollModData), + rollModData, this.getModParameterKeyVal(mString) ); }); return rollModData; }) - .filter((elem): elem is BladesRollMod.Schema => Boolean(elem)); + .filter((elem): elem is Partial => Boolean(elem)); } get status() { @@ -319,10 +317,10 @@ class BladesRollMod extends BladesTargetLink { .filter((rType): rType is BladesRoll.AnyRollType => Boolean(rType)); const typesApply = (!this.rollInstance.isParticipantRoll && types.length === 0) - || rollTypes.some((rType) => types.includes(rType)); + || rollTypes.some((rType) => types.includes(rType)); const traitsApply = (!this.rollInstance.isParticipantRoll && traits.length === 0) - || (this.rollInstance.rollTrait && traits.includes(this.rollInstance.rollTrait)); + || (this.rollInstance.rollTrait && traits.includes(this.rollInstance.rollTrait)); return Boolean(typesApply && traitsApply); } @@ -575,42 +573,46 @@ class BladesRollMod extends BladesTargetLink { this._rollInstance = rollInstance; } - get rollInstance(): BladesRoll { return this._rollInstance; } - get name(): string { return this.data.name; } - get modType(): RollModType { return this.data.modType; } - get sourceName(): string { return this.data.source_name ?? this.data.name; } - get section(): RollModSection { return this.data.section; } - get posNeg(): "positive"|"negative" { return this.data.posNeg; } + get rollInstance(): BladesRoll {return this._rollInstance;} + get name(): string {return this.data.name;} + get modType(): RollModType {return this.data.modType;} + get sourceName(): string {return this.data.source_name ?? this.data.name;} + get section(): RollModSection {return this.data.section;} + get posNeg(): "positive" | "negative" {return this.data.posNeg;} - get userStatus(): RollModStatus|undefined { return this.data.user_status; } + get userStatus(): RollModStatus | undefined {return this.data.user_status;} set userStatus(val: RollModStatus | undefined) { if (val === this.userStatus) {return;} if (!val || val === this.baseStatus) { - this.updateTarget("user_status", null).then(this.rollInstance.renderForAll.bind(this)); + this.updateTarget("user_status", null) + .then(this.rollInstance.renderRollCollab_SocketCall.bind(this.rollInstance)); } else { if (!game.user.isGM && (BladesRollMod.GMOnlyModStatuses.includes(val) - || (this.userStatus && BladesRollMod.GMOnlyModStatuses.includes(this.userStatus))) + || (this.userStatus && BladesRollMod.GMOnlyModStatuses.includes(this.userStatus))) ) { return; } - this.updateTarget("user_status", val).then(this.rollInstance.renderForAll.bind(this)); + this.updateTarget("user_status", val) + .then(this.rollInstance.renderRollCollab_SocketCall.bind(this.rollInstance)); } } - get baseStatus(): RollModStatus { return this.data.base_status; } - get heldStatus(): RollModStatus|undefined { return this.data.held_status; } + get baseStatus(): RollModStatus {return this.data.base_status;} + get heldStatus(): RollModStatus | undefined {return this.data.held_status;} set heldStatus(val: RollModStatus | undefined) { if (val === this.heldStatus) {return;} if (!val) { - this.updateTarget("held_status", null).then(this.rollInstance.renderForAll.bind(this)); + this.updateTarget("held_status", null) + .then(this.rollInstance.renderRollCollab_SocketCall.bind(this.rollInstance)); } else { - this.updateTarget("held_status", val).then(this.rollInstance.renderForAll.bind(this)); + this.updateTarget("held_status", val) + .then(this.rollInstance.renderRollCollab_SocketCall.bind(this.rollInstance)); } } - get value(): number { return this.data.value; } - get effectKeys(): string[] { return this.data.effectKeys ?? []; } + get value(): number {return this.data.value;} + get effectKeys(): string[] {return this.data.effectKeys ?? [];} get sideString(): string | undefined { if (this.data.sideString) {return this.data.sideString;} @@ -704,7 +706,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryDocData { return this._rollPrimaryDoc; } - get flagData(): BladesRoll.PrimaryDocData { + get data(): BladesRoll.PrimaryDocData { return { rollPrimaryID: this.rollPrimaryID, rollPrimaryName: this.rollPrimaryName, @@ -749,7 +751,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryDocData { } get hasArmor() { - if (!this.rollPrimaryDoc) { return false; } + if (!this.rollPrimaryDoc) {return false;} if (this.rollPrimaryType === BladesActorType.pc) { const rollPrimaryDoc = this.rollPrimaryDoc as BladesPC; @@ -760,7 +762,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryDocData { rollPrimaryDoc.system.armor.active.light || rollPrimaryDoc.remainingLoad >= 2 ) - ) { return true; } + ) {return true;} // Otherwise, can PC spend heavy armor? if ( @@ -769,7 +771,7 @@ class BladesRollPrimary implements BladesRoll.PrimaryDocData { rollPrimaryDoc.system.armor.active.heavy || rollPrimaryDoc.remainingLoad >= 3 ) - ) { return true; } + ) {return true;} } if (BladesItem.IsType(this.rollPrimaryDoc, BladesItemType.cohort_gang, BladesItemType.cohort_expert)) { const {value, max} = this.rollPrimaryDoc.system.armor; @@ -779,10 +781,10 @@ class BladesRollPrimary implements BladesRoll.PrimaryDocData { } get hasSpecialArmor() { - if (!this.rollPrimaryDoc) { return false; } - if (!BladesPC.IsType(this.rollPrimaryDoc)) { return false; } - if (!this.rollPrimaryDoc.system.armor.active.special) { return false; } - if (this.rollPrimaryDoc.system.armor.checked.special) { return false; } + if (!this.rollPrimaryDoc) {return false;} + if (!BladesPC.IsType(this.rollPrimaryDoc)) {return false;} + if (!this.rollPrimaryDoc.system.armor.active.special) {return false;} + if (this.rollPrimaryDoc.system.armor.checked.special) {return false;} return true; } @@ -895,7 +897,7 @@ class BladesRollOpposition implements BladesRoll.OppositionDocData { } // #endregion - rollInstance?: BladesRoll|undefined; + rollInstance?: BladesRoll | undefined; _rollOppID: IDString | undefined; @@ -996,7 +998,7 @@ class BladesRollOpposition implements BladesRoll.OppositionDocData { return [C.SYSTEM_ID, "rollCollab.rollOppData"] as const; } - get flagData(): BladesRoll.OppositionDocData { + get data(): BladesRoll.OppositionDocData { return { rollOppID: this.rollOppID, rollOppName: this.rollOppName, @@ -1012,14 +1014,14 @@ class BladesRollOpposition implements BladesRoll.OppositionDocData { } async updateRollFlags() { - if (!this.rollInstance) { return; } - await this.rollInstance.document.setFlag(...this.flagParams, this.flagData); - socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.rollID); + if (!this.rollInstance) {return;} + await this.rollInstance.document.setFlag(...this.flagParams, this.data); + socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.id); } refresh() { - if (!this.rollInstance) { return; } - const rollOppFlags = this.rollInstance.flagData.rollOppData; + if (!this.rollInstance) {return;} + const rollOppFlags = this.rollInstance.data.rollOppData; if (rollOppFlags) { this.rollOppID = rollOppFlags.rollOppID; this.rollOppName = rollOppFlags.rollOppName; @@ -1167,7 +1169,7 @@ class BladesRollParticipant implements BladesRoll.ParticipantDocData { return [C.SYSTEM_ID, `rollCollab.rollParticipantData.${this.rollParticipantSection}.${this.rollParticipantSubSection}`] as const; } - get flagData(): BladesRoll.ParticipantDocData & BladesRoll.ParticipantSectionData { + get data(): BladesRoll.ParticipantDocData & BladesRoll.ParticipantSectionData { return { rollParticipantSection: this.rollParticipantSection, rollParticipantSubSection: this.rollParticipantSubSection, @@ -1183,12 +1185,12 @@ class BladesRollParticipant implements BladesRoll.ParticipantDocData { } async updateRollFlags() { - await this.rollInstance.document.setFlag(...this.flagParams, this.flagData); - socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.rollID); + await this.rollInstance.document.setFlag(...this.flagParams, this.data); + socketlib.system.executeForEveryone("renderRollCollab", this.rollInstance.id); } refresh() { - const rollParticipantFlagData = this.rollInstance.flagData.rollParticipantData?.[this.rollParticipantSection]; + const rollParticipantFlagData = this.rollInstance.data.rollParticipantData?.[this.rollParticipantSection]; if (rollParticipantFlagData) { const rollParticipantFlags = rollParticipantFlagData[ this.rollParticipantSubSection as KeyOf @@ -1214,13 +1216,13 @@ class BladesRollParticipant implements BladesRoll.ParticipantDocData { class BladesRoll extends BladesTargetLink { static _Debug: { - modWatch: RegExp|false + modWatch: RegExp | false } = { modWatch: false }; static Debug = { - watchRollMod(name: string|false) { + watchRollMod(name: string | false) { if (typeof name === "string") { BladesRoll._Debug.modWatch = new RegExp(name, "g"); } else { @@ -1249,61 +1251,68 @@ class BladesRoll extends BladesTargetLink { } static InitSockets() { - socketlib.system.register("constructRollCollab", BladesRoll.ConstructRollCollab); - socketlib.system.register("renderRollCollab", BladesRoll.RenderRollCollab); - socketlib.system.register("closeRollCollab", BladesRoll.CloseRollCollab); + + socketlib.system.register("constructRollCollab_SocketCall", BladesRoll.constructRollCollab_SocketResponse.bind(BladesRoll)); + socketlib.system.register("renderRollCollab_SocketCall", BladesRoll.renderRollCollab_SocketResponse.bind(BladesRoll)); + socketlib.system.register("closeRollCollab_SocketCall", BladesRoll.closeRollCollab_SocketResponse.bind(BladesRoll)); + } - static override ApplySchemaDefaults( - schemaData: Partial & Partial - ) { - // Ensure all properties of Schema are provided - const {rollPrimaryData, rollType, rollUserID} = schemaData; - if (!rollPrimaryData) { - throw new Error("Must include a rollPrimaryData when constructing a BladesRoll object."); - } - if (!rollType) { - throw new Error("Must include a rollType when constructing a BladesRoll object."); + static override ParseConfig( + data: BladesTargetLink.Data & Partial + ): BladesTargetLink.Data & Partial { + if (data.rollPrimaryData instanceof BladesRollPrimary) { + data.rollPrimaryData = data.rollPrimaryData.data; } - if (!rollUserID) { - throw new Error("Must include a rollUserID when constructing a BladesRoll object."); + if (data.rollOppData instanceof BladesRollOpposition) { + data.rollOppData = data.rollOppData.data; } - - if (schemaData.rollParticipantData) { - if (schemaData.rollParticipantData[RollModSection.roll]) { - Object.keys(schemaData.rollParticipantData[RollModSection.roll]).forEach((key) => { - const thisParticipant = schemaData.rollParticipantData?.[RollModSection.roll]?.[key as Exclude]; + if (data.rollParticipantData) { + if (data.rollParticipantData[RollModSection.roll]) { + Object.keys(data.rollParticipantData[RollModSection.roll]).forEach((key) => { + const thisParticipant = data.rollParticipantData?.[RollModSection.roll]?.[key as Exclude]; if (thisParticipant instanceof BladesRollParticipant) { - ((schemaData.rollParticipantData as NonNullable)[RollModSection.roll] as Record)[key as Exclude] = thisParticipant.flagData; + ((data.rollParticipantData as NonNullable)[RollModSection.roll] as Record)[key as Exclude] = thisParticipant.data; } }); } - if (schemaData.rollParticipantData && schemaData.rollParticipantData[RollModSection.position]) { - const participantPositionData = schemaData.rollParticipantData[RollModSection.position]; - Object.keys(participantPositionData) - .forEach(() => { - if (schemaData.rollParticipantData && participantPositionData) { - if (participantPositionData.Setup instanceof BladesRollParticipant) { - participantPositionData.Setup = participantPositionData.Setup.flagData; - } - } - }); + if (data.rollParticipantData[RollModSection.position]) { + Object.keys(data.rollParticipantData[RollModSection.position]).forEach((key) => { + const thisParticipant = data.rollParticipantData?.[RollModSection.position]?.[key as "Setup"]; + if (thisParticipant instanceof BladesRollParticipant) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data.rollParticipantData![RollModSection.position]![key as "Setup"] = thisParticipant.data; + } + }); } - if (schemaData.rollParticipantData && schemaData.rollParticipantData[RollModSection.effect]) { - const participantEffectData = schemaData.rollParticipantData[RollModSection.effect]; - Object.keys(participantEffectData) - .forEach(() => { - if (schemaData.rollParticipantData && participantEffectData) { - if (participantEffectData.Setup instanceof BladesRollParticipant) { - participantEffectData.Setup = participantEffectData.Setup.flagData; - } - } - }); + if (data.rollParticipantData[RollModSection.effect]) { + Object.keys(data.rollParticipantData[RollModSection.effect]).forEach((key) => { + const thisParticipant = data.rollParticipantData?.[RollModSection.effect]?.[key as "Setup"]; + if (thisParticipant instanceof BladesRollParticipant) { + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + data.rollParticipantData![RollModSection.effect]![key as "Setup"] = thisParticipant.data; + } + }); } } + return JSON.parse(JSON.stringify(data)) as BladesTargetLink.Data & Partial; + } + + static override ApplySchemaDefaults( + schemaData: Partial, + linkData: BladesTargetLink.Data + ) { + // Ensure all properties of Schema are provided + const {rollPrimaryData, rollType} = schemaData; + if (!rollPrimaryData) { + throw new Error("Must include a rollPrimaryData when constructing a BladesRoll object."); + } + if (!rollType) { + throw new Error("Must include a rollType when constructing a BladesRoll object."); + } return { - rollModsData: {}, + rollModsData: BladesRoll.GetDefaultRollModsDataSet(linkData), rollPositionInitial: Position.risky, rollEffectInitial: Effect.standard, rollPosEffectTrade: false, @@ -1386,12 +1395,11 @@ class BladesRoll extends BladesTargetLink { userPermissions: {}, ...schemaData, rollType, - rollUserID, rollPrimaryData: rollPrimaryData instanceof BladesRollPrimary - ? rollPrimaryData.flagData + ? rollPrimaryData.data : rollPrimaryData, rollOppData: schemaData.rollOppData instanceof BladesRollOpposition - ? schemaData.rollOppData.flagData + ? schemaData.rollOppData.data : schemaData.rollOppData } as Schema; } @@ -1497,17 +1505,20 @@ class BladesRoll extends BladesTargetLink { ]; } - get defaultRollModsDataSet(): BladesRollMod.Data[] { - return BladesRoll.DefaultRollModSchemas.map((modSchema) => ({ - id: randomID() as IDString, - targetID: this.targetID, - targetKey: this.targetKey ? `${this.targetKey}.${this.id}.rollModsData` as TargetKey : undefined, - targetFlagKey: this.targetFlagKey ? `${this.targetFlagKey}.${this.id}.rollModsData` as TargetFlagKey: undefined, - ...modSchema + static GetDefaultRollModsDataSet(rollLinkData: BladesTargetLink.Data): Record { + return Object.fromEntries(BladesRoll.DefaultRollModSchemas.map((modSchema) => { + const modID = randomID() as IDString; + return [modID, { + id: modID, + targetID: rollLinkData.targetID, + targetKey: rollLinkData.targetKey ? `${rollLinkData.targetKey}.${rollLinkData.id}.rollModsData` as TargetKey : undefined, + targetFlagKey: rollLinkData.targetFlagKey ? `${rollLinkData.targetFlagKey}.${rollLinkData.id}.rollModsData` as TargetFlagKey : undefined, + ...modSchema + }]; })); } - static GetDieClass(rollType: RollType, rollResult: number|false|RollResult, dieVal: number, dieIndex: number) { + static GetDieClass(rollType: RollType, rollResult: number | false | RollResult, dieVal: number, dieIndex: number) { switch (rollType) { case RollType.Resistance: { if (dieVal === 6 && dieIndex <= 1 && rollResult === -1) { @@ -1544,7 +1555,7 @@ class BladesRoll extends BladesTargetLink { static GetDieImage( rollType: RollType, - rollResult: number|false|RollResult, + rollResult: number | false | RollResult, dieVal: number, dieIndex: number, isGhost = false, @@ -1568,47 +1579,25 @@ class BladesRoll extends BladesTargetLink { // #endregion // #region STATIC METHODS: New Roll Creation ~ - static Current: Record = {}; - - static _Active?: BladesRoll; - - static get Active(): BladesRoll | undefined { - if (BladesRoll._Active) {return BladesRoll._Active;} - if (U.objSize(BladesRoll.Current) > 0) {return U.getLast(Object.values(BladesRoll.Current));} - return undefined; - } - - static set Active(val: BladesRoll | undefined) { - BladesRoll._Active = val; - } + // static Current: Record = {}; - static async ConstructRollCollab({ - userID, - rollID, - rollPermission - }: {userID: string, rollID: string, rollPermission: RollPermissions}) { - const rollInst = new BladesRoll(userID, rollID, rollPermission); - eLog.checkLog3("rollCollab", "ConstructRollCollab()", {params: {userID, rollID, rollPermission}, rollInst}); - BladesRoll._Active = rollInst; - await rollInst.renderForAll(); - } + // static _Active?: BladesRoll; - async renderForAll() { - await socketlib.system.executeForEveryone("renderRollCollab", this.id); - } + // static get Active(): BladesRoll | undefined { + // if (BladesRoll._Active) {return BladesRoll._Active;} + // if (U.objSize(BladesRoll.Current) > 0) {return U.getLast(Object.values(BladesRoll.Current));} + // return undefined; + // } - static RenderRollCollab(rollID: string) { - BladesRoll.Current[rollID]?.prepareRollParticipantData(); - BladesRoll.Current[rollID]?.render(); - } + // static set Active(val: BladesRoll | undefined) { + // BladesRoll._Active = val; + // } - static async CloseRollCollab(rollID: string) { - eLog.checkLog3("rollCollab", "CloseRollCollab()", {rollID}); - await BladesRoll.Current[rollID]?.close({rollID}); - // delete BladesRoll.Current[rollID]; - } - static GetUserPermissions(config: BladesRoll.Config): Record { + static GetUserPermissions(config: BladesRoll.Config): Record { + if (!config.rollPrimaryData) { + throw new Error("[BladesRoll.GetUserPermissions()] Missing rollPrimaryData."); + } // === ONE === GET USER IDS @@ -1650,8 +1639,9 @@ class BladesRoll extends BladesTargetLink { ) { if (config.rollUserID === GMUserID) { userIDs[RollPermissions.Primary].push(...playerUserIDs); - } else { - userIDs[RollPermissions.Primary].push(config.rollUserID); + } else if (BladesPC.IsType(rollPrimaryDoc.parent) + && rollPrimaryDoc.parent.primaryUser?.id) { + userIDs[RollPermissions.Primary].push(rollPrimaryDoc.parent.primaryUser.id as IDString); } } else if ( BladesGMTracker.IsType(rollPrimaryDoc) @@ -1660,24 +1650,32 @@ class BladesRoll extends BladesTargetLink { } // === THREE === DETERMINE ROLL PARTICIPANT USER(S) - - // Check config.rollParticipantData to determine if roll starts with any participants if (config.rollParticipantData) { userIDs[RollPermissions.Participant].push(...getParticipantDocUserIDs(config.rollParticipantData, playerUserIDs)); } - // Finally, add remaining players as observers. + // === FOUR === ASSIGN ROLL OBSERVERS + // Add remaining players as observers. userIDs[RollPermissions.Observer] = playerUserIDs .filter((uID) => !userIDs[RollPermissions.Participant].includes(uID)); - return userIDs; + // === FIVE === PARSE INTO {ID: PERMISSION} FORMAT + const userFlagData: Record = {}; + (Object.entries(userIDs) as Array<[RollPermissions, IDString[]]>) + .forEach(([rollPermission, idsArray]) => { + for (const id of idsArray) { + userFlagData[id] = rollPermission; + } + }); + + return userFlagData; /** * Generates BladesRollParticipant documents from the provided schema data. - * @param {BladesRoll.RollParticipantData} participantData + * @param {BladesRoll.RollParticipantDataSet} participantData */ - function getParticipantDocs(participantData: BladesRoll.RollParticipantData) { + function getParticipantDocs(participantData: BladesRoll.RollParticipantDataSet) { return Object.values(flattenObject(participantData)) .map((pData) => { if (BladesRollParticipant.IsDoc(pData)) { @@ -1699,11 +1697,11 @@ class BladesRoll extends BladesTargetLink { /** * Returns the user ids of potential BladesRollParticipants defined in the provided data schema. - * @param {BladesRoll.RollParticipantData} participantData + * @param {BladesRoll.RollParticipantDataSet} participantData * @param {IDString[]} unassignedIDs */ function getParticipantDocUserIDs( - participantData: BladesRoll.RollParticipantData, + participantData: BladesRoll.RollParticipantDataSet, unassignedIDs: IDString[] ): IDString[] { return getParticipantDocs(participantData) @@ -1725,42 +1723,16 @@ class BladesRoll extends BladesTargetLink { } } - static async PrepareActionRoll(rollID: string, config: BladesRoll.Config) { + static PrepareActionRollConfig(config: BladesRoll.Config) { // Validate the rollTrait if (!(config.rollTrait === "" || U.isInt(config.rollTrait) || U.lCase(config.rollTrait) in {...ActionTrait, ...Factor})) { throw new Error(`[PrepareActionRoll()] Bad RollTrait for Action Roll: ${config.rollTrait}`); } - // Retrieve the roll users - const userIDs = BladesRoll.GetUserPermissions(config); - - // Prepare roll user flag data - const userFlagData: Record = {}; - (Object.entries(userIDs) as Array<[RollPermissions, IDString[]]>) - .forEach(([rollPermission, idsArray]) => { - for (const id of idsArray) { - userFlagData[id] = rollPermission; - } - }); - - // Prepare the flag data. - const flagUpdateData: BladesRoll.Data = { - ...BladesRoll.DefaultFlagData, - ...pruneConfig(config as BladesRoll.Config & Record), - userPermissions: userFlagData, - rollID - }; - - // Return flagData and roll users - return { - flagUpdateData, - userIDs - }; - } - static async PrepareResistanceRoll(rollID: string, config: BladesRoll.Config) { + static PrepareResistanceRollConfig(config: BladesRoll.Config) { // Validate consequenceData if (!config.resistanceData || !BladesConsequence.IsValidConsequenceData(config.resistanceData?.consequence)) { @@ -1771,103 +1743,35 @@ class BladesRoll extends BladesTargetLink { // Set rollTrait config.rollTrait = config.resistanceData.consequence.attribute; - eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [1]", {rollID, config}); - - // Retrieve the roll users - const userIDs = BladesRoll.GetUserPermissions(config); - eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [2]", {userIDs}); - - // Prepare roll user flag data - const userFlagData: Record = {}; - (Object.entries(userIDs) as Array<[RollPermissions, string[]]>) - .forEach(([rollPermission, idsArray]) => { - for (const id of idsArray) { - userFlagData[id] = rollPermission; - } - }); - eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [3]", {userFlagData}); - - // Prepare the flag data. - const flagUpdateData: BladesRoll.FlagData = { - ...BladesRoll.DefaultFlagData, - ...pruneConfig(config as BladesRoll.Config & Record), - userPermissions: userFlagData, - rollID - }; - eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [4]", {flagUpdateData}); + eLog.checkLog3("bladesRoll", "BladesRoll.PrepareResistanceRoll() [1]", {config}); - // Return flagData and roll users - return { - flagUpdateData, - userIDs - }; } - static async PrepareFortuneRoll(rollID: string, config: BladesRoll.Config) { + static PrepareFortuneRollConfig(config: BladesRoll.Config) { // Validate the rollTrait if (!(U.isInt(config.rollTrait) || U.lCase(config.rollTrait) in {...ActionTrait, ...AttributeTrait, ...Factor})) { throw new Error(`[PrepareFortuneRoll()] Bad RollTrait for Fortune Roll: ${config.rollTrait}`); } - // Retrieve the roll users - const userIDs = BladesRoll.GetUserPermissions(config); - - // Prepare roll user flag data - const userFlagData: Record = {}; - (Object.entries(userIDs) as Array<[RollPermissions, string[]]>) - .forEach(([rollPermission, idsArray]) => { - for (const id of idsArray) { - userFlagData[id] = rollPermission; - } - }); - - // Prepare the flag data. - const flagUpdateData: BladesRoll.FlagData = { - ...BladesRoll.DefaultFlagData, - ...pruneConfig(config as BladesRoll.Config & Record), - userPermissions: userFlagData, - rollID - }; + } - // Return flagData and roll users - return { - flagUpdateData, - userIDs - }; + static PrepareIndulgeViceRollConfig(config: BladesRoll.Config) { - } + // Validate rollPrimary + if (!config.rollPrimaryData || !BladesPC.IsType(config.rollPrimaryData.rollPrimaryDoc)) { + throw new Error("[BladesRoll.PrepareIndulgeViceRollConfig] RollPrimary must be a PC for Indulge Vice rolls."); + } - static async PrepareIndulgeViceRoll(rollID: string, config: BladesRoll.Config) { + // Set rollTrait + const {attributes} = config.rollPrimaryData.rollPrimaryDoc; + const minAttrVal = Math.min(...Object.values(attributes)); + config.rollTrait = U.sample( + Object.values(AttributeTrait).filter((attr) => attributes[attr] === minAttrVal) + )[0]; // Set other known config values config.rollDowntimeAction = DowntimeAction.IndulgeVice; - - // Retrieve the roll users - const userIDs = BladesRoll.GetUserPermissions(config); - - // Prepare roll user flag data - const userFlagData: Record = {}; - (Object.entries(userIDs) as Array<[RollPermissions, string[]]>) - .forEach(([rollPermission, idsArray]) => { - for (const id of idsArray) { - userFlagData[id] = rollPermission; - } - }); - - // Prepare the flag data. - const flagUpdateData: BladesRoll.FlagData = { - ...BladesRoll.DefaultFlagData, - ...pruneConfig(config as BladesRoll.Config & Record), - userPermissions: userFlagData, - rollID - }; - - // Return flagData and roll users - return { - flagUpdateData, - userIDs - }; } /** @@ -1876,111 +1780,90 @@ class BladesRoll extends BladesTargetLink { * document, then sends out a socket call to the relevant users to construct * and display the roll instance. * - * @param {BladesRoll.ConstructorConfig} config The configuration object for the new roll. + * @param {BladesRoll.Config} config The configuration object for the new roll. */ - static async NewRoll(config: BladesRoll.ConstructorConfig) { - // If no rollType is provided, throw an error. - if (!isRollType(config.rollType)) { - throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollType in the config object."); - } + static async NewRoll(config: BladesRoll.Config) { + + const {targetKey, targetFlagKey} = config; - // Get User document to serve as flag storage. + // Get User document. const rollUser = game.users.get(config.rollUserID ?? game.user.id ?? ""); - if (!rollUser?.id) { - throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollUserID in the config object."); - } - eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll() [1]", {config, rollUser}); - - // If roll flag data is already on user. - const flagData = rollUser.getFlag("eunos-blades", "rollCollab") as BladesRoll.FlagData | undefined; - if (flagData) { - const {rollID} = flagData; - // If user is documenting a roll with a dialog window open, disallow starting a new roll. - if ($(document).find(`.roll-collab-sheet .sheet-topper[data-roll-id='${rollID}']`)[0]) { - throw new Error(`[BladesRoll.NewRoll()] User ${rollUser.name} already documenting live roll with ID '${rollID}'`); - } - // Otherwise, archive the existing roll and prepare for a new one by clearing the main rollCollab flag. - await rollUser.setFlag("eunos-blades", `rollCollabArchive.${rollID}`, flagData); - await rollUser.unsetFlag("eunos-blades", "rollCollab"); - eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll() [2]", {userFlags: rollUser.flags}); - } - - let {rollPrimaryData} = config; - - if (!BladesRollPrimary.IsValidData(rollPrimaryData)) { - // If no rollPrimaryData is provided, attempt to derive it from user data - let rollPrimarySourceData: BladesRoll.PrimaryDocData; - if (BladesPC.IsType(rollUser.character)) { - rollPrimarySourceData = rollUser.character; - rollPrimaryData = { - rollPrimaryID: rollPrimarySourceData.rollPrimaryID, - rollPrimaryName: rollPrimarySourceData.rollPrimaryName, - rollPrimaryType: rollPrimarySourceData.rollPrimaryType, - rollPrimaryImg: rollPrimarySourceData.rollPrimaryImg, - rollModsData: rollPrimarySourceData.rollModsData, - rollFactors: rollPrimarySourceData.rollFactors, - applyHarm: rollPrimarySourceData.applyHarm, - applyWorsePosition: rollPrimarySourceData.applyWorsePosition - }; + // If config is NOT a valid BladesTargetLink config object, link it to the user id + if (!BladesTargetLink.IsValidConfig(config)) { + if (!rollUser?.id) { + throw new Error("[BladesRoll.NewRoll()] You must provide a valid rollUserID or targetID (UUID) in the config object."); } - } - if (!BladesRollPrimary.IsValidData(rollPrimaryData)) { - throw new Error("[BladesRoll.NewRoll()] A valid source of PrimaryDocData must be provided to construct a roll."); - } + eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll() [1]", {config, rollUser}); - // Get the rollPrimary document, if an ID is provided. - const rollPrimary = new BladesRollPrimary(undefined, rollPrimaryData); - const {rollPrimaryDoc} = rollPrimary; + Object.assign(config, { + target: rollUser, + targetKey, + targetFlagKey: targetFlagKey ?? "rollCollab" as TargetFlagKey + }); + } - // Create a random ID for storing the roll instance - const rID = randomID() as IDString; + // If no rollPrimaryData is provided, attempt to derive it from target or user + if (!BladesRollPrimary.IsValidData(config.rollPrimaryData)) { + let rollPrimary: BladesRoll.PrimaryDoc; + if (BladesRollPrimary.IsDoc(config.target)) { + rollPrimary = config.target; + } else if (BladesRollPrimary.IsDoc(rollUser?.character)) { + rollPrimary = rollUser.character; + } else { + throw new Error("[BladesRoll.NewRoll()] A valid source of PrimaryDocData must be provided to construct a roll."); + } - // Derive user flag data depending on given roll type and subtype - let userIDs: Record; - let flagUpdateData: BladesRoll.FlagData; + config.rollPrimaryData = { + rollPrimaryID: rollPrimary.rollPrimaryID, + rollPrimaryName: rollPrimary.rollPrimaryName, + rollPrimaryType: rollPrimary.rollPrimaryType, + rollPrimaryImg: rollPrimary.rollPrimaryImg, + rollModsData: rollPrimary.rollModsData, + rollFactors: rollPrimary.rollFactors, + applyHarm: rollPrimary.applyHarm, + applyWorsePosition: rollPrimary.applyWorsePosition + }; + } - // Construct Config object - const configData: BladesRoll.Config = { - ...config, - rollUserID: rollUser.id as IDString, - rollPrimaryData - }; + // Acquire the roll primary document + const {rollPrimaryDoc} = new BladesRollPrimary(undefined, config.rollPrimaryData); - // Modify Config object depending on subtype and downtime action where necessary. - switch (configData.rollSubType) { + // Modify Config object depending on subtype where necessary. + switch (config.rollSubType) { case RollSubType.Engagement: case RollSubType.Incarceration: { - configData.rollType = RollType.Fortune; + config.rollType = RollType.Fortune; break; } default: break; } - switch (configData.rollDowntimeAction) { // Can be done outside of Downtime during Flashbacks! + // Modify Config object depending on downtime action where necessary. + switch (config.rollDowntimeAction) { // Remember: Can be done outside of Downtime during Flashbacks! case DowntimeAction.AcquireAsset: { - configData.rollType = RollType.Action; - configData.rollTrait = Factor.tier; + config.rollType = RollType.Action; + config.rollTrait = Factor.tier; break; } case DowntimeAction.IndulgeVice: { - configData.rollType = RollType.IndulgeVice; + config.rollType = RollType.IndulgeVice; if (!BladesPC.IsType(rollPrimaryDoc)) { throw new Error("Only a PC character can roll to Indulge Vice."); } const minAttrVal = Math.min(...Object.values(rollPrimaryDoc.attributes)); - configData.rollTrait = U.sample( + config.rollTrait = U.sample( Object.values(AttributeTrait).filter((attr) => rollPrimaryDoc.attributes[attr] === minAttrVal) )[0]; break; } case DowntimeAction.LongTermProject: { - configData.rollType = RollType.Action; + config.rollType = RollType.Action; // Validate that rollOppData points to a project item - if (!BladesRollOpposition.IsValidData(configData.rollOppData)) { + if (!BladesRollOpposition.IsValidData(config.rollOppData)) { throw new Error("No rollOppData provided for LongTermProject roll."); } - const rollOpp = new BladesRollOpposition(undefined, configData.rollOppData); + const rollOpp = new BladesRollOpposition(undefined, config.rollOppData); if (![ BladesItemType.project, BladesItemType.design @@ -1990,26 +1873,26 @@ class BladesRoll extends BladesTargetLink { break; } case DowntimeAction.Recover: { - configData.rollType = RollType.Action; + config.rollType = RollType.Action; // Validate that rollPrimary is an NPC or a PC with Physiker. if (BladesPC.IsType(rollPrimaryDoc)) { if (!rollPrimaryDoc.abilities.find((ability) => ability.name === "Physiker")) { throw new Error("A PC rollPrimary on a Recovery roll must have the Physiker ability."); } - configData.rollTrait = ActionTrait.tinker; - } else if (rollPrimary.rollPrimaryType === BladesActorType.npc) { - configData.rollTrait = Factor.quality; + config.rollTrait = ActionTrait.tinker; + } else if (rollPrimaryDoc?.rollPrimaryType === BladesActorType.npc) { + config.rollTrait = Factor.quality; } else { throw new Error("Only a PC with Physiker or an NPC can be rollPrimary on a Recover roll."); } break; } case DowntimeAction.ReduceHeat: { - configData.rollType = RollType.Action; + config.rollType = RollType.Action; // rollPrimary must be a cohort with a parent PC or Crew, // and PC must be member of a crew // and Crew must not have zero Heat. - let parentCrew: BladesCrew|undefined = undefined; + let parentCrew: BladesCrew | undefined = undefined; if (rollPrimaryDoc) { const {parent} = rollPrimaryDoc; if (BladesCrew.IsType(parent)) { @@ -2020,7 +1903,7 @@ class BladesRoll extends BladesTargetLink { } if (!BladesCrew.IsType(parentCrew)) { - throw new Error(`Could not find crew for rollPrimary ${rollPrimary.rollPrimaryName}`); + throw new Error(`Could not find crew for rollPrimary '${rollPrimaryDoc?.rollPrimaryName}'`); } if (parentCrew.system.heat.value === 0) { throw new Error("Attempt to Reduce Heat for a Crew with no Heat."); @@ -2028,57 +1911,86 @@ class BladesRoll extends BladesTargetLink { break; } case undefined: break; - default: throw new Error(`Unrecognized Roll Downtime Action: ${configData.rollDowntimeAction}`); + default: throw new Error(`Unrecognized Roll Downtime Action: ${config.rollDowntimeAction}`); } + // Prepare roll user flag data + config.userPermissions = BladesRoll.GetUserPermissions(config); + switch (config.rollType) { case RollType.Action: { - ({userIDs, flagUpdateData} = await BladesRoll.PrepareActionRoll(rID, configData)); + BladesRoll.PrepareActionRollConfig(config); break; } case RollType.Resistance: { - ({userIDs, flagUpdateData} = await BladesRoll.PrepareResistanceRoll(rID, configData)); + BladesRoll.PrepareResistanceRollConfig(config); break; } case RollType.Fortune: { - ({userIDs, flagUpdateData} = await BladesRoll.PrepareFortuneRoll(rID, configData)); + BladesRoll.PrepareFortuneRollConfig(config); break; } case RollType.IndulgeVice: { - ({userIDs, flagUpdateData} = await BladesRoll.PrepareIndulgeViceRoll(rID, configData)); + BladesRoll.PrepareIndulgeViceRollConfig(config); break; } + default: throw new Error(`Unrecognized Roll Type: ${String(config.rollType)}`); + } + + // Ensure rollType is defined + if (!config.rollType) { + throw new Error("rollType must be defined in config"); } // Log the roll data - eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll()", {userIDs, flagUpdateData, rollPrimaryData: flagUpdateData.rollPrimaryData}); + eLog.checkLog3("bladesRoll", "BladesRoll.NewRoll()", {config}); - // Store the roll data on the storage document - await rollUser.setFlag(C.SYSTEM_ID, "rollCollab", flagUpdateData); + // Construct and initialize the BladesRoll/BladesTargetLink instance + const rollInst = await BladesRoll.Create(config as BladesTargetLink.Config & {rollType: RollType}) as BladesRoll; // Send out socket calls to all users to see the roll. - socketlib.system.executeForUsers("constructRollCollab", - userIDs[RollPermissions.GM], - {userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.GM} - ); - socketlib.system.executeForUsers("constructRollCollab", - userIDs[RollPermissions.Primary], - {userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Primary} - ); - socketlib.system.executeForUsers("constructRollCollab", - userIDs[RollPermissions.Observer], - {userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Observer} - ); - socketlib.system.executeForUsers("constructRollCollab", - userIDs[RollPermissions.Participant], - {userID: rollUser.id, rollID: rID, rollPermission: RollPermissions.Participant} - ); + rollInst.constructRollCollab_SocketCall(rollInst.linkData); } // #endregion - // #region Constructor ~ - rollID: string; + // #region SOCKET CALLS & RESPONSES ~ + constructRollCollab_SocketCall(linkData: BladesTargetLink.Data) { + socketlib.system.executeForEveryone("constructRollCollab_SocketCall", linkData); + } + + static constructRollCollab_SocketResponse( + linkData: BladesTargetLink.Data + ) { + const rollInst = new BladesRoll(linkData); + eLog.checkLog3("rollCollab", "constructRollCollab_SocketResponse()", {params: {linkData}, rollInst}); + this.renderRollCollab_SocketResponse(rollInst.id); + } + renderRollCollab_SocketCall() { + socketlib.system.executeForEveryone("renderRollCollab_SocketCall", this.id); + } + + static renderRollCollab_SocketResponse(id: IDString) { + const rollInst = game.eunoblades.Rolls.get(id); + if (!rollInst) { + throw new Error(`[BladesRoll.renderRollCollab_SocketResponse] No roll found with id ${id}.`); + } + rollInst.prepareRollParticipantData(); + rollInst.render(); + } + + closeRollCollab_SocketCall() { + socketlib.system.executeForEveryone("closeRollCollab_SocketCall", this.id); + } + + static closeRollCollab_SocketResponse(id: IDString) { + const rollInst = game.eunoblades.Rolls.get(id); + if (!rollInst) {return;} + rollInst.close(); + } + // #endregion + + // #region Constructor ~ rollPermission: RollPermissions; _rollPrimary: BladesRollPrimary; @@ -2089,26 +2001,22 @@ class BladesRoll extends BladesTargetLink { projectSelectOptions?: Array>; - constructor(userID: string, rollID: string, rollPermission: RollPermissions) { - const rollUser = game.users.get(userID); - if (!rollUser) { - throw new Error("[new BladesRoll()] Must provide a valid rollUser to roll."); - } - super(rollUser); - this.rollID = rollID; - this.rollPermission = rollPermission; - const rollFlagData = rollUser.getFlag(C.SYSTEM_ID, "rollCollab") as BladesRoll.FlagData; - this._rollPrimary = new BladesRollPrimary(this, rollFlagData.rollPrimaryData); - if (rollFlagData.rollOppData) { - this._rollOpposition = new BladesRollOpposition(this, rollFlagData.rollOppData); - } else if (rollFlagData.rollDowntimeAction === DowntimeAction.LongTermProject) { + constructor(config: BladesRoll.Config & Partial) + constructor(data: BladesTargetLink.Data & Partial) + constructor(dataOrConfig: (BladesRoll.Config | BladesTargetLink.Data) & Partial) { + super(dataOrConfig); + this.rollPermission = this.data.userPermissions[game.user.id as IDString]; + this._rollPrimary = new BladesRollPrimary(this, this.data.rollPrimaryData); + if (this.data.rollOppData) { + this._rollOpposition = new BladesRollOpposition(this, this.data.rollOppData); + } else if (this.data.rollDowntimeAction === DowntimeAction.LongTermProject) { this.projectSelectOptions = Array.from(game.items) .filter((item) => BladesItem.IsType(item, BladesItemType.project)) .map((project) => ({value: project.id ?? "", display: project.name})); } - if (rollFlagData.rollParticipantData) { + if (this.data.rollParticipantData) { this._rollParticipants = {}; - for (const [rollSection, rollParticipantList] of Object.entries(rollFlagData.rollParticipantData)) { + for (const [rollSection, rollParticipantList] of Object.entries(this.data.rollParticipantData)) { if ([RollModSection.roll, RollModSection.position, RollModSection.effect] .includes(rollSection as RollModSection) && !U.isEmpty(rollParticipantList)) { const sectionParticipants: Record = {}; @@ -2122,7 +2030,7 @@ class BladesRoll extends BladesTargetLink { } } } - BladesRoll.Current[this.rollID] = this; + game.eunoblades.Rolls.set(this.id, this); } // #endregion @@ -2154,14 +2062,14 @@ class BladesRoll extends BladesTargetLink { }); await rollParticipant.updateRollFlags(); - socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + socketlib.system.executeForEveryone("renderRollCollab", this.id); } async removeRollParticipant( rollSection: BladesRoll.RollParticipantSection, rollSubSection: BladesRoll.RollParticipantSubSection ) { - await this.clearFlagVal(`rollParticipantData.${rollSection}.${rollSubSection}`); + await this.updateTarget(`rollParticipantData.${rollSection}.${rollSubSection}`, null); } async updateUserPermission( @@ -2172,12 +2080,12 @@ class BladesRoll extends BladesTargetLink { } // #region Basic User Flag Getters/Setters ~ - get flagData(): BladesRoll.FlagData { - if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { - throw new Error("[get flags()] No RollCollab Flags Found on User Document"); - } - return this.document.getFlag(C.SYSTEM_ID, "rollCollab") as BladesRoll.FlagData; - } + // get data(): BladesRoll.FlagData { + // if (!this.document.getFlag(C.SYSTEM_ID, "rollCollab")) { + // throw new Error("[get flags()] No RollCollab Flags Found on User Document"); + // } + // return this.document.getFlag(C.SYSTEM_ID, "rollCollab") as BladesRoll.FlagData; + // } get rollPrimary(): BladesRollPrimary { return this._rollPrimary; @@ -2194,8 +2102,8 @@ class BladesRoll extends BladesTargetLink { } get rollOpposition(): BladesRollOpposition | undefined { - if (!this._rollOpposition && BladesRollOpposition.IsValidData(this.flagData.rollOppData)) { - this._rollOpposition = new BladesRollOpposition(this, this.flagData.rollOppData); + if (!this._rollOpposition && BladesRollOpposition.IsValidData(this.data.rollOppData)) { + this._rollOpposition = new BladesRollOpposition(this, this.data.rollOppData); } return this._rollOpposition?.refresh(); } @@ -2214,17 +2122,13 @@ class BladesRoll extends BladesTargetLink { } get rollClockKey(): BladesClockKey | undefined { - return this.flagData.rollClockKeyID - ? game.eunoblades.ClockKeys.get(this.flagData.rollClockKeyID) + return this.data.rollClockKeyID + ? game.eunoblades.ClockKeys.get(this.data.rollClockKeyID) : undefined; } set rollClockKey(val: BladesClockKey | undefined) { - if (val) { - this.setFlagVal("rollClockKeyID", val.id); - } else { - this.clearFlagVal("rollClockKeyID"); - } + this.updateTarget("rollClockKeyID", val ?? null); } /** @@ -2234,7 +2138,7 @@ class BladesRoll extends BladesTargetLink { * The created instances are stored in the rollParticipants object. */ private prepareRollParticipantData(): void { - const participantFlagData = this.flagData.rollParticipantData; + const participantFlagData = this.data.rollParticipantData; if (!participantFlagData) {return;} const rollParticipants: BladesRoll.RollParticipantDocs = {}; @@ -2291,7 +2195,7 @@ class BladesRoll extends BladesTargetLink { get rollParticipantSelectOptions(): Record< "Assist" | "Setup" | "Group", Array> - > { + > { const nonPrimaryPCs = BladesPC.All .filter((actor) => actor.hasTag(Tag.PC.ActivePC) && actor.id !== this.rollPrimary.rollPrimaryID) .map((actor) => ({value: actor.id, display: actor.name})); @@ -2302,35 +2206,35 @@ class BladesRoll extends BladesTargetLink { }; } - get rollType(): RollType {return this.flagData.rollType;} + get rollType(): RollType {return this.data.rollType as RollType;} - get rollSubType(): RollSubType | undefined {return this.flagData.rollSubType;} + get rollSubType(): RollSubType | undefined {return this.data.rollSubType;} set rollSubType(val: RollSubType | undefined) { - this.setFlagVal("rollSubType", val); + this.updateTarget("rollSubType", val ?? null); } get rollPhase(): RollPhase { - return this.getFlagVal("rollPhase") ?? RollPhase.Collaboration; + return this.data.rollPhase ?? RollPhase.Collaboration; } async setRollPhase(rollPhase: RollPhase) { - await this.setFlagVal("rollPhase", rollPhase); + await this.updateTarget("rollPhase", rollPhase); } - get rollDowntimeAction(): DowntimeAction | undefined {return this.flagData.rollDowntimeAction;} + get rollDowntimeAction(): DowntimeAction | undefined {return this.data.rollDowntimeAction;} - get rollTrait(): BladesRoll.RollTrait | undefined {return this.flagData.rollTrait;} + get rollTrait(): BladesRoll.RollTrait | undefined {return this.data.rollTrait;} get rollTraitVerb(): ValueOf | undefined { - if (!this.rollTrait) { return undefined; } - if (!(this.rollTrait in C.ActionVerbs)) { return undefined; } + if (!this.rollTrait) {return undefined;} + if (!(this.rollTrait in C.ActionVerbs)) {return undefined;} return C.ActionVerbs[this.rollTrait as ActionTrait] as ValueOf | undefined; } get rollTraitPastVerb(): ValueOf | undefined { - if (!this.rollTrait) { return undefined; } - if (!(this.rollTrait in C.ActionPastVerbs)) { return undefined; } + if (!this.rollTrait) {return undefined;} + if (!(this.rollTrait in C.ActionPastVerbs)) {return undefined;} return C.ActionPastVerbs[this.rollTrait as ActionTrait] as ValueOf | undefined; } @@ -2410,7 +2314,7 @@ class BladesRoll extends BladesTargetLink { } get posEffectTrade(): "position" | "effect" | false { - return this.flagData?.rollPosEffectTrade ?? false; + return this.data?.rollPosEffectTrade ?? false; } // getFlagVal(flagKey?: string): T | undefined { @@ -2423,32 +2327,32 @@ class BladesRoll extends BladesTargetLink { // async setFlagVal(flagKey: string, flagVal: unknown, isRerendering = true) { // await this.document.setFlag(C.SYSTEM_ID, `rollCollab.${flagKey}`.replace(/(rollCollab\.)+/g, "rollCollab."), flagVal); // if (isRerendering) { - // socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + // socketlib.system.executeForEveryone("renderRollCollab", this.id); // } // } // async clearFlagVal(flagKey: string, isRerendering = true) { // await this.document.unsetFlag(C.SYSTEM_ID, `rollCollab.${flagKey}`.replace(/(rollCollab\.)+/g, "rollCollab.")); // if (isRerendering) { - // socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + // socketlib.system.executeForEveryone("renderRollCollab", this.id); // } // } get initialPosition(): Position { - return this.getFlagVal("rollPositionInitial") ?? Position.risky; + return this.data.rollPositionInitial ?? Position.risky; } set initialPosition(val: Position) { - this.setFlagVal("rollPositionInitial", val ?? Position.risky); + this.updateTarget("rollPositionInitial", val ?? Position.risky); } get initialEffect(): Effect { - return this.getFlagVal("rollEffectInitial") ?? Effect.standard; + return this.data.rollEffectInitial ?? Effect.standard; } set initialEffect(val: Effect) { - this.setFlagVal("rollEffectInitial", val); + this.updateTarget("rollEffectInitial", val ?? Effect.standard); } get isApplyingConsequences(): boolean { @@ -2490,21 +2394,21 @@ class BladesRoll extends BladesTargetLink { get finalResult(): number { return this.getModsDelta(RollModSection.result) - + (this.flagData?.GMBoosts.Result ?? 0) + + (this.data?.GMBoosts.Result ?? 0) + (this.tempGMBoosts.Result ?? 0); } get finalDicePool(): number { return Math.max(0, this.rollTraitData.value + this.getModsDelta(RollModSection.roll) - + (this.flagData.GMBoosts.Dice ?? 0) + + (this.data.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)); } get isRollingZero(): boolean { return Math.max(0, this.rollTraitData.value + this.getModsDelta(RollModSection.roll) - + (this.flagData.GMBoosts.Dice ?? 0) + + (this.data.GMBoosts.Dice ?? 0) + (this.tempGMBoosts.Dice ?? 0)) <= 0; } @@ -2576,7 +2480,7 @@ class BladesRoll extends BladesTargetLink { this.rollPrimary.rollFactors, {isMutatingOk: false} ), - this.flagData.rollFactorToggles.source, + this.data.rollFactorToggles.source, {isMutatingOk: false} ) as Record; @@ -2587,7 +2491,7 @@ class BladesRoll extends BladesTargetLink { this.rollOpposition.rollFactors, {isMutatingOk: false} ), - this.flagData.rollFactorToggles.opposition, + this.data.rollFactorToggles.opposition, {isMutatingOk: false} ) as Record : {}; @@ -2597,7 +2501,7 @@ class BladesRoll extends BladesTargetLink { (Object.entries(mergedSourceFactors) as Array<[Factor, BladesRoll.FactorData]>) .map(([factor, factorData]) => { factorData.value += - (this.flagData.GMBoosts[factor] ?? 0) + (this.data.GMBoosts[factor] ?? 0) + (this.tempGMBoosts[factor] ?? 0); if (factor === Factor.tier) { factorData.display = U.romanizeNum(factorData.value); @@ -2610,7 +2514,7 @@ class BladesRoll extends BladesTargetLink { opposition: Object.fromEntries( (Object.entries(mergedOppFactors) as Array<[Factor, BladesRoll.FactorData]>) .map(([factor, factorData]) => { - factorData.value += this.flagData.GMOppBoosts[factor] ?? 0; + factorData.value += this.data.GMOppBoosts[factor] ?? 0; if (factor === Factor.tier) { factorData.display = U.romanizeNum(factorData.value); } else { @@ -2639,7 +2543,7 @@ class BladesRoll extends BladesTargetLink { let initReportCount = 0; const watchMod = (label: string) => { - if (BladesRoll._Debug.modWatch === false) { return; } + if (BladesRoll._Debug.modWatch === false) {return;} const reportLabel = `(${initReportCount}) == ${label}`; const rollMod = this.rollMods .find((mod) => BladesRoll._Debug.modWatch && BladesRoll._Debug.modWatch.exec(mod.name)); @@ -2688,7 +2592,7 @@ class BladesRoll extends BladesTargetLink { watchMod("DISABLE - AUTO-REVEAL/ENABLE"); // ... Payable Pass - autoRevealDisablePass.forEach((rollMod) => { rollMod.setPayableStatus(); }); + autoRevealDisablePass.forEach((rollMod) => {rollMod.setPayableStatus();}); watchMod("DISABLE - PAYABLE"); /* *** PASS TWO: FORCE-ON PASS *** */ @@ -2961,7 +2865,7 @@ class BladesRoll extends BladesTargetLink { set rollMods(val: BladesRollMod[]) {this._rollMods = val;} canResistWithArmor(csqData: BladesRoll.ConsequenceData) { - if (!this.rollPrimary.hasArmor) { return false; } + if (!this.rollPrimary.hasArmor) {return false;} return csqData.attribute === AttributeTrait.prowess; } @@ -2977,16 +2881,16 @@ class BladesRoll extends BladesTargetLink { // #region CONSEQUENCES: Getting, Accepting, Resisting private get _csqData(): BladesRoll.ConsequenceData[] { - const csqData = this.flagData.consequenceData?. - [this.finalPosition]?. - [this.rollResult as RollResult.partial|RollResult.fail]; + const csqData = this.data.consequenceData?. + [this.finalPosition]?. + [this.rollResult as RollResult.partial | RollResult.fail]; if (csqData) { return Object.values(csqData); } return []; } - getConsequenceByID(csqID: string): BladesRoll.ConsequenceData|false { + getConsequenceByID(csqID: string): BladesRoll.ConsequenceData | false { return this._csqData.find((cData) => cData.id === csqID) ?? false; } @@ -3298,7 +3202,7 @@ class BladesRoll extends BladesTargetLink { rollCosts: BladesRoll.CostData[] ): BladesRoll.Context { const { - flagData: rData, + data: rData, rollPrimary, rollTraitData, rollTraitOptions, @@ -3315,7 +3219,7 @@ class BladesRoll extends BladesTargetLink { throw new Error("A primary roll source is required for BladesRoll."); } const baseData = { - ...this.flagData, + ...this.data, cssClass: "roll-collab", editable: this.options.editable, isGM, @@ -3427,21 +3331,21 @@ class BladesRoll extends BladesTargetLink { }; } - private calculateGMBoostsData(flagData: BladesRoll.FlagData) { + private calculateGMBoostsData(data: BladesRoll.FlagData) { return { GMBoosts: { - Dice: flagData.GMBoosts.Dice ?? 0, - [Factor.tier]: flagData.GMBoosts[Factor.tier] ?? 0, - [Factor.quality]: flagData.GMBoosts[Factor.quality] ?? 0, - [Factor.scale]: flagData.GMBoosts[Factor.scale] ?? 0, - [Factor.magnitude]: flagData.GMBoosts[Factor.magnitude] ?? 0, - Result: flagData.GMBoosts.Result ?? 0 + Dice: data.GMBoosts.Dice ?? 0, + [Factor.tier]: data.GMBoosts[Factor.tier] ?? 0, + [Factor.quality]: data.GMBoosts[Factor.quality] ?? 0, + [Factor.scale]: data.GMBoosts[Factor.scale] ?? 0, + [Factor.magnitude]: data.GMBoosts[Factor.magnitude] ?? 0, + Result: data.GMBoosts.Result ?? 0 }, GMOppBoosts: { - [Factor.tier]: flagData.GMOppBoosts[Factor.tier] ?? 0, - [Factor.quality]: flagData.GMOppBoosts[Factor.quality] ?? 0, - [Factor.scale]: flagData.GMOppBoosts[Factor.scale] ?? 0, - [Factor.magnitude]: flagData.GMOppBoosts[Factor.magnitude] ?? 0 + [Factor.tier]: data.GMOppBoosts[Factor.tier] ?? 0, + [Factor.quality]: data.GMOppBoosts[Factor.quality] ?? 0, + [Factor.scale]: data.GMOppBoosts[Factor.scale] ?? 0, + [Factor.magnitude]: data.GMOppBoosts[Factor.magnitude] ?? 0 } }; } @@ -3956,10 +3860,10 @@ class BladesRoll extends BladesTargetLink { const chatMessage$ = chatElem$.closest(".chat-message"); const chatID = chatMessage$.data("messageId") as IDString; const chatMessage = game.messages.get(chatID); - if (!chatMessage) { return; } + if (!chatMessage) {return;} const csqs = await BladesConsequence.GetFromChatMessage(chatMessage); const thisCsq = csqs.find((csq) => csq.id === csqID); - if (!thisCsq) { return; } + if (!thisCsq) {return;} switch (clickTarget$.data("action")) { case "accept-consequence": return thisCsq.accept(); case "resist-consequence": return thisCsq.resistConsequence(); @@ -4018,7 +3922,7 @@ class BladesRoll extends BladesTargetLink { const target = elem$.data("target").replace(/flags\.eunos-blades\./, ""); const value = elem$.data("value"); - await this.document.setFlag(C.SYSTEM_ID, target, value).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + await this.document.setFlag(C.SYSTEM_ID, target, value).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } async _gmControlCycleTarget(event: ClickEvent) { @@ -4054,7 +3958,7 @@ class BladesRoll extends BladesTargetLink { const elem$ = $(event.currentTarget); const target = elem$.data("target").replace(/flags\.eunos-blades\./, ""); - await this.document.unsetFlag(C.SYSTEM_ID, target).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + await this.document.unsetFlag(C.SYSTEM_ID, target).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } /** @@ -4101,7 +4005,7 @@ class BladesRoll extends BladesTargetLink { // If thisToggle is unrecognized, just toggle whatever value target points at if (!["isActive", "isPrimary", "isDominant", "highFavorsPC"].includes(thisToggle)) { await this.document.setFlag(C.SYSTEM_ID, `rollCollab.${target}`, value) - .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } // Otherwise, first toggle targeted factor to new value @@ -4145,7 +4049,7 @@ class BladesRoll extends BladesTargetLink { } await this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollFactorToggles", factorToggleData) - .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + .then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } async _onSelectChange(event: SelectChangeEvent) { @@ -4161,13 +4065,13 @@ class BladesRoll extends BladesTargetLink { ); } else { await U.EventHandlers.onSelectChange(this, event); - socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + socketlib.system.executeForEveryone("renderRollCollab", this.id); } } async _onTextInputBlur(event: InputChangeEvent) { await U.EventHandlers.onTextInputBlur(this, event); - socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + socketlib.system.executeForEveryone("renderRollCollab", this.id); } async _onGMPopupClick(event: ClickEvent) { @@ -4176,7 +4080,7 @@ class BladesRoll extends BladesTargetLink { * * * */ @@ -4232,9 +4136,9 @@ class BladesRoll extends BladesTargetLink { click: (event) => { const curVal = `${$(event.currentTarget).data("value")}`; if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "effect").then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "effect").then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } } }); @@ -4242,9 +4146,9 @@ class BladesRoll extends BladesTargetLink { click: (event) => { const curVal = `${$(event.currentTarget).data("value")}`; if (curVal === "false") { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "position").then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", "position").then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } else { - this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.rollID)); + this.document.setFlag(C.SYSTEM_ID, "rollCollab.rollPosEffectTrade", false).then(() => socketlib.system.executeForEveryone("renderRollCollab", this.id)); } } }); @@ -4347,15 +4251,15 @@ class BladesRoll extends BladesTargetLink { override async _onSubmit(event: Event, {updateData}: FormApplication.OnSubmitOptions = {}) { const returnVal = await super._onSubmit(event, {updateData, preventClose: true}); - await socketlib.system.executeForEveryone("renderRollCollab", this.rollID); + await socketlib.system.executeForEveryone("renderRollCollab", this.id); return returnVal; } - override async close(options: FormApplication.CloseOptions & {rollID?: string} = {}) { + override async close(options: FormApplication.CloseOptions & {id?: string} = {}) { - if (options.rollID) {return super.close({});} + if (options.id) {return super.close({});} // await this.document.setFlag(C.SYSTEM_ID, "rollCollab", null); - socketlib.system.executeForEveryone("closeRollCollab", this.rollID); + socketlib.system.executeForEveryone("closeRollCollab", this.id); return undefined; } diff --git a/ts/classes/BladesTargetLink.ts b/ts/classes/BladesTargetLink.ts index 816133e2..a4b307e6 100644 --- a/ts/classes/BladesTargetLink.ts +++ b/ts/classes/BladesTargetLink.ts @@ -3,33 +3,52 @@ import U from "../core/utilities"; import C from "../core/constants"; import {BladesActor} from "../documents/BladesActorProxy"; import {BladesItem} from "../documents/BladesItemProxy"; +import BladesChat from "./BladesChat"; +import type {AnyDocumentData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/abstract/data.mjs.d.ts"; - -class BladesTargetLink { +class BladesTargetLink { // #region STATIC METHODS ~ + static IsValidConfig(ref: unknown): ref is BladesTargetLink.Config { + return U.isSimpleObj(ref) + && (U.isDocID(ref.target) + || U.isDocUUID(ref.target) + || U.isDocUUID(ref.targetID) + || ref.target instanceof BladesActor + || ref.target instanceof BladesItem + || ref.target instanceof BladesChat + || ref.target instanceof User + ) + && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) + && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); + } + + static IsValidData(ref: unknown): ref is BladesTargetLink.Data { + return U.isSimpleObj(ref) + && U.isDocID(ref.id) + && U.isDocUUID(ref.targetID) + && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) + && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); + } + /** - * This static method applies defaults to any values missing from the class' data Schema. - * 'Schema' is defined by subclasses to BladesTargetLink. - * Subclasses must override this method to apply their own defaults. + * Parses the configuration object to construct a data object for BladesTargetLink. + * It validates the config object, isolates BladesTargetLink.Config entries from unknown PartialSchema entries, + * determines the targetUUID from target/targetID, confirms the existence of 'targetKey' or 'targetFlagKey', + * applies schema defaults, and constructs the final data object to be returned. * * @template Schema - The data schema required by the subclass. - * @param {Partial} schemaData - Schema data overriding the defaults. - * @returns {Schema} - The schema data with defaults applied. - * @throws {Error} - Throws an error if this method is not overridden in a subclass. + * @param {BladesTargetLink.Config & Partial} config - The configuration object containing potential BladesTargetLink properties and any subclass-specific schema data. + * @returns {BladesTargetLink.Data & Schema} - The constructed data object with defaults applied and necessary properties for BladesTargetLink. + * @throws {Error} - Throws an error if the config object is not a simple object, if it lacks a target reference, if it lacks a valid 'targetKey' or 'targetFlagKey', or if both 'targetKey' and 'targetFlagKey' are provided. */ - static ApplySchemaDefaults( + static #ParseConfig( this: BladesTargetLink.StaticThisContext, - schemaData: Partial - ): Schema { - throw new Error("[BladesTargetLink.ApplySchemaDefaults] Static Method ApplySchemaDefaults must be overridden in subclass"); - } + config: (BladesTargetLink.Config | BladesTargetLink.Data) & Partial + ): BladesTargetLink.Data & Partial { - static ParseConfig( - this: BladesTargetLink.StaticThisContext, - config: BladesTargetLink.Config & Partial - ): BladesTargetLink.Data & Schema { + if (this.IsValidData(config)) { return this.ParseConfig(config); } // === VALIDATE CONFIG === // - Confirm 'config' is proper primitive type @@ -39,6 +58,7 @@ class BladesTargetLink { // - Isolate BladesTargetLink.Config entries from unknown PartialSchema entries const {target, targetID, targetKey, targetFlagKey, ...schemaData} = config; + const partialSchema = schemaData as Partial; // - Attempt to determine targetUUID from target/targetID const targetRef = target ?? targetID; @@ -71,45 +91,84 @@ class BladesTargetLink { throw new Error(`[BladesTargetLink.ResolveConfigToData()] Data has BOTH 'targetKey' and 'targetFlagKey': '${JSON.stringify(config)}'`); } - // === APPLY SCHEMA DEFAULTS === - const schema = this.ApplySchemaDefaults(schemaData as Partial) as Schema; - // === RETURN CONSTRUCTED DATA OBJECT === - return { - ...schema, + // Pass it through public ParseConfigData static method, + // So subclasses can override it to incorporate their own logic. + + return this.ParseConfig({ + ...partialSchema, id: randomID() as IDString, targetID: targetUUID, targetKey, targetFlagKey - }; + }) as BladesTargetLink.Data & Partial; } - static async InitTargetLink( - this: BladesTargetLink.StaticThisContext, - data: BladesTargetLink.Data & Schema - ): Promise { - // Validate target. - const target = fromUuidSync(data.targetID); - if (!target) {throw new Error(`[BladesTargetLink.InitTargetLink] No target found with UUID '${data.targetID}'`);} + /** Subclasses can override this method to include their own parse logic */ + static ParseConfig( + data: BladesTargetLink.Data & Partial + ): BladesTargetLink.Data & Partial { + /* Subclasses can override with custom logic here; + if they don't, the default parsed config is returned */ + return data; + } - // Initialize server-side data on target. - if (data.targetKey) { - await target.update({[`${data.targetKey}.${data.id}`]: data}); - } else if (data.targetFlagKey) { - await (target as BladesItem).setFlag(C.SYSTEM_ID, `${data.targetFlagKey}.${data.id}`, data); + static PartitionSchemaData( + data: BladesTargetLink.Data & Partial + ): BladesTargetLink.Data & {partialSchema: Partial} { + const {id, targetID, targetKey, targetFlagKey, ...schemaData} = data; + if (!id || !targetID || !(targetKey || targetFlagKey)) { + eLog.error("BladesTargetLink", "Bad Constructor Data", {data}); + throw new Error("[new BladesTargetLink()] Bad Constructor Data (see log)"); } - return target; + return {id, targetID, targetKey, targetFlagKey, partialSchema: schemaData as Partial}; } - static async Create( + static #ApplySchemaDefaults( this: BladesTargetLink.StaticThisContext, + schemaData: Partial, + linkData: BladesTargetLink.Data + ): Schema { + return this.ApplySchemaDefaults(schemaData, linkData); + } + + /** + * This static method applies defaults to any values missing from the class' data Schema. + * 'Schema' is defined by subclasses to BladesTargetLink. + * Subclasses must override this method to apply their own defaults. + * + * @template Schema - The data schema required by the subclass. + * @param {Partial} schemaData - Schema data overriding the defaults. + * @returns {Schema} - The schema data with defaults applied. + * @throws {Error} - Throws an error if this method is not overridden in a subclass. + */ + static ApplySchemaDefaults( + this: BladesTargetLink.StaticThisContext, + schemaData: Partial, + linkData: BladesTargetLink.Data + ): Schema { + throw new Error("[BladesTargetLink.ApplySchemaDefaults] Static Method ApplySchemaDefaults must be overridden in subclass"); + } + + /** + * Creates a new instance of BladesTargetLink and initializes it with the provided configuration. + * The configuration is parsed into a data object which is then used to initialize the target link. + * The function logs the parsed data for debugging purposes. + * + * @template Schema - The schema type parameter that extends the data structure. + * @param {BladesTargetLink.Config & Partial} config - The configuration object containing both the target link configuration and the schema configuration. + * + * @returns {Promise & BladesTargetLink.Subclass>} - A promise that resolves to a new instance of BladesTargetLink, initialized with the provided data. + * + * @throws {Error} - Throws an error if the initialization of the target link fails. + */ + static async Create( + this: new (config: BladesTargetLink.Config & Partial) => BladesTargetLink, config: BladesTargetLink.Config & Partial - ): Promise & BladesTargetLink.Subclass> { - const data = this.ParseConfig(config); - eLog.checkLog2("BladesTargetLink.Create", "Config Parsed to Data", {config: U.objClone(config), data: U.objClone(data)}); - await this.InitTargetLink(data); - eLog.checkLog3("BladesTargetLink.Create", "After Init Target Link", {data: U.objClone(data)}); - return new this(data) as BladesTargetLink & BladesTargetLink.Subclass; + ) { + const tLink = new this(config); + await tLink.initTargetLink(); + return tLink; } // #endregion @@ -120,7 +179,7 @@ class BladesTargetLink { private _targetID: UUIDString; private _targetKey?: TargetKey; private _targetFlagKey?: TargetFlagKey; - private _initialData: BladesTargetLink.Data & Schema; + private _initialSchema: Schema; get id() {return this._id;} get targetID() {return this._targetID;} @@ -136,19 +195,27 @@ class BladesTargetLink { ? `${this.targetFlagKey}.${this.id}` as TargetFlagKey : undefined; } - get initialData(): BladesTargetLink.Data & Schema { return this._initialData; } + get linkData(): BladesTargetLink.Data { + return { + id: this.id, + targetID: this.targetID, + targetKey: this.targetKey, + targetFlagKey: this.targetFlagKey + }; + } + get initialSchema(): Schema { return this._initialSchema; } - private _target: BladesDoc; - get target(): BladesDoc { + private _target: BladesLinkDoc; + get target(): BladesLinkDoc { return this._target; } protected get localData(): BladesTargetLink.Data & Schema { if (this._target) { - return this.linkData; + return this.data; } return { - ...this.initialData, + ...this.initialSchema, id: this.id, targetID: this.targetID, targetKey: this.targetKey, @@ -156,84 +223,180 @@ class BladesTargetLink { }; } - protected get linkData() { - let linkData: (BladesTargetLink.Data & Schema) | undefined; - if (this.targetFlagKeyPrefix) { - linkData = this.target.getFlag( - C.SYSTEM_ID, - this.targetFlagKeyPrefix - ) as (BladesTargetLink.Data & Schema) - ?? undefined; - } else if (this.targetKeyPrefix) { - linkData = getProperty(this.target, this.targetKeyPrefix); - } + get data(): BladesTargetLink.Data & Schema { + type TargetData = BladesTargetLink.Data & Schema; + if (this._target) { + let data: TargetData | undefined; + if (this.targetFlagKeyPrefix) { + data = this.target.getFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix) as TargetData | undefined; + } else if (this.targetKeyPrefix) { + data = getProperty(this.target, this.targetKeyPrefix); + } - if (!linkData) { - throw new Error("[BladesTargetLink.linkData] Error retrieving linkData."); - } + if (!data) { + throw new Error("[BladesTargetLink.data] Error retrieving data."); + } - return linkData; + return data; + } else { + eLog.warn("BladesTargetLink", "Attempt to access data of uninitiated BladesTargetLink: Returning local data only.", {bladesTargetLink: this, localData: this.localData}); + return this.localData; + } } - - get data() {return this.linkData;} // #endregion // #region CONSTRUCTOR ~ - constructor( - data: BladesTargetLink.Data & BladesTargetLink.UnknownSchema - ) { - const {id, targetID, targetKey, targetFlagKey} = data; - if (!id || !targetID || !(targetKey || targetFlagKey)) { - eLog.error("BladesTargetLink", "Bad Constructor Data", {data}); - throw new Error("[new BladesTargetLink()] Bad Constructor Data (see log)"); - } - this._id = id; - this._targetID = targetID; - if (U.isTargetKey(targetKey)) { - this._targetKey = targetKey; - } else if (U.isTargetFlagKey(targetFlagKey)) { - this._targetFlagKey = targetFlagKey; + constructor(config: BladesTargetLink.Config & Partial) + constructor(data: BladesTargetLink.Data) + constructor(dataOrConfig: BladesTargetLink.Config & Partial | BladesTargetLink.Data) { + + let linkData: BladesTargetLink.Data; + let schema: Schema; + + // First, we construct the link data from the config or data object. + if (BladesTargetLink.IsValidData(dataOrConfig)) { + + // If a simple link data object was provided, acquire the schema from the source document + linkData = { + id: dataOrConfig.id, + targetID: dataOrConfig.targetID, + targetKey: dataOrConfig.targetKey, + targetFlagKey: dataOrConfig.targetFlagKey + }; + const target = fromUuidSync(dataOrConfig.targetID); + if (!target) { + throw new Error(`[new BladesTargetLink()] Unable to resolve target from uuid '${dataOrConfig.targetID}'`); + } + + if (dataOrConfig.targetKey) { + schema = getProperty(target, `${dataOrConfig.targetKey}.${dataOrConfig.id}`); + } else { + schema = target.getFlag(C.SYSTEM_ID, `${dataOrConfig.targetFlagKey}.${dataOrConfig.id}`) as Schema; + } + } else { + // Otherwise, we have to parse the config into a data object, and extract any schema data + // First we convert the config object to a BladesTargetLink.Data & Partial object. + const partialData: BladesTargetLink.Data & Partial = BladesTargetLink.#ParseConfig(dataOrConfig); + + // Next, we partition the data into the target link data and the schema data. + const {id, targetID, targetKey, targetFlagKey, partialSchema} = BladesTargetLink.PartitionSchemaData(partialData); + + // Now we construct the data object + linkData = {id, targetID, targetKey, targetFlagKey}; + + // And apply any schema defaults to the provided schema data. + schema = BladesTargetLink.#ApplySchemaDefaults(partialSchema, linkData); } + + this._id = linkData.id; + this._targetID = linkData.targetID; + this._targetKey = linkData.targetKey; + this._targetFlagKey = linkData.targetFlagKey; + const target = fromUuidSync(this.targetID); if (!target) { throw new Error(`[new BladesTargetLink()] Unable to resolve target from uuid '${this._targetID}'`); } this._target = target; - this._initialData = JSON.parse(JSON.stringify(data)); + + this._initialSchema = schema; } // #endregion // #region ASYNC UPDATE & DELETE METHODS ~ - async updateTarget(prop: string, val: unknown, isSilent = false) { - if (this.targetFlagKeyPrefix && (this.target as BladesItem).getFlag(C.SYSTEM_ID, `${this.targetFlagKeyPrefix}.${prop}`) !== val) { - await (this.target as BladesItem).setFlag(C.SYSTEM_ID, `${this.targetFlagKeyPrefix}.${prop}`, val); - } else if (this.targetKeyPrefix && (this.target as BladesItem)[`${this.targetKeyPrefix}.${prop}` as KeyOf] !== val) { - await this.target.update({[`${this.targetKeyPrefix}.${prop}`]: val}, {render: false}); + private getDotKeyToProp(prop: string|number|undefined, isNullifying = false): string { + if (this.targetKeyPrefix) { + if (prop === undefined) { + return isNullifying ? `${this.targetKey}.-=${this.id}` : this.targetKeyPrefix; + } + return `${this.targetKeyPrefix}.${isNullifying ? "-=" : ""}${prop}`; + } + if (this.targetFlagKeyPrefix) { + if (prop === undefined) { + return this.targetFlagKeyPrefix; + } + return `${this.targetFlagKeyPrefix}.${prop}`; } + throw new Error("[BladesTargetLink.getDotKeyToProp()] Missing 'targetKeyPrefix' and 'targetFlagKeyPrefix'"); + } + + private getFlagParamsToProp(prop: string|number|undefined) { + return [C.SYSTEM_ID, this.getDotKeyToProp(prop)] as const; } - async updateTargetData(val: T | null, isSilent = false) { + private async updateTargetFlag(prop: string|number|undefined, val: unknown) { + if (!this.targetFlagKeyPrefix) { return; } if (val === null) { - if (this.targetFlagKeyPrefix) { - await (this.target as BladesItem).unsetFlag(C.SYSTEM_ID, `${this.targetFlagKeyPrefix}`); - } else { - await this.target.update({[`${this.targetKey}.-=${this.id}`]: null}); - } - } else { + await this.target.unsetFlag(...this.getFlagParamsToProp(prop)); + } else if (this.target instanceof BladesActor) { + await this.target.setFlag(...this.getFlagParamsToProp(prop), val); + } else if (this.target instanceof BladesItem) { + await this.target.setFlag(...this.getFlagParamsToProp(prop), val); + } else if (this.target instanceof User) { + await this.target.setFlag(...this.getFlagParamsToProp(prop), val); + } else if (this.target instanceof BladesChat) { + await this.target.setFlag(...this.getFlagParamsToProp(prop), val); + } + } + + private async updateTargetKey(prop: string|undefined, val: unknown) { + if (!this.targetKeyPrefix) { return; } + await this.target.update({[this.getDotKeyToProp(prop, val === null)]: val}, {render: false}); + } + + /** + * Initializes a target link by updating the target's data with the provided data object. + * If a targetKey is provided, the data is updated directly on the target. + * If a targetFlagKey is provided, the data is set as a flag on the target. + * + * This method need only be run once, when the document is first created and its data must be written to server storage. + * TargetLink documents whose data already exists in server storage can be constructed directly (i.e. new BladesTargetLink(data)) + * + * @param {BladesTargetLink.Data & Schema} data - The combined data object containing both the target link data and the schema data. + * @returns {Promise} - A promise that resolves when the server update is complete. + */ + async initTargetLink() { + // Construct data object + const data: BladesTargetLink.Data & Schema = { + id: this.id, + targetID: this.targetID, + targetKey: this.targetKey, + targetFlagKey: this.targetFlagKey, + ...this.initialSchema + }; + + // Initialize server-side data on target. + if (data.targetKey) { + await this.target.update({[`${data.targetKey}.${data.id}`]: data}); + } else if (data.targetFlagKey) { + await (this.target as BladesItem).setFlag(C.SYSTEM_ID, `${data.targetFlagKey}.${data.id}`, data); + } + } + + async updateTarget(prop: string, val: unknown, isSilent = false) { + if (getProperty(this.data, prop) === val) { return; } + if (this.targetFlagKeyPrefix) { + await this.updateTargetFlag(prop, val); + } else if (this.targetKeyPrefix) { + await this.updateTargetKey(prop, val); + } + } + + async updateTargetData(val: Partial | null, isSilent = false) { + if (val) { // Add BladesTargetLink.Data to provided schema - const linkData: BladesTargetLink.Data & T = { + val = { ...val, id: this.id, targetID: this.targetID, targetKey: this.targetKey, targetFlagKey: this.targetFlagKey }; - // Update target - if (this.targetFlagKeyPrefix) { - await (this.target as BladesItem).setFlag(C.SYSTEM_ID, this.targetFlagKeyPrefix, linkData); - } else if (this.targetKeyPrefix) { - await this.target.update({[this.targetKeyPrefix]: linkData}, {render: !isSilent}); - } + } + if (this.targetFlagKeyPrefix) { + await this.updateTargetFlag(undefined, val); + } else { + await this.updateTargetKey(undefined, val); } } diff --git a/ts/core/constants.ts b/ts/core/constants.ts index 6a5a780f..caba5736 100644 --- a/ts/core/constants.ts +++ b/ts/core/constants.ts @@ -1348,7 +1348,7 @@ const C = { Vice.Life_Essence, Vice.Electroplasmic_Power ] -}; +} as const; // #endregion // #region RANDOMIZER DATA diff --git a/ts/core/logger.ts b/ts/core/logger.ts index 774ef223..32d9bbf2 100644 --- a/ts/core/logger.ts +++ b/ts/core/logger.ts @@ -51,6 +51,11 @@ const STYLES = { "margin-left": "-100px", padding: "0 100px" }, + warn: { + color: C.Colors.dBLACK, + background: C.Colors.dGOLD, + "font-weight": 500 + }, error: { color: C.Colors.bRED, background: C.Colors.ddRED, @@ -182,6 +187,7 @@ const logger = { checkLog3: (...content: eLogParams) => eLogger("checkLog", ...content, 3), checkLog4: (...content: eLogParams) => eLogger("checkLog", ...content, 4), checkLog5: (...content: eLogParams) => eLogger("checkLog", ...content, 5), + warn: (...content: eLogParams) => eLogger("warn", ...content), error: (...content: eLogParams) => eLogger("error", ...content), hbsLog: (...content: eLogParams) => eLogger("handlebars", ...content) };