Habits are goals that you constantly track. For some habits, it only makes sense to gain points (eg, "1h Productive Work"). For others, it only makes sense to lose points (like "Eat Junk Food"). For the rest, both gain and loss apply (eg, for "Take The Stairs", stairs is a gain, elevator is a loss).
-
-
-
-
-
Dailies
-
-
Dailies are goals that you want to complete once a day. At the end of each day, non-completed Dailies dock you points. If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit. But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.
-
-
-
-
-
Todos
-
-
Todos are one-off goals which need to be completed eventually. Non-completed Todos won’t hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.
-
-
-
-
-
Rewards
-
-
As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits. But only buy if you have enough gold - you lose HP otherwise.
-
After you’ve played for a while, you unlock the Item Store under the rewards column. You can now buy weapons, armor, potions, etc. Armor decreases HP loss (by an increasing amount wich each upgrade). Weapons increase experience gain. Potions recover 15 HP.
-
-
-
-
-
License & Credits
-
-
Code is licensed under GNU GPL v3. Content is licensed under CC-BY-SA 3.0. See the LICENSE file for details.
";
- options = $.extend({}, this._options);
- if (step.options) $.extend(options, step.options);
- if (step.reflex) {
- $(step.element).css("cursor", "pointer");
- $(step.element).on("click", function(e) {
- $(step.element).css("cursor", "auto");
- return _this.next();
- });
- }
- nav = [];
- if (step.prev >= 0) {
- nav.push("" + options.labels.prev + "");
- }
- if (step.next >= 0) {
- nav.push("" + options.labels.next + "");
- }
- content += nav.join(" | ");
- content += "" + options.labels.end + "";
- $(step.element).popover({
- placement: step.placement,
- trigger: "manual",
- title: step.title,
- content: content,
- html: true,
- animation: step.animation
- }).popover("show");
- tip = $(step.element).data("popover").tip();
- this._reposition(tip);
- return this._scrollIntoView(tip);
- };
-
- Tour.prototype._reposition = function(tip) {
- var offsetBottom, offsetRight, tipOffset;
- tipOffset = tip.offset();
- offsetBottom = $(document).outerHeight() - tipOffset.top - $(tip).outerHeight();
- if (offsetBottom < 0) tipOffset.top = tipOffset.top + offsetBottom;
- offsetRight = $(document).outerWidth() - tipOffset.left - $(tip).outerWidth();
- if (offsetRight < 0) tipOffset.left = tipOffset.left + offsetRight;
- if (tipOffset.top < 0) tipOffset.top = 0;
- if (tipOffset.left < 0) tipOffset.left = 0;
- return tip.offset(tipOffset);
- };
-
- Tour.prototype._scrollIntoView = function(tip) {
- var tipRect;
- tipRect = tip.get(0).getBoundingClientRect();
- if (!(tipRect.top > 0 && tipRect.bottom < $(window).height() && tipRect.left > 0 && tipRect.right < $(window).width())) {
- return tip.get(0).scrollIntoView(true);
- }
- };
-
- Tour.prototype._onresize = function(cb, timeout) {
- return $(window).resize(function() {
- clearTimeout(timeout);
- return timeout = setTimeout(cb, 100);
- });
- };
-
- Tour.prototype._setupKeyboardNavigation = function() {
- var _this = this;
- if (this._options.keyboard) {
- return $(document).on("keyup.bootstrap-tour", function(e) {
- if (!e.which) return;
- switch (e.which) {
- case 39:
- e.preventDefault();
- if (_this._current < _this._steps.length - 1) return _this.next();
- break;
- case 37:
- e.preventDefault();
- if (_this._current > 0) return _this.prev();
- }
- });
- }
- };
-
- return Tour;
-
- })();
- return window.Tour = Tour;
- })(jQuery, window);
-
-}).call(this);
\ No newline at end of file
diff --git a/server.js b/server.js
index 51db9480cf..6412b78225 100644
--- a/server.js
+++ b/server.js
@@ -22,7 +22,7 @@ process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE");
process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY");
process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY");
-var agent;
+/*var agent;
if (process.env.NODE_ENV === 'development') {
// Follow these instructions for profiling / debugging leaks
// * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
@@ -31,7 +31,7 @@ if (process.env.NODE_ENV === 'development') {
console.log("To debug memory leaks:" +
"\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
"\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
-}
+}*/
process.on('uncaughtException', function (error) {
diff --git a/src/app/algos.coffee b/src/app/algos.coffee
new file mode 100644
index 0000000000..fff6cf5a7a
--- /dev/null
+++ b/src/app/algos.coffee
@@ -0,0 +1,67 @@
+XP = 15
+HP = 2
+
+priorityValue = (priority='!') ->
+ switch priority
+ when '!' then 1
+ when '!!' then 1.5
+ when '!!!' then 2
+ else 1
+
+module.exports.tnl = (level) ->
+ if level >= 100
+ value = 0
+ else
+ value = Math.round(((Math.pow(level,2)*0.25)+(10 * level) + 139.75)/10)*10 # round to nearest 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
+###
+module.exports.expModifier = (value, weaponStr, level, priority='!') ->
+ str = (level-1) / 2 # ultimately get this from user
+ 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
+###
+module.exports.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority='!') ->
+ def = (level-1) / 2 # ultimately get this from user?
+ totalDef = (def + armorDef + helmDef + shieldDef) / 100 #ultimate get this from user
+ defMod = 1 - totalDef
+ hp = value * HP * defMod * priorityValue(priority)
+ return Math.round(hp * 10)/10 # round to 1dp
+
+###
+ Future use
+ {priority} user-defined priority multiplier
+###
+module.exports.gpModifier = (value, modifier, 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
+###
+module.exports.taskDeltaFormula = (currentValue, direction) ->
+ if currentValue < -47.27 then currentValue = -47.27
+ else if currentValue > 21.27 then currentValue = 21.27
+ delta = Math.pow(0.9747,currentValue)
+ return delta if direction is 'up'
+ return -delta
+
+
diff --git a/src/app/browser.coffee b/src/app/browser.coffee
index 2830fefd48..6042821d90 100644
--- a/src/app/browser.coffee
+++ b/src/app/browser.coffee
@@ -1,75 +1,36 @@
_ = require 'underscore'
moment = require 'moment'
+#algos = require './algos'
-module.exports.restoreRefs = restoreRefs = (model) ->
- # tnl function
- model.fn '_tnl', '_user.stats.lvl', (lvl) ->
- # see https://github.com/lefnire/habitrpg/issues/4
- # also update in scoring.coffee. TODO create a function accessible in both locations
- (lvl*100)/5
+restoreRefs = module.exports.restoreRefs = (model) ->
#refLists
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
-module.exports.resetDom = (model) ->
- window.DERBY.app.dom.clear()
- restoreRefs(model)
- window.DERBY.app.view.render(model)
- reconstructPage model
-
-module.exports.app = (appExports, model) ->
- reconstructPage model
-
-reconstructPage = (model) ->
- loadJavaScripts(model)
- setupSortable(model)
- setupTooltips(model)
- setupTour(model)
- setupGrowlNotifications(model) unless model.get('_view.mobileDevice')
- $('.datepicker').datepicker({autoclose:true, todayBtn:true})
- .on 'changeDate', (ev) ->
- #for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
- #FIXME also, it saves as a day behind??
- model.at(ev.target).set 'date', moment(ev.date).add('d',1).format('MM/DD/YYYY')
-
###
Loads JavaScript files from (1) public/js/* and (2) external sources
- We use this file (instead of or inside .html) so we can utilize require() to concatinate for
- faster page load, and $.getScript for asyncronous external script loading
+ If a library is available in a CDN, we put it in (index.html) for better caching. If not, we use
+ this function to utilize require() to concatinate for faster page load, and $.getScript for asyncronous external script loading
###
loadJavaScripts = (model) ->
- require '../../public/vendor/jquery-ui/jquery-1.9.1'
unless model.get('_view.mobileDevice')
require '../../public/vendor/jquery-ui/ui/jquery.ui.core'
require '../../public/vendor/jquery-ui/ui/jquery.ui.widget'
require '../../public/vendor/jquery-ui/ui/jquery.ui.mouse'
require '../../public/vendor/jquery-ui/ui/jquery.ui.sortable'
- # Bootstrap
- require '../../public/vendor/bootstrap/docs/assets/js/bootstrap'
-# require '../../public/vendor/bootstrap/js/bootstrap-tooltip'
-# require '../../public/vendor/bootstrap/js/bootstrap-tab'
-# require '../../public/vendor/bootstrap/js/bootstrap-popover'
-# require '../../public/vendor/bootstrap/js/bootstrap-modal'
-# require '../../public/vendor/bootstrap/js/bootstrap-dropdown'
-
-
- require '../../public/vendor/jquery-cookie/jquery.cookie'
- require '../../public/vendor/bootstrap-tour' #https://raw.github.com/pushly/bootstrap-tour/master/bootstrap-tour.js
- require '../../public/vendor/bootstrap-datepicker/js/bootstrap-datepicker'
- require '../../public/vendor/bootstrap-growl/jquery.bootstrap-growl.min'
-
+ require '../../public/vendor/bootstrap-tour/bootstrap-tour'
# JS files not needed right away (google charts) or entirely optional (analytics)
# Each file getsload asyncronously via $.getScript, so it doesn't bog page-load
unless model.get('_view.mobileDevice')
- $.getScript("https://s7.addthis.com/js/250/addthis_widget.js#pubid=lefnire");
+ $.getScript("//s7.addthis.com/js/250/addthis_widget.js#pubid=lefnire");
# Google Charts
- $.getScript "https://www.google.com/jsapi", ->
+ $.getScript "//www.google.com/jsapi", ->
# Specifying callback in options param is vital! Otherwise you get blank screen, see http://stackoverflow.com/a/12200566/362790
google.load "visualization", "1", {packages:["corechart"], callback: ->}
@@ -81,15 +42,11 @@ loadJavaScripts = (model) ->
###
setupSortable = (model) ->
unless (model.get('_view.mobileDevice') == true) #don't do sortable on mobile
- # Make the lists draggable using jQuery UI
- # Note, have to setup helper function here and call it for each type later
- # due to variable binding of "type"
- setupSortable = (type) ->
+ _.each ['habit', 'daily', 'todo', 'reward'], (type) ->
$("ul.#{type}s").sortable
dropOnEmpty: false
cursor: "move"
items: "li"
- opacity: 0.4
scroll: true
axis: 'y'
update: (e, ui) ->
@@ -102,16 +59,15 @@ setupSortable = (model) ->
# Also, note that refList index arguments can either be an index
# or the item's id property
model.at("_#{type}List").pass(ignore: domId).move {id}, to
- _.each ['habit', 'daily', 'todo', 'reward'], (type) -> setupSortable(type)
setupTooltips = (model) ->
$('[rel=tooltip]').tooltip()
$('[rel=popover]').popover()
- # FIXME: this isn't very efficient, do model.on set for specific attrs for popover
- model.on 'set', '*', ->
- $('[rel=tooltip]').tooltip()
- $('[rel=popover]').popover()
+ $('.priority-multiplier-help').popover
+ title: "How difficult is this task?"
+ trigger: "hover"
+ content: "This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info."
setupTour = (model) ->
tourSteps = [
@@ -160,12 +116,8 @@ setupTour = (model) ->
$('.main-avatar').popover('destroy') #remove previous popovers
tour = new Tour()
_.each tourSteps, (step) ->
- tour.addStep
- html: true
- element: step.element
- title: step.title
- content: step.content
- placement: step.placement
+ tour.addStep _.defaults step, {html:true}
+ tour._current = 0 if isNaN(tour._current) #bootstrap-tour bug
tour.start()
###
@@ -180,7 +132,7 @@ setupGrowlNotifications = (model) ->
return if user.get('stats.lvl') == 0
$.bootstrapGrowl html,
ele: '#notification-area',
- type: type # (null, 'info', 'error', 'success', 'gp', 'xp', 'hp', 'lvl')
+ type: type # (null, 'info', 'error', 'success', 'gp', 'xp', 'hp', 'lvl','death')
top_offset: 20
align: 'right' # ('left', 'right', or 'center')
width: 250 # (integer, or 'auto')
@@ -193,21 +145,55 @@ setupGrowlNotifications = (model) ->
num = captures - args
rounded = Math.abs(num.toFixed(1))
if num < 0
- statsNotification " -#{rounded} HP", 'hp' # lost hp from purchase
+ statsNotification " - #{rounded} HP", 'hp' # lost hp from purchase
+ else if num > 0
+ statsNotification " + #{rounded} HP", 'hp' # gained hp from potion/level?
+
+ user.on 'set', 'stats.exp', (captures, args, isLocal, silent=false) ->
+ # unless silent
+ num = captures - args
+ rounded = Math.abs(num.toFixed(1))
+ if num < 0 and num > -50 # TODO fix hackey negative notification supress
+ statsNotification " - #{rounded} XP", 'xp'
+ else if num > 0
+ statsNotification " + #{rounded} XP", 'xp'
user.on 'set', 'stats.gp', (captures, args) ->
num = captures - args
- rounded = Math.abs(num.toFixed(1))
- # made purchase
- if num < 0
- # FIXME use 'warning' when unchecking an accidently completed daily/todo, and notify of exp too
- statsNotification " -#{rounded} GP", 'gp'
- # gained gp (and thereby exp)
- else if num > 0
- num = Math.abs(num)
- statsNotification " +#{rounded} XP", 'xp'
- statsNotification " +#{rounded} GP", 'gp'
+ absolute = Math.abs(num)
+ gold = Math.floor(absolute)
+ silver = Math.floor((absolute-gold)*100)
+ sign = if num < 0 then '-' else '+'
+ if gold and silver > 0
+ statsNotification "#{sign} #{gold} #{silver} ", 'gp'
+ else if gold > 0
+ statsNotification "#{sign} #{gold} ", 'gp'
+ else if silver > 0
+ statsNotification "#{sign} #{silver} ", 'gp'
user.on 'set', 'stats.lvl', (captures, args) ->
if captures > args
- statsNotification(' Level Up!', 'lvl')
\ No newline at end of file
+ if captures is 1 and args is 0
+ statsNotification ' You died! Game over.', 'death'
+ else
+ statsNotification ' Level Up!', 'lvl'
+
+
+module.exports.resetDom = (model) ->
+ DERBY.app.dom.clear()
+ DERBY.app.view.render(model, DERBY.app.view._lastRender.ns, DERBY.app.view._lastRender.context);
+
+module.exports.app = (appExports, model, app) ->
+ loadJavaScripts(model)
+ setupGrowlNotifications(model) unless model.get('_view.mobileDevice')
+
+ app.on 'render', (ctx) ->
+ #restoreRefs(model)
+ setupSortable(model)
+ setupTooltips(model)
+ setupTour(model)
+ $('.datepicker').datepicker({autoclose:true, todayBtn:true})
+ .on 'changeDate', (ev) ->
+ #for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
+ #FIXME also, it saves as a day behind??
+ model.at(ev.target).set 'date', moment(ev.date).add('d',1).format('MM/DD/YYYY')
\ No newline at end of file
diff --git a/src/app/character.coffee b/src/app/character.coffee
index 1c3dd6334d..e5abfeda80 100644
--- a/src/app/character.coffee
+++ b/src/app/character.coffee
@@ -1,6 +1,7 @@
character = require './character'
browser = require './browser'
items = require './items'
+algos = require './algos'
moment = require 'moment'
_ = require 'underscore'
@@ -21,6 +22,9 @@ module.exports.username = username = (auth) ->
module.exports.view = (view) ->
view.fn "username", (auth) -> username(auth)
+ view.fn "tnl", algos.tnl
+
+
module.exports.app = (appExports, model) ->
user = model.at '_user'
@@ -57,8 +61,14 @@ module.exports.app = (appExports, model) ->
batch.commit()
browser.resetDom(model)
- appExports.closeCelebrationNofitication = (e, el) ->
- user.set('flags.celebrationEvent', 'hide')
+ appExports.closeAlgosNotification = (e, el) ->
+ user.set('flags.algosNotification', 'hide')
+
+ appExports.closeOnliesNotification = (e, el) ->
+ user.set('flags.onliesNotification', 'hide')
+
+ appExports.closePriorityNotification = (e, el) ->
+ user.set('flags.priorityNotification', 'hide')
appExports.customizeGender = (e, el) ->
user.set 'preferences.gender', $(el).attr('data-value')
@@ -76,9 +86,16 @@ module.exports.app = (appExports, model) ->
batch = new BatchUpdate(model)
batch.startTransaction()
$('#restore-form input').each ->
- batch.set $(this).attr('data-for'), parseInt($(this).val())
+ batch.set $(this).attr('data-for'), parseInt($(this).val() || 1)
batch.commit()
+ appExports.toggleHeader = (e, el) ->
+ user.set 'preferences.hideHeader', !user.get('preferences.hideHeader')
+
+ appExports.deleteAccount = (e, el) ->
+ model.del "users.#{user.get('id')}", ->
+ window.location.href = "/logout"
+
user.on 'set', 'flags.customizationsNotification', (captures, args) ->
return unless captures == true
$('.main-avatar').popover('destroy') #remove previous popovers
@@ -99,7 +116,7 @@ userSchema =
stats: { gp: 0, exp: 0, lvl: 1, hp: 50 }
party: { current: null, invitation: null }
items: { weapon: 0, armor: 0, head: 0, shield: 0 }
- preferences: { gender: 'm', skin: 'white', hair: 'blond', armorSet: 'v1' }
+ preferences: { gender: 'm', skin: 'white', hair: 'blond', armorSet: 'v1', dayStart:0, showHelm: true }
habitIds: []
dailyIds: []
todoIds: []
@@ -119,14 +136,18 @@ module.exports.newUserObject = ->
newUser = lodash.cloneDeep userSchema
newUser.apiToken = derby.uuid()
+ repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true}
defaultTasks = [
{type: 'habit', text: '1h Productive Work', notes: '-- Habits: Constantly Track --\nFor some habits, it only makes sense to *gain* points (like this one).', value: 0, up: true, down: false }
{type: 'habit', text: 'Eat Junk Food', notes: 'For others, it only makes sense to *lose* points', value: 0, up: false, down: true}
{type: 'habit', text: 'Take The Stairs', notes: 'For the rest, both + and - make sense (stairs = gain, elevator = lose)', value: 0, up: true, down: true}
- {type: 'daily', text: '1h Personal Project', notes: '-- Dailies: Complete Once a Day --\nAt the end of each day, non-completed Dailies dock you points.', value: 0, completed: false }
- {type: 'daily', text: 'Exercise', notes: "If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit.", value: 3, completed: false }
- {type: 'daily', text: '45m Reading', notes: 'But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.', value: -10, completed: false }
+
+ {type: 'daily', text: '1h Personal Project', notes: '-- Dailies: Complete Once a Day --\nAt the end of each day, non-completed Dailies dock you points.', value: 0, completed: false, repeat: repeat }
+ {type: 'daily', text: 'Exercise', notes: "If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit.", value: 3, completed: false, repeat: repeat }
+ {type: 'daily', text: '45m Reading', notes: 'But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.', value: -10, completed: false, repeat: repeat }
+
{type: 'todo', text: 'Call Mom', notes: "-- Todos: Complete Eventually --\nNon-completed Todos won't hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.", value: -3, completed: false }
+
{type: 'reward', text: '1 Episode of Game of Thrones', notes: '-- Rewards: Treat Yourself! --\nAs you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits.', value: 20 }
{type: 'reward', text: 'Cake', notes: 'But only buy if you have enough gold - you lose HP otherwise.', value: 10 }
]
@@ -177,7 +198,7 @@ module.exports.updateUser = (model) ->
union = _.union obj[type + 'Ids'], taskIds
# 2. remove empty (grey) tasks
- preened = _.filter(union, (val) -> _.contains(taskIds, val))
+ preened = _.filter union, (val) -> _.contains(taskIds, val) and val?
# There were indeed issues found, set the new list
batch.set("#{type}Ids", preened) # if _.difference(preened, userObj[path]).length != 0
@@ -233,6 +254,7 @@ module.exports.BatchUpdate = BatchUpdate = (model) ->
commit: ->
model._dontPersist = false
# some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js
+ # pass true if we have levelled to supress xp notification
user.set "update__", updates
transactionInProgress = false
updates = {}
diff --git a/src/app/debug.coffee b/src/app/debug.coffee
index 25cb833b8f..1017118a9f 100644
--- a/src/app/debug.coffee
+++ b/src/app/debug.coffee
@@ -1,4 +1,5 @@
moment = require 'moment'
+algos = require './algos'
module.exports.app = (appExports, model) ->
user = model.at('_user')
@@ -8,6 +9,20 @@ module.exports.app = (appExports, model) ->
user.set 'lastCron', yesterday
window.location.reload()
+ appExports.emulateTenDays = ->
+ yesterday = +moment().subtract('days', 10).toDate()
+ user.set 'lastCron', yesterday
+ window.location.reload()
+
appExports.cheat = ->
- user.incr 'stats.exp', 20
+ user.incr 'stats.exp', algos.tnl(user.get('stats.lvl'))
user.incr 'stats.gp', 1000
+
+ appExports.reset = ->
+ user.set 'stats.exp', 0
+ user.set 'stats.lvl', 0
+ user.set 'stats.gp', 0
+ user.set 'items.weapon', 0
+ user.set 'items.armor', 0
+ user.set 'items.head', 0
+ user.set 'items.shield', 0
\ No newline at end of file
diff --git a/src/app/helpers.coffee b/src/app/helpers.coffee
index 38733f11d5..363f0b7ada 100644
--- a/src/app/helpers.coffee
+++ b/src/app/helpers.coffee
@@ -1,8 +1,11 @@
-moment = require('moment')
+moment = require 'moment'
+_ = require 'underscore'
-# Absolute diff between two dates, based on 12am for both days
-module.exports.daysBetween = (a, b) ->
- Math.abs(moment(a).startOf('day').diff(moment(b).startOf('day'), 'days'))
+# Absolute diff between two dates
+module.exports.daysBetween = (yesterday, now, dayStart) ->
+ #sanity-check reset-time (is it 24h time?)
+ dayStart = 0 unless (dayStart? and (dayStart = parseInt(dayStart)) and dayStart >= 0 and dayStart <= 24)
+ Math.abs moment(yesterday).startOf('day').add('h', dayStart).diff(moment(now), 'days')
module.exports.dayMapping = dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s',7:'su'}
@@ -14,6 +17,12 @@ module.exports.viewHelpers = (view) ->
view.fn "round", (num) ->
Math.round num
+
+ view.fn "floor", (num) ->
+ Math.floor num
+
+ view.fn "ceil", (num) ->
+ Math.ceil num
view.fn "lt", (a, b) ->
a < b
@@ -29,4 +38,8 @@ module.exports.viewHelpers = (view) ->
loc = window?.location.host or process.env.BASE_URL
encodeURIComponent "http://#{loc}/v1/users/#{uid}/calendar.ics?apiToken=#{apiToken}"
+ view.fn "notEqual", (a, b) -> (a != b)
+ view.fn "and", -> _.reduce arguments, (cumm, curr) -> cumm && curr
+ view.fn "or", -> _.reduce arguments, (cumm, curr) -> cumm || curr
+
diff --git a/src/app/index.coffee b/src/app/index.coffee
index 49e18c800b..551f711952 100644
--- a/src/app/index.coffee
+++ b/src/app/index.coffee
@@ -1,6 +1,7 @@
derby = require 'derby'
-{get, view, ready} = derby.createApp module
-derby.use require('derby-ui-boot'), {styles: ['bootstrap', 'responsive']}
+app = derby.createApp module
+{get, view, ready} = app
+derby.use require('derby-ui-boot'), {styles: []}
derby.use require '../../ui'
derby.use require 'derby-auth/components'
@@ -46,13 +47,12 @@ get '/', (page, model, params, next) ->
ready (model) ->
user = model.at('_user')
- score = new scoring.Scoring(model)
#set cron immediately
lastCron = user.get('lastCron')
user.set('lastCron', +new Date) if (!lastCron? or lastCron == 'new')
- score.cron()
+ scoring.cron(model)
character.app(exports, model)
tasks.app(exports, model)
@@ -61,6 +61,6 @@ ready (model) ->
profile.app(exports, model)
require('../server/private').app(exports, model)
require('./debug').app(exports, model) if model.get('_view.nodeEnv') != 'production'
- browser.app(exports, model)
filters.app(exports, model)
+ browser.app(exports, model, app)
diff --git a/src/app/items.coffee b/src/app/items.coffee
index 911458e60c..a635efd8b6 100644
--- a/src/app/items.coffee
+++ b/src/app/items.coffee
@@ -2,40 +2,40 @@ _ = require 'underscore'
items = module.exports.items =
weapon: [
- {index: 0, text: "Training Sword", classes: "weapon_0", notes:'Training weapon.', modifier: 0.00, value:0}
- {index: 1, text: "Sword", classes:'weapon_1', notes:'Increases experience gain by 3%.', modifier: 0.03, value:20}
- {index: 2, text: "Axe", classes:'weapon_2', notes:'Increases experience gain by 6%.', modifier: 0.06, value:30}
- {index: 3, text: "Morningstar", classes:'weapon_3', notes:'Increases experience gain by 9%.', modifier: 0.09, value:45}
- {index: 4, text: "Blue Sword", classes:'weapon_4', notes:'Increases experience gain by 12%.', modifier: 0.12, value:65}
- {index: 5, text: "Red Sword", classes:'weapon_5', notes:'Increases experience gain by 15%.', modifier: 0.15, value:90}
- {index: 6, text: "Golden Sword", classes:'weapon_6', notes:'Increases experience gain by 18%.', modifier: 0.18, value:120}
+ {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.', modifier: 0.00, value:0}
- {index: 1, text: "Leather Armor", classes: 'armor_1', notes:'Decreases HP loss by 4%.', modifier: 0.04, value:30}
- {index: 2, text: "Chain Mail", classes: 'armor_2', notes:'Decreases HP loss by 6%.', modifier: 0.06, value:45}
- {index: 3, text: "Plate Mail", classes: 'armor_3', notes:'Decreases HP loss by 7%.', modifier: 0.07, value:65}
- {index: 4, text: "Red Armor", classes: 'armor_4', notes:'Decreases HP loss by 8%.', modifier: 0.08, value:90}
- {index: 5, text: "Golden Armor", classes: 'armor_5', notes:'Decreases HP loss by 10%.', modifier: 0.1, value:120}
+ {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.', modifier: 0.00, value:0}
- {index: 1, text: "Leather Helm", classes: 'head_1', notes:'Decreases HP loss by 2%.', modifier: 0.02, value:15}
- {index: 2, text: "Chain Coif", classes: 'head_2', notes:'Decreases HP loss by 3%.', modifier: 0.03, value:25}
- {index: 3, text: "Plate Helm", classes: 'head_3', notes:'Decreases HP loss by 4%.', modifier: 0.04, value:45}
- {index: 4, text: "Red Helm", classes: 'head_4', notes:'Decreases HP loss by 5%.', modifier: 0.05, value:60}
- {index: 5, text: "Golden Helm", classes: 'head_5', notes:'Decreases HP loss by 6%.', modifier: 0.06, value:80}
+ {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.', modifier: 0.00, value:0}
- {index: 1, text: "Wooden Shield", classes: 'shield_1', notes:'Decreases HP loss by 3%', modifier: 0.03, value:20}
- {index: 2, text: "Buckler", classes: 'shield_2', notes:'Decreases HP loss by 4%.', modifier: 0.04, value:35}
- {index: 3, text: "Enforced Shield", classes: 'shield_3', notes:'Decreases HP loss by 5%.', modifier: 0.05, value:55}
- {index: 4, text: "Red Shield", classes: 'shield_4', notes:'Decreases HP loss by 7%.', modifier: 0.07, value:70}
- {index: 5, text: "Golden Shield", classes: 'shield_5', notes:'Decreases HP loss by 8%.', modifier: 0.08, value:90}
+ {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 }
+ reroll: {type: 'reroll', text: "Re-Roll", classes: 'reroll', notes: "Resets your task values back to 0 (yellow). Useful when everything's red and it's hard to stay alive.", value:0 }
pets: [
{index: 0, text: 'Bear Cub', name: 'bearcub', icon: 'Pet-BearCub-Base.png', value: 3}
@@ -165,22 +165,22 @@ module.exports.app = (appExports, model) ->
model.set '_view.activeTabPets', true
model.set '_view.activeTabRewards', false
- user.on 'set', 'flags.itemsEnabled', (captures, args) ->
- return unless captures == true
+ model.on 'set', '_user.flags.itemsEnabled', (captures, args) ->
+ return unless captures is true
html = """
-
+
Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.
- [Close]
+ [Close]
New features! Custom day start, new and better algorithms, authentication enhancements. See details here.
+ "Hey, I had more Exp before this roll-out!" Restore your Exp here.
+ "Onlies" now gain / lose value just like other habits, and are reset back to 0 at the beginning of each day.
+ See details here, and
+ chime in with your recommendations on this mechanic.
+