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] Event Tracking for The Aaron's Mystara Calendar Script

November 17 (9 years ago)

Edited February 20 (8 years ago)
UPDATE 02/20/2016 -- v1.3
--Split the AddEvent command into AddEvent and AddEventGM to simplify input and resolve issues with GM events not showing properly (per the recommendation of Jose P.)

UPDATE 11/18/2015 -- v1.2
---Fixed the bug where the events added on the last day of the month did not display properly.
---Fixed the bug where Public events were not added as Public when added using option 3 (add by days)
Thank you again to Gozer for catching them!

UPDATE 11/18/2015 -- v1.1
--Added functionality to be able to add events by days forward or back in addition to by a specific date.
--Fixed the bug where names with spaces caused part of the name to be included in confirmation message whispers (Thanks to Gozer the Gozeran for the catch!).
**All changes are reflected in the instructions below where appropriate**


ORIGINAL POST (with updated documentation for features)
I took a stab at some additions to Aaron's Mystara Calendar script (original script thread) for one of my upcoming campaigns and I thought I would share it.

Basically I am too lazy to write things down so I went this route :).

Below is documentation on the new functionality and a link to the code:

GitHub link:https://github.com/Cephalopd/Roll20ScriptsMASTER/b...

New Commands:
!cal AddEvent target name --- This command adds a PUBLIC event to the calendar.
example !cal AddEvent 11 12 1001 Harvest Festival

--target - 3 options
     1) Today - adds event at on the current calendar day
           example: !cal AddEvent Today Harvest Festival
     2) Specific Date dd mm yyyy - adds an event at the specified date
           example: !cal AddEvent 11 12 1001 Harvest Festival
    3) A single numeric value which will add an event that number if days in the future (if positive) or past (if negative)
           example: !cal AddEvent 10 Harvest Festival
--name - The name of the event. All text after the target and/or gm flag will be entered as the name of the event

!cal AddEventGM target name --- This command adds a GM event to the calendar.
The functionality of this command is the same as above except it adds an event visible only to the GM.

!cal RemoveEvent name --- This command removes the first event it encounters with a matching name.
example !cal RemoveEvent Harvest Festival

--name - The name of the event to be removed. It is NOT case sensitive but must match exactly in other respects

!cal GetEvents tense range -- This command makes the script whisper a table of the events to the player who sent the command. It will only show Public events to players and all events to GMs
example !cal GetEvents Past 100

--tense - 3 Options
     1) Past - Events from the current day backwards to the first event or range limit set
     2) Present - Events for the current day only
     3) Future - Events from the current day forward to the last event or range limit set
--range - An optional numeric value to limit the number of days in the future or past for which events will be listed


!cal DisplayEvents tense range -- This functions as !cal GetEvents except it sends a table into chat containing only public events specified


Changes to Existing Commands:
!cal today - Below the date text this command now also sends an event table into chat containing only public events. If there are GM only events it whispers a second table to the GM only.


!cal next - Same functionality as !cal today for the new date the calendar is set to

! cal prev - Same functionality as !cal today for the new date the calendar is set to

KNOWN ISSUES:
Currently if you have two events with the same name, only the first one will be shown if both are in the range you Get or Display. I am working on a work around or fix for this issue. For now, make sure your events are all uniquely named or that events with the same name won't be called up in the same table. My apologies for the invconvinience.

Caveats:
I absolutely built this script with my own needs and requirements in mind. I tried to make things a little more general use but if it is useful to some of you and there is additional functionality you could use please let me know and I will take a stab at it.

Additionally my formatting skills are still Level 1 so there isn't a great visual match with Aaron's original designs. I realize the roll templates are a bit jarring but its the best I could muster with my current skill set :)

I've done a fair bit of unit testing and think I have hammered out most of the bugs, but I inevitably missed some. When you find them let me know and I will be happy to try and fix them!

Please feel free to critique the script or let me know if there are things I can do to make it more useful for any of you!
This is a great idea!  I'm using his calendar (with month and day names tweaked) in my own campaign, and I've been thinking I'd like something to have notifications come up as time passes.

Will all of the original !cal commands work as usual?
November 17 (9 years ago)
Thanks Gozer! All the original !cal commands will work as they did before (with the exception of the ones listed in my original post).

If you are going to use this in a campaign that is already running the script you will need to make sure the Version value on line 3 is different than the one you are currently using (The script will have to install the variable where the events are stored). And of course make sure the other settings are the same as your current campaign so it doesn't change those (Months, year, ect).

If you run into any problems or aren't sure about something let me know and I'm more than happy to help out!
Seems to be working, except for the prev command.  When I use it, the script crashes and throws the following error:

November 17 (9 years ago)
Bummer. Sorry about the error Gozer. It seems to be working on my end which makes me think the additions to the state.Calendar variable might not have been setup correctly in your campaign with the existing calendar when you added the script. Would you mind putting the script below in your campaign and then running the !test command and sending me what shows up in the log? That will give me more to work with.

on("chat:message", function(msg) {
var cmdName = "!test";
var msgTxt = msg.content;
var cmdNamePortion = msgTxt.slice (0, cmdName.length);
if (msg.type !== 'api') return;
if (cmdNamePortion !== cmdName) return;
//TEST CODE HERE
log (state.Calendar)
});
Spinning up new sandbox...
"-=> Torch v0.8.4 <=- [Sun Aug 23 2015 16:08:06 GMT-0500 (CDT)]"
"-=> TokenNameNumber v0.5.4 <=- [Tue Sep 22 2015 17:04:58 GMT-0500 (CDT)]"
"-=> TurnMarker v1.3.1 <=- [Tue Oct 13 2015 08:17:46 GMT-0500 (CDT)]"
"-=> GroupInitiative v0.9.12 <=- [Tue Oct 13 2015 08:17:50 GMT-0500 (CDT)]"
"-=> Ammo v0.3.0 <=- [Sat Mar 28 2015 23:19:58 GMT-0500 (CDT)]"
"-=> TokenMod v0.8.14 <=- [Mon Sep 14 2015 08:02:23 GMT-0500 (CDT)]"
"-=> NoteLog v0.1.0 <=- [Mon Jun 15 2015 23:25:11 GMT-0500 (CDT)]"
{"version":1.4,"now":{"year":576,"month":11,"day":12},"setting":{"daysOfTheWeek":["Moonday","Godsday","Waterday","Earthday","Freeday","Starday","Sunday"],"weeksOfTheMonth":[1,2,3,4],"monthsOfTheYear":["Fireseek","Readying","Coldeven","Planting","FLocktime","Wealsun","Reaping","Goodmonth","Willowind","Patchwall","Readyreat","Sunsebb"],"yearPrefix":"CY "}}
November 17 (9 years ago)
Okay, it looks like the events variable did not get added to state for some reason... Running the script below with the !test command once should clear the problem up for you:

on("chat:message", function(msg) {
var cmdName = "!test";
var msgTxt = msg.content;
var cmdNamePortion = msgTxt.slice (0, cmdName.length);
if (msg.type !== 'api') return;
if (cmdNamePortion !== cmdName) return;
//TEST CODE HERE
state.Calendar.events = []
});

I'll have to do some poking around this evening to see if I can figure out why it didn't install properly with your existing campaign script.
That seems to have cleared it up - I'll let you know if anything else crops up.  Thanks!
November 17 (9 years ago)

Edited November 17 (9 years ago)
A little cosmetic thing perhaps, but it's printing out part of my name, but not the whole thing.  Think it has something to do with a name w/spaces:



EDIT: If you're taking requests, it would be nice to be able to add an event for a certain number of days in the future (in addition to being able to set events on a target date). I mainly want to use this for reminders of things that will come up in play - "Oh, I'll have that armor ready for you in two weeks." Then, rather than having to know today's date, I could just do something like: !cal AddEvent Future 14 Armor Ready
November 17 (9 years ago)

