From 0902c9e3c52a7b4dce5c5faf31821d1fc55a1a23 Mon Sep 17 00:00:00 2001 From: Cazra Date: Wed, 21 Jan 2015 21:55:19 -0500 Subject: [PATCH] Added It's A Trap, Token Collisions, and Vector Math scripts (all related). --- Its A Trap/ItsATrap.js | 115 ++++++ Its A Trap/README.md | 16 + Its A Trap/package.json | 24 ++ Token Collisions/TokenCollisions.js | 201 +++++++++++ Token Collisions/package.json | 16 + Vector Math/VecMath.js | 537 ++++++++++++++++++++++++++++ Vector Math/package.json | 10 + 7 files changed, 919 insertions(+) create mode 100644 Its A Trap/ItsATrap.js create mode 100644 Its A Trap/README.md create mode 100644 Its A Trap/package.json create mode 100644 Token Collisions/TokenCollisions.js create mode 100644 Token Collisions/package.json create mode 100644 Vector Math/VecMath.js create mode 100644 Vector Math/package.json diff --git a/Its A Trap/ItsATrap.js b/Its A Trap/ItsATrap.js new file mode 100644 index 0000000000..0dfd5cc62c --- /dev/null +++ b/Its A Trap/ItsATrap.js @@ -0,0 +1,115 @@ +/** + * 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. + */ +var ItsATrap = (function() { + + /** + * Returns the first trap a token collided with during its last movement. + * If it didn't collide with any traps, return false. + * @param {Graphic} token + * @return {Graphic || false} + */ + var getTrapCollision = function(token) { + var traps = getTrapTokens(); + traps = filterList(traps, function(trap) { + return !isTokenFlying(token) || isTokenFlying(trap); + }); + + return TokenCollisions.getFirstCollision(token, traps); + }; + + + /** + * Determines whether a token is currently flying. + * @param {Graphic} token + * @return {Boolean} + */ + var isTokenFlying = function(token) { + return (token.get("status_fluffy-wing") || + token.get("status_angel-outfit")); + }; + + + /** + * Moves the specified token to the same position as the trap. + * @param {Graphic} token + * @param {Graphic} trap + */ + var moveTokenToTrap = function(token, trap) { + var x = trap.get("left"); + var y = trap.get("top"); + + token.set("lastmove",""); + token.set("left", x); + token.set("top", y); + }; + + + + + /** + * Returns all trap tokens on the players' page. + */ + var getTrapTokens = function() { + var currentPageId = Campaign().get("playerpageid"); + return findObjs({_pageid: currentPageId, + _type: "graphic", + status_cobweb: true, + layer: "gmlayer"}); + }; + + + + /** + * Filters items out from a list using some filtering function. + * Only items for which the filtering function returns true are kept in the + * filtered list. + * @param {Object[]} list + * @param {Function} filterFunc Accepts an Object from list as a parameter. + * Returns true to keep the item, or false to + * discard. + * @return {Object[]} + */ + var filterList = function(list, filterFunc) { + var results = []; + for(var i=0; i 0) { + return _getNearestTokenToPoint(startPt, collisions); + } + else { + return false; + } + }; + + + /** + * @private + * For some token, this gets the list of tokens it collided with during its + * movement between two points, from some list of other tokens. + * @param {Graphic} token + * @param {Graphic[]} otherTokens + * @param {vec2} startPt + * @param {vec2} endPt + * @return {Graphic[]} + */ + var _getCollisionsInWaypoint = function(token, otherTokens, startPt, endPt) { + var result = []; + + for(var i in otherTokens) { + var other = otherTokens[i]; + if(_checkCollisionInWaypoint(token, other, startPt, endPt)) { + result.push(other); + } + } + + return result; + }; + + /** + * @private + * Checks if a token collides with the other token during its movement between + * two points. + * @param {Graphic} token The moving token + * @param {Graphic} other + * @param {vec2} startPt + * @param {vec2} endPt + * @return {Boolean} true iff there was a collision. + */ + var _checkCollisionInWaypoint = function(token, other, startPt, endPt) { + var otherPt = _getTokenPt(other); + + // We assume that all tokens are circular, therefore width = diameter. + var thresholdDist = (parseInt(other.get("width")) + parseInt(token.get("width")))/2; + + // Don't count the other token if our movement already started in it. + if(Math.round(VecMath.dist(startPt, otherPt)) >= thresholdDist) { + + // Figure out the closest distance we came to the other token during + // the movement. + var dist = Math.round(VecMath.ptSegDist(otherPt, startPt, endPt)); + if(dist < thresholdDist) { + return true; + } + } + + return false; + }; + + + /** + * @private + * Returns the token nearest to the specified point + * out of some list of tokens. + * @param {vec2} pt + * @param {Graphic[]} tokens + * @return {Graphic} + */ + var _getNearestTokenToPoint = function(pt, tokens) { + var result = null; + var bestDist = -1; + for(var i in tokens) { + var token = tokens[i]; + + var tokenPt = _getTokenPt(token); + var dist = VecMath.dist(pt, tokenPt); + + if(bestDist === -1 || dist < bestDist) { + result = token; + bestDist = dist; + } + } + + return result; + }; + + + /** + * @private + * Gets the position of a token. + * @param {Graphic} token + * @return {vec2} + */ + var _getTokenPt = function(token) { + var x = token.get("left"); + var y = token.get("top"); + return [x, y]; + }; + + + // The exposed API. + return { + getFirstCollision: getFirstCollision, + getCollisions: getCollisions + }; +})(); \ No newline at end of file diff --git a/Token Collisions/package.json b/Token Collisions/package.json new file mode 100644 index 0000000000..219c660c8f --- /dev/null +++ b/Token Collisions/package.json @@ -0,0 +1,16 @@ +{ + "name": "Token Collisions", + "version": "1.0", + "description": "A small library for detecting collisions among tokens.", + "authors": "Stephen Lindberg", + "roll20userid": 46544, + "dependencies": { + "Vector Math": "1.0" + }, + "modifies": { + "lastmove": "read", + "left": "read", + "top": "read" + }, + "conflicts": [] +} \ No newline at end of file diff --git a/Vector Math/VecMath.js b/Vector Math/VecMath.js new file mode 100644 index 0000000000..0c8d226beb --- /dev/null +++ b/Vector Math/VecMath.js @@ -0,0 +1,537 @@ +/** + * This is a small library for (mostly 2D) vector mathematics. + * Internally, the vectors used by this library are simple arrays of numbers. + * The functions provided by this library do not alter the input vectors, + * treating each vector as an immutable object. + */ +var VecMath = (function() { + + /** + * Adds two vectors. + * @param {vec} a + * @param {vec} b + * @return {vec} + */ + var add = function(a, b) { + var result = []; + for(var i=0; i tolerance) { + return false; + } + } + else if(a[i] != b[i]) + return false; + } + return true; + }; + + + + /** + * Returns the length of a vector. + * @param {vec} vector + * @return {number} + */ + var length = function(vector) { + var length = 0; + for(var i=0; i < vector.length; i++) { + length += vector[i]*vector[i]; + } + return Math.sqrt(length); + }; + + + + /** + * Computes the normalization of a vector - its unit vector. + * @param {vec} v + * @return {vec} + */ + var normalize = function(v) { + var vHat = []; + + var vLength = length(v); + for(var i=0; i < v.length; i++) { + vHat[i] = v[i]/vLength; + } + + return vHat; + }; + + + /** + * Computes the projection of vector b onto vector a. + * @param {vec} a + * @param {vec} b + * @return {vec} + */ + var projection = function(a, b) { + var scalar = scalarProjection(a, b); + var aHat = normalize(a); + + return scale(aHat, scalar); + }; + + + /** + * Computes the distance from a point to an infinitely stretching line. + * Works for either 2D or 3D points. + * @param {vec2 || vec3} pt + * @param {vec2 || vec3} linePt1 A point on the line. + * @param {vec2 || vec3} linePt2 Another point on the line. + * @return {number} + */ + var ptLineDist = function(pt, linePt1, linePt2) { + var a = vec(linePt1, linePt2); + var b = vec(linePt1, pt); + + // Make 2D vectors 3D to compute the cross product. + if(!a[2]) + a[2] = 0; + if(!b[2]) + b[2] = 0; + + var aHat = normalize(a); + var aHatCrossB = cross(aHat, b); + return length(aHatCrossB); + }; + + + /** + * Computes the distance from a point to a line segment. + * Works for either 2D or 3D points. + * @param {vec2 || vec3} pt + * @param {vec2 || vec3} linePt1 The start point of the segment. + * @param {vec2 || vec3} linePt2 The end point of the segment. + * @return {number} + */ + var ptSegDist = function(pt, linePt1, linePt2) { + var a = vec(linePt1, linePt2); + var b = vec(linePt1, pt); + var aDotb = dot(a,b); + + // Is pt behind linePt1? + if(aDotb < 0) { + return length(vec(pt, linePt1)); + } + + // Is pt after linePt2? + else if(aDotb > dot(a,a)) { + return length(vec(pt, linePt2)); + } + + // Pt must be between linePt1 and linePt2. + else { + return ptLineDist(pt, linePt1, linePt2); + } + }; + + + /** + * Computes the scalar projection of b onto a. + * @param {vec2} a + * @param {vec2} b + * @return {vec2} + */ + var scalarProjection = function(a, b) { + var aDotB = dot(a, b); + var aLength = length(a); + + return aDotB/aLength; + }; + + + + /** + * Computes a scaled vector. + * @param {vec2} v + * @param {number} scalar + * @return {vec2} + */ + var scale = function(v, scalar) { + var result = []; + + for(var i=0; i