commit 7bffeacaac4cdaceb301e733186d2066e0d91f78 Author: cosmo Date: Sun Apr 26 17:03:48 2026 +0200 initialize Ikonis system module with motherboard configuration and feature management UI diff --git a/lang/en.json b/lang/en.json new file mode 100644 index 0000000..85a0e5c --- /dev/null +++ b/lang/en.json @@ -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" + } + } + } +} \ No newline at end of file diff --git a/module.json b/module.json new file mode 100644 index 0000000..ea9925c --- /dev/null +++ b/module.json @@ -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" + } + } + ] +} \ No newline at end of file diff --git a/packs/000003.log b/packs/000003.log new file mode 100644 index 0000000..eaab53f Binary files /dev/null and b/packs/000003.log differ diff --git a/packs/CURRENT b/packs/CURRENT new file mode 100644 index 0000000..1a84852 --- /dev/null +++ b/packs/CURRENT @@ -0,0 +1 @@ +MANIFEST-000002 diff --git a/packs/LOCK b/packs/LOCK new file mode 100644 index 0000000..e69de29 diff --git a/packs/LOG b/packs/LOG new file mode 100644 index 0000000..c51a2fd --- /dev/null +++ b/packs/LOG @@ -0,0 +1 @@ +2026/04/26-14:45:07.673032 146fb55fe6c0 Delete type=3 #1 diff --git a/packs/MANIFEST-000002 b/packs/MANIFEST-000002 new file mode 100644 index 0000000..bbbc585 Binary files /dev/null and b/packs/MANIFEST-000002 differ diff --git a/packs/ikonis-features.json b/packs/ikonis-features.json new file mode 100644 index 0000000..9f8c377 --- /dev/null +++ b/packs/ikonis-features.json @@ -0,0 +1,12 @@ +[ + { "name": "Kinetic Amplifier", "type": "feature", "img": "icons/magic/force/projectile-orb-blue.webp", "system": { "description": "

+1 Damage on Melee attacks.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Thermal Core", "type": "feature", "img": "icons/magic/fire/orb-magma.webp", "system": { "description": "

Deals Fire damage instead of Physical.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Static Coil", "type": "feature", "img": "icons/magic/lightning/bolt-blue.webp", "system": { "description": "

Targets hit are Dazed.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Reactive Plating", "type": "feature", "img": "icons/equipment/chest/breastplate-metal-scaled-silver.webp", "system": { "description": "

+1 Armor while equipped.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Long-Range Optics", "type": "feature", "img": "icons/tools/navigation/spyglass-brass.webp", "system": { "description": "

Increases Range by 1 step.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Precision Chip", "type": "feature", "img": "icons/commodities/tech/circuit-board.webp", "system": { "description": "

+1 to Crit range.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Burst Module", "type": "feature", "img": "icons/magic/fire/explosion-fireball-medium-red-orange.webp", "system": { "description": "

Can target 2 enemies (Half damage).

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Siphon Link", "type": "feature", "img": "icons/magic/unholy/strike-body-life-drain-red.webp", "system": { "description": "

Recover 1 Hope on kill.

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Gravity Plate", "type": "feature", "img": "icons/magic/air/vortex-wind-blue.webp", "system": { "description": "

Weapon is Heavy (more damage).

", "attribution": { "source": "Ikonis Tech" } } }, + { "name": "Ikonis Bond", "type": "feature", "img": "icons/magic/symbols/rune-sigil-blue-white.webp", "system": { "description": "

This weapon is bonded to your biological signature.