Edited November 17 (9 years ago)
Glad that seems to fix the first error!

The second is because my code is assuming a name without spaces. Oversight on my part :/. I'll put that on my to fix list and post back when I get it taken care of.

EDIT: Just saw your edit. That was something I was playing around with but never actually finished. But I can get that worked in without too much trouble. I'll post back with that addition too, though it may take me a few days :)
November 17 (9 years ago)
The Aaron
Pro
API Scripter
Sweet additions!  I'd always meant to add events to it.  I have a newer version I've been working on (but somewhat delayed by life and other things) where I intend to support events (holidays, battles, anniversaries of events, meetings, etc, etc.), and Player/GM notes.  Looks like you've done a load of the thinking about it already. =D
November 18 (9 years ago)

Gozer the Gozerian said:



EDIT: If you're taking requests, it would be nice to be able to add an event for a certain number of days in the future (in addition to being able to set events on a target date). I mainly want to use this for reminders of things that will come up in play - "Oh, I'll have that armor ready for you in two weeks." Then, rather than having to know today's date, I could just do something like: !cal AddEvent Future 14 Armor Ready

Would really like this option if at all possible.
November 18 (9 years ago)

The Aaron said:

Sweet additions!  I'd always meant to add events to it.  I have a newer version I've been working on (but somewhat delayed by life and other things) where I intend to support events (holidays, battles, anniversaries of events, meetings, etc, etc.), and Player/GM notes.  Looks like you've done a load of the thinking about it already. =D

Thanks Aaron! I appreciate the compliments! It took a while to get it all worked out but I'm pretty happy with where it is at.

Arthur B. said:

Gozer the Gozerian said:



EDIT: If you're taking requests, it would be nice to be able to add an event for a certain number of days in the future (in addition to being able to set events on a target date). I mainly want to use this for reminders of things that will come up in play - "Oh, I'll have that armor ready for you in two weeks." Then, rather than having to know today's date, I could just do something like: !cal AddEvent Future 14 Armor Ready

Would really like this option if at all possible.

The code on GitHub has been updated with this feature as well as a fix for the name spacing issue Gozer reported. See the OP for the syntax of the new functionality. Let me know if you all run into any issues or think of anything else!
November 18 (9 years ago)

Edited November 18 (9 years ago)
EDIT: I see now what you're doing - the public part says "no events," and the event is then whispered to the GM.  Sorry for doubting you!

EDIT, again: However, there does seem to be something breaking down.  When the event is added, the feedback you get from the script says "your PUBLIC event..." whether it's a Public or GM event.  I think that's what confused me at first, then I paid attention to the yellow background.


The GM portion of AddEvent doesn't seem to be working.  It adds it as a Public event:

I copied the syntax from the OP,

Then got this result:


Found another one!  It looks like something funny's going on at the end of the month:

The calendar script is interpreting the date correctly, but for some reason the event is showing as "Vatermont 0th"

Just trying to keep you on your toes!  (It's much easier to find problems than fix them.  :)
November 18 (9 years ago)
No worries! I appreciate you testing it so thoroughly! It's so easy to get myopic when its your own work.

I've found the problem on the first issue after a quick glance... It was a dumb copy/paste error. I'll get that uploaded tonight. 

I'll have to do some more digging this evening on the end of the month one, though as I'm writing this I think I may know what I did that is messing things up...

Thanks again Gozer, I appreciate the help!
November 19 (9 years ago)
Got them both Gozer. Thank you again for catching them and letting me know about them!

I did find a known issue. Since I am using roll templates to format the output, events with the same name will only show the first one. I'm playing around with a work around but the real answer is doing real formatting so this one may be a ways out. I added a note in the OP.
November 19 (9 years ago)

Edited November 19 (9 years ago)
Ok, actually, GM events now still give the notification that a Public event was set, but then shows it as public as well (i.e., it doesn't whisper the event to the GM).

EDIT:  In addition, moving the calendar to the event date after setting an event crashes the script.  Then with a reset, it works.  The error message it returns is below.


Just to clarify, here's how I'm testing.  I add the event with:

!cal AddEvent 2 GM Test Event

Then I input the command to advance the calendar:

!cal next 2

Then, the script crashes.  I restart, hit

!cal next 2

and the calendar advances 2 days, and shows the event - but it shows it not as a GM event, but a Public event.
November 19 (9 years ago)
Hmm, I'll take another look at it tonight. Thanks for the bug reports yet again and sorry for all the issues... I thought I had it working better than this :/ 
December 01 (9 years ago)
Is there any way that someone could add a 2nd moon cycle and have it show up when you pull up the current day?

December 02 (9 years ago)
The Aaron
Pro
API Scripter
On what interval?
December 02 (9 years ago)
About 1.25 times faster than the 1st.
December 02 (9 years ago)
The Aaron
Pro
API Scripter
So, 22 days? :)
December 02 (9 years ago)
Yeah, that's good.
December 02 (9 years ago)

Edited December 02 (9 years ago)
The Aaron
Pro
API Scripter
Second Moon, 22 day cycle (Which means it repeats every 11 years in that calendar):


var Calendar = Calendar || {
    //Modified with event tracking code
    version: 1.4,
    lunarPhaseSize: 15,
    lunarPhasesImage: 'https://s3.amazonaws.com/files.d20.io/images/4277527/CJJWBbiHx3jHglPdccPx3A/max.png?1401939451',
    clearImage: 'https://s3.amazonaws.com/files.d20.io/images/4277467/iQYjFOsYC5JsuOPUCI9RGA/max.png?1401938659',


    _Ordinal: function(num) {
        var ones=(num%10);
        var tens=((num%100)-ones);
        switch(ones)
        {
            case 1: return ((10 == tens) ? 'th' : 'st');
            case 2: return ((10 == tens) ? 'th' : 'nd');
            case 3: return ((10 == tens) ? 'th' : 'rd');
            default: return 'th';
        }
    },
    
    _GetOptionsFromTokens: function (tokens) {
        var options={};
        var switches=_.filter(tokens, function(tok){
            return null != tok.match(/^--/);
        });
        _.each(switches,function(s){
            switch(s)
            {
                case '--lunar': options.showLunarPhases=true; break;
                case '--nolunar': options.showLunarPhases=false; break;
                
            }
        });
        
       return options;
    },


    CheckInstall: function() {    
        if( ! state.hasOwnProperty('Calendar') || state.Calendar.version != Calendar.version)
        {
            /* Default Settings stored in the state. */
            state.Calendar = {
                version: Calendar.version,
                now: {
                    year: 1001,
                    month: 12,
                    day: 4
                },
                setting: {
                    daysOfTheWeek: [
                        'Lunadain',
                        'Gromdain',
                        'Tserdain',
                        'Moldain',
                        'Nytdain',
                        'Loshdain',
                        'Soladain'],
                    weeksOfTheMonth: [1,2,3,4],
                    monthsOfTheYear: [
                        'Nuwmont',
                        'Vatermont',
                        'Thaumont',
                        'Flaurmont',
                        'Yarthmont',
                        'Klarmont',
                        'Felmont',
                        'Fyrmont',
                        'Ambyrmont',
                        'Sviftmont',
                        'Eirmont',
                        'Kaldmont'],
                    yearPrefix: 'AC '
                },
            	events: []
            }
        }
    },
    
    AdvanceDays: function(days){
        var y=Math.floor(days/336);
        days-=(y*336);
        var m = Math.floor(days/28);
        days-=(m*28);
        
        var n = state.Calendar.now;
        
        n.day+=days;
        var _m=Math.floor((n.day-1)/28);
        n.day-=(_m*28);
        m+=_m;
        
        n.month+=m;
        var _y=Math.floor((n.month-1)/12);
        n.month-=(_y*12);
        y+=_y;
        
        n.year+=y;
        
        state.Calendar.now=n;
    },
    
    RemoveDays: function(days){
        var y=Math.floor(days/336);
        days-=(y*336);
        var m = Math.floor(days/28);
        days-=(m*28);
        
        var n = state.Calendar.now;
        
        n.day-=days;
        var _m=((n.day>0)?(0):(1));
        n.day+=(_m*28);
        m+=_m;
        
        n.month-=m;
        var _y=((n.month>0)?(0):(1));
        n.month+=(_y*12);
        y+=_y;
        
        n.year-=y;
        
        state.Calendar.now=n;
    },
    
    _GetPhaseForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showLunarPhases: true
        }),
        moon1={
            row: ((d.day-1)%7),
            col: Math.floor((d.day-1)/7)
        },
        m2day=parseInt(((((d.month-1)*28+((d.year%11)*336)+d.day)%22)/21)*28),
        moon2={
            row: ((m2day-1)%7),
            col: Math.floor((m2day-1)/7)
        };
        
        return ((opt.showLunarPhases)?(
            '<img src="'
            +Calendar.clearImage
            +'" style="width: '+Calendar.lunarPhaseSize+'px; height: '+Calendar.lunarPhaseSize+'px; background:url('
            +Calendar.lunarPhasesImage
            +') -'+moon1.row*Calendar.lunarPhaseSize+'px -'+moon1.col*Calendar.lunarPhaseSize+'px;">'
            +'<img src="'
            +Calendar.clearImage
            +'" style="width: '+(Calendar.lunarPhaseSize)+'px; height: '+(Calendar.lunarPhaseSize)+'px; background:url('
            +Calendar.lunarPhasesImage
            +') -'+moon2.row*Calendar.lunarPhaseSize+'px -'+moon2.col*Calendar.lunarPhaseSize+'px;">'
            ):('')); 
    },
    
    _GetDayForDate: function(d,options){
        var opt=_.defaults((options||{}),{
        });
        
        var n=state.Calendar.now;
        var img = Calendar._GetPhaseForDate(d,opt)
        
        if(d.year == n.year && d.month == n.month && d.day == n.day)
        {
            return '<div style="white-space: nowrap;">'
                    +'<span style="font-weight: bold; color: #990000;">'
                        +d.day
                    +'</span>'
                    +img
                +'</div>';
        }
        else if( (d.year < n.year) 
        || ( (d.year <= n.year) && (d.month<n.month)) 
        || ( (d.year <= n.year) && (d.month<=n.month) && (d.day<n.day)) )
        {
            return '<div style="white-space: nowrap;">'
                    +'<strike style="color:red; font-weight: bold;">'
                        +'<span style="font-weight:bold; color:#999999;">'
                            +d.day
                        +'</span>'
                    +img
                    +'</strike>'
                +'</div>';
        }
        else
        {
            return '<div style="white-space: nowrap;">'
                    +'<span style="font-weight: bold; color: #000099;">'
                        +d.day
                    +'</span>'
                    +img
                +'</div>';
        }
    },
    
    _GetMonthForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showYear: true,
            showMonthNumber: false
        });
        
        var s=state.Calendar.setting;
        var daysHeader='';
        _.each(s.daysOfTheWeek,function(d){
            daysHeader+='<th><div style="width: 40px;margin: 0px auto;">'+d.substring(0,2)+'</div></th>';
        });
        
        var mday=_.clone(d);
        var weeks='';
        mday.day=1;
        _.each(s.weeksOfTheMonth,function(w){
            weeks+='<tr>';
            _.each(s.daysOfTheWeek,function(d){
                weeks+='<td style="vertical-align: middle; text-align:right;">';
                weeks+=Calendar._GetDayForDate(mday,opt);
                weeks+='</td>';
                mday.day++;
            });
            weeks+='</tr>';
        });
        
        return '<table style="border:1px solid black;background-color:#eeffee;">'
        +'<tr><th colspan="'+s.daysOfTheWeek.length+'">'
            +((opt.showMonthNumber)?('<div style="float:right; padding: 0px 3px;">'+d.month+'</div>'):(''))
            +s.monthsOfTheYear[d.month-1]
            +((opt.showYear)?(' '+d.year):(''))
        +'</th></tr>'
        +'<tr style="border-bottom: 1px solid #aaaaaa;">'+daysHeader+'</tr>'
        +weeks
        +'</table>';
    },
    
    _GetYearForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showLunarPhases: false,
            showYear: false,
            showMonthNumber: true
        });
        var s=state.Calendar.setting;
        var yday=_.clone(d);
        yday.day=1;
        yday.month=1;
        var months='';
        _.each([1,2,3,4],function(r){
            _.each([1,2,3],function(c){
                months+='<div style="float:left;padding: 2px 2px;">';
                months+=Calendar._GetMonthForDate(yday,opt);
                months+='</div>';
                yday.month++;
            });
        });
        
        return '<div style="background-color: #DEB887; border: 3px solid #8B4513; padding: 3px 3px;">'
        +'<div style="border-bottom: 2px solid #8B0000;margin: 3px 3px;font-weight: bold; font-size: 130%; text-align: center;">'+s.yearPrefix+d.year+'</div>'
        +months
        +'<div style="clear:both;"></div></div>';
    },
    
    _GetDateAsString: function(date){
        var s=state.Calendar.setting;
        return s.monthsOfTheYear[date.month-1]+' '+date.day+Calendar._Ordinal(date.day)+', '+s.yearPrefix +date.year;
    },
    
    ShowDate: function(d,options) {
        var opt=_.defaults((options||{}),{
            showLunarPhases: true
        });
        sendChat('','/direct '
            +'<div style=\''
                    +'color: white;'
                    +'padding: 5px 5px;'
                    +'background-color: #000033;'
                    +'font-weight: bold;'
                    +'font-family: Baskerville, "Baskerville Old Face", "Goudy Old Style", Garamond, "Times New Roman", serif;'
                    +'border: 3px solid #999999;'
                    +'text-align: center;'
                    +'\'>'
                +Calendar._GetDateAsString(d,opt)
                +' '
                +Calendar._GetPhaseForDate(d,opt)
            +'</div>'
            );
    },
    
    ShowMonth: function(d,options){
        var opt=_.defaults((options||{}),{
        });
        sendChat('','/direct '+Calendar._GetMonthForDate(d,opt));
    },
    
    ShowYear: function(d,options){
        var opt=_.defaults((options||{}),{
            showYear: false
        });
        sendChat('','/direct '+Calendar._GetYearForDate(d,opt));
    },
    
    playerGM: 0,
    senderID: '',
    
    calcDateIndex: function (day,month,year) {
        return ((year*336) + ((month-1)*28) + day);
    },
    
    calcIndexToDate: function (index) {
        var year = Math.floor(index / 336)
        var month = Math.floor((index % 336) / 28) + 1
        var day = Math.floor((index % 336) % 28)
		if (day === 0) {
			day = 28
			month = month - 1
		}
        var dateArray = [day,month,year]
        return dateArray;
    },
    
    spliceEventIn: function (location,name,index,gm){
                var allEvents = state.Calendar.events
                allEvents.splice(location,0,[name,index,gm]);
                state.Calendar.events = allEvents
    },
    
    addCalendarEvent: function (name,day,month,year,gm) {
            var firstEvent = state.Calendar.events[0]
            var endEvents = state.Calendar.events.length - 1
            var lastEvent = state.Calendar.events[endEvents]
            var newEventIndex = Calendar.calcDateIndex (day,month,year)
            if (state.Calendar.events[0]) {
                if (newEventIndex < firstEvent[1]) {
                    Calendar.spliceEventIn (0,name,newEventIndex,gm);
                    return;
                } else if (newEventIndex > lastEvent[1]) {
                    Calendar.spliceEventIn (state.Calendar.events.length,name,newEventIndex,gm);
                    return;
                } else {
                    for (var i = 0; i < state.Calendar.events.length; i++) {
                        var thisEvent = state.Calendar.events[i]
                        var thisEventIndex = thisEvent[1]
                        if (newEventIndex <= thisEventIndex) {
                            Calendar.spliceEventIn (i,name,newEventIndex,gm);
                            return;
                        };
                    };
                }; 
            } else {
              Calendar.spliceEventIn (0,name,newEventIndex,gm);
              return;
            };
    },
    
    removeCalendarEvent: function (name) {
        if (state.Calendar.events[0]) {
            for (var i = 0; i < state.Calendar.events.length; i++) {
                var thisEvent = state.Calendar.events[i]
                if (name.toUpperCase() == thisEvent[0].toUpperCase()) {
                    state.Calendar.events.splice(i,1)
                };
            };
        };
    },
    
    getEvents: function (direction,limit) {
        var currentIndex = Calendar.calcDateIndex (state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year)
        var findEdgeIndex = function (direction,limit,start) {
            if (direction == -1) {
                return start - limit;
            } else if (direction == 1) {
                return start + limit;
            };
        };
        
        var allRecordedEvents = []
        
        var returnEvents = function (start,end) {
            for (var i = 0; i < state.Calendar.events.length; i++) {
                thisEvent = state.Calendar.events[i]
                thisEventIndex = thisEvent[1]
                if (thisEventIndex >= start && thisEventIndex <= end ) {
                    allRecordedEvents.splice(state.Calendar.events.length,0,thisEvent)
                }
            };
        };
        
        var removeGMEvents = function (array) {
            var events = array
            for (var i = 0; i < array.length; i++) {
                thisEvent = events[i]
                if (thisEvent[2] === null){
                    
                } else {
                    if (thisEvent[2].toUpperCase() == "GM") {
                    events.splice(i,1);
                };
            };
            };
            return events;
        };
        
        if (direction == 0) {
            returnEvents (currentIndex,currentIndex);
        } else if (limit) {
            if (direction == -1) {
                returnEvents (currentIndex - limit, currentIndex);
            } else {
                returnEvents (currentIndex, currentIndex + limit);
            };
        } else {
            if (direction == -1) {
                returnEvents (0,currentIndex);
            } else {
                var eventsLength = state.Calendar.events.length
                var lastEvent = state.Calendar.events[eventsLength - 1]
                returnEvents (currentIndex, lastEvent[1]);
            };
        };
        
        if (playerGM == false) {
            allRecordedEvents = removeGMEvents (allRecordedEvents)
        } else {
            //DO NOTHING
        }
        return allRecordedEvents
    },
    
    convertDateArrayToObject: function (array) {
    var newDateObject = {}
    newDateObject.day = array[0]
    newDateObject.month = array[1]
    newDateObject.year = array[2]
    return newDateObject;
    },
        
    buildEventTable: function (direction,title,limit) {
        var targetEvents = Calendar.getEvents (direction,limit);
        if (targetEvents[0]) {
            var eventTable = "&{template:default} {{name=" + title + "}}"
            for (var i = 0; i < targetEvents.length; i++){
                var thisEvent = targetEvents[i]
                var thisEventIndex = thisEvent[1]
                var dateArray = Calendar.calcIndexToDate (thisEventIndex)
                var thisEventString = Calendar._GetDateAsString (Calendar.convertDateArrayToObject (dateArray));
                var newTableEntry = "{{" + thisEvent[0] + "=" + thisEventString + "}}"
                eventTable = eventTable + newTableEntry
            };
            return eventTable;
        } else {
            return "NO EVENTS FOUND FOR SPECIFIED RANGE";
        }
    },
        
    HandleInput: function(tokens){ 
        var options = Calendar._GetOptionsFromTokens(tokens);        
        tokens=_.filter(tokens, function(tok){
            return null == tok.match(/^--/);
        });
        var cmd = tokens[0] || 'month';
        
        var concatenateEventName = function (start) {
            var fullEventName = ''
            var lastWord = tokens.length-1
            for (var i = start; i < tokens.length - 1; i++) {
                var newNamePortion = tokens[i] + " "
                fullEventName = fullEventName
                fullEventName = fullEventName + newNamePortion
            };
            fullEventName = fullEventName + tokens[lastWord]
            return fullEventName;
        };
        
        var sendMsg = function (message) {
            sendChat ("CalendarEvents", "/w " + senderID[0] + " " + message)  
        };
        
        var eventsToday = function () {
            var events = Calendar.buildEventTable (0,"Today's Events")
            if (events === "NO EVENTS FOUND FOR SPECIFIED RANGE") {
                return "No Events Today";
            } else {
            return events;
            }
        };
        
        var returnEventRange = function (direction, title) {
            if (tokens[2]) {
                    var eventsMessage = Calendar.buildEventTable(direction, title + " (" + tokens[2] + " days)",parseInt(tokens[2]))
                    return eventsMessage;
                } else {
                    var eventsMessage = Calendar.buildEventTable(direction,"All " +title)
                    return eventsMessage;
                }
        };
        
        switch (cmd)
        {
            case 'month': 
                Calendar.ShowMonth(state.Calendar.now,options);
                break;
            
            case 'year':
                Calendar.ShowYear(state.Calendar.now,options);
                break;
                
            case 'today':
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                };
                break;
                
            case 'next':
                var days=tokens[1] || 1;
                Calendar.AdvanceDays(days);
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                };
                break;
            
            case 'prev':
                var days=tokens[1] || 1;
                Calendar.RemoveDays(days);
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                }
                break;
                
            case 'AddEvent':
                if (tokens[1].toUpperCase() === "TODAY" ) {
                    if (tokens[2].toUpperCase() === "GM") {
                        Calendar.addCalendarEvent (concatenateEventName(3),state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year,"GM");
                        sendMsg ("Your GM event has been added TODAY");
                    } else {
                        Calendar.addCalendarEvent (concatenateEventName(3),state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year);
                        sendMsg ("Your PUBLIC event has been added TODAY");
                    };   
                } else if (isNaN(parseInt(tokens[1])) === false && isNaN(parseInt(tokens[2])) === false && isNaN(parseInt(tokens[3])) === false) {
                    if (tokens[4].toUpperCase() === "GM" ) {
                        Calendar.addCalendarEvent (concatenateEventName(4),parseInt(tokens[1]),parseInt(tokens[2]),parseInt(tokens[3]),"GM");
                        sendMsg ("Your GM event has been added at the SPECIFIED DATE")
                    } else {
                        Calendar.addCalendarEvent (concatenateEventName(4),parseInt(tokens[1]),parseInt(tokens[2]),parseInt(tokens[3]));
                        sendMsg ("Your PUBLIC event has been added at the SPECIFIED DATE")
                    };
                } else if (isNaN(parseInt(tokens[1])) === false && isNaN(parseInt(tokens[2])) === true) {
var currentIndex = Calendar.calcDateIndex (state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year);
					var newIndex = currentIndex + parseInt(tokens[1])
					var newDate = Calendar.calcIndexToDate (newIndex)
					if (tokens[2].toUpperCase === "GM") {
					Calendar.addCalendarEvent(concatenateEventName(3),newDate[0],newDate[1],newDate[2],"GM");
						sendMsg ("Your GM event has been added " + tokens[1] + " days from today");
						} else {
					Calendar.addCalendarEvent(concatenateEventName(2),newDate[0],newDate[1],newDate[2]);
						sendMsg ("Your PUBLIC event has been added " + tokens[1] + " days from today");
						};
					};
				                break;
            
            case 'RemoveEvent':
                targetName = concatenateEventName(1)
                Calendar.removeCalendarEvent (targetName);
                sendMsg ("You have removed the event named -- " + targetName)
                break;
                
            case 'GetEvents':
                var targetFrame = tokens[1].toUpperCase()
                if (targetFrame === "PAST" ) {
                    sendChat ("CalendarEvents","/w " + senderID[0] + returnEventRange (-1,"Past Events"));
                } else if (targetFrame === "PRESENT") {
                    var eventsMessage = eventsToday ()
                    sendChat ("CalendarEvents","/w " + senderID[0] + eventsMessage);
                } else if (targetFrame === "FUTURE") {
                    sendChat ("CalendarEvents","/w " + senderID[0] + returnEventRange (1,"Upcoming Events"));
                };
                break;
            
            case 'DisplayEvents':
                playerGM = 0
                var targetFrame = tokens[1].toUpperCase()
                if (targetFrame === "PAST" ) {
                    sendChat ("CalendarEvents", returnEventRange (-1,"Past Events"));
                } else if (targetFrame === "PRESENT") {
                    var eventsMessage = eventsToday ()
                    sendChat ("CalendarEvents", eventsMessage);
                } else if (targetFrame === "FUTURE") {
                    sendChat ("CalendarEvents", returnEventRange (1,"Upcoming Events"));
                };
                break;
        }
        
    },
    
    RegisterEventHandlers: function() {
        on("chat:message", function (msg) {
            /* Exit if not an api command */
            if (msg.type != "api") return;
            
            /* clean up message bits. */
            msg.who = msg.who.replace(" (GM)", "");
            msg.content = msg.content.replace("(GM) ", "");


            var tokenized = msg.content.split(" ");
            var command = tokenized[0];
            var sendingPlayerID = msg.playerid
            senderID = msg.who.split(" ")
            playerGM = playerIsGM(sendingPlayerID);


            switch(command)
            {
                case "!cal":
                case "!calendar":
                {
                    Calendar.HandleInput(_.rest(tokenized));
                }
                break;
                
                case "!s":
                {
                    sendChat('',
                        '/direct <div style="border: 2px solid red;"><b>state.Calendar</b><br><pre>'
                        +JSON.stringify(state.Calendar,undefined,"   ").replace(/\n/g,'<br>')
                        +"</pre></div>" );
                }
                break;
            }
        });
    }  
};


