Added transform action to handle phased adversaries

This commit is contained in:
WBHarry 2026-03-07 13:23:43 +01:00
parent f1f5102af1
commit dcf0293008
12 changed files with 196 additions and 38 deletions

View file

@ -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"
}
}
},

View file

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

View file

@ -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',

View file

@ -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
};

View file

@ -0,0 +1,5 @@
import DHBaseAction from './baseAction.mjs';
export default class DHTransformAction extends DHBaseAction {
static extraSchemas = [...super.extraSchemas, 'transform'];
}

View file

@ -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';

View file

@ -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;
}
}

View file

@ -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',

View file

@ -4,14 +4,22 @@
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-summon-name {
.actor-drop-name {
flex: 2;
display: flex;
align-items: center;
@ -22,12 +30,16 @@
}
}
.actor-summon-controls {
.actor-drop-controls {
flex: 1;
display: flex;
align-items: center;
gap: 5px;
&.transform {
justify-content: flex-end;
}
.controls {
display: flex;
gap: 5px;
@ -35,7 +47,7 @@
}
}
.summon-dragger {
.drop-dragger {
display: flex;
align-items: center;
justify-content: center;
@ -46,7 +58,6 @@
border-radius: 3px;
color: light-dark(@dark-blue-50, @beige-50);
}
}
.trigger-data {
width: 100%;

View file

@ -1,19 +1,19 @@
<fieldset class="one-column" id="summon-drop-zone" data-key="summon">
<fieldset class="one-column" id="summon-drop-zone" data-is-drop-zone="true" data-key="summon">
<legend>
{{localize "DAGGERHEART.ACTIONS.TYPES.summon.name"}}
</legend>
<ul class="actor-summon-items">
{{#each @root.summons as |summon index|}}
<li class="actor-summon-line">
<div class="actor-summon-name">
<li class="actor-drop-line">
<div class="actor-drop-name">
<img class="image" src="{{summon.actor.img}}" />
<h4 class="h4">
{{summon.actor.name}}
</h4>
</div>
<div class="actor-summon-controls">
<div class="actor-drop-controls">
<div class="form-group summon-count-wrapper" data-index="{{index}}">
<div class="form-fields">
<input type="text" value="{{summon.count}}" />
@ -43,7 +43,7 @@
</div>
</li>
{{/each}}
<div class="summon-dragger">
<div class="drop-dragger">
<span>{{localize "DAGGERHEART.ACTIONS.Settings.summon.dropSummonsHere"}}</span>
</div>
</ul>

View file

@ -0,0 +1,44 @@
<fieldset class="one-column" id="transform-drop-zone" data-is-drop-zone="true" data-key="transform">
<legend>
{{localize "DAGGERHEART.ACTIONS.TYPES.transform.name"}}
</legend>
<div class="transform-container">
{{#if transform.actor}}
<div class="actor-drop-line">
<div class="actor-drop-name">
<img class="image" src="{{transform.actor.img}}" />
<h4 class="h4">
{{transform.actor.name}}
</h4>
</div>
<div class="actor-drop-controls transform">
<div class="controls">
<a
class='effect-control'
data-action='editDoc'
data-item-uuid="{{transform.actor.uuid}}"
data-tooltip='{{localize "DAGGERHEART.UI.Tooltip.openItemWorld"}}'
>
<i class="fa-solid fa-globe"></i>
</a>
<a
class='effect-control'
data-action='removeTransformActor'
data-tooltip='{{localize "CONTROLS.CommonDelete"}}'
>
<i class='fas fa-trash'></i>
</a>
</div>
</div>
</div>
{{/if}}
{{#unless transform.actor}}
<div class="drop-dragger">
<span>{{localize "DAGGERHEART.ACTIONS.Settings.transform.dropTransformHere"}}</span>
</div>
{{/unless}}
</div>
</fieldset>

View file

@ -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}}
</section>