Update Beastform action

This commit is contained in:
Dapoolp 2025-08-22 23:10:45 +02:00
parent 9d9c636dbc
commit cf55659097
15 changed files with 324 additions and 132 deletions

View file

@ -1,6 +1,13 @@
import BeastformDialog from "../../../applications/dialogs/beastformDialog.mjs";
const fields = foundry.data.fields;
export default class BeastformField extends fields.SchemaField {
/**
* Action Workflow order
*/
static order = 90;
constructor(options = {}, context = {}) {
const beastformFields = {
tierAccess: new fields.SchemaField({
@ -27,4 +34,96 @@ export default class BeastformField extends fields.SchemaField {
};
super(beastformFields, options, context);
}
/**
* Beastform Transformation Action Workflow part.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
static async execute(config) {
// Should not be useful anymore here
await BeastformField.handleActiveTransformations.call(this);
const { selected, evolved, hybrid } = await BeastformDialog.configure(config, this.item);
if (!selected) return false;
return await BeastformField.transform.call(this, selected, evolved, hybrid);
}
/**
* Update Action Workflow config object.
* Must be called within Action context.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
prepareConfig(config) {
if(this.actor.effects.find(x => x.type === 'beastform')) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformAlreadyApplied'));
return false;
}
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
const actorLevel = this.actor.system.levelData.level.current;
const actorTier =
Object.values(settingsTiers).find(
tier => actorLevel >= tier.levels.start && actorLevel <= tier.levels.end
) ?? 1;
config.tierLimit = this.beastform.tierAccess.exact ?? actorTier;
}
/**
* TODO by Harry
* @param {*} selectedForm
* @param {*} evolvedData
* @param {*} hybridData
* @returns
*/
static async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
if (!beastformEffect) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
return false;
}
if (evolvedData?.form) {
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
if (!evolvedForm) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
return false;
}
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
}
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
Object.keys(formCategory).forEach(advantageKey => {
advantages[advantageKey] = formCategory[advantageKey];
});
return advantages;
}, {});
formData.system.features = [
...formData.system.features,
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
];
}
this.actor.createEmbeddedDocuments('Item', [formData]);
}
/**
* Remove existing beastform effect and return true if there was one
* @returns {boolean}
*/
static async handleActiveTransformations() {
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
const existingEffects = beastformEffects.length > 0;
await this.actor.deleteEmbeddedDocuments(
'ActiveEffect',
beastformEffects.map(x => x.id)
);
return existingEffects;
}
}

View file