on("ready",function(){
    Calendar.CheckInstall(); 
    Calendar.RegisterEventHandlers();
});

That really is an old script.  I should get around to finishing up the newer version...
December 02 (9 years ago)
So would I just add this to the current script or overwrite the current script?
December 02 (9 years ago)
The Aaron posted the script in its entirity with the modifications made.
December 02 (9 years ago)
If I copy and paste this script into original script, will it keep all the events that are in there? Or will I have to re-add the events?
December 02 (9 years ago)
The Aaron
Pro
API Scripter
It will keep them.  They are stored in the state object.  I copied Brent's version for the modifications, so it will be as his version, but with an extra moon.
December 02 (9 years ago)

The Aaron said:

It will keep them.  They are stored in the state object.  I copied Brent's version for the modifications, so it will be as his version, but with an extra moon.

Thanks Aaron. I've said it before and will say it again, you guys are awesome.
December 03 (9 years ago)
The Aaron
Pro
API Scripter
No problem!
December 19 (9 years ago)
The Aaron
Pro
API Scripter
Hey Brent, I sent you a pull request with a few minor bugfixes:  https://github.com/Cephalopd/Roll20ScriptsMASTER/p...

In particular, it should allow current calendar users to switch over without having to lose their existing configuration. =D
December 19 (9 years ago)
Thanks Aaron. I just accepted it.

