From e9c6a06614b67b562b6186e3977935a2fd918eb2 Mon Sep 17 00:00:00 2001 From: manveti Date: Fri, 29 May 2015 20:26:44 -0700 Subject: [PATCH 1/8] Initial work on token path tracking --- TokenPath/Help.txt | 21 +++++ TokenPath/package.json | 10 +++ TokenPath/path.js | 175 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 206 insertions(+) create mode 100644 TokenPath/Help.txt create mode 100644 TokenPath/package.json create mode 100644 TokenPath/path.js diff --git a/TokenPath/Help.txt b/TokenPath/Help.txt new file mode 100644 index 0000000000..11f8c6a5d5 --- /dev/null +++ b/TokenPath/Help.txt @@ -0,0 +1,21 @@ +TokenPath + +TokenPath continuously tracks the movement of the token at the top of the turn +tracker. The path is automatically computed, but waypoints can be placed along +it to tweak it as the user desires. + + +Use: + +No special user action is required. Whenever the token at the top of the turn +tracker moves, its path is automatically updated. The path is automatically +cleared when the turn changes. + +The pips which display a token's path are controlled by the same user(s) as the +token in question. When a pip is moved, it creates a waypoint, and the path is +automatically updated to pass through that waypoint. Waypoints are ordered, so +creating a new waypoint B by dragging a pip between existing waypoints A and C +will create a path that passes through A, then B, then C. + +Pips display distance based on the page settings (although they are not updated +retroactively if the page settings are changed while a path is visible). diff --git a/TokenPath/package.json b/TokenPath/package.json new file mode 100644 index 0000000000..14048db2c2 --- /dev/null +++ b/TokenPath/package.json @@ -0,0 +1,10 @@ +{ + "name": "TokenPath", + "version": "0.1", + "description": "Track movement of token at top of tracker, displaying its path for the round.", + "authors": "manveti", + "roll20userid": "503018", + "dependencies": {}, + "modifies": {}, + "conflicts": [] +} diff --git a/TokenPath/path.js b/TokenPath/path.js new file mode 100644 index 0000000000..820b4dd24d --- /dev/null +++ b/TokenPath/path.js @@ -0,0 +1,175 @@ +var TokenPath = TokenPath || { + PIP_IMAGE: "https://s3.amazonaws.com/files.d20.io/images/9817292/f_tAiMi01sv2nba2Uuakig/thumb.png?1432944100", + PIP_SIZE: 30, + WAYPOINT_TINT: "#8080ff", + GRID_SIZE: 70, + + init: function(){ + if (!state.hasOwnProperty('TokenPath')){ state.TokenPath = {}; } + if (!state.TokenPath.hasOwnProperty('pips')){ state.TokenPath.pips = []; } + if (!state.TokenPath.hasOwnProperty('waypoints')){ state.TokenPath.waypoints = []; } + }, + + handleTurnChange: function(newTurnOrder, oldTurnOrder){ + var newTurns = JSON.parse((typeof(newTurnOrder) == typeof("") ? newTurnOrder : newTurnOrder.get('turnorder') || "[]")); + var oldTurns = JSON.parse((typeof(oldTurnOrder) == typeof("") ? oldTurnOrder : oldTurnOrder.turnorder || "[]")); + + if ((!newTurns) || (!newTurns.length)){ return; } // nothing in tracker + if ((oldTurns) && (oldTurns.length) && (newTurns[0].id == oldTurns[0].id)){ return; } // turn didn't change + + // remove existing path + state.TokenPath.waypoints = []; + while (state.TokenPath.pips.length > 0){ + var pip = state.TokenPath.pips.pop(); + if (pip.token){ + var tok = getObj("graphic", pip.token); + if (tok){ tok.remove(); } + } + } + + // start new path if current turn is for a valid token + var tok = getObj("graphic", newTurns[0].id); + if (!tok){ return; } + state.TokenPath.pips.push({'x': tok.get('left'), 'y': tok.get('top'), 'distance': 0, 'round': 0}); + }, + + handleTokenMove: function(tok, prev){ + function drawPath(src, dest, diag, scale){ + // draw a path from pip src to d, excluding src; return array of pips, adding pip token for all but dest + var retval = []; + + var xOff = dest.x - src.x, yOff = dest.y - src.y; + if (xOff % TokenPath.GRID_SIZE){ xOff = Math.round(xOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } + if (yOff % TokenPath.GRID_SIZE){ yOff = Math.round(yOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } + + var pip = src; + while ((xOff != 0) || (yOff != 0)){ + var xDir = (xOff ? xOff / Math.abs(xOff) : 0), yDir = (yOff ? yOff / Math.abs(yOff) : 0); + var xStep = xDir * TokenPath.GRID_SIZE, yStep = yDir * TokenPath.GRID_SIZE; + if (retval.length > 0){ + //draw token for pip + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': pip.x, + 'top': pip.y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "" + (Math.round(pip.distance * 100) / 100), + 'controlledby': tok.get('controlledby'), + 'showname': true, + 'showplayers_name': true}); + pip.token = pipTok.id; + toFront(pipTok); + } + var distance = pip.distance, round = pip.round; + if ((xStep == 0) || (yStep == 0) || (diag == "foure")){ distance += scale; } + else if (diag == "manhattan"){ distance += 2 * scale; } + else if (diag == "threefive"){ + distance += scale * (1 + round); + round = 1 - round; + } + else{ distance += Math.sqrt(2) * scale; } + pip = {'x': pip.x + xStep, + 'y': pip.y + yStep, + 'distance': distance, + 'round': round} + retval.push(pip); + xOff -= xStep; + yOff -= yStep; + } + if (retval.length > 0){ + retval[retval.length - 1].x = dest.x; + retval[retval.length - 1].y = dest.y; + } + return retval; + } + + var page = getObj("page", tok.get('pageid')); + var diag = page.get('diagonaltype'), scale = page.get('scale_number'); + + var pipIdx; + for (pipIdx = 0; pipIdx < state.TokenPath.pips.length; pipIdx++){ + if (state.TokenPath.pips[pipIdx].token == tok.id){ break; } + } + if (pipIdx < state.TokenPath.pips.length){ + // tok is a pip token + var xOff = tok.get('left') % TokenPath.GRID_SIZE, yOff = tok.get('top') % TokenPath.GRID_SIZE, expOff = TokenPath.GRID_SIZE / 2; + if ((xOff != expOff) || (yOff != expOff)){ + // move pip to center of square + tok.set({'left': tok.get('left') + expOff - xOff, 'top': tok.get('top') + expOff - yOff}); + } +///// +// + //if pipIdx==0: create a new waypoint at start of state.TokenPath.waypoints + //elif tok is a waypoint: move that waypoint + //else: create a new waypoint between waypoints to either side of pip + //recalculate affected subpaths; delete affected pip tokens and create new ones (new pip tokens on same layer as existing ones) + //be sure to update distances in later path segments (both in pips and in tokens) +// +///// + return; + } + + var turnOrder = JSON.parse(Campaign().get('turnorder') || "[]"); + if ((!turnOrder) || (!turnOrder.length) || (tok.id != turnOrder[0].id)){ return; } // it isn't tok's turn; ignore its movement + + // if we get here, tok is at the top of the turn order; track its movement + if (!state.TokenPath.pips[0].token){ + // initial pip not created yet; do so + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': state.TokenPath.pips[0].x, + 'top': state.TokenPath.pips[0].y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "0", + 'controlledby': tok.get('controlledby'), + 'tint_color': TokenPath.WAYPOINT_TINT, + 'showname': true, + 'showplayers_name': true}); + state.TokenPath.pips[0].token = pipTok.id; + toFront(pipTok); + } + + // delete last segment of path + var lastGoodPip = (state.TokenPath.waypoints.length > 0 ? state.TokenPath.waypoints[state.TokenPath.waypoints.length - 1] : 0); + while (state.TokenPath.pips.length > lastGoodPip + 1){ + var pip = state.TokenPath.pips.pop(); + if (pip.token){ + var pipTok = getObj("graphic", pip.token); + if (pipTok){ pipTok.remove(); } + } + } + + // generate new path from last good pip to tok's current position + var newPips = drawPath(state.TokenPath.pips[lastGoodPip], {'x': tok.get('left'), 'y': tok.get('top')}, diag, scale); + var lastPip = newPips[newPips.length - 1]; + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': lastPip.x, + 'top': lastPip.y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "" + (Math.round(lastPip.distance * 100) / 100), + 'controlledby': tok.get('controlledby'), + 'showname': true, + 'showplayers_name': true}); + lastPip.token = pipTok.id; + toFront(pipTok); + for (var i = 0; i < newPips.length; i++){ state.TokenPath.pips.push(newPips[i]); } + }, + + registerTokenPath: function(){ + TokenPath.init(); + on("change:campaign:turnorder", TokenPath.handleTurnChange); + on("change:graphic", TokenPath.handleTokenMove); //maybe change:graphic:left and change:graphic:top + } +}; + +on("ready", function(){ TokenPath.registerTokenPath(); }); From b92ab63d0379f900b9c51b57cfed54bfa2f8dd1e Mon Sep 17 00:00:00 2001 From: manveti Date: Sat, 30 May 2015 01:13:38 -0700 Subject: [PATCH 2/8] Add support for dragging pips to create waypoints. --- TokenPath/path.js | 110 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 100 insertions(+), 10 deletions(-) diff --git a/TokenPath/path.js b/TokenPath/path.js index 820b4dd24d..3f7fdc344a 100644 --- a/TokenPath/path.js +++ b/TokenPath/path.js @@ -1,6 +1,7 @@ var TokenPath = TokenPath || { PIP_IMAGE: "https://s3.amazonaws.com/files.d20.io/images/9817292/f_tAiMi01sv2nba2Uuakig/thumb.png?1432944100", PIP_SIZE: 30, + START_TINT: "#80ffff", WAYPOINT_TINT: "#8080ff", GRID_SIZE: 70, @@ -100,15 +101,104 @@ var TokenPath = TokenPath || { // move pip to center of square tok.set({'left': tok.get('left') + expOff - xOff, 'top': tok.get('top') + expOff - yOff}); } -///// -// - //if pipIdx==0: create a new waypoint at start of state.TokenPath.waypoints - //elif tok is a waypoint: move that waypoint - //else: create a new waypoint between waypoints to either side of pip - //recalculate affected subpaths; delete affected pip tokens and create new ones (new pip tokens on same layer as existing ones) - //be sure to update distances in later path segments (both in pips and in tokens) -// -///// + var wpIdx, isWp = false; + for (wpIdx = 0; wpIdx < state.TokenPath.waypoints.length; wpIdx++){ + if (state.TokenPath.waypoints[wpIdx] == pipIdx){ isWp = true; } + if (state.TokenPath.waypoints[wpIdx] >= pipIdx){ break; } + } + function updatePath(pathStart, pathEnd, wp){ + // remove old pips between pathStart and pathEnd (leaving endpoints in place) + for (var i = pathStart + 1; i < pathEnd; i++){ + if (!state.TokenPath.pips[i].token){ continue; } + var pipTok = getObj("graphic", state.TokenPath.pips[i].token); + if (pipTok){ pipTok.remove(); } + } + // draw a new path from pathStart to pathEnd + var newPath = drawPath(state.TokenPath.pips[pathStart], state.TokenPath.pips[pathEnd], diag, scale); + // update pathEnd + newPath[newPath.length - 1].token = state.TokenPath.pips[pathEnd].token; + var pipTok = getObj("graphic", newPath[newPath.length - 1].token); + pipTok.set({'name': "" + (Math.round(newPath[newPath.length - 1].distance * 100) / 100)}) + // splice in new path + var oldLen = pathEnd - pathStart, newLen = newPath.length, dLen = newLen - oldLen; + newPath.unshift(oldLen); + newPath.unshift(pathStart + 1); + state.TokenPath.pips.splice.apply(state.TokenPath.pips, newPath); + // update waypoints based on the length difference between the old and new paths + for (var i = wp; i < state.TokenPath.waypoints.length; i++){ + state.TokenPath.waypoints[i] += dLen; + } + return pathEnd + dLen; + } + if (pipIdx == 0){ + // tok was start point; create a new start point + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': state.TokenPath.pips[0].x, + 'top': state.TokenPath.pips[0].y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "0", + 'controlledby': tok.get('controlledby'), + 'tint_color': TokenPath.START_TINT, + 'showname': true, + 'showplayers_name': true}); + toFront(pipTok); + var startPip = {'x': state.TokenPath.pips[0].x, + 'y': state.TokenPath.pips[0].y, + 'distance': 0, + 'round': 0, + 'token': pipTok.id}; + state.TokenPath.pips.unshift(startPip); + for (var i = 0; i < state.TokenPath.waypoints.length; i++){ + state.TokenPath.waypoints[i] += 1; + } + pipIdx = 1; + } + state.TokenPath.pips[pipIdx].x = tok.get('left'); + state.TokenPath.pips[pipIdx].y = tok.get('top'); + var pathStart, pathEnd, newEnd; + if (isWp){ + // tok is already a waypoint; update paths leading into and out of it + pathStart = (wpIdx > 0 ? state.TokenPath.waypoints[wpIdx - 1] : 0); + pathEnd = pipIdx; + newEnd = updatePath(pathStart, pathEnd, wpIdx); + pathStart = newEnd; + pathEnd = (wpIdx + 1 < state.TokenPath.waypoints.length ? state.TokenPath.waypoints[wpIdx + 1] : state.TokenPath.pips.length - 1); + newEnd = updatePath(pathStart, pathEnd, wpIdx + 1); + } + else{ + // tok was not a waypoint; upgrade it to one and split path it was on into one in and one out of it + pathStart = (wpIdx > 0 ? state.TokenPath.waypoints[wpIdx - 1] : 0); + pathEnd = pipIdx; + newEnd = updatePath(pathStart, pathEnd, wpIdx); + state.TokenPath.waypoints.splice(wpIdx, 0, newEnd); + tok.set({'tint_color': TokenPath.WAYPOINT_TINT}); + pathStart = newEnd; + pathEnd = (wpIdx + 1 < state.TokenPath.waypoints.length ? state.TokenPath.waypoints[wpIdx + 1] : state.TokenPath.pips.length - 1); + newEnd = updatePath(pathStart, pathEnd, wpIdx + 1); + } + var allPips = state.TokenPath.pips; + var distance = allPips[newEnd].distance, round = allPips[newEnd].round; + for (var i = newEnd + 1; i < allPips.length; i++){ + if ((allPips[i].x == allPips[i - 1].x) || (allPips[i].y == allPips[i - 1].y) || diag == "foure"){ distance += scale; } + else if (diag == "manhattan"){ distance += 2 * scale; } + else if (diag == "threefive"){ + distance += scale * (1 + round); + round = 1 - round; + } + else{ distance += Math.sqrt(2) * scale; } + allPips[i].distance = distance; + allPips[i].round = round; + if (allPips[i].token){ + var pipTok = getObj("graphic", allPips[i].token); + if (pipTok){ + pipTok.set({'distance': distance, 'round': round}); + } + } + } return; } @@ -128,7 +218,7 @@ var TokenPath = TokenPath || { 'layer': tok.get('layer'), 'name': "0", 'controlledby': tok.get('controlledby'), - 'tint_color': TokenPath.WAYPOINT_TINT, + 'tint_color': TokenPath.START_TINT, 'showname': true, 'showplayers_name': true}); state.TokenPath.pips[0].token = pipTok.id; From 14425e82255a1d3548104ad369154918ca1f4e98 Mon Sep 17 00:00:00 2001 From: manveti Date: Sat, 30 May 2015 01:30:45 -0700 Subject: [PATCH 3/8] Fix problems moving last pip or moving token back to start without waypoints. --- TokenPath/path.js | 57 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 41 insertions(+), 16 deletions(-) diff --git a/TokenPath/path.js b/TokenPath/path.js index 3f7fdc344a..5f48626734 100644 --- a/TokenPath/path.js +++ b/TokenPath/path.js @@ -157,6 +157,29 @@ var TokenPath = TokenPath || { } pipIdx = 1; } + if (pipIdx == state.TokenPath.pips.length - 1){ + // tok was end point; create a new end point + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': state.TokenPath.pips[pipIdx].x, + 'top': state.TokenPath.pips[pipIdx].y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "" + (Math.round(state.TokenPath.pips[pipIdx].distance * 100) / 100), + 'controlledby': tok.get('controlledby'), + 'tint_color': TokenPath.START_TINT, + 'showname': true, + 'showplayers_name': true}); + toFront(pipTok); + var endPip = {'x': state.TokenPath.pips[pipIdx].x, + 'y': state.TokenPath.pips[pipIdx].y, + 'distance': state.TokenPath.pips[pipIdx].distance, + 'round': state.TokenPath.pips[pipIdx].round, + 'token': pipTok.id}; + state.TokenPath.pips.push(endPip); + } state.TokenPath.pips[pipIdx].x = tok.get('left'); state.TokenPath.pips[pipIdx].y = tok.get('top'); var pathStart, pathEnd, newEnd; @@ -237,22 +260,24 @@ var TokenPath = TokenPath || { // generate new path from last good pip to tok's current position var newPips = drawPath(state.TokenPath.pips[lastGoodPip], {'x': tok.get('left'), 'y': tok.get('top')}, diag, scale); - var lastPip = newPips[newPips.length - 1]; - var pipTok = createObj("graphic", {'_subtype': "token", - '_pageid': tok.get('pageid'), - 'imgsrc': TokenPath.PIP_IMAGE, - 'left': lastPip.x, - 'top': lastPip.y, - 'width': TokenPath.PIP_SIZE, - 'height': TokenPath.PIP_SIZE, - 'layer': tok.get('layer'), - 'name': "" + (Math.round(lastPip.distance * 100) / 100), - 'controlledby': tok.get('controlledby'), - 'showname': true, - 'showplayers_name': true}); - lastPip.token = pipTok.id; - toFront(pipTok); - for (var i = 0; i < newPips.length; i++){ state.TokenPath.pips.push(newPips[i]); } + if (newPips.length > 0){ + var lastPip = newPips[newPips.length - 1]; + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': lastPip.x, + 'top': lastPip.y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "" + (Math.round(lastPip.distance * 100) / 100), + 'controlledby': tok.get('controlledby'), + 'showname': true, + 'showplayers_name': true}); + lastPip.token = pipTok.id; + toFront(pipTok); + for (var i = 0; i < newPips.length; i++){ state.TokenPath.pips.push(newPips[i]); } + } }, registerTokenPath: function(){ From 3876bb6ed240cae8ad2ee76235968d5f22b254cc Mon Sep 17 00:00:00 2001 From: manveti Date: Sat, 30 May 2015 01:58:55 -0700 Subject: [PATCH 4/8] Handle pip deletion: delete waypoints, replace others. --- TokenPath/path.js | 232 ++++++++++++++++++++++++++++++---------------- 1 file changed, 154 insertions(+), 78 deletions(-) diff --git a/TokenPath/path.js b/TokenPath/path.js index 5f48626734..9473ab7993 100644 --- a/TokenPath/path.js +++ b/TokenPath/path.js @@ -5,12 +5,19 @@ var TokenPath = TokenPath || { WAYPOINT_TINT: "#8080ff", GRID_SIZE: 70, + ignoreRemoval: {}, + init: function(){ if (!state.hasOwnProperty('TokenPath')){ state.TokenPath = {}; } if (!state.TokenPath.hasOwnProperty('pips')){ state.TokenPath.pips = []; } if (!state.TokenPath.hasOwnProperty('waypoints')){ state.TokenPath.waypoints = []; } }, + removeToken: function(tok){ + TokenPath.ignoreRemoval[tok.id] = true; + tok.remove(); + }, + handleTurnChange: function(newTurnOrder, oldTurnOrder){ var newTurns = JSON.parse((typeof(newTurnOrder) == typeof("") ? newTurnOrder : newTurnOrder.get('turnorder') || "[]")); var oldTurns = JSON.parse((typeof(oldTurnOrder) == typeof("") ? oldTurnOrder : oldTurnOrder.turnorder || "[]")); @@ -24,7 +31,7 @@ var TokenPath = TokenPath || { var pip = state.TokenPath.pips.pop(); if (pip.token){ var tok = getObj("graphic", pip.token); - if (tok){ tok.remove(); } + if (tok){ TokenPath.removeToken(tok); } } } @@ -34,59 +41,85 @@ var TokenPath = TokenPath || { state.TokenPath.pips.push({'x': tok.get('left'), 'y': tok.get('top'), 'distance': 0, 'round': 0}); }, - handleTokenMove: function(tok, prev){ - function drawPath(src, dest, diag, scale){ - // draw a path from pip src to d, excluding src; return array of pips, adding pip token for all but dest - var retval = []; + drawPath: function(src, dest, diag, scale, pageId, layer, controlledBy){ + // draw a path from pip src to d, excluding src; return array of pips, adding pip token for all but dest + var retval = []; - var xOff = dest.x - src.x, yOff = dest.y - src.y; - if (xOff % TokenPath.GRID_SIZE){ xOff = Math.round(xOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } - if (yOff % TokenPath.GRID_SIZE){ yOff = Math.round(yOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } + var xOff = dest.x - src.x, yOff = dest.y - src.y; + if (xOff % TokenPath.GRID_SIZE){ xOff = Math.round(xOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } + if (yOff % TokenPath.GRID_SIZE){ yOff = Math.round(yOff / TokenPath.GRID_SIZE) * TokenPath.GRID_SIZE; } - var pip = src; - while ((xOff != 0) || (yOff != 0)){ - var xDir = (xOff ? xOff / Math.abs(xOff) : 0), yDir = (yOff ? yOff / Math.abs(yOff) : 0); - var xStep = xDir * TokenPath.GRID_SIZE, yStep = yDir * TokenPath.GRID_SIZE; - if (retval.length > 0){ - //draw token for pip - var pipTok = createObj("graphic", {'_subtype': "token", - '_pageid': tok.get('pageid'), - 'imgsrc': TokenPath.PIP_IMAGE, - 'left': pip.x, - 'top': pip.y, - 'width': TokenPath.PIP_SIZE, - 'height': TokenPath.PIP_SIZE, - 'layer': tok.get('layer'), - 'name': "" + (Math.round(pip.distance * 100) / 100), - 'controlledby': tok.get('controlledby'), - 'showname': true, - 'showplayers_name': true}); - pip.token = pipTok.id; - toFront(pipTok); - } - var distance = pip.distance, round = pip.round; - if ((xStep == 0) || (yStep == 0) || (diag == "foure")){ distance += scale; } - else if (diag == "manhattan"){ distance += 2 * scale; } - else if (diag == "threefive"){ - distance += scale * (1 + round); - round = 1 - round; - } - else{ distance += Math.sqrt(2) * scale; } - pip = {'x': pip.x + xStep, - 'y': pip.y + yStep, - 'distance': distance, - 'round': round} - retval.push(pip); - xOff -= xStep; - yOff -= yStep; - } + var pip = src; + while ((xOff != 0) || (yOff != 0)){ + var xDir = (xOff ? xOff / Math.abs(xOff) : 0), yDir = (yOff ? yOff / Math.abs(yOff) : 0); + var xStep = xDir * TokenPath.GRID_SIZE, yStep = yDir * TokenPath.GRID_SIZE; if (retval.length > 0){ - retval[retval.length - 1].x = dest.x; - retval[retval.length - 1].y = dest.y; + //draw token for pip + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': pageId, + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': pip.x, + 'top': pip.y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': layer, + 'name': "" + (Math.round(pip.distance * 100) / 100), + 'controlledby': controlledBy, + 'showname': true, + 'showplayers_name': true}); + pip.token = pipTok.id; + toFront(pipTok); + } + var distance = pip.distance, round = pip.round; + if ((xStep == 0) || (yStep == 0) || (diag == "foure")){ distance += scale; } + else if (diag == "manhattan"){ distance += 2 * scale; } + else if (diag == "threefive"){ + distance += scale * (1 + round); + round = 1 - round; } - return retval; + else{ distance += Math.sqrt(2) * scale; } + pip = {'x': pip.x + xStep, + 'y': pip.y + yStep, + 'distance': distance, + 'round': round} + retval.push(pip); + xOff -= xStep; + yOff -= yStep; + } + if (retval.length > 0){ + retval[retval.length - 1].x = dest.x; + retval[retval.length - 1].y = dest.y; } + return retval; + }, + updatePath: function(pathStart, pathEnd, wp, diag, scale, pageId, layer, controlledBy){ + // remove old pips between pathStart and pathEnd (leaving endpoints in place) + for (var i = pathStart + 1; i < pathEnd; i++){ + if (!state.TokenPath.pips[i].token){ continue; } + var pipTok = getObj("graphic", state.TokenPath.pips[i].token); + if (pipTok){ TokenPath.removeToken(pipTok); } + } + // draw a new path from pathStart to pathEnd + var newPath = TokenPath.drawPath(state.TokenPath.pips[pathStart], state.TokenPath.pips[pathEnd], + diag, scale, pageId, layer, controlledBy); + // update pathEnd + newPath[newPath.length - 1].token = state.TokenPath.pips[pathEnd].token; + var pipTok = getObj("graphic", newPath[newPath.length - 1].token); + pipTok.set({'name': "" + (Math.round(newPath[newPath.length - 1].distance * 100) / 100)}) + // splice in new path + var oldLen = pathEnd - pathStart, newLen = newPath.length, dLen = newLen - oldLen; + newPath.unshift(oldLen); + newPath.unshift(pathStart + 1); + state.TokenPath.pips.splice.apply(state.TokenPath.pips, newPath); + // update waypoints based on the length difference between the old and new paths + for (var i = wp; i < state.TokenPath.waypoints.length; i++){ + state.TokenPath.waypoints[i] += dLen; + } + return pathEnd + dLen; + }, + + handleTokenMove: function(tok, prev){ var page = getObj("page", tok.get('pageid')); var diag = page.get('diagonaltype'), scale = page.get('scale_number'); @@ -106,30 +139,6 @@ var TokenPath = TokenPath || { if (state.TokenPath.waypoints[wpIdx] == pipIdx){ isWp = true; } if (state.TokenPath.waypoints[wpIdx] >= pipIdx){ break; } } - function updatePath(pathStart, pathEnd, wp){ - // remove old pips between pathStart and pathEnd (leaving endpoints in place) - for (var i = pathStart + 1; i < pathEnd; i++){ - if (!state.TokenPath.pips[i].token){ continue; } - var pipTok = getObj("graphic", state.TokenPath.pips[i].token); - if (pipTok){ pipTok.remove(); } - } - // draw a new path from pathStart to pathEnd - var newPath = drawPath(state.TokenPath.pips[pathStart], state.TokenPath.pips[pathEnd], diag, scale); - // update pathEnd - newPath[newPath.length - 1].token = state.TokenPath.pips[pathEnd].token; - var pipTok = getObj("graphic", newPath[newPath.length - 1].token); - pipTok.set({'name': "" + (Math.round(newPath[newPath.length - 1].distance * 100) / 100)}) - // splice in new path - var oldLen = pathEnd - pathStart, newLen = newPath.length, dLen = newLen - oldLen; - newPath.unshift(oldLen); - newPath.unshift(pathStart + 1); - state.TokenPath.pips.splice.apply(state.TokenPath.pips, newPath); - // update waypoints based on the length difference between the old and new paths - for (var i = wp; i < state.TokenPath.waypoints.length; i++){ - state.TokenPath.waypoints[i] += dLen; - } - return pathEnd + dLen; - } if (pipIdx == 0){ // tok was start point; create a new start point var pipTok = createObj("graphic", {'_subtype': "token", @@ -187,21 +196,21 @@ var TokenPath = TokenPath || { // tok is already a waypoint; update paths leading into and out of it pathStart = (wpIdx > 0 ? state.TokenPath.waypoints[wpIdx - 1] : 0); pathEnd = pipIdx; - newEnd = updatePath(pathStart, pathEnd, wpIdx); + newEnd = TokenPath.updatePath(pathStart, pathEnd, wpIdx, diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); pathStart = newEnd; pathEnd = (wpIdx + 1 < state.TokenPath.waypoints.length ? state.TokenPath.waypoints[wpIdx + 1] : state.TokenPath.pips.length - 1); - newEnd = updatePath(pathStart, pathEnd, wpIdx + 1); + newEnd = TokenPath.updatePath(pathStart, pathEnd, wpIdx + 1, diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); } else{ // tok was not a waypoint; upgrade it to one and split path it was on into one in and one out of it pathStart = (wpIdx > 0 ? state.TokenPath.waypoints[wpIdx - 1] : 0); pathEnd = pipIdx; - newEnd = updatePath(pathStart, pathEnd, wpIdx); + newEnd = TokenPath.updatePath(pathStart, pathEnd, wpIdx, diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); state.TokenPath.waypoints.splice(wpIdx, 0, newEnd); tok.set({'tint_color': TokenPath.WAYPOINT_TINT}); pathStart = newEnd; pathEnd = (wpIdx + 1 < state.TokenPath.waypoints.length ? state.TokenPath.waypoints[wpIdx + 1] : state.TokenPath.pips.length - 1); - newEnd = updatePath(pathStart, pathEnd, wpIdx + 1); + newEnd = TokenPath.updatePath(pathStart, pathEnd, wpIdx + 1, diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); } var allPips = state.TokenPath.pips; var distance = allPips[newEnd].distance, round = allPips[newEnd].round; @@ -254,12 +263,13 @@ var TokenPath = TokenPath || { var pip = state.TokenPath.pips.pop(); if (pip.token){ var pipTok = getObj("graphic", pip.token); - if (pipTok){ pipTok.remove(); } + if (pipTok){ TokenPath.removeToken(pipTok); } } } // generate new path from last good pip to tok's current position - var newPips = drawPath(state.TokenPath.pips[lastGoodPip], {'x': tok.get('left'), 'y': tok.get('top')}, diag, scale); + var newPips = TokenPath.drawPath(state.TokenPath.pips[lastGoodPip], {'x': tok.get('left'), 'y': tok.get('top')}, + diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); if (newPips.length > 0){ var lastPip = newPips[newPips.length - 1]; var pipTok = createObj("graphic", {'_subtype': "token", @@ -280,10 +290,76 @@ var TokenPath = TokenPath || { } }, + handleTokenDelete: function(tok){ + if (TokenPath.ignoreRemoval[tok.id]){ + delete TokenPath.ignoreRemoval[tok.id]; + return; + } + + var idx; + + for (idx = 0; idx < state.TokenPath.waypoints.length; idx++){ + if (state.TokenPath.pips[state.TokenPath.waypoints[idx]].token == tok.id){ break; } + } + if (idx < state.TokenPath.waypoints.length){ + // tok was a waypoint; delete waypoint + state.TokenPath.waypoints.splice(idx, 1); + var page = getObj("page", tok.get('pageid')); + var diag = page.get('diagonaltype'), scale = page.get('scale_number'); + var pathStart = (idx > 0 ? state.TokenPath.waypoints[idx - 1] : 0); + var pathEnd = (idx < state.TokenPath.waypoints.length ? state.TokenPath.waypoints[idx] : state.TokenPath.pips.length - 1); + var newEnd = TokenPath.updatePath(pathStart, pathEnd, idx, diag, scale, tok.get('pageid'), tok.get('layer'), tok.get('controlledby')); + var allPips = state.TokenPath.pips; + var distance = allPips[newEnd].distance, round = allPips[newEnd].round; + for (var i = newEnd + 1; i < allPips.length; i++){ + if ((allPips[i].x == allPips[i - 1].x) || (allPips[i].y == allPips[i - 1].y) || diag == "foure"){ distance += scale; } + else if (diag == "manhattan"){ distance += 2 * scale; } + else if (diag == "threefive"){ + distance += scale * (1 + round); + round = 1 - round; + } + else{ distance += Math.sqrt(2) * scale; } + allPips[i].distance = distance; + allPips[i].round = round; + if (allPips[i].token){ + var pipTok = getObj("graphic", allPips[i].token); + if (pipTok){ + pipTok.set({'distance': distance, 'round': round}); + } + } + } + return; + } + + for (idx = 0; idx < state.TokenPath.pips.length; idx++){ + if (state.TokenPath.pips[idx].token == tok.id){ break; } + } + if (idx < state.TokenPath.pips.length){ + // tok was a non-waypoint pip; replace pip + var pip = state.TokenPath.pips[idx]; + var pipTok = createObj("graphic", {'_subtype': "token", + '_pageid': tok.get('pageid'), + 'imgsrc': TokenPath.PIP_IMAGE, + 'left': pip.x, + 'top': pip.y, + 'width': TokenPath.PIP_SIZE, + 'height': TokenPath.PIP_SIZE, + 'layer': tok.get('layer'), + 'name': "" + (Math.round(pip.distance * 100) / 100), + 'controlledby': tok.get('controlledby'), + 'showname': true, + 'showplayers_name': true}); + pip.token = pipTok.id; + toFront(pipTok); + return; + } + }, + registerTokenPath: function(){ TokenPath.init(); on("change:campaign:turnorder", TokenPath.handleTurnChange); on("change:graphic", TokenPath.handleTokenMove); //maybe change:graphic:left and change:graphic:top + on("destroy:graphic", TokenPath.handleTokenDelete); } }; From 20797902f83be7334b16a2fc49987732529b2152 Mon Sep 17 00:00:00 2001 From: manveti Date: Sat, 30 May 2015 02:21:51 -0700 Subject: [PATCH 5/8] Flesh out the help a bit, now that the script has behavior to document. --- TokenPath/Help.txt | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/TokenPath/Help.txt b/TokenPath/Help.txt index 11f8c6a5d5..9ae5c41c70 100644 --- a/TokenPath/Help.txt +++ b/TokenPath/Help.txt @@ -1,21 +1,29 @@ TokenPath TokenPath continuously tracks the movement of the token at the top of the turn -tracker. The path is automatically computed, but waypoints can be placed along -it to tweak it as the user desires. +tracker, automatically computing the token's shortest path. Waypoints can be +placed to tweak the generated path as the user desires. Use: No special user action is required. Whenever the token at the top of the turn tracker moves, its path is automatically updated. The path is automatically -cleared when the turn changes. +cleared when the turn changes. Tokens moving when it is not their turn do not +generate paths. The pips which display a token's path are controlled by the same user(s) as the token in question. When a pip is moved, it creates a waypoint, and the path is automatically updated to pass through that waypoint. Waypoints are ordered, so creating a new waypoint B by dragging a pip between existing waypoints A and C -will create a path that passes through A, then B, then C. +will create a path that passes through A, then B, then C. Deleting a waypoint +will cause the path to be recomputed without that waypoint. Pips display distance based on the page settings (although they are not updated -retroactively if the page settings are changed while a path is visible). +retroactively if the page settings are changed while a path is visible). Note +that each step of the path will be fully into a square, so all travel will be +along the eight cardinal and ordinal directions. However, when a page is +configured to use Euclidean distance, the ruler tool does not follow this +restriction, and can draw a direct line between any two squares. As a result, +the ruler tool may report a different distance from that reported on the pips +when Euclidean distance is in use. From 6f7d39a89cb8cf8576bb3b793d1ffd41d83cedfa Mon Sep 17 00:00:00 2001 From: manveti Date: Sat, 30 May 2015 02:24:28 -0700 Subject: [PATCH 6/8] Only show one decimal place, to line up with ruler tool. --- TokenPath/path.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TokenPath/path.js b/TokenPath/path.js index 9473ab7993..b96ee30701 100644 --- a/TokenPath/path.js +++ b/TokenPath/path.js @@ -63,7 +63,7 @@ var TokenPath = TokenPath || { 'width': TokenPath.PIP_SIZE, 'height': TokenPath.PIP_SIZE, 'layer': layer, - 'name': "" + (Math.round(pip.distance * 100) / 100), + 'name': "" + (Math.round(pip.distance * 10) / 10), 'controlledby': controlledBy, 'showname': true, 'showplayers_name': true}); @@ -106,7 +106,7 @@ var TokenPath = TokenPath || { // update pathEnd newPath[newPath.length - 1].token = state.TokenPath.pips[pathEnd].token; var pipTok = getObj("graphic", newPath[newPath.length - 1].token); - pipTok.set({'name': "" + (Math.round(newPath[newPath.length - 1].distance * 100) / 100)}) + pipTok.set({'name': "" + (Math.round(newPath[newPath.length - 1].distance * 10) / 10)}) // splice in new path var oldLen = pathEnd - pathStart, newLen = newPath.length, dLen = newLen - oldLen; newPath.unshift(oldLen); @@ -176,7 +176,7 @@ var TokenPath = TokenPath || { 'width': TokenPath.PIP_SIZE, 'height': TokenPath.PIP_SIZE, 'layer': tok.get('layer'), - 'name': "" + (Math.round(state.TokenPath.pips[pipIdx].distance * 100) / 100), + 'name': "" + (Math.round(state.TokenPath.pips[pipIdx].distance * 10) / 10), 'controlledby': tok.get('controlledby'), 'tint_color': TokenPath.START_TINT, 'showname': true, @@ -280,7 +280,7 @@ var TokenPath = TokenPath || { 'width': TokenPath.PIP_SIZE, 'height': TokenPath.PIP_SIZE, 'layer': tok.get('layer'), - 'name': "" + (Math.round(lastPip.distance * 100) / 100), + 'name': "" + (Math.round(lastPip.distance * 10) / 10), 'controlledby': tok.get('controlledby'), 'showname': true, 'showplayers_name': true}); @@ -345,7 +345,7 @@ var TokenPath = TokenPath || { 'width': TokenPath.PIP_SIZE, 'height': TokenPath.PIP_SIZE, 'layer': tok.get('layer'), - 'name': "" + (Math.round(pip.distance * 100) / 100), + 'name': "" + (Math.round(pip.distance * 10) / 10), 'controlledby': tok.get('controlledby'), 'showname': true, 'showplayers_name': true}); From 653a9a9da564d491713ee0a1cf60ee40a4e5d592 Mon Sep 17 00:00:00 2001 From: manveti Date: Sun, 31 May 2015 03:58:31 -0700 Subject: [PATCH 7/8] Replace slashes in help output so they aren't interpreted as commands --- CommandShell/shell.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CommandShell/shell.js b/CommandShell/shell.js index ce1dedde20..c03c050a92 100644 --- a/CommandShell/shell.js +++ b/CommandShell/shell.js @@ -158,7 +158,7 @@ var Shell = Shell || { } if (helpMsg){ Shell.write("Shell Commands:", msg.who); - Shell.write(helpMsg, msg.who, "font-size: small"); + Shell.write(helpMsg.replace(/\//g, "/"), msg.who, "font-size: small"); } }, From f79dd2af9e5a630183abae15e9a2521342a49aea Mon Sep 17 00:00:00 2001 From: manveti Date: Sun, 31 May 2015 04:19:05 -0700 Subject: [PATCH 8/8] Fix argument parsing when last token is quoted. --- CommandShell/package.json | 2 +- CommandShell/shell.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CommandShell/package.json b/CommandShell/package.json index 563fa5b889..b0cb4bde73 100644 --- a/CommandShell/package.json +++ b/CommandShell/package.json @@ -1,6 +1,6 @@ { "name": "CommandShell", - "version": "1.0", + "version": "1.1", "description": "Framework for chat commands", "authors": "manveti", "roll20userid": "503018", diff --git a/CommandShell/shell.js b/CommandShell/shell.js index c03c050a92..5b885f081f 100644 --- a/CommandShell/shell.js +++ b/CommandShell/shell.js @@ -115,9 +115,6 @@ var Shell = Shell || { if (idx < 0){ // no more quotes or whitespace, just add the rest of the string to the current token and terminate curTok += s; - if (curTok){ - retval.push(curTok); - } break; } curTok += s.substr(0, idx); @@ -135,6 +132,9 @@ var Shell = Shell || { curTok = ""; } } + if (curTok){ + retval.push(curTok); + } return retval; },