171 lines
5.8 KiB
JavaScript
171 lines
5.8 KiB
JavaScript
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);
|
|
}
|
|
}
|