Skip to content
Open
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
3 changes: 3 additions & 0 deletions src/global.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import type { ChatMessagePF2e } from "@module/chat-message/index.ts";
import type { ActorsPF2e } from "@module/collection/actors.ts";
import type { CombatantPF2e, EncounterPF2e } from "@module/encounter/index.ts";
import type { MacroPF2e } from "@module/macro.ts";
import { MigrationRunner } from "@module/migration/index.ts";
import type { RuleElement, RuleElements } from "@module/rules/index.ts";
import type { UserPF2e } from "@module/user/index.ts";
import type {
Expand Down Expand Up @@ -185,9 +186,11 @@ interface GamePF2e extends Game<
xpFromEncounter: typeof xpFromEncounter;
};
system: {
migrationRunner: MigrationRunner | null;
moduleArt: ModuleArt;
remigrate: typeof remigrate;
sluggify: typeof sluggify;
worldNeedsMigration: boolean;
generateItemName: (item: PhysicalItemPF2e) => string;
};
variantRules: {
Expand Down
11 changes: 11 additions & 0 deletions src/module/actor/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,8 @@ class ActorPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | n
});
}

declare _migrationSource?: this["_source"];

static override getDefaultArtwork(actorData: ActorSourcePF2e): {
img: ImageFilePath;
texture: { src: ImageFilePath | VideoFilePath };
Expand Down Expand Up @@ -712,6 +714,15 @@ class ActorPF2e<TParent extends TokenDocumentPF2e | null = TokenDocumentPF2e | n
source: Record<string, unknown>,
options?: DocumentConstructionContext<TParent>,
): this["_source"] {
// Record unpruned source data if a migration is necessary
if (game.pf2e.system.worldNeedsMigration && !this._migrationSource) {
Object.defineProperty(this, "_migrationSource", {
value: fu.deepClone(source),
writable: false,
enumerable: false,
});
Object.seal(this._migrationSource);
}
const initialized = super._initializeSource(source, options);

if (options?.pack && initialized._id) {
Expand Down
18 changes: 18 additions & 0 deletions src/module/item/base/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ class ItemPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extends Item
/** The item that granted this item, if any */
declare grantedBy: ItemPF2e<ActorPF2e> | null;

declare _migrationSource?: this["_source"];

static override getDefaultArtwork(itemData: foundry.documents.ItemSource): { img: ImageFilePath } {
return { img: `systems/${SYSTEM_ID}/icons/default-icons/${itemData.type}.svg` as const };
}
Expand Down Expand Up @@ -254,6 +256,22 @@ class ItemPF2e<TParent extends ActorPF2e | null = ActorPF2e | null> extends Item
return this.toMessage(event, { create: true });
}

protected override _initializeSource(
data: this["_source"],
options?: foundry.abstract.DataModelConstructionContext<TParent>,
): this["_source"] {
// Record unpruned source data for world items if necessary
if (game.pf2e.system.worldNeedsMigration && !this.parent && !this._migrationSource) {
Object.defineProperty(this, "_migrationSource", {
value: fu.deepClone(data),
writable: false,
enumerable: false,
});
Object.seal(this._migrationSource);
}
return super._initializeSource(data, options);
}

protected override _initialize(options?: Record<string, unknown>): void {
this.rules = [];
this.specialOptions = [];
Expand Down
4 changes: 2 additions & 2 deletions src/module/migration/runner/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ export class MigrationRunner extends MigrationRunnerBase {
}

async #migrateItem(migrations: MigrationBase[], item: ItemPF2e): Promise<ItemSourcePF2e | null> {
const baseItem = this.#removeSpecialKeys(item.toObject());
const baseItem = this.#removeSpecialKeys(fu.deepClone(item._migrationSource) ?? item.toObject());

try {
return await this.getUpdatedItem(baseItem, migrations);
Expand All @@ -144,7 +144,7 @@ export class MigrationRunner extends MigrationRunnerBase {
options: { pack?: Maybe<string> } = {},
): Promise<ActorSourcePF2e | null> {
const pack = options.pack;
const baseActor = this.#removeSpecialKeys(actor.toObject());
const baseActor = this.#removeSpecialKeys(fu.deepClone(actor._migrationSource) ?? actor.toObject());

const updatedActor = await (async () => {
try {
Expand Down
6 changes: 3 additions & 3 deletions src/scripts/hooks/ready.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { resetActors } from "@actor/helpers.ts";
import { createFirstParty } from "@actor/party/helpers.ts";
import { MigrationSummary } from "@module/apps/migration-summary.ts";
import { SceneDarknessAdjuster } from "@module/apps/scene-darkness-adjuster.ts";
import { MigrationList } from "@module/migration/index.ts";
import { MigrationRunner } from "@module/migration/runner/index.ts";
import { SetGamePF2e } from "@scripts/set-game-pf2e.ts";
import { activateSocketListener } from "@scripts/socket.ts";
Expand Down Expand Up @@ -50,8 +49,8 @@ export const Ready = {
await createFirstParty();

// Perform migrations, if any
const migrationRunner = new MigrationRunner(MigrationList.constructFromVersion(currentVersion));
if (migrationRunner.needsMigration()) {
const migrationRunner = game.pf2e.system.migrationRunner;
if (game.pf2e.system.worldNeedsMigration && migrationRunner) {
if (currentVersion && currentVersion < MigrationRunner.MINIMUM_SAFE_VERSION) {
ui.notifications.error(
`Your PF2E system data is from too old a Foundry version and cannot be reliably migrated to the latest version. The process will be attempted, but errors may occur.`,
Expand All @@ -61,6 +60,7 @@ export const Ready = {
await migrationRunner.runMigration();
new MigrationSummary().render(true);
}
game.pf2e.system.migrationRunner = null;

// Update the world system version
const previous = game.settings.get(SYSTEM_ID, "worldSystemVersion");
Expand Down
35 changes: 34 additions & 1 deletion src/scripts/set-game-pf2e.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { Action } from "@actor/actions/index.ts";
import { AutomaticBonusProgression } from "@actor/character/automatic-bonus-progression.ts";
import { ElementalBlast } from "@actor/character/elemental-blast.ts";
import { ActorSourcePF2e } from "@actor/data/index.ts";
import { CheckModifier, Modifier, StatisticModifier } from "@actor/modifiers.ts";
import { Coins, generateItemName } from "@item/physical/helpers.ts";
import { checkPrompt } from "@module/apps/check-prompt-generator.ts";
import { CompendiumBrowser } from "@module/apps/compendium-browser/browser.ts";
import { EffectsPanel } from "@module/apps/effects-panel.ts";
import { WorldClock } from "@module/apps/world-clock/index.ts";
import { StatusEffects } from "@module/canvas/status-effects.ts";
import { MigrationList } from "@module/migration/index.ts";
import { MigrationRunner } from "@module/migration/runner/index.ts";
import { RuleElement, RuleElements } from "@module/rules/index.ts";
import { DicePF2e } from "@scripts/dice.ts";
import {
Expand Down Expand Up @@ -35,6 +38,7 @@ import { ModuleArt } from "@system/module-art.ts";
import { Predicate } from "@system/predication.ts";
import { TextEditorPF2e } from "@system/text-editor.ts";
import { sluggify } from "@util";
import * as R from "remeda";
import { EarnIncomeDialog } from "./macros/earn-income.ts";

/** Expose public game.pf2e interface */
Expand Down Expand Up @@ -74,6 +78,28 @@ export const SetGamePF2e = {
UNTYPED: "untyped",
} as const;

// Get world schema version from game data as settings are not initalized yet
const currentVersion = ((): number => {
const storedSchemaVersion =
Number(game.data.settings.find((s) => s.key === "pf2e.worldSchemaVersion")?.value) || 0;
if (storedSchemaVersion) return storedSchemaVersion;
const minimumVersion = MigrationRunner.RECOMMENDED_SAFE_VERSION;
const actors = game.data.actors;
const getActorSchemaVersion = (actor: ActorSourcePF2e): number | null => {
const legacyValue = R.isPlainObject(actor.system.schema)
? Number(actor.system.schema.version) || null
: null;
return Number(actor.system._migration?.version) || legacyValue;
};
return actors.length === 0
? MigrationRunner.LATEST_SCHEMA_VERSION
: Math.max(
Math.min(...new Set(actors.map((a) => getActorSchemaVersion(a) ?? minimumVersion))),
minimumVersion,
);
})();
const migrationRunner = new MigrationRunner(MigrationList.constructFromVersion(currentVersion));

const initSafe: Partial<(typeof game)["pf2e"]> = {
Check: Check,
CheckModifier,
Expand Down Expand Up @@ -103,7 +129,14 @@ export const SetGamePF2e = {
},
rollActionMacro,
rollItemMacro,
system: { generateItemName, moduleArt: new ModuleArt(), remigrate, sluggify },
system: {
generateItemName,
migrationRunner,
moduleArt: new ModuleArt(),
remigrate,
sluggify,
worldNeedsMigration: migrationRunner.needsMigration(),
},
variantRules: { AutomaticBonusProgression },
};
game.pf2e = fu.mergeObject(game.pf2e ?? {}, initSafe);
Expand Down
1 change: 1 addition & 0 deletions types/foundry/client/game.d.mts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export default class Game<
macros: TMacro["_source"][];
messages: TChatMessage["_source"][];
packs: CompendiumMetadata[];
settings: Setting["_source"][];
tables: foundry.documents.RollTableSource[];
users: TUser["_source"][];
version: string;
Expand Down
Loading