Last active
December 22, 2018 17:36
-
-
Save SplenectomY/f30a7e562d6ba1ae922ef96156160428 to your computer and use it in GitHub Desktop.
Blocks tokens from going out "windows" on a Roll20 map, but allows them to see through it!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This script will block tokens from going through "windows", | |
// or other transparent obstacles but allow sight through them. | |
// Will work for any paths drawn, even freehand and circles. | |
// (Circle/oval detection accuracy is reduced to save on processing time, | |
// because it's WAY harder to determine the intersect of an elipsoid vs. | |
// a line rather than line segments vs. a line. For better accuracy on curves, | |
// draw it freehand. Excessive use of this might cause slow performance) | |
// Default color for detecting the window path is yellow (#ffff00) | |
// You may change this color in the config below | |
// By SplenectomY | |
// https://github.com/SplenectomY | |
on("ready", function() | |
{ | |
// Begin Config | |
const debug = false; | |
// Verbose logging to console | |
const pathColor = "#ffff00"; // yellow | |
// Color the script will look for when finding window drawings | |
const sendMsgToChat = true; | |
// If true, a message will be sent to chat | |
// when a character's movement is blocked by this script | |
const messageToSend = "$TOKENNAME$ tried to go through a closed window! OUCH!"; | |
// message the narrator will send. | |
// $TOKENNAME$ will be replaced with the name of the token | |
const narrator = "Narrator"; | |
// name of the narrator who "sends" the message | |
// End Config | |
const FindIntersect = function(x1, y1, x2, y2, x3, y3, x4, y4) | |
{ | |
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) | |
return false; | |
const denominator = ((y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1)); | |
if (denominator === 0) | |
return false; | |
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator; | |
const ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator; | |
if (ua < 0 || ua > 1 || ub < 0 || ub > 1) | |
return false; | |
const x = x1 + ua * (x2 - x1); | |
const y = y1 + ua * (y2 - y1); | |
return {x, y}; | |
} | |
const WindowChecker = function(obj) | |
{ | |
try | |
{ | |
if (obj.get("layer") != "objects") | |
return; | |
let windows = findObjs( | |
{ | |
_pageid: obj.get("_pageid"), | |
_type: "path", | |
stroke: pathColor, | |
layer: "gmlayer" | |
}); | |
if (debug) | |
log("Found " + windows.length + " windows!"); | |
const lastmoveSplit = obj.get("lastmove").split(","); | |
if (lastmoveSplit.length < 2) | |
return; | |
let xCoords = []; | |
let yCoords = []; | |
let tokenPoints = []; | |
let j = 0; | |
while (j <= lastmoveSplit.length - 1) | |
{ | |
if (j % 2 === 0 || j === 0) | |
xCoords.push(lastmoveSplit[j]); | |
else | |
yCoords.push(lastmoveSplit[j]); | |
j++; | |
} | |
j = 0; | |
while (j <= xCoords.length - 1) | |
{ | |
tokenPoints.push({"x":xCoords[j],"y":yCoords[j]}); | |
j++; | |
} | |
tokenPoints.push({"x":obj.get("left"),"y":obj.get("top")}); | |
const lastLeft = lastmoveSplit[lastmoveSplit.length - 2]; | |
const lastTop = lastmoveSplit[lastmoveSplit.length - 1]; | |
const firstLeft = lastmoveSplit[0]; | |
const firstTop = lastmoveSplit[1]; | |
if (debug) | |
{ | |
log("lastmoveSplit = " + lastmoveSplit); | |
log("lastLeft = " + lastLeft); | |
log("lastTop = " + lastTop); | |
log("firstLeft = " + firstLeft); | |
log("firstTop = " + firstTop); | |
} | |
_.each(windows, function(win) | |
{ | |
const segments = JSON.parse(win.get("_path")); | |
let points = []; | |
let i = 0; | |
while (i <= segments.length - 1) | |
{ | |
let seg = segments[i]; | |
let x, y; | |
switch (seg[0]) | |
{ | |
case "M": | |
case "L": | |
x = seg[1]; | |
y = seg[2]; | |
break; | |
case "C": | |
x = seg[5]; | |
y = seg[6]; | |
break; | |
case "Q": | |
x = seg[3]; | |
y = seg[4]; | |
break; | |
} | |
points.push({"x":x,"y":y}); | |
i++; | |
} | |
i = 0; | |
while (i < points.length - 1) | |
{ | |
if (debug) | |
{ | |
log("seg start = " + starts[i][1] + "," + starts[i][2]); | |
log("seg end = " + stops[i][1] + "," + stops[i][2]); | |
} | |
var foundIntersect = false; | |
let k = 0; | |
while (k < tokenPoints.length - 1) | |
{ | |
if (FindIntersect((points[i].x - win.get("width")/2) + win.get("left"), | |
(points[i].y - win.get("height")/2) + win.get("top"), | |
(points[i + 1].x - win.get("width")/2) + win.get("left"), | |
(points[i + 1].y - win.get("height")/2) + win.get("top"), | |
tokenPoints[k].x, tokenPoints[k].y, tokenPoints[k + 1].x, tokenPoints[k + 1].y) != false) | |
{ | |
log("PathBlocker: Token '" + obj.get("name") + "' was blocked from transversing a path."); | |
if (sendMsgToChat && typeof narrator == "string" && typeof messageToSend == "string") | |
sendChat(narrator, messageToSend.replace("$TOKENNAME$", obj.get("name")), null, { noarchive: true }); | |
obj.set("left", parseInt(tokenPoints[k].x)); | |
obj.set("top", parseInt(tokenPoints[k].y)); | |
foundIntersect = true; | |
break; | |
} | |
k++; | |
} | |
if (foundIntersect) | |
break; | |
i++; | |
} | |
}); | |
} | |
catch(e) | |
{ | |
sendChat("PathBlocker", "/w gm An error has occurred. Check the log."); | |
log(e); | |
} | |
} | |
on ("change:graphic:left", function(obj) | |
{ WindowChecker(obj); }); | |
on ("change:graphic:top", function(obj) | |
{ WindowChecker(obj); }); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment