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

[Script] Tell time with commands (macros!)

1417415276

Edited 1420462741
Want to quickly add in-game time to a simple, intuitive clock? Want to add 6 seconds at the end of each combat round with the click of a button? Look no further. This script allows easy time tracking built into your Roll20 experience, along with a simple timer feature. As with all of my scripts, convenience is the key to keeping grittier, hardcore campaigns running smoothly. ------------------------------------------------------------------------------------------------- Commands: !ttsethour [0-23] - sets the hour !ttsetmin [0-59] - sets the minute of the hour !ttsetsec [0-59] - sets the second of the minute !tttime - whispers the time to the gm !ttaddsec [any integer] - the most important command. This is what you'll use for macros. For example, add a 6-second increase by writing the following in your macro: !ttaddsec 6 Need a 5 minute increment? (great for out of combat searches and the like) !ttaddsec 300 Taking a long rest? Increase the clock an hour at a time with !ttaddsec 3600 !timer [set/rem] (args) - This command sets (or removes) a timer that will follow your time increments !timer set (optional: timer #) [name] [time remaining in hours:mins:secs] ex: !timer set torch 01:00:00 - This will set a timer to track a standard, fresh 5e torch (1 hour) ex: !timer set 3 confused 00:01:00 - This will set timer #3 as "confused" for 1 minute. !timer rem (optional: timer #) - If a particular timer (order) is not specified, it will remove the most recently added timer ex: !timer rem 3 - This would remove the "confused" timer above ... ex: !timer rem - ... and this would remove the torch timer. Timers are automatically removed when their time is elapsed by the clock. ------------------------------------------------------------------------------------------------- Want to plug this in to your script (for a graphical token clock, etc.)? With this script installed, plug these values into any of your scripts: state.alehounds.clock.second state.alehounds.clock.minute state.alehounds.clock.hour ------------------------------------------------------------------------------------------------- v.0.2 (2015-01-05) Significant code reduction, removal of redundancies [bugfix] Set times no longer display erroneous results due to being parsed as strings v.0.1 (2015-01-05) Beta release. Major overhaul and code clean-up Timers added! Timers will automatically count down as time is increased and update in the chat. Changed command to show the current time v.0.0.2 (2015-01-02) Set time now persist across server/script restarts v.0.0.1 (2014-12-01) Alpha release ///////////////////////////////////////////////// /***********************************************/ var MakeYourTime = { author: { name: "John C." || "Echo" || "SplenectomY", company: "Team Asshat" || "The Alehounds", contact: "<a href="mailto:echo@TeamAsshat.com" rel="nofollow">echo@TeamAsshat.com</a>", }, version: "0.2", gist: "<a href="https://gist.github.com/SplenectomY/1f198a3114903d7a3271" rel="nofollow">https://gist.github.com/SplenectomY/1f198a3114903d7a3271</a>", forum: "<a href="https://app.roll20.net/forum/post/1386741/" rel="nofollow">https://app.roll20.net/forum/post/1386741/</a>", /***********************************************/ ///////////////////////////////////////////////// setMods: function setMods() { if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0"; } else { var hour_mod = ""; } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0"; } else { var min_mod = ""; } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0"; } else { var sec_mod = ""; } return "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."; }, checkError: function checkError(type) { if (state.alehounds.clock.hour &gt; 23 || state.alehounds.clock.hour &lt; 0) { if (type = "hour") { state.alehounds.clock.hour = 0; } return "/w gm ERROR: The value must be an integer between 0 and 23"; } else if (state.alehounds.clock.minute &gt; 59 || state.alehounds.clock.second &gt; 59 || state.alehounds.clock.minute &lt; 0 || state.alehounds.clock.second &lt; 0) { if (type = "minute") { state.alehounds.clock.minute = 0; } else if (type = "second") { state.alehounds.clock.second = 0; } return "/w gm ERROR: The value must be an integer between 0 and 59"; } else return "/w gm Value set."; } }; if (!_.has(state, 'alehounds')) { state.alehounds = { clock: { hour: 0, minute: 0, second: 0 }, timers: [] }; } on("chat:message", function(msg) { var value; // Begin timer functionality if(msg.type == "api" && msg.content.indexOf("!timer") !== -1) { timerArray = msg.content.split(" "); var command = timerArray[timerArray.indexOf("!timer") + 1]; // Syntax: !timer set [name] (position) [value] if (command == "set") { if (timerArray.length == 5) { position = timerArray[timerArray.indexOf("!timer") + 3] value = timerArray[timerArray.indexOf("!timer") + 4] } else { position = state.alehounds.timers.length value = String(timerArray[timerArray.indexOf("!timer") + 3]) } valueArray = value.split(":") state.alehounds.timers[position] = timerArray[2] state.alehounds.timers[position] = { value: (parseInt(valueArray[0]) * 3600) + (parseInt(valueArray[1]) * 60) + parseInt(valueArray[2]), name: timerArray[2], } sendChat(msg.who, "/w gm Timer [" + position + ", " + timerArray[2] + "] set for " + valueArray[0] + ":" + valueArray[1] + ":" + valueArray[2]) // Syntax: !timer rem (position) } else if (command == "rem") { if (timerArray.length == 3) { position = timerArray[timerArray.indexOf("!timer") + 2] } else { position = parseInt(state.alehounds.timers.length) - 1 } sendChat(msg.who, "/w gm Timer [" + position + ", " + state.alehounds.timers[position].name + "] removed.") state.alehounds.timers.splice(position,1); } } //set the hour of day if (msg.type == "api" && msg.content.indexOf("!ttsethour ") !== -1) { state.alehounds.clock.hour = parseInt(msg.content.replace("!ttsethour ", "")); sendChat(msg.who, MakeYourTime.checkError("hour")); sendChat(msg.who, MakeYourTime.setMods()); } //set the minute of the hour if(msg.type == "api" && msg.content.indexOf("!ttsetmin ") !== -1) { state.alehounds.clock.minute = parseInt(msg.content.replace("!ttsetmin ", "")); sendChat(msg.who, MakeYourTime.checkError("minute")); sendChat(msg.who, MakeYourTime.setMods()); } //set the second of the minute if (msg.type == "api" && msg.content.indexOf("!ttsetsec ") !== -1) { state.alehounds.clock.second = parseInt(msg.content.replace("!ttsetsec ", "")); sendChat(msg.who, MakeYourTime.checkError("second")); sendChat(msg.who, MakeYourTime.setMods()); } //Adjust the seconds by a value if(msg.type == "api" && msg.content.indexOf("!ttaddsec ") !== -1) { for (i = 0; i &lt; state.alehounds.timers.length; i++) { state.alehounds.timers[i].value = state.alehounds.timers[i].value - parseInt(msg.content.replace("!ttaddsec ", "")); if (state.alehounds.timers[i].value &gt; 0) { state.alehounds.timers[i].hours = Math.floor(state.alehounds.timers[i].value / 3600); state.alehounds.timers[i].valueNew = state.alehounds.timers[i].value - (state.alehounds.timers[i].hours * 3600); state.alehounds.timers[i].mins = Math.floor(state.alehounds.timers[i].valueNew / 60); state.alehounds.timers[i].valueNew = state.alehounds.timers[i].valueNew - (state.alehounds.timers[i].mins * 60); if (state.alehounds.timers[i].hours == 0) { state.alehounds.timers[i].hours = "00" } else { state.alehounds.timers[i].hours = String(state.alehounds.timers[i].hours / 100) if (!state.alehounds.timers[i].hours.charAt(3)) { state.alehounds.timers[i].hours = state.alehounds.timers[i].hours + "0" } } if (state.alehounds.timers[i].mins == 0) { state.alehounds.timers[i].mins = "00" } else { state.alehounds.timers[i].mins = String(state.alehounds.timers[i].mins / 100) if (!state.alehounds.timers[i].mins.charAt(3)) { state.alehounds.timers[i].mins = state.alehounds.timers[i].mins + "0" } } state.alehounds.timers[i].valueNew = String(state.alehounds.timers[i].valueNew / 100); if (!state.alehounds.timers[i].valueNew.charAt(3)) { state.alehounds.timers[i].valueNew = state.alehounds.timers[i].valueNew + "0" } sendChat(msg.who, "/w gm [" + i + ", " + state.alehounds.timers[i].name + "] : " + state.alehounds.timers[i].hours.replace("0.","") + ":" + state.alehounds.timers[i].mins.replace("0.","") + ":" + state.alehounds.timers[i].valueNew.replace("0.","") + " remains.") } else { sendChat(msg.who, "/w gm [" + i + ", " + state.alehounds.timers[i].name + "] has ended!") state.alehounds.timers.splice(i,1) } } state.alehounds.clock.second = state.alehounds.clock.second + parseInt(msg.content.replace("!ttaddsec ", "")); //If minutes or seconds are over 60, translate to hours or minutes and add the remainder if (state.alehounds.clock.second &gt; 59) { state.alehounds.clock.minute = state.alehounds.clock.minute + Math.floor(state.alehounds.clock.second / 60) state.alehounds.clock.second = state.alehounds.clock.second - (Math.floor(state.alehounds.clock.second / 60) * 60); } if (state.alehounds.clock.minute &gt; 59) { state.alehounds.clock.hour = state.alehounds.clock.hour + Math.floor(state.alehounds.clock.minute / 60); state.alehounds.clock.minute = state.alehounds.clock.minute - (Math.floor(state.alehounds.clock.minute / 60) * 60); } //If hours are over 23, reset the hour timer and add the remainder if (state.alehounds.clock.hour &gt; 23) { state.alehounds.clock.hour = state.alehounds.clock.hour - (Math.floor(state.alehounds.clock.hour / 24) * 24); } //Add pretty formatting for single digits if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0"; } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0"; } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0"; } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = ""; } if(state.alehounds.clock.minute &gt; 9) { var min_mod = ""; } if(state.alehounds.clock.second &gt; 9) { var sec_mod = ""; } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); } //Show the in-game time by using !time if(msg.type == "api" && msg.content.indexOf("!tttime") !== -1) { if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0" } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0" } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0" } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = "" } if(state.alehounds.clock.minute &gt; 9) { var min_mod = "" } if(state.alehounds.clock.second &gt; 9) { var sec_mod = "" } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); } });
version 0.0.2 update - persistent variables. Your set times will now persist across script and server restarts, and I've trimmed some fat off of the code. Next planned feature is simple timers (duration) you can set that will alert you when that time passes. This will be useful for keeping track of torch life, spell buffs and debuffs, etc. I will also add an automatic 6-sec increase on round increases, per 5e rules.
1420215320
The Aaron
Pro
API Scripter
Hi John! I've got a suggestion for you. It would be better if all your variables were under a single property of the state object. It looks like you're already trying to head in that direction with your alehounds_ prefix. Consider this: // [...] if(!_has(state, 'alehounds') { state.alehounds = { clock: { hour: 0, minute: 0, second: 0 } }; } //set the hour of day if(msg.type == "api" && msg.content.indexOf("!ttsethour ") !== -1) { state.alehounds.clock.hour = msg.content.replace("!ttsethour ", "") if(state.alehounds.clock.hour &gt; 23) { sendChat(msg.who, "/w gm ERROR: The value must be an integer between 0 and 23") state.alehounds.clock.hour = 0 return;} if(state.alehounds.clock.hour &lt; 0) { sendChat(msg.who, "/w gm ERROR: The value must be an integer between 0 and 23") state.alehounds.clock.second = 0 return;} else if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0" } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0" } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0" } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = "" } if(state.alehounds.clock.minute &gt; 9) { var min_mod = "" } if(state.alehounds.clock.second &gt; 9) { var sec_mod = "" } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); } // [...] Also, just as a coding in javascript thing, you are better off always putting your curly braces and semicolons in. For example: if(state.alehounds.clock.hour &lt; 0) { sendChat(msg.who, "/w gm ERROR: The value must be an integer between 0 and 23") state.alehounds.clock.second = 0 return;} else if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0" } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0" } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0" } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = "" } if(state.alehounds.clock.minute &gt; 9) { var min_mod = "" } if(state.alehounds.clock.second &gt; 9) { var sec_mod = "" } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); You probably intended to check if the hour is less than 0, otherwise, use it in computations: if(state.alehounds.clock.hour &lt; 0) { sendChat(msg.who, "/w gm ERROR: The value must be an integer between 0 and 23") state.alehounds.clock.second = 0 return;} else { if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0" } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0" } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0" } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = "" } if(state.alehounds.clock.minute &gt; 9) { var min_mod = "" } if(state.alehounds.clock.second &gt; 9) { var sec_mod = "" } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); } But what you really did was this: if(state.alehounds.clock.hour &lt; 0) { sendChat(msg.who, "/w gm ERROR: The value must be an integer between 0 and 23") state.alehounds.clock.second = 0 return;} else { if(state.alehounds.clock.hour &lt; 10) { var hour_mod = "0" } } if(state.alehounds.clock.minute &lt; 10) { var min_mod = "0" } if(state.alehounds.clock.second &lt; 10) { var sec_mod = "0" } if(state.alehounds.clock.hour &gt; 9) { var hour_mod = "" } if(state.alehounds.clock.minute &gt; 9) { var min_mod = "" } if(state.alehounds.clock.second &gt; 9) { var sec_mod = "" } sendChat(msg.who, "/w gm Time: " + hour_mod + state.alehounds.clock.hour + ":" + min_mod + state.alehounds.clock.minute + ":" + sec_mod + state.alehounds.clock.second + "."); The only reason it's working correctly for you is the return in the first if. With that return there, you could omit the else entirely.
Ah, sub properties. Never thought of that. I'm really starting to settle into Javascript a little better, thanks to you. These are really good suggestions from an obviously experienced coder. You deserve a lot of credit for what you're doing for the Roll20 API. You should consider setting up a donations link on your profile. I'll work these into the next build when I introduce timers. I'm learning a lot more every week. Thanks again.
1420235523
Lithl
Pro
Sheet Author
API Scripter
John C. said: You should consider setting up a donations link on your profile. There's a link in everyone's profile to gift subscription time to them. ~_^
1420235993
The Aaron
Pro
API Scripter
John C. said: Ah, sub properties. Never thought of that. I'm really starting to settle into Javascript a little better, thanks to you. These are really good suggestions from an obviously experienced coder. You deserve a lot of credit for what you're doing for the Roll20 API. You should consider setting up a donations link on your profile. I'll work these into the next build when I introduce timers. I'm learning a lot more every week. Thanks again. What I should really add to my profile is this: I highly recommend Javascript: The Good Parts by Douglas Crockford . It's a pretty quick read and will really give you a deep understanding of the good parts of javascript. I sucked at JS before reading it. I'm much better at it now. =D Based on the forum search, I've said it for 3 pages worth of search results so far. =D
Big update with !timer functionality. Feeling a lot more comfortable with this script, and my coding in general. This was my first work here at Roll20, and it was nice to revisit it, pick out all my errors and say "Ah! I know why that's dumb now!" Many thanks for all the help, and I'll be getting around to a nice video feature soon.
1420444772
The Aaron
Pro
API Scripter
Cool!