diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..b512c09d47 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..a79bb4ce61 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +compile: + ./node_modules/browserify/bin/cmd.js index.js > index-browser.js \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000000..4832cac7be --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +Shared resources useful for multiple HabitRPG repositories. Assets (sprites, imgs, etc), CSS, algorithms, and more. + +Because we can't load CommonJS format into the browser, you'll need to run `make` after making your changes. Any RequireJS experts out there wanna help us fix this? \ No newline at end of file diff --git a/algos.js b/algos.js index bd543e0f36..759d79f5af 100644 --- a/algos.js +++ b/algos.js @@ -1,113 +1,109 @@ -(function(exports){ +var HP, XP, priorityValue; - var HP, XP, priorityValue; +XP = 15; - XP = 15; +HP = 2; - HP = 2; +priorityValue = function(priority) { + if (priority == null) { + priority = '!'; + } + switch (priority) { + case '!': + return 1; + case '!!': + return 1.5; + case '!!!': + return 2; + default: + return 1; + } +}; - priorityValue = function(priority) { - if (priority == null) { - priority = '!'; - } - switch (priority) { - case '!': - return 1; - case '!!': - return 1.5; - case '!!!': - return 2; - default: - return 1; - } - }; +exports.tnl = function(level) { + var value; + if (level >= 100) { + value = 0; + } else { + value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10; + } + return value; +}; - exports.tnl = function(level) { - var value; - if (level >= 100) { - value = 0; - } else { - value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10; - } - return value; - }; - - /* - Calculates Exp modificaiton based on level and weapon strength - {value} task.value for exp gain - {weaponStrength) weapon strength - {level} current user level - {priority} user-defined priority multiplier - */ +/* + Calculates Exp modificaiton based on level and weapon strength + {value} task.value for exp gain + {weaponStrength) weapon strength + {level} current user level + {priority} user-defined priority multiplier + */ - exports.expModifier = function(value, weaponStr, level, priority) { - var exp, str, strMod, totalStr; - if (priority == null) { - priority = '!'; - } - str = (level - 1) / 2; - totalStr = (str + weaponStr) / 100; - strMod = 1 + totalStr; - exp = value * XP * strMod * priorityValue(priority); - return Math.round(exp); - }; +exports.expModifier = function(value, weaponStr, level, priority) { + var exp, str, strMod, totalStr; + if (priority == null) { + priority = '!'; + } + str = (level - 1) / 2; + totalStr = (str + weaponStr) / 100; + strMod = 1 + totalStr; + exp = value * XP * strMod * priorityValue(priority); + return Math.round(exp); +}; - /* - Calculates HP modification based on level and armor defence - {value} task.value for hp loss - {armorDefense} defense from armor - {helmDefense} defense from helm - {level} current user level - {priority} user-defined priority multiplier - */ +/* + Calculates HP modification based on level and armor defence + {value} task.value for hp loss + {armorDefense} defense from armor + {helmDefense} defense from helm + {level} current user level + {priority} user-defined priority multiplier + */ - exports.hpModifier = function(value, armorDef, helmDef, shieldDef, level, priority) { - var def, defMod, hp, totalDef; - if (priority == null) { - priority = '!'; - } - def = (level - 1) / 2; - totalDef = (def + armorDef + helmDef + shieldDef) / 100; - defMod = 1 - totalDef; - hp = value * HP * defMod * priorityValue(priority); - return Math.round(hp * 10) / 10; - }; +exports.hpModifier = function(value, armorDef, helmDef, shieldDef, level, priority) { + var def, defMod, hp, totalDef; + if (priority == null) { + priority = '!'; + } + def = (level - 1) / 2; + totalDef = (def + armorDef + helmDef + shieldDef) / 100; + defMod = 1 - totalDef; + hp = value * HP * defMod * priorityValue(priority); + return Math.round(hp * 10) / 10; +}; - /* - Future use - {priority} user-defined priority multiplier - */ +/* + Future use + {priority} user-defined priority multiplier + */ - exports.gpModifier = function(value, modifier, priority) { - if (priority == null) { - priority = '!'; - } - return value * modifier * priorityValue(priority); - }; +exports.gpModifier = function(value, modifier, priority) { + if (priority == null) { + priority = '!'; + } + return value * modifier * priorityValue(priority); +}; - /* - Calculates the next task.value based on direction - Uses a capped inverse log y=.95^x, y>= -5 - {currentValue} the current value of the task - {direction} up or down - */ +/* + Calculates the next task.value based on direction + Uses a capped inverse log y=.95^x, y>= -5 + {currentValue} the current value of the task + {direction} up or down + */ - exports.taskDeltaFormula = function(currentValue, direction) { - var delta; - if (currentValue < -47.27) { - currentValue = -47.27; - } else if (currentValue > 21.27) { - currentValue = 21.27; - } - delta = Math.pow(0.9747, currentValue); - if (direction === 'up') { - return delta; - } - return -delta; - }; - -})(typeof exports === 'undefined'? this['habitrpgAlgos']={}: exports); \ No newline at end of file +exports.taskDeltaFormula = function(currentValue, direction) { + var delta; + if (currentValue < -47.27) { + currentValue = -47.27; + } else if (currentValue > 21.27) { + currentValue = 21.27; + } + delta = Math.pow(0.9747, currentValue); + if (direction === 'up') { + return delta; + } + return -delta; +}; diff --git a/helpers.js b/helpers.js index 75f3b6b3b6..ec1ce7c38c 100644 --- a/helpers.js +++ b/helpers.js @@ -1,24 +1,21 @@ -(function(exports){ - var dayMapping, moment; +var dayMapping, moment; - moment = moment? moment: require('moment'); +moment = require('moment'); - exports.daysBetween = function(yesterday, now, dayStart) { - if (!((dayStart != null) && (dayStart = parseInt(dayStart)) && dayStart >= 0 && dayStart <= 24)) { - dayStart = 0; - } - return Math.abs(moment(yesterday).startOf('day').add('h', dayStart).diff(moment(now), 'days')); - }; +exports.daysBetween = function(yesterday, now, dayStart) { + if (!((dayStart != null) && (dayStart = parseInt(dayStart)) && dayStart >= 0 && dayStart <= 24)) { + dayStart = 0; + } + return Math.abs(moment(yesterday).startOf('day').add('h', dayStart).diff(moment(now), 'days')); +}; - exports.dayMapping = dayMapping = { - 0: 'su', - 1: 'm', - 2: 't', - 3: 'w', - 4: 'th', - 5: 'f', - 6: 's', - 7: 'su' - }; - -})(typeof exports === 'undefined'? this['habitrpgHelpers']={}: exports); \ No newline at end of file +exports.dayMapping = dayMapping = { + 0: 'su', + 1: 'm', + 2: 't', + 3: 'w', + 4: 'th', + 5: 'f', + 6: 's', + 7: 'su' +}; \ No newline at end of file diff --git a/index-browser.js b/index-browser.js new file mode 100644 index 0000000000..7af68ca67a --- /dev/null +++ b/index-browser.js @@ -0,0 +1,1855 @@ +;(function(e,t,n,r){function i(r){if(!n[r]){if(!t[r]){if(e)return e(r);throw new Error("Cannot find module '"+r+"'")}var s=n[r]={exports:{}};t[r][0](function(e){var n=t[r][1][e];return i(n?n:e)},s,s.exports)}return n[r].exports}for(var s=0;s= 100) { + value = 0; + } else { + value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10; + } + return value; +}; + +/* + Calculates Exp modificaiton based on level and weapon strength + {value} task.value for exp gain + {weaponStrength) weapon strength + {level} current user level + {priority} user-defined priority multiplier + */ + + +exports.expModifier = function(value, weaponStr, level, priority) { + var exp, str, strMod, totalStr; + if (priority == null) { + priority = '!'; + } + str = (level - 1) / 2; + totalStr = (str + weaponStr) / 100; + strMod = 1 + totalStr; + exp = value * XP * strMod * priorityValue(priority); + return Math.round(exp); +}; + +/* + Calculates HP modification based on level and armor defence + {value} task.value for hp loss + {armorDefense} defense from armor + {helmDefense} defense from helm + {level} current user level + {priority} user-defined priority multiplier + */ + + +exports.hpModifier = function(value, armorDef, helmDef, shieldDef, level, priority) { + var def, defMod, hp, totalDef; + if (priority == null) { + priority = '!'; + } + def = (level - 1) / 2; + totalDef = (def + armorDef + helmDef + shieldDef) / 100; + defMod = 1 - totalDef; + hp = value * HP * defMod * priorityValue(priority); + return Math.round(hp * 10) / 10; +}; + +/* + Future use + {priority} user-defined priority multiplier + */ + + +exports.gpModifier = function(value, modifier, priority) { + if (priority == null) { + priority = '!'; + } + return value * modifier * priorityValue(priority); +}; + +/* + Calculates the next task.value based on direction + Uses a capped inverse log y=.95^x, y>= -5 + {currentValue} the current value of the task + {direction} up or down + */ + + +exports.taskDeltaFormula = function(currentValue, direction) { + var delta; + if (currentValue < -47.27) { + currentValue = -47.27; + } else if (currentValue > 21.27) { + currentValue = 21.27; + } + delta = Math.pow(0.9747, currentValue); + if (direction === 'up') { + return delta; + } + return -delta; +}; + +},{}],3:[function(require,module,exports){ +exports.items = { + weapon: [ + { + index: 0, + text: "Training Sword", + classes: "weapon_0", + notes: 'Training weapon.', + strength: 0, + value: 0 + }, { + index: 1, + text: "Sword", + classes: 'weapon_1', + notes: 'Increases experience gain by 3%.', + strength: 3, + value: 20 + }, { + index: 2, + text: "Axe", + classes: 'weapon_2', + notes: 'Increases experience gain by 6%.', + strength: 6, + value: 30 + }, { + index: 3, + text: "Morningstar", + classes: 'weapon_3', + notes: 'Increases experience gain by 9%.', + strength: 9, + value: 45 + }, { + index: 4, + text: "Blue Sword", + classes: 'weapon_4', + notes: 'Increases experience gain by 12%.', + strength: 12, + value: 65 + }, { + index: 5, + text: "Red Sword", + classes: 'weapon_5', + notes: 'Increases experience gain by 15%.', + strength: 15, + value: 90 + }, { + index: 6, + text: "Golden Sword", + classes: 'weapon_6', + notes: 'Increases experience gain by 18%.', + strength: 18, + value: 120 + } + ], + armor: [ + { + index: 0, + text: "Cloth Armor", + classes: 'armor_0', + notes: 'Training armor.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Leather Armor", + classes: 'armor_1', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 30 + }, { + index: 2, + text: "Chain Mail", + classes: 'armor_2', + notes: 'Decreases HP loss by 6%.', + defense: 6, + value: 45 + }, { + index: 3, + text: "Plate Mail", + classes: 'armor_3', + notes: 'Decreases HP loss by 7%.', + defense: 7, + value: 65 + }, { + index: 4, + text: "Red Armor", + classes: 'armor_4', + notes: 'Decreases HP loss by 8%.', + defense: 8, + value: 90 + }, { + index: 5, + text: "Golden Armor", + classes: 'armor_5', + notes: 'Decreases HP loss by 10%.', + defense: 10, + value: 120 + } + ], + head: [ + { + index: 0, + text: "No Helm", + classes: 'head_0', + notes: 'Training helm.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Leather Helm", + classes: 'head_1', + notes: 'Decreases HP loss by 2%.', + defense: 2, + value: 15 + }, { + index: 2, + text: "Chain Coif", + classes: 'head_2', + notes: 'Decreases HP loss by 3%.', + defense: 3, + value: 25 + }, { + index: 3, + text: "Plate Helm", + classes: 'head_3', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 45 + }, { + index: 4, + text: "Red Helm", + classes: 'head_4', + notes: 'Decreases HP loss by 5%.', + defense: 5, + value: 60 + }, { + index: 5, + text: "Golden Helm", + classes: 'head_5', + notes: 'Decreases HP loss by 6%.', + defense: 6, + value: 80 + } + ], + shield: [ + { + index: 0, + text: "No Shield", + classes: 'shield_0', + notes: 'No Shield.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Wooden Shield", + classes: 'shield_1', + notes: 'Decreases HP loss by 3%', + defense: 3, + value: 20 + }, { + index: 2, + text: "Buckler", + classes: 'shield_2', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 35 + }, { + index: 3, + text: "Enforced Shield", + classes: 'shield_3', + notes: 'Decreases HP loss by 5%.', + defense: 5, + value: 55 + }, { + index: 4, + text: "Red Shield", + classes: 'shield_4', + notes: 'Decreases HP loss by 7%.', + defense: 7, + value: 70 + }, { + index: 5, + text: "Golden Shield", + classes: 'shield_5', + notes: 'Decreases HP loss by 8%.', + defense: 8, + value: 90 + } + ], + potion: { + type: 'potion', + text: "Potion", + notes: "Recover 15 HP", + value: 25, + classes: 'potion' + }, + reroll: { + type: 'reroll', + text: "Re-Roll", + classes: 'reroll', + notes: "Resets your tasks. When you're struggling and everything's red, use for a clean slate.", + value: 0 + }, + pets: [ + { + index: 0, + text: 'Bear Cub', + name: 'bearcub', + icon: 'Pet-BearCub-Base.png', + value: 3 + }, { + index: 1, + text: 'Cactus', + name: 'cactus', + icon: 'Pet-Cactus-Base.png', + value: 3 + }, { + index: 2, + text: 'Drake', + name: 'dragon', + icon: 'Pet-Dragon-Base.png', + value: 3 + }, { + index: 3, + text: 'Flying Pig', + name: 'flyingpig', + icon: 'Pet-FlyingPig-Base.png', + value: 3 + }, { + index: 4, + text: 'Fox', + name: 'fox', + icon: 'Pet-Fox-Base.png', + value: 3 + }, { + index: 5, + text: 'Lion Cub', + name: 'lioncub', + icon: 'Pet-LionCub-Base.png', + value: 3 + }, { + index: 6, + text: 'Panda Cub', + name: 'pandacub', + icon: 'Pet-PandaCub-Base.png', + value: 3 + }, { + index: 7, + text: 'Tiger Cub', + name: 'tigercub', + icon: 'Pet-TigerCub-Base.png', + value: 3 + }, { + index: 8, + text: 'Desert Wolf', + name: 'wolfDesert', + icon: 'Pet-Wolf-Desert.png', + value: 3 + }, { + index: 9, + text: 'Golden Wolf', + name: 'wolfGolden', + icon: 'Pet-Wolf-Golden.png', + value: 3 + }, { + index: 10, + text: 'Red Wolf', + name: 'wolfRed', + icon: 'Pet-Wolf-Red.png', + value: 3 + }, { + index: 11, + text: 'Shade Wolf', + name: 'wolfShade', + icon: 'Pet-Wolf-Shade.png', + value: 3 + }, { + index: 12, + text: 'Skeleton Wolf', + name: 'wolfSkeleton', + icon: 'Pet-Wolf-Skeleton.png', + value: 3 + }, { + index: 13, + text: 'Veteran Wolf', + name: 'wolfVeteran', + icon: 'Pet-Wolf-Veteran.png', + value: 3 + }, { + index: 14, + text: 'White Wolf', + name: 'wolfWhite', + icon: 'Pet-Wolf-White.png', + value: 3 + }, { + index: 15, + text: 'Zombie Wolf', + name: 'wolfZombie', + icon: 'Pet-Wolf-Zombie.png', + value: 3 + }, { + index: 16, + text: 'Wolf', + name: 'wolfBorder', + icon: 'wolf_border.png', + value: 3 + } + ] +}; +},{}],4:[function(require,module,exports){ +var dayMapping, moment; + +moment = require('moment'); + +exports.daysBetween = function(yesterday, now, dayStart) { + if (!((dayStart != null) && (dayStart = parseInt(dayStart)) && dayStart >= 0 && dayStart <= 24)) { + dayStart = 0; + } + return Math.abs(moment(yesterday).startOf('day').add('h', dayStart).diff(moment(now), 'days')); +}; + +exports.dayMapping = dayMapping = { + 0: 'su', + 1: 'm', + 2: 't', + 3: 'w', + 4: 'th', + 5: 'f', + 6: 's', + 7: 'su' +}; +},{"moment":5}],5:[function(require,module,exports){ +(function(){// moment.js +// version : 2.0.0 +// author : Tim Wood +// license : MIT +// momentjs.com + +(function (undefined) { + + /************************************ + Constants + ************************************/ + + var moment, + VERSION = "2.0.0", + round = Math.round, i, + // internal storage for language config files + languages = {}, + + // check for nodeJS + hasModule = (typeof module !== 'undefined' && module.exports), + + // ASP.NET json date format regex + aspNetJsonRegex = /^\/?Date\((\-?\d+)/i, + + // format tokens + formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g, + localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g, + + // parsing tokens + parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi, + + // parsing token regexes + parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99 + parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999 + parseTokenThreeDigits = /\d{3}/, // 000 - 999 + parseTokenFourDigits = /\d{1,4}/, // 0 - 9999 + parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999 + parseTokenWord = /[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i, // any word (or two) characters or numbers including two word month in arabic. + parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z + parseTokenT = /T/i, // T (ISO seperator) + parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123 + + // preliminary iso regex + // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 + isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/, + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + + // iso time formats and regexes + isoTimes = [ + ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/], + ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/], + ['HH:mm', /(T| )\d\d:\d\d/], + ['HH', /(T| )\d\d/] + ], + + // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"] + parseTimezoneChunker = /([\+\-]|\d\d)/gi, + + // getter and setter names + proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'), + unitMillisecondFactors = { + 'Milliseconds' : 1, + 'Seconds' : 1e3, + 'Minutes' : 6e4, + 'Hours' : 36e5, + 'Days' : 864e5, + 'Months' : 2592e6, + 'Years' : 31536e6 + }, + + // format function strings + formatFunctions = {}, + + // tokens to ordinalize and pad + ordinalizeTokens = 'DDD w W M D d'.split(' '), + paddedTokens = 'M D H h m s w W'.split(' '), + + formatTokenFunctions = { + M : function () { + return this.month() + 1; + }, + MMM : function (format) { + return this.lang().monthsShort(this, format); + }, + MMMM : function (format) { + return this.lang().months(this, format); + }, + D : function () { + return this.date(); + }, + DDD : function () { + return this.dayOfYear(); + }, + d : function () { + return this.day(); + }, + dd : function (format) { + return this.lang().weekdaysMin(this, format); + }, + ddd : function (format) { + return this.lang().weekdaysShort(this, format); + }, + dddd : function (format) { + return this.lang().weekdays(this, format); + }, + w : function () { + return this.week(); + }, + W : function () { + return this.isoWeek(); + }, + YY : function () { + return leftZeroFill(this.year() % 100, 2); + }, + YYYY : function () { + return leftZeroFill(this.year(), 4); + }, + YYYYY : function () { + return leftZeroFill(this.year(), 5); + }, + a : function () { + return this.lang().meridiem(this.hours(), this.minutes(), true); + }, + A : function () { + return this.lang().meridiem(this.hours(), this.minutes(), false); + }, + H : function () { + return this.hours(); + }, + h : function () { + return this.hours() % 12 || 12; + }, + m : function () { + return this.minutes(); + }, + s : function () { + return this.seconds(); + }, + S : function () { + return ~~(this.milliseconds() / 100); + }, + SS : function () { + return leftZeroFill(~~(this.milliseconds() / 10), 2); + }, + SSS : function () { + return leftZeroFill(this.milliseconds(), 3); + }, + Z : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2); + }, + ZZ : function () { + var a = -this.zone(), + b = "+"; + if (a < 0) { + a = -a; + b = "-"; + } + return b + leftZeroFill(~~(10 * a / 6), 4); + }, + X : function () { + return this.unix(); + } + }; + + function padToken(func, count) { + return function (a) { + return leftZeroFill(func.call(this, a), count); + }; + } + function ordinalizeToken(func) { + return function (a) { + return this.lang().ordinal(func.call(this, a)); + }; + } + + while (ordinalizeTokens.length) { + i = ordinalizeTokens.pop(); + formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i]); + } + while (paddedTokens.length) { + i = paddedTokens.pop(); + formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2); + } + formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3); + + + /************************************ + Constructors + ************************************/ + + function Language() { + + } + + // Moment prototype object + function Moment(config) { + extend(this, config); + } + + // Duration Constructor + function Duration(duration) { + var data = this._data = {}, + years = duration.years || duration.year || duration.y || 0, + months = duration.months || duration.month || duration.M || 0, + weeks = duration.weeks || duration.week || duration.w || 0, + days = duration.days || duration.day || duration.d || 0, + hours = duration.hours || duration.hour || duration.h || 0, + minutes = duration.minutes || duration.minute || duration.m || 0, + seconds = duration.seconds || duration.second || duration.s || 0, + milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0; + + // representation for dateAddRemove + this._milliseconds = milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 36e5; // 1000 * 60 * 60 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = days + + weeks * 7; + // It is impossible translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = months + + years * 12; + + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + seconds += absRound(milliseconds / 1000); + + data.seconds = seconds % 60; + minutes += absRound(seconds / 60); + + data.minutes = minutes % 60; + hours += absRound(minutes / 60); + + data.hours = hours % 24; + days += absRound(hours / 24); + + days += weeks * 7; + data.days = days % 30; + + months += absRound(days / 30); + + data.months = months % 12; + years += absRound(months / 12); + + data.years = years; + } + + + /************************************ + Helpers + ************************************/ + + + function extend(a, b) { + for (var i in b) { + if (b.hasOwnProperty(i)) { + a[i] = b[i]; + } + } + return a; + } + + function absRound(number) { + if (number < 0) { + return Math.ceil(number); + } else { + return Math.floor(number); + } + } + + // left zero fill a number + // see http://jsperf.com/left-zero-filling for performance comparison + function leftZeroFill(number, targetLength) { + var output = number + ''; + while (output.length < targetLength) { + output = '0' + output; + } + return output; + } + + // helper function for _.addTime and _.subtractTime + function addOrSubtractDurationFromMoment(mom, duration, isAdding) { + var ms = duration._milliseconds, + d = duration._days, + M = duration._months, + currentDate; + + if (ms) { + mom._d.setTime(+mom + ms * isAdding); + } + if (d) { + mom.date(mom.date() + d * isAdding); + } + if (M) { + currentDate = mom.date(); + mom.date(1) + .month(mom.month() + M * isAdding) + .date(Math.min(currentDate, mom.daysInMonth())); + } + } + + // check if is an array + function isArray(input) { + return Object.prototype.toString.call(input) === '[object Array]'; + } + + // compare two arrays, return the number of differences + function compareArrays(array1, array2) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if (~~array1[i] !== ~~array2[i]) { + diffs++; + } + } + return diffs + lengthDiff; + } + + + /************************************ + Languages + ************************************/ + + + Language.prototype = { + set : function (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (typeof prop === 'function') { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + }, + + _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), + months : function (m) { + return this._months[m.month()]; + }, + + _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"), + monthsShort : function (m) { + return this._monthsShort[m.month()]; + }, + + monthsParse : function (monthName) { + var i, mom, regex, output; + + if (!this._monthsParse) { + this._monthsParse = []; + } + + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + if (!this._monthsParse[i]) { + mom = moment([2000, i]); + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (this._monthsParse[i].test(monthName)) { + return i; + } + } + }, + + _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), + weekdays : function (m) { + return this._weekdays[m.day()]; + }, + + _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"), + weekdaysShort : function (m) { + return this._weekdaysShort[m.day()]; + }, + + _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"), + weekdaysMin : function (m) { + return this._weekdaysMin[m.day()]; + }, + + _longDateFormat : { + LT : "h:mm A", + L : "MM/DD/YYYY", + LL : "MMMM D YYYY", + LLL : "MMMM D YYYY LT", + LLLL : "dddd, MMMM D YYYY LT" + }, + longDateFormat : function (key) { + var output = this._longDateFormat[key]; + if (!output && this._longDateFormat[key.toUpperCase()]) { + output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); + this._longDateFormat[key] = output; + } + return output; + }, + + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } + }, + + _calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[last] dddd [at] LT', + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendar[key]; + return typeof output === 'function' ? output.apply(mom) : output; + }, + + _relativeTime : { + future : "in %s", + past : "%s ago", + s : "a few seconds", + m : "a minute", + mm : "%d minutes", + h : "an hour", + hh : "%d hours", + d : "a day", + dd : "%d days", + M : "a month", + MM : "%d months", + y : "a year", + yy : "%d years" + }, + relativeTime : function (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (typeof output === 'function') ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + }, + pastFuture : function (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return typeof format === 'function' ? format(output) : format.replace(/%s/i, output); + }, + + ordinal : function (number) { + return this._ordinal.replace("%d", number); + }, + _ordinal : "%d", + + preparse : function (string) { + return string; + }, + + postformat : function (string) { + return string; + }, + + week : function (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy); + }, + _week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 1st is the first week of the year. + } + }; + + // Loads a language definition into the `languages` cache. The function + // takes a key and optionally values. If not in the browser and no values + // are provided, it will load the language file module. As a convenience, + // this function also returns the language values. + function loadLang(key, values) { + values.abbr = key; + if (!languages[key]) { + languages[key] = new Language(); + } + languages[key].set(values); + return languages[key]; + } + + // Determines which language definition to use and returns it. + // + // With no parameters, it will return the global language. If you + // pass in a language key, such as 'en', it will return the + // definition for 'en', so long as 'en' has already been loaded using + // moment.lang. + function getLangDefinition(key) { + if (!key) { + return moment.fn._lang; + } + if (!languages[key] && hasModule) { + require('./lang/' + key); + } + return languages[key]; + } + + + /************************************ + Formatting + ************************************/ + + + function removeFormattingTokens(input) { + if (input.match(/\[.*\]/)) { + return input.replace(/^\[|\]$/g, ""); + } + return input.replace(/\\/g, ""); + } + + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; + + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } + + return function (mom) { + var output = ""; + for (i = 0; i < length; i++) { + output += typeof array[i].call === 'function' ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } + + // format date using native date object + function formatMoment(m, format) { + var i = 5; + + function replaceLongDateFormatTokens(input) { + return m.lang().longDateFormat(input) || input; + } + + while (i-- && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + } + + if (!formatFunctions[format]) { + formatFunctions[format] = makeFormatFunction(format); + } + + return formatFunctions[format](m); + } + + + /************************************ + Parsing + ************************************/ + + + // get the regex to find the next token + function getParseRegexForToken(token) { + switch (token) { + case 'DDDD': + return parseTokenThreeDigits; + case 'YYYY': + return parseTokenFourDigits; + case 'YYYYY': + return parseTokenSixDigits; + case 'S': + case 'SS': + case 'SSS': + case 'DDD': + return parseTokenOneToThreeDigits; + case 'MMM': + case 'MMMM': + case 'dd': + case 'ddd': + case 'dddd': + case 'a': + case 'A': + return parseTokenWord; + case 'X': + return parseTokenTimestampMs; + case 'Z': + case 'ZZ': + return parseTokenTimezone; + case 'T': + return parseTokenT; + case 'MM': + case 'DD': + case 'YY': + case 'HH': + case 'hh': + case 'mm': + case 'ss': + case 'M': + case 'D': + case 'd': + case 'H': + case 'h': + case 'm': + case 's': + return parseTokenOneOrTwoDigits; + default : + return new RegExp(token.replace('\\', '')); + } + } + + // function to convert string input to date + function addTimeToArrayFromToken(token, input, config) { + var a, b, + datePartArray = config._a; + + switch (token) { + // MONTH + case 'M' : // fall through to MM + case 'MM' : + datePartArray[1] = (input == null) ? 0 : ~~input - 1; + break; + case 'MMM' : // fall through to MMMM + case 'MMMM' : + a = getLangDefinition(config._l).monthsParse(input); + // if we didn't find a month name, mark the date as invalid. + if (a != null) { + datePartArray[1] = a; + } else { + config._isValid = false; + } + break; + // DAY OF MONTH + case 'D' : // fall through to DDDD + case 'DD' : // fall through to DDDD + case 'DDD' : // fall through to DDDD + case 'DDDD' : + if (input != null) { + datePartArray[2] = ~~input; + } + break; + // YEAR + case 'YY' : + datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000); + break; + case 'YYYY' : + case 'YYYYY' : + datePartArray[0] = ~~input; + break; + // AM / PM + case 'a' : // fall through to A + case 'A' : + config._isPm = ((input + '').toLowerCase() === 'pm'); + break; + // 24 HOUR + case 'H' : // fall through to hh + case 'HH' : // fall through to hh + case 'h' : // fall through to hh + case 'hh' : + datePartArray[3] = ~~input; + break; + // MINUTE + case 'm' : // fall through to mm + case 'mm' : + datePartArray[4] = ~~input; + break; + // SECOND + case 's' : // fall through to ss + case 'ss' : + datePartArray[5] = ~~input; + break; + // MILLISECOND + case 'S' : + case 'SS' : + case 'SSS' : + datePartArray[6] = ~~ (('0.' + input) * 1000); + break; + // UNIX TIMESTAMP WITH MS + case 'X': + config._d = new Date(parseFloat(input) * 1000); + break; + // TIMEZONE + case 'Z' : // fall through to ZZ + case 'ZZ' : + config._useUTC = true; + a = (input + '').match(parseTimezoneChunker); + if (a && a[1]) { + config._tzh = ~~a[1]; + } + if (a && a[2]) { + config._tzm = ~~a[2]; + } + // reverse offsets + if (a && a[0] === '+') { + config._tzh = -config._tzh; + config._tzm = -config._tzm; + } + break; + } + + // if the input is null, the date is not valid + if (input == null) { + config._isValid = false; + } + } + + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function dateFromArray(config) { + var i, date, input = []; + + if (config._d) { + return; + } + + for (i = 0; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } + + // add the offsets to the time to be parsed so that we can have a clean array for checking isValid + input[3] += config._tzh || 0; + input[4] += config._tzm || 0; + + date = new Date(0); + + if (config._useUTC) { + date.setUTCFullYear(input[0], input[1], input[2]); + date.setUTCHours(input[3], input[4], input[5], input[6]); + } else { + date.setFullYear(input[0], input[1], input[2]); + date.setHours(input[3], input[4], input[5], input[6]); + } + + config._d = date; + } + + // date from string and format string + function makeDateFromStringAndFormat(config) { + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var tokens = config._f.match(formattingTokens), + string = config._i, + i, parsedInput; + + config._a = []; + + for (i = 0; i < tokens.length; i++) { + parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0]; + if (parsedInput) { + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + } + // don't parse if its not a known token + if (formatTokenFunctions[tokens[i]]) { + addTimeToArrayFromToken(tokens[i], parsedInput, config); + } + } + // handle am pm + if (config._isPm && config._a[3] < 12) { + config._a[3] += 12; + } + // if is 12 am, change hours to 0 + if (config._isPm === false && config._a[3] === 12) { + config._a[3] = 0; + } + // return + dateFromArray(config); + } + + // date from string and array of format strings + function makeDateFromStringAndArray(config) { + var tempConfig, + tempMoment, + bestMoment, + + scoreToBeat = 99, + i, + currentDate, + currentScore; + + while (config._f.length) { + tempConfig = extend({}, config); + tempConfig._f = config._f.pop(); + makeDateFromStringAndFormat(tempConfig); + tempMoment = new Moment(tempConfig); + + if (tempMoment.isValid()) { + bestMoment = tempMoment; + break; + } + + currentScore = compareArrays(tempConfig._a, tempMoment.toArray()); + + if (currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempMoment; + } + } + + extend(config, bestMoment); + } + + // date from iso format + function makeDateFromString(config) { + var i, + string = config._i; + if (isoRegex.exec(string)) { + config._f = 'YYYY-MM-DDT'; + for (i = 0; i < 4; i++) { + if (isoTimes[i][1].exec(string)) { + config._f += isoTimes[i][0]; + break; + } + } + if (parseTokenTimezone.exec(string)) { + config._f += " Z"; + } + makeDateFromStringAndFormat(config); + } else { + config._d = new Date(string); + } + } + + function makeDateFromInput(config) { + var input = config._i, + matched = aspNetJsonRegex.exec(input); + + if (input === undefined) { + config._d = new Date(); + } else if (matched) { + config._d = new Date(+matched[1]); + } else if (typeof input === 'string') { + makeDateFromString(config); + } else if (isArray(input)) { + config._a = input.slice(0); + dateFromArray(config); + } else { + config._d = input instanceof Date ? new Date(+input) : new Date(input); + } + } + + + /************************************ + Relative Time + ************************************/ + + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) { + return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime(milliseconds, withoutSuffix, lang) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + args[3] = milliseconds > 0; + args[4] = lang; + return substituteTimeAgo.apply({}, args); + } + + + /************************************ + Week of Year + ************************************/ + + + // firstDayOfWeek 0 = sun, 6 = sat + // the day of the week that starts the week + // (usually sunday or monday) + // firstDayOfWeekOfYear 0 = sun, 6 = sat + // the first week is the week that contains the first + // of this day of the week + // (eg. ISO weeks use thursday (4)) + function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) { + var end = firstDayOfWeekOfYear - firstDayOfWeek, + daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(); + + + if (daysToDayOfWeek > end) { + daysToDayOfWeek -= 7; + } + + if (daysToDayOfWeek < end - 7) { + daysToDayOfWeek += 7; + } + + return Math.ceil(moment(mom).add('d', daysToDayOfWeek).dayOfYear() / 7); + } + + + /************************************ + Top Level Functions + ************************************/ + + function makeMoment(config) { + var input = config._i, + format = config._f; + + if (input === null || input === '') { + return null; + } + + if (typeof input === 'string') { + config._i = input = getLangDefinition().preparse(input); + } + + if (moment.isMoment(input)) { + config = extend({}, input); + config._d = new Date(+input._d); + } else if (format) { + if (isArray(format)) { + makeDateFromStringAndArray(config); + } else { + makeDateFromStringAndFormat(config); + } + } else { + makeDateFromInput(config); + } + + return new Moment(config); + } + + moment = function (input, format, lang) { + return makeMoment({ + _i : input, + _f : format, + _l : lang, + _isUTC : false + }); + }; + + // creating with utc + moment.utc = function (input, format, lang) { + return makeMoment({ + _useUTC : true, + _isUTC : true, + _l : lang, + _i : input, + _f : format + }); + }; + + // creating with unix timestamp (in seconds) + moment.unix = function (input) { + return moment(input * 1000); + }; + + // duration + moment.duration = function (input, key) { + var isDuration = moment.isDuration(input), + isNumber = (typeof input === 'number'), + duration = (isDuration ? input._data : (isNumber ? {} : input)), + ret; + + if (isNumber) { + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } + + ret = new Duration(duration); + + if (isDuration && input.hasOwnProperty('_lang')) { + ret._lang = input._lang; + } + + return ret; + }; + + // version number + moment.version = VERSION; + + // default format + moment.defaultFormat = isoFormat; + + // This function will load languages and then set the global language. If + // no arguments are passed in, it will simply return the current global + // language key. + moment.lang = function (key, values) { + var i; + + if (!key) { + return moment.fn._lang._abbr; + } + if (values) { + loadLang(key, values); + } else if (!languages[key]) { + getLangDefinition(key); + } + moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key); + }; + + // returns language data + moment.langData = function (key) { + if (key && key._lang && key._lang._abbr) { + key = key._lang._abbr; + } + return getLangDefinition(key); + }; + + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; + + // for typechecking Duration objects + moment.isDuration = function (obj) { + return obj instanceof Duration; + }; + + + /************************************ + Moment Prototype + ************************************/ + + + moment.fn = Moment.prototype = { + + clone : function () { + return moment(this); + }, + + valueOf : function () { + return +this._d; + }, + + unix : function () { + return Math.floor(+this._d / 1000); + }, + + toString : function () { + return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ"); + }, + + toDate : function () { + return this._d; + }, + + toJSON : function () { + return moment.utc(this).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]'); + }, + + toArray : function () { + var m = this; + return [ + m.year(), + m.month(), + m.date(), + m.hours(), + m.minutes(), + m.seconds(), + m.milliseconds() + ]; + }, + + isValid : function () { + if (this._isValid == null) { + if (this._a) { + this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()); + } else { + this._isValid = !isNaN(this._d.getTime()); + } + } + return !!this._isValid; + }, + + utc : function () { + this._isUTC = true; + return this; + }, + + local : function () { + this._isUTC = false; + return this; + }, + + format : function (inputString) { + var output = formatMoment(this, inputString || moment.defaultFormat); + return this.lang().postformat(output); + }, + + add : function (input, val) { + var dur; + // switch args to support add('s', 1) and add(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, 1); + return this; + }, + + subtract : function (input, val) { + var dur; + // switch args to support subtract('s', 1) and subtract(1, 's') + if (typeof input === 'string') { + dur = moment.duration(+val, input); + } else { + dur = moment.duration(input, val); + } + addOrSubtractDurationFromMoment(this, dur, -1); + return this; + }, + + diff : function (input, units, asFloat) { + var that = this._isUTC ? moment(input).utc() : moment(input).local(), + zoneDiff = (this.zone() - that.zone()) * 6e4, + diff, output; + + if (units) { + // standardize on singular form + units = units.replace(/s$/, ''); + } + + if (units === 'year' || units === 'month') { + diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2 + output = ((this.year() - that.year()) * 12) + (this.month() - that.month()); + output += ((this - moment(this).startOf('month')) - (that - moment(that).startOf('month'))) / diff; + if (units === 'year') { + output = output / 12; + } + } else { + diff = (this - that) - zoneDiff; + output = units === 'second' ? diff / 1e3 : // 1000 + units === 'minute' ? diff / 6e4 : // 1000 * 60 + units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60 + units === 'day' ? diff / 864e5 : // 1000 * 60 * 60 * 24 + units === 'week' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7 + diff; + } + return asFloat ? output : absRound(output); + }, + + from : function (time, withoutSuffix) { + return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix); + }, + + fromNow : function (withoutSuffix) { + return this.from(moment(), withoutSuffix); + }, + + calendar : function () { + var diff = this.diff(moment().startOf('day'), 'days', true), + format = diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + return this.format(this.lang().calendar(format, this)); + }, + + isLeapYear : function () { + var year = this.year(); + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + }, + + isDST : function () { + return (this.zone() < moment([this.year()]).zone() || + this.zone() < moment([this.year(), 5]).zone()); + }, + + day : function (input) { + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + return input == null ? day : + this.add({ d : input - day }); + }, + + startOf: function (units) { + units = units.replace(/s$/, ''); + // the following switch intentionally omits break keywords + // to utilize falling through the cases. + switch (units) { + case 'year': + this.month(0); + /* falls through */ + case 'month': + this.date(1); + /* falls through */ + case 'week': + case 'day': + this.hours(0); + /* falls through */ + case 'hour': + this.minutes(0); + /* falls through */ + case 'minute': + this.seconds(0); + /* falls through */ + case 'second': + this.milliseconds(0); + /* falls through */ + } + + // weeks are a special case + if (units === 'week') { + this.day(0); + } + + return this; + }, + + endOf: function (units) { + return this.startOf(units).add(units.replace(/s?$/, 's'), 1).subtract('ms', 1); + }, + + isAfter: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) > +moment(input).startOf(units); + }, + + isBefore: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) < +moment(input).startOf(units); + }, + + isSame: function (input, units) { + units = typeof units !== 'undefined' ? units : 'millisecond'; + return +this.clone().startOf(units) === +moment(input).startOf(units); + }, + + zone : function () { + return this._isUTC ? 0 : this._d.getTimezoneOffset(); + }, + + daysInMonth : function () { + return moment.utc([this.year(), this.month() + 1, 0]).date(); + }, + + dayOfYear : function (input) { + var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add("d", (input - dayOfYear)); + }, + + isoWeek : function (input) { + var week = weekOfYear(this, 1, 4); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + week : function (input) { + var week = this.lang().week(this); + return input == null ? week : this.add("d", (input - week) * 7); + }, + + // If passed a language key, it will set the language for this + // instance. Otherwise, it will return the language configuration + // variables for this instance. + lang : function (key) { + if (key === undefined) { + return this._lang; + } else { + this._lang = getLangDefinition(key); + return this; + } + } + }; + + // helper for adding shortcuts + function makeGetterAndSetter(name, key) { + moment.fn[name] = moment.fn[name + 's'] = function (input) { + var utc = this._isUTC ? 'UTC' : ''; + if (input != null) { + this._d['set' + utc + key](input); + return this; + } else { + return this._d['get' + utc + key](); + } + }; + } + + // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) + for (i = 0; i < proxyGettersAndSetters.length; i ++) { + makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]); + } + + // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') + makeGetterAndSetter('year', 'FullYear'); + + // add plural methods + moment.fn.days = moment.fn.day; + moment.fn.weeks = moment.fn.week; + moment.fn.isoWeeks = moment.fn.isoWeek; + + /************************************ + Duration Prototype + ************************************/ + + + moment.duration.fn = Duration.prototype = { + weeks : function () { + return absRound(this.days() / 7); + }, + + valueOf : function () { + return this._milliseconds + + this._days * 864e5 + + this._months * 2592e6; + }, + + humanize : function (withSuffix) { + var difference = +this, + output = relativeTime(difference, !withSuffix, this.lang()); + + if (withSuffix) { + output = this.lang().pastFuture(difference, output); + } + + return this.lang().postformat(output); + }, + + lang : moment.fn.lang + }; + + function makeDurationGetter(name) { + moment.duration.fn[name] = function () { + return this._data[name]; + }; + } + + function makeDurationAsGetter(name, factor) { + moment.duration.fn['as' + name] = function () { + return +this / factor; + }; + } + + for (i in unitMillisecondFactors) { + if (unitMillisecondFactors.hasOwnProperty(i)) { + makeDurationAsGetter(i, unitMillisecondFactors[i]); + makeDurationGetter(i.toLowerCase()); + } + } + + makeDurationAsGetter('Weeks', 6048e5); + + + /************************************ + Default Lang + ************************************/ + + + // Set default language, other languages will inherit from English. + moment.lang('en', { + ordinal : function (number) { + var b = number % 10, + output = (~~ (number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); + + + /************************************ + Exposing Moment + ************************************/ + + + // CommonJS module is defined + if (hasModule) { + module.exports = moment; + } + /*global ender:false */ + if (typeof ender === 'undefined') { + // here, `this` means `window` in the browser, or `global` on the server + // add `moment` as a global object via a string identifier, + // for Closure Compiler "advanced" mode + this['moment'] = moment; + } + /*global define:false */ + if (typeof define === "function" && define.amd) { + define("moment", [], function () { + return moment; + }); + } +}).call(this); + +})() +},{}]},{},[1]) +; \ No newline at end of file diff --git a/index.js b/index.js new file mode 100644 index 0000000000..643c320027 --- /dev/null +++ b/index.js @@ -0,0 +1,7 @@ +exports.algos = require('./algos') +exports.items = require('./items') +exports.helpers = require('./helpers') + +// This is how we're exporting this module to the browser. A preferable way would be http://requirejs.org/docs/api.html#packages +// but I couldn't get that working +if (!!window) window.habitrpgShared = exports; \ No newline at end of file diff --git a/items.js b/items.js index dd8907775e..95ae4009b7 100644 --- a/items.js +++ b/items.js @@ -1,312 +1,308 @@ -(function(exports){ - - exports.items = { - weapon: [ - { - index: 0, - text: "Training Sword", - classes: "weapon_0", - notes: 'Training weapon.', - strength: 0, - value: 0 - }, { - index: 1, - text: "Sword", - classes: 'weapon_1', - notes: 'Increases experience gain by 3%.', - strength: 3, - value: 20 - }, { - index: 2, - text: "Axe", - classes: 'weapon_2', - notes: 'Increases experience gain by 6%.', - strength: 6, - value: 30 - }, { - index: 3, - text: "Morningstar", - classes: 'weapon_3', - notes: 'Increases experience gain by 9%.', - strength: 9, - value: 45 - }, { - index: 4, - text: "Blue Sword", - classes: 'weapon_4', - notes: 'Increases experience gain by 12%.', - strength: 12, - value: 65 - }, { - index: 5, - text: "Red Sword", - classes: 'weapon_5', - notes: 'Increases experience gain by 15%.', - strength: 15, - value: 90 - }, { - index: 6, - text: "Golden Sword", - classes: 'weapon_6', - notes: 'Increases experience gain by 18%.', - strength: 18, - value: 120 - } - ], - armor: [ - { - index: 0, - text: "Cloth Armor", - classes: 'armor_0', - notes: 'Training armor.', - defense: 0, - value: 0 - }, { - index: 1, - text: "Leather Armor", - classes: 'armor_1', - notes: 'Decreases HP loss by 4%.', - defense: 4, - value: 30 - }, { - index: 2, - text: "Chain Mail", - classes: 'armor_2', - notes: 'Decreases HP loss by 6%.', - defense: 6, - value: 45 - }, { - index: 3, - text: "Plate Mail", - classes: 'armor_3', - notes: 'Decreases HP loss by 7%.', - defense: 7, - value: 65 - }, { - index: 4, - text: "Red Armor", - classes: 'armor_4', - notes: 'Decreases HP loss by 8%.', - defense: 8, - value: 90 - }, { - index: 5, - text: "Golden Armor", - classes: 'armor_5', - notes: 'Decreases HP loss by 10%.', - defense: 10, - value: 120 - } - ], - head: [ - { - index: 0, - text: "No Helm", - classes: 'head_0', - notes: 'Training helm.', - defense: 0, - value: 0 - }, { - index: 1, - text: "Leather Helm", - classes: 'head_1', - notes: 'Decreases HP loss by 2%.', - defense: 2, - value: 15 - }, { - index: 2, - text: "Chain Coif", - classes: 'head_2', - notes: 'Decreases HP loss by 3%.', - defense: 3, - value: 25 - }, { - index: 3, - text: "Plate Helm", - classes: 'head_3', - notes: 'Decreases HP loss by 4%.', - defense: 4, - value: 45 - }, { - index: 4, - text: "Red Helm", - classes: 'head_4', - notes: 'Decreases HP loss by 5%.', - defense: 5, - value: 60 - }, { - index: 5, - text: "Golden Helm", - classes: 'head_5', - notes: 'Decreases HP loss by 6%.', - defense: 6, - value: 80 - } - ], - shield: [ - { - index: 0, - text: "No Shield", - classes: 'shield_0', - notes: 'No Shield.', - defense: 0, - value: 0 - }, { - index: 1, - text: "Wooden Shield", - classes: 'shield_1', - notes: 'Decreases HP loss by 3%', - defense: 3, - value: 20 - }, { - index: 2, - text: "Buckler", - classes: 'shield_2', - notes: 'Decreases HP loss by 4%.', - defense: 4, - value: 35 - }, { - index: 3, - text: "Enforced Shield", - classes: 'shield_3', - notes: 'Decreases HP loss by 5%.', - defense: 5, - value: 55 - }, { - index: 4, - text: "Red Shield", - classes: 'shield_4', - notes: 'Decreases HP loss by 7%.', - defense: 7, - value: 70 - }, { - index: 5, - text: "Golden Shield", - classes: 'shield_5', - notes: 'Decreases HP loss by 8%.', - defense: 8, - value: 90 - } - ], - potion: { - type: 'potion', - text: "Potion", - notes: "Recover 15 HP", - value: 25, - classes: 'potion' - }, - reroll: { - type: 'reroll', - text: "Re-Roll", - classes: 'reroll', - notes: "Resets your tasks. When you're struggling and everything's red, use for a clean slate.", +exports.items = { + weapon: [ + { + index: 0, + text: "Training Sword", + classes: "weapon_0", + notes: 'Training weapon.', + strength: 0, value: 0 - }, - pets: [ - { - index: 0, - text: 'Bear Cub', - name: 'bearcub', - icon: 'Pet-BearCub-Base.png', - value: 3 - }, { - index: 1, - text: 'Cactus', - name: 'cactus', - icon: 'Pet-Cactus-Base.png', - value: 3 - }, { - index: 2, - text: 'Drake', - name: 'dragon', - icon: 'Pet-Dragon-Base.png', - value: 3 - }, { - index: 3, - text: 'Flying Pig', - name: 'flyingpig', - icon: 'Pet-FlyingPig-Base.png', - value: 3 - }, { - index: 4, - text: 'Fox', - name: 'fox', - icon: 'Pet-Fox-Base.png', - value: 3 - }, { - index: 5, - text: 'Lion Cub', - name: 'lioncub', - icon: 'Pet-LionCub-Base.png', - value: 3 - }, { - index: 6, - text: 'Panda Cub', - name: 'pandacub', - icon: 'Pet-PandaCub-Base.png', - value: 3 - }, { - index: 7, - text: 'Tiger Cub', - name: 'tigercub', - icon: 'Pet-TigerCub-Base.png', - value: 3 - }, { - index: 8, - text: 'Desert Wolf', - name: 'wolfDesert', - icon: 'Pet-Wolf-Desert.png', - value: 3 - }, { - index: 9, - text: 'Golden Wolf', - name: 'wolfGolden', - icon: 'Pet-Wolf-Golden.png', - value: 3 - }, { - index: 10, - text: 'Red Wolf', - name: 'wolfRed', - icon: 'Pet-Wolf-Red.png', - value: 3 - }, { - index: 11, - text: 'Shade Wolf', - name: 'wolfShade', - icon: 'Pet-Wolf-Shade.png', - value: 3 - }, { - index: 12, - text: 'Skeleton Wolf', - name: 'wolfSkeleton', - icon: 'Pet-Wolf-Skeleton.png', - value: 3 - }, { - index: 13, - text: 'Veteran Wolf', - name: 'wolfVeteran', - icon: 'Pet-Wolf-Veteran.png', - value: 3 - }, { - index: 14, - text: 'White Wolf', - name: 'wolfWhite', - icon: 'Pet-Wolf-White.png', - value: 3 - }, { - index: 15, - text: 'Zombie Wolf', - name: 'wolfZombie', - icon: 'Pet-Wolf-Zombie.png', - value: 3 - }, { - index: 16, - text: 'Wolf', - name: 'wolfBorder', - icon: 'wolf_border.png', - value: 3 - } - ] - }; - -})(typeof exports === 'undefined'? this['habitrpgItems']={}: exports); \ No newline at end of file + }, { + index: 1, + text: "Sword", + classes: 'weapon_1', + notes: 'Increases experience gain by 3%.', + strength: 3, + value: 20 + }, { + index: 2, + text: "Axe", + classes: 'weapon_2', + notes: 'Increases experience gain by 6%.', + strength: 6, + value: 30 + }, { + index: 3, + text: "Morningstar", + classes: 'weapon_3', + notes: 'Increases experience gain by 9%.', + strength: 9, + value: 45 + }, { + index: 4, + text: "Blue Sword", + classes: 'weapon_4', + notes: 'Increases experience gain by 12%.', + strength: 12, + value: 65 + }, { + index: 5, + text: "Red Sword", + classes: 'weapon_5', + notes: 'Increases experience gain by 15%.', + strength: 15, + value: 90 + }, { + index: 6, + text: "Golden Sword", + classes: 'weapon_6', + notes: 'Increases experience gain by 18%.', + strength: 18, + value: 120 + } + ], + armor: [ + { + index: 0, + text: "Cloth Armor", + classes: 'armor_0', + notes: 'Training armor.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Leather Armor", + classes: 'armor_1', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 30 + }, { + index: 2, + text: "Chain Mail", + classes: 'armor_2', + notes: 'Decreases HP loss by 6%.', + defense: 6, + value: 45 + }, { + index: 3, + text: "Plate Mail", + classes: 'armor_3', + notes: 'Decreases HP loss by 7%.', + defense: 7, + value: 65 + }, { + index: 4, + text: "Red Armor", + classes: 'armor_4', + notes: 'Decreases HP loss by 8%.', + defense: 8, + value: 90 + }, { + index: 5, + text: "Golden Armor", + classes: 'armor_5', + notes: 'Decreases HP loss by 10%.', + defense: 10, + value: 120 + } + ], + head: [ + { + index: 0, + text: "No Helm", + classes: 'head_0', + notes: 'Training helm.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Leather Helm", + classes: 'head_1', + notes: 'Decreases HP loss by 2%.', + defense: 2, + value: 15 + }, { + index: 2, + text: "Chain Coif", + classes: 'head_2', + notes: 'Decreases HP loss by 3%.', + defense: 3, + value: 25 + }, { + index: 3, + text: "Plate Helm", + classes: 'head_3', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 45 + }, { + index: 4, + text: "Red Helm", + classes: 'head_4', + notes: 'Decreases HP loss by 5%.', + defense: 5, + value: 60 + }, { + index: 5, + text: "Golden Helm", + classes: 'head_5', + notes: 'Decreases HP loss by 6%.', + defense: 6, + value: 80 + } + ], + shield: [ + { + index: 0, + text: "No Shield", + classes: 'shield_0', + notes: 'No Shield.', + defense: 0, + value: 0 + }, { + index: 1, + text: "Wooden Shield", + classes: 'shield_1', + notes: 'Decreases HP loss by 3%', + defense: 3, + value: 20 + }, { + index: 2, + text: "Buckler", + classes: 'shield_2', + notes: 'Decreases HP loss by 4%.', + defense: 4, + value: 35 + }, { + index: 3, + text: "Enforced Shield", + classes: 'shield_3', + notes: 'Decreases HP loss by 5%.', + defense: 5, + value: 55 + }, { + index: 4, + text: "Red Shield", + classes: 'shield_4', + notes: 'Decreases HP loss by 7%.', + defense: 7, + value: 70 + }, { + index: 5, + text: "Golden Shield", + classes: 'shield_5', + notes: 'Decreases HP loss by 8%.', + defense: 8, + value: 90 + } + ], + potion: { + type: 'potion', + text: "Potion", + notes: "Recover 15 HP", + value: 25, + classes: 'potion' + }, + reroll: { + type: 'reroll', + text: "Re-Roll", + classes: 'reroll', + notes: "Resets your tasks. When you're struggling and everything's red, use for a clean slate.", + value: 0 + }, + pets: [ + { + index: 0, + text: 'Bear Cub', + name: 'bearcub', + icon: 'Pet-BearCub-Base.png', + value: 3 + }, { + index: 1, + text: 'Cactus', + name: 'cactus', + icon: 'Pet-Cactus-Base.png', + value: 3 + }, { + index: 2, + text: 'Drake', + name: 'dragon', + icon: 'Pet-Dragon-Base.png', + value: 3 + }, { + index: 3, + text: 'Flying Pig', + name: 'flyingpig', + icon: 'Pet-FlyingPig-Base.png', + value: 3 + }, { + index: 4, + text: 'Fox', + name: 'fox', + icon: 'Pet-Fox-Base.png', + value: 3 + }, { + index: 5, + text: 'Lion Cub', + name: 'lioncub', + icon: 'Pet-LionCub-Base.png', + value: 3 + }, { + index: 6, + text: 'Panda Cub', + name: 'pandacub', + icon: 'Pet-PandaCub-Base.png', + value: 3 + }, { + index: 7, + text: 'Tiger Cub', + name: 'tigercub', + icon: 'Pet-TigerCub-Base.png', + value: 3 + }, { + index: 8, + text: 'Desert Wolf', + name: 'wolfDesert', + icon: 'Pet-Wolf-Desert.png', + value: 3 + }, { + index: 9, + text: 'Golden Wolf', + name: 'wolfGolden', + icon: 'Pet-Wolf-Golden.png', + value: 3 + }, { + index: 10, + text: 'Red Wolf', + name: 'wolfRed', + icon: 'Pet-Wolf-Red.png', + value: 3 + }, { + index: 11, + text: 'Shade Wolf', + name: 'wolfShade', + icon: 'Pet-Wolf-Shade.png', + value: 3 + }, { + index: 12, + text: 'Skeleton Wolf', + name: 'wolfSkeleton', + icon: 'Pet-Wolf-Skeleton.png', + value: 3 + }, { + index: 13, + text: 'Veteran Wolf', + name: 'wolfVeteran', + icon: 'Pet-Wolf-Veteran.png', + value: 3 + }, { + index: 14, + text: 'White Wolf', + name: 'wolfWhite', + icon: 'Pet-Wolf-White.png', + value: 3 + }, { + index: 15, + text: 'Zombie Wolf', + name: 'wolfZombie', + icon: 'Pet-Wolf-Zombie.png', + value: 3 + }, { + index: 16, + text: 'Wolf', + name: 'wolfBorder', + icon: 'wolf_border.png', + value: 3 + } + ] +}; \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000000..a5e3d4c7a9 --- /dev/null +++ b/package.json @@ -0,0 +1,8 @@ +{ + "name": "habitrpg-shared", + "version": "0.0.0", + "dependencies": { + "browserify": "*", + "moment": "*" + } +} diff --git a/scoring.js b/scoring.js deleted file mode 100644 index c013d7b9e3..0000000000 --- a/scoring.js +++ /dev/null @@ -1,322 +0,0 @@ -return; - -//TODO - - -// Generated by CoffeeScript 1.4.0 -var MODIFIER, algos, async, browser, character, cron, helpers, items, moment, score, updateStats, _; - -async = require('async'); - -moment = require('moment'); - -_ = require('underscore'); - -helpers = require('./helpers'); - -browser = require('./browser'); - -character = require('./character'); - -items = require('./items'); - -algos = require('./algos'); - -MODIFIER = algos.MODIFIER; - -score = function(model, taskId, direction, times, batch, cron) { - var addPoints, calculateDelta, commit, delta, exp, gp, historyEntry, hp, lvl, newStats, num, obj, origStats, priority, r, subtractPoints, taskObj, taskPath, type, user, value, _ref, _ref1; - user = model.at('_user'); - commit = false; - if (batch == null) { - commit = true; - batch = new character.BatchUpdate(model); - batch.startTransaction(); - } - obj = batch.obj(); - _ref = obj.stats, gp = _ref.gp, hp = _ref.hp, exp = _ref.exp, lvl = _ref.lvl; - taskPath = "tasks." + taskId; - taskObj = obj.tasks[taskId]; - type = taskObj.type, value = taskObj.value; - priority = taskObj.priority || '!'; - if (taskObj.value > obj.stats.gp && taskObj.type === 'reward') { - r = confirm("Not enough GP to purchase this reward, buy anyway and lose HP? (Punishment for taking a reward you didn't earn)."); - if (!r) { - batch.commit(); - return; - } - } - delta = 0; - if (times == null) { - times = 1; - } - calculateDelta = function(adjustvalue) { - if (adjustvalue == null) { - adjustvalue = true; - } - return _.times(times, function(n) { - var nextDelta; - nextDelta = algos.taskDeltaFormula(value, direction); - if (adjustvalue) { - value += nextDelta; - } - return delta += nextDelta; - }); - }; - addPoints = function() { - var level, weaponStrength; - level = user.get('stats.lvl'); - weaponStrength = items.items.weapon[user.get('items.weapon')].strength; - exp += algos.expModifier(delta, weaponStrength, level, priority); - return gp += algos.gpModifier(delta, 1, priority); - }; - subtractPoints = function() { - var armorDefense, helmDefense, level, shieldDefense; - level = user.get('stats.lvl'); - armorDefense = items.items.armor[user.get('items.armor')].defense; - helmDefense = items.items.head[user.get('items.head')].defense; - shieldDefense = items.items.shield[user.get('items.shield')].defense; - return hp += algos.hpModifier(delta, armorDefense, helmDefense, shieldDefense, level, priority); - }; - switch (type) { - case 'habit': - calculateDelta(); - if (delta > 0) { - addPoints(); - } else { - subtractPoints(); - } - if ((_ref1 = taskObj.history) == null) { - taskObj.history = []; - } - if (taskObj.value !== value) { - historyEntry = { - date: +(new Date), - value: value - }; - taskObj.history.push(historyEntry); - batch.set("" + taskPath + ".history", taskObj.history); - } - break; - case 'daily': - if (cron != null) { - calculateDelta(); - subtractPoints(); - } else { - calculateDelta(false); - if (delta !== 0) { - addPoints(); - } - } - break; - case 'todo': - if (cron != null) { - calculateDelta(); - } else { - calculateDelta(); - addPoints(); - } - break; - case 'reward': - calculateDelta(false); - gp -= Math.abs(taskObj.value); - num = parseFloat(taskObj.value).toFixed(2); - if (gp < 0) { - hp += gp; - gp = 0; - } - } - taskObj.value = value; - batch.set("" + taskPath + ".value", taskObj.value); - origStats = _.clone(obj.stats); - updateStats(model, { - hp: hp, - exp: exp, - gp: gp - }, batch); - if (commit) { - newStats = _.clone(batch.obj().stats); - _.each(Object.keys(origStats), function(key) { - return obj.stats[key] = origStats[key]; - }); - batch.setStats(newStats); - batch.commit(); - } - return delta; -}; - -/* - Updates user stats with new stats. Handles death, leveling up, etc - {stats} new stats - {update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately -*/ - - -updateStats = function(model, newStats, batch) { - var gp, obj, tnl, user; - user = model.at('_user'); - obj = batch.obj(); - if (obj.stats.lvl === 0) { - return; - } - if (newStats.hp != null) { - if (newStats.hp <= 0) { - obj.stats.lvl = 0; - obj.stats.hp = 0; - return; - } else { - obj.stats.hp = newStats.hp; - } - } - if (newStats.exp != null) { - tnl = model.get('_tnl'); - if (obj.stats.lvl >= 100) { - newStats.gp += newStats.exp / 15; - newStats.exp = 0; - obj.stats.lvl = 100; - } else { - if (newStats.exp >= tnl) { - user.set('stats.exp', newStats.exp); - while (newStats.exp >= tnl && obj.stats.lvl < 100) { - newStats.exp -= tnl; - obj.stats.lvl++; - tnl = algos.tnl(obj.stats.lvl); - } - if (obj.stats.lvl === 100) { - newStats.exp = 0; - } - obj.stats.hp = 50; - } - } - obj.stats.exp = newStats.exp; - if (!obj.flags.customizationsNotification && (obj.stats.exp > 10 || obj.stats.lvl > 1)) { - batch.set('flags.customizationsNotification', true); - obj.flags.customizationsNotification = true; - } - if (!obj.flags.itemsEnabled && obj.stats.lvl >= 2) { - batch.set('flags.itemsEnabled', true); - obj.flags.itemsEnabled = true; - } - if (!obj.flags.partyEnabled && obj.stats.lvl >= 3) { - batch.set('flags.partyEnabled', true); - obj.flags.partyEnabled = true; - } - if (!obj.flags.petsEnabled && obj.stats.lvl >= 4) { - batch.set('flags.petsEnabled', true); - obj.flags.petsEnabled = true; - } - } - if (newStats.gp != null) { - if (!(typeof gp !== "undefined" && gp !== null) || gp < 0) { - gp = 0.0; - } - return obj.stats.gp = newStats.gp; - } -}; - -/* - At end of day, add value to all incomplete Daily & Todo tasks (further incentive) - For incomplete Dailys, deduct experience -*/ - - -cron = function(model) { - var batch, daysPassed, expTally, hpAfter, hpBefore, lvl, obj, today, todoTally, user, _base, _base1, _ref, _ref1, _ref2, _ref3; - user = model.at('_user'); - today = +(new Date); - daysPassed = helpers.daysBetween(user.get('lastCron'), today, user.get('preferences.dayStart')); - if (daysPassed > 0) { - batch = new character.BatchUpdate(model); - batch.startTransaction(); - batch.set('lastCron', today); - obj = batch.obj(); - hpBefore = obj.stats.hp; - todoTally = 0; - _.each(obj.tasks, function(taskObj) { - var absVal, completed, daysFailed, id, newValue, repeat, type, value, _ref; - id = taskObj.id, type = taskObj.type, completed = taskObj.completed, repeat = taskObj.repeat; - if (type === 'todo' || type === 'daily') { - if (!completed) { - daysFailed = daysPassed; - if (type === 'daily' && repeat) { - daysFailed = 0; - _.times(daysPassed, function(n) { - var thatDay; - thatDay = moment().subtract('days', n + 1); - if (repeat[helpers.dayMapping[thatDay.day()]] === true) { - return daysFailed++; - } - }); - } - score(model, id, 'down', daysFailed, batch, true); - } - if (type === 'daily') { - if (completed) { - newValue = taskObj.value + algos.taskDeltaFormula(taskObj.value, 'up'); - batch.set("tasks." + taskObj.id + ".value", newValue); - } - if ((_ref = taskObj.history) == null) { - taskObj.history = []; - } - taskObj.history.push({ - date: +(new Date), - value: taskObj.value - }); - batch.set("tasks." + taskObj.id + ".history", taskObj.history); - return batch.set("tasks." + taskObj.id + ".completed", false); - } else { - value = obj.tasks[taskObj.id].value; - absVal = completed ? Math.abs(value) : value; - return todoTally += absVal; - } - } else if (type === 'habit') { - if (taskObj.up === false || taskObj.down === false) { - if (Math.abs(taskObj.value) < 0.1) { - return batch.set("tasks." + taskObj.id + ".value", 0); - } else { - return batch.set("tasks." + taskObj.id + ".value", taskObj.value / 2); - } - } - } - }); - if ((_ref = obj.history) == null) { - obj.history = {}; - } - if ((_ref1 = (_base = obj.history).todos) == null) { - _base.todos = []; - } - if ((_ref2 = (_base1 = obj.history).exp) == null) { - _base1.exp = []; - } - obj.history.todos.push({ - date: today, - value: todoTally - }); - expTally = obj.stats.exp; - lvl = 0; - while (lvl < (obj.stats.lvl - 1)) { - lvl++; - expTally += algos.tnl(lvl); - } - obj.history.exp.push({ - date: today, - value: expTally - }); - _ref3 = [obj.stats.hp, hpBefore], hpAfter = _ref3[0], obj.stats.hp = _ref3[1]; - batch.setStats(); - batch.set('history', obj.history); - batch.commit(); - browser.resetDom(model); - return setTimeout((function() { - return user.set('stats.hp', hpAfter); - }), 1000); - } -}; - -exports = { - score: score, - cron: cron, - expModifier: algos.expModifier, - hpModifier: algos.hpModifier, - taskDeltaFormula: algos.taskDeltaFormula -};