From 270345ee12b77a6423123184cdfaa332c6f9abef Mon Sep 17 00:00:00 2001
From: WBHarry <89362246+WBHarry@users.noreply.github.com>
Date: Tue, 10 Mar 2026 18:46:19 +0100
Subject: [PATCH] [Feature] Custom Homebrew Resources (#1718)
* Added resources to the Homebrew Menu
* Fixed translations
* .
* Inverted from isImage to isIcon. Should be more logical for users
---
daggerheart.mjs | 22 ++--
lang/en.json | 17 +++
.../settings/homebrewSettings.mjs | 106 +++++++++++++++++-
.../applications/sheets/actors/character.mjs | 12 +-
module/config/_module.mjs | 1 +
module/config/actorConfig.mjs | 87 +-------------
module/config/resourceConfig.mjs | 100 +++++++++++++++++
module/config/system.mjs | 2 +
module/data/actor/creature.mjs | 53 ++++-----
module/data/fields/actorField.mjs | 2 +-
module/data/item/base.mjs | 6 +-
module/data/settings/Homebrew.mjs | 71 ++++++++++++
styles/less/ui/index.less | 1 +
.../settings/homebrew-settings/resources.less | 87 ++++++++++++++
.../less/ux/tooltip/resource-management.less | 4 +-
.../settings/homebrew-settings/resources.hbs | 78 +++++++++++++
templates/ui/tooltip/resourceManagement.hbs | 8 +-
17 files changed, 518 insertions(+), 139 deletions(-)
create mode 100644 module/config/resourceConfig.mjs
create mode 100644 styles/less/ui/settings/homebrew-settings/resources.less
create mode 100644 templates/settings/homebrew-settings/resources.hbs
diff --git a/daggerheart.mjs b/daggerheart.mjs
index fa4fff56..71120a20 100644
--- a/daggerheart.mjs
+++ b/daggerheart.mjs
@@ -97,7 +97,7 @@ Hooks.once('init', () => {
fields
};
- CONFIG.DH.ACTOR.characterResources.basic = {
+ CONFIG.DH.RESOURCE.characterResources.basic = {
id: 'basic',
initial: 0,
max: 4,
@@ -105,7 +105,7 @@ Hooks.once('init', () => {
label: 'Basic'
};
- CONFIG.DH.ACTOR.characterResources.corruption = {
+ CONFIG.DH.RESOURCE.characterResources.corruption = {
id: 'corruption',
initial: 0,
max: 4,
@@ -114,16 +114,16 @@ Hooks.once('init', () => {
images: {
full: {
value: 'systems/daggerheart/assets/icons/domains/sage.svg',
- isPath: true
+ isIcon: false
},
empty: {
value: 'systems/daggerheart/assets/icons/domains/sage.svg',
- isPath: true
+ isIcon: false
}
}
};
- CONFIG.DH.ACTOR.characterResources.fire = {
+ CONFIG.DH.RESOURCE.characterResources.fire = {
id: 'fire',
initial: 0,
max: 6,
@@ -132,18 +132,18 @@ Hooks.once('init', () => {
images: {
full: {
value: 'icons/magic/fire/barrier-wall-explosion-orange.webp',
- isPath: true,
+ isIcon: false,
noColorFilter: true
},
empty: {
value: 'icons/magic/fire/barrier-wall-flame-ring-blue.webp',
- isPath: true,
+ isIcon: false,
noColorFilter: true
}
}
};
- CONFIG.DH.ACTOR.characterResources.hunger = {
+ CONFIG.DH.RESOURCE.characterResources.hunger = {
id: 'hunger',
initial: 0,
max: 6,
@@ -151,10 +151,12 @@ Hooks.once('init', () => {
label: 'Hunger',
images: {
full: {
- value: 'fa-solid fa-burger'
+ value: 'fa-solid fa-burger',
+ isIcon: true
},
empty: {
- value: 'fa-regular fa-burger'
+ value: 'fa-regular fa-burger',
+ isIcon: true
}
}
};
diff --git a/lang/en.json b/lang/en.json
index c9d21944..4e15c362 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -2266,6 +2266,7 @@
"identify": "Identity",
"imagePath": "Image Path",
"inactiveEffects": "Inactive Effects",
+ "initial": "Initial",
"inventory": "Inventory",
"itemResource": "Item Resource",
"itemQuantity": "Item Quantity",
@@ -2643,6 +2644,8 @@
"resetMovesText": "Are you sure you want to reset?",
"deleteItemTitle": "Delete Homebrew Item",
"deleteItemText": "Are you sure you want to delete the item?",
+ "deleteResourceTitle": "Delete Homebrew Resource",
+ "deleteResourceText": "Are you sure you want to delete the resource?",
"FIELDS": {
"maxFear": { "label": "Max Fear" },
"maxHope": { "label": "Max Hope" },
@@ -2651,6 +2654,13 @@
"label": "Max Cards in Loadout",
"hint": "Set to blank or 0 for unlimited maximum"
},
+ "resources": {
+ "resources": {
+ "value": { "label": "Icon" },
+ "isIcon": { "label": "Font Awesome Icon" },
+ "noColorFilter": { "label": "Disable Color Filter" }
+ }
+ },
"maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" }
},
"currency": {
@@ -2679,6 +2689,13 @@
"adversaryType": {
"title": "Custom Adversary Types",
"newType": "Adversary Type"
+ },
+ "resources": {
+ "typeTitle": "{type} Resources",
+ "filledIcon": "Filled Icon",
+ "emptyIcon": "Empty Icon",
+ "resourceIdentifier": "Resource Identifier",
+ "setResourceIdentifier": "Set Resource Identifier"
}
},
"Menu": {
diff --git a/module/applications/settings/homebrewSettings.mjs b/module/applications/settings/homebrewSettings.mjs
index 083c468f..9cc0ecb1 100644
--- a/module/applications/settings/homebrewSettings.mjs
+++ b/module/applications/settings/homebrewSettings.mjs
@@ -1,4 +1,5 @@
import { DhHomebrew } from '../../data/settings/_module.mjs';
+import { Resource } from '../../data/settings/Homebrew.mjs';
import { slugify } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@@ -44,6 +45,9 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
addAdversaryType: this.addAdversaryType,
deleteAdversaryType: this.deleteAdversaryType,
selectAdversaryType: this.selectAdversaryType,
+ addResource: this.addResource,
+ removeResource: this.removeResource,
+ resetResourceImage: this.resetResourceImage,
save: this.save,
resetTokenSizes: this.resetTokenSizes,
reset: this.reset
@@ -56,6 +60,10 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.hbs' },
+ resources: {
+ template: 'systems/daggerheart/templates/settings/homebrew-settings/resources.hbs',
+ scrollable: ['.resource-types-container']
+ },
itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
@@ -64,7 +72,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
/** @inheritdoc */
static TABS = {
main: {
- tabs: [{ id: 'settings' }, { id: 'domains' }, { id: 'types' }, { id: 'itemFeatures' }, { id: 'downtime' }],
+ tabs: [
+ { id: 'settings' },
+ { id: 'domains' },
+ { id: 'types' },
+ { id: 'resources' },
+ { id: 'itemFeatures' },
+ { id: 'downtime' }
+ ],
initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
@@ -77,9 +92,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render();
}
+ _attachPartListeners(partId, htmlElement, options) {
+ super._attachPartListeners(partId, htmlElement, options);
+
+ for (const element of htmlElement.querySelectorAll('.path-field input'))
+ element.addEventListener('change', this.toggleResourceIsIcon.bind(this));
+ }
+
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.settingFields = this.settings;
+ context.schemaFields = context.settingFields.schema.fields;
return context;
}
@@ -103,6 +126,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
: null;
break;
+ case 'resources':
+ break;
case 'downtime':
context.restOptions = {
shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(),
@@ -124,6 +149,33 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render();
}
+ async toggleResourceIsIcon(event) {
+ const element = event.target.closest('.resource-icon-container');
+ const { actorType, resourceKey, imageKey } = element.dataset;
+
+ const current = this.settings.resources[actorType].resources[resourceKey].images[imageKey];
+ await this.settings.updateSource({
+ [`resources.${actorType}.resources.${resourceKey}.images.${imageKey}`]: {
+ isIcon: !current.isIcon,
+ value: ''
+ }
+ });
+
+ this.render();
+ }
+
+ static async resetResourceImage(_event, button) {
+ const element = button.closest('.resource-icon-container');
+ const { actorType, resourceKey, imageKey } = element.dataset;
+
+ await this.settings.updateSource({
+ [`resources.${actorType}.resources.${resourceKey}.images.${imageKey}`]:
+ Resource.getDefaultImageData(imageKey)
+ });
+
+ this.render();
+ }
+
static async changeCurrencyIcon(_, target) {
const type = target.dataset.currency;
const currentIcon = this.settings.currency[type].icon;
@@ -466,6 +518,58 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render();
}
+ static async addResource(_, target) {
+ const { actorType } = target.dataset;
+ const content = new foundry.data.fields.StringField({
+ label: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.resources.resourceIdentifier'),
+ required: true
+ }).toFormGroup({}, { name: 'identifier', localize: true }).outerHTML;
+
+ async function callback(_, button) {
+ const identifier = button.form.elements.identifier.value;
+ if (!identifier) return;
+
+ const sluggedIdentifier = slugify(identifier);
+
+ await this.settings.updateSource({
+ [`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)
+ });
+
+ game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
+ this.render();
+ }
+
+ await foundry.applications.api.DialogV2.prompt({
+ content: content,
+ rejectClose: false,
+ modal: true,
+ ok: { callback: callback.bind(this) },
+ window: {
+ title: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.resources.setResourceIdentifier')
+ },
+ position: { width: 400 }
+ });
+ }
+
+ static async removeResource(_, target) {
+ const confirmed = await foundry.applications.api.DialogV2.confirm({
+ window: {
+ title: game.i18n.localize(`DAGGERHEART.SETTINGS.Homebrew.deleteResourceTitle`)
+ },
+ content: game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.deleteResourceText')
+ });
+
+ if (!confirmed) return;
+
+ const { actorType, resourceKey } = target.dataset;
+ await this.settings.updateSource({
+ [`resources.${actorType}.resources.-=${resourceKey}`]: null
+ });
+
+ game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
+ this.render();
+ }
+
static async save() {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.close();
diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs
index 497d935f..537a8713 100644
--- a/module/applications/sheets/actors/character.mjs
+++ b/module/applications/sheets/actors/character.mjs
@@ -245,8 +245,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
async _prepareHeaderContext(context, _options) {
- context.hasExtraResources = Object.keys(CONFIG.DH.ACTOR.characterResources).some(
- key => !CONFIG.DH.ACTOR.characterBaseResources[key]
+ context.hasExtraResources = Object.keys(CONFIG.DH.RESOURCE.allCharacterResources()).some(
+ key => !CONFIG.DH.RESOURCE.characterBaseResources[key]
);
}
@@ -960,8 +960,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
return;
}
- const extraResources = Object.values(CONFIG.DH.ACTOR.characterResources).reduce((acc, resource) => {
- if (CONFIG.DH.ACTOR.characterBaseResources[resource.id]) return acc;
+ const extraResources = Object.values(CONFIG.DH.RESOURCE.allCharacterResources()).reduce((acc, resource) => {
+ if (CONFIG.DH.RESOURCE.characterBaseResources[resource.id]) return acc;
const resourceData = this.document.system.resources[resource.id];
acc[resource.id] = {
@@ -969,8 +969,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
label: game.i18n.localize(resource.label),
value: resourceData.value,
max: resourceData.max,
- fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle' },
- emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle' }
+ fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle', isIcon: true },
+ emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle', isIcon: true }
};
return acc;
diff --git a/module/config/_module.mjs b/module/config/_module.mjs
index 560f3fec..7a725f54 100644
--- a/module/config/_module.mjs
+++ b/module/config/_module.mjs
@@ -11,3 +11,4 @@ export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs';
export * as itemBrowserConfig from './itemBrowserConfig.mjs';
export * as triggerConfig from './triggerConfig.mjs';
+export * as resourceConfig from './resourceConfig.mjs';
diff --git a/module/config/actorConfig.mjs b/module/config/actorConfig.mjs
index 54bf0143..43795a0b 100644
--- a/module/config/actorConfig.mjs
+++ b/module/config/actorConfig.mjs
@@ -1,3 +1,5 @@
+import { allAdversaryResources, allCharacterResources, allCompanionResources } from './resourceConfig.mjs';
+
export const abilities = {
agility: {
id: 'agility',
@@ -55,91 +57,6 @@ export const abilities = {
}
};
-/**
- * Full custom typing:
- * id
- * initial
- * max
- * reverse
- * label
- * images {
- * full { value, isPath, noColorFilter }
- * empty { value, isPath noColorFilter }
- * }
- */
-
-export const characterBaseResources = {
- hitPoints: {
- id: 'hitPoints',
- initial: 0,
- max: 0,
- reverse: true,
- label: 'DAGGERHEART.GENERAL.HitPoints.plural',
- maxLabel: 'DAGGERHEART.ACTORS.Character.maxHPBonus'
- },
- stress: {
- id: 'stress',
- initial: 0,
- max: 6,
- reverse: true,
- label: 'DAGGERHEART.GENERAL.stress'
- },
- hope: {
- id: 'hope',
- initial: 2,
- min: 0,
- reverse: false,
- label: 'DAGGERHEART.GENERAL.hope'
- }
-};
-
-export const characterResources = {
- ...characterBaseResources
-};
-
-export const adversaryBaseResources = {
- hitPoints: {
- id: 'hitPoints',
- initial: 0,
- max: 0,
- reverse: true,
- label: 'DAGGERHEART.GENERAL.HitPoints.plural',
- maxLabel: 'DAGGERHEART.ACTORS.Character.maxHPBonus'
- },
- stress: {
- id: 'stress',
- initial: 0,
- max: 0,
- reverse: true,
- label: 'DAGGERHEART.GENERAL.stress'
- }
-};
-
-export const adversaryResources = {
- ...adversaryBaseResources
-};
-
-export const companionBaseResources = {
- stress: {
- id: 'stress',
- initial: 0,
- max: 0,
- reverse: true,
- label: 'DAGGERHEART.GENERAL.stress'
- },
- hope: {
- id: 'hope',
- initial: 0,
- min: 0,
- reverse: false,
- label: 'DAGGERHEART.GENERAL.hope'
- }
-};
-
-export const companionResources = {
- ...companionBaseResources
-};
-
export const featureProperties = {
agility: {
name: 'DAGGERHEART.CONFIG.Traits.agility.name',
diff --git a/module/config/resourceConfig.mjs b/module/config/resourceConfig.mjs
new file mode 100644
index 00000000..1f594b39
--- /dev/null
+++ b/module/config/resourceConfig.mjs
@@ -0,0 +1,100 @@
+/**
+ * Full custom typing:
+ * id
+ * initial
+ * max
+ * reverse
+ * label
+ * images {
+ * full { value, isIcon, noColorFilter }
+ * empty { value, isIcon noColorFilter }
+ * }
+ */
+
+export const characterBaseResources = {
+ hitPoints: {
+ id: 'hitPoints',
+ initial: 0,
+ max: 0,
+ reverse: true,
+ label: 'DAGGERHEART.GENERAL.HitPoints.plural',
+ maxLabel: 'DAGGERHEART.ACTORS.Character.maxHPBonus'
+ },
+ stress: {
+ id: 'stress',
+ initial: 0,
+ max: 6,
+ reverse: true,
+ label: 'DAGGERHEART.GENERAL.stress'
+ },
+ hope: {
+ id: 'hope',
+ initial: 2,
+ min: 0,
+ reverse: false,
+ label: 'DAGGERHEART.GENERAL.hope'
+ }
+};
+
+export const characterResources = {
+ ...characterBaseResources
+};
+
+export const allCharacterResources = () => {
+ const resources = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).resources.character
+ .resources;
+ return {
+ ...Object.keys(resources).reduce((acc, key) => {
+ acc[key] = { ...resources[key].toObject(), id: key };
+ return acc;
+ }, {}),
+ ...characterResources
+ };
+};
+
+export const adversaryBaseResources = {
+ hitPoints: {
+ id: 'hitPoints',
+ initial: 0,
+ max: 0,
+ reverse: true,
+ label: 'DAGGERHEART.GENERAL.HitPoints.plural',
+ maxLabel: 'DAGGERHEART.ACTORS.Character.maxHPBonus'
+ },
+ stress: {
+ id: 'stress',
+ initial: 0,
+ max: 0,
+ reverse: true,
+ label: 'DAGGERHEART.GENERAL.stress'
+ }
+};
+
+export const adversaryResources = {
+ ...adversaryBaseResources
+};
+
+export const allAdversaryResources = () => adversaryResources;
+
+export const companionBaseResources = {
+ stress: {
+ id: 'stress',
+ initial: 0,
+ max: 0,
+ reverse: true,
+ label: 'DAGGERHEART.GENERAL.stress'
+ },
+ hope: {
+ id: 'hope',
+ initial: 0,
+ min: 0,
+ reverse: false,
+ label: 'DAGGERHEART.GENERAL.hope'
+ }
+};
+
+export const companionResources = {
+ ...companionBaseResources
+};
+
+export const allCompanionResources = () => companionResources;
diff --git a/module/config/system.mjs b/module/config/system.mjs
index 47a41e8d..31dba518 100644
--- a/module/config/system.mjs
+++ b/module/config/system.mjs
@@ -2,6 +2,7 @@ import * as GENERAL from './generalConfig.mjs';
import * as DOMAIN from './domainConfig.mjs';
import * as ENCOUNTER from './encounterConfig.mjs';
import * as ACTOR from './actorConfig.mjs';
+import * as RESOURCE from './resourceConfig.mjs';
import * as ITEM from './itemConfig.mjs';
import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs';
@@ -19,6 +20,7 @@ export const SYSTEM = {
GENERAL,
DOMAIN,
ACTOR,
+ RESOURCE,
ITEM,
SETTINGS,
EFFECTS,
diff --git a/module/data/actor/creature.mjs b/module/data/actor/creature.mjs
index 9b96a5b5..e12c645d 100644
--- a/module/data/actor/creature.mjs
+++ b/module/data/actor/creature.mjs
@@ -9,31 +9,34 @@ export default class DhCreature extends BaseDataActor {
return {
...super.defineSchema(),
resources: new fields.SchemaField({
- ...Object.values(CONFIG.DH.ACTOR[`${this.metadata.type}Resources`]).reduce((acc, resource) => {
- if (resource.max !== undefined) {
- acc[resource.id] = resourceField(
- resource.max,
- resource.initial,
- resource.label,
- resource.maxLabel
- );
- } else {
- acc[resource.id] = new fields.SchemaField(
- {
- value: new fields.NumberField({
- initial: resource.initial,
- min: resource.min,
- integer: true,
- label: resource.label
- }),
- isReversed: new fields.BooleanField({ initial: resource.reverse })
- },
- { label: resource.label }
- );
- }
+ ...Object.values(CONFIG.DH.RESOURCE[`all${this.metadata.type.capitalize()}Resources`]()).reduce(
+ (acc, resource) => {
+ if (resource.max !== undefined) {
+ acc[resource.id] = resourceField(
+ resource.max,
+ resource.initial,
+ resource.label,
+ resource.maxLabel
+ );
+ } else {
+ acc[resource.id] = new fields.SchemaField(
+ {
+ value: new fields.NumberField({
+ initial: resource.initial,
+ min: resource.min,
+ integer: true,
+ label: resource.label
+ }),
+ isReversed: new fields.BooleanField({ initial: resource.reverse })
+ },
+ { label: resource.label }
+ );
+ }
- return acc;
- }, {})
+ return acc;
+ },
+ {}
+ )
}),
advantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
@@ -55,7 +58,7 @@ export default class DhCreature extends BaseDataActor {
prepareDerivedData() {
super.prepareDerivedData();
- const resources = CONFIG.DH.ACTOR[`${this.parent.type}Resources`];
+ const resources = CONFIG.DH.RESOURCE[`all${this.parent.type.capitalize()}Resources`]();
if (resources) {
for (const [key, value] of Object.entries(this.resources)) {
value.label = resources[key]?.label;
diff --git a/module/data/fields/actorField.mjs b/module/data/fields/actorField.mjs
index 5aa55ee6..2c9bb83d 100644
--- a/module/data/fields/actorField.mjs
+++ b/module/data/fields/actorField.mjs
@@ -16,7 +16,7 @@ const resourceField = (max = 0, initial = 0, label, maxLabel) =>
label:
maxLabel ??
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
- }),
+ })
},
{ label }
);
diff --git a/module/data/item/base.mjs b/module/data/item/base.mjs
index 748d292f..6f3256e3 100644
--- a/module/data/item/base.mjs
+++ b/module/data/item/base.mjs
@@ -224,11 +224,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
const armorChanged =
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
- const armorData = getScrollTextData(
- this.parent.parent,
- changed.system.marks,
- 'armor',
- );
+ const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor');
options.scrollingTextData = [armorData];
}
diff --git a/module/data/settings/Homebrew.mjs b/module/data/settings/Homebrew.mjs
index b8804fa7..c525cabd 100644
--- a/module/data/settings/Homebrew.mjs
+++ b/module/data/settings/Homebrew.mjs
@@ -145,6 +145,16 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
description: new fields.StringField()
})
),
+ resources: new fields.TypedObjectField(
+ new fields.SchemaField({
+ resources: new fields.TypedObjectField(new fields.EmbeddedDataField(Resource))
+ }),
+ {
+ initial: {
+ character: { resources: {} }
+ }
+ }
+ ),
itemFeatures: new fields.SchemaField({
weaponFeatures: new fields.TypedObjectField(
new fields.SchemaField({
@@ -186,3 +196,64 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
return source;
}
}
+
+export class Resource extends foundry.abstract.DataModel {
+ static defineSchema() {
+ const fields = foundry.data.fields;
+ return {
+ initial: new fields.NumberField({
+ required: true,
+ integer: true,
+ initial: 0,
+ min: 0,
+ label: 'DAGGERHEART.GENERAL.initial'
+ }),
+ max: new fields.NumberField({
+ nullable: true,
+ initial: null,
+ min: 0,
+ label: 'DAGGERHEART.GENERAL.max'
+ }),
+ label: new fields.StringField({ label: 'DAGGERHEART.GENERAL.label' }),
+ images: new fields.SchemaField({
+ full: imageIconField('fa solid fa-circle'),
+ empty: imageIconField('fa-regular fa-circle')
+ })
+ };
+ }
+
+ static getDefaultResourceData = label => {
+ const images = Resource.schema.fields.images.getInitialValue();
+ return {
+ initial: 0,
+ max: 0,
+ label: label ?? '',
+ images
+ };
+ };
+
+ static getDefaultImageData = imageKey => {
+ return Resource.schema.fields.images.fields[imageKey].getInitialValue();
+ };
+}
+
+const imageIconField = defaultValue =>
+ new foundry.data.fields.SchemaField(
+ {
+ value: new foundry.data.fields.StringField({
+ initial: defaultValue,
+ label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.value.label'
+ }),
+ isIcon: new foundry.data.fields.BooleanField({
+ required: true,
+ initial: true,
+ label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.isIcon.label'
+ }),
+ noColorFilter: new foundry.data.fields.BooleanField({
+ required: true,
+ initial: false,
+ label: 'DAGGERHEART.SETTINGS.Homebrew.FIELDS.resources.resources.noColorFilter.label'
+ })
+ },
+ { required: true }
+ );
diff --git a/styles/less/ui/index.less b/styles/less/ui/index.less
index 065e43c5..5a6e5878 100644
--- a/styles/less/ui/index.less
+++ b/styles/less/ui/index.less
@@ -27,6 +27,7 @@
@import './settings/settings.less';
@import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less';
+@import './settings/homebrew-settings/resources.less';
@import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less';
diff --git a/styles/less/ui/settings/homebrew-settings/resources.less b/styles/less/ui/settings/homebrew-settings/resources.less
new file mode 100644
index 00000000..5333e54d
--- /dev/null
+++ b/styles/less/ui/settings/homebrew-settings/resources.less
@@ -0,0 +1,87 @@
+.daggerheart.dh-style.setting.homebrew-settings .resources.tab {
+ .resource-types-container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ overflow: auto;
+ max-height: 570px;
+
+ fieldset legend {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ }
+
+ .resource-type-container {
+ width: 100%;
+
+ .resources-container {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+
+ .resource-container {
+ .resource-icons-container {
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+ width: 100%;
+
+ .resource-icon-container {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ flex: 1;
+
+ .resource-icon-title-container {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 4px;
+
+ &::before,
+ &::after {
+ color: @dark-blue;
+ content: '';
+ flex: 1;
+ height: 2px;
+ }
+
+ &::before {
+ background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, @golden 100%);
+ }
+
+ &::after {
+ background: linear-gradient(90deg, @golden 0%, rgba(0, 0, 0, 0) 100%);
+ }
+
+ .resource-icon-title {
+ font-size: var(--font-size-16);
+ white-space: nowrap;
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ color: light-dark(@dark-blue, @golden);
+
+ i {
+ font-size: 14px;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ .two-columns {
+ width: 100%;
+ }
+
+ .form-group.vertical {
+ > * {
+ flex: 0 0 100%;
+ }
+ }
+}
diff --git a/styles/less/ux/tooltip/resource-management.less b/styles/less/ux/tooltip/resource-management.less
index a5d6d51b..ff1f4dd2 100644
--- a/styles/less/ux/tooltip/resource-management.less
+++ b/styles/less/ux/tooltip/resource-management.less
@@ -35,8 +35,8 @@
}
img {
- width: 18px;
- height: 18px;
+ width: 14px;
+ height: 14px;
&.empty {
opacity: 0.4;
diff --git a/templates/settings/homebrew-settings/resources.hbs b/templates/settings/homebrew-settings/resources.hbs
new file mode 100644
index 00000000..7f3dee3e
--- /dev/null
+++ b/templates/settings/homebrew-settings/resources.hbs
@@ -0,0 +1,78 @@
+