@ -4,7 +4,7 @@ export default class CostField extends fields.ArrayField {
/**
* Action Workflow order
*/
order = 150;
static order = 150;
/** @inheritDoc */
constructor(options = {}, context = {}) {
@ -29,11 +29,11 @@ export default class CostField extends fields.ArrayField {
/**
* Cost Consumption Action Workflow part.
* Consume configured action resources.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
async execute(config, successCost = false) {
static async execute(config, successCost = false) {
const actor= this.actor.system.partner ?? this.actor,
usefulResources = {
...foundry.utils.deepClone(actor.system.resources),
@ -44,13 +44,15 @@ export default class CostField extends fields.ArrayField {
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {
usefulResources[cost.key] = {
value: cost.value,
target: this.parent.parent,
keyIsID: true
};
if(this.parent?.parent) {
for (var cost of config.costs) {
if (cost.keyIsID) {
usefulResources[cost.key] = {
value: cost.value,
target: this.parent.parent,
keyIsID: true
};
}
}
}
@ -78,7 +80,7 @@ export default class CostField extends fields.ArrayField {
/**
* Update Action Workflow config object.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @returns {boolean} Return false if fast-forwarded and no more uses.
*/

View file

@ -7,7 +7,7 @@ export default class DamageField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 20;
static order = 20;
/** @inheritDoc */
constructor(options, context = {}) {
@ -24,12 +24,12 @@ export default class DamageField extends fields.SchemaField {
/**
* Roll Damage/Healing Action Workflow part.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {string} [messageId=null] ChatMessage Id where the clicked button belong.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, messageId = null, force = false) {
static async execute(config, messageId = null, force = false) {
if((this.hasRoll && DamageField.getAutomation() === CONFIG.DH.SETTINGS.actionAutomationChoices.never.id) && !force) return;
let formulas = this.damage.parts.map(p => ({
@ -97,11 +97,11 @@ export default class DamageField extends fields.SchemaField {
}
/**
*
* Must be called within Action context.
* @param {*} part
* @param {*} data
* @returns
* Return value or valueAlt from damage part
* Must be called within Action context or similar.
* @param {object} part Damage Part
* @param {object} data Action getRollData
* @returns Formula value object
*/
static getFormulaValue(part, data) {
let formulaValue = part.value;
@ -118,10 +118,10 @@ export default class DamageField extends fields.SchemaField {
}
/**
*
* Must be called within Action context.
* @param {*} formulas
* @param {*} systemData
* Prepare formulas for Damage Roll
* Must be called within Action context or similar.
* @param {object[]} formulas Array of formatted formulas object
* @param {object} data Action getRollData
* @returns
*/
static formatFormulas(formulas, data) {

View file

@ -4,7 +4,7 @@ export default class EffectsField extends fields.ArrayField {
/**
* Action Workflow order
*/
order = 100;
static order = 100;
/** @inheritDoc */
constructor(options = {}, context = {}) {
@ -17,12 +17,12 @@ export default class EffectsField extends fields.ArrayField {
/**
* Apply Effects Action Workflow part.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, targets = null, force = false) {
static async execute(config, targets = null, force = false) {
if(!config.hasEffect) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if(!message) {
@ -37,10 +37,9 @@ export default class EffectsField extends fields.ArrayField {
}
/**
*
* Must be called within Action context.
* @param {*} targets
* @returns
* Apply Action Effects to a list of Targets
* Must be called within Action context or similar.
* @param {object[]} targets Array of formatted targets
*/
static async applyEffects(targets) {
if (!this.effects?.length || !targets?.length) return;
@ -59,10 +58,9 @@ export default class EffectsField extends fields.ArrayField {
}
/**
*
* @param {*} effect
* @param {*} actor
* @returns
* Apply an Effect to a target or enable it if already on it
* @param {object} effect Effect object containing ActiveEffect UUID
* @param {object} actor Actor Document
*/
static async applyEffect(effect, actor) {
const existingEffect = actor.effects.find(e => e.origin === effect.uuid);

View file

@ -1,7 +1,10 @@
const fields = foundry.data.fields;
export default class MacroField extends fields.DocumentUUIDField {
order = 70;
/**
* Action Workflow order
*/
static order = 70;
/** @inheritDoc */
constructor(context = {}) {
@ -10,9 +13,10 @@ export default class MacroField extends fields.DocumentUUIDField {
/**
* Macro Action Workflow part.
* Must be called within Action context or similar or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods. Currently not used.
*/
async execute(config) {
static async execute(config) {
const fixUUID = !this.macro.includes('Macro.') ? `Macro.${this.macro}` : this.macro,
macro = await fromUuid(fixUUID);
try {

View file

@ -110,7 +110,10 @@ export class DHActionRollData extends foundry.abstract.DataModel {
}
export default class RollField extends fields.EmbeddedDataField {
order = 10;
/**
* Action Workflow order
*/
static order = 10;
/** @inheritDoc */
constructor(options, context = {}) {
@ -119,10 +122,10 @@ export default class RollField extends fields.EmbeddedDataField {
/**
* Roll Action Workflow part.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
*/
async execute(config) {
static async execute(config) {
if(!config.hasRoll) return;
config = await this.actor.diceRoll(config);
if(!config) return false;

View file

@ -6,7 +6,7 @@ export default class SaveField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 50;
static order = 50;
/** @inheritDoc */
constructor(options = {}, context = {}) {
@ -27,12 +27,12 @@ export default class SaveField extends fields.SchemaField {
/**
* Reaction Roll Action Workflow part.
* Must be called within Action context.
* Must be called within Action context or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {object[]} [targets=null] Array of targets to override pre-selected ones.
* @param {boolean} [force=false] If the method should be executed outside of Action workflow, for ChatMessage button for example.
*/
async execute(config, targets = null, force = false) {
static async execute(config, targets = null, force = false) {
if(!config.hasSave) return;
let message = config.message ?? ui.chat.collection.get(config.parent?._id);
if(!message) {

View file

@ -6,7 +6,7 @@ export default class UsesField extends fields.SchemaField {
/**
* Action Workflow order
*/
order = 160;
static order = 160;
/** @inheritDoc */
constructor(options = {}, context = {}) {
@ -29,11 +29,11 @@ export default class UsesField extends fields.SchemaField {
/**
* Uses Consumption Action Workflow part.
* Increment Action spent uses by 1.
* Must be called within Action context.
* Must be called within Action context or similar or similar.
* @param {object} config Object that contains workflow datas. Usually made from Action Fields prepareConfig methods.
* @param {boolean} [successCost=false] Consume only resources configured as "On Success only" if not already consumed.
*/
async execute(config, successCost = false) {
static async execute(config, successCost = false) {
if (
config.uses?.enabled &&
((!successCost && (!config.uses?.consumeOnSuccess || config.roll?.success)) ||
@ -60,9 +60,9 @@ export default class UsesField extends fields.SchemaField {
}
/**
*
* Prepare Uses object for Action Workflow
* Must be called within Action context.
* @param {*} uses
* @param {object} uses
* @returns {object}
*/
static calcUses(uses) {