Roll20 uses cookies to improve your experience on our site. Cookies enable you to enjoy certain features, social sharing functionality, and tailor message and display ads to your interests on our site and others. They also help us understand how our site is being used. By continuing to use our site, you consent to our use of cookies. Update your cookie preferences .
×
Create a free account

Is there a way to tell which Mod (API) Scripts work in Jumpagate and which don't?

December 14 (7 months ago)
Seb
Pro

Hi

Is there a way to tell which Mods work in Jumpgate and which don't? I mean, without having to test each one manually? I use over 40 in the game I run.

Maybe there is a list of which ones are compatible? 

In a Jumpgate campaign, when you try to install a script, will it hide incompatible scripts from the list or warn you in any way?

I'd be interested in moving my D&D 5e game to Jumpgate but I wouldn't be willing to to so if some of my favorite scripts wouldn't work. Some help speed things up while some others help with immersion.

Any tips are appreciated.

Thanks

---

Here are most of the scripts I currently use:

  • 5th Edition OGL by Roll20 Companion
  • autoPing.js
  • apply-damage.js
  • Bloodied-Dead-Maker.js
  • ChatSetAttr
  • Check It Out
  • Character Sheet Utils
  • Concentration
  • Customizable Roll Listener
  • D&D 5e - Paladin Aura
  • door-sounds.js
  • GroupCheck
  • GroupInitiative
  • HiddenCheck.js
  • HTML Builder
  • linTokenMarkers
  • libline
  • Mapchange
  • Marching Order
  • MatrixMath
  • Messenger
  • multiTokenFX.js
  • Path Math
  • pingme.js
  • RecursiveTable.js
  • Roll20 Audio Master
  • Rollable Table Macros
  • ScriptCards
  • SelectManager
  • Simple Sound
  • SmartAOE
  • splitArgs
  • TableExport
  • Teleport
  • Token Action Marker
  • Token Collisions
  • TokenMarker
  • Token Fate
  • TokenMod
  • Turnmarker1.js
  • Vector Math
  • whisperart.js
December 14 (7 months ago)

Edited December 14 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

As of right now, most of those should work, which a few exceptions:

  • sendPing() is not working currently (though it is being actively worked on!), so pingme and autoping aren't going to be useful (though they won't crash or anything, just won't do the pinging).  Once the ping functionality is fixed, they will just start working as normal.  Scripts that have ping as part of them will similarly have that functionality missing until the function is fixed (TurnMarker1, possibly Teleport)
  • Scripts that make use of player created paths will need to be updated to support Pathv2.  I've updated PathMath, Token Collisions, and It's a Trap for this already, which might cover all of what you're using.
  • There is a bug (not strictly related to the API) that is being looked at right now which happens when using @{target|...} and @{selected|...}.  That may affect some scripts that generate buttons which use those for supplying arguments.  I don't know which of your scripts might do that, it's really more of a macros/chat issue.
  • Scripts that make use of multiple of the same status marker (Flight and Flymore, FateDots, some TokenMod commands) won't show more than one of them.  This has been brought up, I'll follow up on it with the devs.

That covers Jumpgate and API Scripts.  If you are using the 2024 sheet, there are some other things to be aware of, primarily that no scripts currently can interact with it.

Thanks for the update The Aaron, i was wondering how the scripting for Jumpgate was coming along. Much appreciated :)

December 14 (7 months ago)
Gold
Forum Champion

Will this paths change affect the snippit script, "Waypoint Patrol"? The paths are GM created, not "player created" per-se, if that helps. It is a very old script from the Forums that the script author hasn't been around here in a while. 

So can I still use Waypoint Patrol normally in Jumpgate? 

The Aaron said:

  • Scripts that make use of player created paths will need to be updated to support Pathv2.  I've updated PathMath, Token Collisions, and It's a Trap for this already, which might cover all of what you're using.



December 14 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

