initial commit
This commit is contained in:
commit
7246e3f892
10 changed files with 1728 additions and 0 deletions
62
README.md
Normal file
62
README.md
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Daggerheart - Ikonis
|
||||||
|
|
||||||
|
A Foundry VTT module for the **Daggerheart** system that adds a customizable "Ikonis" personalized weapon system.
|
||||||
|
|
||||||
|
The Ikonis system allows weapons to be upgraded with "Augments" (Tech) that provide various bonuses, effects, and modifications, managed through a dedicated hardware interface on the weapon sheet.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### The Ikonis Hardware Tab
|
||||||
|
Every weapon sheet now includes a dedicated **Ikonis** tab.
|
||||||
|
- **Install Tech**: Drag and drop features or select from the pre-defined list.
|
||||||
|
- **Bonded Features**: Link a specific "Bonded" feature to the weapon that follows the user.
|
||||||
|
- **Dynamic Slot Scaling**: The number of available augment slots automatically scales based on the weapon's **Tier** (1-4).
|
||||||
|
|
||||||
|
### Global Hardware Manager
|
||||||
|
GMs can access the **Global Hardware Manager** in the module settings to:
|
||||||
|
- Define the global pool of available Augments.
|
||||||
|
- Set the cost, effect, and name of each augment.
|
||||||
|
- Link augments to specific **Features** (items) for automated description and icon fetching.
|
||||||
|
- Configure the default "Bonded" feature for all Ikonis weapons.
|
||||||
|
|
||||||
|
### Integrated Compendium
|
||||||
|
Includes an **Ikonis Features** compendium containing 10 ready-to-use augments and bond effects with custom icons and descriptions.
|
||||||
|
|
||||||
|
### Per-Tier Configuration
|
||||||
|
Fine-tune the balance of your game by setting exactly how many slots are provided at each tier (1, 2, 3, and 4) in the module settings.
|
||||||
|
|
||||||
|
### Quantum Currency
|
||||||
|
The module automatically overrides the game's homebrew settings to establish a streamlined, high-tech economy:
|
||||||
|
- **Quantum**: Replaces traditional gold with "Quantum" credits.
|
||||||
|
- **Unified Economy**: Disables multi-tier currency (bags, chests) in favor of a single, tech-focused value system.
|
||||||
|
- **Visual Integration**: Features a custom atomic icon for the currency display across all sheets.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Open the Foundry VTT Setup screen.
|
||||||
|
2. Go to the **Add-on Modules** tab.
|
||||||
|
3. Click **Install Module**.
|
||||||
|
4. Paste the following manifest URL:
|
||||||
|
`https://git.geeks.gay/cosmo/dh-ikonis/raw/branch/main/module.json`
|
||||||
|
5. Click **Install**.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
1. **For GMs**:
|
||||||
|
- Go to **Configure Settings** > **Daggerheart - Ikonis**.
|
||||||
|
- Click **Open Augment Manager** to customize your global tech list.
|
||||||
|
- Adjust the **Slots per Tier** settings to match your desired power level.
|
||||||
|
2. **For Players**:
|
||||||
|
- Open a Weapon sheet.
|
||||||
|
- Click the **Ikonis** tab (microchip icon).
|
||||||
|
- Use the **Install Tech** button to add augments from the global list.
|
||||||
|
- Drag and drop features from the compendium into the Ikonis tab to link them.
|
||||||
|
|
||||||
|
## Credits
|
||||||
|
|
||||||
|
- Developed for the Daggerheart community.
|
||||||
|
- Built by Antigravity & Cosmo.
|
||||||
|
- Icons from the Foundry VTT core library.
|
||||||
|
|
||||||
|
---
|
||||||
|
*This is an unofficial module for the Daggerheart system.*
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
46
module.json
Normal file
46
module.json
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
754
scripts/augments.js
Normal file
754
scripts/augments.js
Normal file
|
|
@ -0,0 +1,754 @@
|
||||||
|
export const DEFAULT_AUGMENTS = [
|
||||||
|
{
|
||||||
|
id: "bonded",
|
||||||
|
name: "Bonded",
|
||||||
|
effect: "Primary module for Ikonis hardware synchronization.",
|
||||||
|
cost: "",
|
||||||
|
img: "icons/magic/control/debuff-energy-hold-blue-yellow.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisBondedEffX",
|
||||||
|
name: "Ikonis: Bonded",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/magic/control/debuff-energy-hold-blue-yellow.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.damage.primaryWeapon.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "@tier",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "force",
|
||||||
|
name: "Force",
|
||||||
|
effect: "+1 Damage",
|
||||||
|
cost: "Cost: 3 gears, 2 lenses, 4 aluminum, 1 capacitor",
|
||||||
|
img: "icons/commodities/gems/gem-cut-faceted-princess-purple.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisForceEffXX",
|
||||||
|
name: "Ikonis: Force",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-cut-faceted-princess-purple.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.damage.physical.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "guard",
|
||||||
|
name: "Guard",
|
||||||
|
effect: "+1 Armor Score",
|
||||||
|
cost: "Cost: 3 wires, 2 silver, 2 platinum, 3 fuses",
|
||||||
|
img: "icons/commodities/gems/gem-cut-faceted-square-purple.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisGuardEffXX",
|
||||||
|
name: "Ikonis: Guard",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-cut-faceted-square-purple.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: "armor",
|
||||||
|
phase: "initial",
|
||||||
|
value: {
|
||||||
|
current: 0,
|
||||||
|
max: "1",
|
||||||
|
interaction: "none",
|
||||||
|
damageThresholds: null
|
||||||
|
},
|
||||||
|
priority: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "converge",
|
||||||
|
name: "Converge",
|
||||||
|
effect: "+1 to attack rolls",
|
||||||
|
cost: "Cost: 4 coils, 2 crystals, 5 gold, 3 discs",
|
||||||
|
img: "icons/commodities/gems/gem-cut-square-green.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisConvergeXX",
|
||||||
|
name: "Ikonis: Converge",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-cut-square-green.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.roll.attack.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "amplify",
|
||||||
|
name: "Amplify",
|
||||||
|
effect: "On a successful attack, roll an additional damage die and drop the lowest result.",
|
||||||
|
cost: "Cost: 4 crystals, 4 cobalt, 4 copper, 4 capacitors",
|
||||||
|
img: "icons/commodities/gems/gem-cut-table-green.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisAmplifyXXX",
|
||||||
|
name: "Ikonis: Amplify",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-cut-table-green.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.damage.primaryWeapon.dice",
|
||||||
|
type: "add",
|
||||||
|
value: "1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "scope",
|
||||||
|
name: "Scope",
|
||||||
|
effect: " Increase range by one step (Melee to Very Close, Close to Far, etc.).",
|
||||||
|
cost: "Cost: 5 lenses, 3 silver, 2 circuits, 2 relays",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-asscher-blue.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisScopeEffXX",
|
||||||
|
name: "Ikonis: Scope",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-asscher-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "deny",
|
||||||
|
name: "Deny",
|
||||||
|
effect: "+2 Armor Score",
|
||||||
|
cost: "6 coils, 3 wire, 2 copper, 4 batteries",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-cushion-teal-black.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisGuardEffXX",
|
||||||
|
name: "Ikonis: Guard",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-cushion-teal-black.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: "armor",
|
||||||
|
phase: "initial",
|
||||||
|
value: {
|
||||||
|
current: 0,
|
||||||
|
max: "2",
|
||||||
|
interaction: "none",
|
||||||
|
damageThresholds: null
|
||||||
|
},
|
||||||
|
priority: 20
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "target",
|
||||||
|
name: "Target",
|
||||||
|
effect: "+2 to attack rolls",
|
||||||
|
cost: "Cost: 10 wires, 7 gold, 5 fuses, 5 circuits, 2 batteries",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-cushion-teal.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisConvergeXX",
|
||||||
|
name: "Ikonis: Converge",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-cushion-teal.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.roll.attack.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "2",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "split",
|
||||||
|
name: "Split",
|
||||||
|
effect: "When you make an attack, mark a Stress to target another creature within range.",
|
||||||
|
cost: "12 gears, 5 lenses, 15 aluminum, 9 relays",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-blue.webp",
|
||||||
|
actions: {
|
||||||
|
"IkonisSplitActXX": {
|
||||||
|
"name": "Split Attack",
|
||||||
|
"type": "action",
|
||||||
|
"_id": "IkonisSplitActXX",
|
||||||
|
"img": "icons/commodities/gems/gem-faceted-diamond-blue.webp",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "When you make an attack, mark a Stress to target another creature within range.",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"actionType": "action",
|
||||||
|
"name": "Mark a Stress",
|
||||||
|
"cost": [
|
||||||
|
{
|
||||||
|
"scalable": false,
|
||||||
|
"key": "stress",
|
||||||
|
"value": 1,
|
||||||
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"type": "any",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisSplitEffXX",
|
||||||
|
name: "Ikonis: Split",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "fix",
|
||||||
|
name: "Fix",
|
||||||
|
effect: "When you deal Severe damage, clear a Hit Point.",
|
||||||
|
cost: "Cost: 6 coils, 4 wires, 1 crystal, 5 cobalt, 5 silver, 7 relays, 2 batteries",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-green.webp",
|
||||||
|
actions: {
|
||||||
|
"IkonisFixActXXXX": {
|
||||||
|
"name": "Clear a Hit Point",
|
||||||
|
"type": "healing",
|
||||||
|
"_id": "IkonisFixActXXXX",
|
||||||
|
"img": "icons/commodities/gems/gem-faceted-diamond-green.webp",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "Clear a Hit Point when you deal Severe damage.",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"actionType": "action",
|
||||||
|
"healing": {
|
||||||
|
"parts": {
|
||||||
|
"hitPoints": {
|
||||||
|
"applyTo": "hitPoints",
|
||||||
|
"resultBased": false,
|
||||||
|
"value": {
|
||||||
|
"multiplier": "flat",
|
||||||
|
"flatMultiplier": 1,
|
||||||
|
"dice": "d6",
|
||||||
|
"bonus": null,
|
||||||
|
"custom": {
|
||||||
|
"enabled": true,
|
||||||
|
"formula": "1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"includeBase": false,
|
||||||
|
"direct": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisFixEffXXXX",
|
||||||
|
name: "Ikonis: Fix",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-green.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "scare",
|
||||||
|
name: "Scare",
|
||||||
|
effect: "When you critically succeed on an attack, the target must mark a Stress.",
|
||||||
|
cost: "Cost: 6 triggers, 8 copper, 9 aluminum, 10 discs",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-pink.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisScareEffXX",
|
||||||
|
name: "Ikonis: Scare",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-diamond-pink.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "sear",
|
||||||
|
name: "Sear",
|
||||||
|
effect: "+2 damage",
|
||||||
|
cost: "Cost: 11 triggers, 11 platinum, 11 circuits, 7 discs",
|
||||||
|
precompile: "Tier 2",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-navette-red.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisForceEffXX",
|
||||||
|
name: "Ikonis: Force",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-navette-red.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.damage.physical.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "2",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "absorb",
|
||||||
|
name: "Absorb",
|
||||||
|
effect: "You can mark an additional Armor Slot against incoming damage.",
|
||||||
|
cost: "Cost: 26 gears, 13 gold, 15 relays, 8 batteries",
|
||||||
|
precompile: "Tier 2",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-hexagon-blue.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisAbsorbEffX",
|
||||||
|
name: "Ikonis: Absorb",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-hexagon-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.rules.damageReduction.maxArmorMarked.value",
|
||||||
|
type: "add",
|
||||||
|
value: "1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "kick",
|
||||||
|
name: "Kick",
|
||||||
|
effect: "On a successful attack, you can mark 2 Stress to force the target to mark an additional Hit Point.",
|
||||||
|
cost: "Cost: 33 triggers, 13 crystals, 23 cobalt, 16 discs",
|
||||||
|
precompile: "Tier 2",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-oval-blue.webp",
|
||||||
|
actions: {
|
||||||
|
"IkonisKickActXX": {
|
||||||
|
"name": "Kick",
|
||||||
|
"type": "action",
|
||||||
|
"_id": "IkonisKickActXX",
|
||||||
|
"img": "icons/commodities/gems/gem-faceted-oval-blue.webp",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "On a successful attack, you can mark 2 Stress to force the target to mark an additional Hit Point.",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"actionType": "action",
|
||||||
|
"name": "Mark 2 Stress",
|
||||||
|
"cost": [
|
||||||
|
{
|
||||||
|
"scalable": false,
|
||||||
|
"key": "stress",
|
||||||
|
"value": 2,
|
||||||
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"type": "any",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisKickEffXXX",
|
||||||
|
name: "Ikonis: Kick",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-oval-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "block",
|
||||||
|
name: "Block",
|
||||||
|
effect: "+3 Armor Score; −1 Evasion",
|
||||||
|
cost: "Cost: 27 crystals, 67 aluminum, 33 relays, 4 capacitors, 5 batteries",
|
||||||
|
precompile: "Tier 3",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-octagon-yellow.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisBlockEffXX",
|
||||||
|
name: "Ikonis: Block",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-octagon-yellow.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
type: "armor",
|
||||||
|
phase: "initial",
|
||||||
|
value: {
|
||||||
|
current: 0,
|
||||||
|
max: "3",
|
||||||
|
interaction: "none",
|
||||||
|
damageThresholds: null
|
||||||
|
},
|
||||||
|
priority: 20
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "system.evasion",
|
||||||
|
type: "add",
|
||||||
|
value: "-1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "zip",
|
||||||
|
name: "Zip",
|
||||||
|
effect: "Move up to Far range as part of an attack.",
|
||||||
|
cost: "Cost: 37 coils, 43 silver, 67 fuses, 12 capacitors",
|
||||||
|
precompile: "Tier 3",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-oval-blue.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisZipEffXXXX",
|
||||||
|
name: "Ikonis: Zip",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-oval-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "bury",
|
||||||
|
name: "Bury",
|
||||||
|
effect: "+3 damage",
|
||||||
|
cost: "Cost: 28 triggers, 28 circuits, 28 relays, 1 relic",
|
||||||
|
precompile: "Tier 3",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-radiant-blue.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisBuryEffXXX",
|
||||||
|
name: "Ikonis: Bury",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-radiant-blue.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.bonuses.damage.physical.bonus",
|
||||||
|
type: "add",
|
||||||
|
value: "3",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "follow",
|
||||||
|
name: "Follow",
|
||||||
|
effect: "Mark 2 Stress to reroll your attack.",
|
||||||
|
cost: "Cost: 75 gears, 67 lenses, 30 copper, 33 circuits",
|
||||||
|
precompile: "Tier 4",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-radiant-red.webp",
|
||||||
|
actions: {
|
||||||
|
"IkonisFollowActXX": {
|
||||||
|
"name": "Follow",
|
||||||
|
"type": "action",
|
||||||
|
"_id": "IkonisFollowActXX",
|
||||||
|
"img": "icons/commodities/gems/gem-faceted-radiant-red.webp",
|
||||||
|
"systemPath": "actions",
|
||||||
|
"baseAction": false,
|
||||||
|
"description": "Mark 2 Stress to reroll your attack.",
|
||||||
|
"chatDisplay": true,
|
||||||
|
"actionType": "reroll",
|
||||||
|
"cost": [
|
||||||
|
{
|
||||||
|
"scalable": false,
|
||||||
|
"key": "stress",
|
||||||
|
"value": 2,
|
||||||
|
"itemId": null,
|
||||||
|
"step": null,
|
||||||
|
"consumeOnSuccess": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"target": {
|
||||||
|
"type": "any",
|
||||||
|
"amount": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisFollowEffX",
|
||||||
|
name: "Ikonis: Follow",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-radiant-red.webp",
|
||||||
|
system: {
|
||||||
|
changes: [],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "override",
|
||||||
|
name: "Override",
|
||||||
|
effect: "Attack rolls have advantage.",
|
||||||
|
cost: "Cost: 63 wires, 71 gold, 58 discs, 5 relics",
|
||||||
|
precompile: "Tier 4",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-round-white.webp",
|
||||||
|
effects: [
|
||||||
|
{
|
||||||
|
_id: "IkonisOverrideXX",
|
||||||
|
name: "Ikonis: Override",
|
||||||
|
type: "base",
|
||||||
|
img: "icons/commodities/gems/gem-faceted-round-white.webp",
|
||||||
|
system: {
|
||||||
|
changes: [
|
||||||
|
{
|
||||||
|
key: "system.advantageSources",
|
||||||
|
type: "add",
|
||||||
|
value: "1",
|
||||||
|
priority: null,
|
||||||
|
phase: "initial"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
duration: { description: "", type: "" },
|
||||||
|
rangeDependence: { enabled: false, type: "withinRange", target: "hostile", range: "melee" },
|
||||||
|
stacking: null,
|
||||||
|
targetDispositions: []
|
||||||
|
},
|
||||||
|
disabled: false,
|
||||||
|
transfer: true,
|
||||||
|
statuses: [],
|
||||||
|
showIcon: 1,
|
||||||
|
flags: {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
157
scripts/ikonis-data.js
Normal file
157
scripts/ikonis-data.js
Normal file
|
|
@ -0,0 +1,157 @@
|
||||||
|
import { DEFAULT_AUGMENTS } from './augments.js';
|
||||||
|
|
||||||
|
// Global caches for resolved features to keep getters fast
|
||||||
|
const _featureCache = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Injects Ikonis defaults directly into the system configuration.
|
||||||
|
*/
|
||||||
|
export function registerIkonisFeatures() {
|
||||||
|
if (!CONFIG.DH?.ITEM?.weaponFeatures) return;
|
||||||
|
console.log("DH-Ikonis | Registering features in CONFIG...");
|
||||||
|
for (const aug of DEFAULT_AUGMENTS) {
|
||||||
|
const nativeId = `ikonis-${aug.id}`;
|
||||||
|
CONFIG.DH.ITEM.weaponFeatures[nativeId] = {
|
||||||
|
name: `Ikonis: ${aug.name}`,
|
||||||
|
img: aug.img || "systems/daggerheart/assets/icons/documents/items/chip.svg",
|
||||||
|
description: aug.cost ? `<p>${aug.effect}</p><p>${aug.cost}</p>` : `<p>${aug.effect}</p>`,
|
||||||
|
actions: aug.actions || {},
|
||||||
|
effects: aug.effects || []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
console.log("DH-Ikonis | Features registered in CONFIG.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeds the Daggerheart Homebrew settings with Ikonis defaults if they don't exist.
|
||||||
|
*/
|
||||||
|
export async function seedIkonisHomebrew() {
|
||||||
|
if (!game.user.isGM) return;
|
||||||
|
|
||||||
|
let homebrewKey = 'Homebrew';
|
||||||
|
if (!game.settings.settings.has('daggerheart.Homebrew')) {
|
||||||
|
if (game.settings.settings.has('daggerheart.homebrew')) homebrewKey = 'homebrew';
|
||||||
|
else {
|
||||||
|
console.warn("DH-Ikonis | Daggerheart Homebrew setting not found.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const current = game.settings.get('daggerheart', homebrewKey) || {};
|
||||||
|
const homebrew = (typeof current.toObject === 'function') ? current.toObject() : foundry.utils.deepClone(current);
|
||||||
|
|
||||||
|
console.log(`DH-Ikonis | Seeding homebrew features (key: ${homebrewKey})...`);
|
||||||
|
|
||||||
|
if (!homebrew.itemFeatures) homebrew.itemFeatures = { weaponFeatures: {}, armorFeatures: {} };
|
||||||
|
if (!homebrew.itemFeatures.weaponFeatures) homebrew.itemFeatures.weaponFeatures = {};
|
||||||
|
|
||||||
|
let updates = false;
|
||||||
|
const recognizedIds = DEFAULT_AUGMENTS.map(aug => `ikonis-${aug.id}`);
|
||||||
|
|
||||||
|
// Purge undefined Ikonis features
|
||||||
|
for (const [id, feature] of Object.entries(homebrew.itemFeatures.weaponFeatures)) {
|
||||||
|
if (id.startsWith('ikonis-') && !recognizedIds.includes(id)) {
|
||||||
|
console.log(`DH-Ikonis | Purging undefined feature: ${id}`);
|
||||||
|
delete homebrew.itemFeatures.weaponFeatures[id];
|
||||||
|
updates = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const aug of DEFAULT_AUGMENTS) {
|
||||||
|
const nativeId = `ikonis-${aug.id}`;
|
||||||
|
// Force update to ensure 16-character IDs and correct mechanical effects are synced
|
||||||
|
console.log(`DH-Ikonis | Seeding/Updating feature: ${aug.name}`);
|
||||||
|
|
||||||
|
let description = `<p>${aug.effect}</p>`;
|
||||||
|
if (aug.cost) description += `<p>${aug.cost}</p>`;
|
||||||
|
if (aug.precompile) description += `<p>Precompile: ${aug.precompile}</p>`;
|
||||||
|
|
||||||
|
homebrew.itemFeatures.weaponFeatures[nativeId] = {
|
||||||
|
name: `Ikonis: ${aug.name}`,
|
||||||
|
img: aug.img || "systems/daggerheart/assets/icons/documents/items/chip.svg",
|
||||||
|
description: description,
|
||||||
|
actions: aug.actions || {},
|
||||||
|
effects: aug.effects || []
|
||||||
|
};
|
||||||
|
updates = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (updates) {
|
||||||
|
await game.settings.set('daggerheart', homebrewKey, homebrew);
|
||||||
|
console.log("DH-Ikonis | Default blueprints seeded into Homebrew settings.");
|
||||||
|
} else {
|
||||||
|
console.log("DH-Ikonis | Homebrew features are already up to date.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scans the Daggerheart system config for any weapon features starting with "Ikonis:".
|
||||||
|
* These are treated as available augments for our slot system.
|
||||||
|
*/
|
||||||
|
export function getAugments() {
|
||||||
|
// We get the resolved features from CONFIG.DH.ITEM which includes system + homebrew
|
||||||
|
const allFeatures = CONFIG.DH.ITEM.allWeaponFeatures() || {};
|
||||||
|
const augments = [];
|
||||||
|
|
||||||
|
for (const [id, feature] of Object.entries(allFeatures)) {
|
||||||
|
const name = feature.label || feature.name || "";
|
||||||
|
if (name.startsWith("Ikonis:")) {
|
||||||
|
let desc = feature.description || "";
|
||||||
|
// Replace common line-breaking tags with actual newlines before stripping
|
||||||
|
desc = desc.replace(/<\/p>|<br\s*\/?>/gi, '\n');
|
||||||
|
desc = desc.replace(/<[^>]*>?/gm, '').trim();
|
||||||
|
|
||||||
|
const lines = desc.split('\n').map(l => l.trim()).filter(l => l.length > 0);
|
||||||
|
|
||||||
|
const costLine = lines.find(l => l.toLowerCase().startsWith("cost:"));
|
||||||
|
const precompileLine = lines.find(l => l.toLowerCase().startsWith("precompile:"));
|
||||||
|
const effectLine = lines.find(l => !l.toLowerCase().startsWith("cost:") && !l.toLowerCase().startsWith("precompile:"));
|
||||||
|
|
||||||
|
augments.push({
|
||||||
|
id: id,
|
||||||
|
name: name.replace("Ikonis:", "").trim(),
|
||||||
|
fullName: name,
|
||||||
|
img: feature.img || "systems/daggerheart/assets/icons/documents/items/chip.svg",
|
||||||
|
effect: effectLine || "Native Feature",
|
||||||
|
cost: costLine || "",
|
||||||
|
precompile: precompileLine || ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return augments;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Patches the system's weapon data preparation to handle slot counts.
|
||||||
|
*/
|
||||||
|
export function patchIkonisLogic() {
|
||||||
|
// Current slot logic is handled via getSlotCount in the sheet context
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder for character patching - no longer needed for virtual items
|
||||||
|
*/
|
||||||
|
export function patchDhCharacter(DhCharacter) {
|
||||||
|
// Injection is deprecated in favor of native weapon features
|
||||||
|
}
|
||||||
|
|
||||||
|
export function patchDHWeapon() {
|
||||||
|
// Future: Add damage type override logic here
|
||||||
|
}
|
||||||
237
scripts/ikonis-sheet.js
Normal file
237
scripts/ikonis-sheet.js
Normal file
|
|
@ -0,0 +1,237 @@
|
||||||
|
import { getAugments, getSlotCount } 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
|
||||||
|
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 weaponFeatures = doc.system.weaponFeatures || [];
|
||||||
|
const allAugmentsList = getAugments() || [];
|
||||||
|
|
||||||
|
const processedAugments = [];
|
||||||
|
let bondedFeature = null;
|
||||||
|
|
||||||
|
// Identify "Bonded" features by name
|
||||||
|
const allNativeFeatures = CONFIG.DH.ITEM.allWeaponFeatures() || {};
|
||||||
|
const bondedIds = Object.keys(allNativeFeatures).filter(k => allNativeFeatures[k].name === "Ikonis: Bonded");
|
||||||
|
|
||||||
|
// Auto-install if missing (check by name/presence of any bonded ID)
|
||||||
|
const hasBonded = weaponFeatures.some(f => bondedIds.includes(f.value));
|
||||||
|
if (bondedIds.length > 0 && !hasBonded) {
|
||||||
|
console.log(`DH-Ikonis | Auto-installing Bonded feature on ${doc.name}`);
|
||||||
|
const bondedId = bondedIds.includes("ikonis-bonded") ? "ikonis-bonded" : bondedIds[0];
|
||||||
|
const newFeatures = [...weaponFeatures, { value: bondedId }];
|
||||||
|
doc.update({ "system.weaponFeatures": newFeatures });
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const featureRef of weaponFeatures) {
|
||||||
|
const nativeId = featureRef.value;
|
||||||
|
|
||||||
|
// Special handling for Bonded
|
||||||
|
if (bondedIds.includes(nativeId)) {
|
||||||
|
const feature = allNativeFeatures[nativeId];
|
||||||
|
bondedFeature = {
|
||||||
|
id: nativeId,
|
||||||
|
name: "Bonded",
|
||||||
|
fullName: feature.name,
|
||||||
|
effect: feature.description ? feature.description.replace(/<[^>]*>?/gm, '').substring(0, 100) + "..." : "Primary module",
|
||||||
|
installed: true
|
||||||
|
};
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const base = allAugmentsList.find(a => String(a.id) === String(nativeId));
|
||||||
|
if (!base) continue;
|
||||||
|
|
||||||
|
processedAugments.push({ ...base, installed: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
context.ikonis = {
|
||||||
|
enabled: true,
|
||||||
|
augments: processedAugments,
|
||||||
|
bonded: bondedFeature,
|
||||||
|
isGM: game.user?.isGM || false
|
||||||
|
};
|
||||||
|
|
||||||
|
context.maxSlots = getSlotCount(doc);
|
||||||
|
context.usedSlots = processedAugments.length; // Bonded doesn't count
|
||||||
|
|
||||||
|
// 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 weaponFeatures = this.document.system.weaponFeatures || [];
|
||||||
|
const allAugments = getAugments();
|
||||||
|
const augmentIds = allAugments.map(a => a.id);
|
||||||
|
|
||||||
|
// Identify all features that are recognized as augments (and NOT "Bonded")
|
||||||
|
const allNativeFeatures = CONFIG.DH.ITEM.allWeaponFeatures() || {};
|
||||||
|
const bondedIds = Object.keys(allNativeFeatures).filter(k => allNativeFeatures[k].name === "Ikonis: Bonded");
|
||||||
|
|
||||||
|
const usedSlotsCount = weaponFeatures.filter(f => {
|
||||||
|
if (bondedIds.includes(f.value)) return false;
|
||||||
|
return augmentIds.includes(f.value);
|
||||||
|
}).length;
|
||||||
|
|
||||||
|
const maxSlots = getSlotCount(this.document);
|
||||||
|
if (usedSlotsCount >= maxSlots) {
|
||||||
|
ui.notifications.warn(`No more augment slots available! (Max: ${maxSlots})`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const available = allAugments.filter(a => !weaponFeatures.some(f => f.value === 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; display: flex; align-items: center; gap: 1rem;">
|
||||||
|
<img src="${a.img}" style="width: 40px; height: 40px; border: none; filter: drop-shadow(0 0 5px rgba(255,255,255,0.1));">
|
||||||
|
<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>
|
||||||
|
<div style="display: flex; flex-direction: column; gap: 2px;">
|
||||||
|
<small style="color: #888; font-style: italic;">${a.cost}</small>
|
||||||
|
${a.precompile ? `<small style="color: #ff9f43; font-weight: bold;">${a.precompile}</small>` : ''}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`).join('')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
let selectedId = null;
|
||||||
|
const res = await foundry.applications.api.DialogV2.wait({
|
||||||
|
window: { title: "Install Tech", width: 550 },
|
||||||
|
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 newFeatures = [...weaponFeatures, { value: res }];
|
||||||
|
await this.document.update({ "system.weaponFeatures": newFeatures });
|
||||||
|
this.render(true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Weapon.prototype._onRemoveAugment = async function(event, target) {
|
||||||
|
let weaponFeatures = foundry.utils.deepClone(this.document.system.weaponFeatures || []);
|
||||||
|
const targetId = target.dataset.id;
|
||||||
|
|
||||||
|
// 1. Pre-emptive cleanup of all stale effectIds on the weapon.
|
||||||
|
// This is necessary because the system crashes if it tries to remove a feature
|
||||||
|
// that points to an effect ID that no longer exists in the weapon's effects collection.
|
||||||
|
let needsCleanup = false;
|
||||||
|
const cleanedFeatures = weaponFeatures.map(f => {
|
||||||
|
if (Array.isArray(f.effectIds)) {
|
||||||
|
const originalCount = f.effectIds.length;
|
||||||
|
f.effectIds = f.effectIds.filter(id => this.document.effects.has(id));
|
||||||
|
if (f.effectIds.length !== originalCount) needsCleanup = true;
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (needsCleanup) {
|
||||||
|
console.log("DH-Ikonis | Cleaning up stale effectIds to prevent system crash...");
|
||||||
|
await this.document.update({ "system.weaponFeatures": cleanedFeatures });
|
||||||
|
// Refresh our local copy after update
|
||||||
|
weaponFeatures = foundry.utils.deepClone(this.document.system.weaponFeatures);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Perform the actual removal
|
||||||
|
const idx = weaponFeatures.findIndex(f => String(f.value) === String(targetId));
|
||||||
|
if (idx !== -1) {
|
||||||
|
weaponFeatures.splice(idx, 1);
|
||||||
|
await this.document.update({ "system.weaponFeatures": weaponFeatures });
|
||||||
|
}
|
||||||
|
|
||||||
|
this.render(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("DH-Ikonis | Weapon sheet patched successfully.");
|
||||||
|
}
|
||||||
101
scripts/main.js
Normal file
101
scripts/main.js
Normal file
|
|
@ -0,0 +1,101 @@
|
||||||
|
import { patchDHWeapon, patchIkonisLogic, patchDhCharacter, seedIkonisHomebrew, registerIkonisFeatures } from './ikonis-data.js';
|
||||||
|
import { patchIkonisSheet } from './ikonis-sheet.js';
|
||||||
|
|
||||||
|
const MODULE_ID = 'dh-ikonis';
|
||||||
|
|
||||||
|
Hooks.once('init', () => {
|
||||||
|
console.log(`${MODULE_ID} | Initializing Ikonis Module`);
|
||||||
|
|
||||||
|
// Register default features in CONFIG immediately
|
||||||
|
registerIkonisFeatures();
|
||||||
|
|
||||||
|
// --- Slot Settings ---
|
||||||
|
[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 + 1
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle for Tech Rename
|
||||||
|
game.settings.register(MODULE_ID, "enableTechRename", {
|
||||||
|
name: "Rename Magic to Tech",
|
||||||
|
hint: "Renames 'Magical' damage to 'Tech' damage and updates the icon to a microchip.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
});
|
||||||
|
|
||||||
|
// Toggle for Currency Override
|
||||||
|
game.settings.register(MODULE_ID, "enableCurrencyOverride", {
|
||||||
|
name: "Enable Quantum Currency",
|
||||||
|
hint: "Automatically overrides homebrew settings to use 'Quantum' credits and atomic icons.",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
});
|
||||||
|
|
||||||
|
const DhCharacter = CONFIG.Actor.dataModels?.character;
|
||||||
|
if (DhCharacter) patchDhCharacter(DhCharacter);
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on('setup', () => {
|
||||||
|
// Damage Type Rename
|
||||||
|
if (game.settings.get(MODULE_ID, "enableTechRename")) {
|
||||||
|
const mag = CONFIG.DH?.GENERAL?.damageTypes?.magical;
|
||||||
|
if (mag) {
|
||||||
|
mag.label = "Tech";
|
||||||
|
mag.abbreviation = "TCH";
|
||||||
|
mag.icon = "fa-solid fa-microchip";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
patchDHWeapon();
|
||||||
|
patchIkonisLogic();
|
||||||
|
patchIkonisSheet();
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.once('ready', async () => {
|
||||||
|
console.log(`${MODULE_ID} | Ready.`);
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
// Seed default Ikonis features into native Homebrew if missing
|
||||||
|
await seedIkonisHomebrew();
|
||||||
|
|
||||||
|
if (game.settings.get(MODULE_ID, "enableCurrencyOverride")) {
|
||||||
|
await overrideCurrency();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const DhCharacter = game.system.api?.models?.actors?.DhCharacter || CONFIG.Actor.dataModels?.character;
|
||||||
|
if (DhCharacter) patchDhCharacter(DhCharacter);
|
||||||
|
});
|
||||||
|
|
||||||
|
async function overrideCurrency() {
|
||||||
|
let key = 'Homebrew';
|
||||||
|
if (!game.settings.settings.has('daggerheart.Homebrew')) {
|
||||||
|
if (game.settings.settings.has('daggerheart.homebrew')) key = 'homebrew';
|
||||||
|
else return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const homebrew = game.settings.get('daggerheart', key);
|
||||||
|
if (!homebrew || homebrew.currency?.title === "Quantum") return;
|
||||||
|
|
||||||
|
const newHomebrew = (typeof homebrew.toObject === 'function') ? homebrew.toObject() : foundry.utils.deepClone(homebrew);
|
||||||
|
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', key, newHomebrew);
|
||||||
|
console.log(`${MODULE_ID} | Currency system updated to Quantum Credits.`);
|
||||||
|
}
|
||||||
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;
|
||||||
|
}
|
||||||
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>
|
||||||
49
templates/ikonis-motherboard.hbs
Normal file
49
templates/ikonis-motherboard.hbs
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
|
||||||
|
<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-microchip"></i> {{localize "DAGGERHEART.ITEMS.Ikonis.Motherboard"}}</h2>
|
||||||
|
<div class="slots-info">
|
||||||
|
<span>{{usedSlots}} / {{maxSlots}} {{localize "DAGGERHEART.ITEMS.Ikonis.AugmentSlots"}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{#if ikonis.bonded}}
|
||||||
|
<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;">
|
||||||
|
<div class="text">
|
||||||
|
<h3 style="margin: 0; color: #ff2e63; font-size: 1.2rem;">{{ikonis.bonded.fullName}} (Primary)</h3>
|
||||||
|
<p style="margin: 0.25rem 0 0 0; color: #d1d8e0; font-size: 0.95rem;">{{ikonis.bonded.effect}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
<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; min-height: 85px; display: flex; flex-direction: column;">
|
||||||
|
<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.25rem;">
|
||||||
|
<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; line-height: 1.3;">
|
||||||
|
<div class="effect" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">{{aug.effect}}</div>
|
||||||
|
<div class="cost" style="color: #ff2e63; font-style: italic;">{{aug.cost}}</div>
|
||||||
|
{{#if aug.precompile}}<div class="precompile" style="color: #ff9f43; font-weight: bold;">{{aug.precompile}}</div>{{/if}}
|
||||||
|
</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