Refactor/actions v2 (#402)

* Action Refactor Part #1

* Fixed Weapon/Armor features. Fixed Feature actions

* f

* Action Refactor Part #2

* Fixes

* Remove ActionsField from Companion

* Fixes

* Localization fix

* BaseDataItem hasActions false

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
This commit is contained in:
Dapoulp 2025-07-25 01:10:49 +02:00 committed by GitHub
parent 80744381f5
commit 0632a8c6bb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
52 changed files with 988 additions and 743 deletions

View file

@ -13,7 +13,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
this.action =
config.data.attack?._id == config.source.action
? config.data.attack
: this.item.system.actions.find(a => a._id === config.source.action);
: this.item.system.actions.get(config.source.action);
}
}
@ -68,19 +68,19 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
}));
if (this.config.costs?.length) {
const updatedCosts = this.action.calcCosts(this.config.costs);
const updatedCosts = game.system.api.fields.ActionFields.CostField.calcCosts.call(this.action, this.config.costs);
context.costs = updatedCosts.map(x => ({
...x,
label: x.keyIsID
? this.action.parent.parent.name
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
}));
context.canRoll = this.action.hasCost(updatedCosts);
context.canRoll = game.system.api.fields.ActionFields.CostField.hasCost.call(this.action, updatedCosts);
this.config.data.scale = this.config.costs[0].total;
}
if (this.config.uses?.max) {
context.uses = this.action.calcUses(this.config.uses);
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
context.uses = game.system.api.fields.ActionFields.UsesField.calcUses.call(this.action, this.config.uses);
context.canRoll = context.canRoll && game.system.api.fields.ActionFields.UsesField.hasUses.call(this.action, context.uses);
}
if (this.roll) {
context.roll = this.roll;

View file

@ -105,7 +105,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
context.hasBaseDamage = !!this.action.parent.attack;
context.getRealIndex = this.getRealIndex.bind(this);
context.getEffectDetails = this.getEffectDetails.bind(this);
context.costOptions = this.getCostOptions();
context.disableOption = this.disableOption.bind(this);
@ -147,11 +146,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
return filtered;
}
getRealIndex(index) {
const data = this.action.toObject(false);
return data.damage.parts.find(d => d.base) ? index - 1 : index;
}
getEffectDetails(id) {
return this.action.item.effects.get(id);
}
@ -175,19 +169,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
static async updateForm(event, _, formData) {
const submitData = this._prepareSubmitData(event, formData),
data = foundry.utils.mergeObject(this.action.toObject(), submitData),
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
let newActions;
if (Array.isArray(container)) {
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject());
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
} else newActions = data;
const updates = await this.action.parent.parent.update({ [`system.${this.action.systemPath}`]: newActions });
if (!updates) return;
this.action = Array.isArray(container)
? foundry.utils.getProperty(updates.system, this.action.systemPath)[this.action.index]
: foundry.utils.getProperty(updates.system, this.action.systemPath);
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
this.action = await this.action.update(data);
this.render();
}
@ -210,8 +193,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
static addDamage(event) {
if (!this.action.damage.parts) return;
const data = this.action.toObject();
data.damage.parts.push({});
const data = this.action.toObject(),
part = {};
if(this.action.actor?.isNPC) part.value = { multiplier: 'flat' };
data.damage.parts.push(part);
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
}

View file

