Created
July 24, 2014 19:18
-
-
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.
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
/** | |
* 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