// SPDX-FileCopyrightText: 2021 Johannes Loher // // SPDX-License-Identifier: MIT import { DS4Actor } from "../actor/actor"; import { getGame } from "../helpers"; import { DS4Item } from "../item/item"; import logger from "../logger"; type ItemUpdateDataGetter = ( itemData: Partial, ) => DeepPartial | Record | undefined; export async function migrateItems(getItemUpdateData: ItemUpdateDataGetter): Promise { for (const item of getGame().items ?? []) { try { const updateData = getItemUpdateData(item.toObject()); if (updateData) { logger.info(`Migrating Item document ${item.name} (${item.id})`); await item.update(updateData), { enforceTypes: false }; } } catch (err) { err.message = `Error during migration of Item document ${item.name} (${item.id}), continuing anyways.`; logger.error(err); } } } type ActorUpdateDataGetter = ( itemData: Partial, ) => DeepPartial | undefined; export async function migrateActors(getActorUpdateData: ActorUpdateDataGetter): Promise { for (const actor of getGame().actors ?? []) { try { const updateData = getActorUpdateData(actor.toObject()); if (updateData) { logger.info(`Migrating Actor entity ${actor.name} (${actor.id})`); await actor.update(updateData); } } catch (err) { err.message = `Error during migration of Actor entity ${actor.name} (${actor.id}), continuing anyways.`; logger.error(err); } } } type SceneUpdateDataGetter = ( sceneData: foundry.documents.BaseScene["data"], ) => DeepPartial; export async function migrateScenes(getSceneUpdateData: SceneUpdateDataGetter): Promise { for (const scene of getGame().scenes ?? []) { try { const updateData = getSceneUpdateData(scene.data); if (updateData) { logger.info(`Migrating Scene document ${scene.name} (${scene.id})`); await scene.update(updateData); } } catch (err) { err.message = `Error during migration of Scene document ${scene.name} (${scene.id}), continuing anyways.`; logger.error(err); } } } type CompendiumMigrator = (compendium: CompendiumCollection) => Promise; export async function migrateCompendiums(migrateCompendium: CompendiumMigrator): Promise { for (const compendium of getGame().packs ?? []) { if (compendium.metadata.package !== "world") continue; if (!["Actor", "Item", "Scene"].includes(compendium.metadata.entity)) continue; await migrateCompendium(compendium); } } export function getActorUpdateDataGetter(getItemUpdateData: ItemUpdateDataGetter): ActorUpdateDataGetter { return ( actorData: Partial, ): DeepPartial | undefined => { let hasItemUpdates = false; const items = actorData.items?.map((itemData) => { const update = getItemUpdateData(itemData); if (update) { hasItemUpdates = true; return { ...itemData, ...update }; } else { return itemData; } }); return hasItemUpdates ? { items } : undefined; }; } export function getSceneUpdateDataGetter(getActorUpdateData: ActorUpdateDataGetter): SceneUpdateDataGetter { return (sceneData: foundry.documents.BaseScene["data"]) => { const tokens = (sceneData.tokens as Collection).map((token: TokenDocument) => { const t = token.toObject(); if (!t.actorId || t.actorLink) { t.actorData = {}; } else if (!getGame().actors?.has(t.actorId)) { t.actorId = null; t.actorData = {}; } else if (!t.actorLink) { const actorData = foundry.utils.deepClone(t.actorData); actorData.type = token.actor?.type; const update = getActorUpdateData(actorData); if (update !== undefined) { ["items" as const, "effects" as const].forEach((embeddedName) => { const embeddedUpdates = update[embeddedName]; if (embeddedUpdates === undefined || !embeddedUpdates.length) return; const updates = new Map(embeddedUpdates.flatMap((u) => (u && u._id ? [[u._id, u]] : []))); const originals = t.actorData[embeddedName]; if (!originals) return; originals.forEach((original) => { if (!original._id) return; const update = updates.get(original._id); if (update) foundry.utils.mergeObject(original, update); }); delete update[embeddedName]; }); foundry.utils.mergeObject(t.actorData, update); } } return t; }); return { tokens }; }; } export function getCompendiumMigrator( { getItemUpdateData, getActorUpdateData, getSceneUpdateData, }: { getItemUpdateData?: ItemUpdateDataGetter; getActorUpdateData?: ActorUpdateDataGetter; getSceneUpdateData?: SceneUpdateDataGetter; } = {}, { migrateToTemplateEarly = true } = {}, ) { return async (compendium: CompendiumCollection): Promise => { const entityName = compendium.metadata.entity; if (!["Actor", "Item", "Scene"].includes(entityName)) return; const wasLocked = compendium.locked; await compendium.configure({ locked: false }); if (migrateToTemplateEarly) { await compendium.migrate(); } const documents = await compendium.getDocuments(); for (const doc of documents) { try { logger.info(`Migrating document ${doc.name} (${doc.id}) in compendium ${compendium.collection}`); if (doc instanceof DS4Item && getItemUpdateData) { const updateData = getItemUpdateData(doc.toObject()); updateData && (await doc.update(updateData)); } else if (doc instanceof DS4Actor && getActorUpdateData) { const updateData = getActorUpdateData(doc.toObject()); updateData && (await doc.update(updateData)); } else if (doc instanceof Scene && getSceneUpdateData) { const updateData = getSceneUpdateData(doc.data); updateData && (await doc.update(updateData)); } } catch (err) { err.message = `Error during migration of document ${doc.name} (${doc.id}) in compendium ${compendium.collection}, continuing anyways.`; logger.error(err); } } if (!migrateToTemplateEarly) { await compendium.migrate(); } await compendium.configure({ locked: wasLocked }); }; }