Anything using the user interface to draw paths on Jumpgate and then look at those paths in scripts will need to be updated. Anything where an API script is creating a path using the classic path object will continue work as expected. For example, UniversalVTTImporter will still work just fine, though I am going to be updating it to PathV2 objects.

Player in this sense means a human rather than a script. (All humans are "players" to us API scripts! =D )

December 14 (7 months ago)
Gold
Forum Champion

Ok thanks for the info.

I'm going to be needing some help (or recruiting a volunteer) to update Waypoint Patrol script snippit to work with PathV2 for Jumpgate.

state.WaypointPatrol = state.WaypointPatrol || {'PatrolStatus': true}

var WPP = {};
WPP.Command        = '!wp';
WPP.SpeakAs        = 'Patrol';
WPP.RefreshRate    = 5;    // in seconds, minimum 1
WPP.RetainLastMove = true;  //save patrol moves as lastmove
WPP.ShowPaths      = true;  //refresh visible paths on add or delete

WPP.AddPatrolPath = function(selected, patrolname) {
    //make sure the patrol name is provided
    if (!patrolname || patrolname.trim() === '' ) { return 'Err: patrol name missing'; }
    patrolname = patrolname.toLowerCase();
    //verify only one token is selected
    if (!selected) { return 'Err: no token selected'; }
    if (selected.length !== 1) { return 'Err: multiple tokens selected'; }
    if (!selected[0]['_type'] || selected[0]['_type'] !== 'graphic') { return 'Err: selected must be a graphic'; }
    //get the token object
    var token = getObj('graphic', selected[0]['_id']);
    if (!token) { return 'Err: token not found'; }
    //read the patrol path from token's last move
    var lastmove = token.get('lastmove'); 
    if (lastmove === '') { return 'Err: token missing last move'; } 
    lastmove = lastmove.split(',');
    var pts = [];
    while(lastmove.length > 0) {
        pts.push({
            'left': +lastmove.shift(),
            'top' : +lastmove.shift(),
        });
    }
    pts.push({
        'left': token.get('left'),
        'top' : token.get('top'),
    });
    //add patrol path to state if it doesn't already exist
    var pageid = token.get('_pageid');
    if (!state.WaypointPatrol['paths']) { state.WaypointPatrol['paths'] = {}; }
    if (!state.WaypointPatrol['paths'][pageid]) { state.WaypointPatrol['paths'][pageid] = {}; }
    if (state.WaypointPatrol['paths'][pageid][patrolname]) { return 'Err: name already exists'; } 
    state.WaypointPatrol['paths'][pageid][patrolname] = pts;
    if (WPP.ShowPaths) { WPP.ShowPatrolPaths(); }
    WPP.PatrolHelpMenu();
    return 'Success: patrol path added';
}

WPP.DeletePatrolPath = function(patrolname) {
    //make sure the patrol name is provided
    if (!patrolname || patrolname.trim() === '' ) { return 'Err: patrol name missing'; }
    patrolname = patrolname.toLowerCase();
    //delete the patrol name if it exists
    var pageid = Campaign().get('playerpageid');
    if (!state.WaypointPatrol['paths'][pageid] || !state.WaypointPatrol['paths'][pageid][patrolname]) { return 'Err: patrol name does not exist for this map'; }
    delete state.WaypointPatrol['paths'][pageid][patrolname];
    if (WPP.ShowPaths) { WPP.ShowPatrolPaths(); }
    WPP.PatrolHelpMenu();
    return 'Success: patrol path deleted.';
}

