Skip to content

Commit

Permalink
Convert army to data model
Browse files Browse the repository at this point in the history
  • Loading branch information
CarlosFdez committed Jul 26, 2024
1 parent 1d00850 commit fc36660
Show file tree
Hide file tree
Showing 7 changed files with 262 additions and 120 deletions.
297 changes: 191 additions & 106 deletions src/module/actor/army/data.ts
Original file line number Diff line number Diff line change
@@ -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<ArmyPF2e, ArmySystemSchema> {
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<ArmyPF2e, ArmySystemSchema>, ModelPropsFromSchema<ArmySystemSchema> {
attributes: ModelPropsFromSchema<ArmyAttributesSchema> & {
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<ArmyDetailsSchema> & {
alliance: ActorAlliance;
};
}

interface ArmyWeaponData {
name: string;
potency: number;
}

interface ArmyArmorClass {
value: number;
potency: number;
}

interface ArmyTraitsSource extends Required<ActorTraitsSource<string>> {
languages?: never;
type: (typeof ARMY_TYPES)[number];
alignment: Alignment;
}

interface ArmyDetailsSource extends Required<ActorDetailsSource> {
strongSave: string;
weakSave: string;
description: string;
}

interface ArmySystemData extends Omit<ArmySystemSource, "attributes">, ActorSystemData {
attributes: ArmyAttributes;
traits: ArmyTraits;
perception: Pick<PerceptionTraceData, "senses">;
details: ArmyDetails;
resources: ArmyResourcesData;
saves: ArmySystemSource["saves"] & {
strongSave: "maneuver" | "morale";
traits: ModelPropsFromSchema<ArmyTraitsSchema> & {
size?: never;
};
resources: ModelPropsFromSchema<ArmySystemSchema>["resources"] & {
ammunition: { max: number };
potions: { max: number };
};
/** An audit log of automatic, non-modifier changes applied to various actor data nodes */
autoChanges: Record<string, AutoChangeEntry[] | undefined>;
}

interface ArmyAttributesSource extends ActorAttributesSource {
immunities?: never;
weaknesses?: never;
resistances?: never;

hp: ArmyHitPointsSource;
ac: never;
}

interface ArmyAttributes
extends Omit<ArmyAttributesSource, "immunities" | "weaknesses" | "resistances">,
ActorAttributes {
ac: never;
hp: ArmyHitPoints;
}

interface ArmyHitPointsSource extends Required<BaseHitPointsSource> {
/** 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<ArmyTraitsSource, "size">, Required<ActorTraitsData<string>> {}
type ArmySystemSchema = Omit<ActorSystemSchema, "attributes" | "traits" | "resources"> & {
ac: SchemaField<{
value: NumberField<number, number, true, false, true>;
potency: NumberField<number, number, true, false, true>;
}>;

attributes: SchemaField<ArmyAttributesSchema>;
details: SchemaField<ArmyDetailsSchema>;

consumption: NumberField<number, number, true, false, true>;
scouting: NumberField<number, number, true, false, true>;
recruitmentDC: NumberField;

saves: SchemaField<{
maneuver: NumberField<number, number, true, false, true>;
morale: NumberField<number, number, true, false, true>;
}>;

weapons: SchemaField<{
ranged: SchemaField<
ArmyWeaponSchema,
SourceFromSchema<ArmyWeaponSchema>,
ModelPropsFromSchema<ArmyWeaponSchema>,
false,
true
>;
melee: SchemaField<
ArmyWeaponSchema,
SourceFromSchema<ArmyWeaponSchema>,
ModelPropsFromSchema<ArmyWeaponSchema>,
false,
true
>;
}>;

resources: SchemaField<{
/** How often this army can use ranged attacks */
ammunition: SchemaField<{
value: NumberField<number, number, true, false, true>;
}>;
potions: SchemaField<{
value: NumberField<number, number, true, false, true>;
}>;
}>;

traits: SchemaField<ArmyTraitsSchema>;
};

type ArmyAttributesSchema = {
hp: SchemaField<{
value: NumberField<number, number, true, false, true>;
temp: NumberField<number, number, true, false, true>;
max: NumberField<number, number, true, false, true>;
routThreshold: NumberField<number, number, true, false, true>;
}>;
};

type ArmyDetailsSchema = {
level: SchemaField<{
value: NumberField<number, number, true, false, true>;
}>;
};

type ArmyTraitsSchema = ActorTraitsSchema<string> & {
rarity: StringField<Rarity, Rarity, true, false>;
type: StringField<ArmyType, ArmyType, true, false>;
};

type ArmyWeaponSchema = {
name: StringField;
potency: NumberField<number, number, true, false, true>;
};

type ArmySystemSource = SourceFromSchema<ArmySystemSchema> & {
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 };
13 changes: 6 additions & 7 deletions src/module/actor/army/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ class ArmyPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nu
return ARMY_STATS.maxTactics[this.level];
}

get strongSave(): "maneuver" | "morale" {
return this.system.saves.maneuver >= this.system.saves.morale ? "maneuver" : "morale";
}

override prepareData(): void {
super.prepareData();
this.kingdom?.notifyUpdate();
Expand All @@ -63,13 +67,8 @@ class ArmyPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nu
override prepareBaseData(): void {
super.prepareBaseData();

// Set certain properties to their default values if omitted
this.system.ac.value ??= this._source.system.ac.value ??= ARMY_STATS.ac[this.level];
this.system.scouting ??= this._source.system.scouting ??= ARMY_STATS.scouting[this.level];

this.system.details.level.value = Math.clamp(this.system.details.level.value, 1, 20);
this.system.resources.potions.max = 3;
this.system.saves.strongSave = this.system.saves.maneuver >= this.system.saves.morale ? "maneuver" : "morale";
this.system.perception = { senses: [] };

this.system.details.alliance = this.hasPlayerOwner ? "party" : "opposition";
Expand Down Expand Up @@ -155,7 +154,7 @@ class ArmyPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nu
// Add statistics for saving throws
// Note: Kingmaker refers to these as both a type of save (high/low save) but also as "maneuver check"
for (const saveType of ["maneuver", "morale"] as const) {
const table = this.system.saves.strongSave === saveType ? ARMY_STATS.strongSave : ARMY_STATS.weakSave;
const table = this.strongSave === saveType ? ARMY_STATS.strongSave : ARMY_STATS.weakSave;
const baseValue = table[this.level];
const adjustment = this.system.saves[saveType] - baseValue;

Expand Down Expand Up @@ -383,7 +382,7 @@ class ArmyPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nu
newLevel = Math.clamp(newLevel, 1, 20);
const currentLevel = this.system.details.level.value;

const strongSave = this.system.saves.strongSave;
const strongSave = this.strongSave;
const strongSaveDifference = ARMY_STATS.strongSave[newLevel] - ARMY_STATS.strongSave[currentLevel];
const weakSaveDifference = ARMY_STATS.weakSave[newLevel] - ARMY_STATS.weakSave[currentLevel];

Expand Down
6 changes: 4 additions & 2 deletions src/module/actor/army/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { DamageRollFunction, RollFunction } from "@actor/data/base.ts";
import { ALIGNMENTS } from "./values.ts";
import { AttackRollParams } from "@system/rolls.ts";
import { ALIGNMENTS, ARMY_TYPES } from "./values.ts";

type Alignment = SetElement<typeof ALIGNMENTS>;

Expand All @@ -17,4 +17,6 @@ interface ArmyStrike {
critical: DamageRollFunction;
}

export type { Alignment, ArmyStrike };
type ArmyType = (typeof ARMY_TYPES)[number];

export type { Alignment, ArmyStrike, ArmyType };
2 changes: 1 addition & 1 deletion src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ 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;
return Number(this.system._migration?.version ?? this._source.system.schema?.version) || null;
}

/** Get an active GM or, failing that, a player who can update this actor */
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 @@ -63,7 +63,7 @@ interface ActorDetailsSource {
alliance?: ActorAlliance;
}

interface ActorSystemData extends ActorSystemSource {
interface ActorSystemData extends Omit<ActorSystemSource, "schema"> {
abilities?: Abilities;
details: ActorDetails;
actions?: StrikeData[];
Expand Down
Loading

0 comments on commit fc36660

Please sign in to comment.