// SPDX-FileCopyrightText: 2021 Johannes Loher // // SPDX-License-Identifier: MIT import { DS4Actor } from "../documents/actor/actor"; import { DS4Item } from "../documents/item/item"; import { logger } from "../utils/logger"; import { getGame } from "../utils/utils"; /** @typedef {(itemData: Partial) => DeepPartial | Record | undefined} ItemUpdateDataGetter */ /** * Migrate world items. * @param {ItemUpdateDataGetter} getItemUpdateData A function for getting the update data for a given item data object * @returns {Promise} A promise that resolves once the migration is complete */ export async function migrateItems(getItemUpdateData) { 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) { logger.error(`Error during migration of Item document ${item.name} (${item.id}), continuing anyways.`, err); } } } /** @typedef {(actorData: Partial) => DeepPartial | undefined} ActorUpdateDataGetter */ /** * Migrate world actors. * @param {ActorUpdateDataGetter} getActorUpdateData A function for getting the update data for a given actor data object * @returns {Promise} A promise that resolves once the migration is complete */ export async function migrateActors(getActorUpdateData) { for (const actor of getGame().actors ?? []) { try { const updateData = getActorUpdateData(actor.toObject()); if (updateData) { logger.info(`Migrating Actor document ${actor.name} (${actor.id})`); await actor.update(updateData); } } catch (err) { logger.error( `Error during migration of Actor document ${actor.name} (${actor.id}), continuing anyways.`, err, ); } } } /** @typedef {(aceneData: foundry.data.SceneData) => DeepPartial | undefined} SceneUpdateDataGetter */ /** * Migrate world scenes. * @param {SceneUpdateDataGetter} getSceneUpdateData A function for getting the update data for a given scene data object * @returns {Promise} A promise that resolves once the migration is complete */ export async function migrateScenes(getSceneUpdateData) { 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) { logger.error( `Error during migration of Scene document ${scene.name} (${scene.id}), continuing anyways.`, err, ); } } } /** @typedef {(pack: CompendiumCollection) => Promise} CompendiumMigrator*/ /** * Migrate world compendium packs. * @param {CompendiumMigrator} migrateCompendium A function for migrating a single compendium pack * @returns {Promise} A promise that resolves once the migration is complete */ export async function migrateCompendiums(migrateCompendium) { for (const compendium of getGame().packs ?? []) { if (compendium.metadata.package !== "world") continue; if (!["Actor", "Item", "Scene"].includes(compendium.metadata.type)) continue; await migrateCompendium(compendium); } } /** * Get a function to create actor update data that adjusts the owned items of the actor according to the given function. * @param {ItemUpdateDataGetter} getItemUpdateData The function to generate item update data * @returns {ActorUpdateDataGetter} A function to get actor update data */ export function getActorUpdateDataGetter(getItemUpdateData) { return (actorData) => { 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; }; } /** * Get a function to create scene update data that adjusts the actors of the tokens of the scene according to the given function. * @param {ActorUpdateDataGetter} getItemUpdateData The function to generate actor update data * @returns {SceneUpdateDataGetter} A function to get scene update data */ export function getSceneUpdateDataGetter(getActorUpdateData) { return (sceneData) => { const tokens = sceneData.tokens.map((token) => { 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", "effects"].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 }; }; } /** * @typedef {object} UpdateDataGetters * @property {ItemUpdateDataGetter} [getItemUpdateData] * @property {ActorUpdateDataGetter} [getActorUpdateData] * @property {SceneUpdateDataGetter} [getSceneUpdateData] */ /** * Get a compendium migrator for the given update data getters. * @param {UpdateDataGetters} [updateDataGetters={}] The functions to use for getting update data * @param {{migrateToTemplateEarly?: boolean}} [options={}] Additional options for the compendium migrator * @returns {CompendiumMigrator} The resulting compendium migrator */ export function getCompendiumMigrator( { getItemUpdateData, getActorUpdateData, getSceneUpdateData } = {}, { migrateToTemplateEarly = true } = {}, ) { return async (pack) => { const type = pack.metadata.type; if (!["Actor", "Item", "Scene"].includes(type)) return; const wasLocked = pack.locked; await pack.configure({ locked: false }); if (migrateToTemplateEarly) { await pack.migrate(); } const documents = await pack.getDocuments(); for (const doc of documents) { try { logger.info(`Migrating document ${doc.name} (${doc.id}) in compendium ${pack.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) { logger.error( `Error during migration of document ${doc.name} (${doc.id}) in compendium ${pack.collection}, continuing anyways.`, err, ); } } if (!migrateToTemplateEarly) { await pack.migrate(); } await pack.configure({ locked: wasLocked }); }; }