Here's a function for finding all the roll20 map objects near a given roll20 map object, or location:
static Roll20Type(o) {
const validTypes = [
'ability', 'attribute', 'campaign', 'card', 'character', 'deck',
'graphic', 'hand', 'handout', 'jukeboxtrack', 'macro', 'message', 'page',
'path', 'player', 'rollabletable', 'tableitem', 'text', 'token'
];
if('function' === typeof o.get){
let t = o.get('type');
if(validTypes.includes(t)){
return t;
}
}
}
static Roll20MapObjectType(o) {
const validTypes = ['path','text','graphic'];
let t = Util.Roll20Type(o);
if(validTypes.includes(t)){
return t;
}
}
}
const findNearObjs = (location, predicate=()=>true) => {
let x=0;
let y=0;
let pageid = Campaign().get('playerpageid');
let pred = predicate;
if(Util.Roll20MapObjectType(location)){
pred = (o,d)=>location.id !== o.id && predicate(o,d);
x = location.get('left');
y = location.get('top');
pageid= location.get('pageid');
} else {
x = location.x || x;
y = location.y || y;
pageid = location.pageid || pageid;
}
const distSq = (obj) => Math.pow((obj.get('left')-x),2)+Math.pow((obj.get('top')-y),2);
const compareDistSq = (a,b) => a.distSq-b.distSq;
return findObjs({ pageid })
.map(o=>({distSq:distSq(o),object:o}))
.filter((o)=>pred(o.object,o.distSq))
.sort(compareDistSq);
};
The function is findNearObjs(), and takes two arguments: A location object. This can either be a roll20 object with a location (path, text, or graphic), or a simple Javascript object with an x, y, and pageid property. It will default to 0, 0, and the current ribbon page. A predicate function which will be applied to each found object. It will be supplied the object as the first parameter and the squared distance from the test location/object as the second parameter. The function returns a sorted array of objects based on their distance from the test location/object. Each entry in the array has two properties: object - This is the roll20 object that was found. distSq - This is the squared distance from the test location/object. Note: Squared distances are used for efficiency as square roots are expensive to perform and the relationship between distances and squared distances are preserved. Here's an example script using it, which you can call by selecting a token: !find-near or by providing a location: !find-near 700 700 The script demonstrates different predicate functions as well as dealing with the resulting structure. Code: on('ready',()=>{
class Util {
static Roll20Type(o) {
const validTypes = [
'ability', 'attribute', 'campaign', 'card', 'character', 'deck',
'graphic', 'hand', 'handout', 'jukeboxtrack', 'macro', 'message', 'page',
'path', 'player', 'rollabletable', 'tableitem', 'text', 'token'
];
if('function' === typeof o.get){
let t = o.get('type');
if(validTypes.includes(t)){
return t;
}
}
}
static Roll20MapObjectType(o) {
const validTypes = ['path','text','graphic'];
let t = Util.Roll20Type(o);
if(validTypes.includes(t)){
return t;
}
}
}
const findNearObjs = (location, predicate=()=>true) => {
let x=0;
let y=0;
let pageid = Campaign().get('playerpageid');
let pred = predicate;
if(Util.Roll20MapObjectType(location)){
pred = (o,d)=>location.id !== o.id && predicate(o,d);
x = location.get('left');
y = location.get('top');
pageid= location.get('pageid');
} else {
x = location.x || x;
y = location.y || y;
pageid = location.pageid || pageid;
}
const distSq = (obj) => Math.pow((obj.get('left')-x),2)+Math.pow((obj.get('top')-y),2);
const compareDistSq = (a,b) => a.distSq-b.distSq;
return findObjs({ pageid })
.map(o=>({distSq:distSq(o),object:o}))
.filter((o)=>pred(o.object,o.distSq))
.sort(compareDistSq);
};
const getPageForPlayer = (playerid) => {
let player = getObj('player',playerid);
if(playerIsGM(playerid)){
return player.get('lastpage') || Campaign().get('playerpageid');
}
let psp = Campaign().get('playerspecificpages');
if(psp[playerid]){
return psp[playerid];
}
return Campaign().get('playerpageid');
};
on('chat:message',msg=>{
if('api'===msg.type && /^!find-near(\b\s|$)/i.test(msg.content) && playerIsGM(msg.playerid)){
let who = (getObj('player',msg.playerid)||{get:()=>'API'}).get('_displayname');
let token = (msg.selected || [])
.map(o=>getObj('graphic',o._id))
.filter(g=>undefined !== g)
[0]
;
let minDist = Math.pow(1.5 * 70,2);
let maxDist = Math.pow(5.5 * 70,2);
if(token){
//let near = findNearObjs(token,(t,dsq)=>['objects'].includes(t.get('layer')) && 'graphic'===t.get('type') && dsq>minDist && dsq < maxDist);
let near = findNearObjs(token,(t,dsq)=>
['gmlayer','objects'].includes(t.get('layer'))
&& 'graphic'===t.get('type')
&& dsq > minDist
&& dsq < maxDist
);
sendChat('',`/w "${who}" <div></div>Near token ${token.get('name')} (min: ${Math.sqrt(minDist).toFixed(2)}, max: ${Math.sqrt(maxDist).toFixed(2)}):</div><ol>${near.map(o=>`<li><b>${Math.sqrt(o.distSq).toFixed(2)}</b> ${o.object.get('name')}</li>`).join('')}</ol></div>`);
} else {
let pageid = getPageForPlayer(msg.playerid);
let args = msg.content.split(/\s+/).slice(1);
let loc = {
x:parseInt(args[0])||0,
y:parseInt(args[1])||0,
pageid
};
let near = findNearObjs(loc,(t)=>['gmlayer','objects'].includes(t.get('layer')) && 'graphic'===t.get('type'));
sendChat('',`/w "${who}" <div></div>Near location (${loc.x},${loc.y}):</div><ol>${near.map(o=>`<li><b>${Math.sqrt(o.distSq).toFixed(2)}</b> ${o.object.get('name')}</li>`).join('')}</ol></div>`);
}
}
});
});