Merged with main

This commit is contained in:
WBHarry 2025-07-25 01:14:41 +02:00
commit f0a809266d
70 changed files with 1245 additions and 881 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,24 @@ 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
},
@ -286,65 +285,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 })];
}
/**
@ -356,20 +298,11 @@ export default function DHApplicationMixin(Base) {
{
name: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
callback: async target => {
condition: target => {
const doc = getDocFromElement(target);
const appId = this.element.id;
doc.apps[appId] = this;
const app = await doc.sheet.render({ force: true });
app.addEventListener(
'close',
() => {
delete doc.apps[appId];
},
{ once: true }
);
return;
}
return !doc.hasOwnProperty('systemPath') || doc.inCollection;
},
callback: target => getDocFromElement(target).sheet.render({ force: true })
}
];
@ -437,7 +370,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;
@ -463,61 +396,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;
}
/**
@ -527,12 +428,6 @@ export default function DHApplicationMixin(Base) {
static async #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 });
}
/**
@ -541,34 +436,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)
});
}
/**
@ -577,13 +448,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);
}
@ -593,29 +457,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

@ -47,13 +47,13 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
element.addEventListener('click', this.onToggleTargets)
);
html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(this, event, data.message))
element.addEventListener('click', event => this.abilityUseButton(event, data.message))
);
html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton(this, event, data.message))
element.addEventListener('click', event => this.actionUseButton(event, data.message))
);
html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(this, event, data.message))
element.addEventListener('click', event => this.rerollEvent(event, data.message))
);
};
@ -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,7 +254,7 @@ 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) {
@ -271,6 +271,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
async rerollEvent(event, message) {
event.stopPropagation();
if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
@ -287,6 +288,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
game.system.api.dice[
message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll'
];
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
await game.messages.get(message._id).update({

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');
}