WPP.PatrolHelpMenu = function() {
    //create a bracketed list of avaialble patrols for the current map
    var pageid = Campaign().get('playerpageid');
    var paths = [];
    if (state.WaypointPatrol['paths'] && state.WaypointPatrol['paths'][pageid] && _.size(state.WaypointPatrol['paths'][pageid]) > 0) { 
        paths = _.keys(state.WaypointPatrol['paths'][pageid]);
    }
    var code = encodeURIComponent(':');
    var out = '';
        out = '<div style="background-color: #FFF; border: 1px solid #000; border-radius: 0.5em; margin-left: 2px; margin-right: 2px; padding-top: 5px; padding-bottom: 5px;">'
        + '<div style="font-weight: bold; text-align: center; border-bottom: 1px solid black; font-size: 100%">Map: ' + (getObj('page', pageid).get('name') || '(Untitled)') + '</div>'
        + '<div style="padding-left: 5px; padding-right: 5px; overflow: hidden; font-size: 90%">';
    if (paths.length > 0) {
        out += '<div style="font-weight: bold; padding-top: 5px; padding-bottom: 5px;">Create a Patrol<a style="float: right; padding: 0px 5px 0px 5px; margin: 0px 5px 0px 5px; border: 1px solid black; border-radius: 0.5em; background-color: #999999; color: #FFF" href="' + WPP.Command + ' patrol path' + code +'?{Name of the path|'+ paths.join('|') + '} mode' + code + '?{Mode|Forward and Backwards,0|Forward Only,1|Backwards Only,-1} speed' + code + '?{Distance moved on each refresh|5} rotation' + code + '?{Angle modifier to face waypoint|Do Not Rotate,false|0 degrees,0|90 degrees,90|180 degrees,180|270 degrees,270}">Patrol</a></div>';
        out += '<li style="padding-left: 10px;">Starts moving a token graphic along an existing patrol path.</li>';
    } else {
        out += '<div style="padding-top: 5px; padding-bottom: 5px;">You must add a path to this map from a token\'s last move before creating a patrol.</div>';
        out += '<div style="padding-top: 5px; padding-bottom: 5px;">Place a token to where you want the patrol path to begin.  Move the token to where you want the patrol path to end.  You can press the space bar to create turns in the path as you\'re moving the token.  When the token is in the final position, click the \'Path\' button</div>'
    }
    out += '<div style="font-weight: bold; padding-top: 5px; padding-bottom: 5px;">Create a Path<a style="float: right; padding: 0px 5px 0px 5px; margin: 0px 5px 0px 5px; border: 1px solid black; border-radius: 0.5em; background-color: #999999; color: #FFF" href="' + WPP.Command + ' addpath ?{Name of the new path}">Path</a></div>';
    out += '<li style="padding-left: 10px;">Saves a token\'s last move as a named patrol path for other tokens to follow.</li>';
    if (paths.length > 0){
        out += '<div style="font-weight: bold; padding-top: 5px; padding-bottom: 5px;">Delete a Path<a style="float: right; padding: 0px 5px 0px 5px; margin: 0px 5px 0px 5px; border: 1px solid black; border-radius: 0.5em; background-color: #999999; color: #FFF" href="' + WPP.Command + ' deletepath ?{Name of existing path to delete|' + paths.join('|') + '}">Delete</a></div>';
        out += '<li style="padding-left: 10px;">Deletes an existing patrol path.</li>';
        out += '<div style="font-weight: bold; padding-top: 5px; padding-bottom: 5px;">Show All Paths<a style="float: right; padding: 0px 5px 0px 5px; margin: 0px 5px 0px 5px; border: 1px solid black; border-radius: 0.5em; background-color: #999999; color: #FFF" href="' + WPP.Command + ' showpaths">Show</a></div>';
        out += '<li style="padding-left: 10px;">Draws onto the GM layer all existing patrol paths and labels them.</li>';
        out += '<div style="font-weight: bold; padding-top: 5px; padding-bottom: 5px;">Hide All Paths<a style="float: right; padding: 0px 5px 0px 5px; margin: 0px 5px 0px 5px; border: 1px solid black; border-radius: 0.5em; background-color: #999999; color: #FFF" href="' + WPP.Command + ' hidepaths">Hide</a></div>';
        out += '<li style="padding-left: 10px;">Hides all drawn paths and labels.</li>';
    }
    out += '</div></div>';
    sendChat(WPP.SpeakAs, '/w GM ' + out);
    return;
}

