feat: Add Daggerheart actor item updater module for automatic and manual item synchronization with compendiums.

This commit is contained in:
CPTN Cosmo 2026-01-17 16:00:51 +01:00
commit 1c5f990d64
No known key found for this signature in database
7 changed files with 594 additions and 0 deletions

171
scripts/updater.js Normal file
View file

@ -0,0 +1,171 @@
export class DHUpdater {
static PACK_MAPPING = {
'class': 'daggerheart.classes',
'subclass': 'daggerheart.subclasses',
'ancestry': 'daggerheart.ancestries',
'community': 'daggerheart.communities',
'domainCard': 'daggerheart.domains',
'weapon': 'daggerheart.weapons',
'armor': 'daggerheart.armors',
'consumable': 'daggerheart.consumables',
'loot': 'daggerheart.loot',
'beastform': 'daggerheart.beastforms'
};
static IGNORED_FIELDS = [
'_id',
'sort',
'ownership',
'flags',
'_stats',
'system.quantity',
'system.equipped',
'system.attuned',
'system.favorite',
'system.features',
'system.subclasses',
'system.inventory',
'system.characterGuide'
];
/**
* Check for updates for a specific actor
* @param {Actor} actor
* @returns {Promise<Array>} List of updates found
*/
static async checkActor(actor) {
if (!actor) return [];
const updates = [];
const ignoredItems = game.settings.get('dh-actor-updater', 'ignoredItems') || {};
for (const item of actor.items) {
// Skip if ignored
if (ignoredItems[item.uuid]) continue;
const packId = this.PACK_MAPPING[item.type];
if (!packId) continue; // Not a trackable item type
const pack = game.packs.get(packId);
if (!pack) continue;
// Try to find the source item in the compendium
// We search by name first.
// Ideally we'd match by flags.core.sourceId if available,
// but Daggerheart items might have just been dropped without keeping link,
// or we want to match by name for "generic" updates.
// The user prompt implies "compare them to the SRD compendium version", likely by name or origin.
let compendiumItem = null;
const sourceId = item.flags.core?.sourceId;
if (sourceId && sourceId.startsWith(packId)) {
const id = sourceId.split('.').pop();
compendiumItem = await pack.getDocument(id);
}
if (!compendiumItem) {
// Fallback to name match
const index = pack.index.find(i => i.name === item.name);
if (index) {
compendiumItem = await pack.getDocument(index._id);
}
}
if (compendiumItem) {
const diff = this.compareItems(item, compendiumItem);
if (diff) {
updates.push({
actor: actor,
item: item,
compendiumItem: compendiumItem,
diff: diff
});
}
}
}
return updates;
}
/**
* Compare actor item with compendium item
* @param {Item} item
* @param {Item} compendiumItem
* @returns {Object|null} Diff object or null if identical
*/
static compareItems(item, compendiumItem) {
const itemData = item.toObject();
const compendiumData = compendiumItem.toObject();
const diff = {};
let hasChanges = false;
// Basic fields
if (itemData.name !== compendiumData.name) {
// If we matched by ID, name might have changed
diff.name = { old: itemData.name, new: compendiumData.name };
hasChanges = true;
}
if (itemData.img !== compendiumData.img) {
diff.img = { old: itemData.img, new: compendiumData.img };
hasChanges = true;
}
// System data comparison
const flatItemSystem = foundry.utils.flattenObject(itemData.system);
const flatCompendiumSystem = foundry.utils.flattenObject(compendiumData.system);
for (const [key, value] of Object.entries(flatCompendiumSystem)) {
// Check for ignored fields
if (this.IGNORED_FIELDS.some(ignored => key.startsWith(ignored) || `system.${key}` === ignored)) continue;
// Should probably check if the field exists in compendium but is different in item
if (flatItemSystem[key] !== value) {
// Check if it's "effectively" the same (e.g. null vs undefined or html differences)
// Simple strict equality for now
// Handle embedded arrays explicitly??
// flattenObject handles arrays with numeric indices e.g. "array.0.field"
diff[`system.${key}`] = { old: flatItemSystem[key], new: value };
hasChanges = true;
}
}
// Also check if item has keys that compendium doesn't (removed fields)
// Might be tricky if users added custom data.
// We generally care if the Compendium version is "Standard" and we want to enforce it.
return hasChanges ? diff : null;
}
static async updateItem(item, compendiumItem) {
const itemData = compendiumItem.toObject();
delete itemData._id;
delete itemData.ownership;
delete itemData.folder;
delete itemData.sort;
delete itemData.flags;
delete itemData._stats;
// Fields to preserve from Actor (do not overwrite with Compendium data)
const PRESERVED_SYSTEM_FIELDS = [
// State fields
'quantity', 'equipped', 'attuned', 'favorite',
// Link fields (referencing other items)
'features', 'subclasses', 'inventory', 'characterGuide'
];
if (itemData.system) {
for (const field of PRESERVED_SYSTEM_FIELDS) {
if (field in itemData.system) {
delete itemData.system[field];
}
}
}
return item.update(itemData);
}
}