Skip to content

Commit 93f265a

Browse files
committed
[#5559, #6436] Create proper emanations, attach to tokens
Swaps the Emanation area of effect type to use `emanation` shapes under the hood, adapting the behavior for when tokens are moused over during placement to associate a token with the emanation. This leads to the base size of the emanation being adjusted to match the size of the token. When an emanation is not marked as stationary and placed over a token it will be automatically attached to the token it is placed over, creating an aura that moves with that token. Closes #6436
1 parent f1a6b7a commit 93f265a

3 files changed

Lines changed: 28 additions & 13 deletions

File tree

module/canvas/_types.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
/**
1717
* @typedef {BaseShapeData} TemplatePlacementData
18+
* @property {string} [token] ID of attached token.
1819
*/
1920

2021
/* -------------------------------------------- */

module/canvas/template-placement.mjs

Lines changed: 26 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export default class TemplatePlacement {
5050
const activeLayer = canvas.activeLayer;
5151
try {
5252
const results = [];
53+
const attachToToken = this.config.shapes.some(s => s.type === "emanation");
5354
await canvas.regions.placeRegion({
5455
name: RegionDocument.implementation.defaultName({ parent: canvas.scene }),
5556
color: this.config.color,
@@ -58,12 +59,11 @@ export default class TemplatePlacement {
5859
shapes: this.config.shapes.map(s => this.#createShapeData(s)),
5960
"flags.core.MeasuredTemplate": true
6061
}, {
61-
// TODO: `attachToToken: true` if emanation
62+
attachToToken,
6263
create: false,
6364
preConfirm: ({ document, index }) => {
6465
const obj = document.toObject();
65-
results.push({ ...obj.shapes.at(-1) });
66-
// TODO: Set token ID if emanation attached to token
66+
results.push({ ...obj.shapes.at(-1), token: obj.attachment.token });
6767
}
6868
});
6969
return results;
@@ -87,7 +87,10 @@ export default class TemplatePlacement {
8787
switch ( type ) {
8888
case "circle": return { ...data, radius: size };
8989
case "cone": return { ...data, angle: CONFIG.MeasuredTemplate.defaults.angle, radius: size };
90-
case "emanation": return { base: { ...data }, radius: size }; // TODO: Make this work properly
90+
case "emanation": return {
91+
base: { ...data, width: 1, height: 1, shape: 4, type: "token" },
92+
radius: size, type: "emanation"
93+
};
9194
case "ray":
9295
case "line": return { ...data, length: size, width, type: "line" };
9396
case "rect":
@@ -135,18 +138,27 @@ export default class TemplatePlacement {
135138
*/
136139
if ( Hooks.call("dnd5e.preCreateMeasuredTemplate", activity, config) === false ) return null;
137140

138-
const shapes = await TemplatePlacement.place(config);
139-
if ( !shapes?.length ) return null;
141+
const placements = await TemplatePlacement.place(config);
142+
if ( !placements?.length ) return null;
140143

141-
// TODO: If type=emanation and stationary=false, create multiple templates
142-
// Otherwise only a single template is created with multiple shapes
144+
const combinedShapes = [];
145+
const splitShapes = [];
146+
for ( const placement of placements ) {
147+
if ( placement.token ) {
148+
const { x, y, width, height, shape } = canvas.scene.tokens.get(placement.token);
149+
Object.assign(placement.base, { x, y, width, height, shape });
150+
}
151+
if ( !placement.token || target.stationary ) combinedShapes.push(placement);
152+
else splitShapes.push([placement]);
153+
}
143154

144155
const rollData = activity.getRollData();
145-
const regionData = [foundry.utils.mergeObject({
156+
const regionData = [combinedShapes, ...splitShapes].map(shapes => shapes.length ? foundry.utils.mergeObject({
146157
// TODO: Should the activity name be included?
158+
// TODO: Include count if more than one created?
147159
name: `${activity.item.name} [${game.user.name}]`,
148160
color: game.user.color,
149-
shapes: shapes.map(({ index, ...data }) => data),
161+
shapes: shapes.map(({ index, token, ...data }) => data),
150162
// TODO: Set elevation based on shape's height
151163
levels: [canvas.level.id],
152164
restriction: {
@@ -155,7 +167,9 @@ export default class TemplatePlacement {
155167
// TODO: What about templates like Fireball that flow around walls?
156168
type: "move"
157169
},
158-
// TODO: Set attachedToken if type=emanation and stationary=false and token clicked on
170+
attachment: {
171+
token: target.stationary ? undefined : shapes[0].token
172+
},
159173
visibility: CONST.REGION_VISIBILITY.ALWAYS,
160174
highlightMode: "coverage",
161175
flags: {
@@ -171,7 +185,7 @@ export default class TemplatePlacement {
171185
spellLevel: rollData.item.level
172186
}
173187
}
174-
}, createData)];
188+
}, createData) : null).filter(_ => _);
175189

176190
/**
177191
* A hook event that fires after templates have been placed by the player but before they have been created.

module/config.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2830,7 +2830,7 @@ DND5E.areaTargetTypes = {
28302830
radius: {
28312831
label: "DND5E.TARGET.Type.Emanation.Label",
28322832
counted: "DND5E.TARGET.Type.Emanation.Counted",
2833-
template: "circle",
2833+
template: "emanation",
28342834
standard: true
28352835
},
28362836
sphere: {

0 commit comments

Comments
 (0)