daggerheart/module/data/fields/actorField.mjs
WBHarry 552c62adc1
[Feature] Custom Resources (#1714)
* Initial

* .

* Fixed positioning

* .

* Only showing the menu if there are extra resources

* Improved resourceManager clickable

* .

* Changed variable name

* Refactor resources selection and data prep (#1721)

* Move resources select to scrolly text and accept actor object

* Convert isReversed to prepared data and add label

* Removed unused imports

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>

* Naming

* [Feature] Custom Homebrew Resources (#1718)

* Added resources to the Homebrew Menu

* Fixed translations

* .

* Inverted from isImage to isIcon. Should be more logical for users

* Removed testing resources

* Refactor resource settings to not be a method (#1723)

* Fix editing homebrew resources with a custom ResourcesField

* Fix removing homebrew resources

* Remove vestigial code

* Use custom config for module data instead of including in all (#1724)

* Use custom config for module data instead of including in all

* More simple

* base highest priority

---------

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
Co-authored-by: Carlos Fernandez <cfern1990@gmail.com>
2026-03-11 11:10:28 +01:00

87 lines
3 KiB
JavaScript

const fields = foundry.data.fields;
const attributeField = label =>
new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true, label }),
tierMarked: new fields.BooleanField({ initial: false })
});
const stressDamageReductionRule = localizationPath =>
new fields.SchemaField({
cost: new fields.NumberField({
integer: true,
label: `${localizationPath}.label`,
hint: `${localizationPath}.hint`
})
});
const bonusField = label =>
new fields.SchemaField({
bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }),
dice: new fields.ArrayField(new fields.StringField(), { label: `${game.i18n.localize(label)} Dice` })
});
/**
* Field used for actor resources. It is a resource that validates dynamically based on the config.
* Because "max" may be defined during runtime, we don't attempt to clamp the maximum value.
*/
class ResourcesField extends fields.TypedObjectField {
constructor(actorType) {
super(
new fields.SchemaField({
value: new fields.NumberField({ min: 0, initial: 0, integer: true }),
// Some resources allow changing max. A null max means its the default
max: new fields.NumberField({ initial: null, integer: true, nullable: true })
})
);
this.actorType = actorType;
}
getInitialValue() {
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
return Object.values(resources).reduce((result, resource) => {
result[resource.id] = {
value: resource.initial,
max: null
};
return result;
}, {});
}
_validateKey(key) {
return key in CONFIG.DH.RESOURCE[this.actorType].all;
}
_cleanType(value, options) {
value = super._cleanType(value, options);
// If not partial, ensure all data exists
if (!options.partial) {
value = foundry.utils.mergeObject(this.getInitialValue(), value);
}
return value;
}
/** Initializes the original source data, returning prepared data */
initialize(...args) {
const data = super.initialize(...args);
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
for (const [key, value] of Object.entries(data)) {
// TypedObjectField only calls _validateKey when persisting, so we also call it here
if (!this._validateKey(key)) {
delete value[key];
continue;
}
// Add basic prepared data.
const resource = resources[key];
value.label = resource.label;
value.isReversed = resources[key].reverse;
value.max = typeof resource.max === 'number' ? value.max ?? resource.max : null;
}
return data;
}
}
export { attributeField, ResourcesField, stressDamageReductionRule, bonusField };