Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save TheWhiteWolves/80ddebfde4705cdaf3ba to your computer and use it in GitHub Desktop.
Save TheWhiteWolves/80ddebfde4705cdaf3ba to your computer and use it in GitHub Desktop.
Roll20 API script: Checks if a character token moved through a trapped square during its movement.
/**
* Advanced trap-detection script
*
* A script that checks the interpolation of a token's movement to detect
* whether they have passed through a square containing a trap.
*
* A trap can be any token on the GM layer for which the cobweb status is
* active. Flying tokens (ones with the fluffy-wing status or angel-outfit
* status active) will not set off traps unless the traps are also flying.
*
* This script works best for square traps equal or less than 2x2 squares or
* circular traps of any size.
*/
/**
* Checks if the specified graphic object passed through a point if it moved
* from movePt1 to movePt2.
* obj is the actual graphic object.
* movePt1 and movePt2 are 2D number arrays specifying the endpoints of
* the movement.
* newPath contains a string making up the travel path
* Returns true iff a trap was triggered.
*/
var checkTrapsDuringMovement = function(obj, movePt1, movePt2, newPath) {
var traps = findObjs({_pageid: Campaign().get("playerpageid"),
_type: "graphic",
status_cobweb: true,
layer: "gmlayer"});
// A list of any traps the character activated during this movement.
var triggered = new Array();
for(var i in traps) {
var trap = traps[i];
// log(trap);
// Nonflying traps can't affect flying tokens.
var isObjFlying = (obj.get("status_fluffy-wing") ||
obj.get("status_angel-outfit"));
var isTrapFlying = (trap.get("status_fluffy-wing") ||
trap.get("status_angel-outfit"));
// traps only affect flying objects if the traps are also flying.
if(!isObjFlying || isTrapFlying) {
// Proceed with some fancy vector calculus to tell if we've
// stepped through the trap during our movement.
var trapX = trap.get("left");
var trapY = trap.get("top");
var trapPt = [trapX, trapY];
// log("trapPt: " + trapPt);
// log("moveStart: " + movePt1);
// log("moveEnd: " + movePt2);
// We're assuming here that width = diamter.
var thresholdDist = (parseInt(trap.get("width")) + parseInt(obj.get("width")))/2;
// log("thresholdDist: " + thresholdDist)
// If we started in the trap, we set it off last time already.
// So, skip.
if(Math.round(vecLength(vec(movePt1, trapPt))) < thresholdDist) {
// log("already in trap.");
}
// We didn't start the movement in this trap.
else {
// Figure out the closest distance we came to the trap during
// the movement.
var dist = Math.round(ptSegDist(trapPt, movePt1, movePt2));
// log(dist);
// If we passed through the trap, activate an alert for the trap and
// move the token to the trap.
if(dist < thresholdDist) {
// log("It's a trap!");
triggered.push(trap);
}
}
}
} // end for traps
// We didn't activate any traps during this movement.
if(triggered.length === 0) {
return false;
}
// We activated a trap during this movement.
else {
// Figure out which trap was activated first durign the movement.
// That is, the trap closest to the start point of the movement.
var trap = null;
var bestDist = -1;
for(var i in triggered) {
var t = triggered[i];
var trapX = t.get("left");
var trapY = t.get("top");
var trapPt = [trapX, trapY];
var dist = vecLength(vec(movePt1, trapPt));
if(bestDist === -1 || dist < bestDist) {
trap = t;
bestDist = dist;
}
}
// Display an alert for the trap.
sendChat("Admiral Ackbar", "IT'S A TRAP!!!");
sendChat("Admiral Ackbar", obj.get("name") + " set off a trap!");
// Move the token to the trap.
var x = trap.get("left");
var y = trap.get("top");
newPath += x + "," + y;
// log("newPath: " + newPath);
obj.set("lastmove", newPath);
obj.set("left", x);
obj.set("top", y);
// If the trap has the "bleeding-eye" status active, it will be
// moved to the map layer when it is activated.
if(trap.get("status_bleeding-eye")) {
trap.set("layer","map");
toBack(trap);
}
return true;
}
}
/**
* When a graphic on the objects layer moves, run the script to see if it
* passed through any traps.
*/
on("change:graphic", function(obj, prev) {
// Objects on the GM layer don't set off traps.
if(obj.get("layer") === "objects") {
// Get all the points for the movement.
var movePts = obj.get("lastmove").split(",");
// log("movePts: " + movePts);
for(var i = 0; i < movePts.length; i++) {
movePts[i] = parseInt(movePts[i]);
}
movePts.push(parseInt(obj.get("left")));
movePts.push(parseInt(obj.get("top")));
// log("new movePts: " + movePts);
var newPath = movePts[0] + "," + movePts[1] + ",";
// Check each segment of movement to see if we triggered a trap.
for(var i = 0; i < movePts.length - 2; i+=2) {
var movePt1 = [movePts[i], movePts[i+1]];
var movePt2 = [movePts[i+2], movePts[i+3]];
var v = vec(movePt1, movePt2);
// log("move vector: " + v);
// log("length: " + vecLength(v));
// If we encountered a trap, we're done.
if(checkTrapsDuringMovement(obj, movePt1, movePt2, newPath)) {
return;
}
else {
newPath += movePt2[0] + "," + movePt2[1] + ",";
}
}
}
});
//////// Some vector math.
/** Returns an array representing a 2D vector from pt1 to pt2. */
var vec = function(pt1, pt2) {
return [pt2[0]-pt1[0], pt2[1]-pt1[1]];
}
/** Returns the length of an array representing a vector. */
var vecLength = function(vector) {
var length = 0;
for(var i=0; i < vector.length; i++) {
length += vector[i]*vector[i];
}
return Math.sqrt(length);
}
/** Returns an array representing the cross product of two 3D vectors. */
var vecCrossProduct = function(v1, v2) {
var x = v1[1]*v2[2] - v1[2]*v2[1];
var y = v1[2]*v2[0] - v1[0]*v2[2];
var z = v1[0]*v2[1] - v1[1]*v2[0];
return [x, y, z];
}
/** Returns the cross product of two vectors. */
var vecDotProduct = function(v1, v2) {
var result = 0;
for(var i = 0; i < v1.length; i++) {
result += v1[i]*v2[i];
}
return result;
}
/** Computes the distance from a point to an infinitely stretching line. */
var ptLineDist = function(pt, linePt1, linePt2) {
var a = vec(linePt1, linePt2);
var b = vec(linePt1, pt);
var lenA = vecLength(a);
var lenAxB = vecLength(vecCrossProduct(a, b));
if(lenA == 0) {
return NaN;
}
else {
return lenAxB/lenA;
}
}
/** Computes the distance from a point to a line segment. */
var ptSegDist = function(pt, linePt1, linePt2) {
var a = vec(linePt1, linePt2);
var b = vec(linePt1, pt);
a[2] = 0;
b[2] = 0;
// log(a);
// log(b);
var aDotb = vecDotProduct(a,b);
// log("aDotb = " + aDotb);
// Is pt behind linePt1?
if(aDotb < 0) {
// log("pt before");
return vecLength(vec(pt, linePt1));
}
// Is pt after linePt2?
else if(aDotb > vecDotProduct(a,a)) {
// log("pt after");
return vecLength(vec(pt, linePt2));
}
// Pt must be between linePt1 and linePt2.
else {
// log("pt between");
var lenA = vecLength(a);
var AxB = vecCrossProduct(a,b);
var lenAxB = vecLength(AxB);
// log("lenA = " + lenA);
// log("AxB = " + AxB);
// log("lenAxB = " + lenAxB);
return lenAxB/lenA;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment