Potentially updated Migration

This commit is contained in:
Jacob Lucas 2021-06-09 02:29:03 +01:00
parent db5e90281c
commit 37a3e83f3a
3 changed files with 133 additions and 39 deletions

View file

@ -1,4 +1,3 @@
// TODO: Update
/** /**
* Perform a system migration for the entire World, applying migrations for Actors, Items, and Compendium packs * Perform a system migration for the entire World, applying migrations for Actors, Items, and Compendium packs
* @return {Promise} A Promise which resolves once the migration is completed * @return {Promise} A Promise which resolves once the migration is completed
@ -11,7 +10,7 @@ export const migrateWorld = async function() {
try { try {
console.log(`Checking Actor entity ${a.name} for migration needs`); console.log(`Checking Actor entity ${a.name} for migration needs`);
const updateData = await migrateActorData(a.data); const updateData = await migrateActorData(a.data);
if ( !isObjectEmpty(updateData) ) { if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Actor entity ${a.name}`); console.log(`Migrating Actor entity ${a.name}`);
await a.update(updateData, {enforceTypes: false}); await a.update(updateData, {enforceTypes: false});
} }
@ -25,7 +24,7 @@ export const migrateWorld = async function() {
for ( let i of game.items.entities ) { for ( let i of game.items.entities ) {
try { try {
const updateData = migrateItemData(i.data); const updateData = migrateItemData(i.data);
if ( !isObjectEmpty(updateData) ) { if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Item entity ${i.name}`); console.log(`Migrating Item entity ${i.name}`);
await i.update(updateData, {enforceTypes: false}); await i.update(updateData, {enforceTypes: false});
} }
@ -39,7 +38,7 @@ export const migrateWorld = async function() {
for ( let s of game.scenes.entities ) { for ( let s of game.scenes.entities ) {
try { try {
const updateData = await migrateSceneData(s.data); const updateData = await migrateSceneData(s.data);
if ( !isObjectEmpty(updateData) ) { if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Scene entity ${s.name}`); console.log(`Migrating Scene entity ${s.name}`);
await s.update(updateData, {enforceTypes: false}); await s.update(updateData, {enforceTypes: false});
} }
@ -78,40 +77,39 @@ export const migrateCompendium = async function(pack) {
// Begin by requesting server-side data model migration and get the migrated content // Begin by requesting server-side data model migration and get the migrated content
await pack.migrate(); await pack.migrate();
const content = await pack.getContent(); const documents = await pack.getDocuments();
// Iterate over compendium entries - applying fine-tuned migration functions // Iterate over compendium entries - applying fine-tuned migration functions
for await ( let ent of content ) { for await ( let doc of documents ) {
let updateData = {}; let updateData = {};
try { try {
switch (entity) { switch (entity) {
case "Actor": case "Actor":
updateData = await migrateActorData(ent.data); updateData = await migrateActorData(doc.data);
break; break;
case "Item": case "Item":
updateData = migrateItemData(ent.data); updateData = migrateItemData(doc.data);
break; break;
case "Scene": case "Scene":
updateData = await migrateSceneData(ent.data); updateData = await migrateSceneData(doc.data);
break; break;
} }
if ( isObjectEmpty(updateData) ) continue; if ( foundry.utils.isObjectEmpty(updateData) ) continue;
// Save the entry, if data was changed // Save the entry, if data was changed
updateData["_id"] = ent._id; await doc.update(updateData);
await pack.updateEntity(updateData); console.log(`Migrated ${entity} entity ${doc.name} in Compendium ${pack.collection}`);
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
} }
// Handle migration failures // Handle migration failures
catch(err) { catch(err) {
err.message = `Failed sw5e system migration for entity ${ent.name} in pack ${pack.collection}: ${err.message}`; err.message = `Failed sw5e system migration for entity ${doc.name} in pack ${pack.collection}: ${err.message}`;
console.error(err); console.error(err);
} }
} }
// Apply the original locked status for the pack // Apply the original locked status for the pack
pack.configure({locked: wasLocked}); await pack.configure({locked: wasLocked});
console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`); console.log(`Migrated all ${entity} entities from Compendium ${pack.collection}`);
}; };
@ -131,15 +129,15 @@ export const migrateActorData = async function(actor) {
// Actor Data Updates // Actor Data Updates
_migrateActorMovement(actor, updateData); _migrateActorMovement(actor, updateData);
_migrateActorSenses(actor, updateData); _migrateActorSenses(actor, updateData);
_migrateActorType(actor, updateData);
// Migrate Owned Items // Migrate Owned Items
if ( !!actor.items ) { if ( !!actor.items ) {
let hasItemUpdates = false;
const items = await actor.items.reduce(async (memo, i) => { const items = await actor.items.reduce(async (memo, i) => {
const results = await memo; const results = await memo;
// Migrate the Owned Item // Migrate the Owned Item
let itemUpdate = await migrateActorItemData(i, actor); let itemUpdate = await migrateActorItemData(i.data, actor);
// Prepared, Equipped, and Proficient for NPC actors // Prepared, Equipped, and Proficient for NPC actors
if ( actor.type === "npc" ) { if ( actor.type === "npc" ) {
@ -150,13 +148,15 @@ export const migrateActorData = async function(actor) {
// Update the Owned Item // Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) { if ( !isObjectEmpty(itemUpdate) ) {
hasItemUpdates = true; itemUpdate._id = i.id;
console.log(`Migrating Actor ${actor.name}'s ${i.name}`); console.log(`Migrating Actor ${actor.name}'s ${i.name}`);
return [...results, mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false})]; results.push(itemUpdate)
} else return [...results, i]; }
return results;
}, []); }, []);
if ( hasItemUpdates ) updateData.items = items; if ( items.length > 0 ) updateData.items = items;
} }
// Update NPC data with new datamodel information // Update NPC data with new datamodel information
@ -235,24 +235,20 @@ export const migrateActorItemData = async function(item, actor) {
* @return {Object} The updateData to apply * @return {Object} The updateData to apply
*/ */
export const migrateSceneData = async function(scene) { export const migrateSceneData = async function(scene) {
const tokens = duplicate(scene.tokens); const tokens = await Promise.all(scene.tokens.map(async token => {
return { const t = token.toJSON();
tokens: await Promise.all(tokens.map(async (t) => {
if (!t.actorId || t.actorLink || !t.actorData.data) { if (!t.actorId || t.actorLink || !t.actorData.data) {
t.actorData = {}; t.actorData = {};
return t;
} }
const token = new Token(t); else if (!game.actors.has(t.actorId)) {
if ( !token.actor ) {
t.actorId = null; t.actorId = null;
t.actorData = {}; t.actorData = {};
} else if ( !t.actorLink ) { } else if ( !t.actorLink ) {
const updateData = await migrateActorData(token.data.actorData); t.actorData = mergeObject(token.data.actorData, await migrateActorData(t.actorData));
t.actorData = mergeObject(token.data.actorData, updateData);
} }
return t; return t;
})) }));
}; return {tokens};
}; };
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -448,6 +444,84 @@ function _migrateActorSenses(actor, updateData) {
return updateData; return updateData;
} }
/**
* Migrate the actor details.type string to object
* @private
*/
function _migrateActorType(actor, updateData) {
const ad = actor.data;
const original = ad.details?.type;
if ( (original === undefined) || (foundry.utils.getType(original) === "Object") ) return;
// New default data structure
let data = {
"value": "",
"subtype": "",
"swarm": "",
"custom": ""
}
// Specifics
// (Some of these have weird names, these need to be addressed individually)
if (original === "force entity") {
data.value = "force";
data.subtype = "storm";
} else if (original === "human") {
data.value = "humanoid";
data.subtype = "human";
} else if (["humanoid (any)", "humanoid (Villainous"].includes(original)) {
data.value = "humanoid";
} else if (original === "tree") {
data.value = "plant";
data.subtype = "tree";
} else if (original === "(humanoid) or Large (beast) force entity") {
data.value = "force";
} else if (original === "droid (appears human)") {
data.value = "droid";
} else {
// Match the existing string
const pattern = /^(?:swarm of (?<size>[\w\-]+) )?(?<type>[^(]+?)(?:\((?<subtype>[^)]+)\))?$/i;
const match = original.trim().match(pattern);
if (match) {
// Match a known creature type
const typeLc = match.groups.type.trim().toLowerCase();
const typeMatch = Object.entries(CONFIG.SW5E.creatureTypes).find(([k, v]) => {
return (typeLc === k) ||
(typeLc === game.i18n.localize(v).toLowerCase()) ||
(typeLc === game.i18n.localize(`${v}Pl`).toLowerCase());
});
if (typeMatch) data.value = typeMatch[0];
else {
data.value = "custom";
data.custom = match.groups.type.trim().titleCase();
}
data.subtype = match.groups.subtype?.trim().titleCase() || "";
// Match a swarm
const isNamedSwarm = actor.name.startsWith(game.i18n.localize("SW5E.CreatureSwarm"));
if (match.groups.size || isNamedSwarm) {
const sizeLc = match.groups.size ? match.groups.size.trim().toLowerCase() : "tiny";
const sizeMatch = Object.entries(CONFIG.SW5E.actorSizes).find(([k, v]) => {
return (sizeLc === k) || (sizeLc === game.i18n.localize(v).toLowerCase());
});
data.swarm = sizeMatch ? sizeMatch[0] : "tiny";
} else data.swarm = "";
}
// No match found
else {
data.value = "custom";
data.custom = original;
}
}
// Update the actor data
updateData["data.details.type"] = data;
console.log(data);
return updateData;
}
/* -------------------------------------------- */ /* -------------------------------------------- */
/** /**
@ -457,19 +531,35 @@ function _migrateItemClassPowerCasting(item, updateData) {
if (item.type === "class"){ if (item.type === "class"){
switch (item.name){ switch (item.name){
case "Consular": case "Consular":
updateData["data.powercasting"] = "consular"; updateData["data.powercasting"] = {
progression: "consular",
ability: ""
};
break; break;
case "Engineer": case "Engineer":
updateData["data.powercasting"] = "engineer";
updateData["data.powercasting"] = {
progression: "engineer",
ability: ""
};
break; break;
case "Guardian": case "Guardian":
updateData["data.powercasting"] = "guardian"; updateData["data.powercasting"] = {
progression: "guardian",
ability: ""
};
break; break;
case "Scout": case "Scout":
updateData["data.powercasting"] = "scout"; updateData["data.powercasting"] = {
progression: "scout",
ability: ""
};
break; break;
case "Sentinel": case "Sentinel":
updateData["data.powercasting"] = "sentinel"; updateData["data.powercasting"] = {
progression: "sentinel",
ability: ""
};
break; break;
} }
} }
@ -527,10 +617,14 @@ async function _migrateItemPower(item, actor, updateData) {
/** /**
* Delete the old data.attuned boolean * Delete the old data.attuned boolean
*
* @param {object} item Item data to migrate
* @param {object} updateData Existing update to expand upon
* @return {object} The updateData to apply
* @private * @private
*/ */
function _migrateItemAttunement(item, updateData) { function _migrateItemAttunement(item, updateData) {
if ( item.data.attuned === undefined ) return; if ( item.data.attuned === undefined ) return updateData;
updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE; updateData["data.attunement"] = CONFIG.SW5E.attunementTypes.NONE;
updateData["data.-=attuned"] = null; updateData["data.-=attuned"] = null;
return updateData; return updateData;

View file

@ -219,7 +219,7 @@ Hooks.once("ready", function() {
// Determine whether a system migration is required and feasible // Determine whether a system migration is required and feasible
if ( !game.user.isGM ) return; if ( !game.user.isGM ) return;
const currentVersion = game.settings.get("sw5e", "systemMigrationVersion"); const currentVersion = game.settings.get("sw5e", "systemMigrationVersion");
const NEEDS_MIGRATION_VERSION = "1.2.4.R1-A5"; const NEEDS_MIGRATION_VERSION = "1.3.2.R1-A5";
// Check for R1 SW5E versions // Check for R1 SW5E versions
const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5"; const SW5E_NEEDS_MIGRATION_VERSION = "R1-A5";
const COMPATIBLE_MIGRATION_VERSION = 0.80; const COMPATIBLE_MIGRATION_VERSION = 0.80;

View file

@ -2,7 +2,7 @@
"name": "sw5e", "name": "sw5e",
"title": "SW 5th Edition", "title": "SW 5th Edition",
"description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.", "description": "A comprehensive game system for running games of SW 5th Edition in the Foundry VTT environment.",
"version": "1.3.2", "version": "1.3.2.R1-A6",
"author": "Dev Team", "author": "Dev Team",
"scripts": [], "scripts": [],
"esmodules": ["sw5e.js"], "esmodules": ["sw5e.js"],