241 lines
9.7 KiB
JavaScript
241 lines
9.7 KiB
JavaScript
export const DEFAULT_AUGMENTS = [
|
|
{ id: "force", name: "Kinetic Amplifier", effect: "+1 Damage on Melee attacks", cost: "2 Iron", precompile: 1 },
|
|
{ id: "fire", name: "Thermal Core", effect: "Deals Fire damage instead of Physical", cost: "1 Blaze Glass", precompile: 1 },
|
|
{ id: "shock", name: "Static Coil", effect: "Targets hit are Dazed", cost: "3 Copper", precompile: 2 },
|
|
{ id: "shield", name: "Reactive Plating", effect: "+1 Armor while equipped", cost: "2 Steel", precompile: 1 },
|
|
{ id: "range", name: "Long-Range Optics", effect: "Increases Range by 1 step", cost: "1 Lens", precompile: 2 },
|
|
{ id: "crit", name: "Precision Chip", effect: "+1 to Crit range", cost: "1 Gold", precompile: 3 },
|
|
{ id: "multi", name: "Burst Module", effect: "Can target 2 enemies (Half damage)", cost: "2 Gears", precompile: 4 },
|
|
{ id: "drain", name: "Siphon Link", effect: "Recover 1 Hope on kill", cost: "1 Soul Gem", precompile: 4 },
|
|
{ id: "weight", name: "Gravity Plate", effect: "Weapon is Heavy (more damage)", cost: "4 Lead", precompile: 2 }
|
|
];
|
|
|
|
// Global caches for resolved features to keep getters fast
|
|
const _featureCache = 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}`);
|
|
}
|
|
|
|
/**
|
|
* Robust feature fetching. Tries cache first, then async fromUuid.
|
|
*/
|
|
export async function getAttachedFeature(uuid) {
|
|
if (!uuid) return null;
|
|
if (_featureCache.has(uuid)) return _featureCache.get(uuid);
|
|
return await fromUuid(uuid);
|
|
}
|
|
|
|
export function getSlotCount(item) {
|
|
const flags = item.getFlag('dh-ikonis') || {};
|
|
if (typeof flags.slotOverride === "number") return flags.slotOverride;
|
|
|
|
let tier = item.system?.tier?.value;
|
|
if (tier === undefined) tier = item.system?.tier;
|
|
const tierNum = parseInt(tier) || 1;
|
|
|
|
const settingKey = `slotsTier${tierNum}`;
|
|
try {
|
|
return game.settings.get('dh-ikonis', settingKey);
|
|
} catch (e) {
|
|
return tierNum >= 2 ? 3 : 2;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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...");
|
|
|
|
const originalAllEffects = Actor.prototype.allApplicableEffects;
|
|
Actor.prototype.allApplicableEffects = function* (options) {
|
|
yield* originalAllEffects.call(this, options);
|
|
|
|
for (const item of this.items) {
|
|
if (item.type !== 'weapon' || !item.system.equipped) continue;
|
|
|
|
const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || [];
|
|
const allAugs = getAugments();
|
|
|
|
for (const id of installedIds) {
|
|
const aug = allAugs.find(a => String(a.id) === String(id));
|
|
if (!aug?.featureUuid) continue;
|
|
|
|
// 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) {
|
|
if (effect.transfer) {
|
|
console.log(`DH-Ikonis | Injecting effect '${effect.name}' from augment '${feature.name}' on ${this.name}`);
|
|
|
|
// Clone the effect and set this actor as parent to ensure application
|
|
const effectData = effect.toObject();
|
|
effectData.disabled = false;
|
|
const ActiveEffectClass = getDocumentClass("ActiveEffect");
|
|
const effectInstance = new ActiveEffectClass(effectData, { parent: this });
|
|
yield effectInstance;
|
|
}
|
|
}
|
|
}
|
|
|
|
const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid');
|
|
if (bondedUuid) {
|
|
const feature = _featureCache.get(bondedUuid) || fromUuidSync(bondedUuid);
|
|
if (feature?.effects) {
|
|
for (const effect of feature.effects) {
|
|
if (effect.transfer) {
|
|
console.log(`DH-Ikonis | Injecting bonded effect '${effect.name}' from '${feature.name}' on ${this.name}`);
|
|
|
|
const effectData = effect.toObject();
|
|
effectData.disabled = false;
|
|
const ActiveEffectClass = getDocumentClass("ActiveEffect");
|
|
const effectInstance = new ActiveEffectClass(effectData, { parent: this });
|
|
yield effectInstance;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Internal helper to generate and cache virtual features for an actor.
|
|
* Safe to call multiple times; uses a cache to keep it fast.
|
|
*/
|
|
function _injectIkonisFeatures(actor) {
|
|
if (!actor || actor._isIkonisInjecting) return [];
|
|
|
|
// Initialize the cache if it doesn't exist
|
|
if (!actor._ikonisCache) actor._ikonisCache = new Map();
|
|
|
|
const ikonisFeatures = [];
|
|
actor._isIkonisInjecting = true;
|
|
|
|
try {
|
|
const weapons = actor.items.filter(i => i.type === 'weapon');
|
|
const allAugs = getAugments();
|
|
|
|
for (const item of weapons) {
|
|
const isEquipped = item.system.equipped;
|
|
const installedIds = item.getFlag('dh-ikonis', 'installedAugments') || [];
|
|
const bondedUuid = item.getFlag('dh-ikonis', 'bondedFeatureUuid');
|
|
|
|
const processFeature = (uuid, type) => {
|
|
if (!uuid) return;
|
|
|
|
// Extract original feature ID from UUID to build our virtual ID
|
|
const featureId = uuid.split('.').pop();
|
|
const virtualId = `ikonis-${item.id}-${featureId}`;
|
|
|
|
// Check cache first
|
|
if (actor._ikonisCache.has(virtualId)) {
|
|
if (isEquipped) ikonisFeatures.push(actor._ikonisCache.get(virtualId));
|
|
return;
|
|
}
|
|
|
|
// Resolve original and clone
|
|
const feature = _featureCache.get(uuid) || fromUuidSync(uuid);
|
|
if (feature) {
|
|
const featureClone = feature.clone({ parent: actor }, { keepId: true });
|
|
featureClone.system.type = "ikonis";
|
|
Object.defineProperty(featureClone, "id", { value: virtualId, enumerable: true });
|
|
|
|
actor._ikonisCache.set(virtualId, featureClone);
|
|
if (isEquipped) ikonisFeatures.push(featureClone);
|
|
}
|
|
};
|
|
|
|
if (bondedUuid) processFeature(bondedUuid, "bonded");
|
|
for (const id of installedIds) {
|
|
const aug = allAugs.find(a => String(a.id) === String(id));
|
|
if (aug?.featureUuid) processFeature(aug.featureUuid, "augment");
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error("DH-Ikonis | Error during feature injection:", err);
|
|
} finally {
|
|
actor._isIkonisInjecting = false;
|
|
}
|
|
|
|
return ikonisFeatures;
|
|
}
|
|
|
|
/**
|
|
* Patches the Character Data Model to show features in the UI lists.
|
|
*/
|
|
export function patchDhCharacter(DhCharacter) {
|
|
console.log("DH-Ikonis | Applying DhCharacter.sheetLists patch...");
|
|
|
|
const descriptor = Object.getOwnPropertyDescriptor(DhCharacter.prototype, 'sheetLists');
|
|
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);
|
|
if (!this.parent) return lists;
|
|
|
|
const ikonisFeatures = _injectIkonisFeatures(this.parent);
|
|
|
|
if (ikonisFeatures.length > 0) {
|
|
if (!lists.features) lists.features = {};
|
|
lists.features["Ikonis Augments"] = {
|
|
title: "Ikonis Augments",
|
|
type: "ikonis",
|
|
values: ikonisFeatures
|
|
};
|
|
}
|
|
|
|
return lists;
|
|
},
|
|
configurable: true
|
|
});
|
|
|
|
// Patch getEmbeddedDocument to resolve our virtual features
|
|
const originalGetEmbedded = Actor.prototype.getEmbeddedDocument;
|
|
Actor.prototype.getEmbeddedDocument = function(embeddedName, id, options) {
|
|
if (embeddedName === "Item" && typeof id === "string" && id.startsWith("ikonis-")) {
|
|
if (!this._ikonisCache?.has(id) && !this._isIkonisInjecting) {
|
|
_injectIkonisFeatures(this);
|
|
}
|
|
return this._ikonisCache?.get(id);
|
|
}
|
|
return originalGetEmbedded.call(this, embeddedName, id, options);
|
|
};
|
|
}
|
|
|
|
export function patchDHWeapon() {
|
|
// Future: Add damage type override logic here
|
|
}
|