Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/module/actor/character/document.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CreaturePF2e, type FamiliarPF2e } from "@actor";
import { Abilities } from "@actor/creature/data.ts";
import { CreatureSaves } from "@actor/creature/saves.ts";
import { CreatureUpdateCallbackOptions, ResourceData } from "@actor/creature/types.ts";
import { ALLIANCES, SAVING_THROW_ATTRIBUTES } from "@actor/creature/values.ts";
import { StrikeData } from "@actor/data/base.ts";
Expand Down Expand Up @@ -769,8 +770,7 @@ class CharacterPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e

private prepareSaves(): void {
const wornArmor = this.wornArmor;

this.saves = R.mapToObj(SAVE_TYPES, (saveType) => {
const saves = R.mapToObj(SAVE_TYPES, (saveType) => {
const save = this.system.saves[saveType];
const saveName = _loc(CONFIG.PF2E.saves[saveType]);
const modifiers: Modifier[] = [];
Expand Down Expand Up @@ -833,6 +833,7 @@ class CharacterPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e

return [saveType, statistic];
});
this.saves = new CreatureSaves(saves);
}

private prepareSkills() {
Expand Down
6 changes: 5 additions & 1 deletion src/module/actor/creature/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import { ErrorPF2e, localizer, setHasElement, sluggify, tupleHasValue } from "@u
import * as R from "remeda";
import { CreatureMovementData, CreatureResources, CreatureSystemData, VisionLevel, VisionLevels } from "./data.ts";
import { getHpAdjustment, imposeEncumberedCondition, setImmunitiesFromTraits } from "./helpers.ts";
import { CreatureSaves } from "./saves.ts";
import type {
CreatureMovement,
CreatureSpeeds,
Expand All @@ -57,12 +58,15 @@ abstract class CreaturePF2e<
declare spellcasting: ActorSpellcasting<this>;

declare parties: Set<PartyPF2e>;

/** A creature always has an AC */
declare armorClass: StatisticDifficultyClass<ArmorStatistic>;

/** Skill checks for the creature, built during data prep */
declare skills: Record<string, Statistic<this>>;

/** Saving throw rolls for the creature, built during data prep */
declare saves: Record<SaveType, Statistic>;
declare saves: CreatureSaves;

declare perception: PerceptionStatistic;

Expand Down
53 changes: 53 additions & 0 deletions src/module/actor/creature/saves.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { SaveType } from "@actor/types.ts";
import type { Statistic } from "@system/statistic/statistic.ts";
import { tupleHasValue } from "@util";
import type { CreaturePF2e } from "./document.ts";

/** A record of saving throws with convenience getters for derived data */
export class CreatureSaves {
constructor(saves: Record<SaveType, Statistic<CreaturePF2e>>) {
this.fortitude = saves.fortitude;
this.reflex = saves.reflex;
this.will = saves.will;
}

readonly fortitude: Statistic<CreaturePF2e>;

readonly reflex: Statistic<CreaturePF2e>;

readonly will: Statistic<CreaturePF2e>;

/**
* The highest saving throw factoring in only attribute modifier and proficiency bonus (or base in the case of NPCS)
*/
get baseHighest(): Statistic {
return [this.fortitude, this.reflex, this.will].reduce((highest, candidate) => {
const candidateBase = this.#getBaseValue(candidate);
const highestBase = this.#getBaseValue(highest);
return candidateBase > highestBase ? candidate : highest;
});
}

/**
* The highest saving throw factoring in only attribute modifier and proficiency bonus (or base in the case of NPCS)
*/
get baseLowest(): Statistic {
return [this.fortitude, this.reflex, this.will].reduce((lowest, candidate) => {
const candidateBase = this.#getBaseValue(candidate);
const lowestBase = this.#getBaseValue(lowest);
return candidateBase < lowestBase ? candidate : lowest;
});
}

/**
* Get the "base" value of a saving-throw statistic, or the sum of attribute modifier and proficiency bonus (or just
* the "base"-slugged modifier in the case of NPCS).
*/
#getBaseValue(statistic: Statistic): number {
const baseTypes = ["ability", "proficiency"] as const;
return statistic.modifiers.reduce(
(b, m) => (tupleHasValue(baseTypes, m.type) || m.slug === "base" ? b + m.value : b),
0,
);
}
}
43 changes: 18 additions & 25 deletions src/module/actor/familiar/document.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { CreaturePF2e, type CharacterPF2e } from "@actor";
import type { ActorPF2e } from "@actor/base.ts";
import type { CreatureSaves } from "@actor/creature/data.ts";
import type { CreatureUpdateCallbackOptions } from "@actor/creature/index.ts";
import { CreatureSaves } from "@actor/creature/saves.ts";
import { createEncounterRollOptions, setHitPointsRollOptions } from "@actor/helpers.ts";
import { Modifier, applyStackingRules } from "@actor/modifiers.ts";
import type { SaveType } from "@actor/types.ts";
import { SAVE_TYPES } from "@actor/values.ts";
import type { DatabaseDeleteCallbackOptions } from "@common/abstract/_types.d.mts";
import type { ActorUUID } from "@common/documents/_module.d.mts";
Expand Down Expand Up @@ -115,29 +114,23 @@ class FamiliarPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e
system.attributes.ac = fu.mergeObject(statistic.getTraceData(), { attribute: statistic.attribute ?? "dex" });

// Saving Throws
this.saves = SAVE_TYPES.reduce(
(partialSaves, saveType) => {
const save = master?.saves[saveType];
const source = save?.modifiers.filter((m) => !["status", "circumstance"].includes(m.type)) ?? [];
const totalMod = applyStackingRules(source);
const attribute = CONFIG.PF2E.savingThrowDefaultAttributes[saveType];
const selectors = [saveType, `${attribute}-based`, "saving-throw", "all"];
const stat = new Statistic(this, {
slug: saveType,
label: _loc(CONFIG.PF2E.saves[saveType]),
domains: selectors,
modifiers: [new Modifier(`PF2E.MasterSavingThrow.${saveType}`, totalMod, "untyped")],
check: { type: "saving-throw" },
});

return { ...partialSaves, [saveType]: stat };
},
{} as Record<SaveType, Statistic>,
);
system.saves = SAVE_TYPES.reduce(
(partial, saveType) => ({ ...partial, [saveType]: this.saves[saveType].getTraceData() }),
{} as CreatureSaves,
);
const saves = R.mapToObj(SAVE_TYPES, (saveType) => {
const save = master?.saves[saveType];
const source = save?.modifiers.filter((m) => !["status", "circumstance"].includes(m.type)) ?? [];
const totalMod = applyStackingRules(source);
const attribute = CONFIG.PF2E.savingThrowDefaultAttributes[saveType];
const selectors = [saveType, `${attribute}-based`, "saving-throw", "all"];
const statistic = new Statistic(this, {
slug: saveType,
label: _loc(CONFIG.PF2E.saves[saveType]),
domains: selectors,
modifiers: [new Modifier(`PF2E.MasterSavingThrow.${saveType}`, totalMod, "untyped")],
check: { type: "saving-throw" },
});
return [saveType, statistic];
});
this.saves = new CreatureSaves(saves);
system.saves = R.mapToObj(SAVE_TYPES, (t) => [t, this.saves[t].getTraceData()]);

// Attack
const masterLevel = game.pf2e.settings.variants.pwol.enabled ? 0 : level;
Expand Down
19 changes: 7 additions & 12 deletions src/module/actor/npc/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ import type { ActorUpdateCallbackOptions, ActorUpdateOperation } from "@actor/ba
import type { Abilities } from "@actor/creature/data.ts";
import { getHpAdjustment } from "@actor/creature/helpers.ts";
import type { CreatureUpdateCallbackOptions } from "@actor/creature/index.ts";
import { CreatureSaves } from "@actor/creature/saves.ts";
import { ActorSizePF2e } from "@actor/data/size.ts";
import { attackFromMeleeItem, setHitPointsRollOptions } from "@actor/helpers.ts";
import { ActorInitiative } from "@actor/initiative.ts";
import { Modifier, StatisticModifier } from "@actor/modifiers.ts";
import type { MovementType, SaveType } from "@actor/types.ts";
import type { MovementType } from "@actor/types.ts";
import { SAVE_TYPES } from "@actor/values.ts";
import type { UserAction } from "@common/constants.d.mts";
import type { ItemPF2e, MeleePF2e } from "@item";
Expand Down Expand Up @@ -301,16 +302,12 @@ class NPCPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nul
private prepareSaves(): void {
const system = this.system;
const modifierAdjustments = this.synthetics.modifierAdjustments;

// Saving Throws
const saves: Partial<Record<SaveType, Statistic>> = {};
for (const saveType of SAVE_TYPES) {
const saves = R.mapToObj(SAVE_TYPES, (saveType) => {
const save = system.saves[saveType];
const saveName = _loc(CONFIG.PF2E.saves[saveType]);
const base = save.value;
const attribute = save.attribute;
const domains = [saveType, `${attribute}-based`, "saving-throw", "all"];

const statistic = new Statistic(this, {
slug: saveType,
label: saveName,
Expand All @@ -327,13 +324,11 @@ class NPCPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nul
type: "saving-throw",
},
});

saves[saveType] = statistic;
fu.mergeObject(this.system.saves[saveType], statistic.getTraceData());
fu.mergeObject(system.saves[saveType], statistic.getTraceData());
system.saves[saveType].base = base;
}

this.saves = saves as Record<SaveType, Statistic>;
return [saveType, statistic];
});
this.saves = new CreatureSaves(saves);
}

private prepareSkills() {
Expand Down
Loading