2012-06-20 22:15:38 +00:00
|
|
|
derby = require('derby')
|
|
|
|
|
{get, view, ready} = derby.createApp module
|
|
|
|
|
derby.use require('derby-ui-boot')
|
2012-06-21 00:47:08 +00:00
|
|
|
derby.use(require('../../ui'))
|
2012-07-27 23:38:28 +00:00
|
|
|
|
2012-07-28 21:02:09 +00:00
|
|
|
# Custom requires
|
|
|
|
|
content = require('./content')
|
|
|
|
|
scoring = require('./scoring')
|
|
|
|
|
schema = require('./schema')
|
2012-07-30 21:25:04 +00:00
|
|
|
helpers = require('./helpers')
|
|
|
|
|
helpers.viewHelpers(view)
|
2012-08-03 02:51:39 +00:00
|
|
|
# $ = require('jQuery')
|
2012-08-08 13:57:32 +00:00
|
|
|
# und = require('underscore') # node.js uses _
|
2012-06-20 21:30:10 +00:00
|
|
|
|
2012-07-28 01:11:53 +00:00
|
|
|
# ========== ROUTES ==========
|
2012-07-08 20:59:07 +00:00
|
|
|
|
2012-09-03 02:46:11 +00:00
|
|
|
get '/:uidParam?', (page, model, {uidParam}, next) ->
|
|
|
|
|
#FIXME figure out a better way to do this
|
2012-09-15 19:06:51 +00:00
|
|
|
return next() if (uidParam == 'privacy' or uidParam == 'terms' or uidParam == 'auth')
|
|
|
|
|
|
2012-09-07 23:34:18 +00:00
|
|
|
sess = model.session
|
2012-09-15 19:06:51 +00:00
|
|
|
if sess.habitRpgAuth && sess.habitRpgAuth.facebook
|
|
|
|
|
model.set('_facebookAuthenticated', true)
|
|
|
|
|
model.set '_userId', sess.userId
|
|
|
|
|
model.subscribe "users.#{sess.userId}", (err, user) ->
|
2012-07-08 20:59:07 +00:00
|
|
|
model.ref '_user', user
|
|
|
|
|
|
2012-07-17 19:33:08 +00:00
|
|
|
# Store
|
2012-07-09 01:20:30 +00:00
|
|
|
model.set '_items'
|
|
|
|
|
armor: content.items.armor[parseInt(user.get('items.armor')) + 1]
|
|
|
|
|
weapon: content.items.weapon[parseInt(user.get('items.weapon')) + 1]
|
|
|
|
|
potion: content.items.potion
|
|
|
|
|
reroll: content.items.reroll
|
2012-07-08 23:37:36 +00:00
|
|
|
|
2012-09-01 20:44:01 +00:00
|
|
|
model.fn '_user._tnl', '_user.stats.lvl', (lvl) ->
|
2012-09-06 16:27:41 +00:00
|
|
|
# see https://github.com/lefnire/habitrpg/issues/4
|
|
|
|
|
# also update in scoring.coffee. TODO create a function accessible in both locations
|
|
|
|
|
(lvl*100)/5
|
2012-07-08 20:59:07 +00:00
|
|
|
|
|
|
|
|
# Default Tasks
|
|
|
|
|
model.refList "_habitList", "_user.tasks", "_user.habitIds"
|
|
|
|
|
model.refList "_dailyList", "_user.tasks", "_user.dailyIds"
|
|
|
|
|
model.refList "_todoList", "_user.tasks", "_user.todoIds"
|
2012-07-27 23:38:28 +00:00
|
|
|
model.refList "_completedList", "_user.tasks", "_user.completedIds"
|
2012-07-08 20:59:07 +00:00
|
|
|
model.refList "_rewardList", "_user.tasks", "_user.rewardIds"
|
2012-07-28 03:10:20 +00:00
|
|
|
|
2012-07-08 20:59:07 +00:00
|
|
|
page.render()
|
2012-04-27 02:19:31 +00:00
|
|
|
|
2012-07-28 01:11:53 +00:00
|
|
|
# ========== CONTROLLER FUNCTIONS ==========
|
2012-04-27 02:19:31 +00:00
|
|
|
|
|
|
|
|
ready (model) ->
|
2012-09-06 19:33:06 +00:00
|
|
|
|
2012-09-04 20:06:10 +00:00
|
|
|
$('[rel=tooltip]').tooltip()
|
2012-07-09 01:51:25 +00:00
|
|
|
$('[rel=popover]').popover()
|
2012-08-24 14:33:58 +00:00
|
|
|
# FIXME: this isn't very efficient, do model.on set for specific attrs for popover
|
2012-07-09 01:51:25 +00:00
|
|
|
model.on 'set', '*', ->
|
2012-09-04 21:10:36 +00:00
|
|
|
$('[rel=tooltip]').tooltip()
|
2012-07-09 01:51:25 +00:00
|
|
|
$('[rel=popover]').popover()
|
2012-06-27 13:37:36 +00:00
|
|
|
|
2012-07-31 16:59:42 +00:00
|
|
|
unless (model.get('_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) ->
|
|
|
|
|
$("ul.#{type}s").sortable
|
|
|
|
|
dropOnEmpty: false
|
|
|
|
|
cursor: "move"
|
|
|
|
|
items: "li"
|
|
|
|
|
opacity: 0.4
|
|
|
|
|
scroll: true
|
|
|
|
|
axis: 'y'
|
|
|
|
|
update: (e, ui) ->
|
|
|
|
|
item = ui.item[0]
|
|
|
|
|
domId = item.id
|
|
|
|
|
id = item.getAttribute 'data-id'
|
|
|
|
|
to = $("ul.#{type}s").children().index(item)
|
|
|
|
|
# Use the Derby ignore option to suppress the normal move event
|
|
|
|
|
# binding, since jQuery UI will move the element in the DOM.
|
|
|
|
|
# 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
|
|
|
|
|
setupSortable(type) for type in ['habit', 'daily', 'todo', 'reward']
|
2012-07-07 16:31:51 +00:00
|
|
|
|
|
|
|
|
tour = new Tour()
|
2012-07-08 02:34:34 +00:00
|
|
|
for step in content.tourSteps
|
|
|
|
|
tour.addStep
|
|
|
|
|
element: step.element
|
|
|
|
|
title: step.title
|
|
|
|
|
content: step.content
|
|
|
|
|
placement: step.placement
|
2012-07-07 16:31:51 +00:00
|
|
|
tour.start()
|
2012-08-24 02:54:09 +00:00
|
|
|
|
2012-07-28 01:56:21 +00:00
|
|
|
model.on 'set', '_user.tasks.*.completed', (i, completed, previous, isLocal, passed) ->
|
2012-08-06 14:15:58 +00:00
|
|
|
return if passed? && passed.cron # Don't do this stuff on cron
|
2012-07-28 00:47:59 +00:00
|
|
|
direction = () ->
|
|
|
|
|
return 'up' if completed==true and previous == false
|
|
|
|
|
return 'down' if completed==false and previous == true
|
2012-07-28 01:56:21 +00:00
|
|
|
throw new Error("Direction neither 'up' nor 'down' on checkbox set.")
|
2012-07-28 00:47:59 +00:00
|
|
|
|
|
|
|
|
# Score the user based on todo task
|
|
|
|
|
task = model.at("_user.tasks.#{i}")
|
2012-07-28 21:02:09 +00:00
|
|
|
scoring.score({user:model.at('_user'), task:task, direction:direction()})
|
2012-07-28 00:47:59 +00:00
|
|
|
|
|
|
|
|
# Then move the todos to/from _todoList/_completedList
|
|
|
|
|
if task.get('type') == 'todo'
|
2012-07-28 02:29:02 +00:00
|
|
|
[from, to] = if (direction()=='up') then ['todo', 'completed'] else ['completed', 'todo']
|
|
|
|
|
[from, to] = ["_user.#{from}Ids", "_user.#{to}Ids"]
|
|
|
|
|
# Remove from source (just remove the id from id-list)
|
|
|
|
|
fromIds = model.get(from)
|
|
|
|
|
fromIds.splice(fromIds.indexOf(i), 1)
|
|
|
|
|
model.set from, fromIds
|
|
|
|
|
# Push to target (just the id to id-list)
|
|
|
|
|
toIds = model.get(to)
|
|
|
|
|
toIds.push i
|
|
|
|
|
model.set to, toIds
|
2012-07-28 00:35:05 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
exports.addTask = (e, el, next) ->
|
|
|
|
|
type = $(el).attr('data-task-type')
|
|
|
|
|
list = model.at "_#{type}List"
|
|
|
|
|
newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1))
|
2012-04-27 17:04:44 +00:00
|
|
|
# Don't add a blank todo
|
2012-06-09 18:25:10 +00:00
|
|
|
return unless text = view.escapeHtml newModel.get()
|
|
|
|
|
newModel.set ''
|
|
|
|
|
switch type
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
when 'habit'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 0, up: true, down: true}
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-06-09 18:25:10 +00:00
|
|
|
when 'reward'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 20 }
|
2012-06-20 21:43:23 +00:00
|
|
|
|
2012-09-05 02:09:52 +00:00
|
|
|
when 'daily'
|
|
|
|
|
list.push {type: type, text: text, notes: '', value: 0, repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }
|
|
|
|
|
|
|
|
|
|
when 'todo'
|
2012-06-26 15:02:29 +00:00
|
|
|
list.push {type: type, text: text, notes: '', value: 0, completed: false }
|
2012-06-25 22:26:12 +00:00
|
|
|
|
|
|
|
|
# list.on 'set', '*.completed', (i, completed, previous, isLocal) ->
|
|
|
|
|
# # Move the item to the bottom if it was checked off
|
|
|
|
|
# list.move i, -1 if completed && isLocal
|
2012-04-27 17:04:44 +00:00
|
|
|
|
2012-07-07 02:09:51 +00:00
|
|
|
exports.del = (e, el) ->
|
2012-04-27 17:04:44 +00:00
|
|
|
# Derby extends model.at to support creation from DOM nodes
|
2012-07-07 02:09:51 +00:00
|
|
|
task = model.at(e.target)
|
2012-09-01 23:10:51 +00:00
|
|
|
|
|
|
|
|
history = task.get('history')
|
2012-09-04 21:10:36 +00:00
|
|
|
if history and history.length>2
|
|
|
|
|
# prevent delete-and-recreate hack on red tasks
|
|
|
|
|
if task.get('value') < 0
|
|
|
|
|
result = confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).")
|
|
|
|
|
if result != true
|
|
|
|
|
return # Cancel. Don't delete, don't hurt user
|
|
|
|
|
else
|
|
|
|
|
task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox"
|
|
|
|
|
scoring.score({user:model.at('_user'), task:task, direction:'down'})
|
|
|
|
|
|
|
|
|
|
# prevent accidently deleting long-standing tasks
|
2012-09-01 23:10:51 +00:00
|
|
|
else
|
2012-09-04 21:10:36 +00:00
|
|
|
result = confirm("Are you sure you want to delete this task?")
|
|
|
|
|
return if result != true
|
2012-09-01 23:10:51 +00:00
|
|
|
|
2012-07-07 02:09:51 +00:00
|
|
|
#TODO bug where I have to delete from _users.tasks AND _{type}List,
|
|
|
|
|
# fix when query subscriptions implemented properly
|
2012-09-04 21:10:36 +00:00
|
|
|
$('[rel=tooltip]').tooltip('hide')
|
2012-07-08 22:58:11 +00:00
|
|
|
model.del('_user.tasks.'+task.get('id'))
|
|
|
|
|
task.remove()
|
2012-06-25 01:51:13 +00:00
|
|
|
|
2012-07-28 04:01:58 +00:00
|
|
|
exports.clearCompleted = (e, el) ->
|
|
|
|
|
_.each model.get('_completedList'), (task) ->
|
|
|
|
|
model.del('_user.tasks.'+task.id)
|
|
|
|
|
model.set('_user.completedIds', [])
|
2012-09-05 02:09:52 +00:00
|
|
|
|
|
|
|
|
exports.toggleDay = (e, el) ->
|
|
|
|
|
task = model.at(e.target)
|
|
|
|
|
if /active/.test($(el).attr('class')) # previous state, not current
|
|
|
|
|
task.set('repeat.' + $(el).attr('data-day'), false)
|
|
|
|
|
else
|
|
|
|
|
task.set('repeat.' + $(el).attr('data-day'), true)
|
2012-07-28 04:01:58 +00:00
|
|
|
|
2012-06-29 13:45:50 +00:00
|
|
|
exports.toggleTaskEdit = (e, el) ->
|
2012-07-17 20:35:26 +00:00
|
|
|
hideId = $(el).attr('data-hide-id')
|
|
|
|
|
toggleId = $(el).attr('data-toggle-id')
|
|
|
|
|
$(document.getElementById(hideId)).hide()
|
|
|
|
|
$(document.getElementById(toggleId)).toggle()
|
2012-06-29 08:33:09 +00:00
|
|
|
|
2012-07-07 02:46:22 +00:00
|
|
|
exports.toggleChart = (e, el) ->
|
2012-07-17 20:35:26 +00:00
|
|
|
hideSelector = $(el).attr('data-hide-id')
|
|
|
|
|
chartSelector = $(el).attr('data-toggle-id')
|
2012-07-07 02:46:22 +00:00
|
|
|
historyPath = $(el).attr('data-history-path')
|
|
|
|
|
$(document.getElementById(hideSelector)).hide()
|
|
|
|
|
$(document.getElementById(chartSelector)).toggle()
|
2012-06-29 08:33:09 +00:00
|
|
|
|
|
|
|
|
matrix = [['Date', 'Score']]
|
2012-07-07 02:46:22 +00:00
|
|
|
for obj in model.get(historyPath)
|
2012-06-29 08:51:50 +00:00
|
|
|
date = new Date(obj.date)
|
2012-07-11 19:38:01 +00:00
|
|
|
readableDate = date.toISOString() #use toDateString() when done debugging
|
2012-06-29 08:51:50 +00:00
|
|
|
matrix.push [ readableDate, obj.value ]
|
2012-06-29 08:33:09 +00:00
|
|
|
data = google.visualization.arrayToDataTable matrix
|
2012-06-29 08:44:52 +00:00
|
|
|
|
2012-06-29 08:33:09 +00:00
|
|
|
options = {
|
|
|
|
|
title: 'History'
|
2012-06-29 08:44:52 +00:00
|
|
|
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
|
|
|
|
|
backgroundColor: 'whiteSmoke'
|
2012-06-29 08:33:09 +00:00
|
|
|
}
|
|
|
|
|
|
2012-07-07 02:46:22 +00:00
|
|
|
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
|
2012-06-29 08:33:09 +00:00
|
|
|
chart.draw(data, options)
|
2012-06-29 13:45:50 +00:00
|
|
|
|
2012-07-08 22:58:11 +00:00
|
|
|
exports.buyItem = (e, el, next) ->
|
|
|
|
|
user = model.at '_user'
|
2012-07-09 01:20:30 +00:00
|
|
|
#TODO: this should be working but it's not. so instead, i'm passing all needed values as data-attrs
|
|
|
|
|
# item = model.at(e.target)
|
|
|
|
|
|
2012-07-08 22:58:11 +00:00
|
|
|
money = user.get 'stats.money'
|
2012-07-09 01:20:30 +00:00
|
|
|
[type, value, index] = [ $(el).attr('data-type'), $(el).attr('data-value'), $(el).attr('data-index') ]
|
2012-07-08 22:58:11 +00:00
|
|
|
|
2012-07-09 01:20:30 +00:00
|
|
|
return if money < value
|
2012-07-08 22:58:11 +00:00
|
|
|
user.set 'stats.money', money - value
|
|
|
|
|
if type == 'armor'
|
2012-07-09 01:20:30 +00:00
|
|
|
user.set 'items.armor', index
|
|
|
|
|
model.set '_items.armor', content.items.armor[parseInt(index) + 1]
|
2012-07-08 22:58:11 +00:00
|
|
|
else if type == 'weapon'
|
2012-07-09 01:20:30 +00:00
|
|
|
user.set 'items.weapon', index
|
|
|
|
|
model.set '_items.weapon', content.items.weapon[parseInt(index) + 1]
|
2012-07-08 22:58:11 +00:00
|
|
|
else if type == 'potion'
|
|
|
|
|
hp = user.get 'stats.hp'
|
|
|
|
|
hp += 15
|
|
|
|
|
hp = 50 if hp > 50
|
|
|
|
|
user.set 'stats.hp', hp
|
2012-09-06 19:33:06 +00:00
|
|
|
|
2012-06-20 20:40:59 +00:00
|
|
|
exports.vote = (e, el, next) ->
|
2012-06-10 00:41:21 +00:00
|
|
|
direction = $(el).attr('data-direction')
|
2012-06-26 14:55:24 +00:00
|
|
|
direction = 'up' if direction == 'true/'
|
|
|
|
|
direction = 'down' if direction == 'false/'
|
2012-07-21 01:39:29 +00:00
|
|
|
user = model.at('_user')
|
2012-06-20 20:40:59 +00:00
|
|
|
task = model.at $(el).parents('li')[0]
|
2012-07-21 01:39:29 +00:00
|
|
|
|
2012-07-28 21:02:09 +00:00
|
|
|
scoring.score({user:user, task:task, direction:direction})
|
2012-06-28 22:11:13 +00:00
|
|
|
|
2012-07-28 01:11:53 +00:00
|
|
|
exports.revive = (e, el) ->
|
|
|
|
|
stats = model.at '_user.stats'
|
|
|
|
|
stats.set 'hp', 50; stats.set 'lvl', 1; stats.set 'exp', 0; stats.set 'money', 0
|
|
|
|
|
model.set '_user.items.armor', 0
|
|
|
|
|
model.set '_user.items.weapon', 0
|
|
|
|
|
model.set '_items.armor', content.items.armor[1]
|
|
|
|
|
model.set '_items.weapon', content.items.weapon[1]
|
2012-07-09 02:23:57 +00:00
|
|
|
|
2012-07-28 01:11:53 +00:00
|
|
|
# ========== CRON ==========
|
|
|
|
|
|
2012-08-06 16:34:54 +00:00
|
|
|
exports.poormanscron = poormanscron = () ->
|
2012-08-06 13:32:40 +00:00
|
|
|
today = new Date()
|
|
|
|
|
model.setNull('_user.lastCron', today)
|
|
|
|
|
lastCron = model.get('_user.lastCron')
|
|
|
|
|
daysPassed = helpers.daysBetween(lastCron, today)
|
2012-07-09 02:23:57 +00:00
|
|
|
if daysPassed > 0
|
2012-08-06 16:34:54 +00:00
|
|
|
model.set('_user.lastCron', today) # reset cron
|
2012-09-05 02:09:52 +00:00
|
|
|
_(daysPassed).times (n) ->
|
|
|
|
|
tallyFor = moment(lastCron).add('d',n)
|
|
|
|
|
scoring.tally(model.at('_user'), tallyFor)
|
2012-08-06 17:39:27 +00:00
|
|
|
# FIXME seems can't call poormanscron() instantly, have to call after some time (2s here)
|
|
|
|
|
# Doesn't do anything otherwise. Don't know why... model not initialized enough yet?
|
2012-08-09 00:28:01 +00:00
|
|
|
setTimeout () -> # Run once on refresh
|
2012-08-06 17:39:27 +00:00
|
|
|
poormanscron()
|
2012-08-09 00:28:01 +00:00
|
|
|
, 2000
|
|
|
|
|
setInterval () -> # Then run once every hour
|
2012-07-11 14:17:47 +00:00
|
|
|
poormanscron()
|
2012-08-09 00:28:01 +00:00
|
|
|
, 3600000
|
2012-07-11 14:17:47 +00:00
|
|
|
|
2012-07-28 20:26:06 +00:00
|
|
|
# ========== DEBUGGING ==========
|
|
|
|
|
|
|
|
|
|
exports.endOfDayTally = (e, el) ->
|
2012-07-28 21:02:09 +00:00
|
|
|
scoring.tally(model)
|
2012-07-28 20:26:06 +00:00
|
|
|
|
|
|
|
|
# Temporary solution to running updates against the schema when the code changes
|
2012-09-05 02:09:52 +00:00
|
|
|
# exports.updateSchema = (e, el) ->
|
|
|
|
|
# schema.updateSchema(model)
|
|
|
|
|
|