Advertisement Create a free account

[Script] Turn Manager

1396077370

Edited 1414306099
A little script I modified from HoneyBadger's (much thanks) TurnHighlighter script, it has been modified except for the main turn processing logic. Change Log: v1.0 03/29/2014: Initial Release v1.1 3/29/2014: Adding command !turninit [ordering] Adds/Reinitializes Round tracker automatically when initiated v1.2 3/30/2014: Fixed a couple bugs regarding object types v1.3 4/04/2014 Fixed the Round 0 issue. Fixed a crash bug that occurs when attempting to !turninit an empty turn order list Added a command !eoc - this signals end of combat and clears all tint values as well as clears the turn order list v1.4 4/05/2014 Fixed my last fix - now prevents multiple Round markers from adding to the Turn Order. v1.5 10/26/2014 Added a NotifyEndOfTurn and NotifyStartOfTurn option that sends a chat message when a turn ends and a turn begins. (See post below for screen shots) Known Bugs: [Fixed]: Sometimes have to send !turninit command twice to initiate Round 1 Functions: Tints the token that is at the top of the turn list, Color settable. Provides an end of turn command (!eot) to all players with restrictions (more later) Provides a tint reset command to reset the tint of all tokens currently in the turn order list. Other utility functions. Options: AnnounceNewRound = true; // Turns on round announcement (legacy from HoneyBadgers original script, I really like it so I left it) tint_color_turn = '#FFFF00'; // Tint color for the current tokens turn (currently yellow) tint_color_default = 'transparent'; // Default tint color (when it's not a tokens turn) GmIdList = []; A list of d20 user id's that should be considered a GM in regards to sending commands. (use !getmyid to obtain the id needed) Commands: - Init Tracker : !turninit [ordering] ordering : 321 : (default) numeric descending; e.g. !turninit 321 or !turninit 123 : numeric ascending; e.g. !turninit 123 abc : A to Z ordering; e.g. !turninit abc cba : Z to A ordering; e.g. !turninit cba !turninit is a GM only command. - End of Turn: !eot This can be added to a macro along with other normal end of turn actions (such as saving throws for you D&D 4ers). This helps provide a way to remember making those pesky saving throws. This command can only be performed by a player that can control the token whose turn it is (at the top of the list). If a player uses !eot when it's not his/her turn, a message will be sent to them via chat letting them know. Options: Two options for !eot RestrictEOT = true ; Allows only players that can control a token to end the tokens turn. false ; Allows anyone to use !eot cmd on any token. RestrictGmEOT = false; Allows the GM to use the !eot command on any token; true; forces GM to obey RestrictEOT rule. - GetMyId: !getmyid Responds via a chat whisper with the players _d20userid (e.g. getObj("player",playerid).get("_d20userid")); - Reset End of Turn: !reot Resets the Tint value of all tokens in the turn order. The tinting will resume once the turn order changes after issuing this command. GM Only command - End of Combat: !eoc Ends the combat by resetting all tint values and clearing the turn order list. GM only command The Script // VARIABLE & FUNCTION DECLARATION var GmIdList = ["415342"]; // List of GM's (Used with RestrictGmEOT) var TurnOrderAgent = TurnOrderAgent || {}; // AnnounceNewRound - Set to TRUE if you want the script to announce // the beginning of each new round. var AnnounceNewRound = true; var tint_color_turn = '#FFFF00'; // Tint color for the current tokens turn var tint_color_default = 'transparent'; // Default tint color var RestrictEOT = true; // Restricts !eot to only controllable players var RestrictGmEOT = false; // Make GM Obey RestrictEOT (Setting true allows GM to override) var alternate = false; var NotifyEndOfTurn = false; // Enable Notification in chat window the end of a turn var NotifyStartOfTurn = false; // Enable Notification in chat window the start of a turn var Version = 1.5 // Turn Ordering functions var tosorts = { "abc": function(a,b){return a.pr.toString().localeCompare(b.pr.toString())}, "cba":function(a,b){return b.pr.toString().localeCompare(a.pr.toString())}, "123":function(a,b){return a.pr-b.pr}, "321":function(a,b){return b.pr-a.pr}}; var TOSortFunc = function(turnOrder,ordering){ turnOrder.sort(function(a,b){ try{ if (a.pr.toString().substring(0, 5) == "Round") return -1; else if(b.pr.toString().substring(0,5) == "Round") return 1; else return tosorts[ordering](a,b); } catch(e) { log(e); sendChat("TOSortFunc", "/w GM " + e); } }); return turnOrder; }; /// Process Chat Messages on("chat:message", function(msg) { // Exit if not an api command if (msg.type != "api") return; // Get the API Chat Command msg.who = msg.who.split(" ",1)[0]; var command = msg.content.split(" ", 1); // End Turn if (command == "!eot") EndTurn(msg.playerid,false); // Reset tint of all tokens in turn order if(command == "!reot" && IsGM(msg.playerid)) ResetTint(); if(command == "!eoc" && IsGM(msg.playerid)) EndCombat(); // Testing if(command == "!turninit" && IsGM(msg.playerid)) InitRounds(msg); }); /// Process Turn Change callback on("change:campaign:turnorder", function(obj) { TurnOrderAgent(); }); /// Initialize the Turn Order using AutoRound Tracker with ordering if turned on. function InitRounds(msg){ var cmd = msg.content.split(" "); // tokenize command var ordering = cmd.length > 1 ? cmd[1] : "321" // default descending sort var to = Campaign().get("turnorder"); var turn_order = JSON.parse(to); // Parse the turn order information into JSON if(turn_order.length == 0) return turn_order = TOSortFunc(turn_order,ordering); // Sort turn order if(AnnounceNewRound) // only add round tracker if announce is turned on { if(turn_order[0].pr.toString().substring(0, 5) == "Round") // initialize Round # turn_order[0].pr = "Round 0"; else{ turn_order.unshift({ id: "-1", pr: "Round 0", custom: "--Round--", }); } } Campaign().set("turnorder", JSON.stringify(turn_order)); // Send the turn order back to the tracker TurnOrderAgent(); // Process the tracker now }; /* function TurnOrderSortByPr(turnOrder){ return; turnOrder.sort(function(a,b){ if (a.pr.substring(0, 5) == "Round") return -1; else if(b.pr.substring(0,5) == "Round") return 1; else return b.pr-a.pr; }); return turnOrder; }; */ /// Processes a Turn Order callback function TurnOrderAgent () { if (!Campaign().get("turnorder")) return; var turn_order = JSON.parse(Campaign().get("turnorder")); if (!turn_order.length) return; if (typeof turn_order[0].pr == "string") { if (turn_order[0].pr.toString().substring(0, 5) == "Round") { var RoundTracker = turn_order[0].pr; var CurrentRound = parseInt(RoundTracker.toString().substring(5)); turn_order[0].pr = "Round " + (CurrentRound + 1); Campaign().set({turnorder: JSON.stringify(turn_order)}); if(AnnounceNewRound == true) { log("Sending announce"); sendChat("", "/desc "); sendChat("", "/direct <div style='width: 100%; color: #C8DE84; border: 1px solid #91bd09; background-color: #749a02; box-shadow: 0 0 15px #91bd09; display: block; text-align: center; font-size: 20px; padding: 5px 0; margin-bottom: 0.25em; font-family: Garamond;'>" + turn_order[0].pr + "</div>"); EndTurn(-1,true); } } } // Exit script if custom item on turn order tracker instead of a token... if (turn_order[0].id == -1) return; var current_token = getObj("graphic", turn_order[0].id); var currentTint = current_token.get('tint_color'); ResetTint(); current_token.set({'tint_color' : tint_color_turn}); }; function EndTurn(playerid, force){ var to = Campaign().get('turnorder'); if (!to) return; // Exit if the turn order tracker is not open var turn_order = JSON.parse(to); // Parse the turn order information into JSON if (!turn_order.length) return; // Exit if there are no tokens on the tracker var turn = turn_order.shift(); // Grab the info for the top of initiative var graphic = getObj("graphic", turn.id); // get the graphic obj for the current token if(force || EotRequestValid(RestrictEOT,RestrictGmEOT,playerid,graphic)){ // if request is not valid send error // Request valid... Process if(NotifyEndOfTurn) NotifyEOT(graphic); // Notify End of turn if enabled turn_order.push({ // Add the info to the bottom of initiative id: turn.id, pr: turn.pr, custom: turn.custom }); if(NotifyStartOfTurn) NotifySOT(getObj("graphic", turn_order[0].id)); // Notify Start of turn if enabled Campaign().set("turnorder", JSON.stringify(turn_order)); // Send the turn order back to the tracker log(turn_order); TurnOrderAgent(); } else{ SendChatTo(playerid, "TurnAgent", "It's not your turn silly"); return; } }; function NotifyEOT(graphic){ var char = getObj("character", graphic.get("represents")); if(char){ sendChat("", "/desc "); sendChat("", "/direct <div style='width: 100%; color: #C8DE84; border: 1px solid #91bd09; background-color: #A00000; box-shadow: 0 0 15px #91bd09; display: block; text-align: center; font-size: 20px; padding: 5px 0; margin-bottom: 0.25em; font-family: Garamond;'>" + "Ends Turn - " + char.get("name") + "</div>"); } } function NotifySOT(graphic){ var char = getObj("character", graphic.get("represents")); if(char){ sendChat("", "/desc "); sendChat("", "/direct <div style='width: 100%; color: #C8DE84; border: 1px solid #91bd09; background-color: #749a02; box-shadow: 0 0 15px #91bd09; display: block; text-align: center; font-size: 20px; padding: 5px 0; margin-bottom: 0.25em; font-family: Garamond;'>" + "Starts Turn - " + char.get("name") + "</div>"); } } /// Resets the Tint value of all tokens in the Turn Order to tint_color_default function ResetTint(){ if (!Campaign().get('turnorder')) return; // Exit if the turn order tracker is not open var turn_order = JSON.parse(Campaign().get('turnorder')); // Parse the turn order information into JSON if (!turn_order.length) return; // Exit if there are no tokens on the tracker turn_order.forEach(function(entry){ // Reset all tint colors for current turn order list try{ if(entry.id != "-1"){ var token = getObj("graphic", entry.id); if(token) token.set({'tint_color' : tint_color_default}); // Reset tint value to transparent } //Campaign().set("turnorder", {}); // Reset Tracker }catch(e) { log(e); sendChat("GM", "/w GM Error occured in TurnHighlighter2 script func(ResetTint)"); sendChat("GM", "/w GM " + e); return; } }); }; /// Ends combat clearing all tint values and removing all tokens from the turn order function EndCombat(){ ResetTint(); Campaign().set("turnorder", "[]"); // Reset Tracker }; /// Returns true if the EOT Request is valid given the inputs. /// restrictEot: true: only controlling players can request, false: allows all /// restrictGmEOT: true: GMs must follow restrictEot rule, otherwise they are always valid /// playerid: playerid of the requester /// graphic: graphic object function EotRequestValid(restrictEot, restrictGmEOT, playerid, graphic){ return !restrictEot || // No Restrictions (!restrictGmEOT && IsGM(playerid)) || // GM Override (IsControlledBy(graphic, playerid)); // controlling player }; /******************************************************************** Utility Functions ********************************************************************/ /// Returns the requesters player._d20userid value via chat on("chat:message", function(msg) { // Exit if not an api command if (msg.type != "api") return; // Get the API Chat Command var command = msg.content.split(" ", 1); if (command == "!getmyid") SendChatTo(msg.playerid, "GetMyId", "UserId: " + getObj("player", msg.playerid).get("_d20userid")); }); /// Send a whisper to a player using playerid function SendChatTo(playerid, chatMsg){ SendChatTo(playerid,"Script",chatMsg); }; /// Send a whisper to a player using a playerid with a custom source function SendChatTo(playerid, sendAs, chatMsg){ var player = getObj("player", playerid); if(player) sendChat(sendAs, "/w " + player.get("_displayname").split(" ",1) + " " + chatMsg); }; /// Returns -1 if the value is not in the array, otherwise the array index of the value function ArraySearch(array, value){ if(array.length <= 0) return -1; for(var i = 0; i < array.length; i++) { if(array[i] == value) return i; } return -1; } /// Returns an array of playerids that control the graphic /// First checks if token is a character /// true: list of playerids that control the character /// false: list of playerids that control the graphic e.g. graphic.controlledby function GetControlledBy(graphic){ var charId = graphic.get("represents"); if(charId) return getObj("character", charId).get("controlledby").split(","); return graphic.get("controlledby").split(","); }; /// Returns true if "all" or playerid is a controller for the specified graphic or /// note: while the GM can control all, this does not return true for GM's playerid /// unless GM is explicitly set or "all" is set function IsControlledBy(graphic, playerid){ var controllerIds = GetControlledBy(graphic); if(controllerIds.length <= 0) return false; for(var i = 0; i < controllerIds.length; i++) { if(controllerIds[i] == playerid || controllerIds[i] == "all") return true; } return false; }; /// Returns true if the given playerid is a GM /// Uses the GmIdList global to determine if player is a GM function IsGM(playerid){ if(!GmIdList) return false; return ArraySearch(GmIdList, getObj("player", playerid).get("_d20userid")) >= 0; };
Hi, can you make it include a custom object in the turn order that tracks the number of rounds, automatically?
Indeed I'll look into adding that feature.
Jarret B. said: Hi, can you make it include a custom object in the turn order that tracks the number of rounds, automatically? I have added the capability. Now rather than using the turn order interface or initialize and change turns, I recommend using a macro. Macro Example to initialize the tracker in numeric descending order: !turninit 321 I'm not really satisfied with this solution, however, it was the best I could come up with. There are a few things I might change in the future in regards to logic, but they should be transparent. Specifically the way the Round value increases. I think I would prefer the round value to increase when it's pushed to the bottom of the list rather than being shifted to the top. If you have any suggestions on a different approach, I'd be happy to take a look. I would love for them to include some way to hook more events regarding the turn tracker such as on add/delete, on clear, even on reorder would be nice.
It doesn't matter to me when it changes, but shouldn't it start at round 1 not round 0?
1396148929

Edited 1396149363
Jarret B. said: It doesn't matter to me when it changes, but shouldn't it start at round 1 not round 0? It should start at round 0. Whenever the !turninit is called it will order the tokens. The Round indicator will auto shift to the bottom so it's not needed to end it's turn. Basically whenever !turninit is called it should put Round 1 at the bottom of the initiative stack with all other tokens in the desired sorting. Is this not happening for you? Here is a screen shot that shows the turn order and chat immediately after I use !turninit 321
1396152950

Edited 1396153180
Alicia
Sheet Author
I had to click it twice. Just played around with the script a little. Been looking for something like this for awhile. THANKS! Edit: Also, not really a huge deal as I'm not sure if it would do this when one isn't just spamming the rounds, but it's been doing this: <a href="http://awesomescreenshot.com/0aa2kj9kf0" rel="nofollow">http://awesomescreenshot.com/0aa2kj9kf0</a>
Alicia G said: I had to click it twice. Just played around with the script a little. Been looking for something like this for awhile. THANKS! Edit: Also, not really a huge deal as I'm not sure if it would do this when one isn't just spamming the rounds, but it's been doing this: <a href="http://awesomescreenshot.com/0aa2kj9kf0" rel="nofollow">http://awesomescreenshot.com/0aa2kj9kf0</a> Indeed there seems to be times in which the !turninit must be sent twice for the Round value to initiate. I'll have to look into why this is happening. In regards to the Rounds counting off like your SS shows, that is how I would expect it to go. In truth the script will always announce when a round has increased. I can however, make that an option to turn on round tracking and announcing separate. I have found a couple errors regarding object type, as such I have updated the script to v1.2.
1396190229
Alicia
Sheet Author
Chris N. said: Alicia G said: I had to click it twice. Just played around with the script a little. Been looking for something like this for awhile. THANKS! Edit: Also, not really a huge deal as I'm not sure if it would do this when one isn't just spamming the rounds, but it's been doing this: <a href="http://awesomescreenshot.com/0aa2kj9kf0" rel="nofollow">http://awesomescreenshot.com/0aa2kj9kf0</a> Indeed there seems to be times in which the !turninit must be sent twice for the Round value to initiate. I'll have to look into why this is happening. In regards to the Rounds counting off like your SS shows, that is how I would expect it to go. In truth the script will always announce when a round has increased. I can however, make that an option to turn on round tracking and announcing separate. I have found a couple errors regarding object type, as such I have updated the script to v1.2. I perhaps should have explained a bit better. I was trying to point notice to the ":" that shows up about every 3 message. But I wasn't able to replicate it if I mixed in normal messages so it may have just been a result of me spamming the Round message. Turning the messages off might be appealing to some, however, so might be a good idea.
1396190543
Sky
Pro
Yeah... not much can be done about the blank : message. It's built in to Roll20 to do that every six entries from the same person. Mixed in with chat and attack rolls and such from other people on their turns, it isn't a problem.
I can't get the turns to advance at all, no matter if I try !turninit once or five times. I see the highlight color cycling through the tokens (or is this the native behavior?), and they're changing order in the Turn Order window, but the round # never advances beyond zero. Where am I going wrong? Very new to Roll20, so forgive my ignorance if I'm overlooking something obvious. I plugged the script into the API and saw the "Spinning up new sandbox" message. So this should be working, right?
1396192513
Alicia
Sheet Author
Did you use: !getmyid to find out your ID and then change var GmIdList = []; to include the number? for example var GmIdList = ["12345"];
Ah—that did it. Thank you! M.
1396205288

Edited 1396205910
HoneyBadger said: Yeah... not much can be done about the blank : message. It's built in to Roll20 to do that every six entries from the same person. Mixed in with chat and attack rolls and such from other people on their turns, it isn't a problem. Perhaps creating making the sendChat call using an alternating value of spaces would solve that? As such it will be someone different each time it's called. Edit: Indeed I have found no way to work around this happening.
For some reason, my v1.2 did not make it on the post. I have updated it with the correct script version that fixes an error regarding type conversion. Namely int to string conversion. If you find that the API is crashing when you use !turninit or when you add a bunch of tokens at once, please update as this should fix the problem.
1396643495

Edited 1396677466
v1.3 4/04/2014 Fixed the Round 0 issue. Fixed a crash bug that occurs when attempting to !turninit an empty turn order list Added a command !eoc - this signals end of combat and clears all tint values as well as clears the turn order list v1.4 04/05/2014 Fixed my last fix that caused multiple Round markers to add into the turn order.
1397188676

Edited 1414306437
Possible error when trying to initialize with an empty turn order. v1.5 10/26/2014 Two options NotifyEndOfTurn and NotifyStartOfTurn added to the stop of the script. When they are enabled, they will announce a turn has ended/started. Here is a screen shot for multiple Start/End cycles. One can easily customize the Ends Turn and Starts Turn message by changing the NotifyEOT and NotifySOT functions respectively. This option is turned off by default. Simply change the top of the script NotifyEndOfTurn and NotifyStartOfTurn variables to true in order to turn the messages on.