mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-11 19:25:21 +01:00
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:
parent
80744381f5
commit
0632a8c6bb
52 changed files with 988 additions and 743 deletions
|
|
@ -3,6 +3,7 @@ import * as applications from './module/applications/_module.mjs';
|
||||||
import * as models from './module/data/_module.mjs';
|
import * as models from './module/data/_module.mjs';
|
||||||
import * as documents from './module/documents/_module.mjs';
|
import * as documents from './module/documents/_module.mjs';
|
||||||
import * as dice from './module/dice/_module.mjs';
|
import * as dice from './module/dice/_module.mjs';
|
||||||
|
import * as fields from './module/data/fields/_module.mjs'
|
||||||
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
|
||||||
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
|
||||||
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
|
||||||
|
|
@ -27,7 +28,8 @@ Hooks.once('init', () => {
|
||||||
applications,
|
applications,
|
||||||
models,
|
models,
|
||||||
documents,
|
documents,
|
||||||
dice
|
dice,
|
||||||
|
fields
|
||||||
};
|
};
|
||||||
|
|
||||||
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
CONFIG.TextEditor.enrichers.push(...enricherConfig);
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
|
||||||
this.action =
|
this.action =
|
||||||
config.data.attack?._id == config.source.action
|
config.data.attack?._id == config.source.action
|
||||||
? config.data.attack
|
? 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) {
|
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 => ({
|
context.costs = updatedCosts.map(x => ({
|
||||||
...x,
|
...x,
|
||||||
label: x.keyIsID
|
label: x.keyIsID
|
||||||
? this.action.parent.parent.name
|
? this.action.parent.parent.name
|
||||||
: game.i18n.localize(CONFIG.DH.GENERAL.abilityCosts[x.key].label)
|
: 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;
|
this.config.data.scale = this.config.costs[0].total;
|
||||||
}
|
}
|
||||||
if (this.config.uses?.max) {
|
if (this.config.uses?.max) {
|
||||||
context.uses = this.action.calcUses(this.config.uses);
|
context.uses = game.system.api.fields.ActionFields.UsesField.calcUses.call(this.action, this.config.uses);
|
||||||
context.canRoll = context.canRoll && this.action.hasUses(context.uses);
|
context.canRoll = context.canRoll && game.system.api.fields.ActionFields.UsesField.hasUses.call(this.action, context.uses);
|
||||||
}
|
}
|
||||||
if (this.roll) {
|
if (this.roll) {
|
||||||
context.roll = this.roll;
|
context.roll = this.roll;
|
||||||
|
|
|
||||||
|
|
@ -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.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')
|
if (this.action.damage?.hasOwnProperty('includeBase') && this.action.type === 'attack')
|
||||||
context.hasBaseDamage = !!this.action.parent.attack;
|
context.hasBaseDamage = !!this.action.parent.attack;
|
||||||
context.getRealIndex = this.getRealIndex.bind(this);
|
|
||||||
context.getEffectDetails = this.getEffectDetails.bind(this);
|
context.getEffectDetails = this.getEffectDetails.bind(this);
|
||||||
context.costOptions = this.getCostOptions();
|
context.costOptions = this.getCostOptions();
|
||||||
context.disableOption = this.disableOption.bind(this);
|
context.disableOption = this.disableOption.bind(this);
|
||||||
|
|
@ -147,11 +146,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
return filtered;
|
return filtered;
|
||||||
}
|
}
|
||||||
|
|
||||||
getRealIndex(index) {
|
|
||||||
const data = this.action.toObject(false);
|
|
||||||
return data.damage.parts.find(d => d.base) ? index - 1 : index;
|
|
||||||
}
|
|
||||||
|
|
||||||
getEffectDetails(id) {
|
getEffectDetails(id) {
|
||||||
return this.action.item.effects.get(id);
|
return this.action.item.effects.get(id);
|
||||||
}
|
}
|
||||||
|
|
@ -175,19 +169,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
|
|
||||||
static async updateForm(event, _, formData) {
|
static async updateForm(event, _, formData) {
|
||||||
const submitData = this._prepareSubmitData(event, formData),
|
const submitData = this._prepareSubmitData(event, formData),
|
||||||
data = foundry.utils.mergeObject(this.action.toObject(), submitData),
|
data = foundry.utils.mergeObject(this.action.toObject(), submitData);
|
||||||
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
|
this.action = await this.action.update(data);
|
||||||
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);
|
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -210,8 +193,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
|
|
||||||
static addDamage(event) {
|
static addDamage(event) {
|
||||||
if (!this.action.damage.parts) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject(),
|
||||||
data.damage.parts.push({});
|
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) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,7 +82,6 @@ export default function DHApplicationMixin(Base) {
|
||||||
deleteDoc: DHSheetV2.#deleteDoc,
|
deleteDoc: DHSheetV2.#deleteDoc,
|
||||||
toChat: DHSheetV2.#toChat,
|
toChat: DHSheetV2.#toChat,
|
||||||
useItem: DHSheetV2.#useItem,
|
useItem: DHSheetV2.#useItem,
|
||||||
useAction: DHSheetV2.#useAction,
|
|
||||||
toggleEffect: DHSheetV2.#toggleEffect,
|
toggleEffect: DHSheetV2.#toggleEffect,
|
||||||
toggleExtended: DHSheetV2.#toggleExtended
|
toggleExtended: DHSheetV2.#toggleExtended
|
||||||
},
|
},
|
||||||
|
|
@ -271,65 +270,8 @@ export default function DHApplicationMixin(Base) {
|
||||||
*/
|
*/
|
||||||
static #getActionContextOptions() {
|
static #getActionContextOptions() {
|
||||||
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
|
||||||
const getAction = target => {
|
const options = [];
|
||||||
const { actionId } = target.closest('[data-action-id]').dataset;
|
return [...options, ...this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true })];
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -341,6 +283,10 @@ export default function DHApplicationMixin(Base) {
|
||||||
{
|
{
|
||||||
name: 'CONTROLS.CommonEdit',
|
name: 'CONTROLS.CommonEdit',
|
||||||
icon: 'fa-solid fa-pen-to-square',
|
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 })
|
callback: target => getDocFromElement(target).sheet.render({ force: true })
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
@ -409,7 +355,7 @@ export default function DHApplicationMixin(Base) {
|
||||||
? getDocFromElement(extensibleElement)
|
? getDocFromElement(extensibleElement)
|
||||||
: this.document.system.attack?.id === actionId
|
: this.document.system.attack?.id === actionId
|
||||||
? this.document.system.attack
|
? this.document.system.attack
|
||||||
: this.document.system.actions?.find(a => a.id === actionId);
|
: this.document.system.actions?.get(actionId);
|
||||||
if (!doc) return;
|
if (!doc) return;
|
||||||
|
|
||||||
const description = doc.system?.description ?? doc.description;
|
const description = doc.system?.description ?? doc.description;
|
||||||
|
|
@ -435,61 +381,29 @@ export default function DHApplicationMixin(Base) {
|
||||||
static async #createDoc(event, target) {
|
static async #createDoc(event, target) {
|
||||||
const { documentClass, type, inVault, disabled } = target.dataset;
|
const { documentClass, type, inVault, disabled } = target.dataset;
|
||||||
const parentIsItem = this.document.documentName === 'Item';
|
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 cls =
|
||||||
const { type: actionType } =
|
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
|
||||||
(await foundry.applications.api.DialogV2.input({
|
const data = {
|
||||||
window: { title: 'Select Action Type' },
|
name: cls.defaultName({ type, parent }),
|
||||||
classes: ['daggerheart', 'dh-style'],
|
type
|
||||||
content: await foundry.applications.handlebars.renderTemplate(
|
};
|
||||||
'systems/daggerheart/templates/actionTypes/actionType.hbs',
|
if (inVault) data['system.inVault'] = true;
|
||||||
{
|
if (disabled) data.disabled = true;
|
||||||
types: CONFIG.DH.ACTIONS.actionTypes,
|
|
||||||
itemName: game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction')
|
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
|
||||||
}
|
if (parentIsItem && type === 'feature') {
|
||||||
),
|
await this.document.update({
|
||||||
ok: {
|
'system.features': this.document.system.toObject().features.concat(doc.uuid)
|
||||||
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
|
|
||||||
});
|
});
|
||||||
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) {
|
static #editDoc(_event, target) {
|
||||||
const doc = getDocFromElement(target);
|
const doc = getDocFromElement(target);
|
||||||
if (doc) return doc.sheet.render({ force: true });
|
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) {
|
static async #deleteDoc(event, target) {
|
||||||
const doc = getDocFromElement(target);
|
const doc = getDocFromElement(target);
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
if (event.shiftKey) return doc.delete();
|
if (event.shiftKey) return doc.delete();
|
||||||
else return await doc.deleteDialog();
|
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) {
|
static async #toChat(_event, target) {
|
||||||
let doc = getDocFromElement(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);
|
return doc.toChat(this.document.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -565,29 +442,9 @@ export default function DHApplicationMixin(Base) {
|
||||||
*/
|
*/
|
||||||
static async #useItem(event, target) {
|
static async #useItem(event, target) {
|
||||||
let doc = getDocFromElement(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);
|
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
|
* Toggle a ActiveEffect
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,6 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
submitOnChange: true
|
submitOnChange: true
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
removeAction: DHBaseItemSheet.#removeAction,
|
|
||||||
addFeature: DHBaseItemSheet.#addFeature,
|
addFeature: DHBaseItemSheet.#addFeature,
|
||||||
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
deleteFeature: DHBaseItemSheet.#deleteFeature,
|
||||||
addResource: DHBaseItemSheet.#addResource,
|
addResource: DHBaseItemSheet.#addResource,
|
||||||
|
|
@ -144,33 +143,6 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
||||||
/* Application Clicks Actions */
|
/* 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.
|
* Add a new feature to the item, prompting the user for its type.
|
||||||
* @type {ApplicationClickAction}
|
* @type {ApplicationClickAction}
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
|
||||||
? actor.system.attack
|
? actor.system.attack
|
||||||
: item.system.attack?._id === actionId
|
: item.system.attack?._id === actionId
|
||||||
? item.system.attack
|
? item.system.attack
|
||||||
: item?.system?.actions?.find(a => a._id === actionId);
|
: item?.system?.actions?.get(actionId);
|
||||||
return action;
|
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 action = message.system.actions[Number.parseInt(event.currentTarget.dataset.index)];
|
||||||
const actor = game.actors.get(message.system.source.actor);
|
const actor = game.actors.get(message.system.source.actor);
|
||||||
await actor.useAction(action);
|
await actor.use(action);
|
||||||
}
|
};
|
||||||
|
|
||||||
async actionUseButton(event, message) {
|
async actionUseButton(event, message) {
|
||||||
const { moveIndex, actionIndex } = event.currentTarget.dataset;
|
const { moveIndex, actionIndex } = event.currentTarget.dataset;
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
if (!action) {
|
||||||
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
|
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
export * as ActionDice from './actionDice.mjs';
|
|
||||||
import AttackAction from './attackAction.mjs';
|
import AttackAction from './attackAction.mjs';
|
||||||
import BaseAction from './baseAction.mjs';
|
import BaseAction from './baseAction.mjs';
|
||||||
import BeastformAction from './beastformAction.mjs';
|
import BeastformAction from './beastformAction.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import { DHDamageData } from './actionDice.mjs';
|
import { DHDamageData } from '../fields/action/damageField.mjs';
|
||||||
import DHDamageAction from './damageAction.mjs';
|
import DHDamageAction from './damageAction.mjs';
|
||||||
|
|
||||||
export default class DHAttackAction extends DHDamageAction {
|
export default class DHAttackAction extends DHDamageAction {
|
||||||
static extraSchemas = [...super.extraSchemas, ...['roll', 'save']];
|
static extraSchemas = [...super.extraSchemas, 'roll', 'save'];
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
return parent.type === 'weapon' ? 'attack' : 'spellcast';
|
return parent.parent.type === 'weapon' ? 'attack' : 'spellcast';
|
||||||
}
|
}
|
||||||
|
|
||||||
get chatTemplate() {
|
get chatTemplate() {
|
||||||
|
|
@ -46,8 +46,4 @@ export default class DHAttackAction extends DHDamageAction {
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// get modifiers() {
|
|
||||||
// return [];
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { DHActionDiceData, DHActionRollData, DHDamageData, DHDamageField, DHResourceData } from './actionDice.mjs';
|
|
||||||
import DhpActor from '../../documents/actor.mjs';
|
import DhpActor from '../../documents/actor.mjs';
|
||||||
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
|
||||||
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -16,12 +16,12 @@ const fields = foundry.data.fields;
|
||||||
- Summon Action create method
|
- Summon Action create method
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DHBaseAction extends foundry.abstract.DataModel {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static extraSchemas = [];
|
static extraSchemas = ['cost', 'uses', 'range'];
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
const schemaFields = {
|
||||||
_id: new fields.DocumentIdField(),
|
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||||
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
systemPath: new fields.StringField({ required: true, initial: 'actions' }),
|
||||||
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
type: new fields.StringField({ initial: undefined, readonly: true, required: true }),
|
||||||
name: new fields.StringField({ initial: undefined }),
|
name: new fields.StringField({ initial: undefined }),
|
||||||
|
|
@ -32,87 +32,25 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
choices: CONFIG.DH.ITEM.actionTypes,
|
choices: CONFIG.DH.ITEM.actionTypes,
|
||||||
initial: 'action',
|
initial: 'action',
|
||||||
nullable: true
|
nullable: true
|
||||||
}),
|
})
|
||||||
cost: new fields.ArrayField(
|
|
||||||
new fields.SchemaField({
|
|
||||||
key: new fields.StringField({
|
|
||||||
nullable: false,
|
|
||||||
required: true,
|
|
||||||
initial: 'hope'
|
|
||||||
}),
|
|
||||||
keyIsID: new fields.BooleanField(),
|
|
||||||
value: new fields.NumberField({ nullable: true, initial: 1 }),
|
|
||||||
scalable: new fields.BooleanField({ initial: false }),
|
|
||||||
step: new fields.NumberField({ nullable: true, initial: null })
|
|
||||||
})
|
|
||||||
),
|
|
||||||
uses: new fields.SchemaField({
|
|
||||||
value: new fields.NumberField({ nullable: true, initial: null }),
|
|
||||||
max: new fields.NumberField({ nullable: true, initial: null }),
|
|
||||||
recovery: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.refreshTypes,
|
|
||||||
initial: null,
|
|
||||||
nullable: true
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
range: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.range,
|
|
||||||
required: false,
|
|
||||||
blank: true
|
|
||||||
// initial: null
|
|
||||||
}),
|
|
||||||
...this.defineExtraSchema()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.extraSchemas.forEach(s => {
|
||||||
|
let clsField;
|
||||||
|
if(clsField = this.getActionField(s)) schemaFields[s] = new clsField();
|
||||||
|
});
|
||||||
|
|
||||||
|
return schemaFields;
|
||||||
}
|
}
|
||||||
|
|
||||||
static defineExtraSchema() {
|
static getActionField(name) {
|
||||||
const extraFields = {
|
const field = game.system.api.fields.ActionFields[`${name.capitalize()}Field`];
|
||||||
damage: new DHDamageField(),
|
return fields.DataField.isPrototypeOf(field) && field;
|
||||||
roll: new fields.EmbeddedDataField(DHActionRollData),
|
|
||||||
save: new fields.SchemaField({
|
|
||||||
trait: new fields.StringField({
|
|
||||||
nullable: true,
|
|
||||||
initial: null,
|
|
||||||
choices: CONFIG.DH.ACTOR.abilities
|
|
||||||
}),
|
|
||||||
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
|
|
||||||
damageMod: new fields.StringField({
|
|
||||||
initial: CONFIG.DH.ACTIONS.damageOnSave.none.id,
|
|
||||||
choices: CONFIG.DH.ACTIONS.damageOnSave
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
target: new fields.SchemaField({
|
|
||||||
type: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.ACTIONS.targetTypes,
|
|
||||||
initial: CONFIG.DH.ACTIONS.targetTypes.any.id,
|
|
||||||
nullable: true,
|
|
||||||
initial: null
|
|
||||||
}),
|
|
||||||
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
|
|
||||||
}),
|
|
||||||
effects: new fields.ArrayField( // ActiveEffect
|
|
||||||
new fields.SchemaField({
|
|
||||||
_id: new fields.DocumentIdField(),
|
|
||||||
onSave: new fields.BooleanField({ initial: false })
|
|
||||||
})
|
|
||||||
),
|
|
||||||
healing: new fields.EmbeddedDataField(DHResourceData),
|
|
||||||
beastform: new fields.SchemaField({
|
|
||||||
tierAccess: new fields.SchemaField({
|
|
||||||
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
},
|
|
||||||
extraSchemas = {};
|
|
||||||
|
|
||||||
this.extraSchemas.forEach(s => (extraSchemas[s] = extraFields[s]));
|
|
||||||
return extraSchemas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareData() {}
|
prepareData() {
|
||||||
|
this.name = this.name || game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[this.type].name);
|
||||||
get index() {
|
this.img = this.img ?? this.parent?.parent?.img;
|
||||||
return foundry.utils.getProperty(this.parent, this.systemPath).indexOf(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get id() {
|
get id() {
|
||||||
|
|
@ -141,22 +79,21 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
|
|
||||||
static getSourceConfig(parent) {
|
static getSourceConfig(parent) {
|
||||||
const updateSource = {};
|
const updateSource = {};
|
||||||
updateSource.img ??= parent?.img ?? parent?.system?.img;
|
if (parent?.parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
||||||
if (parent?.type === 'weapon' && this === game.system.api.models.actions.actionsTypes.attack) {
|
|
||||||
updateSource['damage'] = { includeBase: true };
|
updateSource['damage'] = { includeBase: true };
|
||||||
updateSource['range'] = parent?.system?.attack?.range;
|
updateSource['range'] = parent?.attack?.range;
|
||||||
updateSource['roll'] = {
|
updateSource['roll'] = {
|
||||||
useDefault: true
|
useDefault: true
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
if (parent?.system?.trait) {
|
if (parent?.trait) {
|
||||||
updateSource['roll'] = {
|
updateSource['roll'] = {
|
||||||
type: this.getRollType(parent),
|
type: this.getRollType(parent),
|
||||||
trait: parent.system.trait
|
trait: parent.trait
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (parent?.system?.range) {
|
if (parent?.range) {
|
||||||
updateSource['range'] = parent?.system?.range;
|
updateSource['range'] = parent?.range;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return updateSource;
|
return updateSource;
|
||||||
|
|
@ -180,38 +117,14 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
async use(event, ...args) {
|
async use(event, ...args) {
|
||||||
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
|
||||||
|
|
||||||
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
|
let config = this.prepareConfig(event);
|
||||||
// Prepare base Config
|
for(let i = 0; i < this.constructor.extraSchemas.length; i++) {
|
||||||
const initConfig = this.initActionConfig(event);
|
let clsField = this.constructor.getActionField(this.constructor.extraSchemas[i]);
|
||||||
|
if(clsField?.prepareConfig) {
|
||||||
// Prepare Targets
|
const keep = clsField.prepareConfig.call(this, config);
|
||||||
const targetConfig = this.prepareTarget();
|
if(config.isFastForward && !keep) return;
|
||||||
if (isFastForward && !targetConfig) return ui.notifications.warn('Too many targets selected for that actions.');
|
}
|
||||||
|
}
|
||||||
// Prepare Range
|
|
||||||
const rangeConfig = this.prepareRange();
|
|
||||||
|
|
||||||
// Prepare Costs
|
|
||||||
const costsConfig = this.prepareCost();
|
|
||||||
if (isFastForward && !(await this.hasCost(costsConfig)))
|
|
||||||
return ui.notifications.warn("You don't have the resources to use that action.");
|
|
||||||
|
|
||||||
// Prepare Uses
|
|
||||||
const usesConfig = this.prepareUse();
|
|
||||||
if (isFastForward && !this.hasUses(usesConfig))
|
|
||||||
return ui.notifications.warn("That action doesn't have remaining uses.");
|
|
||||||
|
|
||||||
// Prepare Roll Data
|
|
||||||
const actorData = this.getRollData();
|
|
||||||
|
|
||||||
let config = {
|
|
||||||
...initConfig,
|
|
||||||
targets: targetConfig,
|
|
||||||
range: rangeConfig,
|
|
||||||
costs: costsConfig,
|
|
||||||
uses: usesConfig,
|
|
||||||
data: actorData
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
if (Hooks.call(`${CONFIG.DH.id}.preUseAction`, this, config) === false) return;
|
||||||
|
|
||||||
|
|
@ -243,7 +156,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
initActionConfig(event) {
|
prepareConfig(event) {
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
title: this.item.name,
|
title: this.item.name,
|
||||||
|
|
@ -257,7 +170,9 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
hasHealing: !!this.healing,
|
hasHealing: !!this.healing,
|
||||||
hasEffect: !!this.effects?.length,
|
hasEffect: !!this.effects?.length,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
selectedRollMode: game.settings.get('core', 'rollMode')
|
selectedRollMode: game.settings.get('core', 'rollMode'),
|
||||||
|
isFastForward: event.shiftKey,
|
||||||
|
data: this.getRollData()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -265,36 +180,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses);
|
return !config.event.shiftKey && !this.hasRoll && (config.costs?.length || config.uses);
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareCost() {
|
|
||||||
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
|
||||||
return this.calcCosts(costs);
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareUse() {
|
|
||||||
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
|
|
||||||
if (uses && !uses.value) uses.value = 0;
|
|
||||||
return uses;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareTarget() {
|
|
||||||
if (!this.target?.type) return [];
|
|
||||||
let targets;
|
|
||||||
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
|
||||||
targets = this.constructor.formatTarget(this.actor.token ?? this.actor.prototypeToken);
|
|
||||||
targets = Array.from(game.user.targets);
|
|
||||||
if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) {
|
|
||||||
targets = targets.filter(t => this.isTargetFriendly(t));
|
|
||||||
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
|
||||||
}
|
|
||||||
targets = targets.map(t => this.constructor.formatTarget(t));
|
|
||||||
return targets;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareRange() {
|
|
||||||
const range = this.range ?? null;
|
|
||||||
return range;
|
|
||||||
}
|
|
||||||
|
|
||||||
prepareRoll() {
|
prepareRoll() {
|
||||||
const roll = {
|
const roll = {
|
||||||
modifiers: this.modifiers,
|
modifiers: this.modifiers,
|
||||||
|
|
@ -366,108 +251,6 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
/* SAVE */
|
/* SAVE */
|
||||||
|
|
||||||
/* COST */
|
|
||||||
|
|
||||||
getRealCosts(costs) {
|
|
||||||
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
|
||||||
return realCosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
calcCosts(costs) {
|
|
||||||
return costs.map(c => {
|
|
||||||
c.scale = c.scale ?? 1;
|
|
||||||
c.step = c.step ?? 1;
|
|
||||||
c.total = c.value * c.scale * c.step;
|
|
||||||
c.enabled = c.hasOwnProperty('enabled') ? c.enabled : true;
|
|
||||||
return c;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
async getResources(costs) {
|
|
||||||
const actorResources = this.actor.system.resources;
|
|
||||||
const itemResources = {};
|
|
||||||
for (var itemResource of costs) {
|
|
||||||
if (itemResource.keyIsID) {
|
|
||||||
itemResources[itemResource.key] = {
|
|
||||||
value: this.parent.resource.value ?? 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...actorResources,
|
|
||||||
...itemResources
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/* COST */
|
|
||||||
async hasCost(costs) {
|
|
||||||
const realCosts = this.getRealCosts(costs),
|
|
||||||
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
|
||||||
if (hasFearCost > -1) {
|
|
||||||
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
|
||||||
if (
|
|
||||||
!game.user.isGM ||
|
|
||||||
fearCost.total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
|
||||||
)
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
|
|
||||||
const resources = await this.getResources(realCosts);
|
|
||||||
return realCosts.reduce(
|
|
||||||
(a, c) =>
|
|
||||||
a && resources[c.key].isReversed
|
|
||||||
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
|
|
||||||
: resources[c.key]?.value >= (c.total ?? c.value),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* USES */
|
|
||||||
calcUses(uses) {
|
|
||||||
if (!uses) return null;
|
|
||||||
return {
|
|
||||||
...uses,
|
|
||||||
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
hasUses(uses) {
|
|
||||||
if (!uses) return true;
|
|
||||||
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* TARGET */
|
|
||||||
isTargetFriendly(target) {
|
|
||||||
const actorDisposition = this.actor.token
|
|
||||||
? this.actor.token.disposition
|
|
||||||
: this.actor.prototypeToken.disposition,
|
|
||||||
targetDisposition = target.document.disposition;
|
|
||||||
return (
|
|
||||||
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.friendly.id &&
|
|
||||||
actorDisposition === targetDisposition) ||
|
|
||||||
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.hostile.id &&
|
|
||||||
actorDisposition + targetDisposition === 0)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static formatTarget(actor) {
|
|
||||||
return {
|
|
||||||
id: actor.id,
|
|
||||||
actorId: actor.actor.uuid,
|
|
||||||
name: actor.actor.name,
|
|
||||||
img: actor.actor.img,
|
|
||||||
difficulty: actor.actor.system.difficulty,
|
|
||||||
evasion: actor.actor.system.evasion
|
|
||||||
};
|
|
||||||
}
|
|
||||||
/* TARGET */
|
|
||||||
|
|
||||||
/* RANGE */
|
|
||||||
|
|
||||||
/* RANGE */
|
|
||||||
|
|
||||||
/* EFFECTS */
|
/* EFFECTS */
|
||||||
async applyEffects(event, data, targets) {
|
async applyEffects(event, data, targets) {
|
||||||
targets ??= data.system.targets;
|
targets ??= data.system.targets;
|
||||||
|
|
@ -552,27 +335,4 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 = new cls({
|
|
||||||
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.toObject());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import BeastformDialog from '../../applications/dialogs/beastformDialog.mjs';
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DhBeastformAction extends DHBaseAction {
|
export default class DhBeastformAction extends DHBaseAction {
|
||||||
static extraSchemas = ['beastform'];
|
static extraSchemas = [...super.extraSchemas, 'beastform'];
|
||||||
|
|
||||||
async use(event, ...args) {
|
async use(event, ...args) {
|
||||||
const beastformConfig = this.prepareBeastformConfig();
|
const beastformConfig = this.prepareBeastformConfig();
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { setsEqual } from '../../helpers/utils.mjs';
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHDamageAction extends DHBaseAction {
|
export default class DHDamageAction extends DHBaseAction {
|
||||||
static extraSchemas = ['damage', 'target', 'effects'];
|
static extraSchemas = [...super.extraSchemas, 'damage', 'target', 'effects'];
|
||||||
|
|
||||||
getFormulaValue(part, data) {
|
getFormulaValue(part, data) {
|
||||||
let formulaValue = part.value;
|
let formulaValue = part.value;
|
||||||
|
|
@ -49,7 +49,7 @@ export default class DHDamageAction extends DHBaseAction {
|
||||||
const config = {
|
const config = {
|
||||||
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
|
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
|
||||||
roll: formulas,
|
roll: formulas,
|
||||||
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
|
targets: systemData.targets?.filter(t => t.hit) ?? data.targets,
|
||||||
hasSave: this.hasSave,
|
hasSave: this.hasSave,
|
||||||
isCritical: systemData.roll?.isCritical ?? false,
|
isCritical: systemData.roll?.isCritical ?? false,
|
||||||
source: systemData.source,
|
source: systemData.source,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHEffectAction extends DHBaseAction {
|
export default class DHEffectAction extends DHBaseAction {
|
||||||
static extraSchemas = ['effects', 'target'];
|
static extraSchemas = [...super.extraSchemas, 'effects', 'target'];
|
||||||
|
|
||||||
async trigger(event, data) {
|
async trigger(event, data) {
|
||||||
if(this.effects.length) {
|
if(this.effects.length) {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import DHBaseAction from './baseAction.mjs';
|
import DHBaseAction from './baseAction.mjs';
|
||||||
|
|
||||||
export default class DHHealingAction extends DHBaseAction {
|
export default class DHHealingAction extends DHBaseAction {
|
||||||
static extraSchemas = ['target', 'effects', 'healing', 'roll'];
|
static extraSchemas = [...super.extraSchemas, 'target', 'effects', 'healing', 'roll'];
|
||||||
|
|
||||||
static getRollType(parent) {
|
static getRollType(parent) {
|
||||||
return 'spellcast';
|
return 'spellcast';
|
||||||
|
|
|
||||||
0
module/data/action/subDatas/rollData.mjs
Normal file
0
module/data/action/subDatas/rollData.mjs
Normal file
|
|
@ -1,5 +1,5 @@
|
||||||
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
import DHAdversarySettings from '../../applications/sheets-configs/adversary-settings.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DhCharacter extends BaseDataActor {
|
export default class DhCharacter extends BaseDataActor {
|
||||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
|
||||||
|
|
@ -334,6 +334,7 @@ export default class DhCharacter extends BaseDataActor {
|
||||||
return !primaryWeaponEquipped && !secondaryWeaponEquipped
|
return !primaryWeaponEquipped && !secondaryWeaponEquipped
|
||||||
? {
|
? {
|
||||||
...this.attack,
|
...this.attack,
|
||||||
|
uuid: this.attack.uuid,
|
||||||
id: this.attack.id,
|
id: this.attack.id,
|
||||||
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
|
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
|
||||||
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
|
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import BaseDataActor from './base.mjs';
|
import BaseDataActor from './base.mjs';
|
||||||
import DhLevelData from '../levelData.mjs';
|
import DhLevelData from '../levelData.mjs';
|
||||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField, ActionsField } from '../fields/actionField.mjs';
|
||||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||||
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
import DHCompanionSettings from '../../applications/sheets-configs/companion-settings.mjs';
|
||||||
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
import { resourceField, bonusField } from '../fields/actorField.mjs';
|
||||||
|
|
@ -76,7 +76,6 @@ export default class DhCompanion extends BaseDataActor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
actions: new fields.ArrayField(new ActionField()),
|
|
||||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||||
bonuses: new fields.SchemaField({
|
bonuses: new fields.SchemaField({
|
||||||
damage: new fields.SchemaField({
|
damage: new fields.SchemaField({
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
export { ActionCollection } from './actionField.mjs';
|
||||||
export { default as FormulaField } from './formulaField.mjs';
|
export { default as FormulaField } from './formulaField.mjs';
|
||||||
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
||||||
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
|
export { default as ForeignDocumentUUIDArrayField } from './foreignDocumentUUIDArrayField.mjs';
|
||||||
|
export { default as MappingField } from './mappingField.mjs';
|
||||||
|
export * as ActionFields from './action/_module.mjs';
|
||||||
|
|
|
||||||
10
module/data/fields/action/_module.mjs
Normal file
10
module/data/fields/action/_module.mjs
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
export { default as CostField } from './costField.mjs';
|
||||||
|
export { default as UsesField } from './usesField.mjs';
|
||||||
|
export { default as RangeField } from './rangeField.mjs';
|
||||||
|
export { default as TargetField } from './targetField.mjs';
|
||||||
|
export { default as EffectsField } from './effectsField.mjs';
|
||||||
|
export { default as SaveField } from './saveField.mjs';
|
||||||
|
export { default as BeastformField } from './beastformField.mjs';
|
||||||
|
export { default as DamageField } from './damageField.mjs';
|
||||||
|
export { default as HealingField } from './healingField.mjs';
|
||||||
|
export { default as RollField } from './rollField.mjs';
|
||||||
12
module/data/fields/action/beastformField.mjs
Normal file
12
module/data/fields/action/beastformField.mjs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class BeastformField extends fields.SchemaField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const beastformFields = {
|
||||||
|
tierAccess: new fields.SchemaField({
|
||||||
|
exact: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||||
|
})
|
||||||
|
};
|
||||||
|
super(beastformFields, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
82
module/data/fields/action/costField.mjs
Normal file
82
module/data/fields/action/costField.mjs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class CostField extends fields.ArrayField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const element = new fields.SchemaField({
|
||||||
|
key: new fields.StringField({
|
||||||
|
nullable: false,
|
||||||
|
required: true,
|
||||||
|
initial: 'hope'
|
||||||
|
}),
|
||||||
|
keyIsID: new fields.BooleanField(),
|
||||||
|
value: new fields.NumberField({ nullable: true, initial: 1 }),
|
||||||
|
scalable: new fields.BooleanField({ initial: false }),
|
||||||
|
step: new fields.NumberField({ nullable: true, initial: null })
|
||||||
|
});
|
||||||
|
super(element, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static prepareConfig(config) {
|
||||||
|
const costs = this.cost?.length ? foundry.utils.deepClone(this.cost) : [];
|
||||||
|
config.costs = CostField.calcCosts.call(this, costs);
|
||||||
|
const hasCost = CostField.hasCost.call(this, config.costs);
|
||||||
|
if(config.isFastForward && !hasCost)
|
||||||
|
return ui.notifications.warn("You don't have the resources to use that action.");
|
||||||
|
return hasCost;
|
||||||
|
}
|
||||||
|
|
||||||
|
static calcCosts(costs) {
|
||||||
|
return costs.map(c => {
|
||||||
|
c.scale = c.scale ?? 1;
|
||||||
|
c.step = c.step ?? 1;
|
||||||
|
c.total = c.value * c.scale * c.step;
|
||||||
|
c.enabled = c.hasOwnProperty('enabled') ? c.enabled : true;
|
||||||
|
return c;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasCost(costs) {
|
||||||
|
const realCosts = CostField.getRealCosts.call(this, costs),
|
||||||
|
hasFearCost = realCosts.findIndex(c => c.key === 'fear');
|
||||||
|
if (hasFearCost > -1) {
|
||||||
|
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||||
|
if (
|
||||||
|
!game.user.isGM ||
|
||||||
|
fearCost.total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||||
|
)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
|
||||||
|
const resources = CostField.getResources.call(this, realCosts);
|
||||||
|
return realCosts.reduce(
|
||||||
|
(a, c) =>
|
||||||
|
a && resources[c.key].isReversed
|
||||||
|
? resources[c.key].value + (c.total ?? c.value) <= resources[c.key].max
|
||||||
|
: resources[c.key]?.value >= (c.total ?? c.value),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static getResources(costs) {
|
||||||
|
const actorResources = this.actor.system.resources;
|
||||||
|
const itemResources = {};
|
||||||
|
for (var itemResource of costs) {
|
||||||
|
if (itemResource.keyIsID) {
|
||||||
|
itemResources[itemResource.key] = {
|
||||||
|
value: this.parent.resource.value ?? 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
...actorResources,
|
||||||
|
...itemResources
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static getRealCosts(costs) {
|
||||||
|
const realCosts = costs?.length ? costs.filter(c => c.enabled) : [];
|
||||||
|
return realCosts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,62 +1,20 @@
|
||||||
import FormulaField from '../fields/formulaField.mjs';
|
import FormulaField from "../formulaField.mjs";
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
/* Roll Field */
|
export default class DamageField extends fields.SchemaField {
|
||||||
|
constructor(options, context = {}) {
|
||||||
export class DHActionRollData extends foundry.abstract.DataModel {
|
const damageFields = {
|
||||||
/** @override */
|
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
||||||
static defineSchema() {
|
includeBase: new fields.BooleanField({
|
||||||
return {
|
initial: false,
|
||||||
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
|
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
||||||
trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities }),
|
})
|
||||||
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
|
|
||||||
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
|
|
||||||
advState: new fields.StringField({ choices: CONFIG.DH.ACTIONS.advandtageState, initial: 'neutral' }),
|
|
||||||
diceRolling: new fields.SchemaField({
|
|
||||||
multiplier: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.diceSetNumbers,
|
|
||||||
initial: 'prof',
|
|
||||||
label: 'Dice Number'
|
|
||||||
}),
|
|
||||||
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
|
|
||||||
dice: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.diceTypes,
|
|
||||||
initial: 'd6',
|
|
||||||
label: 'Dice Type'
|
|
||||||
}),
|
|
||||||
compare: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.ACTIONS.diceCompare,
|
|
||||||
initial: 'above',
|
|
||||||
label: 'Should be'
|
|
||||||
}),
|
|
||||||
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' })
|
|
||||||
}),
|
|
||||||
useDefault: new fields.BooleanField({ initial: false })
|
|
||||||
};
|
};
|
||||||
}
|
super(damageFields, options, context);
|
||||||
|
|
||||||
getFormula() {
|
|
||||||
if (!this.type) return;
|
|
||||||
let formula = '';
|
|
||||||
switch (this.type) {
|
|
||||||
case 'diceSet':
|
|
||||||
const multiplier =
|
|
||||||
this.diceRolling.multiplier === 'flat'
|
|
||||||
? this.diceRolling.flatMultiplier
|
|
||||||
: `@${this.diceRolling.multiplier}`;
|
|
||||||
formula = `${multiplier}${this.diceRolling.dice}cs${CONFIG.DH.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
formula = '';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return formula;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Damage & Healing Field */
|
|
||||||
|
|
||||||
export class DHActionDiceData extends foundry.abstract.DataModel {
|
export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
|
|
@ -83,19 +41,6 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHDamageField extends fields.SchemaField {
|
|
||||||
constructor(options, context = {}) {
|
|
||||||
const damageFields = {
|
|
||||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
|
|
||||||
includeBase: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.includeBase.label'
|
|
||||||
})
|
|
||||||
};
|
|
||||||
super(damageFields, options, context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DHResourceData extends foundry.abstract.DataModel {
|
export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
|
|
@ -136,4 +81,4 @@ export class DHDamageData extends DHResourceData {
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
11
module/data/fields/action/effectsField.mjs
Normal file
11
module/data/fields/action/effectsField.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class EffectsField extends fields.ArrayField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const element = new fields.SchemaField({
|
||||||
|
_id: new fields.DocumentIdField(),
|
||||||
|
onSave: new fields.BooleanField({ initial: false })
|
||||||
|
});
|
||||||
|
super(element, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
9
module/data/fields/action/healingField.mjs
Normal file
9
module/data/fields/action/healingField.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { DHDamageData } from "./damageField.mjs";
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class HealingField extends fields.EmbeddedDataField {
|
||||||
|
constructor(options, context = {}) {
|
||||||
|
super(DHDamageData, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
16
module/data/fields/action/rangeField.mjs
Normal file
16
module/data/fields/action/rangeField.mjs
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class RangeField extends fields.StringField {
|
||||||
|
constructor(context={}) {
|
||||||
|
const options = {
|
||||||
|
choices: CONFIG.DH.GENERAL.range,
|
||||||
|
required: false,
|
||||||
|
blank: true
|
||||||
|
};
|
||||||
|
super(options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static prepareConfig(config) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
58
module/data/fields/action/rollField.mjs
Normal file
58
module/data/fields/action/rollField.mjs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export class DHActionRollData extends foundry.abstract.DataModel {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
type: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.GENERAL.rollTypes }),
|
||||||
|
trait: new fields.StringField({ nullable: true, initial: null, choices: CONFIG.DH.ACTOR.abilities }),
|
||||||
|
difficulty: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 }),
|
||||||
|
bonus: new fields.NumberField({ nullable: true, initial: null, integer: true }),
|
||||||
|
advState: new fields.StringField({ choices: CONFIG.DH.ACTIONS.advandtageState, initial: 'neutral' }),
|
||||||
|
diceRolling: new fields.SchemaField({
|
||||||
|
multiplier: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.diceSetNumbers,
|
||||||
|
initial: 'prof',
|
||||||
|
label: 'Dice Number'
|
||||||
|
}),
|
||||||
|
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
|
||||||
|
dice: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.diceTypes,
|
||||||
|
initial: 'd6',
|
||||||
|
label: 'Dice Type'
|
||||||
|
}),
|
||||||
|
compare: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ACTIONS.diceCompare,
|
||||||
|
initial: 'above',
|
||||||
|
label: 'Should be'
|
||||||
|
}),
|
||||||
|
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' })
|
||||||
|
}),
|
||||||
|
useDefault: new fields.BooleanField({ initial: false })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormula() {
|
||||||
|
if (!this.type) return;
|
||||||
|
let formula = '';
|
||||||
|
switch (this.type) {
|
||||||
|
case 'diceSet':
|
||||||
|
const multiplier =
|
||||||
|
this.diceRolling.multiplier === 'flat'
|
||||||
|
? this.diceRolling.flatMultiplier
|
||||||
|
: `@${this.diceRolling.multiplier}`;
|
||||||
|
formula = `${multiplier}${this.diceRolling.dice}cs${CONFIG.DH.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
formula = '';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return formula;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class RollField extends fields.EmbeddedDataField {
|
||||||
|
constructor(options, context = {}) {
|
||||||
|
super(DHActionRollData, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
module/data/fields/action/saveField.mjs
Normal file
19
module/data/fields/action/saveField.mjs
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class SaveField extends fields.SchemaField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const saveFields = {
|
||||||
|
trait: new fields.StringField({
|
||||||
|
nullable: true,
|
||||||
|
initial: null,
|
||||||
|
choices: CONFIG.DH.ACTOR.abilities
|
||||||
|
}),
|
||||||
|
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
|
||||||
|
damageMod: new fields.StringField({
|
||||||
|
initial: CONFIG.DH.ACTIONS.damageOnSave.none.id,
|
||||||
|
choices: CONFIG.DH.ACTIONS.damageOnSave
|
||||||
|
})
|
||||||
|
};
|
||||||
|
super(saveFields, options, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
62
module/data/fields/action/targetField.mjs
Normal file
62
module/data/fields/action/targetField.mjs
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class TargetField extends fields.SchemaField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const targetFields = {
|
||||||
|
type: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.ACTIONS.targetTypes,
|
||||||
|
initial: CONFIG.DH.ACTIONS.targetTypes.any.id,
|
||||||
|
nullable: true,
|
||||||
|
initial: null
|
||||||
|
}),
|
||||||
|
amount: new fields.NumberField({ nullable: true, initial: null, integer: true, min: 0 })
|
||||||
|
};
|
||||||
|
super(targetFields, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static prepareConfig(config) {
|
||||||
|
if (!this.target?.type) return [];
|
||||||
|
let targets;
|
||||||
|
if (this.target?.type === CONFIG.DH.ACTIONS.targetTypes.self.id)
|
||||||
|
targets = TargetField.formatTarget.call(this, this.actor.token ?? this.actor.prototypeToken);
|
||||||
|
targets = Array.from(game.user.targets);
|
||||||
|
if (this.target.type !== CONFIG.DH.ACTIONS.targetTypes.any.id) {
|
||||||
|
targets = targets.filter(t => TargetField.isTargetFriendly.call(this, t));
|
||||||
|
if (this.target.amount && targets.length > this.target.amount) targets = [];
|
||||||
|
}
|
||||||
|
config.targets = targets.map(t => TargetField.formatTarget.call(this, t));
|
||||||
|
const hasTargets = TargetField.checkTargets.call(this, this.target.amount, config.targets);
|
||||||
|
if(config.isFastForward && !hasTargets)
|
||||||
|
return ui.notifications.warn('Too many targets selected for that actions.');
|
||||||
|
return hasTargets;
|
||||||
|
}
|
||||||
|
|
||||||
|
static checkTargets(amount, targets) {
|
||||||
|
return true
|
||||||
|
// return !amount || (targets.length > amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
static isTargetFriendly(target) {
|
||||||
|
const actorDisposition = this.actor.token
|
||||||
|
? this.actor.token.disposition
|
||||||
|
: this.actor.prototypeToken.disposition,
|
||||||
|
targetDisposition = target.document.disposition;
|
||||||
|
return (
|
||||||
|
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.friendly.id &&
|
||||||
|
actorDisposition === targetDisposition) ||
|
||||||
|
(this.target.type === CONFIG.DH.ACTIONS.targetTypes.hostile.id &&
|
||||||
|
actorDisposition + targetDisposition === 0)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static formatTarget(actor) {
|
||||||
|
return {
|
||||||
|
id: actor.id,
|
||||||
|
actorId: actor.actor.uuid,
|
||||||
|
name: actor.actor.name,
|
||||||
|
img: actor.actor.img,
|
||||||
|
difficulty: actor.actor.system.difficulty,
|
||||||
|
evasion: actor.actor.system.evasion
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
39
module/data/fields/action/usesField.mjs
Normal file
39
module/data/fields/action/usesField.mjs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class UsesField extends fields.SchemaField {
|
||||||
|
constructor(options={}, context={}) {
|
||||||
|
const usesFields = {
|
||||||
|
value: new fields.NumberField({ nullable: true, initial: null }),
|
||||||
|
max: new fields.NumberField({ nullable: true, initial: null }),
|
||||||
|
recovery: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.refreshTypes,
|
||||||
|
initial: null,
|
||||||
|
nullable: true
|
||||||
|
})
|
||||||
|
};
|
||||||
|
super(usesFields, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
static prepareConfig(config) {
|
||||||
|
const uses = this.uses?.max ? foundry.utils.deepClone(this.uses) : null;
|
||||||
|
if (uses && !uses.value) uses.value = 0;
|
||||||
|
config.uses = uses;
|
||||||
|
const hasUses = UsesField.hasUses.call(this, config.uses);
|
||||||
|
if(config.isFastForward && !hasUses)
|
||||||
|
return ui.notifications.warn("That action doesn't have remaining uses.");
|
||||||
|
return hasUses;
|
||||||
|
}
|
||||||
|
|
||||||
|
static calcUses(uses) {
|
||||||
|
if (!uses) return null;
|
||||||
|
return {
|
||||||
|
...uses,
|
||||||
|
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static hasUses(uses) {
|
||||||
|
if (!uses) return true;
|
||||||
|
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,4 +1,85 @@
|
||||||
export default class ActionField extends foundry.data.fields.ObjectField {
|
import DHActionConfig from "../../applications/sheets-configs/action-config.mjs";
|
||||||
|
import MappingField from "./mappingField.mjs";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specialized collection type for stored actions.
|
||||||
|
* @param {DataModel} model The parent DataModel to which this ActionCollection belongs.
|
||||||
|
* @param {Action[]} entries The actions to store.
|
||||||
|
*/
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Properties */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent DataModel to which this ActionCollection belongs.
|
||||||
|
* @type {DataModel}
|
||||||
|
*/
|
||||||
|
#model;
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Methods */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test the given predicate against every entry in the Collection.
|
||||||
|
* @param {function(*, number, ActionCollection): boolean} predicate The predicate.
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
every(predicate) {
|
||||||
|
return this.reduce((pass, v, i) => pass && predicate(v, i, this), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the ActionCollection to an array of simple objects.
|
||||||
|
* @param {boolean} [source=true] Draw data for contained Documents from the underlying data source?
|
||||||
|
* @returns {object[]} The extracted array of primitive objects.
|
||||||
|
*/
|
||||||
|
toObject(source=true) {
|
||||||
|
return this.map(doc => doc.toObject(source));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
getModel(value) {
|
||||||
return game.system.api.models.actions.actionsTypes[value.type] ?? game.system.api.models.actions.actionsTypes.attack;
|
return game.system.api.models.actions.actionsTypes[value.type] ?? game.system.api.models.actions.actionsTypes.attack;
|
||||||
}
|
}
|
||||||
|
|
@ -35,3 +116,141 @@ export default class ActionField extends foundry.data.fields.ObjectField {
|
||||||
if (cls) cls.migrateDataSafe(fieldData);
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
128
module/data/fields/mappingField.mjs
Normal file
128
module/data/fields/mappingField.mjs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import AttachableItem from './attachableItem.mjs';
|
import AttachableItem from './attachableItem.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionsField } from '../fields/actionField.mjs';
|
||||||
import { armorFeatures } from '../../config/itemConfig.mjs';
|
import { armorFeatures } from '../../config/itemConfig.mjs';
|
||||||
import { actionsTypes } from '../action/_module.mjs';
|
|
||||||
|
|
||||||
export default class DHArmor extends AttachableItem {
|
export default class DHArmor extends AttachableItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -10,7 +9,8 @@ export default class DHArmor extends AttachableItem {
|
||||||
label: 'TYPES.Item.armor',
|
label: 'TYPES.Item.armor',
|
||||||
type: 'armor',
|
type: 'armor',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isInventoryItem: true
|
isInventoryItem: true,
|
||||||
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,8 +39,7 @@ export default class DHArmor extends AttachableItem {
|
||||||
baseThresholds: new fields.SchemaField({
|
baseThresholds: new fields.SchemaField({
|
||||||
major: new fields.NumberField({ integer: true, initial: 0 }),
|
major: new fields.NumberField({ integer: true, initial: 0 }),
|
||||||
severe: new fields.NumberField({ integer: true, initial: 0 })
|
severe: new fields.NumberField({ integer: true, initial: 0 })
|
||||||
}),
|
})
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -65,7 +64,10 @@ export default class DHArmor extends AttachableItem {
|
||||||
actionIds.push(...feature.actionIds);
|
actionIds.push(...feature.actionIds);
|
||||||
}
|
}
|
||||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||||
changes.system.actions = this.actions.filter(x => !actionIds.includes(x._id));
|
changes.system.actions = actionIds.reduce((acc, id) => {
|
||||||
|
acc[`-=${id}`] = null;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
for (var feature of added) {
|
for (var feature of added) {
|
||||||
const featureData = armorFeatures[feature.value];
|
const featureData = armorFeatures[feature.value];
|
||||||
|
|
@ -79,17 +81,38 @@ export default class DHArmor extends AttachableItem {
|
||||||
]);
|
]);
|
||||||
feature.effectIds = embeddedItems.map(x => x.id);
|
feature.effectIds = embeddedItems.map(x => x.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const newActions = {};
|
||||||
if (featureData.actions?.length > 0) {
|
if (featureData.actions?.length > 0) {
|
||||||
const newActions = featureData.actions.map(action => {
|
for (let action of featureData.actions) {
|
||||||
const cls = actionsTypes[action.type];
|
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||||
return new cls(
|
'ActiveEffect',
|
||||||
{ ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) },
|
(action.effects ?? []).map(effect => ({
|
||||||
|
...effect,
|
||||||
|
transfer: false,
|
||||||
|
name: game.i18n.localize(effect.name),
|
||||||
|
description: game.i18n.localize(effect.description)
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
|
||||||
|
const cls = game.system.api.models.actions.actionsTypes[action.type];
|
||||||
|
const actionId = foundry.utils.randomID();
|
||||||
|
newActions[actionId] = new cls(
|
||||||
|
{
|
||||||
|
...cls.getSourceConfig(this),
|
||||||
|
...action,
|
||||||
|
_id: actionId,
|
||||||
|
name: game.i18n.localize(action.name),
|
||||||
|
description: game.i18n.localize(action.description),
|
||||||
|
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||||
|
},
|
||||||
{ parent: this }
|
{ parent: this }
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
changes.system.actions = [...this.actions, ...newActions];
|
|
||||||
feature.actionIds = newActions.map(x => x._id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changes.system.actions = newActions;
|
||||||
|
feature.actionIds = Object.keys(newActions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@
|
||||||
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
* @property {boolean} isInventoryItem- Indicates whether items of this type is a Inventory Item
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { ActionsField } from "../fields/actionField.mjs";
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
|
|
@ -21,7 +23,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
hasDescription: false,
|
hasDescription: false,
|
||||||
hasResource: false,
|
hasResource: false,
|
||||||
isQuantifiable: false,
|
isQuantifiable: false,
|
||||||
isInventoryItem: false
|
isInventoryItem: false,
|
||||||
|
hasActions: false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +72,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
if (this.metadata.isQuantifiable)
|
if (this.metadata.isQuantifiable)
|
||||||
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
schema.quantity = new fields.NumberField({ integer: true, initial: 1, min: 0, required: true });
|
||||||
|
|
||||||
|
if (this.metadata.hasActions)
|
||||||
|
schema.actions = new ActionsField()
|
||||||
|
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHConsumable extends BaseDataItem {
|
export default class DHConsumable extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -9,7 +9,8 @@ export default class DHConsumable extends BaseDataItem {
|
||||||
type: 'consumable',
|
type: 'consumable',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
isQuantifiable: true,
|
||||||
isInventoryItem: true
|
isInventoryItem: true,
|
||||||
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -18,8 +19,7 @@ export default class DHConsumable extends BaseDataItem {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
consumeOnUse: new fields.BooleanField({ initial: false }),
|
consumeOnUse: new fields.BooleanField({ initial: false })
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHDomainCard extends BaseDataItem {
|
export default class DHDomainCard extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -8,7 +8,8 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
label: 'TYPES.Item.domainCard',
|
label: 'TYPES.Item.domainCard',
|
||||||
type: 'domainCard',
|
type: 'domainCard',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
hasResource: true
|
hasResource: true,
|
||||||
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,8 +30,7 @@ export default class DHDomainCard extends BaseDataItem {
|
||||||
required: true,
|
required: true,
|
||||||
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
|
||||||
}),
|
}),
|
||||||
inVault: new fields.BooleanField({ initial: false }),
|
inVault: new fields.BooleanField({ initial: false })
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField, ActionsField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHFeature extends BaseDataItem {
|
export default class DHFeature extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -8,7 +8,8 @@ export default class DHFeature extends BaseDataItem {
|
||||||
label: 'TYPES.Item.feature',
|
label: 'TYPES.Item.feature',
|
||||||
type: 'feature',
|
type: 'feature',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
hasResource: true
|
hasResource: true,
|
||||||
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -24,8 +25,7 @@ export default class DHFeature extends BaseDataItem {
|
||||||
}),
|
}),
|
||||||
subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }),
|
subType: new fields.StringField({ choices: CONFIG.DH.ITEM.featureSubTypes, nullable: true, initial: null }),
|
||||||
originId: new fields.StringField({ nullable: true, initial: null }),
|
originId: new fields.StringField({ nullable: true, initial: null }),
|
||||||
identifier: new fields.StringField(),
|
identifier: new fields.StringField()
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import { ActionField } from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHMiscellaneous extends BaseDataItem {
|
export default class DHMiscellaneous extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -9,16 +9,15 @@ export default class DHMiscellaneous extends BaseDataItem {
|
||||||
type: 'miscellaneous',
|
type: 'miscellaneous',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true,
|
isQuantifiable: true,
|
||||||
isInventoryItem: true
|
isInventoryItem: true,
|
||||||
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const fields = foundry.data.fields;
|
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema()
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import AttachableItem from './attachableItem.mjs';
|
import AttachableItem from './attachableItem.mjs';
|
||||||
import { actionsTypes } from '../action/_module.mjs';
|
import { ActionsField, ActionField } from '../fields/actionField.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
|
||||||
|
|
||||||
export default class DHWeapon extends AttachableItem {
|
export default class DHWeapon extends AttachableItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
@ -9,8 +8,8 @@ export default class DHWeapon extends AttachableItem {
|
||||||
label: 'TYPES.Item.weapon',
|
label: 'TYPES.Item.weapon',
|
||||||
type: 'weapon',
|
type: 'weapon',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isInventoryItem: true
|
isInventoryItem: true,
|
||||||
// hasInitialAction: true
|
hasActions: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -64,8 +63,7 @@ export default class DHWeapon extends AttachableItem {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}),
|
})
|
||||||
actions: new fields.ArrayField(new ActionField())
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,7 +93,10 @@ export default class DHWeapon extends AttachableItem {
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
||||||
changes.system.actions = this.actions.filter(x => !removedActionsUpdate.includes(x._id));
|
changes.system.actions = removedActionsUpdate.reduce((acc, id) => {
|
||||||
|
acc[`-=${id}`] = null;
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
for (let weaponFeature of added) {
|
for (let weaponFeature of added) {
|
||||||
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
||||||
|
|
@ -110,7 +111,7 @@ export default class DHWeapon extends AttachableItem {
|
||||||
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
const newActions = [];
|
const newActions = {};
|
||||||
if (featureData.actions?.length > 0) {
|
if (featureData.actions?.length > 0) {
|
||||||
for (let action of featureData.actions) {
|
for (let action of featureData.actions) {
|
||||||
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||||
|
|
@ -122,24 +123,25 @@ export default class DHWeapon extends AttachableItem {
|
||||||
description: game.i18n.localize(effect.description)
|
description: game.i18n.localize(effect.description)
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
const cls = actionsTypes[action.type];
|
|
||||||
newActions.push(
|
const cls = game.system.api.models.actions.actionsTypes[action.type];
|
||||||
new cls(
|
const actionId = foundry.utils.randomID();
|
||||||
{
|
newActions[actionId] = new cls(
|
||||||
...action,
|
{
|
||||||
_id: foundry.utils.randomID(),
|
...cls.getSourceConfig(this),
|
||||||
name: game.i18n.localize(action.name),
|
...action,
|
||||||
description: game.i18n.localize(action.description),
|
_id: actionId,
|
||||||
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
name: game.i18n.localize(action.name),
|
||||||
},
|
description: game.i18n.localize(action.description),
|
||||||
{ parent: this }
|
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||||
)
|
},
|
||||||
|
{ parent: this }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
changes.system.actions = [...this.actions, ...newActions];
|
changes.system.actions = newActions;
|
||||||
weaponFeature.actionIds = newActions.map(x => x._id);
|
weaponFeature.actionIds = Object.keys(newActions);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export default class DamageRoll extends DHRoll {
|
||||||
|
|
||||||
static async buildEvaluate(roll, config = {}, message = {}) {
|
static async buildEvaluate(roll, config = {}, message = {}) {
|
||||||
if (config.evaluate !== false) {
|
if (config.evaluate !== false) {
|
||||||
|
if(config.dialog.configure === false) roll.constructFormula(config);
|
||||||
for (const roll of config.roll) await roll.roll.evaluate();
|
for (const roll of config.roll) await roll.roll.evaluate();
|
||||||
}
|
}
|
||||||
roll._evaluated = true;
|
roll._evaluated = true;
|
||||||
|
|
|
||||||
|
|
@ -56,13 +56,13 @@ export default class DHRoll extends Roll {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create Chat Message
|
// Create Chat Message
|
||||||
|
if (roll instanceof CONFIG.Dice.daggerheart.DamageRoll && Object.values(config.roll)?.length) {
|
||||||
|
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
||||||
|
Object.values(config.roll).flatMap(r => r.parts.map(p => p.roll))
|
||||||
|
);
|
||||||
|
roll = Roll.fromTerms([pool]);
|
||||||
|
}
|
||||||
if (config.source?.message) {
|
if (config.source?.message) {
|
||||||
if (Object.values(config.roll)?.length) {
|
|
||||||
const pool = foundry.dice.terms.PoolTerm.fromRolls(
|
|
||||||
Object.values(config.roll).flatMap(r => r.parts.map(p => p.roll))
|
|
||||||
);
|
|
||||||
roll = Roll.fromTerms([pool]);
|
|
||||||
}
|
|
||||||
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
|
||||||
} else config.message = await this.toMessage(roll, config);
|
} else config.message = await this.toMessage(roll, config);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,23 @@ export default class DhpActor extends Actor {
|
||||||
return this.system.metadata.isNPC;
|
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) {
|
async _preCreate(data, options, user) {
|
||||||
if ((await super._preCreate(data, options, user)) === false) return false;
|
if ((await super._preCreate(data, options, user)) === false) return false;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,23 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
for (const action of this.system.actions ?? []) action.prepareData();
|
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
|
* @inheritdoc
|
||||||
* @param {object} options - Options which modify the getRollData method.
|
* @param {object} options - Options which modify the getRollData method.
|
||||||
|
|
@ -106,10 +123,10 @@ export default class DHItem extends foundry.documents.Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event) {
|
async use(event) {
|
||||||
const actions = this.system.actionsList;
|
const actions = new Set(this.system.actionsList);
|
||||||
if (actions?.length) {
|
if (actions?.size) {
|
||||||
let action = actions[0];
|
let action = actions.first();
|
||||||
if (actions.length > 1 && !event?.shiftKey) {
|
if (actions.size > 1 && !event?.shiftKey) {
|
||||||
// Actions Choice Dialog
|
// Actions Choice Dialog
|
||||||
action = await this.selectActionDialog(event);
|
action = await this.selectActionDialog(event);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,18 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
|
||||||
|
|
||||||
let html = options.html;
|
let html = options.html;
|
||||||
if (element.dataset.tooltip?.startsWith('#item#')) {
|
if (element.dataset.tooltip?.startsWith('#item#')) {
|
||||||
const splitValues = element.dataset.tooltip.slice(6).split('#action#');
|
const itemUuid = element.dataset.tooltip.slice(6);
|
||||||
const itemUuid = splitValues[0];
|
const item = await foundry.utils.fromUuid(itemUuid);
|
||||||
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;
|
|
||||||
if (item) {
|
if (item) {
|
||||||
const type = actionId ? 'action' : item.type;
|
const isAction = item instanceof game.system.api.models.actions.actionsTypes.base;
|
||||||
const description = await TextEditor.enrichHTML(item.system.description);
|
const description = await TextEditor.enrichHTML(isAction ? item.description : item.system.description);
|
||||||
for (let feature of item.system.features) {
|
if (item.system?.features) {
|
||||||
feature.system.enrichedDescription = await TextEditor.enrichHTML(feature.system.description);
|
for (let feature of item.system.features) {
|
||||||
|
feature.system.enrichedDescription = await TextEditor.enrichHTML(feature.system.description);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const type = isAction ? 'action' : item.type;
|
||||||
html = await foundry.applications.handlebars.renderTemplate(
|
html = await foundry.applications.handlebars.renderTemplate(
|
||||||
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@ export default class RegisterHandlebarsHelpers {
|
||||||
damageFormula: this.damageFormula,
|
damageFormula: this.damageFormula,
|
||||||
damageSymbols: this.damageSymbols,
|
damageSymbols: this.damageSymbols,
|
||||||
rollParsed: this.rollParsed,
|
rollParsed: this.rollParsed,
|
||||||
hasProperty: foundry.utils.hasProperty
|
hasProperty: foundry.utils.hasProperty,
|
||||||
|
setVar: this.setVar
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
static add(a, b) {
|
static add(a, b) {
|
||||||
|
|
@ -50,4 +51,8 @@ export default class RegisterHandlebarsHelpers {
|
||||||
const result = itemAbleRollParse(value, actor, item);
|
const result = itemAbleRollParse(value, actor, item);
|
||||||
return isNumerical && !result ? 0 : result;
|
return isNumerical && !result ? 0 : result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static setVar(name, value, context) {
|
||||||
|
this[name] = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,7 +96,7 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
|
||||||
mapValueTo: 'name',
|
mapValueTo: 'name',
|
||||||
searchKeys: ['name'],
|
searchKeys: ['name'],
|
||||||
enabled: 0,
|
enabled: 0,
|
||||||
maxItems: 20,
|
maxItems: 100,
|
||||||
closeOnSelect: true,
|
closeOnSelect: true,
|
||||||
highlightFirst: false
|
highlightFirst: false
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -346,6 +346,12 @@
|
||||||
&:has(.list-w-img) {
|
&:has(.list-w-img) {
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.no-style {
|
||||||
|
border-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.two-columns {
|
.two-columns {
|
||||||
|
|
|
||||||
|
|
@ -4,90 +4,78 @@
|
||||||
{{localize "DAGGERHEART.GENERAL.damage"}}
|
{{localize "DAGGERHEART.GENERAL.damage"}}
|
||||||
{{#unless (eq path 'system.attack.')}}<a><i class="fa-solid fa-plus icon-button" data-action="addDamage"></i></a>{{/unless}}
|
{{#unless (eq path 'system.attack.')}}<a><i class="fa-solid fa-plus icon-button" data-action="addDamage"></i></a>{{/unless}}
|
||||||
</legend>
|
</legend>
|
||||||
{{#unless (or @root.isNPC path)}}
|
{{#if @root.hasBaseDamage}}
|
||||||
{{#if @root.hasBaseDamage}}
|
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
||||||
{{formField @root.fields.damage.fields.includeBase value=@root.source.damage.includeBase name="damage.includeBase" classes="checkbox" localize=true }}
|
{{/if}}
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
{{#each source.parts as |dmg index|}}
|
{{#each source.parts as |dmg index|}}
|
||||||
{{#if (or @root.isNPC ../path)}}
|
{{#if (and @root.hasBaseDamage @root.source.damage.includeBase)}}
|
||||||
{{formField ../fields.value.fields.custom.fields.enabled value=dmg.value.custom.enabled name=(concat ../path "damage.parts." index ".value.custom.enabled") classes="checkbox"}}
|
{{setVar 'realIndex' (add index -1)}}
|
||||||
<input type="hidden" name="{{../path}}damage.parts.{{index}}.value.multiplier" value="{{dmg.value.multiplier}}">
|
|
||||||
{{#if dmg.value.custom.enabled}}
|
|
||||||
{{formField ../fields.value.fields.custom.fields.formula value=dmg.value.custom.formula name=(concat ../path "damage.parts." index ".value.custom.formula") localize=true}}
|
|
||||||
{{else}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
{{#if @root.isNPC}}{{formField ../fields.value.fields.flatMultiplier value=dmg.value.flatMultiplier name=(concat ../path "damage.parts." index ".value.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}{{/if}}
|
|
||||||
{{formField ../fields.value.fields.dice value=dmg.value.dice name=(concat ../path "damage.parts." index ".value.dice") classes="inline-child"}}
|
|
||||||
{{formField ../fields.value.fields.bonus value=dmg.value.bonus name=(concat ../path "damage.parts." index ".value.bonus") localize=true classes="inline-child"}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
{{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}}
|
|
||||||
{{#if (eq dmg.applyTo 'hitPoints')}}
|
|
||||||
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." index ".type") localize=true}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
{{#if ../horde}}
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
|
||||||
<div class="nest-inputs">
|
|
||||||
{{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." index ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
|
|
||||||
{{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." index ".valueAlt.dice") classes="inline-child"}}
|
|
||||||
{{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." index ".valueAlt.bonus") localize=true classes="inline-child"}}
|
|
||||||
</div>
|
|
||||||
</fieldset>
|
|
||||||
{{/if}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#with (@root.getRealIndex index) as | realIndex |}}
|
{{setVar 'realIndex' index}}
|
||||||
<div class="nest-inputs">
|
|
||||||
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
|
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
|
|
||||||
{{formField ../../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." realIndex ".resultBased") localize=true classes="checkbox"}}
|
|
||||||
{{/if}}
|
|
||||||
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
|
|
||||||
{{> formula fields=../../fields.value.fields type=../../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
|
|
||||||
</fieldset>
|
|
||||||
<fieldset class="one-column">
|
|
||||||
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
|
|
||||||
{{> formula fields=../../fields.valueAlt.fields type=../../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex}}
|
|
||||||
</fieldset>
|
|
||||||
</div>
|
|
||||||
{{else}}
|
|
||||||
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
|
|
||||||
{{> formula fields=../../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex}}
|
|
||||||
</fieldset>
|
|
||||||
{{/if}}
|
|
||||||
<div class="nest-inputs">
|
|
||||||
{{formField ../../fields.applyTo value=dmg.applyTo name=(concat "damage.parts." realIndex ".applyTo") localize=true}}
|
|
||||||
{{#if (eq dmg.applyTo 'hitPoints')}}
|
|
||||||
{{formField ../../fields.type value=dmg.type name=(concat "damage.parts." realIndex ".type") localize=true}}
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
|
|
||||||
</fieldset>
|
|
||||||
{{#unless dmg.base}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
|
|
||||||
</div>
|
|
||||||
{{/with}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}">
|
||||||
|
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
|
||||||
|
{{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." realIndex ".resultBased") localize=true classes="checkbox"}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base) dmg.resultBased)}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}</legend>
|
||||||
|
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex path=../path}}
|
||||||
|
</fieldset>
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.withThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}</legend>
|
||||||
|
{{> formula fields=../fields.valueAlt.fields type=../fields.type dmg=dmg source=dmg.valueAlt target="valueAlt" realIndex=realIndex path=../path}}
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.GENERAL.formula"}}</legend>
|
||||||
|
{{> formula fields=../fields.value.fields type=../fields.type dmg=dmg source=dmg.value target="value" realIndex=realIndex path=../path}}
|
||||||
|
</fieldset>
|
||||||
|
{{/if}}
|
||||||
|
<div class="nest-inputs">
|
||||||
|
{{formField ../fields.applyTo value=dmg.applyTo name=(concat ../path "damage.parts." realIndex ".applyTo") localize=true}}
|
||||||
|
{{#if (eq dmg.applyTo 'hitPoints')}}
|
||||||
|
{{formField ../fields.type value=dmg.type name=(concat ../path "damage.parts." realIndex ".type") localize=true}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
{{#if ../horde}}
|
||||||
|
<fieldset class="one-column">
|
||||||
|
<legend>{{localize "DAGGERHEART.ACTORS.Adversary.hordeDamage"}}</legend>
|
||||||
|
<div class="nest-inputs">
|
||||||
|
<input type="hidden" name="{{../path}}damage.parts.{{realIndex}}.valueAlt.multiplier" value="flat">
|
||||||
|
{{formField ../fields.valueAlt.fields.flatMultiplier value=dmg.valueAlt.flatMultiplier name=(concat ../path "damage.parts." realIndex ".valueAlt.flatMultiplier") label="DAGGERHEART.ACTIONS.Settings.multiplier" classes="inline-child" localize=true }}
|
||||||
|
{{formField ../fields.valueAlt.fields.dice value=dmg.valueAlt.dice name=(concat ../path "damage.parts." realIndex ".valueAlt.dice") classes="inline-child"}}
|
||||||
|
{{formField ../fields.valueAlt.fields.bonus value=dmg.valueAlt.bonus name=(concat ../path "damage.parts." realIndex ".valueAlt.bonus") localize=true classes="inline-child"}}
|
||||||
|
</div>
|
||||||
|
</fieldset>
|
||||||
|
{{/if}}
|
||||||
|
<input type="hidden" name="damage.parts.{{realIndex}}.base" value="{{dmg.base}}">
|
||||||
|
</fieldset>
|
||||||
|
{{#unless (or dmg.base ../path)}}<div class="fas fa-trash" data-action="removeDamage" data-index="{{realIndex}}"></div>{{/unless}}
|
||||||
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
{{#*inline "formula"}}
|
{{#*inline "formula"}}
|
||||||
{{#unless dmg.base}}
|
{{#unless dmg.base}}
|
||||||
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox"}}
|
{{formField fields.custom.fields.enabled value=source.custom.enabled name=(concat path "damage.parts." realIndex "." target ".custom.enabled") classes="checkbox"}}
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if source.custom.enabled}}
|
{{#if source.custom.enabled}}
|
||||||
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat "damage.parts." realIndex "." target ".custom.formula") localize=true}}
|
{{formField fields.custom.fields.formula value=source.custom.formula name=(concat path "damage.parts." realIndex "." target ".custom.formula") localize=true}}
|
||||||
{{else}}
|
{{else}}
|
||||||
<div class="nest-inputs">
|
<div class="nest-inputs">
|
||||||
{{formField fields.multiplier value=source.multiplier name=(concat "damage.parts." realIndex "." target ".multiplier") localize=true}}
|
{{#unless @root.isNPC}}
|
||||||
{{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat "damage.parts." realIndex ".flatMultiplier") }}{{/if}}
|
{{formField fields.multiplier value=source.multiplier name=(concat path "damage.parts." realIndex "." target ".multiplier") localize=true}}
|
||||||
{{formField fields.dice value=source.dice name=(concat "damage.parts." realIndex "." target ".dice")}}
|
{{/unless}}
|
||||||
{{formField fields.bonus value=source.bonus name=(concat "damage.parts." realIndex "." target ".bonus") localize=true}}
|
{{#if (eq source.multiplier 'flat')}}{{formField fields.flatMultiplier value=source.flatMultiplier name=(concat ../path "damage.parts." realIndex "." target ".flatMultiplier") }}{{/if}}
|
||||||
|
{{formField fields.dice value=source.dice name=(concat path "damage.parts." realIndex "." target ".dice")}}
|
||||||
|
{{formField fields.bonus value=source.bonus name=(concat path "damage.parts." realIndex "." target ".bonus") localize=true}}
|
||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
{{#if @root.isNPC}}
|
||||||
|
<input type="hidden" name="{{path}}damage.parts.{{realIndex}}.{{target}}.multiplier" value="flat">
|
||||||
|
{{/if}}
|
||||||
{{/inline}}
|
{{/inline}}
|
||||||
|
|
@ -19,7 +19,5 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</fieldset>
|
</fieldset>
|
||||||
{{#if (eq document.system.type 'horde')}}
|
{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." horde=(eq document.system.type 'horde')}}
|
||||||
{{> 'systems/daggerheart/templates/actionTypes/damage.hbs' fields=systemFields.attack.fields.damage.fields.parts.element.fields source=document.system.attack.damage path="system.attack." horde=true}}
|
|
||||||
{{/if}}
|
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -54,7 +54,7 @@
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="status-label">
|
<div class="status-label">
|
||||||
<h4>{{localize DAGGERHEART.GENERAL.difficulty}}</h4>
|
<h4>{{localize "DAGGERHEART.GENERAL.difficulty"}}</h4>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="status-number">
|
<div class="status-number">
|
||||||
|
|
|
||||||
|
|
@ -249,7 +249,7 @@ Parameters:
|
||||||
{{#if (and showActions (eq item.type 'feature'))}}
|
{{#if (and showActions (eq item.type 'feature'))}}
|
||||||
<div class="item-buttons">
|
<div class="item-buttons">
|
||||||
{{#each item.system.actions as | action |}}
|
{{#each item.system.actions as | action |}}
|
||||||
<button type="button" data-action="useAction" data-action-id="{{action.id}}">
|
<button type="button" data-action="useItem" data-item-uuid="{{action.uuid}}">
|
||||||
{{action.name}}
|
{{action.name}}
|
||||||
</button>
|
</button>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<fieldset class="dice-roll daggerheart chat roll expanded{{#unless damage.roll}} hidden{{/unless}}" data-action="expandRoll">
|
<fieldset class="dice-roll daggerheart chat roll expanded{{#unless damage.roll}} hidden{{/unless}}" data-action="expandRoll">
|
||||||
<legend class="dice-flavor">{{localize "DAGGEHEART.GENERAL.damage"}}</legend>
|
<legend class="dice-flavor">{{localize "DAGGERHEART.GENERAL.damage"}}</legend>
|
||||||
<div class="dice-result">
|
<div class="dice-result">
|
||||||
<div class="dice-tooltip">
|
<div class="dice-tooltip">
|
||||||
<div class="wrapper">
|
<div class="wrapper">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue