2012-09-24 20:19:27 +00:00
|
|
|
async = require 'async'
|
2012-09-24 16:52:44 +00:00
|
|
|
moment = require 'moment'
|
2013-01-21 00:35:31 +00:00
|
|
|
_ = require 'underscore'
|
2012-09-24 16:52:44 +00:00
|
|
|
content = require './content'
|
|
|
|
|
helpers = require './helpers'
|
2013-01-20 21:52:56 +00:00
|
|
|
browser = require './browser'
|
2013-02-01 05:02:19 +00:00
|
|
|
schema = require './schema'
|
|
|
|
|
MODIFIER = .03 # each new level, armor, weapon add 3% modifier (this number may change)
|
2012-09-24 02:01:41 +00:00
|
|
|
user = undefined
|
2012-09-24 12:48:05 +00:00
|
|
|
model = undefined
|
2012-07-21 01:39:29 +00:00
|
|
|
|
2012-09-20 14:56:36 +00:00
|
|
|
# This is required by all the functions, make sure it's set before anythign else is called
|
2012-09-24 12:48:05 +00:00
|
|
|
setModel = (m) ->
|
|
|
|
|
model = m
|
|
|
|
|
user = model.at('_user')
|
2013-01-21 15:00:08 +00:00
|
|
|
|
2013-01-20 21:52:56 +00:00
|
|
|
###
|
2013-01-20 19:40:44 +00:00
|
|
|
Calculates Exp & GP modification based on weapon & lvl
|
|
|
|
|
{value} task.value for gain
|
|
|
|
|
{modifiers} may manually pass in stats as {weapon, exp}. This is used for testing
|
2012-09-26 03:51:15 +00:00
|
|
|
###
|
2012-10-11 16:41:31 +00:00
|
|
|
expModifier = (value, modifiers = {}) ->
|
|
|
|
|
weapon = modifiers.weapon || user.get('items.weapon')
|
|
|
|
|
lvl = modifiers.lvl || user.get('stats.lvl')
|
2012-09-26 03:51:15 +00:00
|
|
|
dmg = weapon * MODIFIER # each new weapon increases exp gain
|
|
|
|
|
dmg += (lvl-1) * MODIFIER # same for lvls
|
2012-07-21 01:39:29 +00:00
|
|
|
modified = value + (value * dmg)
|
|
|
|
|
return modified
|
|
|
|
|
|
2012-09-26 03:51:15 +00:00
|
|
|
###
|
2013-01-20 19:40:44 +00:00
|
|
|
Calculates HP-loss modification based on armor & lvl
|
|
|
|
|
{value} task.value which is hurting us
|
|
|
|
|
{modifiers} may manually pass in modifier as {armor, lvl}. This is used for testing
|
2012-09-26 03:51:15 +00:00
|
|
|
###
|
2012-10-11 16:41:31 +00:00
|
|
|
hpModifier = (value, modifiers = {}) ->
|
|
|
|
|
armor = modifiers.armor || user.get('items.armor')
|
|
|
|
|
lvl = modifiers.lvl || user.get('stats.lvl')
|
2012-09-26 03:51:15 +00:00
|
|
|
ac = armor * MODIFIER # each new armor decreases HP loss
|
|
|
|
|
ac += (lvl-1) * MODIFIER # same for lvls
|
2012-07-21 01:39:29 +00:00
|
|
|
modified = value - (value * ac)
|
|
|
|
|
return modified
|
|
|
|
|
|
2012-09-26 03:51:15 +00:00
|
|
|
###
|
2013-01-20 19:40:44 +00:00
|
|
|
Calculates the next task.value based on direction
|
|
|
|
|
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. Revisit this formula later
|
|
|
|
|
{currentValue} the current value of the task, determines it's next value
|
|
|
|
|
{direction} 'up' or 'down'
|
2012-09-26 03:51:15 +00:00
|
|
|
###
|
|
|
|
|
taskDeltaFormula = (currentValue, direction) ->
|
|
|
|
|
sign = if (direction == "up") then 1 else -1
|
|
|
|
|
delta = if (currentValue < 0) then (( -0.1 * currentValue + 1 ) * sign) else (( Math.pow(0.9,currentValue) ) * sign)
|
2012-09-26 04:19:19 +00:00
|
|
|
return delta
|
2013-01-20 19:40:44 +00:00
|
|
|
|
|
|
|
|
###
|
|
|
|
|
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
|
|
|
|
|
###
|
2013-02-01 05:39:42 +00:00
|
|
|
updateStats = (newStats, batch) ->
|
2013-02-04 05:50:02 +00:00
|
|
|
obj = batch.obj()
|
2013-01-20 19:40:44 +00:00
|
|
|
|
2012-09-20 14:56:36 +00:00
|
|
|
# if user is dead, dont do anything
|
2013-02-04 05:50:02 +00:00
|
|
|
return if obj.stats.lvl == 0
|
|
|
|
|
|
2013-01-20 19:40:44 +00:00
|
|
|
if newStats.hp?
|
|
|
|
|
# Game Over
|
|
|
|
|
if newStats.hp <= 0
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.stats.lvl = 0 # signifies dead
|
|
|
|
|
obj.stats.hp = 0
|
2012-09-20 14:56:36 +00:00
|
|
|
return
|
|
|
|
|
else
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.stats.hp = newStats.hp
|
2013-01-21 04:21:50 +00:00
|
|
|
|
2013-01-20 19:40:44 +00:00
|
|
|
if newStats.exp?
|
2012-07-21 01:39:29 +00:00
|
|
|
# level up & carry-over exp
|
|
|
|
|
tnl = user.get '_tnl'
|
2013-01-20 19:40:44 +00:00
|
|
|
if newStats.exp >= tnl
|
|
|
|
|
newStats.exp -= tnl
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.stats.lvl++
|
|
|
|
|
obj.stats.hp = 50
|
|
|
|
|
if !obj.items.itemsEnabled and obj.stats.lvl >= 2
|
|
|
|
|
obj.items.itemsEnabled = true #bit of trouble using userSet here
|
|
|
|
|
if !obj.flags.partyEnabled and obj.stats.lvl >= 3
|
|
|
|
|
obj.flags.partyEnabled = true
|
|
|
|
|
obj.stats.exp = newStats.exp
|
2013-01-21 00:35:31 +00:00
|
|
|
|
2013-01-20 19:40:44 +00:00
|
|
|
if newStats.money?
|
2013-02-02 19:40:24 +00:00
|
|
|
#FIXME what was I doing here? I can't remember, money isn't defined
|
2012-07-21 01:39:29 +00:00
|
|
|
money = 0.0 if (!money? or money<0)
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.stats.money = newStats.money
|
2013-01-21 00:35:31 +00:00
|
|
|
|
2012-09-24 16:52:44 +00:00
|
|
|
# {taskId} task you want to score
|
|
|
|
|
# {direction} 'up' or 'down'
|
2013-01-20 20:39:47 +00:00
|
|
|
# {times} # times to call score on this task (1 unless cron, usually)
|
|
|
|
|
# {update} if we're running updates en-mass (eg, cron on server) pass in userObj
|
2013-02-01 18:49:18 +00:00
|
|
|
score = (taskId, direction, times, batch, cron) ->
|
2013-02-01 18:22:00 +00:00
|
|
|
commit = false
|
2013-02-02 18:40:26 +00:00
|
|
|
unless batch?
|
2013-02-01 18:22:00 +00:00
|
|
|
commit = true
|
|
|
|
|
batch = new schema.BatchUpdate(model)
|
2013-02-02 18:40:26 +00:00
|
|
|
batch.startTransaction()
|
2013-02-04 05:50:02 +00:00
|
|
|
obj = batch.obj()
|
2013-02-01 05:39:42 +00:00
|
|
|
|
2013-02-04 05:50:02 +00:00
|
|
|
{money, hp, exp, lvl} = obj.stats
|
2013-01-20 22:30:51 +00:00
|
|
|
|
2013-02-04 05:50:02 +00:00
|
|
|
taskObj = obj.tasks[taskId]
|
2012-09-24 20:19:27 +00:00
|
|
|
{type, value} = taskObj
|
2012-10-25 00:56:00 +00:00
|
|
|
|
2013-01-20 22:30:51 +00:00
|
|
|
delta = 0
|
2013-02-04 05:50:02 +00:00
|
|
|
times ?= 1
|
2013-01-20 22:30:51 +00:00
|
|
|
calculateDelta = (adjustvalue=true) ->
|
|
|
|
|
# If multiple days have passed, multiply times days missed
|
|
|
|
|
_.times times, (n) ->
|
|
|
|
|
# Each iteration calculate the delta (nextDelta), which is then accumulated in delta
|
|
|
|
|
# (aka, the total delta). This weirdness won't be necessary when calculating mathematically
|
|
|
|
|
# rather than iteratively
|
|
|
|
|
nextDelta = taskDeltaFormula(value, direction)
|
|
|
|
|
value += nextDelta if adjustvalue
|
|
|
|
|
delta += nextDelta
|
2012-07-21 01:39:29 +00:00
|
|
|
|
2013-01-21 01:53:27 +00:00
|
|
|
addPoints = ->
|
2012-09-20 14:56:36 +00:00
|
|
|
modified = expModifier(delta)
|
2012-09-01 17:59:34 +00:00
|
|
|
exp += modified
|
|
|
|
|
money += modified
|
2013-01-20 22:30:51 +00:00
|
|
|
|
2013-01-21 01:53:27 +00:00
|
|
|
subtractPoints = ->
|
2012-09-20 14:56:36 +00:00
|
|
|
modified = hpModifier(delta)
|
2012-09-01 17:59:34 +00:00
|
|
|
hp += modified
|
2012-07-21 01:39:29 +00:00
|
|
|
|
2013-01-20 22:30:51 +00:00
|
|
|
switch type
|
|
|
|
|
when 'habit'
|
|
|
|
|
# Don't adjust values for habits that don't have both + and -
|
|
|
|
|
adjustvalue = if (taskObj.up==false or taskObj.down==false) then false else true
|
|
|
|
|
calculateDelta(adjustvalue)
|
|
|
|
|
# Add habit value to habit-history (if different)
|
2013-02-01 16:17:02 +00:00
|
|
|
historyEntry = { date: +new Date, value: value }
|
2013-01-20 22:30:51 +00:00
|
|
|
if (delta > 0) then addPoints() else subtractPoints()
|
2013-02-01 16:17:02 +00:00
|
|
|
taskObj.history ?= []
|
2013-02-04 05:50:02 +00:00
|
|
|
taskObj.history.push historyEntry if taskObj.value != value
|
2013-01-20 22:30:51 +00:00
|
|
|
|
2013-01-21 01:53:27 +00:00
|
|
|
when 'daily'
|
2013-01-20 22:30:51 +00:00
|
|
|
calculateDelta()
|
2013-02-01 18:49:18 +00:00
|
|
|
if cron? # cron
|
2013-01-20 22:30:51 +00:00
|
|
|
subtractPoints()
|
2013-02-04 05:50:02 +00:00
|
|
|
taskObj.history ?= []
|
|
|
|
|
taskObj.history.push { date: +new Date, value: value }
|
|
|
|
|
taskObj.completed = false
|
2013-01-21 01:53:27 +00:00
|
|
|
else
|
|
|
|
|
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
|
|
|
|
|
|
|
|
|
when 'todo'
|
|
|
|
|
calculateDelta()
|
2013-02-01 18:49:18 +00:00
|
|
|
unless cron? # don't touch stats on cron
|
2013-01-21 01:53:27 +00:00
|
|
|
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
2013-01-20 22:30:51 +00:00
|
|
|
|
|
|
|
|
when 'reward'
|
|
|
|
|
# Don't adjust values for rewards
|
|
|
|
|
calculateDelta(false)
|
|
|
|
|
# purchase item
|
|
|
|
|
money -= taskObj.value
|
|
|
|
|
num = parseFloat(taskObj.value).toFixed(2)
|
|
|
|
|
# if too expensive, reduce health & zero money
|
|
|
|
|
if money < 0
|
|
|
|
|
hp += money # hp - money difference
|
|
|
|
|
money = 0
|
|
|
|
|
|
2013-02-04 05:50:02 +00:00
|
|
|
taskObj.value = value
|
|
|
|
|
batch.set "tasks.#{taskId}", taskObj
|
2013-02-01 05:39:42 +00:00
|
|
|
updateStats {hp: hp, exp: exp, money: money}, batch
|
2013-02-04 05:50:02 +00:00
|
|
|
if commit
|
|
|
|
|
batch.setStats()
|
|
|
|
|
batch.commit()
|
2013-01-21 01:53:27 +00:00
|
|
|
return delta
|
2013-01-20 22:30:51 +00:00
|
|
|
|
2013-01-21 21:42:23 +00:00
|
|
|
###
|
|
|
|
|
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
|
|
|
|
For incomplete Dailys, deduct experience
|
|
|
|
|
###
|
2013-01-30 18:33:36 +00:00
|
|
|
cron = (resetDom_cb) ->
|
2013-01-29 02:28:45 +00:00
|
|
|
today = +new Date
|
|
|
|
|
daysPassed = helpers.daysBetween(today, user.get('lastCron'))
|
2012-09-24 02:01:41 +00:00
|
|
|
if daysPassed > 0
|
2013-02-01 05:02:19 +00:00
|
|
|
batch = new schema.BatchUpdate(model)
|
2013-02-02 18:59:05 +00:00
|
|
|
batch.startTransaction()
|
|
|
|
|
batch.set 'lastCron', today
|
2013-02-04 05:50:02 +00:00
|
|
|
obj = batch.obj()
|
|
|
|
|
hpBefore = obj.stats.hp #we'll use this later so we can animate hp loss
|
2013-01-20 22:30:51 +00:00
|
|
|
# Tally each task
|
2013-01-21 05:23:48 +00:00
|
|
|
todoTally = 0
|
2013-02-04 05:50:02 +00:00
|
|
|
_.each obj.tasks, (taskObj) ->
|
|
|
|
|
unless taskObj.id?
|
|
|
|
|
console.error "a task had a null id during cron, this should not be happening"
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
{id, type, completed, repeat} = taskObj
|
|
|
|
|
if type in ['todo', 'daily']
|
|
|
|
|
# Deduct experience for missed Daily tasks,
|
|
|
|
|
# but not for Todos (just increase todo's value)
|
|
|
|
|
unless completed
|
|
|
|
|
# for todos & typical dailies, these are equivalent
|
|
|
|
|
daysFailed = daysPassed
|
|
|
|
|
# however, for dailys which have repeat dates, need
|
|
|
|
|
# to calculate how many they've missed according to their own schedule
|
|
|
|
|
if type=='daily' && repeat
|
|
|
|
|
daysFailed = 0
|
|
|
|
|
_.times daysPassed, (n) ->
|
|
|
|
|
thatDay = moment().subtract('days', n+1)
|
|
|
|
|
if repeat[helpers.dayMapping[thatDay.day()]]==true
|
|
|
|
|
daysFailed++
|
|
|
|
|
score id, 'down', daysFailed, batch, true
|
|
|
|
|
|
|
|
|
|
if type == 'todo'
|
|
|
|
|
value = obj.tasks[taskObj.id].value #get updated value
|
|
|
|
|
absVal = if (completed) then Math.abs(value) else value
|
|
|
|
|
todoTally += absVal
|
2013-01-21 00:35:31 +00:00
|
|
|
|
|
|
|
|
# Finished tallying
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.history ?= {}; obj.history.todos ?= []; obj.history.exp ?= []
|
|
|
|
|
obj.history.todos.push { date: today, value: todoTally }
|
2013-01-20 22:30:51 +00:00
|
|
|
# tally experience
|
2013-02-04 05:50:02 +00:00
|
|
|
expTally = obj.stats.exp
|
2013-01-20 22:30:51 +00:00
|
|
|
lvl = 0 #iterator
|
2013-02-04 05:50:02 +00:00
|
|
|
while lvl < (obj.stats.lvl-1)
|
2013-01-20 22:30:51 +00:00
|
|
|
lvl++
|
|
|
|
|
expTally += (lvl*100)/5
|
2013-02-04 05:50:02 +00:00
|
|
|
obj.history.exp.push { date: today, value: expTally }
|
2013-01-29 02:28:45 +00:00
|
|
|
|
|
|
|
|
# Set the new user specs, and animate HP loss
|
2013-02-04 05:50:02 +00:00
|
|
|
[hpAfter, obj.stats.hp] = [obj.stats.hp, hpBefore]
|
|
|
|
|
batch.setStats()
|
|
|
|
|
batch.set('history', obj.history)
|
2013-02-01 05:02:19 +00:00
|
|
|
batch.commit()
|
2013-01-30 18:33:36 +00:00
|
|
|
resetDom_cb(model)
|
2013-01-29 02:28:45 +00:00
|
|
|
setTimeout (-> user.set 'stats.hp', hpAfter), 1000 # animate hp loss
|
|
|
|
|
|
2012-09-24 02:01:41 +00:00
|
|
|
|
|
|
|
|
module.exports = {
|
2012-09-24 12:48:05 +00:00
|
|
|
setModel: setModel
|
2012-09-26 03:51:15 +00:00
|
|
|
MODIFIER: MODIFIER
|
2012-09-24 02:01:41 +00:00
|
|
|
score: score
|
|
|
|
|
cron: cron
|
2013-01-21 00:35:31 +00:00
|
|
|
|
2012-09-26 03:51:15 +00:00
|
|
|
# testing stuff
|
|
|
|
|
expModifier: expModifier
|
|
|
|
|
hpModifier: hpModifier
|
|
|
|
|
taskDeltaFormula: taskDeltaFormula
|
2013-01-18 05:55:04 +00:00
|
|
|
}
|