Skip to content

Instantly share code, notes, and snippets.

@akrigline
Created April 28, 2022 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save akrigline/170159bf376969b69c25a62f97398982 to your computer and use it in GitHub Desktop.
Save akrigline/170159bf376969b69c25a62f97398982 to your computer and use it in GitHub Desktop.
1.6.0 migration macros
// the compendium id which should have the subclasses added to it
const PACK_NAME = 'world.custom-subclasses';
// the core system includes subclasses without features defined, should these be created as subclasses?
const INCLUDE_EMPTY_SUBCLASSES = false;
/* Create Subclass Items that are missing (based on name matching) in the Compendium per legacy CONFIG.DND5E.classFeatures */
/* v0.1 */
const pack = game.packs.get(PACK_NAME);
if (!pack) {
ui.notifications.error('No pack by that name');
return;
};
const itemData = Object.entries(CONFIG.DND5E.classFeatures).map(([classIdentifier, { subclasses }]) => {
const subclassItemData = Object.entries(subclasses).map(([subclassIdentifier, { label, features, source }]) => {
if (!INCLUDE_EMPTY_SUBCLASSES && (!features || foundry.utils.isObjectEmpty(features))) return;
const advancements = (!features || foundry.utils.isObjectEmpty(features)) ? [] : Object.entries(features).map(([level, items]) => {
return {
classRestriction: "",
level,
icon: "",
type: 'ItemGrant',
title: "Features",
configuration: { items },
value: {},
_id: foundry.utils.randomID(),
}
});
return {
data: {
advancement: advancements,
identifier: subclassIdentifier,
classIdentifier,
source,
},
name: label,
type: 'subclass'
}
});
return subclassItemData;
}).flat().filter(Boolean).filter(({name}) => !pack.index.getName(name));
console.log("Creating Items:", itemData);
await Item.implementation.create(itemData, { pack: PACK_NAME });
pack.render(true);
ui.notifications.notify('Complete');
const ACTOR_NAME = "Peter";
/**
* Attempts to migrate the given actor to use the advancements on their classes as defined in the compendium source for those classes
* v0.2
*/
/**
* Gets all actor changes related to the given actor and class item
* @returns {Promise<object[]>} array of objects representing Item updates
*/
const migrateClass = async (ogActor, ogClass) => {
if (!ogClass.getFlag('core', 'sourceId')) {
ui.notifications.warn(`${ogClass.name} does not appear to have a source item`);
return [];
}
const original = await fromUuid(ogClass.getFlag('core', 'sourceId'));
if (!original) {
ui.notifications.warn(`No original class found for ${ogClass.name}, was it deleted?`);
return [];
}
const advancements = original.toJSON().data.advancement
.filter(({type}) => type !== 'HitPoints');
const actorLevels = ogClass.data.data.levels;
// output of this function: all items that should be changed to migrate this class
let changedItems = [];
const changedAdvancements = advancements.map((advancement) => {
// return unchanged if not an itemGrant advancement
if (advancement.type !== 'ItemGrant') return advancement;
if (advancement.level <= actorLevels) {
// allow skipping these because we have already leveled up this actor presumably
advancement.configuration.optional = true;
}
advancement.configuration.items.forEach((uuid) => {
const relevantIndex = game.dnd5e.utils.indexFromUuid(uuid);
// search the actor's items for one with the sourceId equal to the advancement itemgrant item's uuid
// OR fall back to name matching if the item searched has no sourceId flags (brittle)
const relevantActorItem = ogActor.items.find((item) => {
// prefer sourceId flags if those exist
if (item.getFlag('core', 'sourceId') || item.getFlag('dnd5e', 'sourceId')) {
return item.getFlag('core', 'sourceId') === uuid || item.getFlag('dnd5e', 'sourceId') === uuid;
}
// fall back to name matching (brittle)
return item.name === relevantIndex.name;
});
// if one is found, add this item to `added` and add advancementOrigin flag to the item
if (!!relevantActorItem) {
if (!advancement.value.added) advancement.value.added = {};
// add the appropriate flags to the existing item as well
changedItems.push({
_id: relevantActorItem.id,
flags: { dnd5e: {
advancementOrigin: `${ranger.id}.${advancement.id}`
}}
});
advancement.value.added[relevantActorItem.id] = uuid;
}
// if one is not found, simply skip it
});
return advancement;
});
const changedClass = {
_id: ogClass.id,
data: {
advancement: changedAdvancements
}
};
changedItems.push(changedClass);
return changedItems;
}
const ogActor = game.actors.getName(ACTOR_NAME);
if (!ogActor) {
ui.notifications.error('No actor found with that name');
return;
}
let allChangedItems = [];
for (actorClass of ogActor.itemTypes.class) {
const classChangedItems = await migrateClass(ogActor, actorClass);
allChangedItems.push(...classChangedItems);
}
console.log('completed', ogActor.items, allChangedItems);
await ogActor.updateEmbeddedDocuments('Item', allChangedItems);
ui.notifications.notify('Complete');
const PACK_NAME = 'world.blank-class-items';
/* Update All Class Items in the Compendium per legacy CONFIG.DND5E.classFeatures */
/* v0.3 */
const pack = game.packs.get(PACK_NAME);
if (!pack) {
ui.notifications.error('No pack by that name');
return;
};
const docs = await pack.getDocuments();
for (let item of docs) {
const className = item.name.slugify({strict: true});
const clsConfig = CONFIG.DND5E.classFeatures[className];
if (!clsConfig) continue;
const advancements = Object.entries(clsConfig.features).map(([level, items]) => {
return {
classRestriction: "",
level,
icon: "",
type: 'ItemGrant',
title: "Features",
configuration: { items },
value: {},
_id: foundry.utils.randomID(),
}
});
// add hitpoints advancement because this is a class
advancements.push({
classRestriction: "",
type: "HitPoints",
icon: "",
title: "",
configuration: {},
value: {},
_id: foundry.utils.randomID(),
});
await item.update({
'data.advancement': advancements
});
}
pack.render(true);
ui.notifications.notify('Complete');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment