diff --git a/src/module/actor/army/data.ts b/src/module/actor/army/data.ts index 3f0e3a9d664..d9289d6a4d8 100644 --- a/src/module/actor/army/data.ts +++ b/src/module/actor/army/data.ts @@ -1,117 +1,202 @@ -import { - ActorAttributes, - ActorAttributesSource, - ActorDetails, - ActorDetailsSource, - ActorHitPoints, - ActorSystemData, - ActorSystemSource, - ActorTraitsData, - ActorTraitsSource, - BaseActorSourcePF2e, - BaseHitPointsSource, -} from "@actor/data/base.ts"; -import { ValueAndMax, ValueAndMaybeMax } from "@module/data.ts"; +import { ActorSystemSource, BaseActorSourcePF2e } from "@actor/data/base.ts"; +import { Immunity, ImmunitySource, Resistance, ResistanceSource, Weakness, WeaknessSource } from "@actor/data/iwr.ts"; +import { ActorSystemModel, ActorSystemSchema, ActorTraitsSchema } from "@actor/data/schema.ts"; +import { InitiativeTraceData } from "@actor/initiative.ts"; +import { ActorAlliance } from "@actor/types.ts"; +import { Rarity } from "@module/data.ts"; +import { AutoChangeEntry } from "@module/rules/rule-element/ae-like.ts"; import { PerceptionTraceData } from "@system/statistic/perception.ts"; -import { Alignment } from "./types.ts"; -import { ARMY_TYPES } from "./values.ts"; - -type ArmySource = BaseActorSourcePF2e<"army", ArmySystemSource>; - -interface ArmySystemSource extends ActorSystemSource { - ac: ArmyArmorClass; - attributes: ArmyAttributesSource; - details: ArmyDetailsSource; - traits: ArmyTraitsSource; - - consumption: number; - scouting: number; - recruitmentDC: number; - - resources: ArmyResourcesSource; +import { NumberField, SchemaField, StringField } from "types/foundry/common/data/fields.js"; +import { ArmyPF2e } from "./document.ts"; +import { ArmyType } from "./types.ts"; +import { ARMY_STATS, ARMY_TYPES } from "./values.ts"; + +const fields = foundry.data.fields; + +class ArmySystemData extends ActorSystemModel { + static override defineSchema(): ArmySystemSchema { + const parent = super.defineSchema(); + + function createWeaponSchema(): ArmyWeaponSchema { + return { + name: new fields.StringField(), + potency: new fields.NumberField({ required: true, nullable: false, initial: 0 }), + }; + } + + return { + ...parent, + ac: new fields.SchemaField({ + value: new fields.NumberField({ required: true, nullable: false, initial: ARMY_STATS.ac[1] }), + potency: new fields.NumberField({ required: true, nullable: false, initial: 0 }), + }), + attributes: new fields.SchemaField({ + hp: new fields.SchemaField({ + value: new fields.NumberField({ required: true, nullable: false, initial: 4 }), + temp: new fields.NumberField({ required: true, nullable: false, initial: 0 }), + max: new fields.NumberField({ required: true, nullable: false, initial: 4 }), + routThreshold: new fields.NumberField({ required: true, nullable: false, initial: 2 }), + }), + }), + details: new fields.SchemaField({ + level: new fields.SchemaField({ + value: new fields.NumberField({ required: true, nullable: false, initial: 1 }), + }), + }), + consumption: new fields.NumberField({ required: true, nullable: false, initial: 1 }), + scouting: new fields.NumberField({ required: true, nullable: false, initial: ARMY_STATS.scouting[1] }), + recruitmentDC: new fields.NumberField(), + saves: new fields.SchemaField({ + maneuver: new fields.NumberField({ + required: true, + nullable: false, + initial: ARMY_STATS.strongSave[1], + }), + morale: new fields.NumberField({ required: true, nullable: false, initial: ARMY_STATS.weakSave[1] }), + }), + weapons: new fields.SchemaField({ + melee: new fields.SchemaField(createWeaponSchema()), + ranged: new fields.SchemaField(createWeaponSchema()), + }), + resources: new fields.SchemaField({ + /** How often this army can use ranged attacks */ + ammunition: new fields.SchemaField({ + value: new fields.NumberField({ required: true, nullable: false, initial: 0 }), + }), + potions: new fields.SchemaField({ + value: new fields.NumberField({ required: true, nullable: false, initial: 0 }), + }), + }), + traits: new fields.SchemaField({ + value: new fields.ArrayField(new fields.StringField({ required: true, nullable: false })), + rarity: new fields.StringField({ required: true, nullable: false, initial: "common" }), + type: new fields.StringField({ + required: true, + nullable: false, + choices: ARMY_TYPES, + initial: "infantry", + }), + }), + }; + } +} - saves: { - maneuver: number; - morale: number; +interface ArmySystemData extends ActorSystemModel, ModelPropsFromSchema { + attributes: ModelPropsFromSchema & { + hp: { + max: number; + negativeHealing: boolean; + unrecoverable: number; + details: string; + }; + immunities: Immunity[]; + weaknesses: Weakness[]; + resistances: Resistance[]; + flanking: never; }; - - weapons: { - ranged: ArmyWeaponData | null; - melee: ArmyWeaponData | null; + initiative: InitiativeTraceData; + details: ModelPropsFromSchema & { + alliance: ActorAlliance; }; -} - -interface ArmyWeaponData { - name: string; - potency: number; -} - -interface ArmyArmorClass { - value: number; - potency: number; -} - -interface ArmyTraitsSource extends Required> { - languages?: never; - type: (typeof ARMY_TYPES)[number]; - alignment: Alignment; -} - -interface ArmyDetailsSource extends Required { - strongSave: string; - weakSave: string; - description: string; -} - -interface ArmySystemData extends Omit, ActorSystemData { - attributes: ArmyAttributes; - traits: ArmyTraits; perception: Pick; - details: ArmyDetails; - resources: ArmyResourcesData; - saves: ArmySystemSource["saves"] & { - strongSave: "maneuver" | "morale"; + traits: ModelPropsFromSchema & { + size?: never; }; + resources: ModelPropsFromSchema["resources"] & { + ammunition: { max: number }; + potions: { max: number }; + }; + /** An audit log of automatic, non-modifier changes applied to various actor data nodes */ + autoChanges: Record; } -interface ArmyAttributesSource extends ActorAttributesSource { - immunities?: never; - weaknesses?: never; - resistances?: never; - - hp: ArmyHitPointsSource; - ac: never; -} - -interface ArmyAttributes - extends Omit, - ActorAttributes { - ac: never; - hp: ArmyHitPoints; -} - -interface ArmyHitPointsSource extends Required { - /** Typically half the army's hit points, armies that can't be feared have a threshold of 0 instead */ - routThreshold: number; -} - -interface ArmyHitPoints extends ArmyHitPointsSource, ActorHitPoints { - negativeHealing: boolean; - unrecoverable: number; -} - -interface ArmyResourcesSource { - /** How often this army can use ranged attacks */ - ammunition: ValueAndMax; - potions: ValueAndMaybeMax; -} - -interface ArmyResourcesData extends ArmyResourcesSource { - potions: ValueAndMax; -} - -interface ArmyTraits extends Omit, Required> {} +type ArmySystemSchema = Omit & { + ac: SchemaField<{ + value: NumberField; + potency: NumberField; + }>; + + attributes: SchemaField; + details: SchemaField; + + consumption: NumberField; + scouting: NumberField; + recruitmentDC: NumberField; + + saves: SchemaField<{ + maneuver: NumberField; + morale: NumberField; + }>; + + weapons: SchemaField<{ + ranged: SchemaField< + ArmyWeaponSchema, + SourceFromSchema, + ModelPropsFromSchema, + false, + true + >; + melee: SchemaField< + ArmyWeaponSchema, + SourceFromSchema, + ModelPropsFromSchema, + false, + true + >; + }>; + + resources: SchemaField<{ + /** How often this army can use ranged attacks */ + ammunition: SchemaField<{ + value: NumberField; + }>; + potions: SchemaField<{ + value: NumberField; + }>; + }>; + + traits: SchemaField; +}; + +type ArmyAttributesSchema = { + hp: SchemaField<{ + value: NumberField; + temp: NumberField; + max: NumberField; + routThreshold: NumberField; + }>; +}; + +type ArmyDetailsSchema = { + level: SchemaField<{ + value: NumberField; + }>; +}; + +type ArmyTraitsSchema = ActorTraitsSchema & { + rarity: StringField; + type: StringField; +}; + +type ArmyWeaponSchema = { + name: StringField; + potency: NumberField; +}; + +type ArmySystemSource = SourceFromSchema & { + attributes: { + immunities?: ImmunitySource[]; + weaknesses?: WeaknessSource[]; + resistances?: ResistanceSource[]; + flanking: never; + hp: { + details: string; + }; + }; + /** Legacy location of `MigrationRecord` */ + schema?: ActorSystemSource["schema"]; +}; -interface ArmyDetails extends ArmyDetailsSource, ActorDetails {} +type ArmySource = BaseActorSourcePF2e<"army", ArmySystemSource>; export type { ArmySource, ArmySystemData }; diff --git a/src/module/actor/army/document.ts b/src/module/actor/army/document.ts index 0d221103519..1e538050d85 100644 --- a/src/module/actor/army/document.ts +++ b/src/module/actor/army/document.ts @@ -55,6 +55,10 @@ class ArmyPF2e= this.system.saves.morale ? "maneuver" : "morale"; + } + override prepareData(): void { super.prepareData(); this.kingdom?.notifyUpdate(); @@ -63,13 +67,8 @@ class ArmyPF2e= this.system.saves.morale ? "maneuver" : "morale"; this.system.perception = { senses: [] }; this.system.details.alliance = this.hasPlayerOwner ? "party" : "opposition"; @@ -155,7 +154,7 @@ class ArmyPF2e; @@ -17,4 +17,6 @@ interface ArmyStrike { critical: DamageRollFunction; } -export type { Alignment, ArmyStrike }; +type ArmyType = (typeof ARMY_TYPES)[number]; + +export type { Alignment, ArmyStrike, ArmyType }; diff --git a/src/module/actor/base.ts b/src/module/actor/base.ts index 76f36505300..25725acb850 100644 --- a/src/module/actor/base.ts +++ b/src/module/actor/base.ts @@ -173,7 +173,7 @@ class ActorPF2e { abilities?: Abilities; details: ActorDetails; actions?: StrikeData[]; diff --git a/src/module/actor/data/schema.ts b/src/module/actor/data/schema.ts new file mode 100644 index 00000000000..a5cb79e0da1 --- /dev/null +++ b/src/module/actor/data/schema.ts @@ -0,0 +1,59 @@ +import { ActorPF2e } from "@actor"; +import { ArrayField, NumberField, SchemaField, StringField } from "types/foundry/common/data/fields.js"; + +const fields = foundry.data.fields; + +abstract class ActorSystemModel extends foundry.abstract + .TypeDataModel { + static override defineSchema(): ActorSystemSchema { + 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 = { + /** The currently selected initiative, as well as initiative trace data */ + _migration: SchemaField<{ + version: NumberField; + previous: SchemaField< + { + foundry: StringField; + system: StringField; + schema: NumberField; + }, + { foundry: string | null; system: string | null; schema: number | null }, + { foundry: string | null; system: string | null; schema: number | null }, + true, + true, + true + >; + }>; +}; + +type ActorTraitsSchema = { + value: ArrayField>; +}; + +export { ActorSystemModel }; +export type { ActorSystemSchema, ActorTraitsSchema }; diff --git a/static/templates/actors/army/sheet.hbs b/static/templates/actors/army/sheet.hbs index ffb5fdd9da4..347d12ad620 100644 --- a/static/templates/actors/army/sheet.hbs +++ b/static/templates/actors/army/sheet.hbs @@ -12,9 +12,6 @@ -