From dcf0293008e4238ecbb07eae6719d781d258e257 Mon Sep 17 00:00:00 2001 From: WBHarry Date: Sat, 7 Mar 2026 13:23:43 +0100 Subject: [PATCH] Added transform action to handle phased adversaries --- lang/en.json | 9 +++ .../sheets-configs/action-base-config.mjs | 33 ++++++++- module/config/actionConfig.mjs | 6 ++ module/data/action/_module.mjs | 4 +- module/data/action/transformAction.mjs | 5 ++ module/data/fields/action/_module.mjs | 1 + module/data/fields/action/transformField.mjs | 47 ++++++++++++ module/systemRegistration/handlebars.mjs | 1 + styles/less/sheets/actions/actions.less | 73 +++++++++++-------- templates/actionTypes/summon.hbs | 10 +-- templates/actionTypes/transform.hbs | 44 +++++++++++ .../action-settings/effect.hbs | 1 + 12 files changed, 196 insertions(+), 38 deletions(-) create mode 100644 module/data/action/transformAction.mjs create mode 100644 module/data/fields/action/transformField.mjs create mode 100644 templates/actionTypes/transform.hbs diff --git a/lang/en.json b/lang/en.json index c9d21944..9135c2b9 100755 --- a/lang/en.json +++ b/lang/en.json @@ -74,6 +74,12 @@ "invalidDrop": "You can only drop Actor entities to summon.", "chatMessageTitle": "Test2", "chatMessageHeaderTitle": "Summoning" + }, + "transform": { + "name": "Transform", + "tooltip": "Transform one actor into another", + "canvasError": "There is no active scene.", + "prototypeError": "You can only use a transform action from a Token" } }, "Config": { @@ -129,6 +135,9 @@ }, "summon": { "dropSummonsHere": "Drop Summons Here" + }, + "transform": { + "dropTransformHere": "Drop Transform Here" } } }, diff --git a/module/applications/sheets-configs/action-base-config.mjs b/module/applications/sheets-configs/action-base-config.mjs index 34543086..641f3c2c 100644 --- a/module/applications/sheets-configs/action-base-config.mjs +++ b/module/applications/sheets-configs/action-base-config.mjs @@ -28,6 +28,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) removeEffect: this.removeEffect, addElement: this.addElement, removeElement: this.removeElement, + removeTransformActor: this.removeTransformActor, editEffect: this.editEffect, addDamage: this.addDamage, removeDamage: this.removeDamage, @@ -41,7 +42,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) submitOnChange: true, closeOnSubmit: false }, - dragDrop: [{ dragSelector: null, dropSelector: '#summon-drop-zone', handlers: ['_onDrop'] }] + dragDrop: [{ dragSelector: null, dropSelector: '[data-is-drop-zone]', handlers: ['_onDrop'] }] }; static PARTS = { @@ -133,6 +134,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) context.summons.push({ actor, count: summon.count }); } + if (context.source.transform) { + context.transform = { + actor: await foundry.utils.fromUuid(context.source.transform.actorUUID) + }; + } + context.openSection = this.openSection; context.tabs = this._getTabs(this.constructor.TABS); context.config = CONFIG.DH; @@ -266,6 +273,12 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) if (doc) return doc.sheet.render({ force: true }); } + static async removeTransformActor() { + const data = this.action.toObject(); + data.transform.actorUUID = null; + this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); + } + static addDamage(_event) { if (!this.action.damage.parts) return; const data = this.action.toObject(), @@ -364,6 +377,18 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) return; } + const dropZone = event.target.closest('[data-is-drop-zone]'); + if (!dropZone) return; + + switch (dropZone.id) { + case 'summon-drop-zone': + return this.onSummonDrop(data); + case 'transform-drop-zone': + return this.onTransformDrop(data); + } + } + + async onSummonDrop(data) { const actionData = this.action.toObject(); let countvalue = 1; for (const entry of actionData.summon) { @@ -380,4 +405,10 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2) actionData.summon.push({ actorUUID: data.uuid, count: countvalue }); await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) }); } + + async onTransformDrop(data) { + const actionData = this.action.toObject(); + actionData.transform.actorUUID = data.uuid; + await this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(actionData) }); + } } diff --git a/module/config/actionConfig.mjs b/module/config/actionConfig.mjs index c9b70193..0b1bf91d 100644 --- a/module/config/actionConfig.mjs +++ b/module/config/actionConfig.mjs @@ -35,6 +35,12 @@ export const actionTypes = { icon: 'fa-ghost', tooltip: 'DAGGERHEART.ACTIONS.TYPES.summon.tooltip' }, + transform: { + id: 'transform', + name: 'DAGGERHEART.ACTIONS.TYPES.transform.name', + icon: 'fa-dragon', + tooltip: 'DAGGERHEART.ACTIONS.TYPES.transform.tooltip' + }, effect: { id: 'effect', name: 'DAGGERHEART.ACTIONS.TYPES.effect.name', diff --git a/module/data/action/_module.mjs b/module/data/action/_module.mjs index 9cfc48cb..043b039c 100644 --- a/module/data/action/_module.mjs +++ b/module/data/action/_module.mjs @@ -7,6 +7,7 @@ import EffectAction from './effectAction.mjs'; import HealingAction from './healingAction.mjs'; import MacroAction from './macroAction.mjs'; import SummonAction from './summonAction.mjs'; +import TransformAction from './transformAction.mjs'; export const actionsTypes = { base: BaseAction, @@ -17,5 +18,6 @@ export const actionsTypes = { summon: SummonAction, effect: EffectAction, macro: MacroAction, - beastform: BeastformAction + beastform: BeastformAction, + transform: TransformAction }; diff --git a/module/data/action/transformAction.mjs b/module/data/action/transformAction.mjs new file mode 100644 index 00000000..7e552902 --- /dev/null +++ b/module/data/action/transformAction.mjs @@ -0,0 +1,5 @@ +import DHBaseAction from './baseAction.mjs'; + +export default class DHTransformAction extends DHBaseAction { + static extraSchemas = [...super.extraSchemas, 'transform']; +} diff --git a/module/data/fields/action/_module.mjs b/module/data/fields/action/_module.mjs index 0bdffca2..fa3b1cd5 100644 --- a/module/data/fields/action/_module.mjs +++ b/module/data/fields/action/_module.mjs @@ -10,3 +10,4 @@ export { default as DamageField } from './damageField.mjs'; export { default as RollField } from './rollField.mjs'; export { default as MacroField } from './macroField.mjs'; export { default as SummonField } from './summonField.mjs'; +export { default as TransformField } from './transformField.mjs'; diff --git a/module/data/fields/action/transformField.mjs b/module/data/fields/action/transformField.mjs new file mode 100644 index 00000000..9e4acdc3 --- /dev/null +++ b/module/data/fields/action/transformField.mjs @@ -0,0 +1,47 @@ +const fields = foundry.data.fields; + +export default class DHSummonField extends fields.SchemaField { + /** + * Action Workflow order + */ + static order = 130; + + constructor(options = {}, context = {}) { + const transformFields = { + actorUUID: new fields.DocumentUUIDField({ + type: 'Actor', + required: true + }) + }; + super(transformFields, options, context); + } + + static async execute() { + if (!canvas.scene) + return ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.canvasError')); + + if (!this.actor.token) + return ui.notifications.warn(game.i18n.localize('DAGGERHEART.ACTIONS.TYPES.transform.prototypeError')); + + const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(this.transform.actorUUID)); + + await this.actor.token.update( + { ...actor.prototypeToken.toJSON(), actorId: actor.id }, + { diff: false, recursive: false, noHook: true } + ); + this.actor.sheet.close(); + actor.sheet.render({ force: true }); + } + + /* Check for any available instances of the actor present in the world, or create a world actor based on compendium */ + static async getWorldActor(baseActor) { + const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`]; + if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) { + const worldActorCopy = game.actors.find(x => x.name === baseActor.name); + if (worldActorCopy) return worldActorCopy; + } + + const worldActor = await game.system.api.documents.DhpActor.create(baseActor.toObject()); + return worldActor; + } +} diff --git a/module/systemRegistration/handlebars.mjs b/module/systemRegistration/handlebars.mjs index ad8c741a..de085221 100644 --- a/module/systemRegistration/handlebars.mjs +++ b/module/systemRegistration/handlebars.mjs @@ -33,6 +33,7 @@ export const preloadHandlebarsTemplates = async function () { 'systems/daggerheart/templates/actionTypes/beastform.hbs', 'systems/daggerheart/templates/actionTypes/countdown.hbs', 'systems/daggerheart/templates/actionTypes/summon.hbs', + 'systems/daggerheart/templates/actionTypes/transform.hbs', 'systems/daggerheart/templates/settings/components/settings-item-line.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs', 'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs', diff --git a/styles/less/sheets/actions/actions.less b/styles/less/sheets/actions/actions.less index 9be56c8e..b9bebc8e 100644 --- a/styles/less/sheets/actions/actions.less +++ b/styles/less/sheets/actions/actions.less @@ -4,50 +4,61 @@ display: flex; flex-direction: column; gap: 10px; + } - .actor-summon-line { + .transform-container { + width: 100%; + display: flex; + flex-direction: column; + gap: 10px; + } + + .actor-drop-line { + display: flex; + align-items: center; + gap: 5px; + border-radius: 3px; + + .actor-drop-name { + flex: 2; display: flex; align-items: center; gap: 5px; - border-radius: 3px; - .actor-summon-name { - flex: 2; - display: flex; - align-items: center; - gap: 5px; - - img { - height: 40px; - } - } - - .actor-summon-controls { - flex: 1; - display: flex; - align-items: center; - gap: 5px; - - .controls { - display: flex; - gap: 5px; - } + img { + height: 40px; } } - .summon-dragger { + .actor-drop-controls { + flex: 1; display: flex; align-items: center; - justify-content: center; - box-sizing: border-box; - height: 40px; - margin-top: 10px; - border: 1px dashed light-dark(@dark-blue-50, @beige-50); - border-radius: 3px; - color: light-dark(@dark-blue-50, @beige-50); + gap: 5px; + + &.transform { + justify-content: flex-end; + } + + .controls { + display: flex; + gap: 5px; + } } } + .drop-dragger { + display: flex; + align-items: center; + justify-content: center; + box-sizing: border-box; + height: 40px; + margin-top: 10px; + border: 1px dashed light-dark(@dark-blue-50, @beige-50); + border-radius: 3px; + color: light-dark(@dark-blue-50, @beige-50); + } + .trigger-data { width: 100%; display: flex; diff --git a/templates/actionTypes/summon.hbs b/templates/actionTypes/summon.hbs index 429977d9..18fed2e2 100644 --- a/templates/actionTypes/summon.hbs +++ b/templates/actionTypes/summon.hbs @@ -1,19 +1,19 @@ -
+
{{localize "DAGGERHEART.ACTIONS.TYPES.summon.name"}}
    {{#each @root.summons as |summon index|}} -
  • -
    +
  • +

    {{summon.actor.name}}

    -
    +
    @@ -43,7 +43,7 @@
  • {{/each}} -
    +
    {{localize "DAGGERHEART.ACTIONS.Settings.summon.dropSummonsHere"}}
diff --git a/templates/actionTypes/transform.hbs b/templates/actionTypes/transform.hbs new file mode 100644 index 00000000..1c7629e7 --- /dev/null +++ b/templates/actionTypes/transform.hbs @@ -0,0 +1,44 @@ +
+ + {{localize "DAGGERHEART.ACTIONS.TYPES.transform.name"}} + + +
+ {{#if transform.actor}} +
+
+ +

+ {{transform.actor.name}} +

+
+ +
+ +
+
+ {{/if}} + + {{#unless transform.actor}} +
+ {{localize "DAGGERHEART.ACTIONS.Settings.transform.dropTransformHere"}} +
+ {{/unless}} +
+
\ No newline at end of file diff --git a/templates/sheets-settings/action-settings/effect.hbs b/templates/sheets-settings/action-settings/effect.hbs index e94f4328..1bdd0304 100644 --- a/templates/sheets-settings/action-settings/effect.hbs +++ b/templates/sheets-settings/action-settings/effect.hbs @@ -11,4 +11,5 @@ {{#if fields.beastform}}{{> 'systems/daggerheart/templates/actionTypes/beastform.hbs' fields=fields.beastform.fields source=source.beastform}}{{/if}} {{#if fields.summon}}{{> 'systems/daggerheart/templates/actionTypes/summon.hbs' fields=fields.summon.element.fields source=source.summon}}{{/if}} {{#if fields.countdown}}{{> 'systems/daggerheart/templates/actionTypes/countdown.hbs' fields=fields.countdown.element.fields source=source.countdown}}{{/if}} + {{#if fields.transform}}{{> 'systems/daggerheart/templates/actionTypes/transform.hbs' fields=fields.transform.fields source=source.transform}}{{/if}} \ No newline at end of file