@ -82,7 +82,6 @@ export default function DHApplicationMixin(Base) {
deleteDoc: DHSheetV2.#deleteDoc,
toChat: DHSheetV2.#toChat,
useItem: DHSheetV2.#useItem,
useAction: DHSheetV2.#useAction,
toggleEffect: DHSheetV2.#toggleEffect,
toggleExtended: DHSheetV2.#toggleExtended
},
@ -271,65 +270,8 @@ export default function DHApplicationMixin(Base) {
*/
static #getActionContextOptions() {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const getAction = target => {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
return attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
};
const options = [
{
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition:
this.document instanceof foundry.documents.Actor ||
(this.document instanceof foundry.documents.Item && this.document.parent),
callback: (target, event) => getAction(target).use(event)
},
{
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: target => getAction(target).toChat(this.document.id)
},
{
name: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
callback: target => new DHActionConfig(getAction(target)).render({ force: true })
},
{
name: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
condition: target => {
const { actionId } = target.closest('[data-action-id]').dataset;
const { attack } = this.document.system;
return attack?.id !== actionId;
},
callback: async target => {
const action = getAction(target);
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: action.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
name: action.name
})
});
if (!confirmed) return;
return this.document.update({
'system.actions': this.document.system.actions.filter(a => a.id !== action.id)
});
}
}
].map(option => ({
...option,
icon: `<i class="${option.icon}"></i>`
}));
return options;
const options = [];
return [...options, ...this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true })];
}
/**
@ -341,6 +283,10 @@ export default function DHApplicationMixin(Base) {
{
name: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
condition: target => {
const doc = getDocFromElement(target);
return !doc.hasOwnProperty('systemPath') || doc.inCollection
},
callback: target => getDocFromElement(target).sheet.render({ force: true })
}
];
@ -409,7 +355,7 @@ export default function DHApplicationMixin(Base) {
? getDocFromElement(extensibleElement)
: this.document.system.attack?.id === actionId
? this.document.system.attack
: this.document.system.actions?.find(a => a.id === actionId);
: this.document.system.actions?.get(actionId);
if (!doc) return;
const description = doc.system?.description ?? doc.description;
@ -435,61 +381,29 @@ export default function DHApplicationMixin(Base) {
static async #createDoc(event, target) {
const { documentClass, type, inVault, disabled } = target.dataset;
const parentIsItem = this.document.documentName === 'Item';
const parent = parentIsItem && documentClass === 'Item' ? null : this.document;
const parent =
parentIsItem && documentClass === 'Item'
? type === 'action'
? this.document.system
: null
: this.document;
if (type === 'action') {
const { type: actionType } =
(await foundry.applications.api.DialogV2.input({
window: { title: 'Select Action Type' },
classes: ['daggerheart', 'dh-style'],
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/actionTypes/actionType.hbs',
{
types: CONFIG.DH.ACTIONS.actionTypes,
itemName: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction')
}
),
ok: {
label: game.i18n.format('DOCUMENT.Create', {
type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
})
}
})) ?? {};
if (!actionType) return;
const cls = game.system.api.models.actions.actionsTypes[actionType];
const action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
...cls.getSourceConfig(this.document)
},
{
parent: this.document
}
);
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render({
force: true
const cls =
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
const data = {
name: cls.defaultName({ type, parent }),
type
};
if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true;
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
if (parentIsItem && type === 'feature') {
await this.document.update({
'system.features': this.document.system.toObject().features.concat(doc.uuid)
});
return action;
} else {
const cls = getDocumentClass(documentClass);
const data = {
name: cls.defaultName({ type, parent }),
type
};
if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true;
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
if (parentIsItem && type === 'feature') {
await this.document.update({
'system.features': this.document.system.toObject().features.concat(doc.uuid)
});
}
return doc;
}
return doc;
}
/**
@ -499,12 +413,6 @@ export default function DHApplicationMixin(Base) {
static #editDoc(_event, target) {
const doc = getDocFromElement(target);
if (doc) return doc.sheet.render({ force: true });
// TODO: REDO this
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
new DHActionConfig(action).render({ force: true });
}
/**
@ -513,34 +421,10 @@ export default function DHApplicationMixin(Base) {
*/
static async #deleteDoc(event, target) {
const doc = getDocFromElement(target);
if (doc) {
if (event.shiftKey) return doc.delete();
else return await doc.deleteDialog();
}
// TODO: REDO this
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
if (attack?.id === actionId) return;
const action = actions.find(a => a.id === actionId);
if (!event.shiftKey) {
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: action.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
});
if (!confirmed) return;
}
return await this.document.update({
'system.actions': actions.filter(a => a.id !== action.id)
});
}
/**
@ -549,13 +433,6 @@ export default function DHApplicationMixin(Base) {
*/
static async #toChat(_event, target) {
let doc = getDocFromElement(target);
// TODO: REDO this
if (!doc) {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
}
return doc.toChat(this.document.id);
}
@ -565,29 +442,9 @@ export default function DHApplicationMixin(Base) {
*/
static async #useItem(event, target) {
let doc = getDocFromElement(target);
// TODO: REDO this
if (!doc) {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
if (this.document instanceof foundry.documents.Item && !this.document.parent) return;
}
await doc.use(event);
}
/**
* Use a item
* @type {ApplicationClickAction}
*/
static async #useAction(event, target) {
const doc = getDocFromElement(target);
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = doc.system;
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
await action.use(event, doc);
}
/**
* Toggle a ActiveEffect
* @type {ApplicationClickAction}

View file

@ -20,7 +20,6 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
submitOnChange: true
},
actions: {
removeAction: DHBaseItemSheet.#removeAction,
addFeature: DHBaseItemSheet.#addFeature,
deleteFeature: DHBaseItemSheet.#deleteFeature,
addResource: DHBaseItemSheet.#addResource,
@ -144,33 +143,6 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
/* Application Clicks Actions */
/* -------------------------------------------- */
/**
* Remove an action from the item.
* @type {ApplicationClickAction}
*/
static async #removeAction(event, button) {
event.stopPropagation();
const actionIndex = button.closest('[data-index]').dataset.index;
const action = this.document.system.actions[actionIndex];
if (!event.shiftKey) {
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: action.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
});
if (!confirmed) return;
}
await this.document.update({
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
});
}
/**
* Add a new feature to the item, prompting the user for its type.
* @type {ApplicationClickAction}

View file

@ -77,7 +77,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
? actor.system.attack
: item.system.attack?._id === actionId
? item.system.attack
: item?.system?.actions?.find(a => a._id === actionId);
: item?.system?.actions?.get(actionId);
return action;
}
@ -254,8 +254,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
const action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
const actor = game.actors.get(message.system.source.actor);
await actor.useAction(action);
}
await actor.use(action);
};
async actionUseButton(event, message) {
const { moveIndex, actionIndex } = event.currentTarget.dataset;

View file

@ -30,7 +30,7 @@ export default class DhHotbar extends foundry.applications.ui.Hotbar {
});
}
const action = item.system.actions.find(x => x.id === actionId);
const action = item.system.actions.get(actionId);
if (!action) {
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
}