Improving/fixing this has been hovering in the middle of my to-do list for a while but end of semester and wrapping up a major experiment have kept it pinned down :/

I do have Douglas Crockford's book ready for some light holiday reading though! :)
December 19 (9 years ago)
The Aaron
Pro
API Scripter
Sweet!  Ping me if you wanna chat about JS:tGP!  =D
January 01 (9 years ago)

Edited January 01 (9 years ago)
OK so... I did some fiddling, and for the most part it works... lol most...
I've attempted to convert this to Forgotten Realms.

Edit: I just found a great Calendar online that shows exactly how FR works..

Faerun Calendar

Major Differences:

  • 3 ten day weeks instead of 4 sevens (Referred to as a Ten-day)
  • No day names
  • Different month names
There are other subtle differences (like a separate day for holidays, basically a 1 day month) that I didn't bother with adding because the complexity seemed silly.

The part that i'm having issues with is the !cal year command, when i bring it up it only lists 9 months.


Here is the script I have edited.

var Calendar = Calendar || {
    //Modified with event tracking code
    version: 1.5,
    lunarPhaseSize: 15,
    lunarPhasesImage: 'https://s3.amazonaws.com/files.d20.io/images/4277527/CJJWBbiHx3jHglPdccPx3A/max.png?1401939451',
    clearImage: 'https://s3.amazonaws.com/files.d20.io/images/4277467/iQYjFOsYC5JsuOPUCI9RGA/max.png?1401938659',


    _Ordinal: function(num) {
        var ones=(num%10);
        var tens=((num%100)-ones);
        switch(ones)
        {
            case 1: return ((10 == tens) ? 'th' : 'st');
            case 2: return ((10 == tens) ? 'th' : 'nd');
            case 3: return ((10 == tens) ? 'th' : 'rd');
            default: return 'th';
        }
    },
    
    _GetOptionsFromTokens: function (tokens) {
        var options={};
        var switches=_.filter(tokens, function(tok){
            return null != tok.match(/^--/);
        });
        _.each(switches,function(s){
            switch(s)
            {
                case '--lunar': options.showLunarPhases=true; break;
                case '--nolunar': options.showLunarPhases=false; break;
                
            }
        });
        
       return options;
    },


    CheckInstall: function() {    
        if( ! state.hasOwnProperty('Calendar') || state.Calendar.version != Calendar.version)
        {
            /* Default Settings stored in the state. */
            state.Calendar = {
                version: Calendar.version,
                now: {
                    year: 1489,
                    month: 5,
                    day: 20
                },
                setting: {
                    daysOfTheWeek: [
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        ''],
                    weeksOfTheMonth: [1,2,3],
                    monthsOfTheYear: [
                        'Hammer-Jan',
                        'Alturiak-Feb',
                        'Ches-Mar',
                        'Tarsakh-Apr',
                        'Mirtul-May',
                        'Kythorn-Jun',
                        'Flamerule-Jul',
                        'Elesias-Aug',
                        'Eleint-Sep',
                        'Marpenoth-Oct',
                        'Uktar-Nov',
                        'Nightal-Dec'],
                    yearPrefix: 'DR '
                },
                events: []
            }
        }
        if(!_.has(state.Calendar,'events')){
            log('-- Adding events[]');
            state.Calendar.events = [];
        }
    },
    
    AdvanceDays: function(days){
        var y=Math.floor(days/360);
        days-=(y*360);
        var m = Math.floor(days/30);
        days-=(m*30);
        
        var n = state.Calendar.now;
        
        n.day+=days;
        var _m=Math.floor((n.day-1)/30);
        n.day-=(_m*30);
        m+=_m;
        
        n.month+=m;
        var _y=Math.floor((n.month-1)/12);
        n.month-=(_y*12);
        y+=_y;
        
        n.year+=y;
        
        state.Calendar.now=n;
    },
    
    RemoveDays: function(days){
        var y=Math.floor(days/360);
        days-=(y*360);
        var m = Math.floor(days/30);
        days-=(m*30);
        
        var n = state.Calendar.now;
        
        n.day-=days;
        var _m=((n.day>0)?(0):(1));
        n.day+=(_m*30);
        m+=_m;
        
        n.month-=m;
        var _y=((n.month>0)?(0):(1));
        n.month+=(_y*12);
        y+=_y;
        
        n.year-=y;
        
        state.Calendar.now=n;
    },
    
    _GetPhaseForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showLunarPhases: true
        });
        return ((opt.showLunarPhases)?(
        '<img src="'
            +Calendar.clearImage
            +'" style="width: '+Calendar.lunarPhaseSize+'px; height: '+Calendar.lunarPhaseSize+'px; background:url('
            +Calendar.lunarPhasesImage
            +') -'+((d.day-1)%10)*Calendar.lunarPhaseSize+'px -'+Math.floor((d.day-1)/10)*Calendar.lunarPhaseSize+'px;">'
            ):('')); 
    },
    
    _GetDayForDate: function(d,options){
        var opt=_.defaults((options||{}),{
        });
        
        var n=state.Calendar.now;
        var img = Calendar._GetPhaseForDate(d,opt)
        
        if(d.year == n.year && d.month == n.month && d.day == n.day)
        {
            return '<div style="white-space: nowrap;">'
                    +'<span style="font-weight: bold; color: #990000;">'
                        +d.day
                    +'</span>'
                    +img
                +'</div>';
        }
        else if( (d.year < n.year) 
        || ( (d.year <= n.year) && (d.month<n.month)) 
        || ( (d.year <= n.year) && (d.month<=n.month) && (d.day<n.day)) )
        {
            return '<div style="white-space: nowrap;">'
                    +'<strike style="color:red; font-weight: bold;">'
                        +'<span style="font-weight:bold; color:#999999;">'
                            +d.day
                        +'</span>'
                    +img
                    +'</strike>'
                +'</div>';
        }
        else
        {
            return '<div style="white-space: nowrap;">'
                    +'<span style="font-weight: bold; color: #000099;">'
                        +d.day
                    +'</span>'
                    +img
                +'</div>';
        }
    },
    
    _GetMonthForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showYear: true,
            showMonthNumber: false
        });
        
        var s=state.Calendar.setting;
        var daysHeader='';
        _.each(s.daysOfTheWeek,function(d){
            daysHeader+='<th><div style="width: 25px;margin: 0px auto;">'+d.substring(0,2)+'</div></th>';
        });
        
        var mday=_.clone(d);
        var weeks='';
        mday.day=1;
        _.each(s.weeksOfTheMonth,function(w){
            weeks+='<tr>';
            _.each(s.daysOfTheWeek,function(d){
                weeks+='<td style="vertical-align: middle; text-align:right;">';
                weeks+=Calendar._GetDayForDate(mday,opt);
                weeks+='</td>';
                mday.day++;
            });
            weeks+='</tr>';
        });
        
        return '<table style="border:1px solid black;background-color:#eeffee;">'
        +'<tr><th colspan="'+s.daysOfTheWeek.length+'">'
            +((opt.showMonthNumber)?('<div style="float:right; padding: 0px 3px;">'+d.month+'</div>'):(''))
            +s.monthsOfTheYear[d.month-1]
            +((opt.showYear)?(' '+d.year):(''))
        +'</th></tr>'
        +'<tr style="border-bottom: 1px solid #aaaaaa;">'+daysHeader+'</tr>'
        +weeks
        +'</table>';
    },
    
    _GetYearForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showLunarPhases: false,
            showYear: false,
            showMonthNumber: true
        });
        var s=state.Calendar.setting;
        var yday=_.clone(d);
        yday.day=1;
        yday.month=1;
        var months='';
        _.each([1,2,3],function(r){
            _.each([1,2,3],function(c){
                months+='<div style="float:left;padding: 2px 2px;">';
                months+=Calendar._GetMonthForDate(yday,opt);
                months+='</div>';
                yday.month++;
            });
        });
        
        return '<div style="background-color: #DEB887; border: 3px solid #8B4513; padding: 3px 3px;">'
        +'<div style="border-bottom: 2px solid #8B0000;margin: 3px 3px;font-weight: bold; font-size: 130%; text-align: center;">'+s.yearPrefix+d.year+'</div>'
        +months
        +'<div style="clear:both;"></div></div>';
    },
    
    _GetDateAsString: function(date){
        var s=state.Calendar.setting;
        return s.monthsOfTheYear[date.month-1]+' '+date.day+Calendar._Ordinal(date.day)+', '+s.yearPrefix +date.year;
    },
    
    ShowDate: function(d,options) {
        var opt=_.defaults((options||{}),{
            showLunarPhases: true
        });
        sendChat('','/direct '
            +'<div style=\''
                    +'color: white;'
                    +'padding: 5px 5px;'
                    +'background-color: #000033;'
                    +'font-weight: bold;'
                    +'font-family: Baskerville, "Baskerville Old Face", "Goudy Old Style", Garamond, "Times New Roman", serif;'
                    +'border: 3px solid #999999;'
                    +'text-align: center;'
                    +'\'>'
                +Calendar._GetDateAsString(d,opt)
                +' '
                +Calendar._GetPhaseForDate(d,opt)
            +'</div>'
            );
    },
    
    ShowMonth: function(d,options){
        var opt=_.defaults((options||{}),{
        });
        sendChat('','/direct '+Calendar._GetMonthForDate(d,opt));
    },
    
    ShowYear: function(d,options){
        var opt=_.defaults((options||{}),{
            showYear: false
        });
        sendChat('','/direct '+Calendar._GetYearForDate(d,opt));
    },
    
    playerGM: 0,
    senderID: '',
    
    calcDateIndex: function (day,month,year) {
        return ((year*360) + ((month-1)*30) + day);
    },
    
    calcIndexToDate: function (index) {
        var year = Math.floor(index / 360)
        var month = Math.floor((index % 360) / 30) + 1
        var day = Math.floor((index % 360) % 30)
		if (day === 0) {
			day = 30
			month = month - 1
		}
        var dateArray = [day,month,year]
        return dateArray;
    },
    
    spliceEventIn: function (location,name,index,gm){
                var allEvents = state.Calendar.events
                allEvents.splice(location,0,[name,index,gm]);
                state.Calendar.events = allEvents
    },
    
    addCalendarEvent: function (name,day,month,year,gm) {
            var firstEvent = state.Calendar.events[0]
            var endEvents = state.Calendar.events.length - 1
            var lastEvent = state.Calendar.events[endEvents]
            var newEventIndex = Calendar.calcDateIndex (day,month,year)
            if (state.Calendar.events[0]) {
                if (newEventIndex < firstEvent[1]) {
                    Calendar.spliceEventIn (0,name,newEventIndex,gm);
                    return;
                } else if (newEventIndex > lastEvent[1]) {
                    Calendar.spliceEventIn (state.Calendar.events.length,name,newEventIndex,gm);
                    return;
                } else {
                    for (var i = 0; i < state.Calendar.events.length; i++) {
                        var thisEvent = state.Calendar.events[i]
                        var thisEventIndex = thisEvent[1]
                        if (newEventIndex <= thisEventIndex) {
                            Calendar.spliceEventIn (i,name,newEventIndex,gm);
                            return;
                        };
                    };
                }; 
            } else {
              Calendar.spliceEventIn (0,name,newEventIndex,gm);
              return;
            };
    },
    
    removeCalendarEvent: function (name) {
        if (state.Calendar.events[0]) {
            for (var i = 0; i < state.Calendar.events.length; i++) {
                var thisEvent = state.Calendar.events[i]
                if (name && name.toUpperCase() == thisEvent[0].toUpperCase()) {
                    state.Calendar.events.splice(i,1)
                };
            };
        };
    },
    
    getEvents: function (direction,limit) {
        var currentIndex = Calendar.calcDateIndex (state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year)
        var findEdgeIndex = function (direction,limit,start) {
            if (direction == -1) {
                return start - limit;
            } else if (direction == 1) {
                return start + limit;
            };
        };
        
        var allRecordedEvents = []
        
        var returnEvents = function (start,end) {
            for (var i = 0; i < state.Calendar.events.length; i++) {
                thisEvent = state.Calendar.events[i]
                thisEventIndex = thisEvent[1]
                if (thisEventIndex >= start && thisEventIndex <= end ) {
                    allRecordedEvents.splice(state.Calendar.events.length,0,thisEvent)
                }
            };
        };
        
        var removeGMEvents = function (array) {
            var events = array
            for (var i = 0; i < array.length; i++) {
                thisEvent = events[i]
                if (thisEvent[2] === null){
                    
                } else {
                    if (thisEvent[2] && thisEvent[2].toUpperCase() == "GM") {
                    events.splice(i,1);
                };
            };
            };
            return events;
        };
        
        if (direction == 0) {
            returnEvents (currentIndex,currentIndex);
        } else if (limit) {
            if (direction == -1) {
                returnEvents (currentIndex - limit, currentIndex);
            } else {
                returnEvents (currentIndex, currentIndex + limit);
            };
        } else {
            if (direction == -1) {
                returnEvents (0,currentIndex);
            } else {
                var eventsLength = state.Calendar.events.length
                var lastEvent = state.Calendar.events[eventsLength - 1]
                returnEvents (currentIndex, lastEvent[1]);
            };
        };
        
        if (playerGM == false) {
            allRecordedEvents = removeGMEvents (allRecordedEvents)
        } else {
            //DO NOTHING
        }
        return allRecordedEvents
    },
    
    convertDateArrayToObject: function (array) {
    var newDateObject = {}
    newDateObject.day = array[0]
    newDateObject.month = array[1]
    newDateObject.year = array[2]
    return newDateObject;
    },
        
    buildEventTable: function (direction,title,limit) {
        var targetEvents = Calendar.getEvents (direction,limit);
        if (targetEvents[0]) {
            var eventTable = "&{template:default} {{name=" + title + "}}"
            for (var i = 0; i < targetEvents.length; i++){
                var thisEvent = targetEvents[i]
                var thisEventIndex = thisEvent[1]
                var dateArray = Calendar.calcIndexToDate (thisEventIndex)
                var thisEventString = Calendar._GetDateAsString (Calendar.convertDateArrayToObject (dateArray));
                var newTableEntry = "{{" + thisEvent[0] + "=" + thisEventString + "}}"
                eventTable = eventTable + newTableEntry
            };
            return eventTable;
        } else {
            return "NO EVENTS FOUND FOR SPECIFIED RANGE";
        }
    },
        
    HandleInput: function(tokens){ 
        var options = Calendar._GetOptionsFromTokens(tokens);        
        tokens=_.filter(tokens, function(tok){
            return null == tok.match(/^--/);
        });
        var cmd = tokens[0] || 'month';
        
        var concatenateEventName = function (start) {
            var fullEventName = ''
            var lastWord = tokens.length-1
            for (var i = start; i < tokens.length - 1; i++) {
                var newNamePortion = tokens[i] + " "
                fullEventName = fullEventName
                fullEventName = fullEventName + newNamePortion
            };
            fullEventName = fullEventName + tokens[lastWord]
            return fullEventName;
        };
        
        var sendMsg = function (message) {
            sendChat ("CalendarEvents", "/w " + senderID[0] + " " + message)  
        };
        
        var eventsToday = function () {
            var events = Calendar.buildEventTable (0,"Today's Events")
            if (events === "NO EVENTS FOUND FOR SPECIFIED RANGE") {
                return "No Events Today";
            } else {
            return events;
            }
        };
        
        var returnEventRange = function (direction, title) {
            if (tokens[2]) {
                    var eventsMessage = Calendar.buildEventTable(direction, title + " (" + tokens[2] + " days)",parseInt(tokens[2]))
                    return eventsMessage;
                } else {
                    var eventsMessage = Calendar.buildEventTable(direction,"All " +title)
                    return eventsMessage;
                }
        };
        
        switch (cmd)
        {
            case 'month': 
                Calendar.ShowMonth(state.Calendar.now,options);
                break;
            
            case 'year':
                Calendar.ShowYear(state.Calendar.now,options);
                break;
                
            case 'today':
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                };
                break;
                
            case 'next':
                var days=tokens[1] || 1;
                Calendar.AdvanceDays(days);
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                };
                break;
            
            case 'prev':
                var days=tokens[1] || 1;
                Calendar.RemoveDays(days);
                Calendar.ShowDate(state.Calendar.now,options);
                playerGM = 0
                var eventsMessage = eventsToday ()
                sendChat ('', eventsMessage)
                playerGM = 1
                var gmEventsMessage = eventsToday ()
                if (eventsMessage != gmEventsMessage) {
                    sendChat ('', "/w gm " + gmEventsMessage)
                }
                break;
                
            case 'AddEvent':
                if (tokens[1] && tokens[1].toUpperCase() === "TODAY" ) {
                    if (tokens[2].toUpperCase() === "GM") {
                        Calendar.addCalendarEvent (concatenateEventName(3),state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year,"GM");
                        sendMsg ("Your GM event has been added TODAY");
                    } else {
                        Calendar.addCalendarEvent (concatenateEventName(3),state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year);
                        sendMsg ("Your PUBLIC event has been added TODAY");
                    };   
                } else if (isNaN(parseInt(tokens[1])) === false && isNaN(parseInt(tokens[2])) === false && isNaN(parseInt(tokens[3])) === false) {
                    if (tokens[4] && tokens[4].toUpperCase() === "GM" ) {
                        Calendar.addCalendarEvent (concatenateEventName(4),parseInt(tokens[1]),parseInt(tokens[2]),parseInt(tokens[3]),"GM");
                        sendMsg ("Your GM event has been added at the SPECIFIED DATE")
                    } else {
                        Calendar.addCalendarEvent (concatenateEventName(4),parseInt(tokens[1]),parseInt(tokens[2]),parseInt(tokens[3]));
                        sendMsg ("Your PUBLIC event has been added at the SPECIFIED DATE")
                    };
                } else if (isNaN(parseInt(tokens[1])) === false && isNaN(parseInt(tokens[2])) === true) {
var currentIndex = Calendar.calcDateIndex (state.Calendar.now.day,state.Calendar.now.month,state.Calendar.now.year);
					var newIndex = currentIndex + parseInt(tokens[1])
					var newDate = Calendar.calcIndexToDate (newIndex)
					if (tokens[2].toUpperCase === "GM") {
					Calendar.addCalendarEvent(concatenateEventName(3),newDate[0],newDate[1],newDate[2],"GM");
						sendMsg ("Your GM event has been added " + tokens[1] + " days from today");
						} else {
					Calendar.addCalendarEvent(concatenateEventName(2),newDate[0],newDate[1],newDate[2]);
						sendMsg ("Your PUBLIC event has been added " + tokens[1] + " days from today");
						};
					};
				                break;
            
            case 'RemoveEvent':
                targetName = concatenateEventName(1)
                Calendar.removeCalendarEvent (targetName);
                sendMsg ("You have removed the event named -- " + targetName)
                break;
                
            case 'GetEvents':
                var targetFrame = tokens[1] && tokens[1].toUpperCase();
                if (targetFrame === "PAST" ) {
                    sendChat ("CalendarEvents","/w " + senderID[0] + returnEventRange (-1,"Past Events"));
                } else if (targetFrame === "PRESENT") {
                    var eventsMessage = eventsToday ()
                    sendChat ("CalendarEvents","/w " + senderID[0] + eventsMessage);
                } else if (targetFrame === "FUTURE") {
                    sendChat ("CalendarEvents","/w " + senderID[0] + returnEventRange (1,"Upcoming Events"));
                };
                break;
            
            case 'DisplayEvents':
                playerGM = 0
                var targetFrame = tokens[1].toUpperCase()
                if (targetFrame === "PAST" ) {
                    sendChat ("CalendarEvents", returnEventRange (-1,"Past Events"));
                } else if (targetFrame === "PRESENT") {
                    var eventsMessage = eventsToday ()
                    sendChat ("CalendarEvents", eventsMessage);
                } else if (targetFrame === "FUTURE") {
                    sendChat ("CalendarEvents", returnEventRange (1,"Upcoming Events"));
                };
                break;
        }
        
    },
    
    RegisterEventHandlers: function() {
        on("chat:message", function (msg) {
            /* Exit if not an api command */
            if (msg.type != "api") return;
            
            /* clean up message bits. */
            msg.who = msg.who.replace(" (GM)", "");
            msg.content = msg.content.replace("(GM) ", "");


            var tokenized = msg.content.split(" ");
            var command = tokenized[0];
            var sendingPlayerID = msg.playerid
            senderID = msg.who.split(" ")
            playerGM = playerIsGM(sendingPlayerID);


            switch(command)
            {
                case "!cal":
                case "!calendar":
                {
                    Calendar.HandleInput(_.rest(tokenized));
                }
                break;
                
                case "!s":
                {
                    sendChat('',
                        '/direct <div style="border: 2px solid red;"><b>state.Calendar</b><br><pre>'
                        +JSON.stringify(state.Calendar,undefined,"   ").replace(/\n/g,'<br>')
                        +"</pre></div>" );
                }
                break;
            }
        });
    }  
};


on("ready",function(){
    Calendar.CheckInstall(); 
    Calendar.RegisterEventHandlers();
});

January 02 (9 years ago)
The Aaron
Pro
API Scripter
It's because of this function:
    _GetYearForDate: function(d,options){
        var opt=_.defaults((options||{}),{
            showLunarPhases: false,
            showYear: false,
            showMonthNumber: true
        });
        var s=state.Calendar.setting;
        var yday=_.clone(d);
        yday.day=1;
        yday.month=1;
        var months='';
        _.each([1,2,3],function(r){
            _.each([1,2,3],function(c){
                months+='<div style="float:left;padding: 2px 2px;">';
                months+=Calendar._GetMonthForDate(yday,opt);
                months+='</div>';
                yday.month++;
            });
        });
        
        return '<div style="background-color: #DEB887; border: 3px solid #8B4513; padding: 3px 3px;">'
        +'<div style="border-bottom: 2px solid #8B0000;margin: 3px 3px;font-weight: bold; font-size: 130%; text-align: center;">'+s.yearPrefix+d.year+'</div>'
        +months
        +'<div style="clear:both;"></div></div>';
    },

You took the ,4 out.  Honestly, you could simplify this considerably.  This was the first Roll20 script I ever wrote, 2 years ago.  It is before I'd read Javascript: The Good Parts by Douglas Crockford and is quite shamefully ugly in design and style. Try it like this:
        _.each(_.range(state.Calendar.setting.monthsOfTheYear.length),function(){
            months+='<div style="float:left;padding: 2px 2px;">'
                   +Calendar._GetMonthForDate(yday,opt)
                +'</div>';
            yday.month++;
         });
