mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-13 07:18:38 +00:00
Merge and fix tests
This commit is contained in:
commit
822f779812
8 changed files with 308 additions and 327 deletions
|
|
@ -45,13 +45,13 @@ get '/', (page, model, params, next) ->
|
|||
|
||||
ready (model) ->
|
||||
user = model.at('_user')
|
||||
scoring.setModel(model)
|
||||
score = new scoring.Scoring(model)
|
||||
|
||||
#set cron immediately
|
||||
lastCron = user.get('lastCron')
|
||||
user.set('lastCron', +new Date) if (!lastCron? or lastCron == 'new')
|
||||
|
||||
scoring.cron()
|
||||
score.cron()
|
||||
|
||||
character.app(exports, model)
|
||||
tasks.app(exports, model)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,15 @@ partyUnsubscribe = (model, cb) ->
|
|||
1) If the user is solo, just subscribe to the user.
|
||||
2) If in a an empty party, just subscribe to the user & party meta.
|
||||
3) If full party, subscribe to everything.
|
||||
|
||||
Note a strange hack - we subscribe to queries incrementally. First self, then party, then party members.
|
||||
Party members come with limited fields, so you can't hack their stuff. Strangely, subscribing to the members after
|
||||
already subscribing to self limits self's fields to the fields which members are limited to. As a result, we have
|
||||
to re-subscribe to self to get all the fields (otherwise everything breaks). Weirdly, this last subscription doesn't
|
||||
do the opposite - granting all the fields back to members. I dont' know what's going on here
|
||||
|
||||
Another issue: `model.unsubscribe(selfQ)` would seem to mitigate the above, so we at least don't have a stray
|
||||
subscription floating around - but alas, it doesn't seem to work (or at least never calls the callback)
|
||||
###
|
||||
module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb) ->
|
||||
|
||||
|
|
@ -23,65 +32,41 @@ module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb)
|
|||
# partyUnsubscribe model, ->
|
||||
|
||||
# Restart subscription to the main user
|
||||
selfQ = model.query('users').withId model.get('_userId') #or model.session.userId # see http://goo.gl/TPYIt
|
||||
selfQ.subscribe (err, self) ->
|
||||
selfQ = model.query('users').withId (model.get('_userId') or model.session.userId) # see http://goo.gl/TPYIt
|
||||
selfQ.fetch (err, user) ->
|
||||
return next(err) if err
|
||||
u = self.at(0)
|
||||
uObj = u.get()
|
||||
return next("User not found - this shouldn't be happening!") unless user.get()
|
||||
|
||||
finished = (descriptors, paths) ->
|
||||
model.subscribe.apply model, descriptors.concat ->
|
||||
[err, refs] = [arguments[0], arguments]
|
||||
return next(err) if err
|
||||
_.each paths, (path, idx) -> model.ref path, refs[idx+1]
|
||||
cb()
|
||||
|
||||
|
||||
# Attempted handling for 'party of undefined' error, which is caused by bustedSession (see derby-auth).
|
||||
# Theoretically simply reloading the page should restore model.at('_userId') and the second load should work just fine
|
||||
# bustedSession victims might hit a redirection loop if I'm wrong :/
|
||||
return page.redirect('/') unless uObj
|
||||
# return page.redirect('/') unless uObj
|
||||
|
||||
## (1) User is solo, just return that subscription
|
||||
unless uObj.party?.current?
|
||||
model.ref '_user', u
|
||||
return cb()
|
||||
partyId = user.get('party.current')
|
||||
|
||||
###
|
||||
Note this strange hack - we subscribe to queries incrementally. First self, then party, then party members.
|
||||
Party members come with limited fields, so you can't hack their stuff. Strangely, subscribing to the members after
|
||||
already subscribing to self limits self's fields to the fields which members are limited to. As a result, we have
|
||||
to re-subscribe to self to get all the fields (otherwise everything breaks). Weirdly, this last subscription doesn't
|
||||
do the opposite - granting all the fields back to members. I dont' know what's going on here
|
||||
|
||||
Another issue: `model.unsubscribe(selfQ)` would seem to mitigate the above, so we at least don't have a stray
|
||||
subscription floating around - but alas, it doesn't seem to work (or at least never calls the callback)
|
||||
###
|
||||
finished = ->
|
||||
# model.unsubscribe selfQ, ->
|
||||
selfQ.subscribe (err, self) ->
|
||||
return next(err) if err
|
||||
model.ref '_user', self.at(0)
|
||||
cb()
|
||||
# (1) Solo player
|
||||
return finished([selfQ], ['_user']) unless partyId
|
||||
|
||||
# User in a party
|
||||
partiesQ = model.query('parties').withId(uObj.party.current)
|
||||
partiesQ.fetch (err, res) ->
|
||||
partyQ = model.query('parties').withId(partyId)
|
||||
partyQ.fetch (err, party) ->
|
||||
return next(err) if err
|
||||
p = res.at(0)
|
||||
model.ref '_party', p
|
||||
ids = p.get('members')
|
||||
|
||||
# FIXME this is the kicker right here. This isn't getting triggered, and it's the reason why we have to refresh
|
||||
# after every event. Get this working
|
||||
#p.on '*', 'members', (ids) ->
|
||||
# console.log("members listener got called")
|
||||
# debugger
|
||||
# membersSubscribe model, ids
|
||||
members = party.get('members')
|
||||
|
||||
## (2) Party has no members, just subscribe to the party itself
|
||||
if _.isEmpty(ids)
|
||||
return finished()
|
||||
return finished([partyQ, selfQ], ['_party', '_user']) if _.isEmpty(members)
|
||||
|
||||
else
|
||||
## (3) Party has members, subscribe to those users too
|
||||
membersQ = model.query('users').party(ids)
|
||||
membersQ.fetch (err, members) ->
|
||||
return next(err) if err
|
||||
model.ref '_partyMembers', members
|
||||
finished()
|
||||
## (3) Party has members, subscribe to those users too
|
||||
membersQ = model.query('users').party(members)
|
||||
return finished [partyQ, membersQ, selfQ], ['_party', '_partyMembers', '_user']
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
|
|
@ -121,7 +106,7 @@ module.exports.app = (appExports, model) ->
|
|||
query = model.query('users').party([id])
|
||||
model.fetch query, (err, res) ->
|
||||
throw err if err
|
||||
u = res.at(0).get()
|
||||
u = res.get()
|
||||
if !u?.id?
|
||||
model.set "_view.partyError", "User with id #{id} not found."
|
||||
return
|
||||
|
|
@ -143,9 +128,9 @@ module.exports.app = (appExports, model) ->
|
|||
user.set 'party.current', partyId
|
||||
# model.push "parties.#{partyId}.members", user.get('id'), -> #FIXME why this not working?
|
||||
model.query('parties').withId(partyId).fetch (err, p) ->
|
||||
members = p.at(0).get('members')
|
||||
members = p.get('members')
|
||||
members.push user.get('id')
|
||||
p.at(0).set 'members', members, ->
|
||||
p.set 'members', members, ->
|
||||
window.location.reload true
|
||||
# partySubscribe model, ->
|
||||
# p = model.at('_party')
|
||||
|
|
@ -174,5 +159,5 @@ module.exports.app = (appExports, model) ->
|
|||
# partyUnsubscribe model, ->
|
||||
# selfQ = model.query('users').withId model.get('_userId') #or model.session.userId # see http://goo.gl/TPYIt
|
||||
# selfQ.subscribe (err, u) ->
|
||||
# model.ref '_user', u.at(0)
|
||||
# model.ref '_user', u
|
||||
# browser.resetDom model
|
||||
|
|
@ -5,273 +5,269 @@ helpers = require './helpers'
|
|||
browser = require './browser'
|
||||
character = require './character'
|
||||
items = require './items'
|
||||
MODIFIER = .02 # each new level, armor, weapon add 2% modifier (this number may change)
|
||||
user = undefined
|
||||
model = undefined
|
||||
|
||||
# This is required by all the functions, make sure it's set before anythign else is called
|
||||
setModel = (m) ->
|
||||
model = m
|
||||
user = model.at('_user')
|
||||
module.exports.Scoring = (model) ->
|
||||
|
||||
###
|
||||
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
|
||||
###
|
||||
expModifier = (value, modifiers = {}) ->
|
||||
weapon = modifiers.weapon || user.get('items.weapon')
|
||||
lvl = modifiers.lvl || user.get('stats.lvl')
|
||||
dmg = items.items.weapon[weapon].modifier # each new weapon increases exp gain
|
||||
dmg += (lvl-1) * MODIFIER # same for lvls
|
||||
modified = value + (value * dmg)
|
||||
return modified
|
||||
MODIFIER = .02 # each new level, armor, weapon add 2% modifier (this mechanism will change)
|
||||
user = model.at '_user'
|
||||
|
||||
###
|
||||
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
|
||||
###
|
||||
hpModifier = (value, modifiers = {}) ->
|
||||
armor = modifiers.armor || user.get('items.armor')
|
||||
head = modifiers.head || user.get('items.head')
|
||||
shield = modifiers.shield || user.get('items.shield')
|
||||
lvl = modifiers.lvl || user.get('stats.lvl')
|
||||
ac = items.items.armor[armor].modifier + items.items.head[head].modifier + items.items.shield[shield].modifier # each new armor decreases HP loss
|
||||
ac += (lvl-1) * MODIFIER # same for lvls
|
||||
modified = value - (value * ac)
|
||||
return modified
|
||||
|
||||
###
|
||||
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'
|
||||
###
|
||||
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)
|
||||
return delta
|
||||
###
|
||||
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
|
||||
###
|
||||
expModifier = (value, modifiers = {}) ->
|
||||
weapon = modifiers.weapon || user.get('items.weapon')
|
||||
lvl = modifiers.lvl || user.get('stats.lvl')
|
||||
dmg = items.items.weapon[weapon].modifier # each new weapon increases exp gain
|
||||
dmg += (lvl-1) * MODIFIER # same for lvls
|
||||
modified = value + (value * dmg)
|
||||
return modified
|
||||
|
||||
###
|
||||
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 = (newStats, batch) ->
|
||||
obj = batch.obj()
|
||||
###
|
||||
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
|
||||
###
|
||||
hpModifier = (value, modifiers = {}) ->
|
||||
armor = modifiers.armor || user.get('items.armor')
|
||||
head = modifiers.head || user.get('items.head')
|
||||
shield = modifiers.shield || user.get('items.shield')
|
||||
lvl = modifiers.lvl || user.get('stats.lvl')
|
||||
ac = items.items.armor[armor].modifier + items.items.head[head].modifier + items.items.shield[shield].modifier # each new armor decreases HP loss
|
||||
ac += (lvl-1) * MODIFIER # same for lvls
|
||||
modified = value - (value * ac)
|
||||
return modified
|
||||
|
||||
# if user is dead, dont do anything
|
||||
return if obj.stats.lvl == 0
|
||||
###
|
||||
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'
|
||||
###
|
||||
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)
|
||||
return delta
|
||||
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
obj.stats.lvl = 0 # signifies dead
|
||||
obj.stats.hp = 0
|
||||
return
|
||||
else
|
||||
obj.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
# level up & carry-over exp
|
||||
tnl = model.get '_tnl'
|
||||
if newStats.exp >= tnl
|
||||
newStats.exp -= tnl
|
||||
obj.stats.lvl++
|
||||
obj.stats.hp = 50
|
||||
|
||||
obj.stats.exp = newStats.exp
|
||||
|
||||
# Set flags when they unlock features
|
||||
if !obj.flags.customizationsNotification and (obj.stats.exp > 10 or obj.stats.lvl > 1)
|
||||
batch.set 'flags.customizationsNotification', true
|
||||
obj.flags.customizationsNotification = true
|
||||
if !obj.flags.itemsEnabled and obj.stats.lvl >= 2
|
||||
# Set to object, then also send to browser right away to get model.on() subscription notification
|
||||
batch.set 'flags.itemsEnabled', true
|
||||
obj.flags.itemsEnabled = true
|
||||
if !obj.flags.partyEnabled and obj.stats.lvl >= 3
|
||||
batch.set 'flags.partyEnabled', true
|
||||
obj.flags.partyEnabled = true
|
||||
if !obj.flags.petsEnabled and obj.stats.lvl >= 4
|
||||
batch.set 'flags.petsEnabled', true
|
||||
obj.flags.petsEnabled = true
|
||||
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp<0)
|
||||
obj.stats.gp = newStats.gp
|
||||
|
||||
# {taskId} task you want to score
|
||||
# {direction} 'up' or 'down'
|
||||
# {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
|
||||
score = (taskId, direction, times, batch, cron) ->
|
||||
|
||||
commit = false
|
||||
unless batch?
|
||||
commit = true
|
||||
batch = new character.BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
obj = batch.obj()
|
||||
|
||||
{gp, hp, exp, lvl} = obj.stats
|
||||
|
||||
taskPath = "tasks.#{taskId}"
|
||||
taskObj = obj.tasks[taskId]
|
||||
{type, value} = taskObj
|
||||
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
if taskObj.value > obj.stats.gp and taskObj.type is 'reward'
|
||||
r = confirm "Not enough GP to purchase this reward, buy anyway and lose HP? (Punishment for taking a reward you didn't earn)."
|
||||
unless r
|
||||
batch.commit()
|
||||
return
|
||||
|
||||
delta = 0
|
||||
times ?= 1
|
||||
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
|
||||
|
||||
addPoints = ->
|
||||
modified = expModifier(delta)
|
||||
exp += modified
|
||||
gp += modified
|
||||
|
||||
subtractPoints = ->
|
||||
modified = hpModifier(delta)
|
||||
hp += modified
|
||||
|
||||
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)
|
||||
if (delta > 0) then addPoints() else subtractPoints()
|
||||
taskObj.history ?= []
|
||||
if taskObj.value != value
|
||||
historyEntry = { date: +new Date, value: value }
|
||||
taskObj.history.push historyEntry
|
||||
batch.set "#{taskPath}.history", taskObj.history
|
||||
|
||||
when 'daily'
|
||||
calculateDelta()
|
||||
if cron? # cron
|
||||
subtractPoints()
|
||||
else
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
when 'todo'
|
||||
calculateDelta()
|
||||
unless cron? # don't touch stats on cron
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
when 'reward'
|
||||
# Don't adjust values for rewards
|
||||
calculateDelta(false)
|
||||
# purchase item
|
||||
gp -= Math.abs(taskObj.value)
|
||||
num = parseFloat(taskObj.value).toFixed(2)
|
||||
# if too expensive, reduce health & zero gp
|
||||
if gp < 0
|
||||
hp += gp # hp - gp difference
|
||||
gp = 0
|
||||
|
||||
taskObj.value = value
|
||||
batch.set "#{taskPath}.value", taskObj.value
|
||||
origStats = _.clone obj.stats
|
||||
updateStats {hp: hp, exp: exp, gp: gp}, batch
|
||||
if commit
|
||||
# newStats / origStats is a glorious hack to trick Derby into seeing the change in model.on(*)
|
||||
newStats = _.clone batch.obj().stats
|
||||
_.each Object.keys(origStats), (key) -> obj.stats[key] = origStats[key]
|
||||
batch.setStats(newStats)
|
||||
# batch.setStats()
|
||||
batch.commit()
|
||||
return delta
|
||||
|
||||
###
|
||||
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
||||
For incomplete Dailys, deduct experience
|
||||
###
|
||||
cron = () ->
|
||||
today = +new Date
|
||||
daysPassed = helpers.daysBetween(today, user.get('lastCron'))
|
||||
if daysPassed > 0
|
||||
batch = new character.BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
batch.set 'lastCron', today
|
||||
###
|
||||
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 = (newStats, batch) ->
|
||||
obj = batch.obj()
|
||||
hpBefore = obj.stats.hp #we'll use this later so we can animate hp loss
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
_.each obj.tasks, (taskObj) ->
|
||||
{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 == 'daily'
|
||||
taskObj.history ?= []
|
||||
taskObj.history.push { date: +new Date, value: value }
|
||||
batch.set "tasks.#{taskObj.id}.history", taskObj.history
|
||||
batch.set "tasks.#{taskObj.id}.completed", false
|
||||
# if user is dead, dont do anything
|
||||
return if obj.stats.lvl == 0
|
||||
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
obj.stats.lvl = 0 # signifies dead
|
||||
obj.stats.hp = 0
|
||||
return
|
||||
else
|
||||
obj.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
# level up & carry-over exp
|
||||
tnl = model.get '_tnl'
|
||||
if newStats.exp >= tnl
|
||||
newStats.exp -= tnl
|
||||
obj.stats.lvl++
|
||||
obj.stats.hp = 50
|
||||
|
||||
obj.stats.exp = newStats.exp
|
||||
|
||||
# Set flags when they unlock features
|
||||
if !obj.flags.customizationsNotification and (obj.stats.exp > 10 or obj.stats.lvl > 1)
|
||||
batch.set 'flags.customizationsNotification', true
|
||||
obj.flags.customizationsNotification = true
|
||||
if !obj.flags.itemsEnabled and obj.stats.lvl >= 2
|
||||
# Set to object, then also send to browser right away to get model.on() subscription notification
|
||||
batch.set 'flags.itemsEnabled', true
|
||||
obj.flags.itemsEnabled = true
|
||||
if !obj.flags.partyEnabled and obj.stats.lvl >= 3
|
||||
batch.set 'flags.partyEnabled', true
|
||||
obj.flags.partyEnabled = true
|
||||
if !obj.flags.petsEnabled and obj.stats.lvl >= 4
|
||||
batch.set 'flags.petsEnabled', true
|
||||
obj.flags.petsEnabled = true
|
||||
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp<0)
|
||||
obj.stats.gp = newStats.gp
|
||||
|
||||
# {taskId} task you want to score
|
||||
# {direction} 'up' or 'down'
|
||||
# {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
|
||||
score = (taskId, direction, times, batch, cron) ->
|
||||
|
||||
commit = false
|
||||
unless batch?
|
||||
commit = true
|
||||
batch = new character.BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
obj = batch.obj()
|
||||
|
||||
{gp, hp, exp, lvl} = obj.stats
|
||||
|
||||
taskPath = "tasks.#{taskId}"
|
||||
taskObj = obj.tasks[taskId]
|
||||
{type, value} = taskObj
|
||||
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
if taskObj.value > obj.stats.gp and taskObj.type is 'reward'
|
||||
r = confirm "Not enough GP to purchase this reward, buy anyway and lose HP? (Punishment for taking a reward you didn't earn)."
|
||||
unless r
|
||||
batch.commit()
|
||||
return
|
||||
|
||||
delta = 0
|
||||
times ?= 1
|
||||
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
|
||||
|
||||
addPoints = ->
|
||||
modified = expModifier(delta)
|
||||
exp += modified
|
||||
gp += modified
|
||||
|
||||
subtractPoints = ->
|
||||
modified = hpModifier(delta)
|
||||
hp += modified
|
||||
|
||||
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)
|
||||
if (delta > 0) then addPoints() else subtractPoints()
|
||||
taskObj.history ?= []
|
||||
if taskObj.value != value
|
||||
historyEntry = { date: +new Date, value: value }
|
||||
taskObj.history.push historyEntry
|
||||
batch.set "#{taskPath}.history", taskObj.history
|
||||
|
||||
when 'daily'
|
||||
calculateDelta()
|
||||
if cron? # cron
|
||||
subtractPoints()
|
||||
else
|
||||
value = obj.tasks[taskObj.id].value #get updated value
|
||||
absVal = if (completed) then Math.abs(value) else value
|
||||
todoTally += absVal
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
# Finished tallying
|
||||
obj.history ?= {}; obj.history.todos ?= []; obj.history.exp ?= []
|
||||
obj.history.todos.push { date: today, value: todoTally }
|
||||
# tally experience
|
||||
expTally = obj.stats.exp
|
||||
lvl = 0 #iterator
|
||||
while lvl < (obj.stats.lvl-1)
|
||||
lvl++
|
||||
expTally += (lvl*100)/5
|
||||
obj.history.exp.push { date: today, value: expTally }
|
||||
when 'todo'
|
||||
calculateDelta()
|
||||
unless cron? # don't touch stats on cron
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
# Set the new user specs, and animate HP loss
|
||||
[hpAfter, obj.stats.hp] = [obj.stats.hp, hpBefore]
|
||||
batch.setStats()
|
||||
batch.set('history', obj.history)
|
||||
batch.commit()
|
||||
browser.resetDom(model)
|
||||
setTimeout (-> user.set 'stats.hp', hpAfter), 1000 # animate hp loss
|
||||
when 'reward'
|
||||
# Don't adjust values for rewards
|
||||
calculateDelta(false)
|
||||
# purchase item
|
||||
gp -= Math.abs(taskObj.value)
|
||||
num = parseFloat(taskObj.value).toFixed(2)
|
||||
# if too expensive, reduce health & zero gp
|
||||
if gp < 0
|
||||
hp += gp # hp - gp difference
|
||||
gp = 0
|
||||
|
||||
taskObj.value = value
|
||||
batch.set "#{taskPath}.value", taskObj.value
|
||||
origStats = _.clone obj.stats
|
||||
updateStats {hp: hp, exp: exp, gp: gp}, batch
|
||||
if commit
|
||||
# newStats / origStats is a glorious hack to trick Derby into seeing the change in model.on(*)
|
||||
newStats = _.clone batch.obj().stats
|
||||
_.each Object.keys(origStats), (key) -> obj.stats[key] = origStats[key]
|
||||
batch.setStats(newStats)
|
||||
# batch.setStats()
|
||||
batch.commit()
|
||||
return delta
|
||||
|
||||
###
|
||||
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
||||
For incomplete Dailys, deduct experience
|
||||
###
|
||||
cron = () ->
|
||||
today = +new Date
|
||||
daysPassed = helpers.daysBetween(today, user.get('lastCron'))
|
||||
if daysPassed > 0
|
||||
batch = new character.BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
batch.set 'lastCron', today
|
||||
obj = batch.obj()
|
||||
hpBefore = obj.stats.hp #we'll use this later so we can animate hp loss
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
_.each obj.tasks, (taskObj) ->
|
||||
{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 == 'daily'
|
||||
taskObj.history ?= []
|
||||
taskObj.history.push { date: +new Date, value: value }
|
||||
batch.set "tasks.#{taskObj.id}.history", taskObj.history
|
||||
batch.set "tasks.#{taskObj.id}.completed", false
|
||||
else
|
||||
value = obj.tasks[taskObj.id].value #get updated value
|
||||
absVal = if (completed) then Math.abs(value) else value
|
||||
todoTally += absVal
|
||||
|
||||
# Finished tallying
|
||||
obj.history ?= {}; obj.history.todos ?= []; obj.history.exp ?= []
|
||||
obj.history.todos.push { date: today, value: todoTally }
|
||||
# tally experience
|
||||
expTally = obj.stats.exp
|
||||
lvl = 0 #iterator
|
||||
while lvl < (obj.stats.lvl-1)
|
||||
lvl++
|
||||
expTally += (lvl*100)/5
|
||||
obj.history.exp.push { date: today, value: expTally }
|
||||
|
||||
# Set the new user specs, and animate HP loss
|
||||
[hpAfter, obj.stats.hp] = [obj.stats.hp, hpBefore]
|
||||
batch.setStats()
|
||||
batch.set('history', obj.history)
|
||||
batch.commit()
|
||||
browser.resetDom(model)
|
||||
setTimeout (-> user.set 'stats.hp', hpAfter), 1000 # animate hp loss
|
||||
|
||||
|
||||
module.exports = {
|
||||
setModel: setModel
|
||||
MODIFIER: MODIFIER
|
||||
score: score
|
||||
cron: cron
|
||||
return {
|
||||
MODIFIER: MODIFIER
|
||||
score: score
|
||||
cron: cron
|
||||
|
||||
# testing stuff
|
||||
expModifier: expModifier
|
||||
hpModifier: hpModifier
|
||||
taskDeltaFormula: taskDeltaFormula
|
||||
}
|
||||
# testing stuff
|
||||
expModifier: expModifier
|
||||
hpModifier: hpModifier
|
||||
taskDeltaFormula: taskDeltaFormula
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ module.exports.view = (view) ->
|
|||
|
||||
module.exports.app = (appExports, model) ->
|
||||
user = model.at('_user')
|
||||
score = new scoring.Scoring(model)
|
||||
|
||||
user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) ->
|
||||
return if passed? && passed.cron # Don't do this stuff on cron
|
||||
|
|
@ -36,7 +37,7 @@ module.exports.app = (appExports, model) ->
|
|||
|
||||
# Score the user based on todo task
|
||||
task = user.at("tasks.#{i}")
|
||||
scoring.score(i, direction())
|
||||
score.score(i, direction())
|
||||
|
||||
appExports.addTask = (e, el, next) ->
|
||||
type = $(el).attr('data-task-type')
|
||||
|
|
@ -81,7 +82,7 @@ module.exports.app = (appExports, model) ->
|
|||
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(id, direction:'down')
|
||||
score.score(id, direction:'down')
|
||||
|
||||
# prevent accidently deleting long-standing tasks
|
||||
else
|
||||
|
|
@ -171,4 +172,4 @@ module.exports.app = (appExports, model) ->
|
|||
direction = 'up' if direction == 'true/'
|
||||
direction = 'down' if direction == 'false/'
|
||||
task = model.at $(el).parents('li')[0]
|
||||
scoring.score(task.get('id'), direction)
|
||||
score.score(task.get('id'), direction)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ auth = (req, res, next) ->
|
|||
|
||||
query.fetch (err, user) ->
|
||||
return res.json err: err if err
|
||||
user = user.at(0)
|
||||
req.user = user
|
||||
req.userObj = user.get()
|
||||
return res.json 401, NO_USER_FOUND if !req.userObj || _.isEmpty(req.userObj)
|
||||
|
|
@ -109,7 +108,7 @@ router.get '/users/:uid/calendar.ics', (req, res) ->
|
|||
query = model.query('users').withIdAndToken(uid, apiToken)
|
||||
query.fetch (err, result) ->
|
||||
return res.send(400, err) if err
|
||||
tasks = result.at(0).get('tasks')
|
||||
tasks = result.get('tasks')
|
||||
# tasks = result[0].tasks
|
||||
tasksWithDates = _.filter tasks, (task) -> !!task.date
|
||||
return res.send(400, "No events found") if _.isEmpty(tasksWithDates)
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ module.exports.app = (appExports, model) ->
|
|||
token = (res) ->
|
||||
console.log(res);
|
||||
$.ajax({
|
||||
type:"POST",
|
||||
url:"/charge",
|
||||
data:res
|
||||
}).success ->
|
||||
window.location.href = "/"
|
||||
.error (err) ->
|
||||
alert err.responseText
|
||||
type: "POST",
|
||||
url: "/charge",
|
||||
data: res
|
||||
}).success ->
|
||||
window.location.href = "/"
|
||||
.error (err) ->
|
||||
alert err.responseText
|
||||
|
||||
StripeCheckout.open
|
||||
key: model.get('_stripePubKey')
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ REST = (store) ->
|
|||
store.query.expose "users", "withIdAndToken", (uid, token) ->
|
||||
@byId(uid)
|
||||
.where('apiToken').equals(token)
|
||||
.one
|
||||
.findOne()
|
||||
|
||||
store.queryAccess "users", "withIdAndToken", (uid, token, accept, err) ->
|
||||
return accept(true) if uid && token
|
||||
|
|
@ -101,6 +101,7 @@ partySystem = (store) ->
|
|||
|
||||
store.query.expose "parties", "withId", (id) ->
|
||||
@where("id").equals(id)
|
||||
.findOne()
|
||||
store.queryAccess "parties", "withId", (id, accept, err) ->
|
||||
# return err(derbyAuth.SESSION_INVALIDATED_ERROR) if derbyAuth.bustedSession(@)
|
||||
return accept(false) if derbyAuth.bustedSession(@)
|
||||
|
|
|
|||
|
|
@ -119,7 +119,7 @@ describe 'API', ->
|
|||
expect(res.statusCode).to.be 201
|
||||
expect(res.body.id).not.to.be.empty()
|
||||
# Ensure that user owns the newly created object
|
||||
expect(user.at(0).get().tasks[res.body.id]).to.be.an('object')
|
||||
expect(user.get().tasks[res.body.id]).to.be.an('object')
|
||||
done()
|
||||
|
||||
it 'PUT /api/v1/task/:id', (done) ->
|
||||
|
|
@ -128,11 +128,12 @@ describe 'API', ->
|
|||
.set('Accept', 'application/json')
|
||||
.set('X-API-User', currentUser.id)
|
||||
.set('X-API-Key', currentUser.apiToken)
|
||||
.send(title: 'a new title')
|
||||
.send(title: 'a new title',text: 'hi')
|
||||
.end (res) ->
|
||||
expect(res.body.err).to.be undefined
|
||||
expect(res.statusCode).to.be 200
|
||||
currentUser.tasks[tid].title = 'a new title'
|
||||
currentUser.tasks[tid].text = 'hi'
|
||||
expect(res.body).to.eql currentUser.tasks[tid]
|
||||
done()
|
||||
|
||||
|
|
@ -146,8 +147,7 @@ describe 'API', ->
|
|||
query.fetch (err, user) ->
|
||||
expect(res.body.err).to.be undefined
|
||||
expect(res.statusCode).to.be 200
|
||||
currentUser = user.at(0).get()
|
||||
model.ref '_user', user.at(0)
|
||||
model.ref '_user', user
|
||||
tasks = []
|
||||
for type in ['habit','todo','daily','reward']
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
|
|
@ -169,8 +169,7 @@ describe 'API', ->
|
|||
query.fetch (err, user) ->
|
||||
expect(res.body.err).to.be undefined
|
||||
expect(res.statusCode).to.be 200
|
||||
currentUser = user.at(0).get()
|
||||
model.ref '_user', user.at(0)
|
||||
model.ref '_user', user
|
||||
model.refList "_todoList", "_user.tasks", "_user.todoIds"
|
||||
tasks = model.get("_todoList")
|
||||
# Ensure that user owns the tasks
|
||||
|
|
|
|||
Loading…
Reference in a new issue