Cleaned up summon action and made it work for levels

This commit is contained in:
WBHarry 2026-04-11 19:01:10 +02:00
parent 28d9254883
commit 171f732e78
3 changed files with 69 additions and 124 deletions

View file

@ -1,16 +1 @@
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
async _createPreview(createData, options) {
if (options.actor) {
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
if (options.actor?.system.metadata.usesSize) {
const tokenSize = tokenSizes[options.actor.system.size];
if (tokenSize && options.actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
createData.width = tokenSize;
createData.height = tokenSize;
}
}
}
return super._createPreview(createData, options);
}
}
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {}

View file

@ -44,12 +44,18 @@ export default class DHSummonField extends fields.ArrayField {
count = roll.total;
}
const actor = DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */
summon.rolledCount = count;
summon.actor = actor.toObject();
summonData.push({ actor, count: count });
const countNumber = Number.parseInt(count);
for (let i = 0; i < countNumber; i++) {
const remaining = countNumber - i;
summonData.push({
actor,
tokenPreviewName: `${actor.prototypeToken.name}${remaining > 1 ? ` (${remaining}x)` : ''}`
});
}
}
if (rolls.length) await Promise.all(rolls.map(roll => game.dice3d.showForRoll(roll, game.user, true)));
@ -58,32 +64,22 @@ export default class DHSummonField extends fields.ArrayField {
DHSummonField.handleSummon(summonData, this.actor);
}
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium */
static getWorldActor(baseActor) {
/* Check for any available instances of the actor present in the world if we're missing artwork in the compendium. If none exists, create one. */
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);
return worldActorCopy ?? baseActor;
if (worldActorCopy) return worldActorCopy;
return await game.system.api.documents.DhpActor.create(baseActor.toObject());
}
return baseActor;
}
static async handleSummon(summonData, actionActor, summonIndex = 0) {
const summon = summonData[summonIndex];
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
});
static async handleSummon(summonData, actionActor) {
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
if (!result) return actionActor.sheet?.maximize();
summon.actor = result.actor;
summon.count--;
if (summon.count <= 0) {
summonIndex++;
if (summonIndex === summonData.length) return actionActor.sheet?.maximize();
}
DHSummonField.handleSummon(summonData, actionActor, summonIndex);
return actionActor.sheet?.maximize();
}
}

View file

@ -1,104 +1,68 @@
/**
* A singleton class that handles preview tokens.
* A singleton class that handles creating tokens.
*/
export default class DhTokenManager {
#activePreview;
#actor;
#resolve;
/**
* Create a template preview, deactivating any existing ones.
* @param {object} data
* Create a token previer
* @param {Actor} actor
* @param {object} tokenData
*/
async createPreview(actor, tokenData) {
this.#actor = actor;
const token = await canvas.tokens._createPreview(
{
...actor.prototypeToken,
displayName: 50,
...tokenData
},
{ renderSheet: false, actor }
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
if (actor?.system.metadata.usesSize) {
const tokenSize = tokenSizes[actor.system.size];
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
tokenData.width = tokenSize;
tokenData.height = tokenSize;
}
}
return await canvas.tokens.placeTokens(
[
{
...actor.prototypeToken.toObject(),
actorId: actor.id,
displayName: 50,
...tokenData
}
],
{ create: false }
);
this.#activePreview = {
document: token.document,
object: token,
origin: { x: token.document.x, y: token.document.y }
};
this.#activePreview.events = {
contextmenu: this.#cancelTemplate.bind(this),
mousedown: this.#confirmTemplate.bind(this),
mousemove: this.#onDragMouseMove.bind(this)
};
canvas.stage.on('mousemove', this.#activePreview.events.mousemove);
canvas.stage.on('mousedown', this.#activePreview.events.mousedown);
canvas.app.view.addEventListener('contextmenu', this.#activePreview.events.contextmenu);
}
/* Currently intended for using as a preview of where to create a token. (note the flag) */
async createPreviewAsync(actor, tokenData = {}) {
return new Promise(resolve => {
this.#resolve = resolve;
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
});
}
/**
* Handles the movement of the token preview on mousedrag.
* @param {mousemove Event} event
* Creates new tokens on the canvas by placing previews.
* @param {object} tokenData
* @param {object} options
*/
#onDragMouseMove(event) {
event.stopPropagation();
const { moveTime, object } = this.#activePreview;
const update = {};
async createTokensWithPreview(tokensData, { elevation } = {}) {
const scene = game.scenes.get(game.user.viewedScene);
if (!scene) return;
const now = Date.now();
if (now - (moveTime || 0) <= 16) return;
this.#activePreview.moveTime = now;
const level = scene.levels.get(game.user.viewedLevel);
if (!level) return;
let cursor = event.getLocalPosition(canvas.templates);
const createElevation = elevation ?? level.elevation.bottom;
for (const tokenData of tokensData) {
const previewTokens = await this.createPreview(tokenData.actor, {
name: tokenData.tokenPreviewName,
level: game.user.viewedLevel,
elevation: createElevation,
flags: { daggerheart: { createPlacement: true } }
});
if (!previewTokens?.length) return null;
Object.assign(update, canvas.grid.getTopLeftPoint(cursor));
object.document.updateSource(update);
object.renderFlags.set({ refresh: true });
}
/**
* Cancels the preview token on right-click.
* @param {contextmenu Event} event
*/
#cancelTemplate(_event, resolved) {
const { mousemove, mousedown, contextmenu } = this.#activePreview.events;
this.#activePreview.object.destroy();
canvas.stage.off('mousemove', mousemove);
canvas.stage.off('mousedown', mousedown);
canvas.app.view.removeEventListener('contextmenu', contextmenu);
if (this.#resolve && !resolved) this.#resolve(false);
}
/**
* Creates a real Actor and token at the preview location and cancels the preview.
* @param {click Event} event
*/
async #confirmTemplate(event) {
event.stopPropagation();
this.#cancelTemplate(event, true);
const actor = this.#actor.inCompendium
? await game.system.api.documents.DhpActor.create(this.#actor.toObject())
: this.#actor;
const tokenData = await actor.getTokenDocument();
const result = await canvas.scene.createEmbeddedDocuments('Token', [
{ ...tokenData.toObject(), x: this.#activePreview.document.x, y: this.#activePreview.document.y }
]);
this.#activePreview = undefined;
if (this.#resolve && result.length) this.#resolve(result[0]);
await canvas.scene.createEmbeddedDocuments(
'Token',
previewTokens.map(x => ({
...x.toObject(),
name: tokenData.actor.prototypeToken.name,
displayName: tokenData.actor.prototypeToken.displayName,
flags: tokenData.actor.prototypeToken.flags
})),
{ controlObject: true, parent: canvas.scene }
);
}
}
}