mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-04-21 15:03:37 +02:00
Merge branch 'v14-dev' of https://github.com/Foundryborne/daggerheart into v14-dev
This commit is contained in:
commit
6ed975f5b7
4 changed files with 79 additions and 130 deletions
|
|
@ -122,15 +122,14 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
|
|
||||||
async toggleClowncar(actors) {
|
async toggleClowncar(actors) {
|
||||||
const animationDuration = 500;
|
const animationDuration = 500;
|
||||||
const activeTokens = actors.flatMap(member => member.getActiveTokens());
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
|
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||||
|
const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id));
|
||||||
const { x: actorX, y: actorY } = this.document;
|
const { x: actorX, y: actorY } = this.document;
|
||||||
if (activeTokens.length > 0) {
|
if (activeTokens.length > 0) {
|
||||||
for (let token of activeTokens) {
|
for (let token of activeTokens) {
|
||||||
await token.document.update(
|
await token.update({ x: actorX, y: actorY, alpha: 0 }, { animation: { duration: animationDuration } });
|
||||||
{ x: actorX, y: actorY, alpha: 0 },
|
setTimeout(() => token.delete(), animationDuration);
|
||||||
{ animation: { duration: animationDuration } }
|
|
||||||
);
|
|
||||||
setTimeout(() => token.document.delete(), animationDuration);
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
const activeScene = game.scenes.find(x => x.id === game.user.viewedScene);
|
||||||
|
|
@ -140,11 +139,16 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
||||||
tokenData.push(data.toObject());
|
tokenData.push(data.toObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const viewedLevel = game.scenes.get(game.user.viewedScene).levels.get(game.user.viewedLevel);
|
||||||
|
const elevation = this.actor.token?.elevation ?? viewedLevel.elevation.bottom;
|
||||||
|
|
||||||
const newTokens = await activeScene.createEmbeddedDocuments(
|
const newTokens = await activeScene.createEmbeddedDocuments(
|
||||||
'Token',
|
'Token',
|
||||||
tokenData.map(tokenData => ({
|
tokenData.map(tokenData => ({
|
||||||
...tokenData,
|
...tokenData,
|
||||||
alpha: 0,
|
alpha: 0,
|
||||||
|
level: viewedLevel,
|
||||||
|
elevation: elevation,
|
||||||
x: actorX,
|
x: actorX,
|
||||||
y: actorY
|
y: actorY
|
||||||
}))
|
}))
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1 @@
|
||||||
export default class DhTokenLayer extends foundry.canvas.layers.TokenLayer {
|
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -44,12 +44,18 @@ export default class DHSummonField extends fields.ArrayField {
|
||||||
count = roll.total;
|
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. */
|
/* 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();
|
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)));
|
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);
|
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 */
|
/* 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 getWorldActor(baseActor) {
|
static async getWorldActor(baseActor) {
|
||||||
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
const dataType = game.system.api.data.actors[`Dh${baseActor.type.capitalize()}`];
|
||||||
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
if (baseActor.inCompendium && dataType && baseActor.img === dataType.DEFAULT_ICON) {
|
||||||
const worldActorCopy = game.actors.find(x => x.name === baseActor.name);
|
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;
|
return baseActor;
|
||||||
}
|
}
|
||||||
|
|
||||||
static async handleSummon(summonData, actionActor, summonIndex = 0) {
|
static async handleSummon(summonData, actionActor) {
|
||||||
const summon = summonData[summonIndex];
|
await CONFIG.ux.TokenManager.createTokensWithPreview(summonData, { elevation: actionActor.token?.elevation });
|
||||||
const result = await CONFIG.ux.TokenManager.createPreviewAsync(summon.actor, {
|
|
||||||
name: `${summon.actor.prototypeToken.name}${summon.count > 1 ? ` (${summon.count}x)` : ''}`
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!result) return actionActor.sheet?.maximize();
|
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,104 +1,68 @@
|
||||||
/**
|
/**
|
||||||
* A singleton class that handles preview tokens.
|
* A singleton class that handles creating tokens.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export default class DhTokenManager {
|
export default class DhTokenManager {
|
||||||
#activePreview;
|
|
||||||
#actor;
|
|
||||||
#resolve;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a template preview, deactivating any existing ones.
|
* Create a token previer
|
||||||
* @param {object} data
|
* @param {Actor} actor
|
||||||
|
* @param {object} tokenData
|
||||||
*/
|
*/
|
||||||
async createPreview(actor, tokenData) {
|
async createPreview(actor, tokenData) {
|
||||||
this.#actor = actor;
|
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||||
const token = await canvas.tokens._createPreview(
|
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,
|
...actor.prototypeToken.toObject(),
|
||||||
|
actorId: actor.id,
|
||||||
displayName: 50,
|
displayName: 50,
|
||||||
...tokenData
|
...tokenData
|
||||||
},
|
}
|
||||||
{ renderSheet: false, actor }
|
],
|
||||||
|
{ 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 = {}) {
|
* Creates new tokens on the canvas by placing previews.
|
||||||
return new Promise(resolve => {
|
* @param {object} tokenData
|
||||||
this.#resolve = resolve;
|
* @param {object} options
|
||||||
this.createPreview(actor, { ...tokenData, flags: { daggerheart: { createPlacement: true } } });
|
*/
|
||||||
|
async createTokensWithPreview(tokensData, { elevation } = {}) {
|
||||||
|
const scene = game.scenes.get(game.user.viewedScene);
|
||||||
|
if (!scene) return;
|
||||||
|
|
||||||
|
const level = scene.levels.get(game.user.viewedLevel);
|
||||||
|
if (!level) return;
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
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 }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the movement of the token preview on mousedrag.
|
|
||||||
* @param {mousemove Event} event
|
|
||||||
*/
|
|
||||||
#onDragMouseMove(event) {
|
|
||||||
event.stopPropagation();
|
|
||||||
const { moveTime, object } = this.#activePreview;
|
|
||||||
const update = {};
|
|
||||||
|
|
||||||
const now = Date.now();
|
|
||||||
if (now - (moveTime || 0) <= 16) return;
|
|
||||||
this.#activePreview.moveTime = now;
|
|
||||||
|
|
||||||
let cursor = event.getLocalPosition(canvas.templates);
|
|
||||||
|
|
||||||
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]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue