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

Help an API Newb? Initiative Tracker Problems

1426193081

Edited 1426193263
Hello!! So, as the title suggests I know next to nothing about coding (for now). I just got my first subscription BECAUSE I wanted to use API scripts in my games. Anyway, so the one big thing we wanted was a way to track buffs and I like this initiative tracker I found from Manveti. It works great until I try to change any of the parameters in-game, such as disabling the turn announcements. As soon as I attempt !tracker disable announceTurns the script gets disabled and I get an error saying the parameters have not been configured. Anybody out there who could take a look and figure out what's wrong with it? Honestly I won't probably ever use turn or round announcements, but if I try to remove those parameters the script breaks, too (cuz I'm a noob). Anyway, thanks a lot. Specific error: ReferenceError: CONFIG_PARAMS is not defined at Object.Tracker.setConfigParam (evalmachine.<anonymous>:373:10) at Object.Tracker.handleTrackerMessage (evalmachine.<anonymous>:515:14) at Tracker.handleChatMessage (evalmachine.<anonymous>:704:94) at eval ( Here is the code: var Tracker = Tracker || { ALL_STATUSES: ["red", "blue", "green", "brown", "purple", "pink", "yellow", "dead", "skull", "sleepy", "half-heart", "half-haze", "interdiction", "snail", "lightning-helix", "spanner", "chained-heart", "chemical-bolt", "death-zone", "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"], STATUS_ALIASES: {'deflection': "bolt-shield", 'disabled': "pummeled", 'haste': "lightning-helix", 'invisible': "ninja-mask", 'potion': "drink-me", 'stun': "sleepy"}, CONFIG_PARAMS: [['announceRounds',"Announce Each Round"], ['announceTurns',"Announce Each Player's Turn"], ['announceExpiration',"Announce Status Expirations"], ['highToLow',"High-to-Low Initiative Order"]], initConfig: function(){ if (!state.hasOwnProperty('InitiativeTracker')){ state.InitiativeTracker = { 'highToLow':true, 'announceRounds':true, 'announceTurns':true, 'announceExpiration':true }; } if (!state.InitiativeTracker.hasOwnProperty('round')){ state.InitiativeTracker.round = null; } if (!state.InitiativeTracker.hasOwnProperty('count')){ state.InitiativeTracker.count = null; } if (!state.InitiativeTracker.hasOwnProperty('status')){ state.InitiativeTracker.status = []; } }, write: function(s, who, style, from){ if (who){ who = "/w " + who.split(" ", 1)[0] + " "; } sendChat(from, who + s.replace(/</g, "<").replace(/>/g, ">").replace(/\n/g, "<br>")); }, reset: function(){ state.InitiativeTracker.round = null; state.InitiativeTracker.count = null; state.InitiativeTracker.status = []; }, announceRound: function(round){ if (!state.InitiativeTracker.announceRounds){ return; } sendChat("", "/desc Start of Round " + round); }, announceTurn: function(count, tokenName, tokenId){ if (!state.InitiativeTracker.announceTurns){ return; } if (!tokenName){ var token = getObj("graphic", tokenId); if (token){ tokenName = token.get('name'); } } sendChat("", "/desc Start of Turn " + state.InitiativeTracker.round + " for " + tokenName + " (" + count + ")"); }, announceStatusExpiration: function(status, tokenName){ if (!state.InitiativeTracker.announceExpiration){ return; } sendChat("", "/desc Status " + status + " expired on " + tokenName); }, handleTurnChange: function(newTurnOrder, oldTurnOrder){ var newTurns = JSON.parse((typeof(newTurnOrder) == typeof("") ? newTurnOrder : newTurnOrder.get('turnorder') || "[]")); var oldTurns = JSON.parse((typeof(oldTurnOrder) == typeof("") ? oldTurnOrder : oldTurnOrder.turnorder || "[]")); if ((!newTurns) || (!oldTurns)){ return; } if ((newTurns.length == 0) && (oldTurns.length > 0)){ return Tracker.reset(); } // turn order was cleared; reset if ((!newTurns.length) || (newTurns.length != oldTurns.length)){ return; } // something was added or removed; ignore if ((state.InitiativeTracker.round == null) || (state.InitiativeTracker.count == null)){ // first change: see if it's time to start tracking var startTracking = false; for (var i = 0; i < newTurns.length; i++){ if (newTurns[i].id != oldTurns[i].id){ // turn order was sorted; start tracking startTracking = true; break; } if (newTurns[i].pr != oldTurns[i].pr){ break; } // a token's initiative count was changed; don't start tracking yet } if (!startTracking){ return; } state.InitiativeTracker.round = 1; state.InitiativeTracker.count = newTurns[0].pr; Tracker.announceRound(state.InitiativeTracker.round); Tracker.announceTurn(newTurns[0].pr, newTurns[0].custom, newTurns[0].id); return; } if (newTurns[0].id == oldTurns[0].id){ return; } // turn didn't change var newCount = newTurns[0].pr; var oldCount = state.InitiativeTracker.count; if (!state.InitiativeTracker.highToLow){ // use negatives for low-to-high initiative so inequalities work out the same as high-to-low newCount = -newCount; oldCount = -oldCount; } var roundChanged = newCount > oldCount; if (roundChanged){ // made it back to the top of the initiative order state.InitiativeTracker.round += 1; Tracker.announceRound(state.InitiativeTracker.round); } if (newTurns[0].pr != state.InitiativeTracker.count){ // update statuses that update between the last count and this count for (var i = 0; i < state.InitiativeTracker.status.length; i++){ var status = state.InitiativeTracker.status[i]; var token = getObj("graphic", status.token); if (!token){ // token associated with this status doesn't exist anymore; remove it state.InitiativeTracker.status.splice(i, 1); i -= 1; continue; } var statusCount = status.count; if (!state.InitiativeTracker.highToLow){ statusCount = -statusCount; } if ((roundChanged) && (statusCount > oldCount) && (statusCount < newCount)){ continue; } // status not between last count and this count if ((!roundChanged) && ((statusCount > oldCount) || (statusCount < newCount))){ continue; } if (status.expires <= state.InitiativeTracker.round){ // status expired; remove marker and announce expiration token.set("status_" + status.status, false); state.InitiativeTracker.status.splice(i, 1); i -= 1; Tracker.announceStatusExpiration(status.name, token.get('name')); } else if (status.expires - state.InitiativeTracker.round < 10){ // status has nine or fewer rounds left; update marker to reflect remaining rounds token.set("status_" + status.status, status.expires - state.InitiativeTracker.round); } } } state.InitiativeTracker.count = newTurns[0].pr; Tracker.announceTurn(newTurns[0].pr, newTurns[0].custom, newTurns[0].id); }, getConfigParam: function(who, param){ if (param == null){ for (var i = 0; i < Tracker.CONFIG_PARAMS.length; i++){ var head = Tracker.CONFIG_PARAMS[i][1] + " (" + Tracker.CONFIG_PARAMS[i][0] + "): "; Tracker.write(head + state.InitiativeTracker[Tracker.CONFIG_PARAMS[i][0]], who, "", "Tracker"); } } else { var err = true; for (var i = 0; i < CONFIG_PARAMS.length; i++){ if (Tracker.CONFIG_PARAMS[i][0] == param){ var head = Tracker.CONFIG_PARAMS[i][1] + " (" + Tracker.CONFIG_PARAMS[i][0] + "): "; Tracker.write(head + state.InitiativeTracker[Tracker.CONFIG_PARAMS[i][0]], who, "", "Tracker"); err = false; break; } } if (err){ Tracker.write("Error: Config parameter '" + param + "' not found", who, "", "Tracker"); } } }, setConfigParam: function(who, param, value){ var err = true; for (var i = 0; i < Tracker.CONFIG_PARAMS.length; i++){ if (CONFIG_PARAMS[i][0] == param){ state.InitiativeTracker[Tracker.CONFIG_PARAMS[i][0]] = (value == null ? !state.InitiativeTracker[Tracker.CONFIG_PARAMS[i][0]] : value); err = false; break; } } if (err){ Tracker.write("Error: Config parameter '" + param + "' not found", who, "", "Tracker"); } }, showTrackerHelp: function(who, cmd){ Tracker.write(cmd + " commands:", who, "", "Tracker"); var helpMsg = ""; helpMsg += "help: display this help message\n"; helpMsg += "round [NUM]: display the current round number, or set round number to NUM\n"; helpMsg += "forward: advance the initiative counter to the next token\n"; helpMsg += "fwd: synonym for forward\n"; helpMsg += "back: rewind the initiative counter to the previous token\n"; helpMsg += "start: sort the tokens in the initiative counter and begin tracking\n"; helpMsg += "get : display the value of the specified config parameter, or all config parameters\n"; helpMsg += "set PARAM [VALUE]: set the specified config parameter to the specified value (defaults to true)\n"; helpMsg += "enable PARAM: set the specified config parameter to true\n"; helpMsg += "disable PARAM: set the specified config parameter to false\n"; helpMsg += "toggle PARAM: toggle the specified config parameter between true and false"; Tracker.write(helpMsg, who, "font-size: small; font-family: monospace", "Tracker"); }, handleTrackerMessage: function(tokens, msg){ var who = msg.who; msg = msg.content; if ((tokens.length > 1) && (tokens[1] == "public")){ who = ""; tokens.splice(1, 1); } if (tokens.length < 2){ return Tracker.showTrackerHelp(who, tokens[0]); } switch (tokens[1]){ case "round": if (tokens.length <= 2){ Tracker.write("Current Round: " + state.InitiativeTracker.round, who, "", "Tracker"); } else{ var round = parseInt(tokens[2]); if (round != state.InitiativeTracker.round){ state.InitiativeTracker.round = round; if (state.InitiativeTracker.announceRounds){ sendChat("", "/desc Moved to Round " + round); } // update all statuses var curCount = state.InitiativeTracker.count; if (!state.InitiativeTracker.highToLow){ curCount = -curCount; } for (var i = 0; i < state.InitiativeTracker.status.length; i++){ var status = state.InitiativeTracker.status[i]; var token = getObj("graphic", status.token); if (!token){ // token associated with this status doesn't exist anymore; remove it state.InitiativeTracker.status.splice(i, 1); i -= 1; continue; } var statusCount = status.count; if (!state.InitiativeTracker.highToLow){ statusCount = -statusCount; } var statusDuration = status.expires - round; if (statusCount > curCount){ // haven't yet come to this status' initiative count; increment remaining duration statusDuration += 1; } if (statusDuration < 0){ // status expired; remove marker and announce expiration token.set("status_" + status.status, false); state.InitiativeTracker.status.splice(i, 1); i -= 1; Tracker.announceStatusExpiration(status.name, token.get('name')); } else if (statusDuration < 10){ // status has nine or fewer rounds left; update marker to reflect remaining rounds token.set("status_" + status.status, statusDuration); } } } } break; case "forward": case "fwd": var oldTurnOrderStr = Campaign().get('turnorder') || "[]"; var turnOrder = JSON.parse(oldTurnOrderStr); if (turnOrder.length > 0){ turnOrder.push(turnOrder.shift()); var newTurnOrderStr = JSON.stringify(turnOrder); Campaign().set('turnorder', newTurnOrderStr); Tracker.handleTurnChange(newTurnOrderStr, oldTurnOrderStr); } break; case "back": var oldTurnOrderStr = Campaign().get('turnorder') || "[]"; var turnOrder = JSON.parse(oldTurnOrderStr); if (turnOrder.length > 0){ // as far as handleTurnChange is concerned, we're going forward until one count back in the next round; // decrement round counter so that handleTurnChange will do the right thing state.InitiativeTracker.round -= 1; turnOrder.unshift(turnOrder.pop()); var newTurnOrderStr = JSON.stringify(turnOrder); Campaign().set('turnorder', newTurnOrderStr); Tracker.handleTurnChange(newTurnOrderStr, oldTurnOrderStr); } break; case "start": var turnOrder = JSON.parse(Campaign().get('turnorder') || "[]"); if (turnOrder.length > 0){ turnOrder.sort(function(x, y){ return (state.InitiativeTracker.highToLow ? y.pr - x.pr : x.pr - y.pr); }); Campaign().set('turnorder', JSON.stringify(turnOrder)); state.InitiativeTracker.round = 1; state.InitiativeTracker.count = turnOrder[0].pr; Tracker.announceRound(state.InitiativeTracker.round); Tracker.announceTurn(turnOrder[0].pr, turnOrder[0].custom, turnOrder[0].id); } break; case "get": if (tokens.length <= 2){ Tracker.getConfigParam(who, null); } else { Tracker.getConfigParam(who, tokens[2]); } break; case "set": if (tokens.length <= 2){ Tracker.write("Error: The 'set' command requires at least one argument (the parameter to set)", who, "", "Tracker"); break; } var value = true; if (tokens.length > 3){ if ((tokens[3] != "true") && (tokens[3] != "yes") && (tokens[3] != "1")){ value = false; } } Tracker.setConfigParam(who, tokens[2], value); break; case "enable": if (tokens.length != 3){ Tracker.write("Error: The 'enable' command requires exactly one argument (the parameter to enable)", who, "", "Tracker"); break; } Tracker.setConfigParam(who, tokens[2], true); break; case "disable": if (tokens.length != 3){ Tracker.write("Error: The 'disable' command requires exactly one argument (the parameter to disble)", who, "", "Tracker"); break; } Tracker.setConfigParam(who, tokens[2], false); break; case "toggle": if (tokens.length != 3){ Tracker.write("Error: The 'toggle' command requires exactly one argument (the parameter to toggle)", who, "", "Tracker"); break; } Tracker.setConfigParam(who, tokens[2], null); break; case "help": Tracker.showTrackerHelp(who, tokens[0]); break; default: Tracker.write("Error: Unrecognized command: " + tokens[0], who, "", "Tracker"); Tracker.showTrackerHelp(who, tokens[0]); } }, addStatus: function(tokenId, duration, status, name){ var token = getObj("graphic", tokenId); if (!token){ return; } if (Tracker.STATUS_ALIASES[status]){ status = Tracker.STATUS_ALIASES[status]; } state.InitiativeTracker.status.push({'token':tokenId, 'expires':state.InitiativeTracker.round + duration, 'count':state.InitiativeTracker.count, 'status':status, 'name':name}); if (duration > 10){ duration = true; } token.set("status_" + status, duration); }, showStatusHelp: function(who, cmd){ Tracker.write(cmd + " commands:", who, "", "Tracker"); var helpMsg = ""; helpMsg += "help: display this help message\n"; helpMsg += "add DUR ICON DESC: add DUR rounds of status effect with specified icon and description to selected tokens\n"; helpMsg += "list: list all status effects for selected tokens\n"; helpMsg += "show: synonym for list\n"; helpMsg += "remove [ID]: remove specified status effect, or all status effects from selected tokens\n"; helpMsg += "rem, delete, del: synonyms for remove\n"; helpMsg += "icons: list available status icons and aliases"; Tracker.write(helpMsg, who, "font-size: small; font-family: monospace", "Tracker"); }, handleStatusMessage: function(tokens, msg){ var who = msg.who; var selected = msg.selected; msg = msg.content; if ((tokens.length > 1) && (tokens[1] == "public")){ who = ""; tokens.splice(1, 1); } if (tokens.length < 2){ return Tracker.showStatusHelp(who, tokens[0]); } switch (tokens[1]){ case "add": if ((!selected) || (selected.length <= 0)){ Tracker.write("Error: The 'add' command requires at least one selected token", who, "", "Tracker"); break; } if (tokens.length < 5){ Tracker.write("Error: The 'add' command requires three arguments (duration, icon, description)", who, "", "Tracker"); break; } if (state.InitiativeTracker.round <= 0){ Tracker.write("Error: Initiative not being tracked", who, "", "Tracker"); break; } for (var i = 0; i < selected.length; i++){ if (selected[i]._type != "graphic"){ continue; } var token = getObj(selected[i]._type, selected[i]._id); if (!token){ continue; } Tracker.addStatus(selected[i]._id, parseInt(tokens[2]), tokens[3], tokens.slice(4).join(" ")); } break; case "list": case "show": if ((!selected) || (selected.length <= 0)){ Tracker.write("Error: The '" + tokens[1] + "' command requires at least one selected token", who, "", "Tracker"); break; } var tokenIds = []; var byToken = {}; var tokenNames = {}; for (var i = 0; i < selected.length; i++){ if (selected[i]._type != "graphic"){ continue; } var token = getObj(selected[i]._type, selected[i]._id); if (!token){ continue; } tokenIds.push(selected[i]._id); byToken[selected[i]._id] = []; tokenNames[selected[i]._id] = token.get('name'); } tokenIds.sort(function(x, y){ if (tokenNames[x] == tokenNames[y]){ return 0; } if (tokenNames[x] > tokenNames[y]){ return 1; } return -1; }); for (var i = 0; i < state.InitiativeTracker.status.length; i++){ var status = state.InitiativeTracker.status[i]; if (!byToken[status.token]){ continue; } var duration = status.expires - state.InitiativeTracker.round; if ((state.InitiativeTracker.highToLow) && (status.count < state.InitiativeTracker.count)){ duration += 1; } if ((!state.InitiativeTracker.highToLow) && (status.count > state.InitiativeTracker.count)){ duration += 1; } byToken[status.token].push("" + i + ": " + status.name + " (" + duration + ")"); } for (var i = 0; i < tokenIds.length; i++){ var from = (who ? "Tracker" : ""); if (byToken[tokenIds[i]].length <= 0){ var output = "No status effects for token " + tokenNames[tokenIds[i]]; if (who){ Tracker.write(output, who, "", from); } else{ sendChat(from, "/desc " + output); } continue; } var output = "Status effects for token " + tokenNames[tokenIds[i]] + ":"; if (who){ Tracker.write(output, who, "", from); } else{ sendChat(from, "/desc " + output); } for (var j = 0; j < byToken[tokenIds[i]].length; j++){ Tracker.write(byToken[tokenIds[i]][j], who, "", "Tracker"); } } break; case "remove": case "rem": case "delete": case "del": if ((tokens.length == 2) && (selected) && (selected.length > 0)){ // some tokens selected and no ID specified; remove all status effects from selected tokens for (var i = 0; i < state.InitiativeTracker.status.length; i++){ var status = state.InitiativeTracker.status[i]; for (var j = 0; j < selected.length; j++){ if ((selected[j]._type != "graphic") || (selected[j]._id != status.token)){ continue; } var token = getObj(selected[j]._type, selected[j]._id); if (!token){ continue; } token.set("status_" + status.status, false); state.InitiativeTracker.status.splice(i, 1); i -= 1; break; } } break; } // ID specified or nothing selected; require ID and remove specified status effect if (tokens.length != 3){ Tracker.write("Error: The '" + tokens[1] + "' command requires an argument (status effect ID)", who, "", "Tracker"); break; } var idx = parseInt(tokens[2]); if ((idx < 0) || (idx >= state.InitiativeTracker.status.length)){ Tracker.write("Error: Invalid status effect ID: " + tokens[2], who, "", "Tracker"); break; } var status = state.InitiativeTracker.status[idx]; var token = getObj("graphic", status.token); token.set("status_" + status.status, false); state.InitiativeTracker.status.splice(idx, 1); break; case "icons": Tracker.write("Status Icons: " + Tracker.ALL_STATUSES.join(", "), who, "", "Tracker"); Tracker.write("Status Aliases:", who, "", "Tracker"); var output = ""; for (var k in Tracker.STATUS_ALIASES){ if (output){ output += "\n"; } output += k + ": " + Tracker.STATUS_ALIASES[k]; } Tracker.write(output, who, "", "Tracker"); break; case "help": Tracker.showStatusHelp(who, tokens[0]); break; default: Tracker.write("Error: Unrecognized command: " + tokens[0], who, "", "Tracker"); Tracker.showStatusHelp(who, tokens[0]); } }, handleChatMessage: function(msg){ if (msg.type != "api"){ return; } if ((msg.content == "!tracker") || (msg.content.indexOf("!tracker ") == 0)){ return Tracker.handleTrackerMessage(msg.content.split(" "), msg); } if ((msg.content == "!status") || (msg.content.indexOf("!status ") == 0)){ return Tracker.handleStatusMessage(msg.content.split(" "), msg); } }, registerTracker: function(){ Tracker.initConfig(); on("change:campaign:turnorder", Tracker.handleTurnChange); if ((typeof(Shell) != "undefined") && (Shell) && (Shell.registerCommand)){ Shell.registerCommand("!tracker", "!tracker <subcommand> [args]", "Configure the initiative tracker", Tracker.handleTrackerMessage); Shell.registerCommand("!status", "!status <subcommand> [args]", "Track status effects on tokens", Tracker.handleStatusMessage); if (Shell.write){ Tracker.write = Shell.write; } } else{ on("chat:message", Tracker.handleChatMessage); } } }; on("ready", function(){ Tracker.registerTracker(); })
D'oh; good catch. Looks like I missed a line when I refactored the script to be more self-contained. On lines 160 and 177 there are instances of CONFIG_PARAMS that should be Tracker.CONFIG_PARAMS; just add the "Tracker." in front of them and it should be okay. I'll fix it later today when I'm out of work (or at least out of the department meeting that's about to start).
1426202526
Ziechael
Forum Champion
Sheet Author
API Scripter
manveti said: or at least out of the department meeting that's about to start. Just do what i do, take your laptop in... nod in all the right places and look like you are taking notes (the key is to angle your screen away from everyone else and listen out for keywords to sound like you were listening should your name get mentioned ;) ).
1426210563

