/** * TimeTracker.js * * @author Filip Čapek - capekfilip@capekfilip.cz * @version 0.3 * @date September 28, 2015 * @date updated December 6, 2015 * * Based on TrackerJacker.js https://github.com/FGKenL/Roll20_API_Scripts/blob/master/trackerjacker.js * By Ken L. */ var TimeTracker = (function() { 'use strict'; var version = 0.3, author = 'Filip Č.'; var fields = { feedbackName: 'TimeTracker', feedbackImg: 'https://raw.githubusercontent.com/capekfilip/roll20-scripts/master/time-tracker/thumb.png', }; var flags = { archive: false, }; var design = { delete_icon: 'https://s3.amazonaws.com/files.d20.io/images/11381509/YcG-o2Q1-CrwKD_nXh5yAA/thumb.png?1439051579' }; /** * Init */ var init = function() { if (!state.timetracker) { state.timetracker = {}; } if (!state.timetracker.time) { state.timetracker.time = {}; } if (!state.timetracker.timeformat) { state.timetracker.timeformat = 24; } if (!state.timetracker.events) { state.timetracker.events = {}; } }; /** * Send an error */ var sendError = function(msg) { sendFeedback(''+msg+''); }; /** * Send feedback message */ var sendFeedback = function(msg) { var content = '/w GM ' + '
' + '' + '
' + msg; sendChat(fields.feedbackName,content,null,(flags.archive ? {noarchive:true}:null)); }; /** * Send public message */ var sendPublic = function(msg) { if (!msg) { return undefined; } var content = '/desc ' + msg; sendChat('',content,null,(flags.archive ? {noarchive:true}:null)); }; /** * Handle chat message event */ var handleChatMessage = function(msg) { var args = msg.content, senderId = msg.playerid, selected = msg.selected; if (msg.type === 'api' && playerIsGM(senderId) && args.indexOf('!time') === 0) { args = args.replace('!time','').trim(); if (args.indexOf('-help') === 0) { showHelp(); } else if (args.indexOf('-setformat') === 0) { args = args.replace('-setformat','').trim(); doSetFormat(args); } else if (args.indexOf('-set') === 0) { args = args.replace('-set','').trim(); doSetTime(args); } else if (args.indexOf('-plus') === 0) { args = args.replace('-plus','').trim(); doPlusTime(args); } else if (args.indexOf('-addevent') === 0) { args = args.replace('-addevent','').trim(); doAddEvent(args); } else if (args.indexOf('-events') === 0) { doDisplayEvents(); } else if (args.indexOf('-removeevent') === 0) { args = args.replace('-removeevent','').trim(); doRemoveEvent(args); } else if (args.indexOf('-show') === 0) { doShowTime(); } else { sendFeedback('Invalid command " '+msg.content+' "'); showHelp(); } } }; /** * Show help message */ var showHelp = function() { var content = '
' +'
' +'TimeTracker v'+version +'
' +'
' +'

Tracking ingame time and events like lamps, torches and long duration spells (Mage Armor).

' +'
' +'
' +'!time -help' +'
' +'

This command displays the help.

' +'
' +'
' +'
' +'!time -setformat <timeformat>' +'
' +'

Set the show time format.

' +'This command requires 1 parameter:' +'
    ' +'
  • ' +'<timeformat> -- The numeric value of time format. Parametr must be only 12 or 24. Default value is 24. Example 12.' +'
  • ' +'
' +'
' +'
' +'
' +'!time -set <hours>:<minutes>' +'
' +'

Set the current time.

' +'This command requires 2 parameters:' +'
    ' +'
  • ' +'<hours> -- The numeric value of hours. Must be in 24-hours time format. Example 10.' +'
  • ' +'
  • ' +'<minutes> -- The numeric value of minutes. Example 30.' +'
  • ' +'
' +'
' +'
' +'
' +'!time -plus <hours>:<minutes>' +'
' +'

Add hours and minutes to current time. This ammount of time is even automatically deductive from events duration.

' +'This command requires 2 parameters:' +'
    ' +'
  • ' +'<hours> -- The numeric value of how much hours add to current time. Example 1.' +'
  • ' +'
  • ' +'<minutes> -- The numeric value of how much minutes add to current time. Example 28.' +'
  • ' +'
' +'
' +'
' +'
' +'!time -show' +'
' +'

This command displays the current time.

' +'
' +'
' +'
' +'!time -addevent <name>:<hours>:<minutes>' +'
' +'

Add a event to list and automatically track it's duration if is used !time -plus command.

' +'This command requires 3 parameters:' +'
    ' +'
  • ' +'<name> -- String of the event name. Example PC's Mage Armor.' +'
  • ' +'
  • ' +'<hours> -- The numeric value of how much hours duration event have. Example 8.' +'
  • ' +'
  • ' +'<minutes> -- The numeric value of how much minutes duration event have. Example 0.' +'
  • ' +'
' +'
' +'
' +'
' +'!time -events' +'
' +'

This command displays the active events and it's remaining duration.

' +'
' +'
' +'
'; sendFeedback(content); }; /** * Set the current ingame time */ var doSetTime = function(args) { if (!args) {return;} args = args.split(/:| %% /); if (args.length < 1 || args.length > 3) { sendError('Invalid time syntax.'); return; } var hours = parseInt(args[0]), minutes = parseInt(args[1]); if (isNaN(hours) || isNaN(minutes) || hours > 24 || minutes > 60) { sendError('Invalid time syntax.'); return; } if (hours < 10) { var hours = '0'+hours; } if (minutes < 10) { var minutes = '0'+minutes; } var newTime = { hours: hours, minutes: minutes }; state.timetracker.time = newTime; var content = 'Time has been set.'; sendFeedback(content); doShowTime(); }; /** * Add how much time past from init */ var doPlusTime = function(args) { if (!args) { return; } args = args.split(/:| %% /); if (args.length < 1 || args.length > 3) { sendError('Invalid time syntax.'); return; } var hours = parseInt(args[0]), minutes = parseInt(args[1]); if (isNaN(hours) || isNaN(minutes)) { doShowTime(); return; } else { var oldTotalSeconds = (state.timetracker.time.hours * 3600) + (state.timetracker.time.minutes * 60); var newTotalSeconds = (hours * 3600) + (minutes * 60); var newTimeSeconds = oldTotalSeconds + newTotalSeconds; var newHours = Math.floor((newTimeSeconds % 86400) / 3600); var newMinutes = Math.floor(((newTimeSeconds % 86400) % 3600) / 60); if (newHours < 10) { var newHours = '0'+newHours; } if (newMinutes < 10) { var newMinutes = '0'+newMinutes; } var newTime = { hours: newHours, minutes: newMinutes }; state.timetracker.time = newTime; var content = 'Time has been updated.'; sendFeedback(content); doShowTime(); checkEventsTimeLeft(newTotalSeconds); } }; /** * Convert time to 12-hours */ var timeConvert = function(time) { // Check correct time format and split into components time = time.toString ().match (/^([01]\d|2[0-3])(:)([0-5]\d)(:[0-5]\d)?$/) || [time]; if (time.length > 1) { // If time format correct time = time.slice (1); // Remove full string match value time[5] = +time[0] < 12 ? 'AM' : 'PM'; // Set AM/PM time[0] = +time[0] % 12 || 12; // Adjust hours } return time.join (''); // return adjusted time or original string }; /** * Set show time format */ var doSetFormat = function(args) { if (!args) { return; } args = args.split(/:| %% /); if (args.length < 1 || args.length > 2) { sendError('Invalid time format syntax.'); return; } var timeformat = parseInt(args[0]); if (timeformat != 24 && timeformat != 12) { sendError('Invalid time format syntax.'); return; } state.timetracker.timeformat = timeformat; var content = 'Time format has been set to '+timeformat+'-hours.'; sendFeedback(content); }; /** * Show current ingame time */ var doShowTime = function() { var time = state.timetracker.time.hours+':'+state.timetracker.time.minutes; var disp = 'The current time is' +'
'+time+''; if (state.timetracker.timeformat == 12) { var timeConverted = timeConvert(time); var dispConverted = 'The current time is' +'
'+timeConverted+''; sendPublic(dispConverted); } else { sendPublic(disp); } }; /** * Add event to tracker */ var doAddEvent = function(args) { if (!args) { return; } args = args.split(/:| %% /); if (args.length < 3 || args.length > 4) { sendError('Invalid event syntax'); return; } var name = args[0], durHours = parseInt(args[1]), durMinutes = parseInt(args[2]); if (typeof(name) === 'string') { name = name.toLowerCase(); } if (isNaN(durHours) || isNaN(durMinutes)) { sendError('Invalid event syntax'); return; } if (eventExists(name)) { sendError('Event with the name "'+name+'" already exists'); return; } var durTotalSeconds = (durHours * 3600) + (durMinutes * 60); var newHours = Math.floor(durTotalSeconds / 3600); var newMinutes = Math.floor((durTotalSeconds % 3600) / 60); var newEvent = { name: name, durHours: newHours, durMinutes: newMinutes }; state.timetracker.events[name] = newEvent; var content = 'Event ' + ''+name+' was added.'; sendFeedback(content); }; /** * Check if a event exists */ var eventExists = function(statusName) { statusName = statusName.toLowerCase(); var found = _.find(_.keys(state.timetracker.events), function(e) { return e === statusName; }); if (found) { found = state.timetracker.events[found]; } return found; }; /** * Build a listing of events * */ var makeEventsConfig = function() { var midcontent = '', content = ''; _.each(state.timetracker.events,function(e) { if (e.durHours < 10) { var leftHours = '0'+e.durHours; } else { var leftHours = e.durHours; } if (e.durMinutes < 10) { var leftMinutes = '0'+e.durMinutes; } else { var leftMinutes = e.durMinutes; } midcontent += '' +'' +leftHours+':'+leftMinutes +'' +'' +e.name +'' +'' +'' + '' +''; }); if ('' === midcontent) { midcontent = 'No Events Available'; } content = '
' + '
' + 'Events' + '
' + ''; content += midcontent; content += '
'; return content; }; /** * Display events configuration */ var doDisplayEvents = function() { var content = makeEventsConfig(); sendFeedback(content); }; /** * Remove event from the tracker */ var doRemoveEvent = function(args) { if (!args) { return; } args = args.split(/:| %% /); if (args.length < 1 || args.length > 2) { sendError('Invalid event syntax'); return; } var name = args[0]; if (typeof(name) === 'string') { name = name.toLowerCase(); } if (!eventExists(name)) { sendFeedback('Event "' + name + '" is not on the list'); return; } var content = 'Event ' + ''+name+' removed from event list.'; delete state.timetracker.events[name]; sendFeedback(content); }; var checkEventsTimeLeft = function(plusTotalSeconds) { _.each(state.timetracker.events,function(e) { var durTotalSeconds = (e.durHours * 3600) + (e.durMinutes * 60); var leftTimeSeconds = durTotalSeconds - plusTotalSeconds; if (leftTimeSeconds <= 0) { var content = ''+e.name+' went off.'; sendFeedback(content); doRemoveEvent(e.name); } else { var newHours = Math.floor(leftTimeSeconds / 3600); var newMinutes = Math.floor((leftTimeSeconds % 3600) / 60); e.durHours = newHours; e.durMinutes = newMinutes; } }); } /** * Write to log if its ready */ var loadMessage = function() { log('-=> TimeTracker v'+version+' is ready! Time format is set to '+state.timetracker.timeformat+'-hours. <=-'); }; /** * Register and bind event handlers */ var registerAPI = function() { on('chat:message',handleChatMessage); }; return { init: init, registerAPI: registerAPI, loadMessage: loadMessage }; }()); on("ready", function() { 'use strict'; TimeTracker.init(); TimeTracker.registerAPI(); TimeTracker.loadMessage(); });