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)