Hello all, I recently posted a question asking about a specific way to move a targeted token a specified distance directly away (rounded to the nearest grid square) from a selected token using Token-Mod here: <a href="https://app.roll20.net/forum/post/12651687/token-mod-and-moving-targeted-tokens-away-from-a-selected-token" rel="nofollow">https://app.roll20.net/forum/post/12651687/token-mod-and-moving-targeted-tokens-away-from-a-selected-token</a> I had trouble getting things to work with what was suggested by Timmaugh on that thread, so I've spent the last week and a half learning enough JavaScript to write my own script to do what I needed, and I have had success up to the point of integrating TokenCollisions. I'm a little lost on where to start with it, which is my primary question. My script is provided below for reference, and takes the command "!displace <direction> <distance> @{target|token_id}". — Direction is either push or pull. — Distance is parsed as an integer and uses the grid size of the page the tokens are on, so that the distance input is essentially in grid squares. Additional questions I have: — Currently, the way I wrote this requires me to use SelectManager to specify a source token, I think because the msg.selected[0]._id is lost with the @{target|token_id} call dropping selection. I would like this to be able to function without dependency on SelectManager, if possible. — I would love to be able to target multiple tokens at once with this script a la "!displace <direction> <distance> <number of targets>". I think this would involve an extensive rewrite. — The new coordinates the token is moved to are rounded to the nearest multiple of 70 to snap the token to the grid, but I think that wouldn't work on pages where the grid size is not 70. — Any JavaScript-veteran suggestions on cleaning up this script for readability, simplicity, ease-of-use, or fixing unnoticed problems or oversights are exceedingly welcome. on('chat:message', function (msg) {
if (msg.type !== 'api') return;
if (!msg.content.startsWith('!displace')) return;
const args = msg.content.split(/\s+/);
// Usage error message
if (args.length < 4) {
sendChat('Token Displacement', '❌ Usage: !displace <direction> <distance> @{target|token_id}');
return;
}
//Direction error usage
if (args[1] != "push" && args[1] != "pull") {
sendChat('Token Displacement', '❌ Direction must be either push or pull.');
return;
}
//Distance and Target variables
const distanceUnits = parseFloat(args[2]);
const targetId = args[3];
//Distance error message
if (isNaN(distanceUnits) || distanceUnits <= 0) {
sendChat('Token Displacement', '❌ Displacement distance must be a positive number.');
return;
}
//Selected token error message
if (!msg.selected || msg.selected.length !== 1) {
sendChat('Token Displacement', '❌ Please select exactly one source token.');
return;
}
//Source and Target definitions
const source = getObj('graphic', msg.selected[0]._id); // SelectManager is currently necessary for overriding Source
const target = getObj('graphic', targetId);
//Source or Target error message
if (!source || !target) {
sendChat('Token Displacement', '❌ Invalid source or target token.');
return;
}
//Grid variable definitions
const page = getObj('page', source.get('pageid'));
const gridSize = page ? page.get('snapping_increment') * 70 : 70;
// Displacement distance variable
const displaceDistance = distanceUnits * gridSize;
//Source and Target coordinates
const sx = source.get('left');
const sy = source.get('top');
const tx = target.get('left');
const ty = target.get('top');
//Difference(Delta) between Target and Source coordinates
const dx = tx - sx;
const dy = ty - sy;
//Distance between Source and Target coordinates
const length = Math.hypot(dx,dy);
//Same position usage error
if (length === 0) {
sendChat('Token Displacement', '❌ The target token is in the same position as the source token.');
return;
}
// Normalized direction vector
const nx = dx / length;
const ny = dy / length;
// Rounding the coordinate changes
const xChange = (Math.round((nx * displaceDistance) / 70 ) * 70);
const yChange = (Math.round((ny * displaceDistance) / 70 ) * 70);
//Direction operator output variables
let xFinal, yFinal;
if (args[1] === "push") {
xFinal = tx + xChange;
yFinal = ty + yChange;
}
if (args[1] === "pull") {
xFinal = tx - xChange;
yFinal = ty - yChange;
}
// Apply push
target.set({
left: xFinal,
top: yFinal
});
});