WPP.ShowPatrolPaths = function() {
    WPP.HidePatrolPaths();
    if (!state.WaypointPatrol['paths']) { return; }
    state.WaypointPatrol['traces'] = state.WaypointPatrol['traces'] || [];
    state.WaypointPatrol['labels'] = state.WaypointPatrol['labels'] || [];
    for(pageid in state.WaypointPatrol['paths']) {
        for(patrolname in state.WaypointPatrol['paths'][pageid]) {    
            var pts = [];
            var ul = {'left': Infinity, 'top': Infinity};
            var lr = {'left': 0, 'top': 0};
            for (var pt = 0; pt < state.WaypointPatrol['paths'][pageid][patrolname].length ; pt++) { 
                var curr = state.WaypointPatrol['paths'][pageid][patrolname][pt];
                ul = {'left': Math.min(ul['left'],curr['left']), 'top': Math.min(ul['top'], curr['top'])};
                lr = {'left': Math.max(lr['left'],curr['left']), 'top': Math.max(lr['top'], curr['top'])};
                pts.push(['L', curr['left'], curr['top']]);
            }; 
            state.WaypointPatrol['traces'].push(createObj('path', {
                '_pageid'     : pageid,
                '_path'       : JSON.stringify(pts), //see notes
                'stroke'      : '#ff9900',
                'stroke_width': 14,
                'layer'       : 'gmlayer',
                'width'       : lr['left'] - ul['left'],
                'height'      : lr['top'] - ul['top'],
                'left'        : (lr['left'] - ul['left']) / 2 + ul['left'],
                'top'         : (lr['top'] - ul['top']) / 2 + ul['top'],
            }).get('_id'));
            for ( var pt = 0; pt < state.WaypointPatrol['paths'][pageid][patrolname].length - 1; pt++) {
            var p1 = state.WaypointPatrol['paths'][pageid][patrolname][pt];
            var p2 = state.WaypointPatrol['paths'][pageid][patrolname][pt + 1];
            state.WaypointPatrol['labels'].push(createObj('text', {
                '_pageid'    : pageid,
                'text'       : 'path: ' + patrolname,
                'font_size'  : 14,
                'color'      : 'rgb(0,0,0)',
                'font_family': 'Arial',
                'layer'      : 'gmlayer',
                'left'       : Math.abs(p1['left']-p2['left']) / 2 + Math.min(p1['left'],p2['left']),
                'top'        : Math.abs(p1['top']-p2['top']) / 2 + Math.min(p1['top'],p2['top']),
                'rotation'   : ((p2['left'] < p1['left']) ? Math.atan2(p1['top'] - p2['top'], p1['left'] - p2['left']) : Math.atan2(p2['top'] - p1['top'], p2['left'] - p1['left'])) * 180 / Math.PI
            }).get('_id'));
            }
        }
    }  
    WPP.ShowPaths = true;
}

WPP.HidePatrolPaths = function() {
    if (state.WaypointPatrol['traces']) { 
        while (state.WaypointPatrol['traces'].length > 0) {
            var path = getObj('path', state.WaypointPatrol['traces'].shift());
            if (path) { path.remove(); }
        }
    }
    if (state.WaypointPatrol['labels']) {
        while (state.WaypointPatrol['labels'].length > 0) {
            var text = getObj('text', state.WaypointPatrol['labels'].shift());
            if (text) { text.remove(); }
        }
    }
    WPP.ShowPaths = false;
}

