} 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);
+ }
+}
+
+/* -------------------------------------------- */
+
+/**
+ * Field that stores actions.
+ */
+export class ActionsField extends MappingField {
+ constructor(options) {
+ super(new ActionField(), options);
+ }
+
+ /* -------------------------------------------- */
+
+ /** @inheritDoc */
+ initialize(value, model, options) {
+ const actions = Object.values(super.initialize(value, model, options));
+ return new ActionCollection(model, actions);
+ }
+}
+
+/* -------------------------------------------- */
+
+/**
+ * Field that stores action data and swaps class based on action type.
+ */
+export class ActionField extends foundry.data.fields.ObjectField {
getModel(value) {
return game.system.api.models.actions.actionsTypes[value.type] ?? game.system.api.models.actions.actionsTypes.attack;
}
@@ -35,3 +246,141 @@ export default class ActionField extends foundry.data.fields.ObjectField {
if (cls) cls.migrateDataSafe(fieldData);
}
}
+
+/* -------------------------------------------- */
+
+export function ActionMixin(Base) {
+ class Action extends Base {
+ static metadata = Object.freeze({
+ name: "Action",
+ label: "DAGGERHEART.GENERAL.Action.single",
+ sheetClass: DHActionConfig
+ });
+
+ static _sheets = new Map();
+
+ static get documentName() {
+ return this.metadata.name;
+ }
+
+ get documentName() {
+ return this.constructor.documentName;
+ }
+
+ static defaultName() {
+ return this.documentName;
+ }
+
+ get relativeUUID() {
+ return `.Item.${this.item.id}.Action.${this.id}`;
+ }
+
+ get uuid() {
+ return `${this.item.uuid}.${this.documentName}.${this.id}`;
+ }
+
+ get sheet() {
+ if(!this.constructor._sheets.has(this.uuid)) {
+ const sheet = new this.constructor.metadata.sheetClass(this);
+ this.constructor._sheets.set(this.uuid, sheet);
+ }
+ return this.constructor._sheets.get(this.uuid);
+ }
+
+ get inCollection() {
+ return foundry.utils.getProperty(this.parent, this.systemPath) instanceof Collection;
+ }
+
+ static async create(data, operation={}) {
+ const { parent, renderSheet } = operation;
+ let { type } = data;
+ if(!type || !game.system.api.models.actions.actionsTypes[type]) {
+ ({ type } =
+ (await foundry.applications.api.DialogV2.input({
+ window: { title: 'Select Action Type' },
+ content: await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/actionTypes/actionType.hbs',
+ { types: CONFIG.DH.ACTIONS.actionTypes }
+ ),
+ ok: {
+ label: game.i18n.format('DOCUMENT.Create', {
+ type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
+ })
+ }
+ })) ?? {});
+ }
+ if (!type) return;
+
+ const cls = game.system.api.models.actions.actionsTypes[type];
+ const action = new cls(
+ {
+ type,
+ ...cls.getSourceConfig(parent)
+ },
+ {
+ parent
+ }
+ );
+ const created = await parent.parent.update({ [`system.actions.${action.id}`]: action.toObject() });
+ const newAction = parent.actions.get(action.id);
+ if(!newAction) return null;
+ if( renderSheet ) newAction.sheet.render({ force: true });
+ return newAction;
+ }
+
+ async update(updates, options={}) {
+ const path = this.inCollection ? `system.${this.systemPath}.${this.id}` : `system.${this.systemPath}`,
+ result = await this.item.update({[path]: updates}, options);
+ return this.inCollection ? foundry.utils.getProperty(result, `system.${this.systemPath}`).get(this.id) : foundry.utils.getProperty(result, `system.${this.systemPath}`);
+ }
+
+ 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 });
+ this.constructor._sheets.get(this.uuid)?.close();
+ }
+
+ async deleteDialog() {
+ const confirmed = await foundry.applications.api.DialogV2.confirm({
+ window: {
+ title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
+ type: game.i18n.localize(`DAGGERHEART.GENERAL.Action.single`),
+ name: this.name
+ })
+ },
+ content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
+ name: this.name
+ })
+ });
+ if (!confirmed) return;
+ return this.delete();
+ }
+
+ async toChat(origin) {
+ const cls = getDocumentClass('ChatMessage');
+ const systemData = {
+ title: game.i18n.localize('DAGGERHEART.CONFIG.ActionType.action'),
+ origin: origin,
+ img: this.img,
+ name: this.name,
+ description: this.description,
+ actions: []
+ };
+ const msg = {
+ type: 'abilityUse',
+ user: game.user.id,
+ system: systemData,
+ content: await foundry.applications.handlebars.renderTemplate(
+ 'systems/daggerheart/templates/ui/chat/ability-use.hbs',
+ systemData
+ )
+ };
+
+ cls.create(msg);
+ }
+ }
+
+ return Action;
+}
diff --git a/module/data/item/armor.mjs b/module/data/item/armor.mjs
index 8522c8fc..85026121 100644
--- a/module/data/item/armor.mjs
+++ b/module/data/item/armor.mjs
@@ -1,5 +1,5 @@
import AttachableItem from './attachableItem.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionField } from '../fields/actionField.mjs';
import { armorFeatures } from '../../config/itemConfig.mjs';
import { actionsTypes } from '../action/_module.mjs';
diff --git a/module/data/item/consumable.mjs b/module/data/item/consumable.mjs
index 3e70f97a..8c3c0dae 100644
--- a/module/data/item/consumable.mjs
+++ b/module/data/item/consumable.mjs
@@ -1,5 +1,5 @@
import BaseDataItem from './base.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionField } from '../fields/actionField.mjs';
export default class DHConsumable extends BaseDataItem {
/** @inheritDoc */
diff --git a/module/data/item/domainCard.mjs b/module/data/item/domainCard.mjs
index df60b9d1..1ecc167f 100644
--- a/module/data/item/domainCard.mjs
+++ b/module/data/item/domainCard.mjs
@@ -1,5 +1,5 @@
import BaseDataItem from './base.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionField } from '../fields/actionField.mjs';
export default class DHDomainCard extends BaseDataItem {
/** @inheritDoc */
diff --git a/module/data/item/feature.mjs b/module/data/item/feature.mjs
index 62b955e9..3f7fdad8 100644
--- a/module/data/item/feature.mjs
+++ b/module/data/item/feature.mjs
@@ -1,5 +1,5 @@
import BaseDataItem from './base.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionField } from '../fields/actionField.mjs';
export default class DHFeature extends BaseDataItem {
/** @inheritDoc */
diff --git a/module/data/item/miscellaneous.mjs b/module/data/item/miscellaneous.mjs
index cad07f48..bdf608c6 100644
--- a/module/data/item/miscellaneous.mjs
+++ b/module/data/item/miscellaneous.mjs
@@ -1,5 +1,5 @@
import BaseDataItem from './base.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionField } from '../fields/actionField.mjs';
export default class DHMiscellaneous extends BaseDataItem {
/** @inheritDoc */
diff --git a/module/data/item/weapon.mjs b/module/data/item/weapon.mjs
index 0d0c7f76..fd26975b 100644
--- a/module/data/item/weapon.mjs
+++ b/module/data/item/weapon.mjs
@@ -1,6 +1,6 @@
import AttachableItem from './attachableItem.mjs';
import { actionsTypes } from '../action/_module.mjs';
-import ActionField from '../fields/actionField.mjs';
+import { ActionsField, ActionField } from '../fields/actionField.mjs';
export default class DHWeapon extends AttachableItem {
/** @inheritDoc */
@@ -65,7 +65,8 @@ export default class DHWeapon extends AttachableItem {
}
}
}),
- actions: new fields.ArrayField(new ActionField())
+ actions: new ActionsField()
+ // actions: new fields.ArrayField(new ActionField())
};
}
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index 490f53eb..f0b374f7 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -23,6 +23,23 @@ export default class DhpActor extends Actor {
return this.system.metadata.isNPC;
}
+ /** @inheritDoc */
+ getEmbeddedDocument(embeddedName, id, options) {
+ let doc;
+ switch ( embeddedName ) {
+ case "Action":
+ doc = this.system.actions?.get(id);
+ if(!doc && this.system.attack?.id === id) doc = this.system.attack;
+ break;
+ default:
+ return super.getEmbeddedDocument(embeddedName, id, options);
+ }
+ if ( options?.strict && !doc ) {
+ throw new Error(`The key ${id} does not exist in the ${embeddedName} Collection`);
+ }
+ return doc;
+ }
+
async _preCreate(data, options, user) {
if ((await super._preCreate(data, options, user)) === false) return false;
diff --git a/module/documents/item.mjs b/module/documents/item.mjs
index 6c3732db..ba1c73ae 100644
--- a/module/documents/item.mjs
+++ b/module/documents/item.mjs
@@ -9,6 +9,23 @@ export default class DHItem extends foundry.documents.Item {
for (const action of this.system.actions ?? []) action.prepareData();
}
+ /** @inheritDoc */
+ getEmbeddedDocument(embeddedName, id, options) {
+ let doc;
+ switch ( embeddedName ) {
+ case "Action":
+ doc = this.system.actions?.get(id);
+ if(!doc && this.system.attack?.id === id) doc = this.system.attack;
+ break;
+ default:
+ return super.getEmbeddedDocument(embeddedName, id, options);
+ }
+ if ( options?.strict && !doc ) {
+ throw new Error(`The key ${id} does not exist in the ${embeddedName} Collection`);
+ }
+ return doc;
+ }
+
/**
* @inheritdoc
* @param {object} options - Options which modify the getRollData method.
diff --git a/module/documents/tooltipManager.mjs b/module/documents/tooltipManager.mjs
index 8e1c729e..bf6083ab 100644
--- a/module/documents/tooltipManager.mjs
+++ b/module/documents/tooltipManager.mjs
@@ -9,7 +9,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const actionId = splitValues.length > 1 ? splitValues[1] : null;
const baseItem = await foundry.utils.fromUuid(itemUuid);
- const item = actionId ? baseItem.system.actions.find(x => x.id === actionId) : baseItem;
+ const item = actionId ? baseItem.system.actions.get(actionId) : baseItem;
if (item) {
const type = actionId ? 'action' : item.type;
const description = await TextEditor.enrichHTML(item.system.description);
diff --git a/templates/sheets/global/partials/inventory-item-V2.hbs b/templates/sheets/global/partials/inventory-item-V2.hbs
index b58f6f44..8d7fb1a8 100644
--- a/templates/sheets/global/partials/inventory-item-V2.hbs
+++ b/templates/sheets/global/partials/inventory-item-V2.hbs
@@ -249,7 +249,7 @@ Parameters:
{{#if (and showActions (eq item.type 'feature'))}}
{{#each item.system.actions as | action |}}
-