initialize Ikonis system module with motherboard configuration and feature management UI
This commit is contained in:
commit
7bffeacaac
16 changed files with 1008 additions and 0 deletions
180
scripts/ikonis-sheet.js
Normal file
180
scripts/ikonis-sheet.js
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
import { getAugments, getSlotCount, getAttachedFeature } from './ikonis-data.js';
|
||||
|
||||
/**
|
||||
* Patches the Daggerheart Weapon sheet to include the Ikonis tab.
|
||||
* This is more robust than extending the class in Foundry V14.
|
||||
*/
|
||||
export function patchIkonisSheet() {
|
||||
const { Weapon } = game.system.api.applications.sheets.items || {};
|
||||
if (!Weapon) {
|
||||
console.error("DH-Ikonis | Weapon sheet class not found in system API!");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("DH-Ikonis | Patching Weapon sheet prototype...");
|
||||
|
||||
// 1. Add the Ikonis Tab to TABS (on the static class)
|
||||
if (Weapon.TABS?.primary) {
|
||||
const hasTab = Weapon.TABS.primary.tabs.some(t => t.id === 'motherboard');
|
||||
if (!hasTab) {
|
||||
Weapon.TABS.primary.tabs.push({
|
||||
id: 'motherboard',
|
||||
label: 'DAGGERHEART.ITEMS.Ikonis.Motherboard',
|
||||
icon: 'fa-solid fa-microchip'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Add the Motherboard Part to PARTS
|
||||
// We REMOVE the 'tab' property here to prevent Foundry from automatically hiding it
|
||||
// instead, we will handle visibility in the template.
|
||||
if (!Weapon.PARTS.motherboard) {
|
||||
Weapon.PARTS.motherboard = {
|
||||
template: 'modules/dh-ikonis/templates/ikonis-motherboard.hbs',
|
||||
scrollable: ['.motherboard-content']
|
||||
};
|
||||
}
|
||||
|
||||
// 3. Patch _prepareContext
|
||||
const originalPrepare = Weapon.prototype._prepareContext;
|
||||
Weapon.prototype._prepareContext = async function(options) {
|
||||
let context = await originalPrepare.call(this, options);
|
||||
|
||||
try {
|
||||
const doc = this.document;
|
||||
if (!doc) return context;
|
||||
|
||||
const installedIds = doc.getFlag('dh-ikonis', 'installedAugments') || [];
|
||||
const allAugmentsList = getAugments() || [];
|
||||
|
||||
const processedAugments = [];
|
||||
for (const id of installedIds) {
|
||||
const base = allAugmentsList.find(a => String(a.id) === String(id));
|
||||
if (!base) continue;
|
||||
const aug = { ...base, installed: true };
|
||||
if (aug.featureUuid) {
|
||||
const item = await getAttachedFeature(aug.featureUuid);
|
||||
if (item) aug.feature = { name: item.name, img: item.img, uuid: item.uuid };
|
||||
}
|
||||
processedAugments.push(aug);
|
||||
}
|
||||
|
||||
const bondedUuid = doc.getFlag('dh-ikonis', 'bondedFeatureUuid') || game.settings.get('dh-ikonis', 'defaultBondedUuid');
|
||||
const bonded = { enabled: true, feature: null };
|
||||
if (bondedUuid) {
|
||||
const item = await getAttachedFeature(bondedUuid);
|
||||
if (item) bonded.feature = { name: item.name, img: item.img, uuid: item.uuid };
|
||||
}
|
||||
|
||||
context.ikonis = {
|
||||
enabled: true,
|
||||
augments: processedAugments,
|
||||
bonded: bonded,
|
||||
isGM: game.user?.isGM || false
|
||||
};
|
||||
|
||||
context.maxSlots = getSlotCount(doc);
|
||||
context.usedSlots = processedAugments.length;
|
||||
|
||||
// Explicitly pass the active tab to the template
|
||||
context.activeTab = this.tabGroups?.primary || "";
|
||||
|
||||
return context;
|
||||
} catch (err) {
|
||||
console.error("DH-Ikonis | Error in patched _prepareContext:", err);
|
||||
return context;
|
||||
}
|
||||
};
|
||||
|
||||
// 4. Patch _onClickAction
|
||||
const originalClick = Weapon.prototype._onClickAction;
|
||||
Weapon.prototype._onClickAction = function(event, target) {
|
||||
const action = target.dataset.action;
|
||||
if (action === "addAugment") return this._onAddAugment(event, target);
|
||||
if (action === "removeAugment") return this._onRemoveAugment(event, target);
|
||||
return originalClick.call(this, event, target);
|
||||
};
|
||||
|
||||
// 5. Add custom methods
|
||||
Weapon.prototype._onAddAugment = async function(event, target) {
|
||||
const installedIds = this.document.getFlag('dh-ikonis', 'installedAugments') || [];
|
||||
const allAugments = getAugments();
|
||||
const validInstalled = installedIds.filter(id => allAugments.some(a => String(a.id) === String(id)));
|
||||
|
||||
const maxSlots = getSlotCount(this.document);
|
||||
if (validInstalled.length >= maxSlots) {
|
||||
ui.notifications.warn(`No more augment slots available! (Max: ${maxSlots})`);
|
||||
return;
|
||||
}
|
||||
|
||||
const available = allAugments.filter(a => !validInstalled.includes(String(a.id)));
|
||||
|
||||
const content = `
|
||||
<div class="augment-picker" style="max-height: 500px; display: flex; flex-direction: column; background: #0f0f1b; padding: 1rem; border-radius: 8px;">
|
||||
<div class="picker-header" style="margin-bottom: 1rem;">
|
||||
<input type="text" id="augment-search" placeholder="Search augments..." style="width: 100%; background: #1a1a2e; color: white; border: 1px solid #2d3436; padding: 0.5rem;">
|
||||
</div>
|
||||
<div class="picker-list" style="flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
${available.map(a => `
|
||||
<div class="picker-item" data-id="${a.id}" style="border: 2px solid #2d3436; background: #1a1a2e; padding: 1rem; border-radius: 8px; cursor: pointer;">
|
||||
<div class="augment-info">
|
||||
<strong class="aug-name" style="display: block; font-size: 1.1rem; color: #ffffff;">${a.name}</strong>
|
||||
<span style="display: block; margin: 2px 0; font-size: 0.9rem; color: #d1d8e0;">${a.effect}</span>
|
||||
<small style="display: block; color: #888; font-style: italic;">Cost: ${a.cost}</small>
|
||||
</div>
|
||||
</div>
|
||||
`).join('')}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
let selectedId = null;
|
||||
const res = await foundry.applications.api.DialogV2.wait({
|
||||
window: { title: "Install Tech" },
|
||||
content: content,
|
||||
buttons: [
|
||||
{ action: "ok", label: "Install", icon: "fa-solid fa-download", callback: () => selectedId },
|
||||
{ action: "cancel", label: "Cancel", icon: "fa-solid fa-times" }
|
||||
],
|
||||
render: (e, app) => {
|
||||
const search = app.element.querySelector('#augment-search');
|
||||
if (search) search.focus();
|
||||
search?.addEventListener('input', (event) => {
|
||||
const query = event.target.value.toLowerCase();
|
||||
app.element.querySelectorAll('.picker-item').forEach(el => {
|
||||
el.style.display = el.innerText.toLowerCase().includes(query) ? 'block' : 'none';
|
||||
});
|
||||
});
|
||||
|
||||
app.element.querySelectorAll('.picker-item').forEach(el => {
|
||||
el.addEventListener('click', () => {
|
||||
app.element.querySelectorAll('.picker-item').forEach(i => {
|
||||
i.style.borderColor = "#2d3436";
|
||||
i.style.background = "#1a1a2e";
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
el.style.borderColor = "#00d2ff";
|
||||
el.style.background = "rgba(0, 210, 255, 0.15)";
|
||||
el.classList.add('selected');
|
||||
selectedId = el.dataset.id;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (res && res !== "cancel") {
|
||||
const newIds = [...validInstalled, String(res)];
|
||||
await this.document.update({ "flags.dh-ikonis.installedAugments": newIds });
|
||||
this.render(true);
|
||||
}
|
||||
};
|
||||
|
||||
Weapon.prototype._onRemoveAugment = async function(event, target) {
|
||||
const installedIds = this.document.getFlag('dh-ikonis', 'installedAugments') || [];
|
||||
const newIds = installedIds.filter(id => id !== String(target.dataset.id));
|
||||
await this.document.update({ "flags.dh-ikonis.installedAugments": newIds });
|
||||
this.render(true);
|
||||
};
|
||||
|
||||
console.log("DH-Ikonis | Weapon sheet patched successfully.");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue