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

Best Skills for a Would-Be API User

Hey folks, I'm planning on moving to Roll20 from another platform in the very near future. I'm thinking of subscribing at some point because I really like Dynamic Lighting. I'm also interested in advance functions, but I don't know any coding. Looking at the wiki, there is a pointer to Code Academy to get some Javascript basics, which should help me get started. I also know very little html and no CSS. Html doesn't particularly frighten me though, I've played around with that a bit.. What I'd like know is what is a good skill path for a self-motivated and patiently determined person to take in order to be able to take advantage of the API. Should I focus on Javascript? Or, should I take a few courses on html/CSS first? Any recommendations besides what was on the Wiki? I'm interested in character sheets for a few games that I don't see (I like automation), but more importantly some unusual functions.. Like the new Torg: Eternity has a 'different' approach towards exploding d6's. I also just don't want to have to hope someone else makes something for me for a game.  But don't get hung up on the specifics of that, I'm just looking for best skills to help me get started. Thanks!
1498688501

Edited 1498688817
The Aaron
Pro
API Scripter
Hi Jim! For API scripts, all you need to know is vanilla Javascript. &nbsp;API Scripts run in a Node.js based Sandbox, so understanding the difference between writing Javascript in a browser (with a DOM) and writing it in a Node Module would be helpful (there is no DOM in the API). &nbsp;Understanding how event driven software works and asynchronous functions would be a big help. &nbsp; API scripts run on an API server separate from the game and the clients connected to it, so all of your communication will occur though receiving events and responding to them by creating/modifying Roll20 Objects and/or issuing function calls like sendChat(). &nbsp; Here are two small and heavily annotated scripts that you can read through to get an idea what it's like. Drunk Move: Tokens with the overdrive status marker get moved a random direction for half the distance they moved when they are moved.&nbsp; // run this function when the api is fully loaded. &nbsp; // (like going when the light turns green, instead of one of the yellow ones) on('ready',function(){ &nbsp; &nbsp; // tells the javascript engine to make certain sloppy coding practices into errors &nbsp; &nbsp; "use strict"; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // starts defining things. &nbsp;first is a distance function &nbsp; &nbsp; var distance=function(p1,p2) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // pythagorean theorem for distance between two points &nbsp; &nbsp; &nbsp; &nbsp; return Math.sqrt(Math.pow(p2[0]-p1[0],2)+Math.pow(p2[1]-p2[1],2)); &nbsp; &nbsp; &nbsp; &nbsp; }, &nbsp; &nbsp; // function to determine how long a move chain was &nbsp; &nbsp; moveDistance=function(lastMove){ &nbsp; &nbsp; &nbsp; &nbsp; return _.chain(lastMove.split(/,/)) &nbsp; //&lt; break the move string up by commas &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(parseFloat) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//&lt; turn each thing into a number &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .groupBy(function(v,k){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&lt; break up the array into points &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return Math.round((k-1)/2); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .reduce(function(m,p){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;//&lt; walk the points adding the distance between them. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(m.pp){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m.sum+=distance(m.pp,p); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; m.pp=p; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return m; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; },{ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; pp:null, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sum:0 &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .value() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .sum; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&lt; return just the sum distance &nbsp; &nbsp; }; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // run this function when graphics (tokens) are changed &nbsp; &nbsp; on('change:graphic',function(obj,prev){ &nbsp; &nbsp; &nbsp; &nbsp; // setup some variables for later use &nbsp; &nbsp; &nbsp; &nbsp; var page, move, theta,tprime,lprime; &nbsp; &nbsp; &nbsp; &nbsp; // determine if we want to affect this object &nbsp; &nbsp; &nbsp; &nbsp; if( 'objects' === obj.get('layer') &nbsp; /* on the objects layer */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && -1 !== obj.get('statusmarkers').indexOf('overdrive') &nbsp;/* has the overdrive symbol */ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; && (obj.get('top') !== prev.top || obj.get('left') !== prev.left) &nbsp;/* has changed position */ &nbsp; &nbsp; &nbsp; &nbsp; ) { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // grab the page object based on the page the token is on (for later use bounding) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; page=getObj('page',obj.get('pageid')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // figure out how far the token moved &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move = moveDistance(obj.get('lastmove')); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(!move){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // if it was just dragged directly, the lastmove field won't give us a distance, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // use the distance between where it is now and where it was instead &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move=distance([obj.get('left'),obj.get('top')],[prev.left,prev.top]); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // cut move in half. &nbsp;move/=2 &nbsp;is the same as move = move/2&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; move/=2; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // get a random angle in radians &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; theta=(randomInteger(360) * (Math.PI/180)); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // calculate the new top, bounded by half a unit from the top and bottom edge of the map &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; tprime = Math.max(35,Math.min((page.get('height')*70)-35,obj.get('top')+move*Math.sin(theta))); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // calculate the new left, bounded by half a unit from the left and right edge of the map &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; lprime = Math.max(35,Math.min((page.get('width')*70)-35,obj.get('left')+move*Math.cos(theta))); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // set the object's new position &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; obj.set({ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; top: tprime, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; left: lprime &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; }); }); Random FX: select one or more tokens and run the api command !randomfx to cause that token to emit a random fx every 5-15 seconds. // This registers a function to run when the 'ready' event occurs // &nbsp; 'ready' happens when the API Sandbox has fully loaded all the elements of the current game // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] on('ready',function(){ &nbsp; &nbsp; // This tells Javascript to enforce certain strict rules when evaluating the code (generally things that avoid errors). &nbsp; &nbsp; "use strict"; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; // Setting up variables. &nbsp;These exist in the current function and for all functions defined within it. &nbsp; &nbsp; // &nbsp; These are in a Javascript Closure, something you may want to read up on. &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; var affectedTokens={}, &nbsp;//&lt; an object to store tokens that are in the rotation to receive random animations &nbsp; &nbsp; &nbsp; &nbsp; minSeconds = 5, &nbsp; &nbsp; //&lt; the minimum time to wait before applying another effect &nbsp; &nbsp; &nbsp; &nbsp; maxSeconds = 15, &nbsp; &nbsp;//&lt; the maximum time to wait before applying another effect &nbsp; &nbsp; &nbsp; &nbsp; // Arrays of choices for building a random effect &nbsp; &nbsp; &nbsp; &nbsp; eColor = [ 'acid', 'blood', 'death', 'fire', 'frost', 'holy', 'slime' ], &nbsp; &nbsp; &nbsp; &nbsp; eType = [ 'bubblingcauldron','burn','burst','explosion','healinglight','holyfire','nova','smokebomb','splatter' ], &nbsp; &nbsp; // a function that assembles a random effect name &nbsp; &nbsp; randomEffect=function(){ &nbsp; &nbsp; &nbsp; &nbsp; // _.sample() returns a random subset from a collection. &nbsp;By default, it returns a single value. &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp;[ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp;This will return a string such as 'burn-acid' or 'nova-frost' &nbsp; &nbsp; &nbsp; &nbsp; return _.sample(eType)+'-'+_.sample(eColor); &nbsp; &nbsp; }, &nbsp; &nbsp; // a function that creates a random effect at a particular location (x,y) on a particular page (p) &nbsp; &nbsp; applyEffect=function(x,y,p){ &nbsp; &nbsp; &nbsp; &nbsp; // spawnFx() creates an effect at a point on a page. &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; spawnFx(x,y,randomEffect(),p); &nbsp; &nbsp; }; &nbsp; &nbsp; // This registers a function to run when the 'chat:message' event occurs &nbsp; &nbsp; // &nbsp; 'chat:message' happens whenever anything is put into the chat box by anyone connected to the game &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; on('chat:message',function(msg){ &nbsp; &nbsp; &nbsp; &nbsp; // do some checks to make sure we want to run for this message &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; 'api' message type is for messages that begin with '!' &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; playerIsGM() will insure that only a gm can run this command &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; .match(/^!randomfx/) checks to make sure the command is the right one. &nbsp; &nbsp; &nbsp; &nbsp; if('api' === msg.type && playerIsGM(msg.playerid) && msg.content.match(/^!randomfx/) ){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // _.chain() is a powerful functional programming method in underscore.js &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; The basic idea is you take a collection as the start of a chain and &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; perform various operations on it. &nbsp;The input to an operation is the&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; output from the previous and so on. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; msg.selected is an array of { _type: &lt;typename&gt; ,_id: &lt;id&gt; } objects which &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; describe the currently selected objects. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; _.chain(msg.selected) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .map() takes a collection and call a function on each element, then returns &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; an array containing the result of each call. &nbsp;Using .chain(), the first argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; //&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; msg.selected -&gt; .map() -&gt; all the Roll20 graphics that were selected &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .map(function(o){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // This will get the Roll20 graphic associated with the selected id. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; If what was selected wasn't a graphic (a drawing, some text, etc) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; it will return the javascript identifier 'undefined'. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return getObj('graphic',o._id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .compact() returns the collection it was given without any of the 'falsy' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; entries, things like 'undefined', 'false', 'null', etc. &nbsp;Using .chain(), the argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // selected graphics and undefined -&gt; .compact() -&gt; selected graphics &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .compact() &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // .each() is just like .map(), except what it returns is what it was given. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Effectively, it gives you the opportunity to do something with each element &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; without changing the list. &nbsp;Using .chain(), the first argument &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; behind the scenes is the collection in the chain. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; selected graphics -&gt; .each() -&gt; selected graphics&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .each(function(t){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Now that we've filtered down the results to just selected graphics, the real work &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; starts. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // _.has() is an alias for &lt;object&gt;.hasOwnProperty() which simply takes a given object &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; and returns 'true' if it contains the specified property. &nbsp;In this case, I'm using &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; the id of the graphic as the property name, effectively treating the affectedTokens &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; object as a hash. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(_.has(affectedTokens,t.id)){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If affectedTokens has the id in it, it means effects are already happening for&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; that token, so I want to remove it from the rotation of things having effects. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; delete &lt;object&gt;[&lt;property name&gt;] removes that property from the object, effectively&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; removing this token from the hash. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete affectedTokens[t.id]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If affectedTokens didn't have the id, it means I'm adding it into rotation. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; I set the property for the graphic's id to be true. &nbsp;I considered &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; storing an object with some timing information instead of true, which &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; is why I made affectedTokens an object instead of a simple array, but &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; the implementation went another way. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; affectedTokens[t.id]=true; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // This creates another Javascript Closure to capture the id and page number and &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; save them for executions of the function I'll be defining within it. &nbsp;Technically, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; the function on each is already a Closure for this function so I probably didn't&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; need another one here. &nbsp;I have a tendency to over Closure, but it's not generally a &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; problem to do so and likely the encapsulation makes it somewhat clearer... maybe. =D &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; 'id' and 'p' here are what is passed further down, t.id and t.get('pageid'). &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; This is an Immediately Invoking Function Expression (IIFE or "iffy") &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; Basically, this function gets created and executed all in one, making a Closure to &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; save the parameters for use internally. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; (function (id,p){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Here I'm creating a function that will apply effects to the current graphic and also deal &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; with kicking off the next effect on the current graphic. &nbsp;A different copy of this function &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; will get created for each graphic, safely in it's own Closure. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var effectRunner=function(){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // First, check if the graphic is still in rotation. &nbsp;If a later execution of &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; !randomfx removed the id from affectedTokens, that means I want to stop &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; running the effects. &nbsp;Doing nothing (not going into this if block) means &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; no further timers are set and the function stops getting executed. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; I could have used _.has() here, or used this style above, they are almost equivalent &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; and are interchangeable in almost every circumstance. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(affectedTokens[id]){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Grab the graphic for this id &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; var t = getObj('graphic',id); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // If we found it, we'll do some things (otherwise, it was deleted and we'll stop) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if(t){ &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // With the graphic in hand, we can now apply an effect to it's current position. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; applyEffect(t.get('left'),t.get('top'),p); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Next we set a timer to run this function again. &nbsp;setTimeout() is asynchronous, so &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; we don't need to worry about stack depth for recursion. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; randomInteger() returns a number between 1 and the supplied argument, like rolling &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; a die. &nbsp;Technically, this means the minimum time between effects is&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; (minSeconds seconds + 1 millisecond), but hopefully you'll forgive that oversight! &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; setTimeout( &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; effectRunner, &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; randomInteger((maxSeconds-minSeconds)*1000)+(minSeconds*1000) &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } else { &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // In the case where we didn't find the graphic, it means someone deleted it. For that &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; case, we want to stop the rotation on the graphic be removing it from the&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; collection of affectedTokens. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; delete affectedTokens[id]; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // Start the rotation for this graphic by running the created function. &nbsp;This will trigger the &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; first effect and set a random timer for the next effect. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; effectRunner(); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }(t.id,t.get('pageid'))); //&lt; invoking the IIFE to set 'id' and 'p' &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; } &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }); &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // End of the chain. &nbsp;If we wanted the results, we would call &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; .value() to cause _.chain() above to return them. &nbsp;In this &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; case we don't need the results. &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // &nbsp; [ <a href="https://wiki.roll20.net/API:Events#Campaign_Event" rel="nofollow">https://wiki.roll20.net/API:Events#Campaign_Event</a>... ] &nbsp; &nbsp; &nbsp; &nbsp; } // end of the check for !randomfx &nbsp; &nbsp; }); // end of the on('chat:message') }); // end of the on('ready') We're a helpful community, so definitely ask whatever questions you would have. =D Regarding Character Sheets, that's a whole other ball of wax...
1498690633
Scott C.
Forum Champion
Sheet Author
API Scripter
Compendium Curator
The way I learned was by shamelessly ripping the guts out of Aaron's scripts to do what I wanted. He was nice enough to let me pester him with questions while doing it:)
1498776090
Lithl
Pro
Sheet Author
API Scripter
As Aaron alluded to, you don't need any HTML/CSS if you want to play with the API. If you want to create custom character sheets, however, those skills will be needed. =)
1498776253
The Aaron
Pro
API Scripter
Good point. &nbsp;I meant to mention that the only place you might want to know about HTML/CSS is in the case of formating text sent to sendChat() for display.
Thanks guys! I appreciate it. I went over to Code Academy and got started on their free lessons.
Aaron this post was amazing! I was first motivated to learn JS by Roll20 API stuff - but then once I actually got started I became a web developer instead (you could say I got side tracked :D) and earlier today I came back to inspect&nbsp; the roll20 app and as it works with canvas I don't see obvious ways of hooking into the dom. Then I go to look at character sheets and see the storing js constants in inputs thing and think "why would you do that? just use js" - and memories come flashing back about the sheetworker and how for security reasons (right?) it's all quite limited. So seems my DOM manipulation experience is of limited use here... On the other hand, learning to write for roll20 might be useful to me with other web app projects in the future. So time for round two of learning, perhaps :) Started reading the API intro in the wiki... neat&nbsp;
1500917430
The Aaron
Pro
API Scripter
Yeah, knowledge of the Roll20 API translates almost directly to Node.js Module writing. &nbsp; Similarly to you, I didn't care much for JavaScript when I started writing API scripts, but shortly after working on them I started integrating that knowledge into my professional life. Fast forward several years and I've written several Chrome Extensions (one for work explicitly to compete in a given market) and now I'm migrating our frontend to a React Redux Node Module based solution. =D&nbsp;