Edited 1426211092
Oh wow Manveti himself! XD Thanks so much for taking a look at it, woot. How in the world you knew where to look so quickly is beyond me, but thank you so much. I am also having trouble using !status add. For instance I am adding Rage to a barbarian for 10 rounds and I use the screaming icon. It works perfect the first instance I use it (!status add 10 screaming Rage) and the icon counts down and is awesome but then I can't get any other buffs on any other characters or even the same one NOT using the same icon. Any ideas off the top of your head? :C **Actually so far so good, I was using /talktomyself so I wasn't spamming the chatlog and it wasn't letting any of the commands work. Turned it off and working perfect again.
I've fixed the problem in my repository and submitted a pull request (also found and fixed a bug one of my players suspected: statuses added at the bottom of the initiative order were counting down and expiring as if they had been added at the top -- reducing their duration by almost a full turn). It's pretty easy to track down a problem like this from the error trace you gave: ReferenceError: CONFIG_PARAMS is not defined means that something was trying to use a variable called CONFIG_PARAMS which didn't exist. From there, it's just a matter of searching for that name and looking for anywhere it didn't have the appropriate namespace qualifier. The next lines of the traceback tell you what was executing in the script when the error happened: at Object.Tracker.setConfigParam (evalmachine.<anonymous>:373:10) at Object.Tracker.handleTrackerMessage (evalmachine.<anonymous>:515:14) at Tracker.handleChatMessage (evalmachine.<anonymous>:704:94) means that the error happened inside the Tracker.setConfigParam function (which was called by Tracker.handleTrackerMessage, which, in turn, was called by Tracker.handleChatMessage). Unfortunately, because of the way Roll20 handles its scripts, the filename and line number entries (the stuff in parentheses at the end of each line) in the traceback are essentially meaningless. But knowing which function the error happened in is often enough; you can just search for the function name and hopefully have something fairly self-contained to look at.
Awesome great to know! I hadn't noticed that other bug, but I will download the whole new script anyway just to be safe. Thanks so much for a really great script, too - does exactly everything I wanted and I love using the icons. :D