Skip to content

Commit

Permalink
Convert familiar system data to TypeDataModel (#16999)
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam authored Nov 11, 2024
1 parent 8acccda commit a2d07c7
Show file tree
Hide file tree
Showing 16 changed files with 210 additions and 133 deletions.
5 changes: 4 additions & 1 deletion src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,10 @@ class ActorPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | n

/** The recorded schema version of this actor, updated after each data migration */
get schemaVersion(): number | null {
return Number(this.system._migration?.version ?? this.system.schema?.version) || null;
const legacyValue = R.isPlainObject(this._source.system.schema)
? Number(this._source.system.schema.version) || null
: null;
return Number(this._source.system._migration?.version) || legacyValue;
}

/** Get an active GM or, failing that, a player who can update this actor */
Expand Down
4 changes: 1 addition & 3 deletions src/module/actor/creature/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import type {
StrikeData,
} from "@actor/data/base.ts";
import type { ActorSizePF2e } from "@actor/data/size.ts";
import type { DamageDicePF2e, ModifierPF2e, RawModifier, StatisticModifier } from "@actor/modifiers.ts";
import type { ModifierPF2e, RawModifier, StatisticModifier } from "@actor/modifiers.ts";
import type { AttributeString, MovementType, SaveType, SkillSlug } from "@actor/types.ts";
import type { LabeledNumber, Size, ValueAndMax, ValueAndMaybeMax, ZeroToThree } from "@module/data.ts";
import type { ArmorClassTraceData } from "@system/statistic/index.ts";
Expand Down Expand Up @@ -82,8 +82,6 @@ interface CreatureSystemData extends Omit<CreatureSystemSource, "attributes">, A

/** Maps roll types -> a list of modifiers which should affect that roll type. */
customModifiers: Record<string, ModifierPF2e[]>;
/** Maps damage roll types -> a list of damage dice which should be added to that damage roll type. */
damageDice: Record<string, DamageDicePF2e[]>;

/** Saving throw data */
saves: CreatureSaves;
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/data/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ type ActorSystemSource = {
/** A record of this actor's current world schema version as well a log of the last migration to occur */
_migration: MigrationRecord;
/** Legacy location of `MigrationRecord` */
schema?: Readonly<{ version: number | null; lastMigration: object | null }>;
schema?: object;
};

interface ActorAttributesSource {
Expand Down
41 changes: 41 additions & 0 deletions src/module/actor/data/model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import type { ActorPF2e } from "@actor";
import type { MigrationDataField } from "@module/data.ts";
import type { AutoChangeEntry } from "@module/rules/rule-element/ae-like.ts";

abstract class ActorSystemModel<TParent extends ActorPF2e, TSchema extends ActorSystemSchema> extends foundry.abstract
.TypeDataModel<TParent, TSchema> {
declare autoChanges: Record<string, AutoChangeEntry[] | undefined>;

static override defineSchema(): ActorSystemSchema {
const fields = foundry.data.fields;
return {
_migration: new fields.SchemaField({
version: new fields.NumberField({
required: true,
nullable: true,
positive: true,
initial: null,
}),
previous: new fields.SchemaField(
{
foundry: new fields.StringField({ required: true, nullable: true, initial: null }),
system: new fields.StringField({ required: true, nullable: true, initial: null }),
schema: new fields.NumberField({
required: true,
nullable: true,
positive: true,
initial: null,
}),
},
{ required: true, nullable: true, initial: null },
),
}),
};
}
}

type ActorSystemSchema = {
_migration: MigrationDataField;
};

export { ActorSystemModel, type ActorSystemSchema };
157 changes: 109 additions & 48 deletions src/module/actor/familiar/data.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,137 @@
import {
import { ActorPF2e } from "@actor";
import type {
BaseCreatureSource,
CreatureAttributes,
CreatureDetails,
CreatureDetailsSource,
CreatureSystemData,
CreatureSystemSource,
CreatureLanguagesData,
CreaturePerceptionData,
CreatureResources,
CreatureSaves,
CreatureTraitsData,
SkillData,
} from "@actor/creature/data.ts";
import { AttributeString } from "@actor/types.ts";
import { StatisticTraceData } from "@system/statistic/index.ts";
import { ActorSystemModel, ActorSystemSchema } from "@actor/data/model.ts";
import type { ModifierPF2e } from "@actor/modifiers.ts";
import type { AttributeString } from "@actor/types.ts";
import { ATTRIBUTE_ABBREVIATIONS } from "@actor/values.ts";
import type { StatisticTraceData } from "@system/statistic/data.ts";
import type { ModelPropFromDataField, SourcePropFromDataField } from "types/foundry/common/data/fields.d.ts";
import type { FamiliarPF2e } from "./document.ts";
import fields = foundry.data.fields;

type FamiliarSource = BaseCreatureSource<"familiar", FamiliarSystemSource>;

interface FamiliarSystemSource extends CreatureSystemSource {
details: FamiliarDetailsSource;
attributes: FamiliarAttributesSource;
master: {
id: string | null;
ability: AttributeString | null;
};
class FamiliarSystemData extends ActorSystemModel<FamiliarPF2e, FamiliarSystemSchema> {
declare traits: CreatureTraitsData;

declare perception: CreaturePerceptionData;

declare saves: CreatureSaves;

declare skills: Record<string, SkillData>;

declare attack: StatisticTraceData;

declare resources: CreatureResources;

static override defineSchema(): FamiliarSystemSchema {
return {
...super.defineSchema(),
master: new fields.SchemaField({
id: new fields.ForeignDocumentField(ActorPF2e, {
idOnly: true,
required: true,
nullable: true,
initial: null,
}),
ability: new fields.StringField({
required: true,
nullable: true,
choices: Array.from(ATTRIBUTE_ABBREVIATIONS),
initial: null,
}),
}),
attributes: new fields.SchemaField({
hp: new fields.SchemaField({
value: new fields.NumberField({
required: true,
nullable: false,
integer: true,
min: 0,
initial: 5,
}),
temp: new fields.NumberField({
required: true,
nullable: false,
integer: true,
min: 0,
initial: 0,
}),
}),
}),
details: new fields.SchemaField({
creature: new fields.SchemaField({
value: new fields.StringField({ required: true, nullable: false, blank: true, initial: "" }),
}),
}),
};
}
}

interface FamiliarSystemData
extends foundry.abstract.TypeDataModel<FamiliarPF2e, FamiliarSystemSchema>,
ModelPropsFromSchema<FamiliarSystemSchema> {
attributes: CreatureAttributes;
details: FamiliarDetails;
customModifiers: Record<string, ModifierPF2e[]>;
}

type FamiliarSystemSchema = ActorSystemSchema & {
master: fields.SchemaField<{
id: fields.ForeignDocumentField<string, true, true, true>;
ability: fields.StringField<AttributeString, AttributeString, true, true, true>;
}>;
attributes: fields.SchemaField<{
hp: fields.SchemaField<{
value: fields.NumberField<number, number, true, false, true>;
temp: fields.NumberField<number, number, true, false, true>;
}>;
}>;
details: fields.SchemaField<{
creature: fields.SchemaField<{
value: fields.StringField<string, string, true, false, true>;
}>;
}>;
};

interface FamiliarSystemSource extends SourceFromSchema<FamiliarSystemSchema> {
attributes: FamiliarAttributesSource;
details: FamiliarDetailsSource;
customModifiers?: never;
perception?: never;
resources?: never;
saves?: never;
skills?: never;
traits?: never;
/** Legacy location of `MigrationRecord` */
schema?: object;
}

interface FamiliarAttributesSource {
hp: { value: number; temp: number };
interface FamiliarAttributesSource extends SourcePropFromDataField<FamiliarSystemSchema["attributes"]> {
immunities?: never;
weaknesses?: never;
resistances?: never;
}

interface FamiliarDetailsSource extends CreatureDetailsSource {
creature: {
value: string;
};
interface FamiliarDetailsSource extends SourcePropFromDataField<FamiliarSystemSchema["details"]> {
alliance?: never;
languages?: never;
level?: never;
}

/** The raw information contained within the actor data object for familiar actors. */
interface FamiliarSystemData extends Omit<FamiliarSystemSource, SourceOmission>, CreatureSystemData {
details: FamiliarDetails;
attack: StatisticTraceData;
attributes: CreatureAttributes;
master: {
id: string | null;
ability: AttributeString | null;
};

actions?: never;
initiative?: never;
}

type SourceOmission =
| "attributes"
| "customModifiers"
| "details"
| "perception"
| "resources"
| "saves"
| "skills"
| "traits";

interface FamiliarDetails extends CreatureDetails {
creature: {
value: string;
};
interface FamiliarDetails extends ModelPropFromDataField<FamiliarSystemSchema["details"]>, CreatureDetails {
languages: CreatureLanguagesData;
}

export type { FamiliarSource, FamiliarSystemData, FamiliarSystemSource };
export { FamiliarSystemData };
export type { FamiliarSource, FamiliarSystemSource };
4 changes: 2 additions & 2 deletions src/module/actor/familiar/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { TokenDocumentPF2e } from "@scene";
import { Predicate } from "@system/predication.ts";
import { ArmorStatistic, HitPointsStatistic, PerceptionStatistic, Statistic } from "@system/statistic/index.ts";
import * as R from "remeda";
import { FamiliarSource, FamiliarSystemData } from "./data.ts";
import type { FamiliarSource, FamiliarSystemData } from "./data.ts";

class FamiliarPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | null> extends CreaturePF2e<TParent> {
/** The familiar's attack statistic, for the rare occasion it must make an attack roll */
Expand All @@ -29,7 +29,7 @@ class FamiliarPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e
// The Actors world collection needs to be initialized for data preparation
if (!game.ready || !this.system.master.id) return null;

const master = game.actors.get(this.system.master.id ?? "");
const master = game.actors.get(this.system.master.id);
if (master?.isOfType("character")) {
master.familiar ??= this;
return master;
Expand Down
26 changes: 12 additions & 14 deletions src/module/actor/sheet/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,16 +106,15 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo
options.editable = this.isEditable;
options.sheetConfig &&=
Object.values(CONFIG.Actor.sheetClasses[this.actor.type]).filter((c) => c.canConfigure).length > 1;
const actor = this.actor;

for (const item of [...this.actor.itemTypes.action, ...this.actor.itemTypes.feat]) {
for (const item of [...actor.itemTypes.action, ...this.actor.itemTypes.feat]) {
if (item.system.selfEffect) {
item.system.selfEffect.img ??= fromUuidSync(item.system.selfEffect.uuid)?.img ?? null;
}
}

// The Actor and its Items
const actorData = this.actor.toObject(false);

const actorData = { ...actor, _id: this.actor.id, system: fu.deepClone({ ...actor.system }) };
// Alphabetize displayed IWR
const iwrKeys = ["immunities", "weaknesses", "resistances"] as const;
const attributes: Record<(typeof iwrKeys)[number], { label: string }[]> = actorData.system.attributes;
Expand All @@ -124,15 +123,15 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo
}

// Calculate financial and total wealth
const coins = this.actor.inventory.coins;
const coins = actor.inventory.coins;
const totalCoinage = ActorSheetPF2e.coinsToSheetData(coins);
const totalCoinageGold = (coins.copperValue / 100).toFixed(2);

const totalWealth = this.actor.inventory.totalWealth;
const totalWealth = actor.inventory.totalWealth;
const totalWealthGold = (totalWealth.copperValue / 100).toFixed(2);

const traitsMap = ((): Record<string, string> => {
switch (this.actor.type) {
switch (actor.type) {
case "hazard":
return CONFIG.PF2E.hazardTraits;
case "vehicle":
Expand All @@ -144,7 +143,7 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo

// Consolidate toggles from across domains and regroup by sheet placement
const toggles = R.groupBy(
Object.values(this.actor.synthetics.toggles).flatMap((domain) => Object.values(domain)),
Object.values(actor.synthetics.toggles).flatMap((domain) => Object.values(domain)),
(t) => t.placement,
);

Expand All @@ -159,7 +158,7 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo
inventory: this.prepareInventory(),
isLootSheet: this.isLootSheet,
isTargetFlatFooted: !!this.actor.rollOptions.all["target:condition:off-guard"],
items: actorData.items,
items: [], // No longer used but part of type signature
limited: this.actor.limited,
options,
owner: this.actor.isOwner,
Expand Down Expand Up @@ -200,10 +199,11 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo
{ label: game.i18n.localize("PF2E.Item.Container.Plural"), types: ["backpack"], items: [] },
];

const actor = this.actor;
const actor = this.actor.clone({}, { keepId: true });
for (const item of actor.inventory.contents.sort((a, b) => (a.sort || 0) - (b.sort || 0))) {
if (item.isInContainer) continue;
sections.find((s) => s.types.includes(item.type))?.items.push(this.prepareInventoryItem(item));
const section = sections.find((s) => s.types.includes(item.type));
section?.items.push(this.prepareInventoryItem(item));
}

return {
Expand All @@ -220,9 +220,7 @@ abstract class ActorSheetPF2e<TActor extends ActorPF2e> extends ActorSheet<TActo

protected prepareInventoryItem(item: PhysicalItemPF2e): InventoryItem {
const editable = game.user.isGM || item.isIdentified;
const heldItems = item.isOfType("backpack")
? item.contents.map((i) => this.prepareInventoryItem(i))
: undefined;
const heldItems = item.isOfType("backpack") ? item.contents.map((i) => this.prepareInventoryItem(i)) : null;
heldItems?.sort((a, b) => (a.item.sort || 0) - (b.item.sort || 0));

const actor = this.actor;
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/sheet/data-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ interface InventoryItem<TItem extends PhysicalItemPF2e = PhysicalItemPF2e> {
isInvestable: boolean;
isSellable: boolean;
hasCharges: boolean;
heldItems?: InventoryItem[];
heldItems?: InventoryItem[] | null;
notifyInvestment?: boolean;
/** Whether the item should be hidden if the user isn't the owner */
hidden: boolean;
Expand Down
Loading

0 comments on commit a2d07c7

Please sign in to comment.