WPP.SetTokenToPatrol = function(selected, opts) {
    //cehck to see if a token has been selected
    if (!selected || selected.length === 0) { return 'Err: no token selected'; }
    if (!state.WaypointPatrol['tokens']) { state.WaypointPatrol['tokens'] = {}; }
    if (!opts || !opts['patrolname'] || opts['patrolname'].trim() === '' ) { return 'Err: patrol name missing'; }
    opts['patrolname'] = opts['patrolname'].toLowerCase();
    var pageid = getObj(selected[0]['_type'], selected[0]['_id']).get('_pageid');
    if (!state.WaypointPatrol['paths'] || !state.WaypointPatrol['paths'][pageid] || !state.WaypointPatrol['paths'][pageid][opts['patrolname']]) { return 'Err: patrol name does not exist for this map'; }
    var points = state.WaypointPatrol['paths'][pageid][opts['patrolname']];
    for (i in selected) {
        if (selected[i]['_type'] === 'graphic') {
            state.WaypointPatrol['tokens'][selected[i]['_id']] = {
                'patrolname'   : opts['patrolname'],
                'active'       : opts['active'] || true,
                'patrolmode'   : opts['patrolmode'] || 0,
                'movementrate' : opts['movementrate'] || getObj('page', pageid).get('scale_number'),
                'rotation'     : isNaN(opts['rotation']) ? false : opts['rotation'],
                'random'       : opts['random'] || false,
                'phase'        : opts['phase'] || 0,
            };
            //move token to closest point on patrol path
            var obj = getObj('graphic', selected[i]['_id']);
            WPP.MoveTokenToPath(obj, points);
        }
    }
    return 'Success: patrol added';
}

WPP.ModifyPatrollingToken = function(selected, opts) {
    if(!selected || selected.length === 0) { return 'Err: no token selected'; }
    var pageid = getObj(selected[0]['_type'], selected[0]['_id']).get('_pageid');
    if('patrolname' in opts && !state.WaypointPatrol['paths'][pageid][opts['patrolname']]) { return 'Err: patrol name does not exist for this map';}
    if (state.WaypointPatrol['tokens']) {
        for (o in selected) {
            if (state.WaypointPatrol['tokens'][selected[o]['_id']]) {
                for (opt in opts) {
                    if (opts.hasOwnProperty(opt)) { 
                        state.WaypointPatrol['tokens'][selected[o]['_id']][opt] = opts[opt];
                        switch(opt) {
                            case 'patrolname':
                                WPP.MoveTokenToPath(getObj('graphic',selected[o]['_id']), state.WaypointPatrol['paths'][pageid][opts[opt]]);
                                break;
                        }
                    }
                }
            }
        }
    }
    return 'Success: patrol modified';
}

WPP.RemoveTokenFromPatrol = function(selected) {
    if (!selected || selected.length === 0) { return 'Err: no token selected'; }
    if (state.WaypointPatrol['tokens']) { 
        for (o in selected) {
            if (selected[o]['_type'] == 'graphic') {
                delete state.WaypointPatrol['tokens'][selected[o]['_id']];
            }
        }
    }
    return 'Success: patrols removed';
}

