DND5e Core 1.3.5

DND5e Core 1.3.5 modded to SW5e System

Combining with DND5e Core 1.3.2 to see one big commit since last core update

DND5e Core 1.3.2 modded to SW5e System
This commit is contained in:
supervj 2021-05-18 09:11:03 -04:00
parent c208552f70
commit b56a074697
147 changed files with 3615 additions and 1875 deletions

View file

@ -6,10 +6,10 @@ export const migrateWorld = async function() {
ui.notifications.info(`Applying SW5e System Migration for version ${game.system.data.version}. Please be patient and do not close your game or shut down your server.`, {permanent: true});
// Migrate World Actors
for ( let a of game.actors.entities ) {
for ( let a of game.actors.contents ) {
try {
const updateData = migrateActorData(a.data);
if ( !isObjectEmpty(updateData) ) {
if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Actor entity ${a.name}`);
await a.update(updateData, {enforceTypes: false});
}
@ -20,10 +20,10 @@ export const migrateWorld = async function() {
}
// Migrate World Items
for ( let i of game.items.entities ) {
for ( let i of game.items.contents ) {
try {
const updateData = migrateItemData(i.data);
if ( !isObjectEmpty(updateData) ) {
const updateData = migrateItemData(i.toObject());
if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Item entity ${i.name}`);
await i.update(updateData, {enforceTypes: false});
}
@ -34,12 +34,15 @@ export const migrateWorld = async function() {
}
// Migrate Actor Override Tokens
for ( let s of game.scenes.entities ) {
for ( let s of game.scenes.contents ) {
try {
const updateData = migrateSceneData(s.data);
if ( !isObjectEmpty(updateData) ) {
if ( !foundry.utils.isObjectEmpty(updateData) ) {
console.log(`Migrating Scene entity ${s.name}`);
await s.update(updateData, {enforceTypes: false});
// If we do not do this, then synthetic token actors remain in cache
// with the un-updated actorData.
s.tokens.contents.forEach(t => t._actor = null);
}
} catch(err) {
err.message = `Failed sw5e system migration for Scene ${s.name}: ${err.message}`;
@ -76,40 +79,39 @@ export const migrateCompendium = async function(pack) {
// Begin by requesting server-side data model migration and get the migrated content
await pack.migrate();
const content = await pack.getContent();
const documents = await pack.getDocuments();
// Iterate over compendium entries - applying fine-tuned migration functions
for ( let ent of content ) {
for ( let doc of documents ) {
let updateData = {};
try {
switch (entity) {
case "Actor":
updateData = migrateActorData(ent.data);
updateData = migrateActorData(doc.data);
break;
case "Item":
updateData = migrateItemData(ent.data);
updateData = migrateItemData(doc.toObject());
break;
case "Scene":
updateData = migrateSceneData(ent.data);
updateData = migrateSceneData(doc.data);
break;
}
if ( isObjectEmpty(updateData) ) continue;
// Save the entry, if data was changed
updateData["_id"] = ent._id;
await pack.updateEntity(updateData);
console.log(`Migrated ${entity} entity ${ent.name} in Compendium ${pack.collection}`);
if ( foundry.utils.isObjectEmpty(updateData) ) continue;
await doc.update(updateData);
console.log(`Migrated ${entity} entity ${doc.name} in Compendium ${pack.collection}`);
}
// Handle migration failures
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);
}
}
// 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}`);
};
@ -120,38 +122,42 @@ export const migrateCompendium = async function(pack) {
/**
* Migrate a single Actor entity to incorporate latest data model changes
* Return an Object of updateData to be applied
* @param {Actor} actor The actor to Update
* @return {Object} The updateData to apply
* @param {object} actor The actor data object to update
* @return {Object} The updateData to apply
*/
export const migrateActorData = function(actor) {
const updateData = {};
// Actor Data Updates
_migrateActorMovement(actor, updateData);
_migrateActorSenses(actor, updateData);
if (actor.data) {
_migrateActorMovement(actor, updateData);
_migrateActorSenses(actor, updateData);
_migrateActorType(actor, updateData);
}
// Migrate Owned Items
if ( !actor.items ) return updateData;
let hasItemUpdates = false;
const items = actor.items.map(i => {
const items = actor.items.reduce((arr, i) => {
// Migrate the Owned Item
let itemUpdate = migrateItemData(i);
const itemData = i instanceof CONFIG.Item.documentClass ? i.toObject() : i;
let itemUpdate = migrateItemData(itemData);
// Prepared, Equipped, and Proficient for NPC actors
if ( actor.type === "npc" ) {
if (getProperty(i.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
if (getProperty(i.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(i.data, "proficient") === false) itemUpdate["data.proficient"] = true;
if (getProperty(itemData.data, "preparation.prepared") === false) itemUpdate["data.preparation.prepared"] = true;
if (getProperty(itemData.data, "equipped") === false) itemUpdate["data.equipped"] = true;
if (getProperty(itemData.data, "proficient") === false) itemUpdate["data.proficient"] = true;
}
// Update the Owned Item
if ( !isObjectEmpty(itemUpdate) ) {
hasItemUpdates = true;
return mergeObject(i, itemUpdate, {enforceTypes: false, inplace: false});
} else return i;
});
if ( hasItemUpdates ) updateData.items = items;
itemUpdate._id = itemData._id;
arr.push(expandObject(itemUpdate));
}
return arr;
}, []);
if ( items.length > 0 ) updateData.items = items;
return updateData;
};
@ -187,11 +193,14 @@ function cleanActorData(actorData) {
/**
* Migrate a single Item entity to incorporate latest data model changes
* @param item
*
* @param {object} item Item data to migrate
* @return {object} The updateData to apply
*/
export const migrateItemData = function(item) {
const updateData = {};
_migrateItemAttunement(item, updateData);
_migrateItemPowercasting(item, updateData);
return updateData;
};
@ -204,24 +213,34 @@ export const migrateItemData = function(item) {
* @return {Object} The updateData to apply
*/
export const migrateSceneData = function(scene) {
const tokens = duplicate(scene.tokens);
return {
tokens: tokens.map(t => {
if (!t.actorId || t.actorLink || !t.actorData.data) {
t.actorData = {};
return t;
}
const token = new Token(t);
if ( !token.actor ) {
t.actorId = null;
t.actorData = {};
} else if ( !t.actorLink ) {
const updateData = migrateActorData(token.data.actorData);
t.actorData = mergeObject(token.data.actorData, updateData);
}
return t;
})
};
const tokens = scene.tokens.map(token => {
const t = token.toJSON();
if (!t.actorId || t.actorLink) {
t.actorData = {};
}
else if ( !game.actors.has(t.actorId) ){
t.actorId = null;
t.actorData = {};
}
else if ( !t.actorLink ) {
const actorData = duplicate(t.actorData);
actorData.type = token.actor?.type;
const update = migrateActorData(actorData);
['items', 'effects'].forEach(embeddedName => {
if (!update[embeddedName]?.length) return;
const updates = new Map(update[embeddedName].map(u => [u._id, u]));
t.actorData[embeddedName].forEach(original => {
const update = updates.get(original._id);
if (update) mergeObject(original, update);
});
delete update[embeddedName];
});
mergeObject(t.actorData, update);
}
return t;
});
return {tokens};
};
/* -------------------------------------------- */
@ -232,13 +251,24 @@ export const migrateSceneData = function(scene) {
* Migrate the actor speed string to movement object
* @private
*/
function _migrateActorMovement(actor, updateData) {
const ad = actor.data;
const old = actor.type === 'vehicle' ? ad?.attributes?.speed : ad?.attributes?.speed?.value;
if ( typeof old !== "string" ) return;
const s = (old || "").split(" ");
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
updateData["data.attributes.-=speed"] = null;
function _migrateActorMovement(actorData, updateData) {
const ad = actorData.data;
// Work is needed if old data is present
const old = actorData.type === 'vehicle' ? ad?.attributes?.speed : ad?.attributes?.speed?.value;
const hasOld = old !== undefined;
if ( hasOld ) {
// If new data is not present, migrate the old data
const hasNew = ad?.attributes?.movement?.walk !== undefined;
if ( !hasNew && (typeof old === "string") ) {
const s = (old || "").split(" ");
if ( s.length > 0 ) updateData["data.attributes.movement.walk"] = Number.isNumeric(s[0]) ? parseInt(s[0]) : null;
}
// Remove the old attribute
updateData["data.attributes.-=speed"] = null;
}
return updateData
}
@ -252,9 +282,10 @@ function _migrateActorSenses(actor, updateData) {
const ad = actor.data;
if ( ad?.traits?.senses === undefined ) return;
const original = ad.traits.senses || "";
if ( typeof original !== "string" ) return;
// Try to match old senses with the format like "Darkvision 60 ft, Blindsight 30 ft"
const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/
const pattern = /([A-z]+)\s?([0-9]+)\s?([A-z]+)?/;
let wasMatched = false;
// Match each comma-separated term
@ -281,17 +312,101 @@ function _migrateActorSenses(actor, updateData) {
/* -------------------------------------------- */
/**
* Migrate the actor details.type string to object
* @private
*/
function _migrateActorType(actor, updateData) {
const ad = actor.data;
const original = ad.details?.type;
if ( typeof original !== "string" ) return;
// New default data structure
let data = {
"value": "",
"subtype": "",
"swarm": "",
"custom": ""
}
// 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;
return updateData;
}
/* -------------------------------------------- */
/**
* 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
*/
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.-=attuned"] = null;
return updateData;
}
/* -------------------------------------------- */
/**
* Replace class powercasting string to object.
*
* @param {object} item Item data to migrate
* @param {object} updateData Existing update to expand upon
* @return {object} The updateData to apply
* @private
*/
function _migrateItemPowercasting(item, updateData) {
if ( item.type !== "class" || (foundry.utils.getType(item.data.powercasting) === "Object") ) return updateData;
updateData["data.powercasting"] = {
progression: item.data.powercasting,
ability: ""
};
return updateData;
}
/* -------------------------------------------- */