mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-18 07:59:03 +01:00
Action Refactor Part #2
This commit is contained in:
parent
479e147640
commit
9da6a13009
20 changed files with 324 additions and 340 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);
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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,139 +0,0 @@
|
||||||
import FormulaField from '../fields/formulaField.mjs';
|
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
|
||||||
|
|
||||||
/* Roll Field */
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Damage & Healing Field */
|
|
||||||
|
|
||||||
export class DHActionDiceData extends foundry.abstract.DataModel {
|
|
||||||
/** @override */
|
|
||||||
static defineSchema() {
|
|
||||||
return {
|
|
||||||
multiplier: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.multiplierTypes,
|
|
||||||
initial: 'prof',
|
|
||||||
label: 'Multiplier'
|
|
||||||
}),
|
|
||||||
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
|
|
||||||
dice: new fields.StringField({ choices: CONFIG.DH.GENERAL.diceTypes, initial: 'd6', label: 'Dice' }),
|
|
||||||
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
|
|
||||||
custom: new fields.SchemaField({
|
|
||||||
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
|
|
||||||
formula: new FormulaField({ label: 'Formula' })
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
getFormula() {
|
|
||||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
|
||||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
|
||||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
/** @override */
|
|
||||||
static defineSchema() {
|
|
||||||
return {
|
|
||||||
applyTo: new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.healingTypes,
|
|
||||||
required: true,
|
|
||||||
blank: false,
|
|
||||||
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
|
|
||||||
}),
|
|
||||||
resultBased: new fields.BooleanField({
|
|
||||||
initial: false,
|
|
||||||
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
|
||||||
}),
|
|
||||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
|
||||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DHDamageData extends DHResourceData {
|
|
||||||
/** @override */
|
|
||||||
static defineSchema() {
|
|
||||||
return {
|
|
||||||
...super.defineSchema(),
|
|
||||||
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
|
||||||
type: new fields.SetField(
|
|
||||||
new fields.StringField({
|
|
||||||
choices: CONFIG.DH.GENERAL.damageTypes,
|
|
||||||
initial: 'physical',
|
|
||||||
nullable: false,
|
|
||||||
required: true
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
label: 'Type'
|
|
||||||
}
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
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.parent.type === 'weapon' ? 'attack' : 'spellcast';
|
return parent.parent.type === 'weapon' ? 'attack' : 'spellcast';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +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';
|
import { ActionMixin } from '../fields/actionField.mjs';
|
||||||
import * as ActionFields from '../fields/action/_module.mjs';
|
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -19,8 +17,7 @@ const fields = foundry.data.fields;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel) {
|
||||||
static baseFields = ['cost', 'uses', 'range'];
|
static extraSchemas = ['cost', 'uses', 'range'];
|
||||||
static extraFields = [];
|
|
||||||
|
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
const schemaFields = {
|
const schemaFields = {
|
||||||
|
|
@ -38,27 +35,17 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
[...this.baseFields, ...extraFields].forEach(s => {
|
this.extraSchemas.forEach(s => {
|
||||||
// const clsField =
|
let clsField;
|
||||||
|
if(clsField = this.getActionField(s)) schemaFields[s] = new clsField();
|
||||||
});
|
});
|
||||||
|
|
||||||
return schemaFields;
|
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 ActionFields.SaveField(),
|
|
||||||
target: new ActionFields.TargetField(),
|
|
||||||
effects: new ActionFields.EffectField(),
|
|
||||||
healing: new fields.EmbeddedDataField(DHResourceData),
|
|
||||||
beastform: new ActionFields.BeastformField()
|
|
||||||
},
|
|
||||||
extraSchemas = {};
|
|
||||||
|
|
||||||
this.extraSchemas.forEach(s => (extraSchemas[s] = extraFields[s]));
|
|
||||||
return extraSchemas;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prepareData() {
|
prepareData() {
|
||||||
|
|
@ -130,38 +117,14 @@ export default class DHBaseAction extends ActionMixin(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;
|
||||||
|
|
||||||
|
|
@ -193,7 +156,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
|
||||||
}
|
}
|
||||||
|
|
||||||
/* */
|
/* */
|
||||||
initActionConfig(event) {
|
prepareConfig(event) {
|
||||||
return {
|
return {
|
||||||
event,
|
event,
|
||||||
title: this.item.name,
|
title: this.item.name,
|
||||||
|
|
@ -207,7 +170,9 @@ export default class DHBaseAction extends ActionMixin(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 || (!this.hasRoll && !this.hasSave),
|
||||||
|
data: this.getRollData()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -215,36 +180,6 @@ export default class DHBaseAction extends ActionMixin(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,
|
||||||
|
|
@ -316,108 +251,6 @@ export default class DHBaseAction extends ActionMixin(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;
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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';
|
||||||
|
|
|
||||||
|
|
@ -3,3 +3,4 @@ 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 { default as MappingField } from './mappingField.mjs';
|
||||||
|
export * as ActionFields from './action/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@ export { default as CostField } from './costField.mjs';
|
||||||
export { default as UsesField } from './usesField.mjs';
|
export { default as UsesField } from './usesField.mjs';
|
||||||
export { default as RangeField } from './rangeField.mjs';
|
export { default as RangeField } from './rangeField.mjs';
|
||||||
export { default as TargetField } from './targetField.mjs';
|
export { default as TargetField } from './targetField.mjs';
|
||||||
export { default as EffectField } from './effectField.mjs';
|
export { default as EffectsField } from './effectsField.mjs';
|
||||||
export { default as SaveField } from './saveField.mjs';
|
export { default as SaveField } from './saveField.mjs';
|
||||||
export { default as BeastformField } from './beastformField.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';
|
||||||
|
|
@ -15,4 +15,68 @@ export default class CostField extends fields.ArrayField {
|
||||||
});
|
});
|
||||||
super(element, options, context);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
import FormulaField from "../formulaField.mjs";
|
||||||
|
|
||||||
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
export default class DamageField 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 DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
multiplier: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.multiplierTypes,
|
||||||
|
initial: 'prof',
|
||||||
|
label: 'Multiplier'
|
||||||
|
}),
|
||||||
|
flatMultiplier: new fields.NumberField({ nullable: true, initial: 1, label: 'Flat Multiplier' }),
|
||||||
|
dice: new fields.StringField({ choices: CONFIG.DH.GENERAL.diceTypes, initial: 'd6', label: 'Dice' }),
|
||||||
|
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
|
||||||
|
custom: new fields.SchemaField({
|
||||||
|
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
|
||||||
|
formula: new FormulaField({ label: 'Formula' })
|
||||||
|
})
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getFormula() {
|
||||||
|
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
||||||
|
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
||||||
|
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DHResourceData extends foundry.abstract.DataModel {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
applyTo: new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.healingTypes,
|
||||||
|
required: true,
|
||||||
|
blank: false,
|
||||||
|
initial: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.applyTo.label'
|
||||||
|
}),
|
||||||
|
resultBased: new fields.BooleanField({
|
||||||
|
initial: false,
|
||||||
|
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'
|
||||||
|
}),
|
||||||
|
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||||
|
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DHDamageData extends DHResourceData {
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
return {
|
||||||
|
...super.defineSchema(),
|
||||||
|
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
|
||||||
|
type: new fields.SetField(
|
||||||
|
new fields.StringField({
|
||||||
|
choices: CONFIG.DH.GENERAL.damageTypes,
|
||||||
|
initial: 'physical',
|
||||||
|
nullable: false,
|
||||||
|
required: true
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
label: 'Type'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
export default class EffectField extends fields.ArrayField {
|
export default class EffectsField extends fields.ArrayField {
|
||||||
constructor(options={}, context={}) {
|
constructor(options={}, context={}) {
|
||||||
const element = new fields.SchemaField({
|
const element = new fields.SchemaField({
|
||||||
_id: new fields.DocumentIdField(),
|
_id: new fields.DocumentIdField(),
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,4 +9,6 @@ export default class RangeField extends fields.StringField {
|
||||||
};
|
};
|
||||||
super(options, context);
|
super(options, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static prepareConfig(config) {}
|
||||||
}
|
}
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,4 +13,49 @@ export default class TargetField extends fields.SchemaField {
|
||||||
};
|
};
|
||||||
super(targetFields, options, context);
|
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 !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
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -13,4 +13,27 @@ export default class UsesField extends fields.SchemaField {
|
||||||
};
|
};
|
||||||
super(usesFields, options, context);
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue