diff --git a/scripts/ikonis-data.js b/scripts/ikonis-data.js index 4d716e7..c1929dc 100644 --- a/scripts/ikonis-data.js +++ b/scripts/ikonis-data.js @@ -12,12 +12,37 @@ export const DEFAULT_AUGMENTS = [ // Global caches for resolved features to keep getters fast const _featureCache = new Map(); -const _itemObjectCache = new Map(); export function getAugments() { return game.settings.get('dh-ikonis', 'augmentsList') || DEFAULT_AUGMENTS; } +/** + * Pre-loads all features from the compendium into memory. + * This is necessary because sheetLists getter is synchronous. + */ +export async function loadIkonisFeatures() { + console.log("DH-Ikonis | Pre-loading features into cache..."); + const allAugs = getAugments(); + const bondedUuid = game.settings.get('dh-ikonis', 'defaultBondedUuid'); + + const uuids = new Set(allAugs.map(a => a.featureUuid).filter(Boolean)); + if (bondedUuid) uuids.add(bondedUuid); + + for (const uuid of uuids) { + try { + const item = await fromUuid(uuid); + if (item) { + _featureCache.set(uuid, item); + console.log(`DH-Ikonis | Cached feature: ${item.name} [${uuid}]`); + } + } catch (e) { + console.error(`DH-Ikonis | Failed to load feature ${uuid}`, e); + } + } + console.log(`DH-Ikonis | Cache size: ${_featureCache.size}`); +} + export function getSlotCount(item) { const flags = item.getFlag('dh-ikonis') || {}; if (typeof flags.slotOverride === "number") return flags.slotOverride; @@ -34,37 +59,15 @@ export function getSlotCount(item) { } } -/** - * Robust feature fetching with timeout and caching. - */ -export async function getAttachedFeature(uuid) { - if (!uuid) return null; - if (_featureCache.has(uuid)) return _featureCache.get(uuid); - - const timeout = new Promise((_, reject) => - setTimeout(() => reject(new Error("Timeout fetching feature")), 2000) - ); - - try { - const item = await Promise.race([fromUuid(uuid), timeout]); - if (item) _featureCache.set(uuid, item); - return item; - } catch (err) { - console.warn(`DH-Ikonis | Failed or timed out fetching feature [${uuid}]:`, err.message); - return null; - } -} - /** * Patches the system to ensure Ikonis effects are applied to the actor. */ export function patchIkonisLogic() { console.log("DH-Ikonis | Patching Actor for Ikonis effect injection..."); - // 1. Patch allApplicableEffects to inject Active Effects from Augments const originalAllEffects = Actor.prototype.allApplicableEffects; - Actor.prototype.allApplicableEffects = function* () { - yield* originalAllEffects.call(this); + Actor.prototype.allApplicableEffects = function* (options) { + yield* originalAllEffects.call(this, options); for (const item of this.items) { if (item.type !== 'weapon' || !item.system.equipped) continue; @@ -76,7 +79,8 @@ export function patchIkonisLogic() { const aug = allAugs.find(a => String(a.id) === String(id)); if (!aug?.featureUuid) continue; - const feature = fromUuidSync(aug.featureUuid); + // Try cache first, then sync + const feature = _featureCache.get(aug.featureUuid) || fromUuidSync(aug.featureUuid); if (!feature || !feature.effects) continue; for (const effect of feature.effects) { @@ -86,7 +90,7 @@ export function patchIkonisLogic() { const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid'); if (bondedUuid) { - const feature = fromUuidSync(bondedUuid); + const feature = _featureCache.get(bondedUuid) || fromUuidSync(bondedUuid); if (feature?.effects) { for (const effect of feature.effects) { if (effect.transfer) yield effect; @@ -101,44 +105,46 @@ export function patchIkonisLogic() { * Patches the Character Data Model to show features in the UI lists. */ export function patchDhCharacter(DhCharacter) { - console.log("DH-Ikonis | Patching DhCharacter data model for UI lists..."); + console.log("DH-Ikonis | Applying DhCharacter.sheetLists patch..."); const descriptor = Object.getOwnPropertyDescriptor(DhCharacter.prototype, 'sheetLists'); - if (!descriptor) return; + if (!descriptor) { + console.error("DH-Ikonis | FAILED to find sheetLists descriptor on DhCharacter prototype!"); + return; + } const originalSheetLists = descriptor.get; Object.defineProperty(DhCharacter.prototype, 'sheetLists', { get: function() { const lists = originalSheetLists.call(this); const ikonisFeatures = []; + + if (!this.parent) return lists; - // Find equipped Ikonis weapons for (const item of this.parent.items) { if (item.type !== 'weapon' || !item.system.equipped) continue; const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || []; - const allAugs = getAugments(); const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid'); - // Collect Bonded Feature if (bondedUuid) { - const feature = fromUuidSync(bondedUuid); + const feature = _featureCache.get(bondedUuid) || fromUuidSync(bondedUuid); if (feature) ikonisFeatures.push(feature); } - // Collect Augments + const allAugs = getAugments(); for (const id of installedIds) { const aug = allAugs.find(a => String(a.id) === String(id)); if (!aug?.featureUuid) continue; - const feature = fromUuidSync(aug.featureUuid); + const feature = _featureCache.get(aug.featureUuid) || fromUuidSync(aug.featureUuid); if (feature) ikonisFeatures.push(feature); } } if (ikonisFeatures.length > 0) { - // Ensure unique features const uniqueFeatures = Array.from(new Set(ikonisFeatures)); + console.log(`DH-Ikonis | Injecting ${uniqueFeatures.length} features for ${this.parent.name}`); lists.ikonis = { title: "Ikonis Augments", diff --git a/scripts/main.js b/scripts/main.js index 989f63e..9696097 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -1,4 +1,4 @@ -import { patchDHWeapon, patchIkonisLogic, patchDhCharacter, DEFAULT_AUGMENTS } from './ikonis-data.js'; +import { patchDHWeapon, patchIkonisLogic, patchDhCharacter, loadIkonisFeatures, DEFAULT_AUGMENTS } from './ikonis-data.js'; import { patchIkonisSheet } from './ikonis-sheet.js'; import { IkonisAugmentConfig } from './ikonis-config.js'; @@ -82,27 +82,68 @@ Hooks.on('updateItem', (item, changes, options, userId) => { }); Hooks.once('ready', async () => { + console.log("DH-Ikonis | Ready hook triggered."); + + // Sync features from compendium if needed if (game.user.isGM) { await syncIkonisFeatures(); + await overrideCurrency(); } - const actorsApi = game.system.api.models.actors || {}; - const DhCharacter = actorsApi.DhCharacter || actorsApi.character; + // Load features into memory for sync getters + await loadIkonisFeatures(); + + // Patch Character Data Model + const DhCharacter = game.system.api?.models?.actors?.DhCharacter || CONFIG.Actor.dataModels?.character; if (DhCharacter) { - // Apply visual injection patch patchDhCharacter(DhCharacter); - - Object.defineProperty(DhCharacter.prototype, 'primaryWeapon', { - get: function() { return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary); }, - configurable: true - }); - Object.defineProperty(DhCharacter.prototype, 'secondaryWeapon', { - get: function() { return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary); }, - configurable: true - }); + } else { + console.warn("DH-Ikonis | Could not find DhCharacter class for patching visual features."); } + + // Force re-render of open character sheets to show newly patched features + Object.values(ui.windows).forEach(w => { + if (w.document?.type === 'character') w.render(true); + }); }); +async function overrideCurrency() { + const homebrew = game.settings.get('daggerheart', 'homebrew'); + // Only override if not already set to Quantum to avoid spamming updates + if (homebrew.currency?.title !== "Quantum") { + console.log("DH-Ikonis | Overriding currency settings to Quantum..."); + + // We must work with a plain object for settings updates + const newHomebrew = homebrew.toObject(); + + newHomebrew.currency = { + title: "Quantum", + coins: { + enabled: true, + label: "Quantum", + icon: "fa-solid fa-atom" + }, + handfuls: { + enabled: false, + label: "Handfuls", + icon: "fa-solid fa-coins" + }, + bags: { + enabled: false, + label: "Bags", + icon: "fa-solid fa-sack" + }, + chests: { + enabled: false, + label: "Chests", + icon: "fa-solid fa-treasure-chest" + } + }; + + await game.settings.set('daggerheart', 'homebrew', newHomebrew); + } +} + async function syncIkonisFeatures() { const pack = game.packs.get("dh-ikonis.ikonis-features"); if (!pack) return;