Ugly or not it works beautifully :) 
If my defense I thought that 4 referred to the number of weeks in a month. lol
I got a copy of Javascript: The Good Parts by Douglas Crockford, I'm sure your commission check should be arriving shortly ;)
January 02 (9 years ago)
The Aaron
Pro
API Scripter
Bwahahahaha. It would be funny to know how many copies he's sold because of my mentioning it. :)
January 04 (9 years ago)
Ziechael
Forum Champion
Sheet Author
API Scripter
Now if you could just add variable weather by season I'd be a happy man indeed... or i could get off my lazy behind and add events to the current calendar/weather script i use i suppose.... nah!
January 06 (9 years ago)
Ziechael
Forum Champion
Sheet Author
API Scripter
Just playing around with this and I love the event additions, i've decided to take a different tack and add the weather from the other script to this one rather than the other way around (expect a message soon Aaron ;) ).

One question, is there a way to add repeating events currently (My players have a stronghold which generates income every 10 day (week) and it would be handy to be able to set that as a recurring event with one command).

Thanks for all the work you guys put into these scripts :)
January 06 (9 years ago)
The Aaron
Pro
API Scripter
There isn't a repeating events method yet.  I'd suggest creating a macro for setting repeating events and a Roll Query to prompt you for the date, then just run it and flesh out the next decade or so.  =D
January 06 (9 years ago)
Ziechael
Forum Champion
Sheet Author
API Scripter
That was going to be my workaround but since implementing my weather mashup will likely take a while (depending on your availability, patience and my ability to learn anything at all) i figured i'd put it out there in case some quick code would solve it ;)
January 07 (9 years ago)

