diff --git a/src/scss/sheets/_roll-collab-sheet.scss b/src/scss/sheets/_roll-collab-sheet.scss index 4b29cb2..1c1ca63 100644 --- a/src/scss/sheets/_roll-collab-sheet.scss +++ b/src/scss/sheets/_roll-collab-sheet.scss @@ -149,7 +149,7 @@ right: calc(100% + 2*var(--roll-spacing, 5px) - 2px); background: rgb(24, 24, 24); top: 0px; - padding: 2px 0 2px 5px; + padding: 2px 5px; outline: 1px solid var(--blades-white); gap: 0; flex-wrap: wrap; @@ -157,10 +157,7 @@ .roll-mod-container { - &, - & * { - --roll-mod-height: 20px - } + --roll-mod-height: 20px display: inline-flex; flex-direction: row; @@ -168,6 +165,7 @@ background: transparent; box-shadow: none; height: var(--roll-mod-height); + width: 100%; margin: 0; padding: 0; justify-content: flex-start; @@ -199,7 +197,7 @@ &.status-forcedoff { .roll-mod-label { - left: calc(-1.35 * var(--roll-mod-height)); + // left: calc(-1.35 * var(--roll-mod-height)); svg { opacity: 0 @@ -254,7 +252,7 @@ --roll-mod-font-style: normal; --roll-mod-font-weight: normal; --roll-mod-text-transform: none; - --roll-mod-text-indent: 10px; + --roll-mod-text-indent: 0px; --roll-mod-text-shadow: var(--text-shadow-dark); // --roll-mod-sidestring-font-color: var(--roll-mod-font-color); @@ -270,6 +268,7 @@ height: var(--roll-mod-height); width: min-content; + padding: 0px 5px; display: block; flex-grow: 0; flex-shrink: 0; diff --git a/src/ts/@types/blades-roll.d.ts b/src/ts/@types/blades-roll.d.ts index eafdfd2..0e9051e 100644 --- a/src/ts/@types/blades-roll.d.ts +++ b/src/ts/@types/blades-roll.d.ts @@ -11,6 +11,8 @@ import BladesTargetLink from "../classes/BladesTargetLink"; declare global { + type RollResultOrNumber = RollResult|number; + namespace BladesRollMod { // export type Value = string|number|string[]; diff --git a/src/ts/@types/index.d.ts b/src/ts/@types/index.d.ts index a31e57f..24a59f4 100644 --- a/src/ts/@types/index.d.ts +++ b/src/ts/@types/index.d.ts @@ -53,7 +53,7 @@ declare global { declare class ObjectField extends foundry.data.fields.OBJECT_FIELD { } - let _backTrace: List; + // let _backTrace: List; declare function fromUuidSync(uuid: string, options?: { relative?: Document, @@ -61,8 +61,6 @@ declare global { strict?: boolean }): BladesDoc | null; - declare function randomID(): IDString; - declare namespace EunoBlades { diff --git a/src/ts/classes/BladesChat.ts b/src/ts/classes/BladesChat.ts index c501fbb..848ab4c 100644 --- a/src/ts/classes/BladesChat.ts +++ b/src/ts/classes/BladesChat.ts @@ -39,10 +39,7 @@ class BladesChat extends ChatMessage { static Initialize() { - const backTraceID = U.markBackTrace(BladesChat as AnyClass); - Hooks.on("renderChatMessage", (msg: BladesChat, html: JQuery) => { - eLog.backTrace(U.runBackTrace(backTraceID), "hooks", "renderChatMessage", {msg, html}); ApplyTooltipAnimations(html); if (msg.isBladesRoll) { html.addClass("blades-chat-message"); diff --git a/src/ts/classes/BladesRoll.ts b/src/ts/classes/BladesRoll.ts index 4f553dd..d4f32a8 100644 --- a/src/ts/classes/BladesRoll.ts +++ b/src/ts/classes/BladesRoll.ts @@ -593,22 +593,26 @@ class BladesRollMod extends BladesTargetLink { ) { return; } - const oldStatus = this.statusReport; - this.updateTarget("user_status", val).then(() => { - eLog.checkLog3("rollModStatus", `[set USER] ${this.name} Status Change: ${oldStatus["!STATUS"]} -> ${this.status} (val = ${val})`, { - from: oldStatus.comps, - to: this.statusReport.comps - }); - }); } + const oldStatus = this.statusReport; + this.updateTarget("user_status", val).then(() => { + eLog.checkLog3("rollModStatus", `[set USER] ${this.name} Status Change: ${oldStatus["!STATUS"]} -> ${this.status} (val = ${val})`, { + from: oldStatus.comps, + to: this.statusReport.comps + }); + }); } - // @ts-expect-error Why aren't I able to simply pass the function parameters through to the superclass? - override async updateTarget(...args: Parameters["updateTarget"]>) { + async silentUpdateTarget(...args: Parameters["updateTarget"]>) { await super.updateTarget(...args); + } + + override async updateTarget(...args: unknown[]) { + await super.updateTarget(...args as Parameters["updateTarget"]>); this.rollInstance.renderRollCollab_SocketCall(); } + get elem$() { return this.rollInstance.elem$.find(`#${this.id}`); } get elem() { return this.elem$[0]; } @@ -619,16 +623,16 @@ class BladesRollMod extends BladesTargetLink { }; } get baseStatus(): RollModStatus {return this.data.base_status;} - get heldStatus(): RollModStatus | null | undefined {return this.data.held_status;} - set heldStatus(val: RollModStatus | undefined) { + _heldStatus: RollModStatus | null | undefined; + get heldStatus(): RollModStatus | null | undefined {return this._heldStatus;} + set heldStatus(val: RollModStatus | null | undefined) { if (val === this.heldStatus) { return; } const oldStatus = this.statusReport; - this.updateTarget("held_status", val || null).then(() => { - eLog.checkLog3("rollModStatus", `[set HELD] ${this.name} Status Change: ${oldStatus["!STATUS"]} -> ${this.status} (val = ${val})`, { - from: oldStatus.comps, - to: this.statusReport.comps - }); + eLog.checkLog3("set heldStatus", `[set HELD] ${this.name} Status Change: ${oldStatus["!STATUS"]} -> ${this.status} (val = ${val})`, { + from: oldStatus.comps, + to: this.statusReport.comps }); + this._heldStatus = val; } get value(): number {return this.data.value;} @@ -943,14 +947,16 @@ class BladesRollOpposition implements BladesRoll.OppositionData { // #region Static Methods ~ static IsValidData(data: unknown): data is BladesRoll.OppositionData { if (BladesRollOpposition.IsDoc(data)) {return true;} - return U.isList(data) - && typeof data.rollOppName === "string" - && typeof data.rollOppType === "string" - && typeof data.rollOppImg === "string" - && (!data.rollOppSubName || typeof data.rollOppSubName === "string") - && (!data.rollOppModsSchemaSet || Array.isArray(data.rollOppModsSchemaSet)) - && U.isList(data.rollFactors) - && (!data.rollOppID || typeof data.rollOppID === "string"); + if (!U.isList(data)) { return false; } + if ([ + typeof data.rollOppName, + typeof data.rollOppType, + typeof data.rollOppImg + ].some((type: string) => type !== "string")) { return false; } + if (!Array.isArray(data.rollOppModsSchemaSet)) { return false; } + if (!U.isList(data.rollFactors)) { return false; } + if (data.rollOppID && typeof data.rollOppID !== "string") { return false; } + return true; } static GetDoc(docRef: unknown): BladesRoll.OppositionDoc | false { @@ -1101,14 +1107,17 @@ class BladesRollParticipant implements BladesRoll.ParticipantData { // #region Static Methods ~ static IsValidData(data: unknown): data is BladesRoll.ParticipantData { if (BladesRollParticipant.IsDoc(data)) {return true;} - return U.isList(data) - && typeof data.rollParticipantName === "string" - && typeof data.rollParticipantType === "string" - && typeof data.rollParticipantIcon === "string" - && (!data.rollParticipantModsSchemaSet || Array.isArray(data.rollParticipantModsSchemaSet)) - && U.isList(data.rollFactors) - && (!data.rollParticipantID || typeof data.rollParticipantID === "string") - && (!data.rollParticipantDoc || BladesRollParticipant.IsDoc(data.rollParticipantDoc)); + if (!U.isList(data)) { return false; } + if ([ + typeof data.rollParticipantName, + typeof data.rollParticipantType, + typeof data.rollParticipantIcon + ].some((type: string) => type !== "string")) { return false; } + if (!Array.isArray(data.rollParticipantModsSchemaSet)) { return false; } + if (!U.isList(data.rollFactors)) { return false; } + if (data.rollParticipantID && typeof data.rollParticipantID !== "string") { return false; } + if (data.rollParticipantDoc && !BladesRollParticipant.IsDoc(data.rollParticipantDoc)) { return false; } + return true; } static GetDoc(docRef: unknown): BladesRoll.ParticipantDoc | false { @@ -1383,7 +1392,7 @@ class BladesRoll extends BladesTargetLink { return []; } - static GetDieClass(rollType: RollType, rollResult: number | false | RollResult, dieVal: number, dieIndex: number) { + static GetDieClass(rollType: RollType, rollResult: RollResultOrNumber | false, dieVal: number, dieIndex: number) { switch (rollType) { case RollType.Resistance: { if (dieVal === 6 && dieIndex <= 1 && rollResult === -1) { @@ -1420,7 +1429,6 @@ class BladesRoll extends BladesTargetLink { static GetDieImage( rollType: RollType, - rollResult: number | false | RollResult, dieVal: number, dieIndex: number, isGhost = false, @@ -1551,12 +1559,10 @@ class BladesRoll extends BladesTargetLink { if (BladesRollParticipant.IsDoc(pData)) { return pData; } - if (BladesRollParticipant.IsValidData(pData)) { - if (typeof pData.rollParticipantID === "string") { - const pDoc = game.actors.get(pData.rollParticipantID) ?? game.items.get(pData.rollParticipantID); - if (BladesRollParticipant.IsDoc(pDoc)) { - return pDoc; - } + if (BladesRollParticipant.IsValidData(pData) && typeof pData.rollParticipantID === "string") { + const pDoc = game.actors.get(pData.rollParticipantID) ?? game.items.get(pData.rollParticipantID); + if (BladesRollParticipant.IsDoc(pDoc)) { + return pDoc; } } // Throw an error with sufficient debug data if pData does not match any expected types @@ -1893,6 +1899,22 @@ class BladesRoll extends BladesTargetLink { projectSelectOptions?: Array>; + _getSectionParticipants( + rollSection: BladesRoll.ParticipantSection, + rollParticipantList: BladesRoll.RollParticipantDataSet + ) { + const sectionParticipants: Record = {}; + for (const [participantType, participantData] of Object.entries(rollParticipantList)) { + sectionParticipants[participantType] = new BladesRollParticipant( + this, + rollSection, + participantType as BladesRoll.ParticipantSubSection, + participantData as BladesRoll.ParticipantData + ); + } + return sectionParticipants; + } + constructor(config: BladesRoll.Config) constructor(data: BladesTargetLink.Data & Partial) constructor(dataOrConfig: BladesRoll.Config | BladesTargetLink.Data & Partial) { @@ -1911,16 +1933,10 @@ class BladesRoll extends BladesTargetLink { 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 = {}; - for (const [participantType, participantData] of Object.entries(rollParticipantList)) { - sectionParticipants[participantType] = new BladesRollParticipant( - this, - rollSection as BladesRoll.ParticipantSection, - participantType as BladesRoll.ParticipantSubSection, - participantData as BladesRoll.ParticipantData - ); - } - this._rollParticipants[rollSection as BladesRoll.ParticipantSection] = sectionParticipants; + this._rollParticipants[rollSection as BladesRoll.ParticipantSection] = this._getSectionParticipants( + rollSection as BladesRoll.ParticipantSection, + rollParticipantList + ); } } } @@ -1940,12 +1956,14 @@ class BladesRoll extends BladesTargetLink { rollSubSection = "Assist"; } - const participantData = typeof participantRef === "string" - ? game.actors.get(participantRef) - ?? game.actors.getName(participantRef) - ?? game.items.get(participantRef) - ?? game.items.getName(participantRef) - : participantRef; + function getParticipantData(pRef: typeof participantRef) { + if (typeof pRef === "string") { + return game.actors.get(pRef) ?? game.items.get(pRef) ?? game.actors.getName(pRef) ?? game.items.getName(pRef); + } + return pRef; + } + const participantData = getParticipantData(participantRef); + if (!BladesRollParticipant.IsValidData(participantData)) { throw new Error("Bad data."); } @@ -2085,7 +2103,7 @@ class BladesRoll extends BladesTargetLink { }; } - get rollType(): RollType {return this.data.rollType as RollType;} + get rollType(): RollType {return this.data.rollType;} get rollSubType(): RollSubType | undefined {return this.data.rollSubType;} @@ -2277,7 +2295,7 @@ class BladesRoll extends BladesTargetLink { + (this.tempGMBoosts.Result ?? 0); } - get rollResultFinal(): RollResult | number | false { + get rollResultFinal(): RollResultOrNumber | false { if (this.rollResult === false) {return false;} if (this.rollResultDelta === 0) {return this.rollResult;} @@ -2439,10 +2457,11 @@ class BladesRoll extends BladesTargetLink { this.rollTraitValOverride = undefined; this.rollFactorPenaltiesNegated = {}; this.tempGMBoosts = {}; + this.rollMods.forEach((rollMod) => rollMod.heldStatus = undefined); // ESLINT DISABLE: Dev Code. // eslint-disable-next-line @typescript-eslint/no-explicit-any - const initReport: Record = {}; + const initReport: Record = {}; let initReportCount = 0; const watchMod = (label: string) => { @@ -3158,7 +3177,7 @@ class BladesRoll extends BladesTargetLink { get dieVals(): number[] { return (this.roll.terms as DiceTerm[])[0].results .map((result) => result.result) - .sort() + .sort((a, b) => (a - b)) .reverse(); // return this._dieVals; } @@ -3177,14 +3196,14 @@ class BladesRoll extends BladesTargetLink { const diceData: BladesRoll.DieData[] = dieVals.map((val, i) => ({ value: val, dieClass: BladesRoll.GetDieClass(this.rollType, this.rollResult, val, i), - dieImage: BladesRoll.GetDieImage(this.rollType, this.rollResult, val, i, false, isCritical) + dieImage: BladesRoll.GetDieImage(this.rollType, val, i, false, isCritical) })); if (ghostNum) { diceData.push({ value: ghostNum, dieClass: "blades-die-ghost", - dieImage: BladesRoll.GetDieImage(this.rollType, this.rollResult, ghostNum, diceData.length, true, false) + dieImage: BladesRoll.GetDieImage(this.rollType, ghostNum, diceData.length, true, false) }); } @@ -3599,6 +3618,7 @@ class BladesRoll extends BladesTargetLink { const modID = elem$.attr("id") as IDString; const mod = this.getRollModByID(modID); if (!mod) {throw new Error(`Unable to find roll mod with id '${modID}'`);} + eLog.checkLog3("playerToggleRollMod", "BEFORE _onPlayerToggleRollMod", {modID, modStatus: mod.status, baseStatus: mod.baseStatus, userStatus: mod.userStatus, heldStatus: mod.heldStatus}); switch (mod.status) { case RollModStatus.ToggledOff: { mod.userStatus = RollModStatus.ToggledOn; @@ -3610,6 +3630,7 @@ class BladesRoll extends BladesTargetLink { } default: break; } + eLog.checkLog3("playerToggleRollMod", "AFTER _onPlayerToggleRollMod", {modID, modStatus: mod.status, baseStatus: mod.baseStatus, userStatus: mod.userStatus, heldStatus: mod.heldStatus}); } _onGMToggleRollMod(event: ClickEvent) { event.preventDefault(); diff --git a/src/ts/classes/BladesTargetLink.ts b/src/ts/classes/BladesTargetLink.ts index 9a0a639..6334659 100644 --- a/src/ts/classes/BladesTargetLink.ts +++ b/src/ts/classes/BladesTargetLink.ts @@ -18,26 +18,39 @@ class BladesTargetLink { ] as const; } + static HasValidID(ref: Record) { + return U.isDocID(ref.target) || U.isDocID(ref.targetID); + } + + static HasValidUUID(ref: Record) { + return U.isDocUUID(ref.target) || U.isDocUUID(ref.targetID); + } + + static HasValidKey(ref: Record) { + // Return false if ref has neither targetKey nor targetFlagKey + if (!U.isTargetKey(ref.targetKey) && !U.isTargetFlagKey(ref.targetFlagKey)) { return false; } + // Return false if ref has BOTH targetKey and targetFlagKey + if (U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)) { return false; } + // Return true if exactly one is set + return true; + } + + static HasValidTargetRef(ref: Record) { + return this.HasValidID(ref) + || this.HasValidUUID(ref) + || this.ValidTargetClasses.some((cls) => ref.target instanceof cls); + } + static IsValidConfig(ref: unknown): ref is BladesTargetLink.Config { - return U.isSimpleObj(ref) - && ( - U.isDocID(ref.target) - || U.isDocUUID(ref.target) - || U.isDocID(ref.targetID) - || U.isDocUUID(ref.targetID) - || this.ValidTargetClasses.some((cls) => ref.target instanceof cls) - ) - && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) - && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); + if (!U.isSimpleObj(ref)) { return false; } + return this.HasValidKey(ref) && this.HasValidTargetRef(ref); } static IsValidData(ref: unknown): ref is BladesTargetLink.Data { - return U.isSimpleObj(ref) - && U.isDocID(ref.id) + if (!U.isSimpleObj(ref)) { return false; } + return U.isDocID(ref.id) && U.isDocUUID(ref.targetID) - && (U.isTargetKey(ref.targetKey) || U.isTargetFlagKey(ref.targetFlagKey)) - && !(U.isTargetKey(ref.targetKey) && U.isTargetFlagKey(ref.targetFlagKey)); - // && (typeof ref.isScopingById === "boolean"); + && this.HasValidKey(ref); } static #ParseChildLinkData( @@ -251,13 +264,13 @@ class 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. + * @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 + _schemaData: Partial ): Schema { throw new Error("[BladesTargetLink.ApplySchemaDefaults] Static Method ApplySchemaDefaults must be overridden in subclass"); } @@ -275,8 +288,8 @@ class BladesTargetLink { * @throws {Error} - Throws an error if the initialization of the target link fails. */ static async Create, - parentLinkData?: BladesTargetLink.Data & Schema + configType: BladesTargetLink.Config & Partial, + parentLinkDataType?: BladesTargetLink.Data & Schema ) => BladesTargetLink, Schema>( this: C, config: BladesTargetLink.Config & Partial, @@ -291,12 +304,12 @@ class BladesTargetLink { // #region GETTERS ~ get isGM() {return game.user.isGM;} - private _id: IDString; - private _targetID: UUIDString; - private _targetKey?: TargetKey; - private _targetFlagKey?: TargetFlagKey; - private _isScopingById = true; - private _initialSchema: Schema; + private readonly _id: IDString; + private readonly _targetID: UUIDString; + private readonly _targetKey?: TargetKey; + private readonly _targetFlagKey?: TargetFlagKey; + private readonly _isScopingById = true; + private readonly _initialSchema: Schema; get id() { return this._id; } get targetID() { return this._targetID; } @@ -354,7 +367,7 @@ class BladesTargetLink { } - private _target: BladesLinkDoc; + private readonly _target: BladesLinkDoc; get target(): BladesLinkDoc { return this._target; } protected get localData(): BladesTargetLink.Data & Schema { @@ -409,14 +422,14 @@ class BladesTargetLink { if (subclassConstructor.IsValidData(dataOrConfig)) { // If a simple link data object was provided, acquire the schema from the source document ({linkData} = subclassConstructor.PartitionSchemaData(dataOrConfig)); - const target = fromUuidSync(linkData.targetID); - if (!target) { + const linkTarget = fromUuidSync(linkData.targetID); + if (!linkTarget) { throw new Error(`[new BladesTargetLink()] Unable to resolve target from uuid '${linkData.targetID}'`); } if ("targetKey" in linkData) { - schema = getProperty(target, `${linkData.targetKey}.${linkData.id}`); + schema = getProperty(linkTarget, `${linkData.targetKey}.${linkData.id}`); } else { - schema = target.getFlag(C.SYSTEM_ID, `${linkData.targetFlagKey}.${linkData.id}`) as Schema; + schema = linkTarget.getFlag(C.SYSTEM_ID, `${linkData.targetFlagKey}.${linkData.id}`) as Schema; } // Set the isInitPromiseResolved flag to true this.isInitPromiseResolved = true; @@ -427,7 +440,7 @@ class BladesTargetLink { const parsedData = BladesTargetLink.#ParseConfigToData( dataOrConfig, parentLinkData - ) as BladesTargetLink.Data & Partial; + ); // Next we separate the linkData and the schemaData from the parsedData object. let partialSchema: Partial; @@ -456,7 +469,7 @@ class BladesTargetLink { // #endregion // #region ASYNC UPDATE & DELETE METHODS ~ - private getDotKeyToProp(prop: string|number|undefined, isNullifying = false): string { + private getDotKeyToProp(prop: Maybe, isNullifying = false): string { if (this.targetKeyPrefix) { if (prop === undefined) { return isNullifying ? this.targetKeyNullPrefix as TargetKey : this.targetKeyPrefix; @@ -472,11 +485,11 @@ class BladesTargetLink { throw new Error("[BladesTargetLink.getDotKeyToProp()] Missing 'targetKeyPrefix' and 'targetFlagKeyPrefix'"); } - private getFlagParamsToProp(prop: string|number|undefined) { + private getFlagParamsToProp(prop: Maybe) { return [C.SYSTEM_ID, this.getDotKeyToProp(prop)] as const; } - private async updateTargetFlag(prop: string|number|undefined, val: unknown) { + private async updateTargetFlag(prop: Maybe, val: unknown) { if (!this.targetFlagKeyPrefix) { return; } if (val === null) { await this.target.unsetFlag(...this.getFlagParamsToProp(prop)); @@ -542,7 +555,7 @@ class BladesTargetLink { resolve(); }).catch(reject); } else { - reject(); + reject(new Error(`[BladesTargetLink.#updateTargetViaMerge] Unable to update target data for BladesTargetLink id '${this.id}': Missing both 'targetKeyPrefix' and 'targetFlagKeyPrefix'`)); } }); @@ -553,7 +566,7 @@ class BladesTargetLink { await U.waitFor(waitFor); if (this.targetKeyPrefix) { // First, prepend targetKeyPrefix or targetFlagKeyPrefix (as appropriate) to each key of updateData - updateData = U.objMap(updateData, false, (key) => `${this.targetKeyPrefix || this.targetFlagKeyPrefix}.${String(key)}`) as Record; + updateData = U.objMap(updateData, false, (key) => `${this.targetKeyPrefix ?? this.targetFlagKeyPrefix}.${String(key)}`) as Record; return this.target.update(updateData, {render: false}); } else if (this.targetFlagKeyPrefix) { // We must retrieve the existing flag data, flattenObject it, then merge it with updateData @@ -573,6 +586,8 @@ class BladesTargetLink { return this.target.update({[`${this.targetKeyPrefix}.${prop}`]: val}); } else if (this.targetFlagKeyPrefix) { return this.updateTargetFlag(prop, val); + } else { + throw new Error(`[BladesTargetLink.#updateTargetPropVal] Unable to update target data for BladesTargetLink id '${this.id}': Missing both 'targetKeyPrefix' and 'targetFlagKeyPrefix'`); } } @@ -580,20 +595,19 @@ class BladesTargetLink { async updateTarget(prop: string, val: unknown, waitFor?: Promise|gsapAnim): Promise async updateTarget( propOrData: string | Record, - valOrWaitFor?: unknown | Promise|gsapAnim, + valOrWaitFor?: unknown, waitFor?: Promise|gsapAnim ): Promise { // If the provided data is an object, we assume it is a full data object and we update the target with it. if (typeof propOrData === "string") { - if (getProperty(this.data, propOrData) === valOrWaitFor) { return; } + if (getProperty(this.data, propOrData) === valOrWaitFor) { return Promise.resolve(); } return this.#updateTargetPropVal(propOrData, valOrWaitFor, waitFor); } if (typeof propOrData === "object") { return this.#updateTargetViaMerge(propOrData, valOrWaitFor as Promise|gsapAnim|undefined); - } else { - throw new Error(`[BladesTargetLink.updateTarget()] Bad updateData for id '${this.id}': ${propOrData}`); } + throw new Error(`[BladesTargetLink.updateTarget()] Bad updateData for id '${this.id}': ${propOrData}`); } async updateTargetData(val: Partial | null, waitFor?: Promise|gsapAnim) { diff --git a/src/ts/core/logger.ts b/src/ts/core/logger.ts index fd76cac..48625f0 100644 --- a/src/ts/core/logger.ts +++ b/src/ts/core/logger.ts @@ -1,14 +1,24 @@ import U from "./utilities"; import {getColor} from "./helpers"; -const LOGGERCONFIG = { - fullName: "eLogger", - aliases: ["dbLog"], - stackTraceExclusions: { - handlebars: [/scripts\/handlebars/] // From internal Handlebars module - } -}; +// const LOGGERCONFIG = { +// fullName: "eLogger", +// aliases: ["dbLog"], +// stackTraceExclusions: { +// handlebars: [/scripts\/handlebars/] // From internal Handlebars module +// } +// }; +const STACK_TRACE_EXCLUSIONS = [ + /at Logger/, + /\beLog\b/, + /scripts\/handlebars/ // From internal Handlebars module +]; + +const STACK_TRACE_PROJECT_CODE = [ + /\/systems\//, + /\/modules\// +]; const STYLES = { base: { @@ -86,50 +96,48 @@ const STYLELINES = Object.fromEntries( ]) ); +/** + * Runs a stack trace from the calling scope. If an ID is passed, it will be used to identify a previously-recorded trace from another scope. Both stack traces will be parsed and combined into a readable description of the caller's call chain. + * @param caller - The calling class or function. + * @param id - Optional ID string for a previously-recorded backtrace. If not provided, the standard stack trace will not be modified by a backtrace. + * @returns The combined and parsed full stack trace of the caller's call chain. + */ +// const runBackTrace = (id?: IDString): string => { +// if (!stackTrace) { return "StackTrace Unavailable"; } +// if (id && _backTrace[id]) { +// const backTrace = _backTrace[id].split("\n"); +// const parsedTrace = stackTrace.split("\n").map((line, i) => { +// if (backTrace[i]) { +// return `${line} // ${backTrace[i]}`; +// } +// return line; +// }); +// return parsedTrace.join("\n"); +// } +// return stackTrace; +// }; + +type DebugLevel = 0|1|2|3|4|5; +function isDebugLevel(level: unknown): level is DebugLevel { + return typeof level === "number" && [0, 1, 2, 3, 4, 5].includes(level); +} + const eLogger = (type: "checkLog"|"log"|KeyOf = "base", ...content: [string, ...unknown[]]) => { - if (!(["error", "display"].includes(type) || CONFIG.debug.logging)) { return; } - const lastElem = U.getLast(content); - let trace: string|null = null; - if (content[0].startsWith("StackTrace:")) { - trace = (content.shift() as string)?.replace("StackTrace:", "") ?? null; - } + if (!CONFIG.debug.logging && type !== "display" && type !== "error") { return; } + + const dbLevel: DebugLevel = isDebugLevel(U.getLast(content)) ? content.pop() as DebugLevel : 3; + if (((U.getSetting("debugLevel", "debugSettings") ?? 5) as DebugLevel) < dbLevel) { return; } - let dbLevel: 0|1|2|3|4|5 = typeof lastElem === "number" && [0, 1, 2, 3, 4, 5].includes(lastElem) - ? content.pop() as 0|1|2|3|4|5 - : 3; - let key: string|false = false; if (type === "checkLog") { - key = content.shift() as string; - type = `log${dbLevel}`; + content.shift(); + type = "log"; } - const [message, ...data] = content; - - if (key) { - const validKey: string = key; - const blacklist = ((U.getSetting("blacklist", "debugSettings") ?? "") as string).split(/,/).map((pat) => new RegExp(`\\b${pat.trim()}\\b`, "igu")); - const whitelist = ((U.getSetting("whitelist", "debugSettings") ?? "") as string).split(/,/).map((pat) => new RegExp(`\\b${pat.trim()}\\b`, "igu")); - const isBlack = blacklist.some((pat) => pat.test(validKey)); - const isWhite = whitelist.some((pat) => pat.test(validKey)); - if (isBlack && !isWhite) { - dbLevel = Math.max(4, Math.min(5, dbLevel + 2)) as 4|5; - } - if (isWhite && !isBlack) { - dbLevel = Math.min(3, Math.max(1, dbLevel - 2)) as 1|2|3; - } - } - if ((U.getSetting("debugLevel", "debugSettings") ?? 5) as 0|1|2|3|4|5 < dbLevel) { return; } if (type === "log") { type = `${type}${dbLevel}`; } - let stackTrace: string|null; - if (type === "display") { - stackTrace = null; - } else if (trace) { - stackTrace = filterStackTrace(trace, LOGGERCONFIG.stackTraceExclusions[type as KeyOf] ?? []); - } else { - stackTrace = getStackTrace(LOGGERCONFIG.stackTraceExclusions[type as KeyOf] ?? []); - } + + const stackTrace = type === "display" ? null : getStackTrace(); let logFunc; if (stackTrace) { @@ -163,54 +171,86 @@ const eLogger = (type: "checkLog"|"log"|KeyOf = "base", ...conten } console.groupEnd(); + function filterStackTrace(traceString: string): string { + const trace = traceString + .split(/\n/) + .slice(1) + .filter((sLine) => !STACK_TRACE_EXCLUSIONS.some((rTest) => rTest.test(sLine))) + .map((sLine, i, arr) => { + let sL = sLine.trim(); + if (sL.includes("Object.fn")) { + if (arr[i + 1]?.includes("at #call ")) { + sL = sL.replace(/at Object\.fn (\(.*?([^/.]+)[^/]*\)\s*)$/, "at $2 Hook $1"); + } else { + sL = sL.replace(/at((?: async)? Object\.fn \(.*?([^/.]+(?:\.\w+)?)[^/]*\)\s*)$/, "at $2$1"); + } + } + if (i === 0) { return `${sL.replace(/^at/, "LOGGED AT")}`; } + return ` ${!STACK_TRACE_PROJECT_CODE.some((rTest) => rTest.test(sL)) ? " ..." : ">>>"} ${sL.replace(/\bat /, "")}`; + }); - function parseStackTrace(traceString: string) { - return traceString.split(/\n/).map((ln) => { - const [_full, method, file, line, col] = ln.match(/at (.+) \((.+):(\d+):(\d+)\)/) ?? []; - return {method, file, line, col}; - }); - } + // if (trace.length === 0) { return traceString; } + // trace[0] = `${trace[0].replace(/^\s*[.>]{3} /, "LOGGED AT ")}`; - function filterStackTrace(traceString: string, regExpFilters: RegExp[] = []): string { - regExpFilters.push(new RegExp(`at (getStackTrace|${LOGGERCONFIG.fullName}|${ - LOGGERCONFIG.aliases.map(String).join("|") - }|Object\\.(log|display|hbsLog|error))`), /^Error/); - return traceString - .split(/\n/) - .map((sLine) => sLine.trim()) - .filter((sLine) => !regExpFilters.some((rTest) => rTest.test(sLine))) - .join("\n"); + return trace.join("\n"); } - /** - * - * @param regExpFilters - */ - function getStackTrace(regExpFilters: RegExp[] = []): string|null { - return filterStackTrace((new Error()).stack ?? "", regExpFilters); + + function getStackTrace(): string { + const trace = new Error(); + Error.captureStackTrace(trace, eLogger); + return trace.stack + ? filterStackTrace(trace.stack) + : "... Stack Trace Unavailable ..."; } }; type eLogParams = [string, ...unknown[]]; -const logger = { - display: (...content: eLogParams) => eLogger("display", ...content), - log0: (...content: eLogParams) => eLogger("log", ...content, 0), - log1: (...content: eLogParams) => eLogger("log", ...content, 1), - log2: (...content: eLogParams) => eLogger("log", ...content, 2), - log: (...content: eLogParams) => eLogger("log", ...content, 3), - log3: (...content: eLogParams) => eLogger("log", ...content, 3), - log4: (...content: eLogParams) => eLogger("log", ...content, 4), - log5: (...content: eLogParams) => eLogger("log", ...content, 5), - checkLog0: (...content: eLogParams) => eLogger("checkLog", ...content, 0), - checkLog1: (...content: eLogParams) => eLogger("checkLog", ...content, 1), - checkLog2: (...content: eLogParams) => eLogger("checkLog", ...content, 2), - checkLog: (...content: eLogParams) => eLogger("checkLog", ...content, 3), - 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), - backTrace: (...content: eLogParams) => eLogger("checkLog", `StackTrace:${String(content.shift() ?? "")}`, ...content, 3) -}; -export default logger; + +function checkLog3(...content: eLogParams) { + eLogger("checkLog", ...content, 3); +} + +class Logger { + static display(...content: eLogParams) { eLogger("display", ...content); } + static log0(...content: eLogParams) { eLogger("log", ...content, 0); } + static log1(...content: eLogParams) { eLogger("log", ...content, 1); } + static log2(...content: eLogParams) { eLogger("log", ...content, 2); } + static log(...content: eLogParams) { eLogger("log", ...content, 3); } + static log3(...content: eLogParams) { eLogger("log", ...content, 3); } + static log4(...content: eLogParams) { eLogger("log", ...content, 4); } + static log5(...content: eLogParams) { eLogger("log", ...content, 5); } + static checkLog0(...content: eLogParams) { eLogger("checkLog", ...content, 0); } + static checkLog1(...content: eLogParams) { eLogger("checkLog", ...content, 1); } + static checkLog2(...content: eLogParams) { eLogger("checkLog", ...content, 2); } + static checkLog(...content: eLogParams) { eLogger("checkLog", ...content, 3); } + static checkLog3(...content: eLogParams) { eLogger("checkLog", ...content, 3); } + static checkLog4(...content: eLogParams) { eLogger("checkLog", ...content, 4); } + static checkLog5(...content: eLogParams) { eLogger("checkLog", ...content, 5); } + static warn(...content: eLogParams) { eLogger("warn", ...content); } + static error(...content: eLogParams) { eLogger("error", ...content); } + static hbsLog(...content: eLogParams) { eLogger("handlebars", ...content); } +} + +// const logger = { +// display: (...content: eLogParams) => eLogger("display", ...content), +// log0: (...content: eLogParams) => eLogger("log", ...content, 0), +// log1: (...content: eLogParams) => eLogger("log", ...content, 1), +// log2: (...content: eLogParams) => eLogger("log", ...content, 2), +// log: (...content: eLogParams) => eLogger("log", ...content, 3), +// log3: (...content: eLogParams) => eLogger("log", ...content, 3), +// log4: (...content: eLogParams) => eLogger("log", ...content, 4), +// log5: (...content: eLogParams) => eLogger("log", ...content, 5), +// checkLog0: (...content: eLogParams) => eLogger("checkLog", ...content, 0), +// checkLog1: (...content: eLogParams) => eLogger("checkLog", ...content, 1), +// checkLog2: (...content: eLogParams) => eLogger("checkLog", ...content, 2), +// checkLog: (...content: eLogParams) => eLogger("checkLog", ...content, 3), +// 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) +// }; + +export default Logger; diff --git a/src/ts/core/utilities.ts b/src/ts/core/utilities.ts index bd8d356..9ae8e0e 100644 --- a/src/ts/core/utilities.ts +++ b/src/ts/core/utilities.ts @@ -1864,43 +1864,6 @@ const testFuncPerformance = ( runFunc(); // Start the first call to 'func' }; - -/** - * Marks a backtrace for debugging purposes. - * - * @param caller - The calling class or function. - * @param id - Optional ID string for the backtrace. If not provided, a new ID will be generated. - * @returns The ID of the backtrace. - */ -const markBackTrace = (caller: AnyClass, id?: IDString) => { - id ??= getID(); - const stackTrace = new Error().stack?.split("\n").slice(1).join("\n") ?? `... StackTrace from ${caller.name} Unavailable ...`; - eLog.checkLog3(id ?? "backTrace", caller.name, {id, stackTrace}); - _backTrace[id] = stackTrace; - return id; -}; - -/** - * Runs a stack trace from the calling scope. If an ID is passed, it will be used to identify a previously-recorded trace from another scope. Both stack traces will be parsed and combined into a readable description of the caller's call chain. - * @param caller - The calling class or function. - * @param id - Optional ID string for a previously-recorded backtrace. If not provided, the standard stack trace will not be modified by a backtrace. - * @returns The combined and parsed full stack trace of the caller's call chain. - */ -const runBackTrace = (id?: IDString): string => { - const stackTrace = new Error().stack?.split("\n").slice(1).join("\n"); - if (!stackTrace) { return "StackTrace Unavailable"; } - if (id && _backTrace[id]) { - const backTrace = _backTrace[id].split("\n"); - const parsedTrace = stackTrace.split("\n").map((line, i) => { - if (backTrace[i]) { - return `${line} // ${backTrace[i]}`; - } - return line; - }); - return parsedTrace.join("\n"); - } - return stackTrace; -}; // #endregion // #region ░░░░░░░[GreenSock]░░░░ Wrappers for GreenSock Functions ░░░░░░░ ~ @@ -2242,7 +2205,7 @@ export default { extractComputedStyles, compareComputedStyles, // ████████ PERFORMANCE & DEBUG: Debugging Functions, Performance Testing & Metrics ████████ - testFuncPerformance, markBackTrace, runBackTrace, + testFuncPerformance, // ░░░░░░░ GreenSock ░░░░░░░ gsap, get, set, getGSAngleDelta, getNearestLabel, reverseRepeatingTimeline, /* to, from, fromTo, */ diff --git a/src/ts/documents/BladesActiveEffect.ts b/src/ts/documents/BladesActiveEffect.ts index 1f9fcd6..487d97d 100644 --- a/src/ts/documents/BladesActiveEffect.ts +++ b/src/ts/documents/BladesActiveEffect.ts @@ -15,7 +15,6 @@ const CUSTOMFUNCS: Record< (actor: BladesActor, funcData: string, effect?: BladesActiveEffect, isReversing?: boolean) => Promise > = { addItem: async (actor: BladesActor, funcData: string, _, isReversing = false) => { - eLog.checkLog("activeEffects", "addItem", {actor, funcData, isReversing}); if (actor.hasActiveSubItemOf(funcData)) { if (isReversing) { return actor.remSubItem(funcData); @@ -26,7 +25,6 @@ const CUSTOMFUNCS: Record< return undefined; }, addIfChargen: async (actor, funcData, _, isReversing = false) => { - eLog.checkLog("activeEffects", "addIfChargen", {actor, funcData, isReversing}); if (!isReversing && game.eunoblades.Tracker?.system.phase !== BladesPhase.CharGen) { return; } const [target, qty] = funcData.split(/:/); if (isReversing) { @@ -36,7 +34,6 @@ const CUSTOMFUNCS: Record< await actor.update({[target]: U.pInt(getProperty(actor, target)) + U.pInt(qty)}); }, upgradeIfChargen: async (actor, funcData, _, isReversing = false) => { - eLog.checkLog("activeEffects", "upgradeIfChargen", {actor, funcData, isReversing}); if (!isReversing && game.eunoblades.Tracker?.system.phase !== BladesPhase.CharGen) { return; } const [target, qty] = funcData.split(/:/); if (getProperty(actor, target) < U.pInt(qty)) { @@ -284,22 +281,16 @@ class BladesActiveEffect extends ActiveEffect { static ThrottleCustomFunc(actor: BladesActor, data: BladesCustomFuncData) { const {funcName, funcData, isReversing, effect} = data; if (!actor.id) { return; } - eLog.checkLog3("activeEffect", `Throttling Func: ${funcName}(${funcData}, ${isReversing})`); // Is there a currently-running function for this actor? if (actor.id && actor.id in FUNCQUEUE) { // Is this a duplicate of a function already queued? const matchingQueue = FUNCQUEUE[actor.id].queue .find((fData: BladesCustomFuncData) => JSON.stringify(fData) === JSON.stringify(data)); - eLog.checkLog("activeEffects", "... Checking Queue", {data, FUNCQUEUE: FUNCQUEUE[actor.id], matchingQueue}); - if (matchingQueue) { - eLog.error("... Function ALREADY QUEUED, SKIPPING"); - return; - } + if (matchingQueue) { return; } FUNCQUEUE[actor.id].queue.push(data); return; } // If not, create FUNCQUEUE entry and run first function. - eLog.checkLog3("activeEffect", "... Creating New FUNCQUEUE, RUNNING:"); FUNCQUEUE[actor.id] = { curFunc: BladesActiveEffect.RunCustomFunc(actor, CUSTOMFUNCS[funcName](actor, funcData, effect, isReversing)), queue: [] @@ -308,20 +299,16 @@ class BladesActiveEffect extends ActiveEffect { static async RunCustomFunc(actor: BladesActor, funcPromise: Promise): Promise { if (!actor.id) { return; } - eLog.checkLog("activeEffects", "... Running Func ..."); await funcPromise; - eLog.checkLog("activeEffects", "... Function Complete!"); if (FUNCQUEUE[actor.id].queue.length) { const {funcName, funcData, isReversing, effect} = FUNCQUEUE[actor.id].queue.shift() ?? {}; if (!funcName || !(funcName in CUSTOMFUNCS)) { return; } if (!funcData) { return; } - eLog.checkLog3("activeEffect", `Progressing Queue: ${funcName}(${funcData}, ${isReversing}) -- ${FUNCQUEUE[actor.id].queue.length} remaining funcs.`); FUNCQUEUE[actor.id].curFunc = BladesActiveEffect.RunCustomFunc( actor, CUSTOMFUNCS[funcName](actor, funcData, effect, isReversing) ); } else { - eLog.checkLog3("activeEffect", "Function Queue Complete! Deleting."); delete FUNCQUEUE[actor.id]; } } diff --git a/src/ts/documents/actors/BladesPC.ts b/src/ts/documents/actors/BladesPC.ts index 962cc15..b8ea5d0 100644 --- a/src/ts/documents/actors/BladesPC.ts +++ b/src/ts/documents/actors/BladesPC.ts @@ -122,11 +122,6 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, } // #endregion - constructor(data: ActorDataConstructorData) { - super(data); - eLog.checkLog3("pcConstructor", "new BladesPC()", {data}); - } - // #region BladesPrimaryActor Implementation ~ get primaryUser(): User | null { return game.users?.find((user) => user.character?.id === this?.id) || null; @@ -294,8 +289,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, get healingClock(): BladesClockKey|undefined { if (!this.isHealingClockReady) { return undefined; } const [clockKeyID] = Object.keys(this.system.clocksData); - const clockKey = game.eunoblades.ClockKeys.get(clockKeyID ?? ""); - return clockKey; + return game.eunoblades.ClockKeys.get(clockKeyID ?? ""); } get harmLevel(): number { @@ -670,7 +664,7 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, } // #endregion - override render(force?: boolean) { + override render(force = false) { // if (!this.isHealingClockReady) { // setTimeout(() => this.render(force), 1000); // return;