Skip to content

Commit

Permalink
Move stamina and resolve out of attributes, refactor settings (foundr…
Browse files Browse the repository at this point in the history
  • Loading branch information
stwlam committed Oct 7, 2023
1 parent 37d3af0 commit 18ea39d
Show file tree
Hide file tree
Showing 34 changed files with 317 additions and 301 deletions.
10 changes: 2 additions & 8 deletions build/run-migration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ import { getFilesRecursively } from "./lib/helpers.ts";
import { MigrationBase } from "@module/migration/base.ts";
import { MigrationRunnerBase } from "@module/migration/runner/base.ts";

import { Migration851JustInnovationId } from "@module/migration/migrations/851-just-innovation-id.ts";
import { Migration852AbilityScoresToModifiers } from "@module/migration/migrations/852-ability-scores-to-modifiers.ts";
import { Migration853RemasterLanguages } from "@module/migration/migrations/853-remaster-languages.ts";
import { Migration854BracketedAbilityScoresToModifiers } from "@module/migration/migrations/854-bracketed-ability-scores-to-modifiers.ts";
import { Migration855ApexEquipmentSystemData } from "@module/migration/migrations/855-apex-equipment-system-data.ts";
import { Migration856NoSystemDotCustom } from "@module/migration/migrations/856-no-system-dot-custom.ts";
import { Migration857WeaponSpecializationRE } from "@module/migration/migrations/857-weapon-spec-re.ts";
Expand All @@ -29,6 +25,7 @@ import { Migration868StrikeRERange } from "@module/migration/migrations/868-stri
import { Migration869RefreshMightyBulwark } from "@module/migration/migrations/869-refresh-mighty-bulwark.ts";
import { Migration870MartialToProficiencies } from "@module/migration/migrations/870-martial-to-proficiencies.ts";
import { Migration873RemoveBonusBulkLimit } from "@module/migration/migrations/873-remove-bonus-bulk-limit.ts";
import { Migration874MoveStaminaStuff } from "@module/migration/migrations/874-move-stamina-stuff.ts";

// ^^^ don't let your IDE use the index in these imports. you need to specify the full path ^^^

Expand All @@ -39,10 +36,6 @@ globalThis.HTMLParagraphElement = window.HTMLParagraphElement;
globalThis.Text = window.Text;

const migrations: MigrationBase[] = [
new Migration851JustInnovationId(),
new Migration852AbilityScoresToModifiers(),
new Migration853RemasterLanguages(),
new Migration854BracketedAbilityScoresToModifiers(),
new Migration855ApexEquipmentSystemData(),
new Migration856NoSystemDotCustom(),
new Migration857WeaponSpecializationRE(),
Expand All @@ -58,6 +51,7 @@ const migrations: MigrationBase[] = [
new Migration869RefreshMightyBulwark(),
new Migration870MartialToProficiencies(),
new Migration873RemoveBonusBulkLimit(),
new Migration874MoveStaminaStuff(),
];

global.deepClone = <T>(original: T): T => {
Expand Down
4 changes: 2 additions & 2 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ declare global {
get(module: "pf2e", setting: "automaticBonusVariant"): "noABP" | "ABPFundamentalPotency" | "ABPRulesAsWritten";
get(module: "pf2e", setting: "dualClassVariant"): boolean;
get(module: "pf2e", setting: "freeArchetypeVariant"): boolean;
get(module: "pf2e", setting: "proficiencyVariant"): "ProficiencyWithLevel" | "ProficiencyWithoutLevel";
get(module: "pf2e", setting: "staminaVariant"): 0 | 1;
get(module: "pf2e", setting: "proficiencyVariant"): boolean;
get(module: "pf2e", setting: "staminaVariant"): boolean;

get(module: "pf2e", setting: "proficiencyUntrainedModifier"): number;
get(module: "pf2e", setting: "proficiencyTrainedModifier"): number;
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1237,7 +1237,7 @@ class ActorPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | n

const hpUpdate = this.calculateHealthDelta({
hp: hitPoints,
sp: this.isOfType("character") ? this.attributes.sp : undefined,
sp: this.isOfType("character") ? this.attributes.hp.sp : undefined,
delta: finalDamage - damageAbsorbedByShield - damageAbsorbedByActor,
});
const hpDamage = hpUpdate.totalApplied;
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/character/crafting/formula.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CraftingFormula implements CraftingFormulaData {
dc ??
calculateDC(item.level, {
rarity: item.rarity,
proficiencyWithoutLevel: game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel",
proficiencyWithoutLevel: game.settings.get("pf2e", "proficiencyVariant"),
});

/** Use the passed batch size if provided or otherwise according to the following */
Expand Down
20 changes: 8 additions & 12 deletions src/module/actor/character/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ interface CharacterAttributesSource extends Omit<ActorAttributesSource, "percept
hp: {
value: number;
temp: number;
/** Stamina points: present if Stamina variant is enabled */
sp?: { value: number };
};
speed: {
value: number;
Expand All @@ -111,15 +113,6 @@ interface CharacterAttributesSource extends Omit<ActorAttributesSource, "percept
}[];
};
initiative: CreatureInitiativeSource;
sp: {
value: number;
max: number;
details: string;
};
resolve: {
value: number;
max: number;
};
}

interface CharacterTraitsSource extends Omit<CreatureTraitsSource, "rarity" | "size"> {
Expand Down Expand Up @@ -217,6 +210,9 @@ interface AttributeBoostsSource {
interface CharacterResourcesSource {
heroPoints: ValueAndMax;
focus?: { value: number; max?: never };
crafting?: { infusedReagents?: { value: number } };
/** Used in the variant stamina rules; a resource expended to regain stamina/hp. */
resolve?: { value: number };
}

/** The raw information contained within the actor data object for characters. */
Expand Down Expand Up @@ -418,6 +414,7 @@ interface CharacterResources extends CreatureResources {
/** The current and maximum number of invested items */
investiture: ValueAndMax;
crafting: { infusedReagents: ValueAndMax };
resolve?: ValueAndMax;
}

interface CharacterPerception extends PerceptionData {
Expand Down Expand Up @@ -485,9 +482,6 @@ interface CharacterAttributes extends Omit<CharacterAttributesSource, Attributes
*/
shield: HeldShieldData;

/** Used in the variant stamina rules; a resource expended to regain stamina/hp. */
resolve: { value: number; max: number };

/** Whether this actor is under a polymorph effect */
polymorphed: boolean;

Expand All @@ -499,6 +493,7 @@ type AttributesSourceOmission = "immunities" | "weaknesses" | "resistances";
interface CharacterHitPoints extends HitPointsStatistic {
recoveryMultiplier: number;
recoveryAddend: number;
sp?: ValueAndMax;
}

interface CharacterTraitsData extends CreatureTraitsData, Omit<CharacterTraitsSource, "size" | "value"> {
Expand Down Expand Up @@ -539,6 +534,7 @@ export type {
CharacterFlags,
CharacterProficiency,
CharacterResources,
CharacterResourcesSource,
CharacterSaveData,
CharacterSaves,
CharacterSkillData,
Expand Down
41 changes: 27 additions & 14 deletions src/module/actor/character/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,12 +540,19 @@ class CharacterPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e

if (game.settings.get("pf2e", "staminaVariant")) {
const halfClassHp = Math.floor(classHP / 2);
systemData.attributes.sp.max = (halfClassHp + systemData.abilities.con.mod) * this.level;
systemData.attributes.resolve.max = systemData.abilities[systemData.details.keyability.value].mod;
systemData.attributes.hp.sp = {
value: systemData.attributes.hp.sp?.value ?? 0,
max: (halfClassHp + systemData.abilities.con.mod) * this.level,
};
systemData.resources.resolve = {
value: systemData.resources.resolve?.value ?? 0,
max: systemData.abilities[systemData.details.keyability.value].mod,
};

modifiers.push(new ModifierPF2e("PF2E.ClassHP", halfClassHp * this.level, "untyped"));
} else {
modifiers.push(new ModifierPF2e("PF2E.ClassHP", classHP * this.level, "untyped"));
delete systemData.resources.resolve;

// Facilitate level-zero variant play by always adding the constitution modifier at at least level 1
const conHP = systemData.abilities.con.mod * Math.max(this.level, 1);
Expand Down Expand Up @@ -1824,21 +1831,27 @@ class CharacterPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e
// Clamp Stamina and Resolve
if (game.settings.get("pf2e", "staminaVariant")) {
// Do not allow stamina to go over max
if (changed.system?.attributes?.sp) {
changed.system.attributes.sp.value = Math.clamped(
changed.system?.attributes?.sp?.value || 0,
0,
systemData.attributes.sp.max
);
if (changed.system?.attributes?.hp?.sp) {
changed.system.attributes.hp.sp.value =
Math.floor(
Math.clamped(
changed.system.attributes.hp.sp?.value ?? 0,
0,
systemData.attributes.hp.sp?.max ?? 0
)
) || 0;
}

// Do not allow resolve to go over max
if (changed.system?.attributes?.resolve) {
changed.system.attributes.resolve.value = Math.clamped(
changed.system?.attributes?.resolve?.value || 0,
0,
systemData.attributes.resolve.max
);
if (changed.system?.resources?.resolve) {
changed.system.resources.resolve.value =
Math.floor(
Math.clamped(
changed.system.resources.resolve.value ?? 0,
0,
systemData.resources.resolve?.max ?? 0
)
) || 0;
}
}

Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/character/sheet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ class CharacterSheetPF2e<TActor extends CharacterPF2e> extends CreatureSheetPF2e
sheetData.data.details.keyability.singleOption = actor.class?.system.keyAbility.value.length === 1;

// Is the stamina variant rule enabled?
sheetData.hasStamina = game.settings.get("pf2e", "staminaVariant") > 0;
sheetData.hasStamina = game.settings.get("pf2e", "staminaVariant");

sheetData.spellcastingEntries = await this.prepareSpellcasting();
sheetData.actions = this.#prepareAbilities();
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/familiar/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ class FamiliarPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e

const { level, masterAttributeModifier } = this;

const masterLevel = game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel" ? 0 : level;
const masterLevel = game.settings.get("pf2e", "proficiencyVariant") ? 0 : level;

const { synthetics } = this;
const speeds = (attributes.speed = this.prepareSpeed("land"));
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/modifiers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ function createProficiencyModifier({
}: CreateProficiencyModifierParams): ModifierPF2e {
rank = Math.clamped(rank, 0, 4) as ZeroToFour;
addLevel ??= rank > 0;
const pwolVariant = game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel";
const pwolVariant = game.settings.get("pf2e", "proficiencyVariant");

const baseBonuses: [number, number, number, number, number] = pwolVariant
? [
Expand Down
5 changes: 2 additions & 3 deletions src/module/actor/npc/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class NPCPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nul
}

get identificationDCs(): CreatureIdentificationData {
const proficiencyWithoutLevel = game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel";
const proficiencyWithoutLevel = game.settings.get("pf2e", "proficiencyVariant");
return creatureIdentificationDCs(this, { proficiencyWithoutLevel });
}

Expand Down Expand Up @@ -114,8 +114,7 @@ class NPCPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | nul

attributes.spellDC = null;
attributes.classDC = ((): { value: number } => {
const proficiencyWithoutLevel =
game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel";
const proficiencyWithoutLevel = game.settings.get("pf2e", "proficiencyVariant");
const levelBasedDC = calculateDC(level.base, { proficiencyWithoutLevel, rarity: this.rarity });
const adjusted = this.isElite ? levelBasedDC + 2 : this.isWeak ? levelBasedDC - 2 : levelBasedDC;
return { value: adjusted };
Expand Down
2 changes: 1 addition & 1 deletion src/module/actor/sheet/popups/identify-popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export class IdentifyItemPopup extends FormApplication<PhysicalItemPF2e> {
override async getData(): Promise<IdentifyPopupData> {
const item = this.object;
const notMatchingTraditionModifier = game.settings.get("pf2e", "identifyMagicNotMatchingTraditionModifier");
const proficiencyWithoutLevel = game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel";
const proficiencyWithoutLevel = game.settings.get("pf2e", "proficiencyVariant");
const dcs = getItemIdentificationDCs(item, { proficiencyWithoutLevel, notMatchingTraditionModifier });

return {
Expand Down
2 changes: 1 addition & 1 deletion src/module/data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ interface ValueAndMaybeMax {
max?: number;
}

type ValueAndMax = Required<ValueAndMaybeMax>;
interface ValueAndMax extends Required<ValueAndMaybeMax> {}

function goesToEleven(value: number): value is ZeroToEleven {
return value >= 0 && value <= 11;
Expand Down
9 changes: 2 additions & 7 deletions src/module/dc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,14 +104,9 @@ interface DCOptions {
rarity?: Rarity;
}

/**
* Normal Level Based DCs
* @param level
* @param proficiencyWithoutLevel
*/
/** Level-based DCs */
function calculateDC(level: number, { proficiencyWithoutLevel, rarity = "common" }: DCOptions = {}): number {
const pwlSetting = game.settings.get("pf2e", "proficiencyVariant");
proficiencyWithoutLevel ??= pwlSetting === "ProficiencyWithoutLevel";
proficiencyWithoutLevel ??= game.settings.get("pf2e", "proficiencyVariant");

// assume level 0 if garbage comes in. We cast level to number because the backing data may actually have it
// stored as a string, which we can't catch at compile time
Expand Down
2 changes: 1 addition & 1 deletion src/module/encounter/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ class EncounterPF2e extends Combat {
fightyPartyMembers.length,
opposition.filter((e) => e.isOfType("character", "npc")).map((e) => e.level),
opposition.filter((e): e is HazardPF2e => e.isOfType("hazard")),
{ proficiencyWithoutLevel: game.settings.get("pf2e", "proficiencyVariant") === "ProficiencyWithoutLevel" }
{ proficiencyWithoutLevel: game.settings.get("pf2e", "proficiencyVariant") }
);
const threat = result.rating;
const budget = { spent: result.totalXP, max: result.encounterBudgets[threat], partyLevel };
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export class Migration651EphemeralFocusPool extends MigrationBase {

override async updateActor(source: ActorSourcePF2e): Promise<void> {
if (source.type !== "character") return;
const systemData: { resources: { focus?: { max?: unknown; "-=max"?: null } } } = source.system;
const systemData: { resources: { focus?: { value?: unknown; max?: never; "-=max"?: null } } } = source.system;
systemData.resources ??= {};

const resources = systemData.resources;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ActorSourcePF2e } from "@actor/data/index.ts";
import { MigrationBase } from "../base.ts";
import { ItemSourcePF2e } from "@item/data/index.ts";
import { recursiveReplaceString } from "@util";
import { MigrationBase } from "../base.ts";

export class Migration873RemoveBonusBulkLimit extends MigrationBase {
static override version = 0.873;
Expand Down
71 changes: 71 additions & 0 deletions src/module/migration/migrations/874-move-stamina-stuff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
import { CharacterSystemSource } from "@actor/character/data.ts";
import { ActorSourcePF2e } from "@actor/data/index.ts";
import { ItemSourcePF2e } from "@item/data/index.ts";
import { isObject, recursiveReplaceString } from "@util";
import { MigrationBase } from "../base.ts";

/** Move stamina/resolve and update setting to be a boolean. */
export class Migration874MoveStaminaStuff extends MigrationBase {
static override version = 0.874;

override async updateActor(source: ActorSourcePF2e): Promise<void> {
if (source.type !== "character") return;

const variantEnabled =
"game" in globalThis &&
game.settings.storage.get("world").find((s) => s.key === "pf2e.staminaVariant")?.value !== '"0"' &&
game.settings.get("pf2e", "staminaVariant");
const systemSource: PCSystemSourceWithOldStaminaData = source.system;

if (isObject<{ value: unknown }>(systemSource.attributes.sp)) {
const value = Math.floor(Number(systemSource.attributes.sp.value)) || 0;
if (value > 0 && variantEnabled) systemSource.attributes.hp.sp = { value };

delete systemSource.attributes.sp;
systemSource.attributes["-=sp"] = null;
}

if (isObject<{ value: unknown }>(systemSource.attributes.resolve)) {
const value = Math.floor(Number(systemSource.attributes.resolve.value)) || 0;
if (value > 0 && variantEnabled) systemSource.resources.resolve = { value };

delete systemSource.attributes.resolve;
systemSource.attributes["-=resolve"] = null;
}
}

override async updateItem(source: ItemSourcePF2e): Promise<void> {
source.system.rules = recursiveReplaceString(source.system.rules, (text) =>
text
.replace(/^system\.attributes\.sp\.max$/, "system.attributes.hp.sp.max")
.replace(/^system\.attributes\.resolve.max$/, "system.resoures.resolve.max")
);
}

override async migrate(): Promise<void> {
const staminaVariant = game.settings.storage.get("world").find((s) => s.key === "pf2e.staminaVariant");
// Pre-V11, this setting was being stored as a string
if (["1", '"1"'].includes(staminaVariant?._source.value ?? "")) {
await game.settings.set("pf2e", "staminaVariant", true);
} else if (staminaVariant) {
await game.settings.set("pf2e", "staminaVariant", false);
}

// Aayyy, while we're at it:
const pwolVariant = game.settings.storage.get("world").find((s) => s.key === "pf2e.proficiencyVariant");
if (pwolVariant?._source.value === '"ProficiencyWithoutLevel"') {
await game.settings.set("pf2e", "proficiencyVariant", true);
} else if (staminaVariant) {
await game.settings.set("pf2e", "proficiencyVariant", false);
}
}
}

type PCSystemSourceWithOldStaminaData = CharacterSystemSource & {
attributes: {
sp?: unknown;
resolve?: unknown;
"-=resolve"?: null;
"-=sp"?: null;
};
};
1 change: 1 addition & 0 deletions src/module/migration/migrations/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,4 @@ export { Migration870MartialToProficiencies } from "./870-martial-to-proficienci
export { Migration871MigrateRollActionMacroParams } from "./871-migrate-rollactionmacro-params.ts";
export { Migration872MoveSchemaProperty } from "./872-move-schema-property.ts";
export { Migration873RemoveBonusBulkLimit } from "./873-remove-bonus-bulk-limit.ts";
export { Migration874MoveStaminaStuff } from "./874-move-stamina-stuff.ts";
2 changes: 1 addition & 1 deletion src/module/migration/runner/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface CollectionDiff<T extends foundry.documents.ActiveEffectSource | ItemSo
export class MigrationRunnerBase {
migrations: MigrationBase[];

static LATEST_SCHEMA_VERSION = 0.873;
static LATEST_SCHEMA_VERSION = 0.874;

static MINIMUM_SAFE_VERSION = 0.618;

Expand Down
Loading

0 comments on commit 18ea39d

Please sign in to comment.