Merged with v14-Dev

This commit is contained in:
WBHarry 2026-03-15 19:13:37 +01:00
commit 88be00567e
650 changed files with 6323 additions and 4508 deletions

View file

@ -1,7 +1,7 @@
export { ActionCollection } from './actionField.mjs';
export { default as IterableTypedObjectField } from './iterableTypedObjectField.mjs';
export { default as FormulaField } from './formulaField.mjs';
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
export { default as TriggerField } from './triggerField.mjs';
export { default as MappingField } from './mappingField.mjs';
export * as ActionFields from './action/_module.mjs';

View file

@ -1,5 +1,6 @@
import FormulaField from '../formulaField.mjs';
import { setsEqual } from '../../../helpers/utils.mjs';
import IterableTypedObjectField from '../iterableTypedObjectField.mjs';
const fields = foundry.data.fields;
@ -12,7 +13,7 @@ export default class DamageField extends fields.SchemaField {
/** @inheritDoc */
constructor(options, context = {}) {
const damageFields = {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
parts: new IterableTypedObjectField(DHDamageData),
includeBase: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'

View file

@ -1,6 +1,5 @@
import DHActionConfig from '../../applications/sheets-configs/action-config.mjs';
import { itemAbleRollParse } from '../../helpers/utils.mjs';
import MappingField from './mappingField.mjs';
/**
* Specialized collection type for stored actions.
@ -11,9 +10,9 @@ export class ActionCollection extends Collection {
constructor(model, entries) {
super();
this.#model = model;
for (const entry of entries) {
if (!(entry instanceof game.system.api.models.actions.actionsTypes.base)) continue;
this.set(entry._id, entry);
for (const [key, value] of entries) {
if (!(value instanceof game.system.api.models.actions.actionsTypes.base)) continue;
this.set(key, value);
}
}
@ -61,7 +60,7 @@ export class ActionCollection extends Collection {
/**
* Field that stores actions.
*/
export class ActionsField extends MappingField {
export class ActionsField extends foundry.data.fields.TypedObjectField {
constructor(options) {
super(new ActionField(), options);
}
@ -70,7 +69,7 @@ export class ActionsField extends MappingField {
/** @inheritDoc */
initialize(value, model, options) {
const actions = Object.values(super.initialize(value, model, options));
const actions = Object.entries(super.initialize(value, model, options));
return new ActionCollection(model, actions);
}
}
@ -88,10 +87,10 @@ export class ActionField extends foundry.data.fields.ObjectField {
/* -------------------------------------------- */
/** @override */
_cleanType(value, options) {
_cleanType(value, options, _state) {
if (!(typeof value === 'object')) value = {};
const cls = this.getModel(value);
if (cls) return cls.cleanData(value, options);
if (cls) return cls.cleanData(value, options, _state);
return value;
}
@ -111,9 +110,17 @@ export class ActionField extends foundry.data.fields.ObjectField {
* @param {object} sourceData Candidate source data of the root model.
* @param {any} fieldData The value of this field within the source data.
*/
migrateSource(sourceData, fieldData) {
const cls = this.getModel(fieldData);
if (cls) cls.migrateDataSafe(fieldData);
_migrate(sourceData, _fieldData) {
const source = sourceData ?? this.options.initial;
if (!source) return sourceData;
const cls = this.getModel(source);
if (cls) {
cls.migrateDataSafe(source);
return source;
}
return sourceData;
}
}
@ -237,11 +244,11 @@ export function ActionMixin(Base) {
: foundry.utils.getProperty(result, basePath);
}
delete() {
async delete() {
if (!this.inCollection) return this.item;
const action = foundry.utils.getProperty(this.item, `system.${this.systemPath}`)?.get(this.id);
if (!action) return this.item;
this.item.update({ [`system.${this.systemPath}.-=${this.id}`]: null });
await this.item.update({ [`system.${this.systemPath}.${this.id}`]: _del }); // Does not work. Unsure why. It worked in v13 <_<'
this.constructor._sheets.get(this.uuid)?.close();
}

View file

@ -21,7 +21,7 @@ const bonusField = label =>
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.
*/
@ -52,8 +52,8 @@ class ResourcesField extends fields.TypedObjectField {
return key in CONFIG.DH.RESOURCE[this.actorType].all;
}
_cleanType(value, options) {
value = super._cleanType(value, options);
_cleanType(value, options, _state) {
value = super._cleanType(value, options, _state);
// If not partial, ensure all data exists
if (!options.partial) {
@ -78,10 +78,28 @@ class ResourcesField extends fields.TypedObjectField {
const resource = resources[key];
value.label = resource.label;
value.isReversed = resources[key].reverse;
value.max = typeof resource.max === 'number' ? value.max ?? resource.max : null;
value.max = typeof resource.max === 'number' ? (value.max ?? resource.max) : null;
}
return data;
}
/**
* Foundry bar attributes are unable to handle finding the schema field nor the label normally.
* This returns the element if its a valid resource key and overwrites the element's label for that retrieval.
*/
_getField(path) {
if (path.length === 0) return this;
const first = path.shift();
if (first === this.element.name) return this.element_getField(path);
const resources = CONFIG.DH.RESOURCE[this.actorType].all;
if (first in resources) {
this.element.label = resources[first].label;
return this.element._getField(path);
}
return undefined;
}
}
export { attributeField, ResourcesField, stressDamageReductionRule, bonusField };

View file

@ -14,7 +14,7 @@ export default class ForeignDocumentUUIDArrayField extends foundry.data.fields.A
/** @inheritdoc */
initialize(value, model, options = {}) {
const v = super.initialize(value, model, options);
const v = super.initialize(value ?? [], model, options);
return () => {
const data = v.map(entry => (typeof entry === 'function' ? entry() : entry));
return this.options.prune ? data.filter(d => !!d) : data;

View file

@ -0,0 +1,32 @@
export default class IterableTypedObjectField extends foundry.data.fields.TypedObjectField {
constructor(model, options = { collectionClass: foundry.utils.Collection }, context = {}) {
super(new foundry.data.fields.EmbeddedDataField(model), options, context);
this.#elementClass = model;
}
#elementClass;
/** Initializes an object with an iterator. This modifies the prototype instead of */
initialize(values) {
const object = Object.create(IterableObjectPrototype);
for (const [key, value] of Object.entries(values)) {
object[key] = new this.#elementClass(value);
}
return object;
}
}
/**
* The prototype of an iterable object.
* This allows the functionality of a class but also allows foundry.utils.getType() to return "Object" instead of "Unknown".
*/
const IterableObjectPrototype = {
[Symbol.iterator]: function* () {
for (const value of Object.values(this)) {
yield value;
}
},
map: function (func) {
return Array.from(this, func);
}
};

View file

@ -1,128 +0,0 @@
/**
* A subclass of ObjectField that represents a mapping of keys to the provided DataField type.
*
* @param {DataField} model The class of DataField which should be embedded in this field.
* @param {MappingFieldOptions} [options={}] Options which configure the behavior of the field.
* @property {string[]} [initialKeys] Keys that will be created if no data is provided.
* @property {MappingFieldInitialValueBuilder} [initialValue] Function to calculate the initial value for a key.
* @property {boolean} [initialKeysOnly=false] Should the keys in the initialized data be limited to the keys provided
* by `options.initialKeys`?
*/
export default class MappingField extends foundry.data.fields.ObjectField {
constructor(model, options) {
if (!(model instanceof foundry.data.fields.DataField)) {
throw new Error('MappingField must have a DataField as its contained element');
}
super(options);
/**
* The embedded DataField definition which is contained in this field.
* @type {DataField}
*/
this.model = model;
model.parent = this;
}
/* -------------------------------------------- */
/** @inheritDoc */
static get _defaults() {
return foundry.utils.mergeObject(super._defaults, {
initialKeys: null,
initialValue: null,
initialKeysOnly: false
});
}
/* -------------------------------------------- */
/** @inheritDoc */
_cleanType(value, options) {
Object.entries(value).forEach(([k, v]) => {
if (k.startsWith('-=')) return;
value[k] = this.model.clean(v, options);
});
return value;
}
/* -------------------------------------------- */
/** @inheritDoc */
getInitialValue(data) {
let keys = this.initialKeys;
const initial = super.getInitialValue(data);
if (!keys || !foundry.utils.isEmpty(initial)) return initial;
if (!(keys instanceof Array)) keys = Object.keys(keys);
for (const key of keys) initial[key] = this._getInitialValueForKey(key);
return initial;
}
/* -------------------------------------------- */
/**
* Get the initial value for the provided key.
* @param {string} key Key within the object being built.
* @param {object} [object] Any existing mapping data.
* @returns {*} Initial value based on provided field type.
*/
_getInitialValueForKey(key, object) {
const initial = this.model.getInitialValue();
return this.initialValue?.(key, initial, object) ?? initial;
}
/* -------------------------------------------- */
/** @override */
_validateType(value, options = {}) {
if (foundry.utils.getType(value) !== 'Object') throw new Error('must be an Object');
const errors = this._validateValues(value, options);
if (!foundry.utils.isEmpty(errors)) {
const failure = new foundry.data.validation.DataModelValidationFailure();
failure.elements = Object.entries(errors).map(([id, failure]) => ({ id, failure }));
throw failure.asError();
}
}
/* -------------------------------------------- */
/**
* Validate each value of the object.
* @param {object} value The object to validate.
* @param {object} options Validation options.
* @returns {Record<string, Error>} An object of value-specific errors by key.
*/
_validateValues(value, options) {
const errors = {};
for (const [k, v] of Object.entries(value)) {
if (k.startsWith('-=')) continue;
const error = this.model.validate(v, options);
if (error) errors[k] = error;
}
return errors;
}
/* -------------------------------------------- */
/** @override */
initialize(value, model, options = {}) {
if (!value) return value;
const obj = {};
const initialKeys = this.initialKeys instanceof Array ? this.initialKeys : Object.keys(this.initialKeys ?? {});
const keys = this.initialKeysOnly ? initialKeys : Object.keys(value);
for (const key of keys) {
const data = value[key] ?? this._getInitialValueForKey(key, value);
obj[key] = this.model.initialize(data, model, options);
}
return obj;
}
/* -------------------------------------------- */
/** @inheritDoc */
_getField(path) {
if (path.length === 0) return this;
else if (path.length === 1) return this.model;
path.shift();
return this.model._getField(path);
}
}