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-08 02:29:34 +00:00
content = require ( ' ./content ' )
2012-07-11 23:27:53 +00:00
Guid = require ( ' guid ' )
2012-04-27 02:19:31 +00:00
2012-06-10 00:15:33 +00:00
## VIEW HELPERS ##
2012-06-27 13:37:36 +00:00
view . fn ' taskClasses ' , (type, completed, value, hideCompleted) ->
#TODO figure out how to just pass in the task model, so i can access all these properties from one object
2012-06-20 21:13:47 +00:00
classes = type
2012-06-26 12:45:35 +00:00
classes += " completed " if completed
2012-06-27 13:37:36 +00:00
if type == ' todo '
classes += " hide " if ( hideCompleted and completed ) or ( ! hideCompleted and ! completed )
2012-06-26 12:02:16 +00:00
switch
2012-06-26 15:02:29 +00:00
when value < - 8 then classes += ' color-worst '
when value >= - 8 and value < - 5 then classes += ' color-worse '
when value >= - 5 and value < - 1 then classes += ' color-bad '
when value >= - 1 and value < 1 then classes += ' color-neutral '
when value >= 1 and value < 5 then classes += ' color-good '
when value >= 5 and value < 10 then classes += ' color-better '
when value >= 10 then classes += ' color-best '
2012-06-10 00:15:33 +00:00
return classes
2012-06-20 21:30:10 +00:00
2012-06-20 22:34:15 +00:00
view . fn " percent " , (x, y) ->
x = 1 if x == 0
Math . round ( x / y * 100 )
2012-06-20 21:30:10 +00:00
view . fn " round " , (num) ->
Math . round num
view . fn " gold " , (num) ->
2012-07-08 22:58:11 +00:00
if num
return num . toFixed ( 1 ) . split ( ' . ' ) [ 0 ]
else
return " 0 "
2012-06-20 21:30:10 +00:00
view . fn " silver " , (num) ->
2012-07-08 22:58:11 +00:00
if num
num . toFixed ( 1 ) . split ( ' . ' ) [ 1 ]
else
return " 0 "
2012-07-08 20:59:07 +00:00
## ROUTES ##
2012-07-17 18:47:19 +00:00
debug = (obj, message) ->
console . log obj , " [debug] #{ message } "
2012-07-11 23:27:53 +00:00
2012-07-08 20:59:07 +00:00
2012-07-17 18:47:19 +00:00
get ' /:userId? ' , (page, model, {userId}) ->
# # Saved session
# # TODO: this doesn't check that the user at guid exists, and
# # will probably error since no user is created. Will only happen if
# # first access, but still. model.get(userId) and model.get("users.#{userId}") aren't
# # working for some reason
# debuggingUsers = (parseInt(userId) < 40) #these are users created before guid was in use, need to convert them to guid and get rid of this
# if userId? and (Guid.isGuid(userId) or debuggingUsers)# and model.get(userId)?
# model.set '_userId', userId
# return getRoom page, model, userId
model . subscribe " users " , (err, users) ->
# The session middleware will assign a _userId automatically
# Render page if a userId is already stored in session data
userId = model . get ' _userId '
debug userId , ' model.get _userId '
if model . get " users. #{ userId } "
debug userId , ' model.get userId '
return getRoom page , model , userId
else
# Otherwise, select a new userId and initialize user
newUser = {
tasks: { }
}
users . set userId , newUser , (err, path, value) ->
debug { err : err , path : path , value : value } , ' new user '
getRoom page , model , userId
2012-07-08 20:59:07 +00:00
getRoom = (page, model, userId) ->
2012-07-17 18:47:19 +00:00
user = model . at ( " users. #{ userId } " )
2012-07-08 20:59:07 +00:00
model . ref ' _user ' , user
2012-07-17 18:47:19 +00:00
debug user , ' user '
2012-07-08 20:59:07 +00:00
### Set User Defaults ###
# Default Items & Stats
user . setNull ' stats ' , { money: 0 , exp: 0 , lvl: 1 , hp: 50 }
2012-07-08 21:28:10 +00:00
user . setNull ' items ' , { itemsEnabled: false , armor: 0 , weapon: 0 }
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-07-08 20:59:07 +00:00
# http://tibia.wikia.com/wiki/Formula
model . fn ' _tnl ' , ' _user.stats.lvl ' , (lvl) -> 50 * Math . pow ( lvl , 2 ) - 150 * lvl + 200
# Default Tasks
model . refList " _habitList " , " _user.tasks " , " _user.habitIds "
model . refList " _dailyList " , " _user.tasks " , " _user.dailyIds "
model . refList " _todoList " , " _user.tasks " , " _user.todoIds "
model . refList " _rewardList " , " _user.tasks " , " _user.rewardIds "
unless model . get ( ' _user.tasks ' )
2012-07-17 18:47:19 +00:00
model . push ' _habitList ' , { type: ' habit ' , text: ' 1h Productive Work ' , notes: ' <u>Habits: Constantly Track</u><br/>For some habits, it only makes sense to <b>gain</b> points (like this one). ' , value: 0 , up: true , down: false }
# model.push '_habitList', task for task in content.defaultTasks.habits
# model.push '_dailyList', task for task in content.defaultTasks.dailys
# model.push '_todoList', task for task in content.defaultTasks.todos
# model.push '_rewardList', task for task in content.defaultTasks.rewards
2012-07-08 20:59:07 +00:00
page . render ( )
2012-04-27 02:19:31 +00:00
## CONTROLLER FUNCTIONS ##
ready (model) ->
2012-07-08 03:43:35 +00:00
2012-07-12 00:07:54 +00:00
model . set ' _purl ' , window . location . origin + ' / ' + model . get ( ' _user.id ' )
2012-07-11 23:27:53 +00:00
2012-07-09 01:51:25 +00:00
$ ( ' [rel=popover] ' ) . popover ( )
#TODO: this isn't very efficient, do model.on set for specific attrs for popover
model . on ' set ' , ' * ' , ->
$ ( ' [rel=popover] ' ) . popover ( )
2012-06-27 13:37:36 +00:00
model . set ( ' _hideCompleted ' , true )
$ ( ' a[data-toggle= " tab " ] ' ) . on ' shown ' , (e) ->
#see http://twitter.github.com/bootstrap/javascript.html#tabs
hideCompleted = if $ ( e . target ) . attr ( ' href ' ) == ' # tab1 ' then true else false
model . set ( ' _hideCompleted ' , hideCompleted )
2012-06-25 02:35:04 +00:00
2012-07-07 04:07:30 +00:00
# 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
2012-06-26 15:44:37 +00:00
dropOnEmpty: false
cursor: " move "
items: " li "
opacity: 0.4
scroll: true
2012-05-03 01:07:36 +00:00
axis: ' y '
update: (e, ui) ->
item = ui . item [ 0 ]
domId = item . id
id = item . getAttribute ' data-id '
2012-07-07 04:07:30 +00:00
to = $ ( " ul. #{ type } s " ) . children ( ) . index ( item )
2012-05-03 01:07:36 +00:00
# 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
2012-07-07 04:07:30 +00:00
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-06-26 15:34:45 +00:00
#TODO: implement this for completed tab
# clearCompleted: ->
# _.each @options.habits.doneTodos(), (todo) ->
# todo.destroy()
# @render()
# return false
2012-06-26 15:31:58 +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-06-09 18:25:10 +00:00
when ' daily ' , ' 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 )
#TODO bug where I have to delete from _users.tasks AND _{type}List,
# fix when query subscriptions implemented properly
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-06-29 13:45:50 +00:00
exports.toggleTaskEdit = (e, el) ->
2012-06-29 08:33:09 +00:00
task = model . at $ ( el ) . parents ( ' li ' ) [ 0 ]
$ ( ' # \\ ' + task . get ( ' id ' ) + ' -chart ' ) . hide ( )
$ ( ' # \\ ' + task . get ( ' id ' ) + ' -edit ' ) . toggle ( )
2012-07-07 02:46:22 +00:00
exports.toggleChart = (e, el) ->
hideSelector = $ ( el ) . attr ( ' data-hide-selector ' )
chartSelector = $ ( el ) . attr ( ' data-chart-selector ' )
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
else if type == ' reroll '
2012-07-09 01:35:32 +00:00
for taskId of user . get ( ' tasks ' )
task = model . at ( ' _user.tasks. ' + taskId )
task . set ( ' value ' , 0 ) unless task . get ( ' type ' ) == ' reward '
2012-07-09 02:23:57 +00:00
# Setter for user.stats: handles death, leveling up, etc
exports.updateStats = updateStats = (user, stats) ->
if stats . hp ?
# game over
if stats . hp < 0
2012-07-09 03:29:54 +00:00
user . set ' stats.lvl ' , 0 # this signifies dead
2012-07-09 02:23:57 +00:00
else
user . set ' stats.hp ' , stats . hp
if stats . exp ?
# level up & carry-over exp
tnl = model . get ' _tnl '
if stats . exp >= tnl
stats . exp -= tnl
user . set ' stats.lvl ' , user . get ( ' stats.lvl ' ) + 1
if ! user . get ( ' items.itemsEnabled ' ) and stats . exp >= 50
user . set ' items.itemsEnabled ' , true
$ ( ' ul.items ' ) . popover
title: content . items . unlockedMessage . title
placement: ' left '
trigger: ' manual '
html: true
content: " <div class= ' item-store-popover ' > \
< img src = ' /img/BrowserQuest/chest.png ' / > \
#{content.items.unlockedMessage.content} <a href='#' onClick=\"$('ul.items').popover('hide');return false;\">[Close]</a>\
< / div > "
$ ( ' ul.items ' ) . popover ' show '
user . set ' stats.exp ' , stats . exp
if stats . money ?
money = 0.0 if ( ! money ? or money < 0 )
user . set ' stats.money ' , stats . money
# Calculates Exp modification based on weapon & lvl
expModifier = (value) ->
user = model . at ' _user '
dmg = user . get ( ' items.weapon ' ) * . 03 # each new weapon adds an additional 3% experience
dmg += user . get ( ' stats.lvl ' ) * . 03 # same for lvls
modified = value + ( value * dmg )
return modified
# Calculates HP-loss modification based on armor & lvl
hpModifier = (value) ->
user = model . at ' _user '
ac = user . get ( ' items.armor ' ) * . 03 # each new armor blocks an additional 3% damage
ac += user . get ( ' stats.lvl ' ) * . 03 # same for lvls
modified = value - ( value * ac )
return modified
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-06-20 20:40:59 +00:00
#TODO this should be model.at(el), shouldn't have to find parent
task = model . at $ ( el ) . parents ( ' li ' ) [ 0 ]
user = model . at ' _user '
2012-06-10 00:41:21 +00:00
# For negative values, use a line: something like y=-.1x+1
# For positibe values, taper off with inverse log: y=.9^x
# Would love to use inverse log for the whole thing, but after 13 fails it hits infinity
sign = if ( direction == " up " ) then 1 else - 1
2012-06-26 15:02:29 +00:00
value = task . get ( ' value ' )
2012-06-10 00:41:21 +00:00
delta = 0
2012-06-26 15:02:29 +00:00
if value < 0
delta = ( ( - 0.1 * value + 1 ) * sign )
2012-06-10 00:41:21 +00:00
else
2012-06-26 15:02:29 +00:00
delta = ( ( Math . pow ( 0.9 , value ) ) * sign )
2012-06-20 21:43:23 +00:00
2012-06-26 15:02:29 +00:00
# Don't adjust values for rewards, or for habits that don't have both + and -
adjustvalue = ( task . get ( ' type ' ) != ' reward ' )
2012-06-10 00:41:21 +00:00
if ( task . get ( ' type ' ) == ' habit ' ) and ( task . get ( " up " ) == false or task . get ( " down " ) == false )
2012-06-26 15:02:29 +00:00
adjustvalue = false
value += delta if adjustvalue
2012-06-20 21:43:23 +00:00
2012-06-26 14:55:24 +00:00
# up/down -voting as checkbox & assigning as completed, 2 birds one stone
completed = task . get ( " completed " )
2012-06-10 01:16:11 +00:00
if task . get ( ' type ' ) != ' habit '
2012-06-26 14:55:24 +00:00
completed = true if direction == " up "
completed = false if direction == " down "
2012-07-06 20:30:18 +00:00
else
# Add habit value to habit-history (if different)
task . push ' history ' , { date: new Date ( ) , value: value } if task . get ( ' value ' ) != value
2012-06-26 15:02:29 +00:00
task . set ( ' value ' , value )
2012-06-26 14:55:24 +00:00
task . set ( ' completed ' , completed )
2012-06-20 21:43:23 +00:00
2012-06-10 00:41:21 +00:00
# Update the user's status
2012-07-08 20:59:07 +00:00
[ money , hp , exp , lvl ] = [ user . get ( ' stats.money ' ) , user . get ( ' stats.hp ' ) , user . get ( ' stats.exp ' ) , user . get ( ' stats.lvl ' ) ]
2012-06-10 00:41:21 +00:00
if task . get ( ' type ' ) == ' reward '
# purchase item
2012-06-26 15:02:29 +00:00
money -= task . get ( ' value ' )
2012-06-10 00:41:21 +00:00
# if too expensive, reduce health & zero money
if money < 0
hp += money # hp - money difference
money = 0
# If positive delta, add points to exp & money
# Only take away mony if it was a mistake (aka, a checkbox)
if delta > 0 or ( task . get ( ' type ' ) == ' daily ' or task . get ( ' type ' ) == ' todo ' )
2012-07-09 02:23:57 +00:00
exp += expModifier ( delta )
2012-06-10 00:41:21 +00:00
money += delta
# Deduct from health (rewards case handled above)
else if task . get ( ' type ' ) != ' reward '
2012-07-09 02:23:57 +00:00
hp += hpModifier ( delta )
2012-07-08 21:28:10 +00:00
updateStats ( user , { hp: hp , exp: exp , money: money } )
2012-06-28 22:11:13 +00:00
2012-07-09 02:23:57 +00:00
# Note: Set 12am daily cron for this
# At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
# For incomplete Dailys, deduct experience
#TODO: remove from exports when cron implemented
exports.endOfDayTally = endOfDayTally = (e, el) ->
# users = model.at('users') #TODO this isn't working, iterate over all users
# for user in users
user = model . at ' _user '
todoTally = 0
for key of model . get ' _user.tasks '
task = model . at " _user.tasks. #{ key } "
[ type , value , completed ] = [ task . get ( ' type ' ) , task . get ( ' value ' ) , task . get ( ' completed ' ) ]
if type == ' todo ' or type == ' daily '
unless completed
value += if ( value < 0 ) then ( ( - 0.1 * value + 1 ) * - 1 ) else ( ( Math . pow ( 0.9 , value ) ) * - 1 )
task . set ( ' value ' , value )
# Deduct experience for missed Daily tasks,
# but not for Todos (just increase todo's value)
if ( type == ' daily ' )
hp = user . get ( ' stats.hp ' ) + hpModifier ( value )
updateStats user , { hp: hp }
if type == ' daily '
task . push " history " , { date: new Date ( ) , value: value }
else
absVal = if ( completed ) then Math . abs ( value ) else value
todoTally += absVal
task . set ( ' completed ' , false ) if type == ' daily '
model . push ' _user.history.todos ' , { date: new Date ( ) , value: todoTally }
# tally experience
expTally = user . get ' stats.exp '
lvl = 0 #iterator
2012-07-16 23:01:18 +00:00
while lvl < ( user . get ( ' stats.lvl ' ) - 1 )
2012-07-09 02:23:57 +00:00
lvl ++
expTally += 50 * Math . pow ( lvl , 2 ) - 150 * lvl + 200
model . push ' _user.history.exp ' , { date: new Date ( ) , value: expTally }
#TODO: remove when cron implemented
2012-07-11 19:58:17 +00:00
exports.poormanscron = poormanscron = ->
2012-07-11 19:24:23 +00:00
model . setNull ( ' _user.lastCron ' , new Date ( ) )
2012-07-13 14:43:53 +00:00
lastCron = new Date ( ( new Date ( model . get ( ' _user.lastCron ' ) ) ) . toDateString ( ) ) # calculate as midnight
today = new Date ( ( new Date ) . toDateString ( ) ) # calculate as midnight
2012-07-11 19:26:27 +00:00
DAY = 1000 * 60 * 60 * 24
2012-07-11 18:37:47 +00:00
daysPassed = Math . floor ( ( today . getTime ( ) - lastCron . getTime ( ) ) / DAY )
2012-07-09 02:23:57 +00:00
if daysPassed > 0
2012-07-11 19:13:08 +00:00
model . set ( ' _user.lastCron ' , today ) # reset cron
2012-07-16 23:01:18 +00:00
for n in [ 1 . . daysPassed ]
console . log { today: today , lastCron: lastCron , daysPassed: daysPassed , n : n } , " [debug] Cron ( #{ today } , #{ n } ) "
endOfDayTally ( )
2012-07-11 14:17:47 +00:00
poormanscron ( ) # Run once on refresh
setInterval ( -> # Then run once every hour
poormanscron ( )
) , 3600000
2012-07-09 03:29:54 +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-06-26 15:44:10 +00:00
## SHORTCUTS ##
2012-04-27 17:04:44 +00:00
exports.shortcuts = (e) ->
return unless e . metaKey || e . ctrlKey
code = e . which
return unless command = ( switch code
when 66 then ' bold ' # Bold: Ctrl/Cmd + B
when 73 then ' italic ' # Italic: Ctrl/Cmd + I
when 32 then ' removeFormat ' # Clear formatting: Ctrl/Cmd + Space
when 220 then ' removeFormat ' # Clear formatting: Ctrl/Cmd + \
else null
)
document . execCommand command , false , null
e . preventDefault ( ) if e . preventDefault
return false
# Tell Firefox to use elements for styles instead of CSS
# See: https://developer.mozilla.org/en/Rich-Text_Editing_in_Mozilla
document . execCommand ' useCSS ' , false , true
document . execCommand ' styleWithCSS ' , false , false