From 3068420fa48e88d5e316df24e57b842d96ed0cb5 Mon Sep 17 00:00:00 2001 From: Kurohyou Date: Tue, 20 Sep 2016 07:48:16 -0500 Subject: [PATCH] PageNavigator v1.43 Final tweaks to the teleport system. --- .../1.42/{PageNavigator => PageNavigator.js} | 0 Page Navigator/1.43/PageNavigator.js | 1653 +++++++++++++++++ Page Navigator/script.json | 6 +- 3 files changed, 1656 insertions(+), 3 deletions(-) rename Page Navigator/1.42/{PageNavigator => PageNavigator.js} (100%) create mode 100644 Page Navigator/1.43/PageNavigator.js diff --git a/Page Navigator/1.42/PageNavigator b/Page Navigator/1.42/PageNavigator.js similarity index 100% rename from Page Navigator/1.42/PageNavigator rename to Page Navigator/1.42/PageNavigator.js diff --git a/Page Navigator/1.43/PageNavigator.js b/Page Navigator/1.43/PageNavigator.js new file mode 100644 index 0000000000..92f894c56c --- /dev/null +++ b/Page Navigator/1.43/PageNavigator.js @@ -0,0 +1,1653 @@ +/* +PAGENAVIGATOR script: +Author: Scott C. +Contact: https://app.roll20.net/users/459831/scott-c +Thanks to: The Aaron, Arcane Scriptomancer and Stephen for their help with the bulk of this script. + +Script goal: to simplify moving between maps in Roll20. +IDEAS FOR IMPLEMENTING AUTO-TOKEN GENERATION: +Chat menu selection of default player character's/pets + Chat button to bring up drop-down selection of all characters controlled by that player + an option for has pets + has pets would bring up following dropdowns: + Primary Character: select primary character + # of followers: enter number of followers + Would bring up a drop-down to select what follower to use for each number (e.g. follower 1: Character options) + Chat Prompt to GM on move for any additional characters? +*/ +var PAGENAVIGATOR= PAGENAVIGATOR|| (function(){ + 'use strict'; + + var version = '0.1.43', + lastUpdate = 1474374817, + schemaVersion = 1.42, + defaults = { + css: { + button: { + 'border': '1px solid #cccccc', + 'border-radius': '1em', + 'background-color': '#006dcc', + 'margin': '0 .1em', + 'font-weight': 'bold', + 'padding': '.1em 1em', + 'color': 'white' + } + } + }, + statusColormap = ['#C91010', '#1076c9', '#2fc910', '#c97310', '#9510c9', '#eb75e1', '#e5eb75'], + templates = {}, + allPages, + playerPages, + destTokens, + players, + tokenNotes = {}, + + /*Provides handling of troublesome characters for sendChat*/ + ch = function (c) { + var entities = { + '<' : 'lt', + '>' : 'gt', + "'" : '#39', + '@' : '#64', + '{' : '#123', + '|' : '#124', + '}' : '#125', + '[' : '#91', + ']' : '#93', + '"' : 'quot', + '-' : 'mdash', + ' ' : 'nbsp' + }; + + if(_.has(entities,c) ){ + return ('&'+entities[c]+';'); + } + return ''; + }, + + esRE = function (s) { + var escapeForRegexp = /(\\|\/|\[|\]|\(|\)|\{|\}|\?|\+|\*|\||\.|\^|\$)/g; + return s.replace(escapeForRegexp,"\\$1"); + }, + + HE = (function(){ + var entities={ + //' ' : '&'+'nbsp'+';', + '&' : '&'+'amp'+';', + '<' : '&'+'lt'+';', + '>' : '&'+'gt'+';', + "'" : '&'+'#39'+';', + '@' : '&'+'#64'+';', + //'{' : '&'+'#123'+';', + '|' : '&'+'#124'+';', + '}' : '&'+'#125'+';', + ',' : '&'+'#44'+';', + '[' : '&'+'#91'+';', + ']' : '&'+'#93'+';', + '"' : '&'+'quot'+';', + //'-' : '&'+'mdash'+';' + }, + re=new RegExp('('+_.map(_.keys(entities),esRE).join('|')+')','g'); + return function(s){ + return s.replace(re, function(c){ return entities[c] || c; }); + }; + }()), + + /*Checks the API environment to make sure everything is prepped for the script*/ + checkInstall = function() { + log('-=> Page Navigator v'+version+' <=- ['+(new Date(lastUpdate*1000))+']'); + if( ! _.has(state,'PAGENAVIGATOR') || state.PAGENAVIGATOR.version !== schemaVersion) { + log(' > Updating Schema to v'+schemaVersion+' <'); + switch(state.PAGENAVIGATOR&& state.PAGENAVIGATOR.version) { + case 'UpdateSchemaVersion': + state.PAGENAVIGATOR.version = schemaVersion; + + break; + + default: + state.PAGENAVIGATOR= { + version: schemaVersion, + }; + break; + } + }; + if(!state.PAGENAVIGATOR.players){ + state.PAGENAVIGATOR.players={}; + }; + loadObjects(); + _.each(players,function(p){ + if(!state.PAGENAVIGATOR.players[p.id]){ + state.PAGENAVIGATOR.players[p.id]=[]; + }; + }) + log('Page Navigator Memory: Stored Page & Destination-Token Ids converted to Roll20 objects. Ready to be accessed'); + defaultOptions(); + buildTemplates(); + if(!state.PAGENAVIGATOR.configButton){ + state.PAGENAVIGATOR.configButton = makeButton( + '!nav config', + 'Configuration Options', '#191970', '#fff8dc' + ); + } + }, + + /*Builds templates for use in all other functions*/ + buildTemplates = function() { + templates.cssProperty =_.template( + '<%=name %>: <%=value %>;' + ); + + templates.style = _.template( + 'style="<%='+ + '_.map(css,function(v,k) {'+ + 'return templates.cssProperty({'+ + 'defaults: defaults,'+ + 'templates: templates,'+ + 'name:k,'+ + 'value:v'+ + '});'+ + '}).join("")'+ + ' %>"' + ); + + templates.button = _.template( + ' href="<%= command %>"><%= label||"Button" %>' + ); + }, + + //Makes the config option status buttons as they have a slightly different formatting than the rest of the buttons. + makeStatusButton = function(status, button) { + var i=_.indexOf(state.PAGENAVIGATOR.statusquery,status), + backColor = 'transparent', + command; + if(!button){ + if(status===state.PAGENAVIGATOR.dmarker && !button){ + backColor = '#228b22'; + } + command = ''; + } + if(button){ + command = command = ''; + } + + if(i===0){ + return command + '
Name Only
' + } + if(i>0 && i<8) { + return command + '
'; + } + if(i===8) { + return command + '
X
'; + } + if(i>8){ + return command + '
'; + } + }, + + /*Makes the API buttons used throughout the script*/ + makeButton = function(command, label, backgroundColor, color){ + return templates.button({ + command: command, + label: label, + templates: templates, + defaults: defaults, + css: { + color: color, + 'background-color': backgroundColor + } + }); + }, + + /*stores all tokens designated as a destination token in the state*/ + getDestinations = function(){ + //state.PAGENAVIGATOR.destinations = false; + var graphics = findObjs({ + type: 'graphic', + }), + dests; + switch(state.PAGENAVIGATOR.dmarker) { + case 'name': + dests = _.filter(graphics, function(d){ + return (_.contains(state.PAGENAVIGATOR.pagenames, d.get('name'))===true && (d.get('layer')==='gmlayer'||d.get('layer')==='gmlayer')); + }); + state.PAGENAVIGATOR.destinationids = _.map(dests,function(d){ + return d.get('id'); + }); + break; + default: + dests = _.filter(graphics, function(d){ + return (_.contains(state.PAGENAVIGATOR.pagenames, d.get('name'))===true && (d.get('layer')==='map'||d.get('layer')==='gmlayer') && d.get('statusmarkers').indexOf(state.PAGENAVIGATOR.dmarker)>=0); + }); + state.PAGENAVIGATOR.destinationids = _.map(dests,function(d){ + log(d); + return d.get('id'); + }); + break; + } + loadObjects(); + //sendChat('Page Navigation','/w gm The array of destination tokens has been updated'); + }, + + /*Finds all the nonarchived pages in the campaign and returns pages based on the access + */ + getPages = function() { + state.PAGENAVIGATOR.pageids = false; + state.PAGENAVIGATOR.playerpageids = false;//wipe the page related states clean + var pages = findObjs({ + type: 'page', + archived: false + }), + pPages; + switch(state.PAGENAVIGATOR.access){ + case 'restricted': + pPages = _.filter(pages, function(p){ + return (p.get('name').indexOf(state.PAGENAVIGATOR.pageMarker)>=0); + }) + break; + case 'open': + pPages = pages; + }; + state.PAGENAVIGATOR.pageids = _.map(pages, function(p) { + return p.get('id'); + }); + state.PAGENAVIGATOR.playerpageids = _.map(pPages, function(p) { + return p.get('id'); + }); + state.PAGENAVIGATOR.pagenames = _.map(pages, function(p) { + return p.get('name'); + }); + getDestinations(); + }, + + loadObjects = function(){ + allPages = findObjs({ + type: 'page', + }); + allPages = _.filter(allPages, function(p){ + return _.contains(state.PAGENAVIGATOR.pageids, p.get('id')); + }); + playerPages = findObjs({ + type: 'page', + }); + playerPages = _.filter(allPages, function(p){ + return _.contains(state.PAGENAVIGATOR.playerpageids, p.get('id')); + }); + destTokens = findObjs({ + type: 'graphic', + }); + destTokens = _.filter(destTokens, function(t){ + return _.contains(state.PAGENAVIGATOR.destinationids, t.get('id')); + }); + _.each(destTokens,function(t){ + t.get('gmnotes',function(gm){ + tokenNotes[t.id]=gm; + }); + }); + players = findObjs({ + type: 'player' + }); + }, + + //sets the default states if they do not currently have a value or do not currently exist + defaultOptions = function(){ + if(!state.PAGENAVIGATOR.control){ + state.PAGENAVIGATOR.control = 'whole';//can be 'whole', 'current', or 'self' + } + if(!state.PAGENAVIGATOR.access){ + state.PAGENAVIGATOR.access = 'restricted';//can be 'restricted' or 'open' + } + if(!state.PAGENAVIGATOR.dmarker){ + state.PAGENAVIGATOR.dmarker = 'statusmarker';//can be any 'statusmarker' or 'name' for 'name only' + } + if(!state.PAGENAVIGATOR.marker){ + state.PAGENAVIGATOR.marker = 'flying-flag';//can be the name of any statusmarker + } + if(!state.PAGENAVIGATOR.statusquery) { + var markers = 'name,red,blue,green,brown,purple,pink'+ + ',yellow,dead,skull,sleepy,half-heart,half-haze,interdiction,snail,lightning-helix,'+ + 'spanner,chained-heart,chemical-bolt,deathzone,drink-me,edge-crack,ninja-mask,stopwatch,'+ + 'fishing-net,overdrive,strong,fist,padlock,three-leaves,fluffy-wing,pummeled,tread,arrowed'+ + ',aura,back-pain,black-flag,bleeding-eye,bolt-shield,broken-heart,cobweb,broken-shield,flying-flag'+ + ',radioactive,trophy,broken-skull,frozen-orb,rolling-bomb,white-tower,grab,screaming,'+ + 'grenade,sentry-gun,all-for-one,angel-outfit,archery-target'; + state.PAGENAVIGATOR.statusquery = markers.split(','); + } + if(!state.PAGENAVIGATOR.pageMarker){ + state.PAGENAVIGATOR.pageMarker = '.players'; + } + }, + + //the following three functions set the various states to new options when they are updated in the config screen. + setControl = function(option) {//Sets the control state to one of 'whole' (default), 'current', or 'self' + state.PAGENAVIGATOR.control = option; + getPages(); + outputConfig(); + }, + + setAccess = function(option) {//sets the access state to either 'restricted' (default) or 'open' + state.PAGENAVIGATOR.access = option; + getPages(); + outputConfig(); + }, + + setDmarker = function(option) {//sets the dmarker state to either a specific status marker (default) or 'name' + state.PAGENAVIGATOR.dmarker = option; + getPages(); + outputConfig(); + }, + + setPageMarker = function(tag){//sets the player page tag. Defaults to "players" + state.PAGENAVIGATOR.pageMarker = tag + getPages(); + outputConfig(); + }, + + cleanImgSrc = function(img){ + var parts = img.match(/(.*\/images\/.*)(thumb|med|original|max)(.*)$/); + if(parts) { + return parts[1]+'thumb'+parts[3]; + } + return; + }, + + setDefaultTokens = function(playerId,characters){ + var charNum = 0; + _.each(characters,function(obj){ + obj.get('defaulttoken',function(t){ + if(charNum === 0){ + state.PAGENAVIGATOR.players[playerId]=[]; + }; + t=JSON.parse(t); + var img = cleanImgSrc(t.imgsrc); + state.PAGENAVIGATOR.players[playerId].push({ + //character ID + //'cid' : character.id, + //Represents + 'represents' :t.represents || '', + 'name' :t.name || '', + 'controlledby' :t.controlledby || '', + //Image/Token appearance + 'imgsrc' :img, + 'width' :t.width || 70, + 'height' :t.height || 70, + 'isdrawing' :t.isdrawing || false, + 'tint_color' :t.tint_color || 'transparent', + 'statusmarkers' :t.statusmarkers || '', + //Token Fields + 'gmnotes' :t.gmnotes || '', + //Bars + 'bar1_link' :t.bar1_link || '', + 'bar2_link' :t.bar2_link || '', + 'bar3_link' :t.bar3_link || '', + 'bar1_value' :t.bar1_value || '', + 'bar2_value' :t.bar2_value || '', + 'bar3_value' :t.bar3_value || '', + 'bar1_max' :t.bar1_max || '', + 'bar2_max' :t.bar2_max || '', + 'bar3_max' :t.bar3_max || '', + //Auras + 'aura1_radius' :t.aura1_radius || '', + 'aura2_radius' :t.aura2_radius || '', + 'aura1_color' :t.aura1_color || '#FFFF99', + 'aura2_color' :t.aura2_color || '#59E594', + 'aura1_square' :t.aura1_square || false, + 'aura2_square' :t.aura2_square || false, + //show attributes + 'showname' :t.showname || false, + 'showplayers_name' :t.showplayers_name || false, + 'showplayers_bar1' :t.showplayers_bar1 || false, + 'showplayers_bar2' :t.showplayers_bar2 || false, + 'showplayers_bar3' :t.showplayers_bar3 || false, + 'showplayers_aura1' :t.showplayers_aura1 || false, + 'showplayers_aura2' :t.showplayers_aura2 || false, + //Edit attributes + 'playersedit_name' :t.playersedit_name || false, + 'playersedit_bar1' :t.playersedit_bar1 || false, + 'playersedit_bar2' :t.playersedit_bar2 || false, + 'playersedit_bar3' :t.playersedit_bar3 || false, + 'playersedit_aura1' :t.playersedit_aura1 || false, + 'playersedit_aura2' :t.playersedit_aura2 || false, + //Dynamic Lighting + 'light_radius' :t.light_radius || '', + 'light_dimradius' :t.light_dimradius || '', + 'light_otherplayers' :t.light_otherplayers || false, + 'light_hassight' :t.light_hassight || false, + 'light_angle' :t.light_angle || '360', + 'light_losangle' :t.light_losangle || '360', + 'light_multiplier' :t.light_multiplier || '1' + }); + charNum++; + }); + + }); + }, + + //gets the current states, and outputs everything in a nicely formatted config screen + outputConfig = function() { + /*Outline of Config options: + Players control:Whole Party/Current Party/Self Only }-Sets whether players can move everyone, the current party only, or + themselves/other controllers of a token they control only + Access: Restricted/Open }-Sets whether players only have access to pages designated as "players" or can + access all pages + Identification Mode: Specific StatusMarker/By Name Only }-The state is selected from a list of status markers or name only and affects + how destination tokens are identified by the script. Selecting a marker will + set it to look for destination tokens named after a page and marked with the + appropriate statusmarker + Update: Updates the destination pages }-Causes getPages and getDestinations to be called to update the state.*/ + var controlButton, + accessButton, + dmarkerButton = '', + updateButton = makeButton( + '!nav update', 'Update Destinations & Pages', '#fff8dc', '#191970' + ), + pageButton, + pageMsg, + teleportButton, + removeButton, + teleportMsg = '', + playerButton; + if(state.PAGENAVIGATOR.teleport === 'on'){ + teleportButton = makeButton( + '!nav-config teleport off', + 'Teleport On', 'green','black' + ); + teleportMsg+=teleportButton+'

'; + _.each(players,function(p){ + var characters = _.filter(findObjs({ + type: 'character', + }), function(c){ + return _.contains(c.get('controlledby').split(','),p.id); + }), + charQuery = '', + petQuery = ''; + _.each(characters, function(c){ + charQuery += '|' + c.get('name') + ',' + c.id; + }); + petQuery += HE('?{Primary Character'+charQuery+'|none}' + +' ?{Number of followers' + +'|1,'+HE('?{Follower 1'+charQuery+'|none}') + +'|2,'+HE('?{Follower 1'+charQuery+'|none}'+' ?{Follower 2'+charQuery+'}') + +'|3,'+HE('?{Follower 1'+charQuery+'|none}'+' ?{Follower 2'+charQuery+'|none}'+' ?{Follower 3'+charQuery+'|none}') + +'|4,'+HE('?{Follower 1'+charQuery+'|none}'+' ?{Follower 2'+charQuery+'|none}'+' ?{Follower 3'+charQuery+'|none}'+' ?{Follower 4'+charQuery+'|none}') + +'|5,'+HE('?{Follower 1'+charQuery+'|none}'+' ?{Follower 2'+charQuery+'|none}'+' ?{Follower 3'+charQuery+'|none}'+' ?{Follower 4'+charQuery+'|none}' + +' ?{Follower 5'+charQuery+'|none}') + +'}' + ); + charQuery+='|Has pets/followers,'; + playerButton = makeButton( + '!nav-config tokens ' + p.id + ' ?{'+p.get('displayname')+"'s default character"+charQuery+HE(petQuery)+'}', + 'Set Default Token', 'white', 'black' + ); + teleportMsg+=p.get('displayname') + +'
' + +playerButton+'



'; + }); + }else if(state.PAGENAVIGATOR.teleport === 'off' || !state.PAGENAVIGATOR.teleport){ + teleportButton = makeButton( + '!nav-config teleport on', + 'Teleport Off', 'gray','white' + ); + teleportMsg+=teleportButton; + }; + if(state.PAGENAVIGATOR.access === 'restricted'){ + pageButton = makeButton( + '!nav-config setpmarker ?{Player accessible page tag|'+state.PAGENAVIGATOR.pageMarker+'}', + state.PAGENAVIGATOR.pageMarker, 'white', 'black' + ); + pageMsg = 'Player page tag:' + +'
' + +pageButton+'



'; + }else{ + pageMsg = ''; + } + switch(state.PAGENAVIGATOR.control) { + case 'whole': + controlButton = makeButton( + '!nav setControl current', + 'The Whole Party', '#228b22' + ); + break; + case 'current': + controlButton = makeButton( + '!nav setControl self', + 'The Current Party', '#ffff00', '#000000' + ); + break; + case 'self': + controlButton = makeButton( + '!nav setControl whole', + 'Themselves Only', '#ff4500' + ); + break; + } + + switch(state.PAGENAVIGATOR.access) { + case 'restricted': + accessButton = makeButton( + '!nav setAccess open', + ' Off ', '#ff4500' + ); + break; + case 'open': + accessButton = makeButton( + '!nav setAccess restricted', + ' On ', '#228b22' + ); + break; + } + + + _.each(state.PAGENAVIGATOR.statusquery, function(s){ + dmarkerButton += makeStatusButton(s); + }); + + sendChat('','/w gm ' + +'
' + +'
' + +'Page Navigator v'+version+' Options' + +'
' + +'
' + +'

Page Navigator now has several options that you can set that will stay selected across play sessions.

' + +'
' + +'Players can move:' + +'
' + +controlButton+'



' + +'
' + +'

Players can access all pages:

' + +'
' + +accessButton+'


' + +pageMsg+'
' + +'
' + +'

Destination tokens marked by:

' + +dmarkerButton+'
' + +'
' + +teleportMsg+'
' + +'
' + +'
' + +updateButton+'



' + +'
' + +'
' + +'
' + ); + }, + + //loc attributes: left, top, width,height,pageid + mapSurrounding = function(loc,newToken,start){//landing token will be filled, finds nearest center of a square that does not ahve a token in it. Pushes the coords to an + // array, returns the array + var map = [], + surround = { + left : loc.left - (loc.width/2 - 35) + (newToken.width - 70)/2, + top : loc.top - (loc.height/2 - 35) + (newToken.height - 70)/2, + width : 0, + height : 0 + }, + squareNum = 0, + created = false, + error, + flip = false, + wallPaths = findObjs({ + _type: 'path', + _pageid: loc.pageid, + layer: 'walls' + }), + wallSegments, + squareTest = findObjs({ + type: 'graphic', + pageid: loc.pageid, + layer: 'objects' + }), + squareSeg, + page = getObj('page',loc.pageid), + squarePt, + squareObj, + landingPt = [loc.left,loc.top,1]; + wallSegments = PathMath.toSegments(wallPaths); + while(!created && ((_.now() - start)/1000)<25){ + error = null; + var squareFilter = _.find(squareTest,function(obj){ + var x = false, + y = false; + if((~~(obj.get('left')/70 - 0.5) + 1) === (~~(surround.left/70 - 0.5) + 1)){ + x = true; + }else if(obj.get('left') < surround.left){ + if((obj.get('left') + obj.get('width')/2) > (surround.left - newToken.width/2)){ + x = true; + }; + }else if(obj.get('left') > surround.left){ + if((obj.get('left') - obj.get('width')/2) < (surround.left + newToken.width/2)){ + x = true; + }; + }; + if((~~(obj.get('top')/70 - 0.5) + 1) === (~~(surround.top/70 - 0.5) + 1)){ + y = true; + }else if(obj.get('top') < surround.top){ + if((obj.get('top') + obj.get('height')/2) > (surround.top - newToken.height/2)){ + y = true; + }; + }else if(obj.get('top') > surround.top){ + if((obj.get('top') - obj.get('height')/2) < (surround.top + newToken.height/2)){ + y = true; + }; + }; + if(x && y){ + return true + } + }); + //need to add DL path detection + landingPt = [loc.left,loc.top,1]; + wallSegments = PathMath.toSegments(wallPaths); + squarePt = [surround.left,surround.top,1]; + squareSeg = [landingPt,squarePt]; + _.each(wallSegments,function(wallSeg){ + if(PathMath.segmentIntersection(squareSeg,wallSeg)){ + error=1; + }; + }); + if(surround.left<=0 || surround.top <=0 || (~~(surround.top/70 - 0.5) + 1)>page.get('height') + || (~~(surround.left/70 - 0.5) + 1)>page.get('weight')){ + error=1; + }; + if(!squareFilter && !error){ + return { + left : surround.left, + top : surround.top, + layer: 'objects', + pageid:loc.pageid + }; + created = true; + }; + if(surround.width<(loc.width- 70)){ + surround.width += 70; + if(flip === false){ + surround.left += 70; + }else{ + surround.left -= 70; + }; + }else if(surround.height<(loc.height- 70)){ + surround.height += 70; + if(flip === false){ + surround.top += 70; + }else{ + surround.top -= 70; + }; + }else if(surround.height >= (loc.height - 70) && surround.width >= (loc.width - 70)){ + if(flip===false){ + surround.height = 70; + surround.width = 70; + surround.left -=70; + flip=true; + }else{ + surround.height = 0; + surround.width = 0; + loc.width += 140; + loc.height += 140; + surround.left -=70; + surround.top -=140; + flip=false; + }; + }; + }; + }, + + createTokenLanding = function(landingToken, playerId){ + if(typeof landingToken ==='string'){ + landingToken = getObj('graphic',landingToken); + }; + var landingLocation = { + top : landingToken.get('top'), + left : landingToken.get('left'), + pageid : landingToken.get('pageid'), + height : landingToken.get('height'), + width : landingToken.get('width'), + layer : 'objects' + }, + landingSize = { + height : landingToken.get('height'), + width : landingToken.get('width'), + }, + landingWork, + landingMod={ + top : 0, + left: 0 + }, + objWork, + firstChar, + charNum = 0, + characters = [], + newToken, + landingWork = _.clone(landingLocation); + _.each(playerId,function(id){ + var stored = _.clone(state.PAGENAVIGATOR.players[id]); + _.each(stored,function(c){ + characters.push(c); + }); + }); + var start = _.now(), + surr = 'initialized', + i=0; + log(characters); + log(`landingLocation leftXtop: ${landingLocation.left - landingLocation.width/2 + 35} X ${landingLocation.top - landingLocation.height/2 + 35}`); + while(i/g).join('')); + var destGM = JSON.parse(decodeURIComponent(obj.get('gmnotes')).split(/<[/]?.+?>/g).join('')); + if(destGM['location']===originGM['linkLocation'] && obj.get('name')===originName){ + createTokenLanding(obj,playerId); + }; + }; + }); + }, + + /*Moves a specific player or group to a page + { + location: any descriptive text + linkLocation: what descriptive text to look for in the link + } + */ + movePlayer = function(destToken, destination, playerid){ + let pp = Campaign().get('playerspecificpages'); + var dToken = getObj('graphic',destToken), + gm = JSON.parse(decodeURIComponent(dToken.get('gmnotes')).split(/<[/]?.+?>/g).join('')); + pp = (_.isObject(pp) ? pp : {} ); + sendChat('Page Navigator', 'Some players are being split from the party.'); + _.each(playerid, function(id){ + pp[id] = destination; + }); + if(state.PAGENAVIGATOR.teleport = 'on'){ + findEntrance(getObj('page',dToken.get('pageid')), gm, destination, playerid); + }; + Campaign().set({playerspecificpages: false}); + Campaign().set({playerspecificpages: pp}); + }, + + movePlayerFromDialog = function(destination, playerid){ + let pp = Campaign().get('playerspecificpages'); + pp = (_.isObject(pp) ? pp : {} ); + sendChat('Page Navigator', 'Some players are being split from the party.'); + _.each(playerid, function(id){ + pp[id] = destination; + }); + Campaign().set({playerspecificpages: false}); + Campaign().set({playerspecificpages: pp}); + }, + + movePartyFromDialog = function(whoToMove,destination){ + Campaign().set({playerpageid: destination}); + if(whoToMove === 'whole'){ + sendChat('Page Navigator', 'All players are being moved to a new map'); + Campaign().set({playerspecificpages: false}); + }else{ + sendChat('Page Navigator', 'The current party is being moved to a new map.'); + }; + }, + + /*Moves the current party (maintains split party) to a specific page*/ + moveCurrentParty = function(destToken, destination){ + var players, + party = [], + oop, + gm, + dToken = getObj('graphic',destToken); + sendChat('Page Navigator', 'The current party is being moved to a new map.'); + Campaign().set({playerpageid: destination}); + if(state.PAGENAVIGATOR.teleport = 'on'){ + log(destToken); + gm = JSON.parse(decodeURIComponent(dToken.get('gmnotes')).split(/<[/]?.+?>/g).join('')); + players = findObjs({ + type: 'player' + }); + oop = Campaign().get('playerspecificpages'); + _.each(players, function(p){ + if(!oop[p.id] || oop[p.id]===false){ + party.push(p.id); + }; + }); + findEntrance(getObj('page',dToken.get('pageid')), gm, destination, party); + }; + }, + + /*Moves all players to a specific page and moves everyone back to the party ribbon*/ + moveAllPlayers = function(destToken, destination){ + var players, + party = [], + oop, + gm, + party, + dToken = getObj('graphic',destToken); + sendChat('Page Navigator', 'All players are being moved to a new map'); + Campaign().set({playerpageid: destination, playerspecificpages: false}); + if(state.PAGENAVIGATOR.teleport = 'on'){ + gm = JSON.parse(decodeURIComponent(dToken.get('gmnotes')).split(/<[/]?.+?>/g).join('')); + players = findObjs({ + type: 'player' + }); + _.each(players, function(p){ + party.push(p.id); + }); + oop = Campaign().get('playerspecificpages'); + findEntrance(getObj('page',dToken.get('pageid')), gm, destination, party); + }; + }, + + /*Determines if there was a collision between a character-token and a destination-token and calls for API permission buttons be made and outputs them to chat*/ + getDestinationCollisions = function(token) { + if(!state.PAGENAVIGATOR.destinationids || !destTokens){ + sendChat('Page Navigation', '/w gm ERROR CODE: NULLDEST - You have not yet defined destination tokens in the state. Please update the list of' + +' destination pages and tokens through the '+state.PAGENAVIGATOR.configButton+' dialog'); + return + }; + var pageId, + destinations = _.filter(destTokens, function(d){ + return (d.get('_pageid')===token.get('_pageid')); + }), + collisions, + tokenControl, + controlList = '', + iteration = 0, + character, + msg = '', + destination, + overlapCollision, + lastCollision; + pageId = token.get('_pageid'); + collisions = TokenCollisions.getCollisions(token, destinations); + lastCollision = _.last(collisions); + if(token.get('represents')){ + character = getObj('character', token.get('represents')); + tokenControl = character.get('controlledby').split(","); + }else{ + tokenControl = token.get('controlledby').split(","); + } + if(lastCollision && token.get('name') && tokenControl){ + overlapCollision = TokenCollisions.isOverlapping(token, lastCollision, false); + if(overlapCollision){ + destination = findObjs({ + type: 'page', + name: lastCollision.get('name') + }); + if(lastCollision && _.contains(tokenControl, 'all') && destination[0]){ + msg += makeButton( + '!nav whole ' + lastCollision.id +' '+ destination[0].id, + 'Whole Party', '#fff8dc', '#191970' + ); + msg += makeButton( + '!nav current ' + lastCollision.id +' '+ destination[0].id, + 'Current Party', '#fff8dc', '#191970' + ); + } else if(lastCollision && !_.contains(tokenControl, 'all') && destination[0]){ + _.each(tokenControl, function(){ + controlList += tokenControl[iteration] + ' '; + iteration++; + }); + iteration = 0; + msg += makeButton( + '!nav whole ' + lastCollision.id +' '+ destination[0].id, + 'Whole Party', '#fff8dc', '#191970' + ); + msg += makeButton( + '!nav current ' + lastCollision.id +' '+ destination[0].id, + 'Current Party', '#fff8dc', '#191970' + ); + msg += makeButton( + '!nav player ' + lastCollision.id + ' ' + destination[0].id +' ' + controlList, + 'Controlling Player(s)', '#fff8dc', '#191970' + ); + } + if(destination[0]){ + if((tokenControl && destination[0].get('name').indexOf(state.PAGENAVIGATOR.pageMarker)>=0) || state.PAGENAVIGATOR.access === 'open'){ + sendChat('Page Navigation', '/w "' + token.get('name') +'"
' + +'
' + + 'Your character, '+token.get('name') + ', would like to move to ' + destination[0].get('name').split(state.PAGENAVIGATOR.pageMarker)[0] + '. Who is traveling with you?
' + +'

' + msg + '

' + + '

' + ); + }else{ + sendChat('Page Navigation', '/w gm
' + +'
' + + token.get('name') + ' would like to move to ' + destination[0].get('name').split(state.PAGENAVIGATOR.pageMarker)[0] + '. Who is traveling with them?
' + +'

' + msg + '

' + + '

' + ); + } + } + } + } + }, + + pagesTeleport = function(speakerid, destinationid, playerid){ + var landingTokens = _.filter(destTokens, function(d){ + return (d.get('_pageid')===destinationid); + }), + landingDetails, + landingButton, + landingMsg, + speaker, + playerList = ''; + if(speakerid === 'gm'){ + speaker = 'gm'; + }else{ + speaker = getObj('player',speakerid).get('displayname'); + }; + landingMsg = '/w "' + speaker + '"
' + +'
' + + "Token teleportation is enabled. Players have been moved to "+getObj('page',destinationid).get('name')+"; select the location to create tokens at or ignore if " + +'tokens should not be created:

', + _.each(playerid,function(id){ + playerList += id + ' '; + }); + _.each(landingTokens,function(obj){ + var notes = decodeURIComponent(obj.get('gmnotes')).trim(); + if(notes.indexOf('{')===0){ + landingDetails = JSON.parse(notes.split(/<[/]?.+?>/g).join('')); + landingButton = makeButton( + '!nav-teleport ' + obj.id + ' ' + playerList, + landingDetails.location, '#fff8dc', '#191970' + ); + landingMsg += landingButton; + }; + }); + sendChat('Page Navigation', landingMsg + '

'); + }, + + /*PAGES FUNCTIONS: The following functions are related to the !nav pages syntax for manually sending players to specific pages*/ + //Allows players to be returned to the player ribbon (rejoin the party) + pagesReturn = function(whoToMove, access, speaker, speakerid){ + let pp = Campaign().get('playerspecificpages'); + pp = (_.isObject(pp) ? pp : {} ); + var control, + nameList = '', + who, + msg = '', + ribbon = findObjs({ + type: 'page', + id: Campaign().get('playerpageid') + })[0], + character; + if(access==='player' || whoToMove === 'allow'){ + if(ribbon.get('name').indexOf(state.PAGENAVIGATOR.pageMarker)>=0 || state.PAGENAVIGATOR.access === 'open' || whoToMove === 'allow'){ + pp[speakerid] = false; + sendChat('Page Navigation', '/w "' + speaker + '" You are rejoining the party.'); + Campaign().set({playerspecificpages: false}); + Campaign().set({playerspecificpages: pp}); + }else{ + msg += makeButton( + '!nav return ' + speakerid, + 'Allow', '#fff8dc', '#191970' + ); + sendChat('Page Navigation', '/w "' + speaker + '" The party is currently not on a player accessible page. The GM has been asked to move you back.') + sendChat('Page Navigation', '/w gm' + '
' + +'
' + + 'The following players would like to return to the party:
'+ nameList + '
' + +'

' + msg + '

' + + '

' + ); + }; + }else if(access === 'gm'){ + if(whoToMove){ + control = _.map(whoToMove, function(obj){ + if(obj[0].get('represents')){ + character = findObjs({ + type: 'character', + id: obj[0].get('represents') + }); + if(!character[0].get('controlledby')){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE:RETURN NULLCHAR - '+character[0].get('name')+' is not controlled by anyone.
' + ); + }else{ + return character[0].get('controlledby').split(','); + } + }else{ + if(!obj[0].get('controlledby')){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE:RETURN NULLTOKEN - '+obj[0].get('name')+' is not controlled by anyone.
' + ); + }else{ + return obj[0].get('controlledby').split(','); + } + + } + }); + if(!control[0]){return;}; + _.each(control, function(p){ + pp[p] = false; + }); + Campaign().set({playerspecificpages: false}); + Campaign().set({playerspecificpages: pp}); + }else{ + Campaign().set({playerspecificpages: false}); + }; + } + }, + + /*Takes an array of tokens and gets who they are controlled by. Creates API buttons to send the indicated player(s) to specific maps and outputs those + buttons to Chat based on the access of the messaging user. + */ + pagesPlayerDialog = function(whoToMove, access, speaker) { + if(!allPages || (access === 'player' && (!playerPages || playerPages.length===0))){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: NULLPAGE - There are no valid destinations. This is either because there are no pages in the campaign, or because the GM has not ' + +'specified any as player accessible via adding "players" to the page name.
' + ); + return; + } + var msg = '', + tokensControl, + allCount = 0, + iteration = 0, + wtm = _.map(whoToMove, function(objid){ + return findObjs({ + type: 'graphic' || 'character', + id: objid + }) + }), + control, + players, + character, + idList = '', + names, + nameList = ''; + if(access === 'gm' || state.PAGENAVIGATOR.control !== 'self'){ + control = _.map(wtm, function(obj){ + if(obj[0].get('represents')){ + character = findObjs({ + type: 'character', + id: obj[0].get('represents') + }) + if(!character[0].get('controlledby')){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: PLAYER NULLCHAR - '+character[0].get('name')+' is not controlled by anyone.
' + ); + }else{ + return character[0].get('controlledby').split(','); + } + }else if(!obj[0].get('represents')){ + if(!obj[0].get('controlledby')){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: PLAYER NULLTOK - '+obj[0].get('name')+' is not controlled by anyone.
' + ); + }else{ + return obj[0].get('controlledby').split(','); + } + } + }); + }else{ + control = whoToMove; + } + if(!control[0]){return;}; + _.each(control, function(objid){ + if(_.contains(objid, 'all') && allCount === 0){ + pagesPartyDialog('all', access, speaker); + allCount++; + } + }); + if(allCount===0){ + if(control.indexOf('-')===-1){ + players = _.map(control, function(id){ + return findObjs({ + type: 'player', + id: id[0] + }); + }); + names = _.map(players, function(obj){ + return obj[0].get('_displayname'); + }); + _.each(control, function(objid){ + idList += objid + ' '; + }); + }else if(control.indexOf('-')>-1){ + players = findObjs({ + type: 'player', + id: control + }); + names = _.map(players, function(obj){ + return obj.get('_displayname'); + }); + idList = control; + } + _.each(names, function(n){ + nameList += n + ', '; + }); + if(access === 'gm'){ + _.each(allPages, function(aP){ + msg += makeButton( + '!nav-pages player ' + speaker.id + ' ' + aP.id + ' ' + idList, + aP.get('name').split(state.PAGENAVIGATOR.pageMarker)[0], '#fff8dc', '#191970' + ); + + }); + }else if(access === 'player'){ + _.each(playerPages, function(pP){ + msg += makeButton( + '!nav-pages player ' + speaker.id + ' ' + pP.id + ' ' + idList, + pP.get('name').split(state.PAGENAVIGATOR.pageMarker)[0], '#fff8dc', '#191970' + ); + }); + } + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + + 'As a ' + access + ' you can move ' + nameList + ' to:' + '
' + +'

' + msg + '

' + + '

' + ); + }; + }, + + /* Makes and outputs buttons to send the whole party, or just the current party to any page (based on the user's access).*/ + pagesPartyDialog = function(whoToMove, access, speaker) { + if(!allPages || (access === 'player' && (!playerPages || playerPages.length===0))){ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: NULLPAGE There are no valid destinations. This is either because there are no pages in the campaign, or because the GM has not ' + +'specified any as player accessible via adding "players" to the page name.
' + ); + return; + }; + var msg = '', + iteration = 0; + + if(whoToMove === 'whole' || whoToMove === 'current') { + if(access==='player'){ + _.each(playerPages, function(){ + msg += makeButton( + '!nav-pages ' + whoToMove + ' ' + playerPages[iteration].id, + playerPages[iteration].get('name').split(state.PAGENAVIGATOR.pageMarker)[0], '#fff8dc', '#191970' + ); + iteration++; + }); + sendChat('Page Navigation', '/w "' + speaker + '"
' + +'
' + + 'As a player you can set the ' + whoToMove + ' party' + ch("'") + 's page to:
' + +'

' + msg + '

' + + '

' + ); + }else if(access==='gm'){ + iteration=0; + _.each(allPages, function(){ + msg += makeButton( + '!nav-pages ' + whoToMove + ' ' + speaker.id + ' ' + allPages[iteration].id, + allPages[iteration].get('name').split(state.PAGENAVIGATOR.pageMarker)[0], '#fff8dc', '#191970' + ); + iteration++; + }); + sendChat('Page Navigation', '/w "' + speaker + '"
' + +'
' + + 'As GM, you can set the ' + whoToMove + ' party' + ch("'") + 's page to:
' + +'

' + msg + '

' + + '

' + ); + } + return; + }else if(pages.length === 0){ + sendChat('Page Navigation', '/w "' + speaker + '"
' + +'
' + +' There are no valid destinations. This is either because there are no pages in the campaign, or because the GM has not specified any as player accessible via adding "players" to the page name.' + ); + } + }, + + /*Shows the help dialog*/ + showHelp = function(who, details, gm) { + //state.PAGENAVIGATOR.statusquery = false; + var commands, + buttons, + access, + definitions, + gmMsg = ''; + if(!details){details=''} + switch (details){ + case '': + commands = makeButton( + '!nav help commands', 'Commands', '#191970', '#fff8dc' + ); + buttons = makeButton( + '!nav help buttons', 'Buttons', '#191970', '#fff8dc' + ); + access = makeButton( + '!nav help access', 'Access', '#191970', '#fff8dc' + ); + definitions = makeButton( + '!nav help definitions', 'Defining Destinations', '#191970', '#fff8dc' + ); + if(gm==='gm'){ + gmMsg = '
'+definitions + +'
' + +'

Click here for details on how to define destination tokens.

' + +'
'+access + +'
' + +'

Click here for details on setting different access levels for specific pages and what exactly player vs. gm access means

' + +'
'+state.PAGENAVIGATOR.configButton + +'
' + +'

Access the options screen to adjust Page Navigator'+ch("'")+'s behavior.

'; + }; + sendChat('','/w "'+who+'" ' + +'
' + +'
' + +'Page Navigator v'+version + +'
' + +'
' + +'

Page Navigator allows you to more easily move your players from page to page. You can utilize the script through API commands or by relying on token collisions to provide a more interactive map for your players.

' + +'
'+commands + +'
' + +'

Click here for details on chat commands for the script using !nav

' + +'
'+buttons + +'
' + +'

Click here for details on all the various API butons which are generated by the script.

' + +gmMsg + +'
' + +'
' + ); + break; + case 'commands': + sendChat('','/w "'+who+'" ' + +'
' + +'
' + +'Page Navigator v'+version + ' Commands:' + +'
' + +'
' + +'The api command to trigger the script is !nav. The commands that can be passed with !nav are:
' + +'
    ' + +'
  • ' + +'help '+ch('-')+' Shows the Help screen' + +'
  • ' + +'
  • ' + +'pages '+ch('-')+' Brings up a dialog of all non-archived pages in the Campaign for specifying a page to send players to.' + +'
    The exact behavior is dependent on the next argument, which can be:' + +'
      ' + +'
    • ' + +'whole '+ch('-')+' To move all players to the selected page.' + +'
    • ' + +'
    • ' + +'current '+ch('-')+' To move only those players currently in the player ribbon to the selected page.' + +'
    • ' + +'
    • ' + +'player '+ch('-')+' To move a specific player(s) to the selected page. This must be followed by at least one token or character id and will move the owners of that token/character to the selected page or will only move the messaging player if the gm has set player control to only themselves.' + +'
    • ' + +'
    • ' + +'return '+ch('-')+' If sent by the gm; returns all designated players (via token/character id) or everyone if "whole" is sent as the third argument to the player ribbon. If sent by a player, returns that player to the player ribbon or asks for GM permission to return if the ribbon is on a non-player page.' + +'

  • ' + +'
  • ' + +'config '+ch('-')+' If sent by the gm; brings up the configuration options. Has no effect if sent by a player.' + +'
  • ' + +'
' + ); + break; + case 'buttons': + sendChat('','/w "'+who+'" ' + +'
' + +'
' + +'Page Navigator v'+version + ' Buttons:' + +'
' + +'
' + +'There are several API button dialogs that may be generated while using this script. These are descriptions of what each button will do.
' + +'Dialog for a token colliding with a destination-token:' + +'
    ' + +'
  • ' + +'Whole Party '+ch('-')+' Will move all players in the game to the player ' + +'ribbon, and will move the player ribbon to the page described in the dialog.' + +'
  • ' + +'
  • ' + +'Current Party '+ch('-')+' Will move the player ribbon to the designated page,' + +' but will not affect any players split from the party.' + +'
  • ' + +'
  • ' + +'Controlling Player(s) '+ch('-')+' Will move any players set to control the ' + +'token or its associated character (if it represents a character) to the designated page.' + +'

' + +'Dialog for the pages command:' + +'
The dialogs that are produced by using !nav pages produce a button ' + +'for each page that the sender of the message has access to. These buttons will send players to the indicated map based on how pages was called.
' + ); + break; + case 'access': + if(gm==='gm'){ + sendChat('','/w "'+who+'" ' + +'
' + +'
' + +'Page Navigator v'+version + ' Access:' + +'
' + +'
' + +'Page Navigator functions differently based on the access allowed by the triggering player.' + +'
  • ' + +'Setting Access '+ch('-')+' Pages are GM access only by default. To set a page' + +' to Player access, simply add the tag that you have selected to the end of the page name. By default the tag is set to .players. Any text after the tag will not be shown in any button labels or dialogs.' + +'
      ' + +'Example: Test Page - GM only page
      vs.
      Test Page.players - player visible page' + +'
    ' + +'
  • ' + +'
  • ' + +'GM Access '+ch('-')+' If the !nav pages ... command is sent from a GM, all ' + +'pages are populated as API buttons. The GM receives a whispered movement prompt for all token collision prompted navigation requests.' + +'
  • ' + +'
  • ' + +'Player Access '+ch('-')+' If the !nav pages ... command is sent from a player' + +', only those pages flagged as player pages are populated as API buttons. Players only receive a whispered movement prompt when their token collides with a player accessible destination.' + +'
  • ' + +'
' + ); + } + break; + case 'definitions': + var status; + if(gm==='gm'){ + _.each(state.PAGENAVIGATOR.statusquery, function(s){ + if(s===state.PAGENAVIGATOR.dmarker){ + status = 'the '+makeStatusButton(s, 'no'); + } + }); + sendChat('','/w "'+who+'" ' + +'
' + +'
' + +'Page Navigator v'+version + ' Defining Destinations:' + +'
' + +'
' + +"In order for Page Navigator's token collision functionality to work, you must properly set up the destination tokens. The " + +'script looks for the following token properties when determining if a token is a destination or not:' + +'
  • ' + +'Layer '+ch('-')+' The token must be on the GM layer.' + +'
  • ' + +'
  • ' + +'Marked as destination '+ch('-')+' The token must be marked with the correct ' + +'statusmarker. The script is currently set to look for tokens marked with ' +status + '
    ' + +'
  • ' + +'
  • ' + +'Token Name '+ch('-')+' The name must exactly match the name of whatever page ' + +'you want it to link to. This includes the player accessible tag you have designated above for player pages.' + +'
  • ' + +'
' + ); + } + break; + } + }, + + /*Self-explanatory, but handles chat input. Also passes the access of the user*/ + handleInput = function(msg_orig){ + var msg = _.clone(msg_orig), + args, + who, + page, + tokens, + tokenIds, + iteration = 0, + player, + speaker, + access; + + if (msg.type !== "api") { + return; + } + + if(playerIsGM(msg.playerid)) { + access = 'gm'; + speaker = 'gm'; + }else if(!playerIsGM(msg.playerid)){ + access = 'player'; + speaker = msg.who; + } + + who=getObj('player',msg_orig.playerid).get('_displayname'); + + args = msg.content.split(/\s/); + switch(args[0]) { + case '!nav': + /*if(_.contains(args,'help')) { + showHelp(who); + return; + }*/ + switch(args[1]){ + case 'update': + if(access==='gm'){ + getPages(); + sendChat('Page Navigation', '/w gm The stored pages and destination-tokens have been updated'); + }; + break; + case 'setControl': + if(access==='gm'){ + setControl(args[2]); + }; + break; + case 'setAccess': + if(access==='gm'){ + setAccess(args[2]); + }; + break; + case 'setDmarker': + if(access==='gm'){ + setDmarker(args[2]); + }; + break; + case 'config': + if(access==='gm'){ + outputConfig(); + }else{ + sendChat('Page Navigation', '/w "' + who + '"Apologies, but only the GM has access to the script Configuration dialog' + +'; you are currently logged in as a player.') + }; + break; + case 'help': + showHelp(who, args[2], access); + break; + case 'whole': + if(access==='gm'){ + if(args[2] && args[3]){ + moveAllPlayers(args[2],args[3]); + }else{ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: BADID - There is no page associated with that ID. Please use the '+state.PAGENAVIGATOR.configButton+' to check your settings and update stored values.
' + ); + } + } + break; + case 'current': + if(access==='gm'){ + if(args[2] && args[3]){ + moveCurrentParty(args[2], args[3]); + } else{ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: BADID - There is no page associated with that ID. Please use the '+state.PAGENAVIGATOR.configButton+' to check your settings and update stored values.
' + ); + } + } + break; + case 'player': + /*Handle teleport for pages commands by additional command syntax in API command button if teleport enabled.*/ + if(access==='gm'){ + if(args[2] && _.rest(args, 3)){ + movePlayer(args[2], args[3], _.rest(args, 4)); + }else{ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: BADID - There is either no page associated with that ID or an invalid player ID was passed to the function. ' + +'Please use the '+state.PAGENAVIGATOR.configButton+' to check your settings and update stored values.
' + ); + } + } + break; + case 'return': + if(access==='gm'){ + pagesReturn('allow', access, speaker, args[2]); + } + case 'pages': + //Add handling for names(character, token, player?) and character id's. Handle names based on config options (token, + //character, or player chosen in config). + //Need to figure out how to handle multi-word names + switch(args[2]){ + case 'whole': + if(access==='gm' || state.PAGENAVIGATOR.control==='whole'){ + pagesPartyDialog(args[2], access, speaker); + } + break; + case 'current': + if(access==='gm' || state.PAGENAVIGATOR.control!=='self'){ + pagesPartyDialog(args[2], access, speaker); + } + break; + case 'return': + if(access==='gm'){ + if(args[3]){ + if(args[3]==='whole'){ + pagesReturn(undefined, access, speaker, msg.playerid); + }else{ + tokens = _.map(_.rest(args,3), function(id){ + return findObjs({ + type: 'graphic', + id: id + }); + }); + pagesReturn(tokens, access, speaker, msg.playerid); + }; + }else if(msg.selected){ + tokens = _.map(msg.selected, function(obj){ + return findObjs({ + type: 'graphic', + id: obj._id + }); + }); + pagesReturn(tokens, access, speaker, msg.playerid); + }; + }else if(access === 'player'){ + pagesReturn('dna', access, speaker, msg.playerid); + }; + break; + case 'player': + if(args[3] && (access==='gm' || state.PAGENAVIGATOR.control!=='self')){ + pagesPlayerDialog(_.rest(args,3), access, speaker); + }else if(!args[3] && (access==='gm' || state.PAGENAVIGATOR.control!=='self') && msg.selected){ + tokens = _.map(msg.selected, function(s){ + return s._id; + }); + pagesPlayerDialog(tokens, access, speaker); + }else if(state.PAGENAVIGATOR.control==='self'){ + pagesPlayerDialog(msg_orig.playerid, access, speaker); + }else{ + sendChat('Page Navigation', '/w ' + speaker + '
' + +'
' + +'ERROR CODE: SHORTARG - No token or character id detected. You must pass a ' + +'targeted/selected token'+ch("'")+'s or a specified character'+ch("'")+'s id in order to utilize !nav ' + +'pages player. Please see the help using !nav help if you have further questions.
' + ); + } + break; + } + default: + showHelp(who, args[2], access); + break; + } + break; + case '!nav-pages': + var players, + playerList =[]; + switch(args[1]){ + case 'whole'://Expected Syntax: !nav-pages whole [speakerid] [destinationid] + players = findObjs({type:'player'}); + _.each(players,function(p){ + playerList.push(p.id); + }); + movePartyFromDialog(args[1], args[3]); + break; + case 'current'://Expected Syntax: !nav-pages whole [speakerid] [destinationid] + var oop = Campaign().get('playerspecificpages'); + players = findObjs({type:'player'}); + _.each(players, function(p){ + if(!oop[p.id] || oop[p.id]===false){ + playerList.push(p.id); + }; + }); + playerList = _.filter + movePartyFromDialog(args[1], args[3]); + break; + case 'player'://Expected Syntax: !nav-pages whole [speakerid] [destinationid] [list of playerid's] + playerList = _.rest(args,4); + movePlayerFromDialog(args[3],playerList); + break; + } + if(state.PAGENAVIGATOR.teleport === 'on'){ + if(args[2]==='undefined'){ + args[2] = 'gm'; + }; + pagesTeleport(args[2],args[3],playerList); + } + break; + case '!nav-teleport': + createTokenLanding(args[1],_.rest(args,2)); + break; + case '!nav-config': + switch (args[1]){ + case 'setpmarker': + setPageMarker(args[2]); + break; + case 'teleport': + state.PAGENAVIGATOR.teleport = args[2]; + outputConfig(); + break; + case 'tokens': + var rest = _.rest(args, 3); + if(args[2] && args[3]){ + var charObj = _.map(rest,function(id){ + return getObj('character',id); + }); + setDefaultTokens(args[2],charObj); + }; + break; + } + break; + } + }, + + RegisterEventHandlers = function() { + on('change:graphic', getDestinationCollisions); + on('chat:message', handleInput); + }; + + return { + CheckInstall: checkInstall, + RegisterEventHandlers: RegisterEventHandlers + }; +}()); + +on("ready",function(){ + 'use strict'; + + PAGENAVIGATOR.CheckInstall(); + PAGENAVIGATOR.RegisterEventHandlers(); +}); \ No newline at end of file diff --git a/Page Navigator/script.json b/Page Navigator/script.json index 37de83178e..d9f0f556ce 100644 --- a/Page Navigator/script.json +++ b/Page Navigator/script.json @@ -1,8 +1,8 @@ { "name": "Page Navigator", "script": "PageNavigator.js", - "version": "1.42", - "previousversions": ["1.4","1.3","1.031"], + "version": "1.43", + "previousversions": ["1.42","1.4","1.3","1.031"], "description": "Page Navigator allows you to more easily move your players from page to page. You can utilize the script through API commands or by relying on token collisions to provide a more interactive map for your players. Use !nav help in game to bring up a help dialog with details on using all the scripts features. Use !nav config to setup Page Navigator according to your preferences. If you were using a previous version of Page Navigator, the player page tag has changed to .players due to how variables interact with strings.", "authors": "Scott C.", "roll20userid": "459831", @@ -14,7 +14,7 @@ "state.PAGENAVIGATOR.version": "read,write", "token.statusmarker": "read", "token.gmnotes":"read", - "token.gmnotes":"read" + "character.defaulttoken":"read" }, "conflicts": [] } \ No newline at end of file