Attribute Item Subtype #76

Merged
Oliver merged 50 commits from feature/attribute-items into main 2026-04-27 02:12:56 +00:00
6 changed files with 192 additions and 1 deletions
Showing only changes of commit 14f76e0a27 - Show all commits

View file

@ -1,7 +1,7 @@
import { __ID__ } from "../consts.mjs"; import { __ID__ } from "../consts.mjs";
const { Actor } = foundry.documents; const { Actor } = foundry.documents;
const { hasProperty } = foundry.utils; const { hasProperty, setProperty } = foundry.utils;
export class TAFActor extends Actor { export class TAFActor extends Actor {
@ -33,6 +33,12 @@ export class TAFActor extends Actor {
super._onEmbeddedDocumentChange(...args); super._onEmbeddedDocumentChange(...args);
this.#sortedTypes = null; this.#sortedTypes = null;
}; };
static migrateData(data, ...args) {
if (Object.keys(data.system?.attr ?? {}).length > 0) {
setProperty(data, `flags.${__ID__}.convertAttributesIntoItems`, true);
};
};
// #endregion Lifecycle // #endregion Lifecycle
// #region Token Attributes // #region Token Attributes

View file

@ -1,6 +1,10 @@
import { checkMigrations } from "../migrations/checkMigrations.mjs";
Hooks.on(`ready`, () => { Hooks.on(`ready`, () => {
// Remove with issue: Foundry/taf#52 // Remove with issue: Foundry/taf#52
if (game.release.generation < 14 && globalThis._loc == null) { if (game.release.generation < 14 && globalThis._loc == null) {
globalThis._loc = game.i18n.format.bind(game.i18n); globalThis._loc = game.i18n.format.bind(game.i18n);
}; };
checkMigrations();
}); });

View file

@ -0,0 +1,24 @@
import { __ID__ } from "../consts.mjs";
import { Logger } from "../utils/Logger.mjs";
import { migrateTo3_0_0 } from "./v3.0.0.mjs";
const { isNewerVersion } = foundry.utils;
export async function checkMigrations() {
if (!game.user.isActiveGM) {
Logger.debug(`User not active GM, skipping data migrations`);
return;
};
const migrationVersion = game.settings.get(__ID__, `migrationVersion`);
let updateVersion = !migrationVersion;
if (isNewerVersion("3.0.0", migrationVersion)) {
await migrateTo3_0_0();
updateVersion = true;
};
if (updateVersion) {
game.settings.set(__ID__, `migrationVersion`, game.system.version);
};
};

View file

@ -0,0 +1,79 @@
import { __ID__ } from "../consts.mjs";
/**
* Migrate the documents within a collection based on what
*
* This function was originally reproduced from [Draw Steel's codebase](https://github.com/MetaMorphic-Digital/draw-steel/blob/82a0a050da7c0d6d28c0cd283cf3b6915f47ee2a/src/module/data/migrations.mjs#L206-L226),
* with modifications to it to work better without
*
* @param collection The Collection of documents to update.
* @param flag The flag name to reference for if the document should be migrated.
* @param convertor The function that takes the document and performs the.
* transformations to get the required update data.
* @param options Options to configure how the method behaves.
* @param options.pack The compendium pack to update.
* @param options.parent Parent of the collection for embedded collections.
* @param options.update Whether or not this method should perform the update, or pass back the array of DB operations.
* @returns An array of batch operations to perform.
*/
export async function migrateCollection(
collection,
flag,
convertor,
options = {}
) {
const toMigrate = collection
.filter(doc => {
console.log(`toMigrate.filter doc`, doc);
return doc.getFlag(__ID__, flag)
})
.map(doc => {
const update = convertor(doc) ?? {};
update[`_id`] = doc._id;
// v13/v14+ compatibility shim
if (game.release.generation > 13) {
update[`flags.${__ID__}.${flag}`] = _del;
} else {
update[`flags.${__ID__}.-=${flag}`] = null;
};
return update;
})
.filter(data => !!data);
// update in increments of 100
// TODO: optionally return an array of DB operations for modifyBatch
const batches = Math.ceil(toMigrate.length / 100);
for (let i = 0; i < batches; i++) {
const updateData = toMigrate.slice(i * 100, (i + 1) * 100);
await collection.documentClass.updateDocuments(
updateData,
{
pack: options.pack,
parent: options.parent,
diff: false,
},
);
};
};
/**
* Determine whether a compendium pack should be migrated during `migrateWorld`.
*
* This function was reproduced from [Draw Steel's codebase](https://github.com/MetaMorphic-Digital/draw-steel/blob/82a0a050da7c0d6d28c0cd283cf3b6915f47ee2a/src/module/data/migrations.mjs#L287-L302)
*
* @param pack The CompendiumPack document
* @returns {boolean} Whether or not the pack should be migrated
*/
export function shouldMigrateCompendium(pack) {
// We only care about actor and item migrations
if (!["Actor", "Item"].includes(pack.documentName)) return false;
// World compendiums should all be migrated, system ones should never by migrated
if (pack.metadata.packageType === "world") return true;
if (pack.metadata.packageType === "system") return false;
// Module compendiums should only be migrated if they don't have a download or manifest URL
const module = game.modules.get(pack.metadata.packageName);
return !module.download && !module.manifest;
}

View file

@ -0,0 +1,72 @@
import { Logger } from "../utils/Logger.mjs";
import { migrateCollection, shouldMigrateCompendium } from "./utils.mjs";
const flag = `convertAttributesIntoItems`;
const operations = [];
export async function migrateTo3_0_0() {
Logger.debug(`Starting v3.0.0 data migration`);
operations.push(
...await migrateCollection(
game.actors,
flag,
handleMigratingActor,
{ update: false, },
),
);
// for (const pack of game.packs) {
// if (
// pack.metadata.type !== "Actor"
// || !shouldMigrateCompendium(pack)
// ) {
// continue;
// };
// await pack.getDocuments();
// // TODO: unlock compendium if required then re-lock after finishing
// await migrateCollection(
// pack,
// flag,
// handleMigratingActor,
// { pack },
// );
// };
// TODO: create the item documents (batch them if possible)
Logger.debug(`Finished v3.0.0 migration, resulting operations:`);
console.log(operations);
};
function handleMigratingActor(actor) {
console.log(actor);
const operation = {
action: `create`,
documentName: `Item`,
parent: actor,
data: [],
};
const attrs = actor.system.attr;
for (const [ key, attr ] of Object.entries(attrs)) {
operation.data.push(convertToItem(key, attr));
};
operations.push(operation);
return null;
};
function convertToItem(key, attr) {
return {
name: attr.name,
type: "attribute",
system: {
key,
value: attr.value,
max: attr.isRange ? attr.max : null,
},
};
};

View file

@ -74,4 +74,10 @@ export function registerWorldSettings() {
type: Object, type: Object,
scope: `world`, scope: `world`,
}); });
game.settings.register(__ID__, `migrationVersion`, {
config: false,
type: String,
scope: `world`,
});
}; };