Skip to content

Commit

Permalink
Convert loot system data to TypeDataModel (foundryvtt#17284)
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam authored Nov 11, 2024
1 parent 424cddb commit 9f5aadb
Show file tree
Hide file tree
Showing 11 changed files with 91 additions and 73 deletions.
2 changes: 1 addition & 1 deletion src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1848,7 +1848,7 @@ class ActorPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | n
if (isFullReplace) return super._preUpdate(changed, operation, user);

// Always announce HP changes for player-owned actors as floaty text (via `damageTaken` option)
const currentHP = this._source.system.attributes.hp?.value;
const currentHP = this._source.system.attributes?.hp?.value;
const updatedHP = changed.system?.attributes?.hp?.value ?? currentHP;
if (!operation.damageTaken && this.hasPlayerOwner && currentHP && updatedHP && updatedHP !== currentHP) {
const damageTaken = -1 * (updatedHP - currentHP);
Expand Down
25 changes: 14 additions & 11 deletions src/module/actor/data/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ type ActorFlagsPF2e = DocumentFlags & {

type ActorSystemSource = {
details?: ActorDetailsSource;
attributes: ActorAttributesSource;
attributes?: ActorAttributesSource;
traits?: ActorTraitsSource<string>;

/** A record of this actor's current world schema version as well a log of the last migration to occur */
Expand Down Expand Up @@ -88,16 +88,18 @@ interface ActorAttributes extends ActorAttributesSource {
broken: boolean;
itemId: string | null;
};
flanking: {
/** Whether the actor can flank at all */
canFlank: boolean;
/** Given the actor can flank, the conditions under which it can do so without an ally opposite the target */
canGangUp: GangUpCircumstance[];
/** Whether the actor can be flanked at all */
flankable: boolean;
/** Given the actor is flankable, whether it is off-guard when flanked */
offGuardable: OffGuardableCircumstance;
};
flanking: FlankingData;
}

interface FlankingData {
/** Whether the actor can flank at all */
canFlank: boolean;
/** Given the actor can flank, the conditions under which it can do so without an ally opposite the target */
canGangUp: GangUpCircumstance[];
/** Whether the actor can be flanked at all */
flankable: boolean;
/** Given the actor is flankable, whether it is off-guard when flanked */
offGuardable: OffGuardableCircumstance;
}

interface ActorHitPoints extends Required<BaseHitPointsSource> {
Expand Down Expand Up @@ -307,6 +309,7 @@ export type {
BaseActorSourcePF2e,
BaseHitPointsSource,
DamageRollFunction,
FlankingData,
GangUpCircumstance,
HitPointsStatistic,
InitiativeData,
Expand Down
94 changes: 56 additions & 38 deletions src/module/actor/loot/data.ts
Original file line number Diff line number Diff line change
@@ -1,55 +1,73 @@
import {
ActorAttributes,
ActorAttributesSource,
ActorDetails,
ActorSystemData,
ActorSystemSource,
BaseActorSourcePF2e,
} from "@actor/data/base.ts";
import { BaseActorSourcePF2e, FlankingData } from "@actor/data/base.ts";
import { ActorSystemModel, ActorSystemSchema } from "@actor/data/model.ts";
import { ModelPropFromDataField } from "types/foundry/common/data/fields.js";
import type { LootPF2e } from "./document.ts";
import fields = foundry.data.fields;

/** The stored source data of a loot actor */
type LootSource = BaseActorSourcePF2e<"loot", LootSystemSource>;

/** The system-level data of loot actors. */
interface LootSystemSource extends ActorSystemSource {
attributes: LootAttributesSource;
details: LootDetailsSource;
lootSheetType: "Merchant" | "Loot";
hiddenWhenEmpty: boolean;
traits?: never;
class LootSystemData extends ActorSystemModel<LootPF2e, LootSystemSchema> {
static override defineSchema(): LootSystemSchema {
return {
...super.defineSchema(),
details: new fields.SchemaField({
description: new fields.HTMLField({ required: true, nullable: false, blank: true, initial: "" }),
level: new fields.SchemaField({
value: new fields.NumberField({
required: true,
nullable: false,
min: 0,
integer: true,
initial: 0,
}),
}),
}),
lootSheetType: new fields.StringField({
required: true,
nullable: false,
choices: ["Loot", "Merchant"],
initial: "Loot",
}),
hiddenWhenEmpty: new fields.BooleanField(),
};
}
}

interface LootSystemData extends Omit<LootSystemSource, "attributes" | "details">, ActorSystemData {
attributes: LootAttributes;
interface LootSystemData extends ActorSystemModel<LootPF2e, LootSystemSchema>, ModelPropsFromSchema<LootSystemSchema> {
details: LootDetails;
traits?: never;
attributes: LootAttributes;
}

interface LootAttributesSource extends ActorAttributesSource {
hp?: never;
ac?: never;
perception?: never;
immunities?: never;
weaknesses?: never;
resistances?: never;
}
type LootSystemSchema = ActorSystemSchema & {
details: fields.SchemaField<{
description: fields.HTMLField<string, string, true, false, true>;
level: fields.SchemaField<{
value: fields.NumberField<number, number, true, false, true>;
}>;
}>;
lootSheetType: fields.StringField<"Merchant" | "Loot", "Merchant" | "Loot", true, false, true>;
hiddenWhenEmpty: fields.BooleanField;
};

interface LootAttributes
extends Omit<LootAttributesSource, "immunities" | "weaknesses" | "resistances">,
Omit<ActorAttributes, "perception" | "hp" | "ac"> {
initiative?: never;
/** The system-level data of loot actors. */
interface LootSystemSource extends SourceFromSchema<LootSystemSchema> {
attributes?: never;
traits?: never;
schema?: never;
}

interface LootDetailsSource {
alliance?: never;
description: string;
level: {
value: number;
};
interface LootDetails extends ModelPropFromDataField<LootSystemSchema["details"]> {
alliance: null;
}

interface LootDetails extends Omit<LootDetailsSource, "alliance">, ActorDetails {
alliance: null;
interface LootAttributes {
immunities: never[];
weaknesses: never[];
resistances: never[];
flanking: FlankingData;
}

export type { LootSource, LootSystemData, LootSystemSource };
export { LootSystemData };
export type { LootSource, LootSystemSource };
7 changes: 7 additions & 0 deletions src/module/actor/loot/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ class LootPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nu
await Promise.allSettled(promises);
}

/** Set base emphemeral data for later updating by derived-data preparation. */
override prepareBaseData(): void {
const system: DeepPartial<LootSystemData> = this.system;
system.attributes = {};
super.prepareBaseData();
}

/** Never process rules elements on loot actors */
override prepareDerivedData(): void {
this.rules = [];
Expand Down
7 changes: 4 additions & 3 deletions src/module/migration/migrations/812-restructure-iwr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export class Migration812RestructureIWR extends MigrationBase {
override async updateActor(source: ActorSourcePF2e): Promise<void> {
const traits: MaybeWithOldIWRData | undefined = source.system.traits;
if (!traits || source.type === "familiar") return;
const { attributes } = source.system;
const attributes = source.system.attributes;

if ("ci" in traits) {
if (!("game" in globalThis)) delete traits.ci;
Expand All @@ -28,6 +28,7 @@ export class Migration812RestructureIWR extends MigrationBase {
traits["-=di"] = null;

if (
attributes &&
R.isPlainObject(oldData) &&
"value" in oldData &&
Array.isArray(oldData.value) &&
Expand Down Expand Up @@ -62,7 +63,7 @@ export class Migration812RestructureIWR extends MigrationBase {
return weakness;
});

if (weaknesses.length > 0) attributes.weaknesses = weaknesses;
if (attributes && weaknesses.length > 0) attributes.weaknesses = weaknesses;
}
}

Expand Down Expand Up @@ -91,7 +92,7 @@ export class Migration812RestructureIWR extends MigrationBase {
return resistance;
});

if (resistances.length > 0) attributes.resistances = resistances;
if (attributes && resistances.length > 0) attributes.resistances = resistances;
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class Migration882SpellDataReorganization extends MigrationBase {
.filter(R.isTruthy)
.sort();
}
if (!source.system.attributes) return;

source.system.attributes.immunities = source.system.attributes.immunities?.filter(
(i) => !this.#SCHOOL_TRAITS.has(i.type),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,10 @@ export class Migration885ConvertAlignmentDamage extends MigrationBase {
source.type === "character" ? { value: [] } : (source.system.traits ?? { value: [] });

const iwrKeys = ["immunities", "weaknesses", "resistances"] as const;
const iwr: WeaklyTypedIWR = R.pick(source.system.attributes, iwrKeys);
const iwr: WeaklyTypedIWR = R.pick(
source.system.attributes ?? { immunities: undefined, weaknesses: undefined, resistances: undefined },
iwrKeys,
);

for (const key of iwrKeys) {
iwr[key] = iwr[key]?.filter((i) => !["chaotic", "lawful"].includes(i.type));
Expand All @@ -76,7 +79,7 @@ export class Migration885ConvertAlignmentDamage extends MigrationBase {
}
}

fu.mergeObject(source.system.attributes, iwr);
source.system.attributes &&= fu.mergeObject(source.system.attributes, iwr);
traits.value = R.unique(traits.value.sort());
if (traits.value.includes("holy") && traits.value.includes("unholy")) {
// Something weird about this one!
Expand Down
2 changes: 2 additions & 0 deletions src/scripts/hooks/load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { ActorProxyPF2e } from "@actor";
import { AutomaticBonusProgression } from "@actor/character/automatic-bonus-progression.ts";
import { FamiliarSystemData } from "@actor/familiar/data.ts";
import { resetActors } from "@actor/helpers.ts";
import { LootSystemData } from "@actor/loot/data.ts";
import { ActorSheetPF2e } from "@actor/sheet/base.ts";
import { ItemProxyPF2e } from "@item";
import { AbilitySystemData } from "@item/ability/index.ts";
Expand Down Expand Up @@ -84,6 +85,7 @@ export const Load = {

// Actor system data models
CONFIG.Actor.dataModels.familiar = FamiliarSystemData;
CONFIG.Actor.dataModels.loot = LootSystemData;

// Item system data models
CONFIG.Item.dataModels.action = AbilitySystemData;
Expand Down
16 changes: 0 additions & 16 deletions static/template.json
Original file line number Diff line number Diff line change
Expand Up @@ -275,22 +275,6 @@
"previous": null
}
},
"loot": {
"templates": [],
"attributes": {},
"details": {
"description": "",
"level": {
"value": 0
}
},
"lootSheetType": "Loot",
"hiddenWhenEmpty": false,
"_migration": {
"version": null,
"previous": null
}
},
"party": {
"templates": [],
"attributes": {},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import type { PrimaryOccludableObjectMixin } from "./primary-occludable-object.d
* @param [options.name] The name of this sprite.
* @param [options.object] Any object that owns this sprite.
*/
/* eslint-disable @typescript-eslint/no-unused-expressions, no-unused-expressions */
export class PrimarySpriteMesh extends PrimaryOccludableObjectMixin(SpriteMesh) {
constructor(
options: { texture?: PIXI.Texture; name?: string | null; object?: object },
Expand Down
2 changes: 1 addition & 1 deletion types/foundry/common/data/fields.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -805,7 +805,7 @@ export class EmbeddedDataField<
THasInitial extends boolean = true,
> extends SchemaField<
TModelProp["schema"]["fields"],
SourceFromSchema<TModelProp["schema"]["fields"]>,
TModelProp["_source"],
TModelProp,
TRequired,
TNullable,
Expand Down

0 comments on commit 9f5aadb

Please sign in to comment.