From c72e67f8845c640143018b3086be2130e3c7fe24 Mon Sep 17 00:00:00 2001 From: Eunomiac Date: Mon, 27 Nov 2023 14:06:54 -0500 Subject: [PATCH] (amend) --- module/BladesActor.js | 149 +------------------- module/BladesDialog.js | 4 +- module/BladesItem.js | 41 +++--- module/BladesRoll.js | 1 - module/blades.js | 3 +- module/core/utilities.js | 1 + module/documents/actors/BladesCrew.js | 99 ++++++++++++- module/documents/actors/BladesPC.js | 99 ++++++++++++- module/documents/items/BladesClock.js | 4 +- module/documents/items/BladesGMTracker.js | 4 +- module/sheets/actor/BladesActorSheet.js | 41 +++--- module/sheets/item/BladesItemSheet.js | 23 +-- module/sheets/roll/BladesConsequence.js | 2 +- ts/@types/blades-actor-sheet.d.ts | 4 + ts/@types/blades-actor.d.ts | 14 +- ts/BladesActor.ts | 164 ++-------------------- ts/BladesItem.ts | 41 +++--- ts/documents/actors/BladesCrew.ts | 78 +++++++++- ts/documents/actors/BladesPC.ts | 93 ++++++++++++ ts/sheets/actor/BladesActorSheet.ts | 52 +++---- 20 files changed, 505 insertions(+), 412 deletions(-) diff --git a/module/BladesActor.js b/module/BladesActor.js index 76f020c8..41b75d30 100644 --- a/module/BladesActor.js +++ b/module/BladesActor.js @@ -473,108 +473,6 @@ class BladesActor extends Actor { return 0; }); } - getDialogItems(category) { - const dialogData = {}; - const isPC = BladesActor.IsType(this, BladesActorType.pc); - const isCrew = BladesActor.IsType(this, BladesActorType.crew); - if (!BladesActor.IsType(this, BladesActorType.pc) - && !BladesActor.IsType(this, BladesActorType.crew)) { - return false; - } - const { playbookName } = this; - if (category === SelectionCategory.Heritage && isPC) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.heritage)); - } - else if (category === SelectionCategory.Background && isPC) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.background)); - } - else if (category === SelectionCategory.Vice && isPC && playbookName !== null) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.vice, playbookName)); - } - else if (category === SelectionCategory.Playbook) { - if (this.type === BladesActorType.pc) { - dialogData.Basic = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.playbook) - .filter((item) => !item.hasTag(Tag.Gear.Advanced))); - dialogData.Advanced = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.playbook, Tag.Gear.Advanced)); - } - else if (this.type === BladesActorType.crew) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_playbook)); - } - } - else if (category === SelectionCategory.Reputation && isCrew) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_reputation)); - } - else if (category === SelectionCategory.Preferred_Op && isCrew && playbookName !== null) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.preferred_op, playbookName)); - } - else if (category === SelectionCategory.Gear && BladesActor.IsType(this, BladesActorType.pc)) { - const self = this; - if (playbookName === null) { - return false; - } - const gearItems = this._processEmbeddedItemMatches([ - ...BladesItem.GetTypeWithTags(BladesItemType.gear, playbookName), - ...BladesItem.GetTypeWithTags(BladesItemType.gear, Tag.Gear.General) - ]) - .filter((item) => self.remainingLoad >= item.system.load); - // Two tabs, one for playbook and the other for general items - dialogData[playbookName] = gearItems.filter((item) => item.hasTag(playbookName)); - dialogData.General = gearItems - .filter((item) => item.hasTag(Tag.Gear.General)) - // Remove featured class from General items - .map((item) => { - if (item.dialogCSSClasses) { - item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); - } - return item; - }) - // Re-sort by world_name - .sort((a, b) => { - if (a.system.world_name > b.system.world_name) { - return 1; - } - if (a.system.world_name < b.system.world_name) { - return -1; - } - return 0; - }); - } - else if (category === SelectionCategory.Ability) { - if (isPC) { - if (playbookName === null) { - return false; - } - dialogData[playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability, playbookName)); - dialogData.Veteran = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability)) - .filter((item) => !item.hasTag(playbookName)) - // Remove featured class from Veteran items - .map((item) => { - if (item.dialogCSSClasses) { - item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); - } - return item; - }) - // Re-sort by world_name - .sort((a, b) => { - if (a.system.world_name > b.system.world_name) { - return 1; - } - if (a.system.world_name < b.system.world_name) { - return -1; - } - return 0; - }); - } - else if (isCrew) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_ability, playbookName)); - } - } - else if (category === SelectionCategory.Upgrade && isCrew && playbookName !== null) { - dialogData[playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, playbookName)); - dialogData.General = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, Tag.Gear.General)); - } - return dialogData; - } getSubItem(itemRef, activeOnly = false) { const activeCheck = (i) => !activeOnly || !i.hasTag(Tag.System.Archived); if (typeof itemRef === "string" && this.items.get(itemRef)) { @@ -845,48 +743,6 @@ class BladesActor extends Actor { } get rollPrimaryImg() { return this.img; } // #region BladesCrew Implementation ~ - get members() { - if (!BladesActor.IsType(this, BladesActorType.crew)) { - return []; - } - const self = this; - return BladesActor.GetTypeWithTags(BladesActorType.pc).filter((actor) => actor.isMember(self)); - } - get contacts() { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { - return []; - } - const self = this; - return this.activeSubActors.filter((actor) => actor.hasTag(self.playbookName)); - } - get claims() { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { - return {}; - } - return this.playbook.system.turfs; - } - get turfCount() { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { - return 0; - } - return Object.values(this.playbook.system.turfs) - .filter((claim) => claim.isTurf && claim.value).length; - } - get upgrades() { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { - return []; - } - return this.activeSubItems - .filter((item) => item.type === BladesItemType.crew_upgrade); - } - get cohorts() { - return this.activeSubItems - .filter((item) => [BladesItemType.cohort_gang, BladesItemType.cohort_expert].includes(item.type)); - } - getTaggedItemBonuses(tags) { - // Check ACTIVE EFFECTS supplied by upgrade/ability against submitted tags? - return tags.length; // Placeholder to avoid linter error - } // #endregion // #region PREPARING DERIVED DATA prepareDerivedData() { @@ -1163,5 +1019,10 @@ class BladesActor extends Actor { }); this.update(updateData); } + // #endregion NPC Randomizers + // Unlock lower-level update method for subclasses + async callOnUpdate(...args) { + await this._onUpdate(...args); + } } export default BladesActor; diff --git a/module/BladesDialog.js b/module/BladesDialog.js index c0310c9a..2b54fb75 100644 --- a/module/BladesDialog.js +++ b/module/BladesDialog.js @@ -359,7 +359,7 @@ class BladesDialog extends Dialog { } cData.resistOptions = resistOptions; } - updateConsequenceArmorResist(csqElem$, cData) { + updateConsequenceArmorResist(_csqElem$, cData) { // If consequence is already minimal, toggle armorNegates to true and set 'armorTo' to None-type const minimalCsqTypes = Object.entries(C.ResistedConsequenceTypes) .filter(([_, rCsqType]) => rCsqType === ConsequenceType.None) @@ -373,7 +373,7 @@ class BladesDialog extends Dialog { cData.armorTo = this.getSelectedResistOption(cData); } } - updateConsequenceSpecialArmorResist(csqElem$, cData) { + updateConsequenceSpecialArmorResist(_csqElem$, cData) { // If consequence is already minimal, toggle specialArmorNegates to true and set 'specialArmorTo' to None-type const minimalCsqTypes = Object.entries(C.ResistedConsequenceTypes) .filter(([_, rCsqType]) => rCsqType === ConsequenceType.None) diff --git a/module/BladesItem.js b/module/BladesItem.js index df11b533..5b9de048 100644 --- a/module/BladesItem.js +++ b/module/BladesItem.js @@ -1,6 +1,6 @@ -import C, { BladesActorType, BladesItemType, Tag, Factor } from "./core/constants.js"; +import C, { BladesItemType, Tag, Factor } from "./core/constants.js"; import U from "./core/utilities.js"; -import { BladesActor } from "./documents/BladesActorProxy.js"; +import { BladesCrew, BladesPC } from "./documents/BladesActorProxy.js"; import { BladesRollMod } from "./BladesRoll.js"; import BladesPushAlert from "./BladesPushAlert.js"; class BladesItem extends Item { @@ -93,13 +93,12 @@ class BladesItem extends Item { return this.getFactorTotal(Factor.tier) + (this.system.quality_bonus ?? 0) + 1; } if (BladesItem.IsType(this, BladesItemType.gear)) { - return this.getFactorTotal(Factor.tier) - + (this.hasTag("Fine") ? 1 : 0) - + (this.parent?.getTaggedItemBonuses(this.tags) ?? 0) - + (BladesActor.IsType(this.parent, BladesActorType.pc) - && BladesActor.IsType(this.parent.crew, BladesActorType.crew) - ? this.parent.crew.getTaggedItemBonuses(this.tags) - : 0); + let thisQuality = this.getFactorTotal(Factor.tier) + + (this.hasTag("Fine") ? 1 : 0); + if (BladesPC.IsType(this.parent)) { + thisQuality += this.parent.getTaggedItemBonuses(this.tags); + } + return thisQuality; } if (BladesItem.IsType(this, BladesItemType.design)) { return this.system.min_quality; @@ -280,16 +279,19 @@ class BladesItem extends Item { const subtypes = U.unique(Object.values(system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))); - const eliteSubtypes = U.unique([ - ...Object.values(system.elite_subtypes), - ...(this.parent?.upgrades ?? []) + const eliteSubtypes = [ + ...Object.values(system.elite_subtypes) + ]; + if (BladesCrew.IsType(this.parent)) { + eliteSubtypes.push(...this.parent.upgrades .filter((upgrade) => (upgrade.name ?? "").startsWith("Elite")) - .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, "")) - ] - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype))); + .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, ""))); + } system.subtypes = Object.fromEntries(subtypes.map((subtype, i) => [`${i + 1}`, subtype])); - system.elite_subtypes = Object.fromEntries(eliteSubtypes.map((subtype, i) => [`${i + 1}`, subtype])); + system.elite_subtypes = Object.fromEntries(U.unique(eliteSubtypes + .map((subtype) => subtype.trim()) + .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype))) + .map((subtype, i) => [`${i + 1}`, subtype])); system.edges = Object.fromEntries(Object.values(system.edges ?? []) .filter((edge) => /[A-Za-z]/.test(edge)) .map((edge, i) => [`${i + 1}`, edge.trim()])); @@ -350,5 +352,10 @@ class BladesItem extends Item { // eLog.checkLog3("gatherInfoQuestions", {gatherInfoData}); } } + // #endregion + // Unlock lower-level update method for subclasses + async callOnUpdate(...args) { + await this._onUpdate(...args); + } } export default BladesItem; diff --git a/module/BladesRoll.js b/module/BladesRoll.js index bdb5760a..b76dd9b2 100644 --- a/module/BladesRoll.js +++ b/module/BladesRoll.js @@ -129,7 +129,6 @@ function pruneConfig(cfg) { Object.keys(cfg.rollParticipantData[RollModSection.roll]).forEach((key) => { const thisParticipant = cfg.rollParticipantData?.[RollModSection.roll]?.[key]; if (thisParticipant instanceof BladesRollParticipant) { - // eslint-disable-next-line @typescript-eslint/no-non-null-assertion cfg.rollParticipantData[RollModSection.roll][key] = thisParticipant.flagData; } }); diff --git a/module/blades.js b/module/blades.js index 6bcd1b1a..99ab2c3c 100644 --- a/module/blades.js +++ b/module/blades.js @@ -20,7 +20,6 @@ import BladesAI, { AGENTS, AIAssistant } from "./core/ai.js"; import BladesActiveEffect from "./BladesActiveEffect.js"; import BladesGMTrackerSheet from "./sheets/item/BladesGMTrackerSheet.js"; import BladesClockKeeperSheet from "./sheets/item/BladesClockKeeperSheet.js"; -// import {updateClaims, updateContacts, updateOps, updateFactions, updateDescriptions, updateRollMods} from "./data-import/data-import.js"; CONFIG.debug.logging = false; /* DEVCODE*/ CONFIG.debug.logging = true; Object.assign(globalThis, { eLog: logger }); @@ -42,7 +41,7 @@ class GlobalGetter { get sheetData() { return this.roll?.getData(); } newActionRoll() { const pc = game.actors.getName("Alistair"); - const idList = [...new Array(15)].map(() => randomID()); + const idList = Array.from({ length: 15 }).map(() => randomID()); if (!pc) { return; } diff --git a/module/core/utilities.js b/module/core/utilities.js index e462458b..39e275f9 100644 --- a/module/core/utilities.js +++ b/module/core/utilities.js @@ -1,3 +1,4 @@ +// /// // #region ▮▮▮▮▮▮▮ IMPORTS ▮▮▮▮▮▮▮ ~ import C, { SVGDATA } from "./constants.js"; // eslint-disable-next-line import/no-unresolved diff --git a/module/documents/actors/BladesCrew.js b/module/documents/actors/BladesCrew.js index 7844fc2f..0a4b8591 100644 --- a/module/documents/actors/BladesCrew.js +++ b/module/documents/actors/BladesCrew.js @@ -1,7 +1,35 @@ -import { BladesItemType } from "../../core/constants.js"; -import BladesActor from "../../BladesActor.js"; +import { BladesActorType, BladesItemType, Tag } from "../../core/constants.js"; +import { BladesActor, BladesPC } from "../BladesActorProxy.js"; +import { BladesItem } from "../BladesItemProxy.js"; +import { SelectionCategory } from "../../BladesDialog.js"; class BladesCrew extends BladesActor { // #region Static Overrides: Create ~ + static IsType(doc) { + return super.IsType(doc, BladesActorType.crew); + } + static GetFromUser(userRef) { + const actor = BladesPC.GetFromUser(userRef); + if (!actor) { + return undefined; + } + return actor.crew; + } + static GetFromPC(pcRef) { + let actor; + if (typeof pcRef === "string") { + actor = game.actors.get(pcRef) ?? game.actors.getName(pcRef); + } + else if (pcRef instanceof BladesPC) { + actor = pcRef; + } + else { + actor ??= BladesPC.GetFromUser(pcRef); + } + if (!BladesPC.IsType(actor)) { + throw new Error(`Unable to find BladesPC from "${pcRef}.js"`); + } + return actor.crew; + } static async create(data, options = {}) { data.token = data.token || {}; data.system = data.system ?? {}; @@ -19,6 +47,73 @@ class BladesCrew extends BladesActor { return super.create(data, options); } // #endregion + // #region BladesCrew Implementation + getDialogItems(category) { + const dialogData = {}; + const { playbookName } = this; + if (category === SelectionCategory.Playbook) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_playbook)); + } + else if (category === SelectionCategory.Reputation) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_reputation)); + } + else if (category === SelectionCategory.Preferred_Op && playbookName !== null) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.preferred_op, playbookName)); + } + else if (category === SelectionCategory.Ability) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_ability, this.playbookName)); + } + else if (category === SelectionCategory.Upgrade && playbookName !== null) { + dialogData[playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, playbookName)); + dialogData.General = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, Tag.Gear.General)); + } + return dialogData; + } + get members() { + if (!BladesActor.IsType(this, BladesActorType.crew)) { + return []; + } + const self = this; + return BladesActor.GetTypeWithTags(BladesActorType.pc).filter((actor) => actor.isMember(self)); + } + get contacts() { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { + return []; + } + const self = this; + return this.activeSubActors.filter((actor) => actor.hasTag(self.playbookName)); + } + get claims() { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { + return {}; + } + return this.playbook.system.turfs; + } + get turfCount() { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { + return 0; + } + return Object.values(this.playbook.system.turfs) + .filter((claim) => claim.isTurf && claim.value).length; + } + get upgrades() { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { + return []; + } + return this.activeSubItems + .filter((item) => item.type === BladesItemType.crew_upgrade); + } + get cohorts() { + return this.activeSubItems + .filter((item) => [BladesItemType.cohort_gang, BladesItemType.cohort_expert].includes(item.type)); + } + getTaggedItemBonuses(tags) { + // Given a list of item tags, will return the total bonuses to that item + // Won't return a number, but an object literal that includes things like extra load space or concealability + // Check ACTIVE EFFECTS supplied by upgrade/ability against submitted tags? + return tags.length; // Placeholder to avoid linter error + } + // #endregion // #region BladesRoll Implementation // #region BladesRoll.ParticipantDoc Implementation get rollParticipantID() { return this.id; } diff --git a/module/documents/actors/BladesPC.js b/module/documents/actors/BladesPC.js index c7adc618..2248d9bf 100644 --- a/module/documents/actors/BladesPC.js +++ b/module/documents/actors/BladesPC.js @@ -3,12 +3,13 @@ import U from "../../core/utilities.js"; import { BladesActor } from "../BladesActorProxy.js"; import { BladesItem } from "../BladesItemProxy.js"; import BladesPushAlert from "../../BladesPushAlert.js"; +import { SelectionCategory } from "../../BladesDialog.js"; class BladesPC extends BladesActor { // #region Static Overrides: Create ~ static IsType(doc) { return super.IsType(doc, BladesActorType.pc); } - static GetFromUser(userRef) { + static GetUser(userRef) { let user; if (typeof userRef === "string") { user = game.users.get(userRef) ?? game.users.getName(userRef); @@ -16,6 +17,10 @@ class BladesPC extends BladesActor { else if (userRef instanceof User) { user = userRef; } + return user; + } + static GetFromUser(userRef) { + const user = BladesPC.GetUser(userRef); if (!user) { throw new Error(`Unable to find user '${userRef}'`); } @@ -133,6 +138,10 @@ class BladesPC extends BladesActor { return this.activeSubItems .filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); } + get cohorts() { + return this.activeSubItems + .filter((item) => BladesItem.IsType(item, BladesItemType.cohort_gang, BladesItemType.cohort_expert)); + } get playbookName() { return this.playbook?.name; } @@ -236,6 +245,94 @@ class BladesPC extends BladesActor { } return this.system.downtime_actions.max + this.system.downtime_action_bonus - this.system.downtime_actions.value; } + _processAbilityDialogItems(dialogData) { + if (!this.playbookName) { + return; + } + dialogData[this.playbookName] = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability, this.playbookName)); + dialogData.Veteran = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability)) + .filter((item) => !item.hasTag(this.playbookName)) + // Remove featured class from Veteran items + .map((item) => { + if (item.dialogCSSClasses) { + item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); + } + return item; + }) + // Re-sort by world_name + .sort((a, b) => { + if (a.system.world_name > b.system.world_name) { + return 1; + } + if (a.system.world_name < b.system.world_name) { + return -1; + } + return 0; + }); + } + processGearDialogItems(dialogData) { + if (this.playbookName === null) { + return; + } + const gearItems = this._processEmbeddedItemMatches([ + ...BladesItem.GetTypeWithTags(BladesItemType.gear, this.playbookName), + ...BladesItem.GetTypeWithTags(BladesItemType.gear, Tag.Gear.General) + ]) + .filter((item) => this.remainingLoad >= item.system.load); + // Two tabs, one for playbook and the other for general items + dialogData[this.playbookName] = gearItems.filter((item) => item.hasTag(this.playbookName)); + dialogData.General = gearItems + .filter((item) => item.hasTag(Tag.Gear.General)) + // Remove featured class from General items + .map((item) => { + if (item.dialogCSSClasses) { + item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); + } + return item; + }) + // Re-sort by world_name + .sort((a, b) => { + if (a.system.world_name > b.system.world_name) { + return 1; + } + if (a.system.world_name < b.system.world_name) { + return -1; + } + return 0; + }); + } + getDialogItems(category) { + const dialogData = {}; + const { playbookName } = this; + if (category === SelectionCategory.Heritage) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.heritage)); + } + else if (category === SelectionCategory.Background) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.background)); + } + else if (category === SelectionCategory.Vice && playbookName !== null) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.vice, playbookName)); + } + else if (category === SelectionCategory.Playbook) { + dialogData.Basic = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.playbook) + .filter((item) => !item.hasTag(Tag.Gear.Advanced))); + dialogData.Advanced = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.playbook, Tag.Gear.Advanced)); + } + else if (category === SelectionCategory.Gear) { + this.processGearDialogItems(dialogData); + } + else if (category === SelectionCategory.Ability) { + this._processAbilityDialogItems(dialogData); + } + return dialogData; + } + getTaggedItemBonuses(tags) { + // Given a list of item tags, will return the total bonuses to that item + // Won't return a number, but an object literal that includes things like extra load space or concealability + // Check ACTIVE EFFECTS supplied by ability against submitted tags? + // Should INCLUDE bonuses from crew. + return tags.length; // Placeholder to avoid linter error + } // #endregion // #region BladesRoll.PrimaryDoc Implementation get rollModsData() { diff --git a/module/documents/items/BladesClock.js b/module/documents/items/BladesClock.js index ed9d4464..f8a72e7f 100644 --- a/module/documents/items/BladesClock.js +++ b/module/documents/items/BladesClock.js @@ -38,8 +38,8 @@ class BladesClock extends BladesItem { } get rollOppImg() { return ""; } // #region OVERRIDES: _onUpdate - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); + async _onUpdate(...args) { + await super.callOnUpdate(...args); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); } } diff --git a/module/documents/items/BladesGMTracker.js b/module/documents/items/BladesGMTracker.js index 318e6b1d..48964de3 100644 --- a/module/documents/items/BladesGMTracker.js +++ b/module/documents/items/BladesGMTracker.js @@ -12,8 +12,8 @@ class BladesGMTracker extends BladesItem { this.system.phases = Object.values(BladesPhase); } // #region OVERRIDES: prepareDerivedData, _onUpdate - async _onUpdate(changed, options, userId) { - await super._onUpdate(changed, options, userId); + async _onUpdate(...args) { + await super.callOnUpdate(...args); BladesActor.GetTypeWithTags(BladesActorType.pc).forEach((actor) => actor.render()); } } diff --git a/module/sheets/actor/BladesActorSheet.js b/module/sheets/actor/BladesActorSheet.js index f92b8c87..bafa8c3b 100644 --- a/module/sheets/actor/BladesActorSheet.js +++ b/module/sheets/actor/BladesActorSheet.js @@ -3,7 +3,7 @@ import U from "../../core/utilities.js"; import G, { ApplyTooltipAnimations } from "../../core/gsap.js"; import C, { BladesActorType, BladesItemType, DowntimeAction, AttributeTrait, ActionTrait, Factor, RollType } from "../../core/constants.js"; import Tags from "../../core/tags.js"; -import { BladesActor, BladesPC } from "../../documents/BladesActorProxy.js"; +import { BladesActor, BladesPC, BladesCrew } from "../../documents/BladesActorProxy.js"; import BladesItem from "../../BladesItem.js"; import BladesDialog from "../../BladesDialog.js"; import BladesActiveEffect from "../../BladesActiveEffect.js"; @@ -34,27 +34,32 @@ class BladesActorSheet extends ActorSheet { || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER), hasLimitedVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED), - hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER), + hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER) + }; + if (BladesPC.IsType(this.actor) || BladesCrew.IsType(this.actor)) { // Prepare items for display on the actor sheet. - preparedItems: { + sheetData.preparedItems = { + abilities: [], + loadout: [], cohorts: { - gang: this.actor.activeSubItems + gang: this.actor.cohorts .filter((item) => item.type === BladesItemType.cohort_gang) .map((item) => { // Prepare gang cohort items. const subtypes = U.unique(Object.values(item.system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))); - const eliteSubtypes = U.unique([ - ...Object.values(item.system.elite_subtypes), - ...(item.parent?.upgrades ?? []) - .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, "")) - ] - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/ - .test(subtype) && subtypes.includes(subtype))); + const eliteSubtypes = [ + ...Object.values(item.system.elite_subtypes) + ]; + if (BladesCrew.IsType(item.parent)) { + eliteSubtypes.push(...(item.parent.upgrades ?? []) + .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, ""))); + } // Prepare images for gang cohort items. - const imgTypes = [...eliteSubtypes]; + const imgTypes = [...U.unique(eliteSubtypes.map((subtype) => subtype.trim()) + .filter((subtype) => /[A-Za-z]/ + .test(subtype) && subtypes.includes(subtype)))]; if (imgTypes.length < 2) { imgTypes.push(...subtypes.filter((subtype) => !imgTypes.includes(subtype))); } @@ -100,8 +105,8 @@ class BladesActorSheet extends ActorSheet { return item; }) } - } - }; + }; + } // Prepare additional data for PC and Crew actors. if (BladesActor.IsType(this.actor, BladesActorType.pc) || BladesActor.IsType(this.actor, BladesActorType.crew)) { sheetData.playbookData = { @@ -275,7 +280,7 @@ class BladesActorSheet extends ActorSheet { const target = clock$.data("target"); const curValue = U.pInt(clock$.data("value")); const maxValue = U.pInt(clock$.data("size")); - await G.effects.pulseClockWedges(clock$.find("wedges")).then(async () => await this.actor.update({ + await G.effects.pulseClockWedges(clock$.find("wedges")).then(async () => this.actor.update({ [target]: G.utils.wrap(0, maxValue + 1, curValue + 1) })); } @@ -287,7 +292,7 @@ class BladesActorSheet extends ActorSheet { } const target = clock$.data("target"); const curValue = U.pInt(clock$.data("value")); - await G.effects.reversePulseClockWedges(clock$.find("wedges")).then(async () => await this.actor.update({ + await G.effects.reversePulseClockWedges(clock$.find("wedges")).then(async () => this.actor.update({ [target]: Math.max(0, curValue - 1) })); } @@ -309,7 +314,7 @@ class BladesActorSheet extends ActorSheet { Item: this.actor.getSubItem(compData.docID) }[compData.docType]; } - if (compData.docCat && compData.docType) { + if (compData.docCat && compData.docType && (BladesPC.IsType(this.actor) || BladesCrew.IsType(this.actor))) { compData.dialogDocs = { Actor: this.actor.getDialogActors(compData.docCat), Item: this.actor.getDialogItems(compData.docCat) diff --git a/module/sheets/item/BladesItemSheet.js b/module/sheets/item/BladesItemSheet.js index 03599ba1..5176cbba 100644 --- a/module/sheets/item/BladesItemSheet.js +++ b/module/sheets/item/BladesItemSheet.js @@ -315,16 +315,7 @@ class BladesItemSheet extends ItemSheet { return pathComps.join("/"); } /* -------------------------------------------- */ - activateListeners(html) { - super.activateListeners(html); - const self = this; - Tags.InitListeners(html, this.item); - ApplyTooltipAnimations(html); - // Everything below here is only needed if the sheet is editable - if (!this.options.editable) { - return; - } - // Add dotline functionality + addDotlineListeners(html) { html.find(".dotline").each((__, elem) => { if ($(elem).hasClass("locked")) { return; @@ -360,6 +351,18 @@ class BladesItemSheet extends ItemSheet { }); }); }); + } + activateListeners(html) { + super.activateListeners(html); + const self = this; + Tags.InitListeners(html, this.item); + ApplyTooltipAnimations(html); + // Everything below here is only needed if the sheet is editable + if (!this.options.editable) { + return; + } + // Add dotline functionality + this.addDotlineListeners(html); // Harm Bar Functionality for Cohorts if (BladesItem.IsType(this.item, BladesItemType.cohort_expert, BladesItemType.cohort_gang)) { html.find("[data-harm-click]").on({ diff --git a/module/sheets/roll/BladesConsequence.js b/module/sheets/roll/BladesConsequence.js index 29e1f062..b31bb6b1 100644 --- a/module/sheets/roll/BladesConsequence.js +++ b/module/sheets/roll/BladesConsequence.js @@ -51,7 +51,6 @@ class BladesConsequence { roll$.closest(".chat-message").removeClass("active-chat-roll"); } const rollPhase = roll$.data("rollPhase"); - // eLog.checkLog3("rollCollab", "ApplyChatListeners", {html, roll$, rollPhase}); if (rollPhase !== RollPhase.AwaitingConsequences) { return; } @@ -77,6 +76,7 @@ class BladesConsequence { await csq.resistSpecialArmorConsequence(); break; } + default: } } }); diff --git a/ts/@types/blades-actor-sheet.d.ts b/ts/@types/blades-actor-sheet.d.ts index 3b8f09d0..d33c555c 100644 --- a/ts/@types/blades-actor-sheet.d.ts +++ b/ts/@types/blades-actor-sheet.d.ts @@ -37,6 +37,10 @@ declare global { numberCircleClass?: string, inRuleDotline?: BladesDotlineData }>, + cohorts: { + gang: BladesItemOfType[], + expert: BladesItemOfType[] + }, playbook?: BladesItemOfType }, diff --git a/ts/@types/blades-actor.d.ts b/ts/@types/blades-actor.d.ts index 3b016df9..bf866963 100644 --- a/ts/@types/blades-actor.d.ts +++ b/ts/@types/blades-actor.d.ts @@ -170,12 +170,12 @@ declare global { DeepPartial { } // Distinguishing schema types for BladesActor subtypes - type BladesActorOfType = ( - T extends BladesActorType.pc ? BladesPC - : T extends BladesActorType.npc ? BladesNPC - : T extends BladesActorType.crew ? BladesCrew - : T extends BladesActorType.faction ? BladesFaction : never - ) & { + type BladesActorOfType = + T extends BladesActorType.pc ? BladesPC & { system: ExtractBladesActorSystem } : + T extends BladesActorType.npc ? BladesNPC & { system: ExtractBladesActorSystem } : + T extends BladesActorType.crew ? BladesCrew & { system: ExtractBladesActorSystem } : + T extends BladesActorType.faction ? BladesFaction & { system: ExtractBladesActorSystem } : + never & { system: ExtractBladesActorSystem }; @@ -299,14 +299,12 @@ declare global { } export interface NPC extends BladesActorComponent.Default, - BladesActorComponent.SubItemControl, BladesActorComponent.CanSubActor { } export interface Faction extends BladesActorComponent.Default, BladesActorComponent.SubActorControl, - BladesActorComponent.SubItemControl, BladesActorComponent.CanSubActor { } } diff --git a/ts/BladesActor.ts b/ts/BladesActor.ts index 24dfb906..8941ab2b 100644 --- a/ts/BladesActor.ts +++ b/ts/BladesActor.ts @@ -2,7 +2,6 @@ import U from "./core/utilities"; import C, {BladesActorType, Tag, Playbook, BladesItemType, AttributeTrait, ActionTrait, PrereqType, AdvancementPoint, Randomizers, Factor, Vice} from "./core/constants"; -import {BladesPC, BladesCrew, BladesNPC, BladesFaction} from "./documents/BladesActorProxy"; import {BladesItem} from "./documents/BladesItemProxy"; import {BladesRollMod} from "./BladesRoll"; @@ -13,6 +12,7 @@ import type {ActorData, ActorDataConstructorData} from "@league-of-foundry-devel import type {ItemDataConstructorData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/itemData"; import type BladesActiveEffect from "./BladesActiveEffect"; import type EmbeddedCollection from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/abstract/embedded-collection.mjs"; +// import type {ToObjectFalseType} from "@league-of-foundry-developers/foundry-vtt-types/src/types/helperTypes"; import type {MergeObjectOptions} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/utils/helpers.mjs"; // #endregion @@ -328,7 +328,7 @@ class BladesActor extends Actor implements BladesDocument { get archivedSubItems() { return this.items.filter((item) => item.hasTag(Tag.System.Archived)); } - private _checkItemPrereqs(item: BladesItem): boolean { + protected _checkItemPrereqs(item: BladesItem): boolean { if (!item.system.prereqs) { return true; } for (const [pType, pReqs] of Object.entries( item.system.prereqs as Partial> @@ -342,7 +342,7 @@ class BladesActor extends Actor implements BladesDocument { return true; } - private _processPrereqArray( + protected _processPrereqArray( pReqArray: string[], pType: PrereqType, hitRecord: Partial> @@ -357,7 +357,7 @@ class BladesActor extends Actor implements BladesDocument { return true; } - private _processPrereqType( + protected _processPrereqType( pType: PrereqType, pString: string | undefined, hitRecord: Partial> @@ -376,7 +376,7 @@ class BladesActor extends Actor implements BladesDocument { } } - private _processActiveItemPrereq( + protected _processActiveItemPrereq( pString: string | undefined, hitRecord: Partial>, pType: PrereqType @@ -392,7 +392,7 @@ class BladesActor extends Actor implements BladesDocument { } } - private _processActiveItemsByTagPrereq( + protected _processActiveItemsByTagPrereq( pString: string | undefined, hitRecord: Partial>, pType: PrereqType @@ -408,7 +408,7 @@ class BladesActor extends Actor implements BladesDocument { } } - private _processAdvancedPlaybookPrereq(): boolean { + protected _processAdvancedPlaybookPrereq(): boolean { if (!BladesActor.IsType(this, BladesActorType.pc)) { return false; } if (!this.playbookName || ![Playbook.Ghost, Playbook.Hull, Playbook.Vampire].includes(this.playbookName)) { return false; @@ -416,7 +416,7 @@ class BladesActor extends Actor implements BladesDocument { return true; } - private _processEmbeddedItemMatches( + protected _processEmbeddedItemMatches( globalItems: Array> ): Array> { @@ -501,114 +501,6 @@ class BladesActor extends Actor implements BladesDocument { } - private processGearDialogItems(dialogData: Record): void { - if (!BladesActor.IsType(this, BladesActorType.pc)) { - throw new Error(`[BladesActor.processGearDialogItems] Can't fetch gear of type = '${this.type}'`); - } - if (this.playbookName === null) { return; } - // const self = this; - const gearItems = this._processEmbeddedItemMatches([ - ...BladesItem.GetTypeWithTags(BladesItemType.gear, this.playbookName), - ...BladesItem.GetTypeWithTags(BladesItemType.gear, Tag.Gear.General) - ]) - .filter((item) => (this as BladesPC).remainingLoad >= item.system.load); - - // Two tabs, one for playbook and the other for general items - dialogData[(this as BladesPC).playbookName] = gearItems.filter((item) => item.hasTag(this.playbookName)); - dialogData.General = gearItems - .filter((item) => item.hasTag(Tag.Gear.General)) - // Remove featured class from General items - .map((item) => { - if (item.dialogCSSClasses) { - item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); - } - return item; - }) - // Re-sort by world_name - .sort((a, b) => { - if (a.system.world_name > b.system.world_name) { return 1; } - if (a.system.world_name < b.system.world_name) { return -1; } - return 0; - }); - } - - private processAbilityDialogItems(dialogData: Record): void { - if (BladesPC.IsType(this)) { - if (!this.playbookName) { return; } - - dialogData[this.playbookName] = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.ability, this.playbookName) - ); - dialogData.Veteran = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability)) - .filter((item) => !item.hasTag((this as BladesPC).playbookName)) - // Remove featured class from Veteran items - .map((item) => { - if (item.dialogCSSClasses) { - item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); - } - return item; - }) - // Re-sort by world_name - .sort((a, b) => { - if (a.system.world_name > b.system.world_name) { return 1; } - if (a.system.world_name < b.system.world_name) { return -1; } - return 0; - }); - } else if (BladesCrew.IsType(this)) { - dialogData.Main = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.crew_ability, this.playbookName) - ); - } - } - - getDialogItems(category: SelectionCategory): Record | false { - const dialogData: Record = {}; - const isPC = BladesActor.IsType(this, BladesActorType.pc); - const isCrew = BladesActor.IsType(this, BladesActorType.crew); - if (!BladesActor.IsType(this, BladesActorType.pc) - && !BladesActor.IsType(this, BladesActorType.crew)) { return false; } - const {playbookName} = this; - - if (category === SelectionCategory.Heritage && isPC) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.heritage)); - } else if (category === SelectionCategory.Background && isPC) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.background)); - } else if (category === SelectionCategory.Vice && isPC && playbookName !== null) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.vice, playbookName)); - } else if (category === SelectionCategory.Playbook) { - if (this.type === BladesActorType.pc) { - dialogData.Basic = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.playbook) - .filter((item) => !item.hasTag(Tag.Gear.Advanced)) - ); - dialogData.Advanced = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.playbook, Tag.Gear.Advanced) - ); - } else if (this.type === BladesActorType.crew) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_playbook)); - } - } else if (category === SelectionCategory.Reputation && isCrew) { - dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_reputation)); - } else if (category === SelectionCategory.Preferred_Op && isCrew && playbookName !== null) { - dialogData.Main = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.preferred_op, playbookName) - ); - } else if (category === SelectionCategory.Gear && BladesActor.IsType(this, BladesActorType.pc)) { - this.processGearDialogItems(dialogData); - } else if (category === SelectionCategory.Ability) { - this.processAbilityDialogItems(dialogData); - } else if (category === SelectionCategory.Upgrade && isCrew && playbookName !== null) { - dialogData[playbookName] = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, playbookName) - ); - dialogData.General = this._processEmbeddedItemMatches( - BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, Tag.Gear.General) - ); - } - - return dialogData; - } - getSubItem(itemRef: ItemRef, activeOnly = false): BladesItem | undefined { const activeCheck = (i: BladesItem) => !activeOnly || !i.hasTag(Tag.System.Archived); if (typeof itemRef === "string" && this.items.get(itemRef)) { @@ -917,46 +809,6 @@ class BladesActor extends Actor implements BladesDocument { // #region BladesCrew Implementation ~ - get members(): BladesPC[] { - if (!BladesActor.IsType(this, BladesActorType.crew)) { return []; } - const self = this as BladesCrew; - return BladesActor.GetTypeWithTags(BladesActorType.pc).filter((actor): actor is BladesPC => actor.isMember(self)); - } - - get contacts(): Array { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; } - const self: BladesCrew = this as BladesCrew; - return this.activeSubActors.filter((actor): actor is BladesNPC|BladesFaction => actor.hasTag(self.playbookName)); - } - - get claims(): Record { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return {}; } - return this.playbook.system.turfs; - } - - get turfCount(): number { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return 0; } - return Object.values(this.playbook.system.turfs) - .filter((claim) => claim.isTurf && claim.value).length; - } - - get upgrades(): Array> { - if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; } - return this.activeSubItems - .filter((item): item is BladesItemOfType => - item.type === BladesItemType.crew_upgrade); - } - - get cohorts(): Array> { - return this.activeSubItems - .filter((item): item is BladesItemOfType => - [BladesItemType.cohort_gang, BladesItemType.cohort_expert].includes(item.type)); - } - - getTaggedItemBonuses(tags: BladesTag[]): number { - // Check ACTIVE EFFECTS supplied by upgrade/ability against submitted tags? - return tags.length; // Placeholder to avoid linter error - } // #endregion // #region PREPARING DERIVED DATA diff --git a/ts/BladesItem.ts b/ts/BladesItem.ts index 9ab07bf4..5f725959 100644 --- a/ts/BladesItem.ts +++ b/ts/BladesItem.ts @@ -1,6 +1,6 @@ -import C, {BladesActorType, BladesItemType, Tag, Factor} from "./core/constants"; +import C, {BladesItemType, Tag, Factor} from "./core/constants"; import U from "./core/utilities"; -import {BladesActor} from "./documents/BladesActorProxy"; +import {BladesActor, BladesCrew, BladesPC} from "./documents/BladesActorProxy"; import {BladesRollMod} from "./BladesRoll"; import BladesPushAlert from "./BladesPushAlert"; import type {ItemDataConstructorData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/itemData"; @@ -122,15 +122,12 @@ class BladesItem extends Item implements BladesDocument, return this.getFactorTotal(Factor.tier) + (this.system.quality_bonus ?? 0) + 1; } if (BladesItem.IsType(this, BladesItemType.gear)) { - return this.getFactorTotal(Factor.tier) - + (this.hasTag("Fine") ? 1 : 0) - + (this.parent?.getTaggedItemBonuses(this.tags) ?? 0) - + ( - BladesActor.IsType(this.parent, BladesActorType.pc) - && BladesActor.IsType(this.parent.crew, BladesActorType.crew) - ? this.parent.crew.getTaggedItemBonuses(this.tags) - : 0 - ); + let thisQuality = this.getFactorTotal(Factor.tier) + + (this.hasTag("Fine") ? 1 : 0); + if (BladesPC.IsType(this.parent)) { + thisQuality += this.parent.getTaggedItemBonuses(this.tags); + } + return thisQuality; } if (BladesItem.IsType(this, BladesItemType.design)) { return this.system.min_quality; } return this.getFactorTotal(Factor.tier); @@ -338,17 +335,25 @@ class BladesItem extends Item implements BladesDocument, const subtypes = U.unique(Object.values(system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))); - const eliteSubtypes = U.unique([ - ...Object.values(system.elite_subtypes), - ...(this.parent?.upgrades ?? []) + + const eliteSubtypes = [ + ...Object.values(system.elite_subtypes) + ]; + if (BladesCrew.IsType(this.parent)) { + eliteSubtypes.push(...this.parent.upgrades .filter((upgrade) => (upgrade.name ?? "").startsWith("Elite")) .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, "")) - ] - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype))); + ); + } system.subtypes = Object.fromEntries(subtypes.map((subtype, i) => [`${i + 1}`, subtype])); - system.elite_subtypes = Object.fromEntries(eliteSubtypes.map((subtype, i) => [`${i + 1}`, subtype])); + system.elite_subtypes = Object.fromEntries( + U.unique(eliteSubtypes + .map((subtype) => subtype.trim()) + .filter((subtype) => /[A-Za-z]/.test(subtype) && subtypes.includes(subtype)) + ) + .map((subtype, i) => [`${i + 1}`, subtype]) + ); system.edges = Object.fromEntries(Object.values(system.edges ?? []) .filter((edge) => /[A-Za-z]/.test(edge)) .map((edge, i) => [`${i + 1}`, edge.trim()])); diff --git a/ts/documents/actors/BladesCrew.ts b/ts/documents/actors/BladesCrew.ts index 34a796a0..aee4d4ff 100644 --- a/ts/documents/actors/BladesCrew.ts +++ b/ts/documents/actors/BladesCrew.ts @@ -1,8 +1,9 @@ -import {BladesActorType, Playbook, BladesItemType} from "../../core/constants"; -import {BladesActor, BladesPC} from "../BladesActorProxy"; +import {BladesActorType, Playbook, BladesItemType, Tag} from "../../core/constants"; +import {BladesActor, BladesPC, BladesNPC, BladesFaction} from "../BladesActorProxy"; import {BladesItem} from "../BladesItemProxy"; import BladesRoll from "../../BladesRoll"; +import {SelectionCategory} from "../../BladesDialog"; import type {ActorDataConstructorData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/actorData"; class BladesCrew extends BladesActor implements BladesActorSubClass.Crew, @@ -63,6 +64,79 @@ class BladesCrew extends BladesActor implements BladesActorSubClass.Crew, } // #endregion + // #region BladesCrew Implementation + + getDialogItems(category: SelectionCategory): Record { + const dialogData: Record = {}; + const {playbookName} = this; + + if (category === SelectionCategory.Playbook) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_playbook)); + } else if (category === SelectionCategory.Reputation) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.crew_reputation)); + } else if (category === SelectionCategory.Preferred_Op && playbookName !== null) { + dialogData.Main = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.preferred_op, playbookName) + ); + } else if (category === SelectionCategory.Ability) { + dialogData.Main = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.crew_ability, this.playbookName) + ); + } else if (category === SelectionCategory.Upgrade && playbookName !== null) { + dialogData[playbookName] = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, playbookName) + ); + dialogData.General = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.crew_upgrade, Tag.Gear.General) + ); + } + + return dialogData; + } + + get members(): BladesPC[] { + if (!BladesActor.IsType(this, BladesActorType.crew)) { return []; } + const self = this as BladesCrew; + return BladesActor.GetTypeWithTags(BladesActorType.pc).filter((actor): actor is BladesPC => actor.isMember(self)); + } + + get contacts(): Array { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; } + const self: BladesCrew = this as BladesCrew; + return this.activeSubActors.filter((actor): actor is BladesNPC|BladesFaction => actor.hasTag(self.playbookName)); + } + + get claims(): Record { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return {}; } + return this.playbook.system.turfs; + } + + get turfCount(): number { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return 0; } + return Object.values(this.playbook.system.turfs) + .filter((claim) => claim.isTurf && claim.value).length; + } + + get upgrades(): Array> { + if (!BladesActor.IsType(this, BladesActorType.crew) || !this.playbook) { return []; } + return this.activeSubItems + .filter((item): item is BladesItemOfType => + item.type === BladesItemType.crew_upgrade); + } + + get cohorts(): Array> { + return this.activeSubItems + .filter((item): item is BladesItemOfType => + [BladesItemType.cohort_gang, BladesItemType.cohort_expert].includes(item.type)); + } + + getTaggedItemBonuses(tags: BladesTag[]): number { + // Given a list of item tags, will return the total bonuses to that item + // Won't return a number, but an object literal that includes things like extra load space or concealability + // Check ACTIVE EFFECTS supplied by upgrade/ability against submitted tags? + return tags.length; // Placeholder to avoid linter error + } + // #endregion // #region BladesRoll Implementation diff --git a/ts/documents/actors/BladesPC.ts b/ts/documents/actors/BladesPC.ts index 0b437721..6edb24cd 100644 --- a/ts/documents/actors/BladesPC.ts +++ b/ts/documents/actors/BladesPC.ts @@ -4,6 +4,7 @@ import {BladesActor, BladesCrew} from "../BladesActorProxy"; import {BladesItem} from "../BladesItemProxy"; import BladesRoll from "../../BladesRoll"; import BladesPushAlert from "../../BladesPushAlert"; +import {SelectionCategory} from "../../BladesDialog"; import type {ActorDataConstructorData} from "@league-of-foundry-developers/foundry-vtt-types/src/foundry/common/data/data.mjs/actorData"; type harmLevel = 1|2|3|4; @@ -158,6 +159,13 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, .filter((item) => [BladesItemType.ability, BladesItemType.crew_ability].includes(item.type)); } + get cohorts(): Array> { + return this.activeSubItems + .filter((item) => + BladesItem.IsType(item, BladesItemType.cohort_gang, BladesItemType.cohort_expert) + ) as Array>; + } + get playbookName() { return this.playbook?.name as (BladesTag & Playbook) | undefined; } @@ -261,6 +269,91 @@ class BladesPC extends BladesActor implements BladesActorSubClass.Scoundrel, return this.system.downtime_actions.max + this.system.downtime_action_bonus - this.system.downtime_actions.value; } + + protected _processAbilityDialogItems(dialogData: Record): void { + if (!this.playbookName) { return; } + + dialogData[this.playbookName] = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.ability, this.playbookName) + ); + dialogData.Veteran = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.ability)) + .filter((item) => !item.hasTag(this.playbookName)) + // Remove featured class from Veteran items + .map((item) => { + if (item.dialogCSSClasses) { + item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); + } + return item; + }) + // Re-sort by world_name + .sort((a, b) => { + if (a.system.world_name > b.system.world_name) { return 1; } + if (a.system.world_name < b.system.world_name) { return -1; } + return 0; + }); + } + + private processGearDialogItems(dialogData: Record): void { + if (this.playbookName === null) { return; } + const gearItems = this._processEmbeddedItemMatches([ + ...BladesItem.GetTypeWithTags(BladesItemType.gear, this.playbookName), + ...BladesItem.GetTypeWithTags(BladesItemType.gear, Tag.Gear.General) + ]) + .filter((item) => this.remainingLoad >= item.system.load); + + // Two tabs, one for playbook and the other for general items + dialogData[this.playbookName] = gearItems.filter((item) => item.hasTag(this.playbookName)); + dialogData.General = gearItems + .filter((item) => item.hasTag(Tag.Gear.General)) + // Remove featured class from General items + .map((item) => { + if (item.dialogCSSClasses) { + item.dialogCSSClasses = item.dialogCSSClasses.replace(/featured-item\s?/g, ""); + } + return item; + }) + // Re-sort by world_name + .sort((a, b) => { + if (a.system.world_name > b.system.world_name) { return 1; } + if (a.system.world_name < b.system.world_name) { return -1; } + return 0; + }); + } + + getDialogItems(category: SelectionCategory): Record { + const dialogData: Record = {}; + const {playbookName} = this; + + if (category === SelectionCategory.Heritage) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.heritage)); + } else if (category === SelectionCategory.Background) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.background)); + } else if (category === SelectionCategory.Vice && playbookName !== null) { + dialogData.Main = this._processEmbeddedItemMatches(BladesItem.GetTypeWithTags(BladesItemType.vice, playbookName)); + } else if (category === SelectionCategory.Playbook) { + dialogData.Basic = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.playbook) + .filter((item) => !item.hasTag(Tag.Gear.Advanced)) + ); + dialogData.Advanced = this._processEmbeddedItemMatches( + BladesItem.GetTypeWithTags(BladesItemType.playbook, Tag.Gear.Advanced) + ); + } else if (category === SelectionCategory.Gear) { + this.processGearDialogItems(dialogData); + } else if (category === SelectionCategory.Ability) { + this._processAbilityDialogItems(dialogData); + } + + return dialogData; + } + + getTaggedItemBonuses(tags: BladesTag[]): number { + // Given a list of item tags, will return the total bonuses to that item + // Won't return a number, but an object literal that includes things like extra load space or concealability + // Check ACTIVE EFFECTS supplied by ability against submitted tags? + // Should INCLUDE bonuses from crew. + return tags.length; // Placeholder to avoid linter error + } // #endregion // #region BladesRoll.PrimaryDoc Implementation diff --git a/ts/sheets/actor/BladesActorSheet.ts b/ts/sheets/actor/BladesActorSheet.ts index 1e1c798b..a0f53507 100644 --- a/ts/sheets/actor/BladesActorSheet.ts +++ b/ts/sheets/actor/BladesActorSheet.ts @@ -4,7 +4,7 @@ import U from "../../core/utilities"; import G, {ApplyTooltipAnimations} from "../../core/gsap"; import C, {BladesActorType, BladesItemType, DowntimeAction, AttributeTrait, Tag, ActionTrait, Factor, RollType} from "../../core/constants"; import Tags from "../../core/tags"; -import {BladesActor, BladesPC} from "../../documents/BladesActorProxy"; +import {BladesActor, BladesPC, BladesCrew} from "../../documents/BladesActorProxy"; import BladesItem from "../../BladesItem"; import BladesDialog, {SelectionCategory} from "../../BladesDialog"; import BladesActiveEffect from "../../BladesActiveEffect"; @@ -38,12 +38,7 @@ class BladesActorSheet extends ActorSheet { const context = super.getData(); // Prepare additional data specific to this actor's sheet. - const sheetData: FullPartial - & BladesActorDataOfType - & BladesActorDataOfType - & BladesActorDataOfType - > = { + const sheetData: DeepPartial = { // Basic actor data. cssClass: this.actor.type, editable: this.options.editable, @@ -57,32 +52,37 @@ class BladesActorSheet extends ActorSheet { || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OBSERVER), hasLimitedVision: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.LIMITED), - hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER), + hasControl: game.user.isGM || this.actor.testUserPermission(game.user, CONST.DOCUMENT_PERMISSION_LEVELS.OWNER) + }; + if (BladesPC.IsType(this.actor) || BladesCrew.IsType(this.actor)) { // Prepare items for display on the actor sheet. - preparedItems: { + sheetData.preparedItems = { + abilities: [], + loadout: [], cohorts: { - gang: this.actor.activeSubItems - .filter((item): item is BladesItemOfType => - item.type === BladesItemType.cohort_gang) + gang: this.actor.cohorts + .filter((item) => item.type === BladesItemType.cohort_gang) .map((item) => { // Prepare gang cohort items. const subtypes = U.unique(Object.values(item.system.subtypes) .map((subtype) => subtype.trim()) .filter((subtype) => /[A-Za-z]/.test(subtype))) as Tag.GangType[]; - const eliteSubtypes = U.unique([ - ...Object.values(item.system.elite_subtypes), - ...(item.parent?.upgrades ?? []) - .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, "")) - ] - .map((subtype) => subtype.trim()) - .filter((subtype) => /[A-Za-z]/ - .test(subtype) && subtypes.includes(subtype as Tag.GangType) - ) - ) as Tag.GangType[]; + const eliteSubtypes = [ + ...Object.values(item.system.elite_subtypes) + ]; + if (BladesCrew.IsType(item.parent)) { + eliteSubtypes.push(...(item.parent.upgrades ?? []) + .map((upgrade) => (upgrade.name ?? "").trim().replace(/^Elite /, ""))); + } // Prepare images for gang cohort items. - const imgTypes = [...eliteSubtypes]; + const imgTypes = [...U.unique( + eliteSubtypes.map((subtype) => subtype.trim()) + .filter((subtype) => /[A-Za-z]/ + .test(subtype) && subtypes.includes(subtype as Tag.GangType) + ) + )]; if (imgTypes.length < 2) { imgTypes.push(...subtypes.filter((subtype) => !imgTypes.includes(subtype))); } @@ -135,8 +135,8 @@ class BladesActorSheet extends ActorSheet { return item; }) } - } - }; + }; + } // Prepare additional data for PC and Crew actors. if (BladesActor.IsType(this.actor, BladesActorType.pc) || BladesActor.IsType(this.actor, BladesActorType.crew)) { @@ -371,7 +371,7 @@ class BladesActorSheet extends ActorSheet { Item: this.actor.getSubItem(compData.docID) }[compData.docType]; } - if (compData.docCat && compData.docType) { + if (compData.docCat && compData.docType && (BladesPC.IsType(this.actor) || BladesCrew.IsType(this.actor))) { compData.dialogDocs = { Actor: this.actor.getDialogActors(compData.docCat), Item: this.actor.getDialogItems(compData.docCat)