WPP.MovePatrols = function() {
    //loop through each token in state data, cleanup deleted
    if(!state.WaypointPatrol['tokens']) { return; }
    for (id in state.WaypointPatrol['tokens']) {
        //validate object exists
        var obj = getObj('graphic', id);
        if (!obj) { delete state.WaypointPatrol['tokens'][id]; continue; }
        //validate patrol exists
        var pageid = obj.get('_pageid');
        var patrolname = state.WaypointPatrol['tokens'][id]['patrolname'];
        if (!patrolname || !state.WaypointPatrol['paths'][pageid] || !state.WaypointPatrol['paths'][pageid][patrolname]) { 
            obj.set({'layer':'objects'});
            delete state.WaypointPatrol['tokens'][id]; 
            continue;
        }
        //only patrol active units
        if (!state.WaypointPatrol['tokens'][obj.get('_id')]['active']) { continue; }
        //check for random movement, 20% chance the token doesn't move
        if (state.WaypointPatrol['tokens'][obj.get('_id')]['random'] && Math.random() < 0.2) { continue; }
        //set up the proper path
        var points = state.WaypointPatrol['paths'][pageid][patrolname].slice(0);
        switch(state.WaypointPatrol['tokens'][id]['patrolmode']) {
            case 1:  //forward patrolling
                break;
            case -1:   //reverse patrolling
                points.reverse();
                break;
            default:  //back and forth patrolling
                points = points.concat(points.slice(0).reverse());
                break;
        }
        //set up control variables
        var nextindex = state.WaypointPatrol['tokens'][id]['nextpointindex'] > points.length  -1 ? 0 : state.WaypointPatrol['tokens'][id]['nextpointindex'] ;
        var moverate = state.WaypointPatrol['tokens'][id]['movementrate'] <= 0 ? 5 : state.WaypointPatrol['tokens'][id]['movementrate'];
        var distremain = moverate * 70 / getObj('page', obj.get('_pageid')).get('scale_number'); 
        var newpoint = {'left': obj.get('left'), 'top': obj.get('top')};
        var lastmove = [newpoint['left'], newpoint['top']];
        //move along the path based on the movement rate of the token
        do {
            var disttonext = WPP.Distance(newpoint, points[nextindex]);
            if (distremain > disttonext) {
                newpoint = points[nextindex];
                lastmove.push(points[nextindex]['left'],points[nextindex]['top']);
                distremain -= disttonext;
                nextindex = (nextindex >= points.length - 1) ? 0 : nextindex + 1;
            } else {
                var moveratio = distremain / disttonext;
                var newrotation = (state.WaypointPatrol['tokens'][id]['rotation'] === false) ?  obj.get('rotation') : Math.atan2(points[nextindex]['top'] - newpoint['top'], points[nextindex]['left'] - newpoint['left']) * 180 / Math.PI - 90 + state.WaypointPatrol['tokens'][id]['rotation'] ;;
                newpoint = {
                    'left': moveratio * (points[nextindex]['left'] - newpoint['left']) + newpoint['left'],
                    'top' : moveratio * (points[nextindex]['top'] - newpoint['top']) + newpoint['top'],
                };
                obj.set({
                    'left'     : newpoint['left'],
                    'top'      : newpoint['top'],
                    'lastmove' : WPP.RetainLastMove ? lastmove.join(',') : '',
                    'layer'    : Math.random() < state.WaypointPatrol['tokens'][id]['phase'] ? 'gmlayer' : 'objects' ,
                    'rotation' : newrotation,
                });
                state.WaypointPatrol['tokens'][id]['nextpointindex'] = nextindex;
                distremain = 0;
            }
        } while (distremain > 0);
    }
}

WPP.Resume = function(selected) {
    if(!selected || !state.WaypointPatrol['tokens']) { return; }
    for (id in selected) {
        if (state.WaypointPatrol['tokens'][selected[id]['_id']]) { 
            //set the token patrol status to active
            state.WaypointPatrol['tokens'][selected[id]['_id']]['active'] = true;
            //move token to the closest point on path
            var obj = getObj('graphic', selected[id]['_id']);
            WPP.MoveTokenToPath(obj, state.WaypointPatrol['paths'][obj.get('_pageid')][state.WaypointPatrol['tokens'][selected[id]['_id']]['patrolname']]);
        }
    }
}

WPP.Distance = function(p1, p2) {
    return Math.sqrt(Math.pow((p1['left'] - p2['left']),2) + Math.pow((p1['top'] - p2['top']),2)); 
}

