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
71
lang/en.json
Normal file
71
lang/en.json
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"DAGGERHEART": {
|
||||
"CONFIG": {
|
||||
"DamageType": {
|
||||
"magical": {
|
||||
"name": "Tech",
|
||||
"abbreviation": "TECH"
|
||||
},
|
||||
"magicalDamage": "Tech Damage"
|
||||
},
|
||||
"ArmorFeature": {
|
||||
"magical": {
|
||||
"name": "Tech",
|
||||
"description": "You can't mark an Armor Slot to reduce tech damage.",
|
||||
"effects": {
|
||||
"magical": {
|
||||
"name": "Tech",
|
||||
"description": "You can't mark an Armor Slot to reduce tech damage."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"GENERAL": {
|
||||
"Damage": {
|
||||
"magicalDamage": "Tech Damage"
|
||||
},
|
||||
"DamageResistance": {
|
||||
"magicalResistance": {
|
||||
"label": "Damage Resistance: Tech",
|
||||
"hint": "Tech Damage is halved if this is set to 1"
|
||||
},
|
||||
"magicalImmunity": {
|
||||
"label": "Damage Immunity: Tech",
|
||||
"hint": "Immune to Tech Damage if this is set to 1"
|
||||
},
|
||||
"magicalReduction": {
|
||||
"label": "Damage Reduction: Tech",
|
||||
"hint": "Tech Damage is reduced by the amount set here"
|
||||
}
|
||||
},
|
||||
"Rules": {
|
||||
"damageReduction": {
|
||||
"magical": {
|
||||
"label": "Damage Reduction: Only Tech",
|
||||
"hint": "Armor can only be used to reduce tech damage"
|
||||
},
|
||||
"reduceSeverity": {
|
||||
"magical": {
|
||||
"label": "Reduce Damage Severity: Tech",
|
||||
"hint": "Lowers any tech damage received by the set amount of severity degrees"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ITEMS": {
|
||||
"Ikonis": {
|
||||
"Label": "Ikonis",
|
||||
"Bonded": "Bonded",
|
||||
"BondedHint": "Gain a bonus to your damage rolls equal to your level.",
|
||||
"Augments": "Augments",
|
||||
"AugmentSlots": "Augment Slots",
|
||||
"Trait": "Trait",
|
||||
"Range": "Range",
|
||||
"Damage": "Damage",
|
||||
"Motherboard": "Ikonis"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
59
module.json
Normal file
59
module.json
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"id": "dh-ikonis",
|
||||
"title": "Daggerheart - Ikonis",
|
||||
"description": "Adds the Ikonis personalized weapon and Motherboard module. Renames Magic damage to Tech damage.",
|
||||
"version": "1.0.0",
|
||||
"url": "https://git.geeks.gay/cosmo/dh-ikonis",
|
||||
"manifest": "https://git.geeks.gay/cosmo/dh-ikonis/raw/branch/main/module.json",
|
||||
"download": "https://git.geeks.gay/cosmo/dh-ikonis/releases/download/1.0.0/dh-ikonis.zip",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Cosmo",
|
||||
"email": "cptncosmo@gmail.com",
|
||||
"url": "https://git.geeks.gay/cosmo/",
|
||||
"discord": "@cptn_cosmo",
|
||||
"flags": {}
|
||||
}
|
||||
],
|
||||
"compatibility": {
|
||||
"minimum": "14",
|
||||
"verified": "14"
|
||||
},
|
||||
"relationships": {
|
||||
"systems": [
|
||||
{
|
||||
"id": "daggerheart",
|
||||
"type": "system",
|
||||
"compatibility": {
|
||||
"minimum": "1.0.0"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"esmodules": [
|
||||
"scripts/main.js"
|
||||
],
|
||||
"styles": [
|
||||
"styles/ikonis.css"
|
||||
],
|
||||
"languages": [
|
||||
{
|
||||
"lang": "en",
|
||||
"name": "English",
|
||||
"path": "lang/en.json"
|
||||
}
|
||||
],
|
||||
"packs": [
|
||||
{
|
||||
"name": "ikonis-features",
|
||||
"label": "Ikonis Features",
|
||||
"path": "packs/ikonis-features",
|
||||
"type": "Item",
|
||||
"system": "daggerheart",
|
||||
"ownership": {
|
||||
"PLAYER": "OBSERVER",
|
||||
"ASSISTANT": "OWNER"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
BIN
packs/000003.log
Normal file
BIN
packs/000003.log
Normal file
Binary file not shown.
1
packs/CURRENT
Normal file
1
packs/CURRENT
Normal file
|
|
@ -0,0 +1 @@
|
|||
MANIFEST-000002
|
||||
0
packs/LOCK
Normal file
0
packs/LOCK
Normal file
1
packs/LOG
Normal file
1
packs/LOG
Normal file
|
|
@ -0,0 +1 @@
|
|||
2026/04/26-14:45:07.673032 146fb55fe6c0 Delete type=3 #1
|
||||
BIN
packs/MANIFEST-000002
Normal file
BIN
packs/MANIFEST-000002
Normal file
Binary file not shown.
12
packs/ikonis-features.json
Normal file
12
packs/ikonis-features.json
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[
|
||||
{ "name": "Kinetic Amplifier", "type": "feature", "img": "icons/magic/force/projectile-orb-blue.webp", "system": { "description": "<p>+1 Damage on Melee attacks.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Thermal Core", "type": "feature", "img": "icons/magic/fire/orb-magma.webp", "system": { "description": "<p>Deals Fire damage instead of Physical.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Static Coil", "type": "feature", "img": "icons/magic/lightning/bolt-blue.webp", "system": { "description": "<p>Targets hit are Dazed.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Reactive Plating", "type": "feature", "img": "icons/equipment/chest/breastplate-metal-scaled-silver.webp", "system": { "description": "<p>+1 Armor while equipped.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Long-Range Optics", "type": "feature", "img": "icons/tools/navigation/spyglass-brass.webp", "system": { "description": "<p>Increases Range by 1 step.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Precision Chip", "type": "feature", "img": "icons/commodities/tech/circuit-board.webp", "system": { "description": "<p>+1 to Crit range.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Burst Module", "type": "feature", "img": "icons/magic/fire/explosion-fireball-medium-red-orange.webp", "system": { "description": "<p>Can target 2 enemies (Half damage).</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Siphon Link", "type": "feature", "img": "icons/magic/unholy/strike-body-life-drain-red.webp", "system": { "description": "<p>Recover 1 Hope on kill.</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Gravity Plate", "type": "feature", "img": "icons/magic/air/vortex-wind-blue.webp", "system": { "description": "<p>Weapon is Heavy (more damage).</p>", "attribution": { "source": "Ikonis Tech" } } },
|
||||
{ "name": "Ikonis Bond", "type": "feature", "img": "icons/magic/symbols/rune-sigil-blue-white.webp", "system": { "description": "<p>This weapon is bonded to your biological signature.</p>", "attribution": { "source": "Ikonis Tech" } } }
|
||||
]
|
||||
160
scripts/ikonis-config.js
Normal file
160
scripts/ikonis-config.js
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
import { DEFAULT_AUGMENTS, getAttachedFeature } from './ikonis-data.js';
|
||||
|
||||
export class IkonisAugmentConfig {
|
||||
static async open() {
|
||||
const augments = game.settings.get('dh-ikonis', 'augmentsList') || DEFAULT_AUGMENTS;
|
||||
const defaultBondedUuid = game.settings.get('dh-ikonis', 'defaultBondedUuid') || "";
|
||||
|
||||
const processedAugments = [];
|
||||
for (const a of augments) {
|
||||
const aug = { ...a };
|
||||
if (aug.featureUuid) {
|
||||
const item = await getAttachedFeature(aug.featureUuid);
|
||||
if (item) aug.featureName = item.name;
|
||||
}
|
||||
processedAugments.push(aug);
|
||||
}
|
||||
|
||||
let bondedName = "";
|
||||
if (defaultBondedUuid) {
|
||||
const item = await getAttachedFeature(defaultBondedUuid);
|
||||
if (item) bondedName = item.name;
|
||||
}
|
||||
|
||||
const template = "modules/dh-ikonis/templates/ikonis-config.hbs";
|
||||
const content = await foundry.applications.handlebars.renderTemplate(template, { augments: processedAugments, defaultBondedUuid, bondedName });
|
||||
|
||||
return foundry.applications.api.DialogV2.wait({
|
||||
window: {
|
||||
title: "Global Hardware Manager",
|
||||
icon: "fa-solid fa-microchip",
|
||||
width: 800,
|
||||
height: 650,
|
||||
resizable: true
|
||||
},
|
||||
content: content,
|
||||
buttons: [
|
||||
{
|
||||
action: "add", label: "Add New", icon: "fa-solid fa-plus",
|
||||
callback: () => { this._onAdd(); return false; }
|
||||
},
|
||||
{
|
||||
action: "reset", label: "Reset", icon: "fa-solid fa-undo",
|
||||
callback: () => { this._onReset(); return false; }
|
||||
},
|
||||
{
|
||||
action: "save", label: "Save & Close", icon: "fa-solid fa-save",
|
||||
callback: (event, button) => this._onSave(event, button)
|
||||
}
|
||||
],
|
||||
render: (event, app) => {
|
||||
const html = app.element;
|
||||
|
||||
const form = html.querySelector('form');
|
||||
if (form) {
|
||||
form.style.height = "100%";
|
||||
form.style.maxHeight = "100%";
|
||||
form.style.display = "flex";
|
||||
form.style.flexDirection = "column";
|
||||
form.style.overflow = "hidden";
|
||||
}
|
||||
const formContent = html.querySelector('.form-content');
|
||||
if (formContent) {
|
||||
formContent.style.flex = "1";
|
||||
formContent.style.overflow = "hidden";
|
||||
formContent.style.display = "flex";
|
||||
formContent.style.flexDirection = "column";
|
||||
}
|
||||
|
||||
html.querySelectorAll('[data-action="delete"]').forEach(el => {
|
||||
el.addEventListener('click', () => this._onDelete(el.dataset.id, app));
|
||||
});
|
||||
|
||||
html.addEventListener('drop', async (e) => {
|
||||
// V14 namespaced TextEditor
|
||||
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(e);
|
||||
if (data.type !== "Item") return;
|
||||
|
||||
const targetRow = e.target.closest('[data-id]');
|
||||
const targetBonded = e.target.closest('.bonded-drop-zone');
|
||||
|
||||
if (targetBonded) {
|
||||
await game.settings.set('dh-ikonis', 'defaultBondedUuid', data.uuid);
|
||||
app.close(); this.open();
|
||||
return;
|
||||
}
|
||||
|
||||
if (targetRow) {
|
||||
const id = targetRow.dataset.id;
|
||||
const augs = game.settings.get('dh-ikonis', 'augmentsList') || [...DEFAULT_AUGMENTS];
|
||||
const idx = augs.findIndex(a => String(a.id) === String(id));
|
||||
if (idx !== -1) {
|
||||
augs[idx].featureUuid = data.uuid;
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', augs);
|
||||
}
|
||||
app.close(); this.open();
|
||||
}
|
||||
});
|
||||
|
||||
html.querySelectorAll('[data-action="clearFeature"]').forEach(el => {
|
||||
el.addEventListener('click', async (e) => {
|
||||
const id = el.dataset.id;
|
||||
const augs = game.settings.get('dh-ikonis', 'augmentsList') || [...DEFAULT_AUGMENTS];
|
||||
const idx = augs.findIndex(a => String(a.id) === String(id));
|
||||
if (idx !== -1) {
|
||||
augs[idx].featureUuid = null;
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', augs);
|
||||
}
|
||||
app.close(); this.open();
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async _onAdd() {
|
||||
const augments = game.settings.get('dh-ikonis', 'augmentsList') || [...DEFAULT_AUGMENTS];
|
||||
augments.push({ id: foundry.utils.randomID(), name: "New Augment", effect: "Effect", cost: "Cost", precompile: 1 });
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', augments);
|
||||
this.open();
|
||||
}
|
||||
|
||||
static async _onDelete(id, app) {
|
||||
const augments = (game.settings.get('dh-ikonis', 'augmentsList') || []).filter(a => String(a.id) !== String(id));
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', augments);
|
||||
app.close(); this.open();
|
||||
}
|
||||
|
||||
static async _onReset() {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: { title: "Reset" }, content: "Reset to defaults?", yes: { label: "Reset" }
|
||||
});
|
||||
if (confirmed) {
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', DEFAULT_AUGMENTS);
|
||||
await game.settings.set('dh-ikonis', 'defaultBondedUuid', "");
|
||||
this.open();
|
||||
}
|
||||
}
|
||||
|
||||
static async _onSave(event, button) {
|
||||
// V14 namespaced FormDataExtended
|
||||
const fde = new foundry.applications.ux.FormDataExtended(button.form);
|
||||
const data = foundry.utils.expandObject(fde.object);
|
||||
const currentAugs = game.settings.get('dh-ikonis', 'augmentsList') || [];
|
||||
|
||||
const augments = Object.entries(data.augments || {}).map(([id, val]) => {
|
||||
const existing = currentAugs.find(a => String(a.id) === String(id));
|
||||
return {
|
||||
id,
|
||||
name: val.name,
|
||||
effect: val.effect,
|
||||
cost: val.cost,
|
||||
precompile: parseInt(val.precompile) || 1,
|
||||
featureUuid: existing?.featureUuid || null
|
||||
};
|
||||
});
|
||||
|
||||
await game.settings.set('dh-ikonis', 'augmentsList', augments);
|
||||
ui.notifications.info("Global Hardware saved!");
|
||||
}
|
||||
}
|
||||
61
scripts/ikonis-data.js
Normal file
61
scripts/ikonis-data.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
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 }
|
||||
];
|
||||
|
||||
export function getAugments() {
|
||||
return game.settings.get('dh-ikonis', 'augmentsList') || DEFAULT_AUGMENTS;
|
||||
}
|
||||
|
||||
export function getSlotCount(item) {
|
||||
const flags = item.getFlag('dh-ikonis') || {};
|
||||
if (typeof flags.slotOverride === "number") return flags.slotOverride;
|
||||
|
||||
// Daggerheart Tier detection (can be system.tier.value or system.tier)
|
||||
let tier = item.system?.tier?.value;
|
||||
if (tier === undefined) tier = item.system?.tier;
|
||||
|
||||
// Ensure we have a number
|
||||
const tierNum = parseInt(tier) || 1;
|
||||
|
||||
console.log(`DH-Ikonis | Detecting slots for Tier ${tierNum} (Raw: ${tier})`);
|
||||
|
||||
const settingKey = `slotsTier${tierNum}`;
|
||||
try {
|
||||
const slots = game.settings.get('dh-ikonis', settingKey);
|
||||
console.log(`DH-Ikonis | Setting [${settingKey}] returned: ${slots}`);
|
||||
return slots;
|
||||
} catch (e) {
|
||||
return tierNum >= 2 ? 3 : 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Robust feature fetching with timeout to prevent sheet hangs.
|
||||
*/
|
||||
export async function getAttachedFeature(uuid) {
|
||||
if (!uuid) return null;
|
||||
|
||||
const timeout = new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error("Timeout fetching feature")), 2000)
|
||||
);
|
||||
|
||||
try {
|
||||
const item = await Promise.race([fromUuid(uuid), timeout]);
|
||||
return item;
|
||||
} catch (err) {
|
||||
console.warn(`DH-Ikonis | Failed or timed out fetching feature [${uuid}]:`, err.message);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function patchDHWeapon() {
|
||||
console.log("DH-Ikonis | Patching DH Weapon system...");
|
||||
}
|
||||
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.");
|
||||
}
|
||||
92
scripts/main.js
Normal file
92
scripts/main.js
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
import { patchDHWeapon, DEFAULT_AUGMENTS } from './ikonis-data.js';
|
||||
import { patchIkonisSheet } from './ikonis-sheet.js';
|
||||
import { IkonisAugmentConfig } from './ikonis-config.js';
|
||||
|
||||
const MODULE_ID = 'dh-ikonis';
|
||||
|
||||
Hooks.once('init', () => {
|
||||
console.log(`${MODULE_ID} | Initializing Ikonis Module`);
|
||||
|
||||
// Global Augments List
|
||||
game.settings.register(MODULE_ID, "augmentsList", {
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: Array,
|
||||
default: DEFAULT_AUGMENTS
|
||||
});
|
||||
|
||||
// Default Bonded Feature
|
||||
game.settings.register(MODULE_ID, "defaultBondedUuid", {
|
||||
scope: "world",
|
||||
config: false,
|
||||
type: String,
|
||||
default: ""
|
||||
});
|
||||
|
||||
// Slot Scaling for Tiers 1, 2, 3, 4
|
||||
[1, 2, 3, 4].forEach(tier => {
|
||||
game.settings.register(MODULE_ID, `slotsTier${tier}`, {
|
||||
name: `Slots at Tier ${tier}`,
|
||||
hint: `How many augment slots a weapon gets at Tier ${tier}.`,
|
||||
scope: "world",
|
||||
config: true,
|
||||
type: Number,
|
||||
default: tier >= 2 ? 3 : 2
|
||||
});
|
||||
});
|
||||
|
||||
// Configuration Menu (V2 compliant)
|
||||
game.settings.registerMenu(MODULE_ID, "augmentsMenu", {
|
||||
name: "Manage Ikonis Augments",
|
||||
label: "Open Augment Manager",
|
||||
hint: "Add, remove, or edit the global list of Ikonis augments.",
|
||||
icon: "fa-solid fa-microchip",
|
||||
type: class extends foundry.applications.api.ApplicationV2 {
|
||||
render() {
|
||||
IkonisAugmentConfig.open();
|
||||
return this;
|
||||
}
|
||||
},
|
||||
restricted: true
|
||||
});
|
||||
});
|
||||
|
||||
Hooks.on('setup', () => {
|
||||
patchDHWeapon();
|
||||
patchIkonisSheet();
|
||||
});
|
||||
|
||||
// Watch for Tier changes and force a refresh
|
||||
Hooks.on('updateItem', (item, changes, options, userId) => {
|
||||
// Check if system data or flags were updated
|
||||
const isTierUpdate = foundry.utils.hasProperty(changes, "system.tier");
|
||||
const isFlagUpdate = foundry.utils.hasProperty(changes, "flags.dh-ikonis");
|
||||
|
||||
if (isTierUpdate || isFlagUpdate) {
|
||||
console.log(`DH-Ikonis | Update detected for ${item.name}. Re-rendering sheets...`);
|
||||
// Find all active sheets for this item and force a full render
|
||||
Object.values(item.apps || {}).forEach(app => {
|
||||
if (app.render) app.render(true);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
Hooks.once('ready', () => {
|
||||
const actorsApi = game.system.api.models.actors || {};
|
||||
const DhCharacter = actorsApi.DhCharacter || actorsApi.DHCharacter || actorsApi.character;
|
||||
if (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
|
||||
});
|
||||
}
|
||||
});
|
||||
185
styles/ikonis.css
Normal file
185
styles/ikonis.css
Normal file
|
|
@ -0,0 +1,185 @@
|
|||
|
||||
/* --- Motherboard Specific Styling --- */
|
||||
|
||||
.motherboard-content {
|
||||
padding: 1.5rem;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background: #0d0d16;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.motherboard-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 2px solid #ff2e63;
|
||||
padding-bottom: 1rem;
|
||||
margin-bottom: 2rem;
|
||||
position: sticky;
|
||||
top: -1.5rem;
|
||||
background: #0d0d16;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.motherboard-header h2 {
|
||||
margin: 0;
|
||||
color: #ff2e63;
|
||||
border: none;
|
||||
font-size: 1.8rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.slots-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
background: #ff2e63;
|
||||
padding: 0.5rem 1.2rem;
|
||||
border-radius: 20px;
|
||||
box-shadow: 0 0 15px rgba(255, 46, 99, 0.4);
|
||||
}
|
||||
|
||||
.slot-override-btn {
|
||||
background: rgba(0,0,0,0.3);
|
||||
color: white;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
border-radius: 50%;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.motherboard-card {
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
border: 1px solid #2d3436;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
margin-bottom: 1.5rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.motherboard-card:hover {
|
||||
border-color: #ff2e63;
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.75rem 1rem;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-bottom: 1px solid #2d3436;
|
||||
}
|
||||
|
||||
.card-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
color: #ff2e63;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.card-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.aug-stats {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.aug-effect {
|
||||
font-size: 1.05rem;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.aug-cost {
|
||||
font-size: 0.85rem;
|
||||
color: #888;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
/* --- Feature Slots --- */
|
||||
|
||||
.attached-feature {
|
||||
border: 2px dashed #333;
|
||||
border-radius: 8px;
|
||||
min-height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 0.5rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.attached-feature.empty:hover {
|
||||
border-color: #ff2e63;
|
||||
background: rgba(255, 46, 99, 0.05);
|
||||
}
|
||||
|
||||
.attached-feature.filled {
|
||||
border-style: solid;
|
||||
border-color: #00d2ff;
|
||||
background: rgba(0, 210, 255, 0.05);
|
||||
justify-content: flex-start;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.feature-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.feature-content img {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border: 1px solid #00d2ff;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.feature-content .name {
|
||||
flex: 1;
|
||||
font-size: 0.95rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.feature-content .remove {
|
||||
color: #ff2e63;
|
||||
cursor: pointer;
|
||||
padding: 0.2rem 0.5rem;
|
||||
}
|
||||
|
||||
.drop-zone {
|
||||
color: #444;
|
||||
font-size: 0.9rem;
|
||||
text-transform: uppercase;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
/* --- Sections --- */
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.add-augment-btn {
|
||||
background: #ff2e63;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
}
|
||||
62
templates/ikonis-config.hbs
Normal file
62
templates/ikonis-config.hbs
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
|
||||
<div class="ikonis-config-container" style="background: #0d0d16; color: white; padding: 1.5rem; display: flex; flex-direction: column; max-height: 600px; box-sizing: border-box;">
|
||||
|
||||
<div class="bonded-config-section" style="flex: 0 0 auto; margin-bottom: 1rem; padding: 1rem; border: 1px solid #ff2e63; border-radius: 8px; background: rgba(255,46,99,0.05);">
|
||||
<h3 style="margin-top: 0; color: #ff2e63; font-size: 1.1rem; border-bottom: 1px solid rgba(255,46,99,0.2); padding-bottom: 0.5rem;">Default Bonded Feature</h3>
|
||||
<div class="bonded-drop-zone" style="height: 50px; border: 2px dashed #444; border-radius: 8px; display: flex; align-items: center; justify-content: center; background: rgba(0,0,0,0.2);">
|
||||
{{#if bondedName}}
|
||||
<div style="font-weight: bold; color: #00d2ff;"><i class="fa-solid fa-link"></i> {{bondedName}}</div>
|
||||
{{else}}
|
||||
<span style="color: #666; font-size: 0.9rem;">Drop Default Bonded Feature Here</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3 style="flex: 0 0 auto; margin: 0 0 0.5rem 0; color: #ff2e63; font-size: 1.1rem;">Augment Blueprints</h3>
|
||||
|
||||
<div class="augment-manager-scroll" style="flex: 1; overflow-y: auto; border: 1px solid #2d3436; border-radius: 8px; background: rgba(0,0,0,0.2); margin-bottom: 0.5rem; min-height: 150px;">
|
||||
<table class="config-table" style="width: 100%; border-collapse: separate; border-spacing: 0;">
|
||||
<thead style="position: sticky; top: 0; background: #16213e; z-index: 10;">
|
||||
<tr style="text-align: left;">
|
||||
<th style="padding: 0.75rem 0.5rem; color: #ff2e63; border-bottom: 2px solid #ff2e63;">Name</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: #ff2e63; border-bottom: 2px solid #ff2e63;">Effect</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: #ff2e63; border-bottom: 2px solid #ff2e63;">Cost</th>
|
||||
<th style="padding: 0.75rem 0.5rem; color: #ff2e63; border-bottom: 2px solid #ff2e63; width: 140px;">Feature</th>
|
||||
<th style="padding: 0.75rem 0.5rem; width: 50px; color: #ff2e63; border-bottom: 2px solid #ff2e63;">T</th>
|
||||
<th style="padding: 0.75rem 0.5rem; width: 30px; border-bottom: 2px solid #ff2e63;"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{#each augments as |aug|}}
|
||||
<tr style="border-bottom: 1px solid #2d3436;" data-id="{{aug.id}}">
|
||||
<td style="padding: 0.4rem;">
|
||||
<input type="text" name="augments.{{aug.id}}.name" value="{{aug.name}}" style="width: 100%; background: #1a1a2e; color: white; border: 1px solid #333; padding: 2px 4px;">
|
||||
</td>
|
||||
<td style="padding: 0.4rem;">
|
||||
<input type="text" name="augments.{{aug.id}}.effect" value="{{aug.effect}}" style="width: 100%; background: #1a1a2e; color: white; border: 1px solid #333; padding: 2px 4px;">
|
||||
</td>
|
||||
<td style="padding: 0.4rem;">
|
||||
<input type="text" name="augments.{{aug.id}}.cost" value="{{aug.cost}}" style="width: 100%; background: #1a1a2e; color: white; border: 1px solid #333; padding: 2px 4px;">
|
||||
</td>
|
||||
<td style="padding: 0.4rem;">
|
||||
<div class="feature-slot" style="min-height: 24px; border: 1px dashed #444; border-radius: 4px; display: flex; align-items: center; justify-content: center; font-size: 0.75rem; background: rgba(0,0,0,0.3);">
|
||||
{{#if aug.featureName}}
|
||||
<span style="color: #00d2ff; flex: 1; padding: 0 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;" title="{{aug.featureName}}">{{aug.featureName}}</span>
|
||||
<a data-action="clearFeature" data-id="{{aug.id}}" style="color: #ff2e63; margin-right: 4px;"><i class="fa-solid fa-times"></i></a>
|
||||
{{else}}
|
||||
<span style="color: #444;">Drop Item</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</td>
|
||||
<td style="padding: 0.4rem;">
|
||||
<input type="number" name="augments.{{aug.id}}.precompile" value="{{aug.precompile}}" min="1" max="4" style="width: 100%; background: #1a1a2e; color: white; border: 1px solid #333; padding: 2px 4px;">
|
||||
</td>
|
||||
<td style="padding: 0.4rem; text-align: center;">
|
||||
<a data-action="delete" data-id="{{aug.id}}" style="color: #ff2e63;"><i class="fa-solid fa-trash"></i></a>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
66
templates/ikonis-item-manager.hbs
Normal file
66
templates/ikonis-item-manager.hbs
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
|
||||
<div class="ikonis-manager-container" style="background: #0f0f1b; color: white; padding: 1.5rem; height: 100%; display: flex; flex-direction: column; gap: 1.5rem;">
|
||||
|
||||
<div class="manager-section bonded-config">
|
||||
<h3 style="color: #ff2e63; border-bottom: 1px solid #ff2e63; padding-bottom: 0.5rem; margin-top: 0;">
|
||||
<i class="fa-solid fa-link"></i> Bonded Feature Configuration
|
||||
</h3>
|
||||
<div class="bonded-slot motherboard-card" data-slot="bonded" style="padding: 1rem; background: rgba(255,255,255,0.03); border: 1px dashed #444;">
|
||||
<p style="font-size: 0.9rem; color: #888; margin-bottom: 0.5rem;">Attach the core feature of this motherboard.</p>
|
||||
<div class="feature-drop-zone {{#if ikonis.bonded.feature}}filled{{else}}empty{{/if}}" data-augment-id="bonded" style="min-height: 60px; border: 2px dashed #333; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
|
||||
{{#if ikonis.bonded.feature}}
|
||||
<div class="feature-display" style="display: flex; align-items: center; gap: 1rem; width: 100%; padding: 0.5rem;">
|
||||
<img src="{{ikonis.bonded.feature.img}}" style="width: 40px; height: 40px; border-radius: 4px;">
|
||||
<span style="font-weight: bold; flex: 1;">{{ikonis.bonded.feature.name}}</span>
|
||||
<a data-action="removeFeature" data-augment-id="bonded" style="color: #ff2e63;"><i class="fa-solid fa-times-circle"></i></a>
|
||||
</div>
|
||||
{{else}}
|
||||
<span style="color: #444;">Drop Feature Item Here</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="manager-section augments-config" style="flex: 1; display: flex; flex-direction: column;">
|
||||
<h3 style="color: #ff2e63; border-bottom: 1px solid #ff2e63; padding-bottom: 0.5rem;">
|
||||
<i class="fa-solid fa-microchip"></i> Augment Slots ({{usedSlots}} / {{maxSlots}})
|
||||
</h3>
|
||||
|
||||
<div class="slots-grid" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; overflow-y: auto; flex: 1; padding-top: 0.5rem;">
|
||||
{{#each ikonis.slots as |slot|}}
|
||||
<div class="slot-card motherboard-card" style="background: rgba(255,255,255,0.03); border: 1px solid #2d3436; border-radius: 12px; display: flex; flex-direction: column;">
|
||||
<div class="slot-header" style="background: rgba(255,255,255,0.05); padding: 0.5rem 1rem; display: flex; justify-content: space-between; align-items: center;">
|
||||
<span style="font-weight: bold; color: #ff2e63;">Slot {{slot.index}}</span>
|
||||
{{#if slot.aug}}
|
||||
<a data-action="removeAugment" data-id="{{slot.aug.id}}" style="color: #888;"><i class="fa-solid fa-trash-alt"></i></a>
|
||||
{{/if}}
|
||||
</div>
|
||||
<div class="slot-body" style="padding: 1rem; flex: 1; display: flex; flex-direction: column; gap: 0.75rem;">
|
||||
{{#if slot.aug}}
|
||||
<div class="aug-info">
|
||||
<div style="font-weight: bold; font-size: 1.1rem;">{{slot.aug.name}}</div>
|
||||
<div style="font-size: 0.9rem; color: #d1d8e0;">{{slot.aug.effect}}</div>
|
||||
<div style="font-size: 0.8rem; color: #888; font-style: italic;">Cost: {{slot.aug.cost}}</div>
|
||||
</div>
|
||||
<div class="feature-drop-zone {{#if slot.aug.feature}}filled{{else}}empty{{/if}}" data-augment-id="{{slot.aug.id}}" style="min-height: 50px; border: 2px dashed #333; border-radius: 8px; display: flex; align-items: center; justify-content: center;">
|
||||
{{#if slot.aug.feature}}
|
||||
<div class="feature-display" style="display: flex; align-items: center; gap: 0.5rem; width: 100%; padding: 0.25rem;">
|
||||
<img src="{{slot.aug.feature.img}}" style="width: 30px; height: 30px; border-radius: 4px;">
|
||||
<span style="font-size: 0.9rem; flex: 1;">{{slot.aug.feature.name}}</span>
|
||||
<a data-action="removeFeature" data-augment-id="{{slot.aug.id}}" style="color: #ff2e63;"><i class="fa-solid fa-times"></i></a>
|
||||
</div>
|
||||
{{else}}
|
||||
<span style="font-size: 0.8rem; color: #444;">Drop Feature</span>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{else}}
|
||||
<button type="button" class="install-btn" data-action="addAugment" style="flex: 1; background: transparent; border: 2px dashed #333; color: #444; border-radius: 8px; cursor: pointer;">
|
||||
<i class="fa-solid fa-plus"></i> Install Tech
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
58
templates/ikonis-motherboard.hbs
Normal file
58
templates/ikonis-motherboard.hbs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
|
||||
<section class="motherboard-content tab {{#if (eq activeTab 'motherboard')}}active{{/if}}" data-group="primary" data-tab="motherboard">
|
||||
<div class="motherboard-header" style="margin-bottom: 2rem;">
|
||||
<h2><i class="fa-solid fa-shield-halved"></i> {{localize "DAGGERHEART.ITEMS.Ikonis.Motherboard"}}</h2>
|
||||
<div class="slots-info">
|
||||
<span>{{usedSlots}} / {{maxSlots}} {{localize "DAGGERHEART.ITEMS.Ikonis.AugmentSlots"}}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{!-- Bonded Feature Section (Always visible if enabled) --}}
|
||||
<div class="bonded-feature motherboard-card" style="border-left: 4px solid #ff2e63; background: rgba(255,46,99,0.05); margin-bottom: 2rem; padding: 1rem; border-radius: 8px;">
|
||||
<div class="bonded-info" style="display: flex; align-items: center; gap: 1rem;">
|
||||
{{#if ikonis.bonded.feature}}
|
||||
<img src="{{ikonis.bonded.feature.img}}" style="width: 48px; height: 48px; border-radius: 4px; border: 1px solid #ff2e63;">
|
||||
<div class="text">
|
||||
<h3 style="margin: 0; color: #ff2e63; font-size: 1.2rem;">{{ikonis.bonded.feature.name}} (Bonded)</h3>
|
||||
<p style="margin: 0.25rem 0 0 0; color: #d1d8e0; font-size: 0.95rem;">Primary system module is active and synced.</p>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="text">
|
||||
<h3 style="margin: 0; color: #666; font-size: 1.2rem;">No Bonded Feature</h3>
|
||||
<p style="margin: 0.25rem 0 0 0; color: #444; font-size: 0.95rem;">Configure the default bonded feature in Module Settings.</p>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="augments-section">
|
||||
<div class="section-header" style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem;">
|
||||
<h3 style="margin: 0; color: #e0e0e0;">Installed Augments</h3>
|
||||
<button type="button" class="add-augment-btn" data-action="addAugment" style="background: #ff2e63; color: white; border: none; padding: 0.4rem 0.8rem; border-radius: 4px; cursor: pointer;">
|
||||
<i class="fa-solid fa-plus"></i> Install Tech
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="augments-list" style="display: grid; grid-template-columns: 1fr 1fr; gap: 1rem;">
|
||||
{{#each ikonis.augments as |aug|}}
|
||||
<div class="augment-item motherboard-card" style="padding: 1rem; background: rgba(255,255,255,0.02); border: 1px solid #2d3436; border-radius: 8px; position: relative;">
|
||||
<a class="remove-btn" data-action="removeAugment" data-id="{{aug.id}}" style="position: absolute; top: 0.5rem; right: 0.5rem; color: #666; cursor: pointer;"><i class="fa-solid fa-trash-alt"></i></a>
|
||||
<div style="display: flex; gap: 0.75rem; align-items: center; margin-bottom: 0.5rem;">
|
||||
{{#if aug.feature}}
|
||||
<img src="{{aug.feature.img}}" style="width: 32px; height: 32px; border-radius: 4px; border: 1px solid #00d2ff;">
|
||||
{{/if}}
|
||||
<span class="aug-name" style="font-weight: bold; font-size: 1.1rem; color: #ffffff;">{{aug.name}}</span>
|
||||
</div>
|
||||
<div class="aug-stats" style="font-size: 0.9rem; color: #a4b0be;">
|
||||
<div class="effect">{{aug.effect}}</div>
|
||||
<div class="cost" style="font-style: italic; margin-top: 0.25rem;">Cost: {{aug.cost}}</div>
|
||||
</div>
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="empty-msg" style="grid-column: 1/-1; text-align: center; padding: 2rem; border: 1px dashed #333; border-radius: 8px; color: #666;">
|
||||
No hardware modules detected. Click <strong>Install Tech</strong> to begin.
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
Loading…
Add table
Add a link
Reference in a new issue