Circles are a problem because their representation is interpreted as a line, rather than 4 Bézier curves. Here's a script that fixes all the circles on the DL layer by approximating them with polygons as you draw them. The number of line segments used is based on the size of the circle, but will be a minimum of 12 and a maximum of 196 (but it would need to be a huge circle!), approximately 2-3 every grid square: You can also fix existing circles on the Dynamic Lighting Layer by running: !dlcircle-fix Sometimes circles (or paths in general) can end up corrupted by scripts that are generating them. If you happen to run into that circumstance, you can run: !dlcircle-fix-clean And it will remove the broken ones (and will say (cleaned) instead of (skipped) ). Here's the script: on('ready',()=>{
const circleKey=['M','C','C','C','C'];
const simpleObject = (o)=>JSON.parse(JSON.stringify(o));
const approximateCircle = (w,h) => {
const w2=w/2;
const h2=h/2;
const at = (theta) => ({x: Math.cos(theta)*w2, y: Math.sin(theta)*h2});
const steps = Math.min(Math.max(Math.round( (Math.PI*2*Math.sqrt((w2*w2+h2*h2)/2))/35),4),50);
const stepSize = Math.PI/(2*steps);
let acc=[[],[],[],[]];
let th=0;
_.times(steps+1,()=>{
let pt=at(th);
acc[0].push([pt.x,pt.y]);
acc[1].push([-pt.x,pt.y]);
acc[2].push([-pt.x,-pt.y]);
acc[3].push([pt.x,-pt.y]);
th+=stepSize;
});
acc = acc[0].concat(
acc[1].reverse().slice(1),
acc[2].slice(1),
acc[3].reverse().slice(1)
);
return JSON.stringify(acc.map((v,i)=>([(i?'L':'M'),w2+v[0],h2+v[1]])));
};
const buildCircle = (p) => Object.assign(
simpleObject(p),
{
_path: approximateCircle(
p.get('width'),
p.get('height')
)
}
);
on('add:path',(p)=>{
if(p.get('layer')==='walls'){
let path = JSON.parse(p.get('path'));
if(_.pluck(path,0).every((v,i)=>v===circleKey[i])){
createObj('path',buildCircle(p));
p.remove();
}
}
});
on('chat:message',(msg)=>{
if('api'===msg.type && /^!dlcircle-fix\b/i.test(msg.content) && playerIsGM(msg.playerid)){
let clean = /^!dlcircle-fix-clean\b/i.test(msg.content),
fixed = 0,
invalid = 0;
findObjs({type:'path', layer:'walls'}).forEach((p) => {
try {
let path = JSON.parse(p.get('path'));
if(_.pluck(path,0).every((v,i)=>v===circleKey[i])){
createObj('path',buildCircle(p));
p.remove();
fixed++;
}
} catch (e) {
invalid++;
if(clean){
p.remove();
}
}
});
sendChat('DLCircle',`/w gm Fixed ${fixed} circle${1===fixed?'':'s'}, ${invalid} invalid circle${1===invalid?'':'s'} ${clean?'(cleaned)':'(skipped)'}`);
}
});
}); Support my work on If you use my scripts, want to contribute, and have the spare bucks to do so , go right ahead. However, please don't feel like you must contribute just to use them! I'd much rather have happy Roll20 users armed with my scripts than people not using them out of some sense of shame. Use them and be happy, completely guilt-free! Disclaimer: This Patreon campaign is not affiliated with Roll20; as such, contributions are voluntary and Roll20 cannot provide support or refunds for contributions.