", "attribution": { "source": "Ikonis Tech" } } } +] diff --git a/scripts/ikonis-config.js b/scripts/ikonis-config.js new file mode 100644 index 0000000..0c7766e --- /dev/null +++ b/scripts/ikonis-config.js @@ -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!"); + } +} diff --git a/scripts/ikonis-data.js b/scripts/ikonis-data.js new file mode 100644 index 0000000..5bbe54e --- /dev/null +++ b/scripts/ikonis-data.js @@ -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..."); +} diff --git a/scripts/ikonis-sheet.js b/scripts/ikonis-sheet.js new file mode 100644 index 0000000..ff2d2c0 --- /dev/null +++ b/scripts/ikonis-sheet.js @@ -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 = ` +
+
+ +
+
+ ${available.map(a => ` +
+
+ ${a.name} + ${a.effect} + Cost: ${a.cost} +
+
+ `).join('')} +
+
+ `; + + 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."); +} diff --git a/scripts/main.js b/scripts/main.js new file mode 100644 index 0000000..42e83c2 --- /dev/null +++ b/scripts/main.js @@ -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 + }); + } +}); diff --git a/styles/ikonis.css b/styles/ikonis.css new file mode 100644 index 0000000..e60d711 --- /dev/null +++ b/styles/ikonis.css @@ -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; +} diff --git a/templates/ikonis-config.hbs b/templates/ikonis-config.hbs new file mode 100644 index 0000000..547a17e --- /dev/null +++ b/templates/ikonis-config.hbs @@ -0,0 +1,62 @@ + +
+ +
+

Default Bonded Feature

+
+ {{#if bondedName}} +
{{bondedName}}
+ {{else}} + Drop Default Bonded Feature Here + {{/if}} +
+
+ +

Augment Blueprints

+ +
+ + + + + + + + + + + + + {{#each augments as |aug|}} + + + + + + + + + {{/each}} + +
NameEffectCostFeatureT
+ + + + + + +
+ {{#if aug.featureName}} + {{aug.featureName}} + + {{else}} + Drop Item + {{/if}} +
+
+ + + +
+
+
diff --git a/templates/ikonis-item-manager.hbs b/templates/ikonis-item-manager.hbs new file mode 100644 index 0000000..04c102e --- /dev/null +++ b/templates/ikonis-item-manager.hbs @@ -0,0 +1,66 @@ + +
+ +
+

+ Bonded Feature Configuration +

+
+

Attach the core feature of this motherboard.

+
+ {{#if ikonis.bonded.feature}} +
+ + {{ikonis.bonded.feature.name}} + +
+ {{else}} + Drop Feature Item Here + {{/if}} +
+
+
+ +
+

+ Augment Slots ({{usedSlots}} / {{maxSlots}}) +

+ +
+ {{#each ikonis.slots as |slot|}} +
+
+ Slot {{slot.index}} + {{#if slot.aug}} + + {{/if}} +
+
+ {{#if slot.aug}} +
+
{{slot.aug.name}}
+
{{slot.aug.effect}}
+
Cost: {{slot.aug.cost}}
+
+
+ {{#if slot.aug.feature}} +
+ + {{slot.aug.feature.name}} + +
+ {{else}} + Drop Feature + {{/if}} +
+ {{else}} + + {{/if}} +
+
+ {{/each}} +
+
+
diff --git a/templates/ikonis-motherboard.hbs b/templates/ikonis-motherboard.hbs new file mode 100644 index 0000000..3f3b6b7 --- /dev/null +++ b/templates/ikonis-motherboard.hbs @@ -0,0 +1,58 @@ + +
+
+

{{localize "DAGGERHEART.ITEMS.Ikonis.Motherboard"}}

+
+ {{usedSlots}} / {{maxSlots}} {{localize "DAGGERHEART.ITEMS.Ikonis.AugmentSlots"}} +
+
+ + {{!-- Bonded Feature Section (Always visible if enabled) --}} +
+
+ {{#if ikonis.bonded.feature}} + +
+

{{ikonis.bonded.feature.name}} (Bonded)

+

Primary system module is active and synced.

+
+ {{else}} +
+

No Bonded Feature

+

Configure the default bonded feature in Module Settings.

+
+ {{/if}} +
+
+ +
+
+

Installed Augments

+ +
+ +
+ {{#each ikonis.augments as |aug|}} +
+ +
+ {{#if aug.feature}} + + {{/if}} + {{aug.name}} +
+
+
{{aug.effect}}
+
Cost: {{aug.cost}}
+
+
+ {{else}} +
+ No hardware modules detected. Click Install Tech to begin. +
+ {{/each}} +
+
+