WPP.MoveTokenToPath = function(obj, path) {
    //set up control variables
    var pt = {'left': obj.get('left'), 'top': obj.get('top')};
    var newpt = pt;
    var nextid = 0;
    var delta = {'left': 0, 'top': 0};
    var dmin = Infinity;
    //loop through each path segment and find the closest point
    for( var i = 0; i < path.length-1; i++) {
        //get the next line segment in the path
        var p1    = path[i];
        var p2    = path[i+1];
        //calculate the closest point on the segment to the token
        var delta = {'left': p2['left'] - p1['left'], 'top': p2['top'] - p1['top']};
        var u     = ((pt['left'] - p1['left']) * delta['left'] + (pt['top'] - p1['top']) * delta['top']) / (Math.pow(delta['left'], 2) + Math.pow(delta['top'], 2));
        u = ( u > 1 ) ? 1 : (u < 0) ? 0 : u;
        var testpt = {'left': p1['left'] + u * delta['left'], 'top': p1['top'] + u * delta['top']};
        var d = WPP.Distance(testpt, pt);
        //save point if it's the closest point of all segments
        if (d < dmin) {
            dmin = d;
            nextid = i + 1;
            newpt = JSON.parse(JSON.stringify(testpt));  //deep copy of obj
        }
    }
    //move token to closest point
    obj.set({
        'left'     : newpt['left'],
        'top'      : newpt['top'],
        'lastmove' : obj.get('left') + ',' + obj.get('top'),
    });
    //set the next index patrol point for token
    state.WaypointPatrol['tokens'][obj.get('_id')]['nextpointindex'] = nextid;
}

on('chat:message', function(msg) {
    var params = msg.content.split(' ');
    if (msg.type !== 'api' || !playerIsGM(msg.playerid) || params.shift().toLowerCase() !== WPP.Command.toLowerCase() || params.length === 0) { return; }
    var replyTo = '/w ' + msg.who.split(' ')[0] + ' ';
    var cmd = params.shift().toLowerCase();
    var out = undefined;
    //set default parameters
    var options = {};
    function ReadParam(key) {
        keys = {
            'path'     : function(val) { options['patrolname'] = val; },
            'random'   : function(val) { options['random'] = (val.toLowerCase() === 'true'); },
            'phase'    : function(val) { options['phase'] = isNaN(val) ? 0 : Math.min(Math.max(0,val),100) / 100; }, 
            'active'   : function(val) { options['active'] = (val.toLowerCase() === 'true'); },
            'mode'     : function(val) { options['patrolmode'] = (val =='1' || val =='-1') ? parseInt(val) : 0; },
            'speed'    : function(val) { options['movementrate'] = (isNaN(val) || val < 1) ? 1 : parseFloat(val); },
            'rotation' : function(val) { options['rotation'] = isNaN(val) ? false : parseInt(val % 360); },
            'default'  : function() { out = 'Err: Unknown parameter [' + key + ']'; },
        };
        return (keys[key.toLowerCase()] || keys['default']); 
    }
    while (params.length > 0) {
        var opt = decodeURIComponent(params.shift()).split(':');
        switch (opt.length) {
            case 1:
                if (options['patrolname']) { options['patrolname'] += ' ' + opt[0]; } else { options['patrolname'] = opt[0]; }
                break;
            case 2:
                ReadParam(opt[0])(opt[1]);
                break;
            default:
                out = 'Err: too many delimiters';
                break;
        }
    } 
    //exit if parameters are invalid
    if (out) { 
        sendChat(WPP.SpeakAs, replyTo + out); 
        return;
    }
    switch(cmd) {
        case 'on': case 'off':
            state.WaypointPatrol['PatrolStatus'] = (cmd === 'on');
            out = 'Patrolling ' + cmd.toUpperCase();
            break;
        case 'resume':
            WPP.Resume(msg.selected);
            break;
        case 'help': case 'menu':
            out = WPP.PatrolHelpMenu(); //ListPatrolPaths();
            break;
        case 'addpath':
            out = WPP.AddPatrolPath(msg.selected, options['patrolname']);
            break;
        case 'deletepath':
            out = WPP.DeletePatrolPath(options['patrolname']);
            break;
        case 'patrol':
            out = WPP.SetTokenToPatrol(msg.selected, options);
            break;
        case 'modify':
            out = WPP.ModifyPatrollingToken(msg.selected, options);
            break;
        case 'stoppatrol':
            out = WPP.RemoveTokenFromPatrol(msg.selected);
            break;
        case 'showpaths':
            WPP.ShowPatrolPaths();
            break;
        case 'hidepaths':
            WPP.HidePatrolPaths();
            break;
        default:
            out = 'Err: unknown command [' + cmd + ']';
            break;
    }
    if (out) { sendChat(WPP.SpeakAs, replyTo + out); }
});