Edited January 07 (9 years ago)
Ziechael I actually use the Randomizer script for weather, works great just made a macro called day advance that runs this script and the randomizer

Randomizer

this is what I get when I use it...
January 08 (9 years ago)
Ziechael
Forum Champion
Sheet Author
API Scripter
I ended up doing something similar, I stripped out all of the references to days, dates and new days in my calendar/weather script and made sure it was starting on the same day as this awesome event tracking version of Aaron's Calendar. Then I set up a macro that runs both the !cal next and !day commands from both scripts which gives me this for each new day:


I also coaxed Aaron (under the pretence of trying to learn something) into helping me only show the event dates when running a range rather than showing them for 'todays' events since the date is already there:

By changing one line in the table builder you too can get this wonderful effect (thanks Aaron!):
buildEventTable: function (direction,title,limit) {
        var targetEvents = Calendar.getEvents (direction,limit);
        if (targetEvents[0]) {
            var eventTable = "&{template:default} {{name=" + title + "}}"
            for (var i = 0; i < targetEvents.length; i++){
                var thisEvent = targetEvents[i]
                var thisEventIndex = thisEvent[1]
                var dateArray = Calendar.calcIndexToDate (thisEventIndex)
                var thisEventString = ( limit ? Calendar._GetDateAsString (Calendar.convertDateArrayToObject (dateArray)) : '');
                var newTableEntry = "{{" + thisEvent[0] + "=" + thisEventString + "}}"
                eventTable = eventTable + newTableEntry
            };
            return eventTable;
        } else {
            return "NO EVENTS FOUND FOR SPECIFIED RANGE";
        }
    },
But still retain the dates in the range driven output:

January 10 (9 years ago)

Edited January 10 (9 years ago)
Hello, I've been trying to get the GM events to work, but they always keep posting as public events. I notice someone mentioned it earlier. I'm actually using the Faerun calendar variant posted afterwards. What exactly was the fix needed to get it creating properly?

I think using a separate function for adding GM events would be cleaner than having to parse it, who knows, the game might have a GM Day event within its calendar!

Edit: Well, seems the only part that's not working its the X amount of days in the future version of the GM event. I can do specified date and today.
Big thanks to everyone involved. I'd been using The Aaron's calendar for a while, and adding events definitely moved it from a 9/10 script to an 11/10.
February 20 (8 years ago)
Hello and apologies for not responding to fixes to this script in such a long time, life has been hectic but I finally have some free time.

I've posed an update to the script fixing the issue reported by Gozer and implementing the change recommended by Jose P. The OP has been updated to reflect these changes and code is in the GitHub link.

These changes should work fine if you are running the old script. But if you have written macros to do the commands for you, you will need to update them.

Post back if there are still issues or anything I can do to improve the script for any of you.
February 20 (8 years ago)
The Aaron
Pro
API Scripter
Cool!
February 22 (8 years ago)
I'm trying to install this script, but I seem to be running into an issue. I didn't change anything but the month and day names, and didn't alter any of the special characters that aren't letters outside of those names.

But it either gives me this error

Spinning up new sandbox...
Error downloading scripts (probably no scripts exist for campaign.)

or this error

Spinning up new sandbox...
Unexpected token :
And I'm not sure what to do.
February 22 (8 years ago)
The Aaron
Pro
API Scripter
That first one looks like the API error that was happening over the weekend (I got it myself on sunday), see the Stickie at the top of the API forum.

The second looks more like a code issue.  You might want to disable your current copy and make sure the sandbox starts up, then copy it fresh from the github link and see if the issue goes away.