[Feature] Custom Homebrew Resources (#1718)

* Added resources to the Homebrew Menu

* Fixed translations

* .

* Inverted from isImage to isIcon. Should be more logical for users
This commit is contained in:
WBHarry 2026-03-10 18:46:19 +01:00 committed by GitHub
parent 023dda6806
commit 270345ee12
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 518 additions and 139 deletions

View file

@ -97,7 +97,7 @@ Hooks.once('init', () => {
fields fields
}; };
CONFIG.DH.ACTOR.characterResources.basic = { CONFIG.DH.RESOURCE.characterResources.basic = {
id: 'basic', id: 'basic',
initial: 0, initial: 0,
max: 4, max: 4,
@ -105,7 +105,7 @@ Hooks.once('init', () => {
label: 'Basic' label: 'Basic'
}; };
CONFIG.DH.ACTOR.characterResources.corruption = { CONFIG.DH.RESOURCE.characterResources.corruption = {
id: 'corruption', id: 'corruption',
initial: 0, initial: 0,
max: 4, max: 4,
@ -114,16 +114,16 @@ Hooks.once('init', () => {
images: { images: {
full: { full: {
value: 'systems/daggerheart/assets/icons/domains/sage.svg', value: 'systems/daggerheart/assets/icons/domains/sage.svg',
isPath: true isIcon: false
}, },
empty: { empty: {
value: 'systems/daggerheart/assets/icons/domains/sage.svg', value: 'systems/daggerheart/assets/icons/domains/sage.svg',
isPath: true isIcon: false
} }
} }
}; };
CONFIG.DH.ACTOR.characterResources.fire = { CONFIG.DH.RESOURCE.characterResources.fire = {
id: 'fire', id: 'fire',
initial: 0, initial: 0,
max: 6, max: 6,
@ -132,18 +132,18 @@ Hooks.once('init', () => {
images: { images: {
full: { full: {
value: 'icons/magic/fire/barrier-wall-explosion-orange.webp', value: 'icons/magic/fire/barrier-wall-explosion-orange.webp',
isPath: true, isIcon: false,
noColorFilter: true noColorFilter: true
}, },
empty: { empty: {
value: 'icons/magic/fire/barrier-wall-flame-ring-blue.webp', value: 'icons/magic/fire/barrier-wall-flame-ring-blue.webp',
isPath: true, isIcon: false,
noColorFilter: true noColorFilter: true
} }
} }
}; };
CONFIG.DH.ACTOR.characterResources.hunger = { CONFIG.DH.RESOURCE.characterResources.hunger = {
id: 'hunger', id: 'hunger',
initial: 0, initial: 0,
max: 6, max: 6,
@ -151,10 +151,12 @@ Hooks.once('init', () => {
label: 'Hunger', label: 'Hunger',
images: { images: {
full: { full: {
value: 'fa-solid fa-burger' value: 'fa-solid fa-burger',
isIcon: true
}, },
empty: { empty: {
value: 'fa-regular fa-burger' value: 'fa-regular fa-burger',
isIcon: true
} }
} }
}; };

View file

@ -2266,6 +2266,7 @@
"identify": "Identity", "identify": "Identity",
"imagePath": "Image Path", "imagePath": "Image Path",
"inactiveEffects": "Inactive Effects", "inactiveEffects": "Inactive Effects",
"initial": "Initial",
"inventory": "Inventory", "inventory": "Inventory",
"itemResource": "Item Resource", "itemResource": "Item Resource",
"itemQuantity": "Item Quantity", "itemQuantity": "Item Quantity",
@ -2643,6 +2644,8 @@
"resetMovesText": "Are you sure you want to reset?", "resetMovesText": "Are you sure you want to reset?",
"deleteItemTitle": "Delete Homebrew Item", "deleteItemTitle": "Delete Homebrew Item",
"deleteItemText": "Are you sure you want to delete the 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": { "FIELDS": {
"maxFear": { "label": "Max Fear" }, "maxFear": { "label": "Max Fear" },
"maxHope": { "label": "Max Hope" }, "maxHope": { "label": "Max Hope" },
@ -2651,6 +2654,13 @@
"label": "Max Cards in Loadout", "label": "Max Cards in Loadout",
"hint": "Set to blank or 0 for unlimited maximum" "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" } "maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" }
}, },
"currency": { "currency": {
@ -2679,6 +2689,13 @@
"adversaryType": { "adversaryType": {
"title": "Custom Adversary Types", "title": "Custom Adversary Types",
"newType": "Adversary Type" "newType": "Adversary Type"
},
"resources": {
"typeTitle": "{type} Resources",
"filledIcon": "Filled Icon",
"emptyIcon": "Empty Icon",
"resourceIdentifier": "Resource Identifier",
"setResourceIdentifier": "Set Resource Identifier"
} }
}, },
"Menu": { "Menu": {

View file

@ -1,4 +1,5 @@
import { DhHomebrew } from '../../data/settings/_module.mjs'; import { DhHomebrew } from '../../data/settings/_module.mjs';
import { Resource } from '../../data/settings/Homebrew.mjs';
import { slugify } from '../../helpers/utils.mjs'; import { slugify } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api; const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -44,6 +45,9 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
addAdversaryType: this.addAdversaryType, addAdversaryType: this.addAdversaryType,
deleteAdversaryType: this.deleteAdversaryType, deleteAdversaryType: this.deleteAdversaryType,
selectAdversaryType: this.selectAdversaryType, selectAdversaryType: this.selectAdversaryType,
addResource: this.addResource,
removeResource: this.removeResource,
resetResourceImage: this.resetResourceImage,
save: this.save, save: this.save,
resetTokenSizes: this.resetTokenSizes, resetTokenSizes: this.resetTokenSizes,
reset: this.reset reset: this.reset
@ -56,6 +60,10 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' }, settings: { template: 'systems/daggerheart/templates/settings/homebrew-settings/settings.hbs' },
domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' }, domains: { template: 'systems/daggerheart/templates/settings/homebrew-settings/domains.hbs' },
types: { template: 'systems/daggerheart/templates/settings/homebrew-settings/types.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' }, itemTypes: { template: 'systems/daggerheart/templates/settings/homebrew-settings/itemFeatures.hbs' },
downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' }, downtime: { template: 'systems/daggerheart/templates/settings/homebrew-settings/downtime.hbs' },
footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' } footer: { template: 'systems/daggerheart/templates/settings/homebrew-settings/footer.hbs' }
@ -64,7 +72,14 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
/** @inheritdoc */ /** @inheritdoc */
static TABS = { static TABS = {
main: { 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', initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs' labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
} }
@ -77,9 +92,17 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render(); 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) { async _prepareContext(_options) {
const context = await super._prepareContext(_options); const context = await super._prepareContext(_options);
context.settingFields = this.settings; context.settingFields = this.settings;
context.schemaFields = context.settingFields.schema.fields;
return context; return context;
} }
@ -103,6 +126,8 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] } ? { id: this.selected.adversaryType, ...this.settings.adversaryTypes[this.selected.adversaryType] }
: null; : null;
break; break;
case 'resources':
break;
case 'downtime': case 'downtime':
context.restOptions = { context.restOptions = {
shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(), shortRest: CONFIG.DH.GENERAL.defaultRestOptions.shortRest(),
@ -124,6 +149,33 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render(); 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) { static async changeCurrencyIcon(_, target) {
const type = target.dataset.currency; const type = target.dataset.currency;
const currentIcon = this.settings.currency[type].icon; const currentIcon = this.settings.currency[type].icon;
@ -466,6 +518,58 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
this.render(); 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() { static async save() {
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject()); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, this.settings.toObject());
this.close(); this.close();

View file

@ -245,8 +245,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
async _prepareHeaderContext(context, _options) { async _prepareHeaderContext(context, _options) {
context.hasExtraResources = Object.keys(CONFIG.DH.ACTOR.characterResources).some( context.hasExtraResources = Object.keys(CONFIG.DH.RESOURCE.allCharacterResources()).some(
key => !CONFIG.DH.ACTOR.characterBaseResources[key] key => !CONFIG.DH.RESOURCE.characterBaseResources[key]
); );
} }
@ -960,8 +960,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
return; return;
} }
const extraResources = Object.values(CONFIG.DH.ACTOR.characterResources).reduce((acc, resource) => { const extraResources = Object.values(CONFIG.DH.RESOURCE.allCharacterResources()).reduce((acc, resource) => {
if (CONFIG.DH.ACTOR.characterBaseResources[resource.id]) return acc; if (CONFIG.DH.RESOURCE.characterBaseResources[resource.id]) return acc;
const resourceData = this.document.system.resources[resource.id]; const resourceData = this.document.system.resources[resource.id];
acc[resource.id] = { acc[resource.id] = {
@ -969,8 +969,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
label: game.i18n.localize(resource.label), label: game.i18n.localize(resource.label),
value: resourceData.value, value: resourceData.value,
max: resourceData.max, max: resourceData.max,
fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle' }, fullIcon: resource.images?.full ?? { value: 'fa-solid fa-circle', isIcon: true },
emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle' } emptyIcon: resource.images?.empty ?? { value: 'fa-regular fa-circle', isIcon: true }
}; };
return acc; return acc;

View file

@ -11,3 +11,4 @@ export * as settingsConfig from './settingsConfig.mjs';
export * as systemConfig from './system.mjs'; export * as systemConfig from './system.mjs';
export * as itemBrowserConfig from './itemBrowserConfig.mjs'; export * as itemBrowserConfig from './itemBrowserConfig.mjs';
export * as triggerConfig from './triggerConfig.mjs'; export * as triggerConfig from './triggerConfig.mjs';
export * as resourceConfig from './resourceConfig.mjs';

View file

@ -1,3 +1,5 @@
import { allAdversaryResources, allCharacterResources, allCompanionResources } from './resourceConfig.mjs';
export const abilities = { export const abilities = {
agility: { agility: {
id: '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 = { export const featureProperties = {
agility: { agility: {
name: 'DAGGERHEART.CONFIG.Traits.agility.name', name: 'DAGGERHEART.CONFIG.Traits.agility.name',

View file

@ -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;

View file

@ -2,6 +2,7 @@ import * as GENERAL from './generalConfig.mjs';
import * as DOMAIN from './domainConfig.mjs'; import * as DOMAIN from './domainConfig.mjs';
import * as ENCOUNTER from './encounterConfig.mjs'; import * as ENCOUNTER from './encounterConfig.mjs';
import * as ACTOR from './actorConfig.mjs'; import * as ACTOR from './actorConfig.mjs';
import * as RESOURCE from './resourceConfig.mjs';
import * as ITEM from './itemConfig.mjs'; import * as ITEM from './itemConfig.mjs';
import * as SETTINGS from './settingsConfig.mjs'; import * as SETTINGS from './settingsConfig.mjs';
import * as EFFECTS from './effectConfig.mjs'; import * as EFFECTS from './effectConfig.mjs';
@ -19,6 +20,7 @@ export const SYSTEM = {
GENERAL, GENERAL,
DOMAIN, DOMAIN,
ACTOR, ACTOR,
RESOURCE,
ITEM, ITEM,
SETTINGS, SETTINGS,
EFFECTS, EFFECTS,

View file

@ -9,31 +9,34 @@ export default class DhCreature extends BaseDataActor {
return { return {
...super.defineSchema(), ...super.defineSchema(),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
...Object.values(CONFIG.DH.ACTOR[`${this.metadata.type}Resources`]).reduce((acc, resource) => { ...Object.values(CONFIG.DH.RESOURCE[`all${this.metadata.type.capitalize()}Resources`]()).reduce(
if (resource.max !== undefined) { (acc, resource) => {
acc[resource.id] = resourceField( if (resource.max !== undefined) {
resource.max, acc[resource.id] = resourceField(
resource.initial, resource.max,
resource.label, resource.initial,
resource.maxLabel resource.label,
); resource.maxLabel
} else { );
acc[resource.id] = new fields.SchemaField( } else {
{ acc[resource.id] = new fields.SchemaField(
value: new fields.NumberField({ {
initial: resource.initial, value: new fields.NumberField({
min: resource.min, initial: resource.initial,
integer: true, min: resource.min,
label: resource.label integer: true,
}), label: resource.label
isReversed: new fields.BooleanField({ initial: resource.reverse }) }),
}, isReversed: new fields.BooleanField({ initial: resource.reverse })
{ label: resource.label } },
); { label: resource.label }
} );
}
return acc; return acc;
}, {}) },
{}
)
}), }),
advantageSources: new fields.ArrayField(new fields.StringField(), { advantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label', label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
@ -55,7 +58,7 @@ export default class DhCreature extends BaseDataActor {
prepareDerivedData() { prepareDerivedData() {
super.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) { if (resources) {
for (const [key, value] of Object.entries(this.resources)) { for (const [key, value] of Object.entries(this.resources)) {
value.label = resources[key]?.label; value.label = resources[key]?.label;

View file

@ -16,7 +16,7 @@ const resourceField = (max = 0, initial = 0, label, maxLabel) =>
label: label:
maxLabel ?? maxLabel ??
game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) }) game.i18n.format('DAGGERHEART.GENERAL.maxWithThing', { thing: game.i18n.localize(label) })
}), })
}, },
{ label } { label }
); );

View file

@ -224,11 +224,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
const armorChanged = const armorChanged =
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value; changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') { if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
const armorData = getScrollTextData( const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor');
this.parent.parent,
changed.system.marks,
'armor',
);
options.scrollingTextData = [armorData]; options.scrollingTextData = [armorData];
} }

View file

@ -145,6 +145,16 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
description: new fields.StringField() 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({ itemFeatures: new fields.SchemaField({
weaponFeatures: new fields.TypedObjectField( weaponFeatures: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
@ -186,3 +196,64 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
return source; 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 }
);

View file

@ -27,6 +27,7 @@
@import './settings/settings.less'; @import './settings/settings.less';
@import './settings/homebrew-settings/domains.less'; @import './settings/homebrew-settings/domains.less';
@import './settings/homebrew-settings/types.less'; @import './settings/homebrew-settings/types.less';
@import './settings/homebrew-settings/resources.less';
@import './sidebar/tabs.less'; @import './sidebar/tabs.less';
@import './sidebar/daggerheartMenu.less'; @import './sidebar/daggerheartMenu.less';

View file

@ -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%;
}
}
}

View file

@ -35,8 +35,8 @@
} }
img { img {
width: 18px; width: 14px;
height: 18px; height: 14px;
&.empty { &.empty {
opacity: 0.4; opacity: 0.4;

View file

@ -0,0 +1,78 @@
<section
class="tab {{tabs.resources.cssClass}} {{tabs.resources.id}} scrollable"
data-tab="{{tabs.resources.id}}"
data-group="{{tabs.resources.group}}"
>
<div class="resource-types-container">
{{#each settingFields.resources as |type key|}}
<fieldset>
<legend>
{{localize "DAGGERHEART.SETTINGS.Homebrew.resources.typeTitle" type=(localize (concat "TYPES.Actor." key))}}
<a data-action="addResource" data-actor-type="{{key}}"><i class="fa-solid fa-plus"></i></a>
</legend>
<div class="resource-type-container">
<div class="resources-container">
{{#each type.resources as |resource key|}}
<fieldset class="resource-container">
<legend>{{resource.label}}<a data-action="removeResource" data-actor-type="{{@../key}}" data-resource-key="{{key}}"><i class="fa-solid fa-trash"></i></a></legend>
{{formField @root.schemaFields.resources.element.fields.resources.element.fields.label value=resource.label name=(concat "resources." @../key ".resources." key ".label") classes="vertical" localize=true }}
<div class="two-columns even">
{{formField @root.schemaFields.resources.element.fields.resources.element.fields.initial value=resource.initial name=(concat "resources." @../key ".resources." key ".initial") classes="vertical" localize=true }}
{{formField @root.schemaFields.resources.element.fields.resources.element.fields.max value=resource.max name=(concat "resources." @../key ".resources." key ".max") classes="vertical" localize=true }}
</div>
<div class="resource-icons-container">
<div class="resource-icon-container" data-actor-type="{{@../key}}" data-resource-key="{{key}}" data-image-key="full">
{{#with @root.schemaFields.resources.element.fields.resources.element.fields.images.fields.full.fields}}
<div class="resource-icon-title-container">
<div class="resource-icon-title">
<span>{{localize "DAGGERHEART.SETTINGS.Homebrew.resources.filledIcon"}}</span>
<a data-action="resetResourceImage"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</div>
</div>
<div class="resource-icon-content">
{{#if ../images.full.isIcon}}
{{formGroup this.value value=../images.full.value name=(concat "resources." @../key ".resources." key ".images.full.value") localize=true }}
{{else}}
<div class="form-fields">
<file-picker name="{{concat "resources." @../key ".resources." key ".images.full.value"}}" value="{{../images.full.value}}" type="image"></file-picker>
</div>
{{/if}}
{{formGroup this.isIcon value=../images.full.isIcon name="" classes="path-field" localize=true }}
{{formGroup this.noColorFilter value=../images.full.noColorFilter name=(concat "resources." @../key ".resources." key ".images.full.noColorFilter") localize=true }}
</div>
{{/with}}
</div>
<div class="resource-icon-container" data-actor-type="{{@../key}}" data-resource-key="{{key}}" data-image-key="empty">
{{#with @root.schemaFields.resources.element.fields.resources.element.fields.images.fields.empty.fields}}
<div class="resource-icon-title-container">
<div class="resource-icon-title">
<span>{{localize "DAGGERHEART.SETTINGS.Homebrew.resources.emptyIcon"}}</span>
<a data-action="resetResourceImage"><i class="fa-solid fa-arrow-rotate-left"></i></a>
</div>
</div>
<div class="resource-icon-content">
{{#if ../images.empty.isIcon}}
{{formGroup this.value value=../images.empty.value name=(concat "resources." @../key ".resources." key ".images.empty.value") localize=true }}
{{else}}
<div class="form-fields">
<file-picker name="{{concat "resources." @../key ".resources." key ".images.empty.value"}}" value="{{../images.empty.value}}" type="image"></file-picker>
</div>
{{/if}}
{{formGroup this.isIcon value=resource.images.empty.isIcon name="" classes="path-field" localize=true }}
{{formGroup this.noColorFilter value=resource.images.empty.noColorFilter name=(concat "resources." @../key ".resources." key ".images.empty.noColorFilter") localize=true }}
</div>
{{/with}}
</div>
</div>
</fieldset>
{{/each}}
</div>
</div>
</fieldset>
{{/each}}
</div>
</section>

View file

@ -4,16 +4,16 @@
<h4>{{resource.label}}</h4> <h4>{{resource.label}}</h4>
{{#times resource.max}} {{#times resource.max}}
<span class='resource-value' data-action='toggleResource' data-value="{{add this 1}}" data-resource="{{resource.id}}"> <span class='resource-value' data-action='toggleResource' data-value="{{add this 1}}" data-resource="{{resource.id}}">
{{#unless resource.fullIcon.isPath}} {{#if resource.fullIcon.isIcon}}
<i class='{{resource.fullIcon.value}} full {{#unless (gte ../value (add this 1))}}hidden{{/unless}}'></i> <i class='{{resource.fullIcon.value}} full {{#unless (gte ../value (add this 1))}}hidden{{/unless}}'></i>
{{else}} {{else}}
<img src="{{resource.fullIcon.value}}" class="full {{#unless resource.fullIcon.noColorFilter}}filter{{else}}non-transparent{{/unless}} {{#unless (gte ../value (add this 1))}}hidden{{/unless}}" /> <img src="{{resource.fullIcon.value}}" class="full {{#unless resource.fullIcon.noColorFilter}}filter{{else}}non-transparent{{/unless}} {{#unless (gte ../value (add this 1))}}hidden{{/unless}}" />
{{/unless}} {{/if}}
{{#unless resource.emptyIcon.isPath}} {{#if resource.emptyIcon.isIcon}}
<i class='{{resource.emptyIcon.value}} empty {{#if (gte ../value (add this 1))}}hidden{{/if}}'></i> <i class='{{resource.emptyIcon.value}} empty {{#if (gte ../value (add this 1))}}hidden{{/if}}'></i>
{{else}} {{else}}
<img src="{{resource.emptyIcon.value}}" class="empty {{#unless resource.fullIcon.noColorFilter}}filter{{else}}non-transparent{{/unless}} {{#if (gte ../value (add this 1))}}hidden{{/if}}" /> <img src="{{resource.emptyIcon.value}}" class="empty {{#unless resource.fullIcon.noColorFilter}}filter{{else}}non-transparent{{/unless}} {{#if (gte ../value (add this 1))}}hidden{{/if}}" />
{{/unless}} {{/if}}
</span> </span>
{{/times}} {{/times}}
</div> </div>