on('change:graphic', function(obj, old) {
    var id = obj.get('_id');
    if (!state.WaypointPatrol['tokens'] || !state.WaypointPatrol['tokens'][id] || state.WaypointPatrol['tokens'][id]['active'] === false) return;
    if (obj.get('left') === old['left'] && obj.get('top') === old['top'] && obj.get('height') === old['height'] && obj.get('width') === old['width'] && obj.get('rotation') === old['rotation']) { return; }
    state.WaypointPatrol['tokens'][obj.get('_id')]['active'] = false;
});

on('ready', function() {
    setInterval(function() {
        //do nothing if not enabled or initiative page is open
        if (!state.WaypointPatrol['PatrolStatus'] || Campaign().get('initiativepage')) { return; }
        WPP.MovePatrols();        
    }, Math.max(WPP.RefreshRate,1) * 1000);
    log('Script loaded: Waypoint Patrols' + (state.WaypointPatrol['PatrolStatus'] ? ', actively patrolling' : ''));
});
December 14 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

That doesn't actually use paths, it uses the lastmove of the token, which should work just fine.  If it doesn't, let me know (there was a bug with lastmove on Jumpgate, but it should be fixed at this point).

December 14 (7 months ago)
Gold
Forum Champion

Oh, wow. Not even paths afterall! 

Yes I will let you know if it doesn't work when I use it with Jumpgate. 

tysm

December 15 (7 months ago)
I have asked this before but will ask it again. Instead of needing to use multiple status markers for things like how high you are flying, why not allow for larger numbers on the status markers? You could allow up to 999 and handle this programmatically by allowing a key press delay between numbers, so if you type in 100 quickly show 100 instead of 0.

The Aaron said:

  • Scripts that make use of multiple of the same status marker (Flight and Flymore, FateDots, some TokenMod commands) won't show more than one of them.  This has been brought up, I'll follow up on it with the devs.


December 15 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

I certainly like that idea, and I'll mention it, no promises. 

December 15 (7 months ago)
Ulti
Pro
Sheet Author
API Scripter

Is there a way for a script to know if it is used in a Jumpgate game?

December 15 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

Yup!  This is the function I use for that:

  let isJumpgate = ()=>{
    if(['jumpgate'].includes(Campaign().get('_release'))) {
      isJumpgate = () => true;
    } else {
      isJumpgate = () => false;
    }
    return isJumpgate();
  };
You could also just check:
if( "jumpgate" === Campaign().get('_release')) {

I use the function because it caches the result and just becomes a return of true or false on future invocations, and it allows for future expansion with other releases.

December 15 (7 months ago)
Ulti
Pro
Sheet Author
API Scripter

Thanks!

December 16 (7 months ago)
Seb
Pro

Thanks for replying The Aaron. Sounds like we're in much better shape than I was expecting for Jumpgate.

Thank you also for the caveat regarding the 2024 sheet and script interaction. I will wait until until scripts can interact with it before considering moving to 2024.

December 18 (7 months ago)
The Aaron
Roll20 Production Team
API Scripter

The Aaron said:

  • sendPing() is not working currently (though it is being actively worked on!), so pingme and autoping aren't going to be useful (though they won't crash or anything, just won't do the pinging).  Once the ping functionality is fixed, they will just start working as normal.  Scripts that have ping as part of them will similarly have that functionality missing until the function is fixed (TurnMarker1, possibly Teleport)

Just a heads up, sendPing() is now fixed on Jumpgate!

December 18 (7 months ago)
keithcurtis
Forum Champion
Marketplace Creator
API Scripter

Much rejoicing!