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'
|
2013-03-13 15:37:06 +00:00
|
|
|
{ randomProp } = helpers = require './helpers'
|
2013-01-20 21:52:56 +00:00
|
|
|
browser = require './browser'
|
2013-02-08 22:44:06 +00:00
|
|
|
character = require './character'
|
2013-02-08 19:33:12 +00:00
|
|
|
items = require './items'
|
2013-03-13 15:37:06 +00:00
|
|
|
{ pets, food } = items.items
|
2013-03-02 18:23:43 +00:00
|
|
|
algos = require './algos'
|
2013-02-26 21:51:00 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
MODIFIER = algos.MODIFIER # each new level, armor, weapon add 2% modifier (this mechanism will change)
|
|
|
|
|
|
|
|
|
|
# {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 = (model, taskId, direction, times, batch, cron) ->
|
2013-02-26 21:51:00 +00:00
|
|
|
user = model.at '_user'
|
|
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
commit = false
|
|
|
|
|
unless batch?
|
|
|
|
|
commit = true
|
|
|
|
|
batch = new character.BatchUpdate(model)
|
|
|
|
|
batch.startTransaction()
|
|
|
|
|
obj = batch.obj()
|
2013-02-26 21:51:00 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
{gp, hp, exp, lvl} = obj.stats
|
2013-02-26 21:51:00 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
taskPath = "tasks.#{taskId}"
|
|
|
|
|
taskObj = obj.tasks[taskId]
|
|
|
|
|
{type, value} = taskObj
|
2013-03-04 16:33:36 +00:00
|
|
|
priority = taskObj.priority or '!'
|
2013-02-26 21:51:00 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
# 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
|
2013-02-26 21:51:00 +00:00
|
|
|
batch.commit()
|
2013-03-03 04:38:31 +00:00
|
|
|
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 = algos.taskDeltaFormula(value, direction)
|
2013-03-06 17:02:08 +00:00
|
|
|
value += nextDelta if adjustvalue
|
2013-03-03 04:38:31 +00:00
|
|
|
delta += nextDelta
|
|
|
|
|
|
|
|
|
|
addPoints = ->
|
|
|
|
|
level = user.get('stats.lvl')
|
|
|
|
|
weaponStrength = items.items.weapon[user.get('items.weapon')].strength
|
2013-03-12 14:05:18 +00:00
|
|
|
exp += algos.expModifier(delta,weaponStrength,level, priority) / 2 # / 2 hack for now bcause people leveling too fast
|
2013-03-04 16:33:36 +00:00
|
|
|
gp += algos.gpModifier(delta, 1, priority)
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
subtractPoints = ->
|
|
|
|
|
level = user.get('stats.lvl')
|
|
|
|
|
armorDefense = items.items.armor[user.get('items.armor')].defense
|
|
|
|
|
helmDefense = items.items.head[user.get('items.head')].defense
|
|
|
|
|
shieldDefense = items.items.shield[user.get('items.shield')].defense
|
2013-03-06 17:02:08 +00:00
|
|
|
hp += algos.hpModifier(delta,armorDefense,helmDefense,shieldDefense,level, priority)
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
switch type
|
|
|
|
|
when 'habit'
|
2013-03-04 22:00:12 +00:00
|
|
|
calculateDelta()
|
2013-03-03 04:38:31 +00:00
|
|
|
# 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'
|
|
|
|
|
if cron? # cron
|
|
|
|
|
calculateDelta()
|
|
|
|
|
subtractPoints()
|
2013-03-02 20:02:57 +00:00
|
|
|
else
|
2013-03-03 04:38:31 +00:00
|
|
|
calculateDelta(false)
|
2013-03-06 17:02:08 +00:00
|
|
|
if delta != 0
|
|
|
|
|
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
2013-02-26 21:51:00 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
when 'todo'
|
|
|
|
|
if cron? #cron
|
|
|
|
|
calculateDelta()
|
|
|
|
|
#don't touch stats on cron
|
|
|
|
|
else
|
2013-03-04 22:00:12 +00:00
|
|
|
calculateDelta()
|
2013-03-03 04:38:31 +00:00
|
|
|
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 model, {hp: hp, exp: exp, gp: gp}, batch
|
2013-03-13 15:37:06 +00:00
|
|
|
|
|
|
|
|
# 1% chance of getting a pet or meat
|
2013-03-13 15:47:37 +00:00
|
|
|
if obj.flags.dropsEnabled and Math.random() < .01
|
2013-03-13 15:37:06 +00:00
|
|
|
if Math.random() < .5
|
|
|
|
|
drop = randomProp(food)
|
|
|
|
|
user.push 'items.food', drop.name
|
|
|
|
|
drop.type = 'food'
|
|
|
|
|
else
|
|
|
|
|
drop = randomProp(pets)
|
|
|
|
|
user.push 'items.eggs', drop
|
|
|
|
|
drop.type = 'egg'
|
|
|
|
|
|
|
|
|
|
model.set '_drop', drop
|
2013-03-13 15:47:37 +00:00
|
|
|
$('#item-dropped-modal').modal 'show'
|
2013-03-13 15:37:06 +00:00
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
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
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
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 = (model, newStats, batch) ->
|
|
|
|
|
user = model.at '_user'
|
|
|
|
|
obj = batch.obj()
|
|
|
|
|
|
|
|
|
|
# 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?
|
|
|
|
|
tnl = model.get '_tnl'
|
2013-03-06 17:02:08 +00:00
|
|
|
#silent = false
|
|
|
|
|
# if we're at level 100, turn xp to gold
|
|
|
|
|
if obj.stats.lvl >= 100
|
|
|
|
|
newStats.gp += newStats.exp / 15
|
|
|
|
|
newStats.exp = 0
|
|
|
|
|
obj.stats.lvl = 100
|
|
|
|
|
else
|
|
|
|
|
# level up & carry-over exp
|
|
|
|
|
if newStats.exp >= tnl
|
|
|
|
|
#silent = true # push through the negative xp silently
|
|
|
|
|
user.set('stats.exp', newStats.exp) # push normal + notification
|
|
|
|
|
while newStats.exp >= tnl and obj.stats.lvl < 100 # keep levelling up
|
|
|
|
|
newStats.exp -= tnl
|
|
|
|
|
obj.stats.lvl++
|
|
|
|
|
tnl = algos.tnl(obj.stats.lvl)
|
|
|
|
|
if obj.stats.lvl== 100
|
|
|
|
|
newStats.exp = 0
|
|
|
|
|
obj.stats.hp = 50
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
obj.stats.exp = newStats.exp
|
2013-03-06 17:02:08 +00:00
|
|
|
#if silent
|
|
|
|
|
#console.log("pushing silent :" + obj.stats.exp)
|
2013-03-08 17:55:04 +00:00
|
|
|
#user.pass(true).set('stats.exp', obj.stats.exp)
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
# 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
|
2013-03-08 17:16:44 +00:00
|
|
|
batch.set 'flags.itemsEnabled', obj.flags.itemsEnabled = true
|
2013-03-03 04:38:31 +00:00
|
|
|
if !obj.flags.partyEnabled and obj.stats.lvl >= 3
|
2013-03-08 17:16:44 +00:00
|
|
|
batch.set 'flags.partyEnabled', obj.flags.partyEnabled = true
|
2013-03-08 17:44:21 +00:00
|
|
|
if !obj.flags.dropsEnabled and obj.stats.lvl >= 4
|
2013-03-08 17:16:44 +00:00
|
|
|
batch.set 'flags.dropsEnabled', obj.flags.dropsEnabled = true
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
###
|
|
|
|
|
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
|
|
|
|
|
For incomplete Dailys, deduct experience
|
|
|
|
|
###
|
|
|
|
|
cron = (model) ->
|
|
|
|
|
user = model.at '_user'
|
|
|
|
|
today = +new Date
|
2013-03-03 05:17:39 +00:00
|
|
|
daysPassed = helpers.daysBetween(user.get('lastCron'), today, user.get('preferences.dayStart'))
|
2013-03-03 04:38:31 +00:00
|
|
|
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 model, id, 'down', daysFailed, batch, true
|
|
|
|
|
if type == 'daily'
|
2013-03-04 22:00:12 +00:00
|
|
|
if completed #set OHV for completed dailies
|
|
|
|
|
newValue = taskObj.value + algos.taskDeltaFormula(taskObj.value,'up')
|
|
|
|
|
batch.set "tasks.#{taskObj.id}.value", newValue
|
|
|
|
|
|
2013-03-03 04:38:31 +00:00
|
|
|
taskObj.history ?= []
|
2013-03-10 15:40:58 +00:00
|
|
|
taskObj.history.push { date: +new Date, value: taskObj.value }
|
2013-03-03 04:38:31 +00:00
|
|
|
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
|
2013-03-06 17:02:08 +00:00
|
|
|
else if type is 'habit' # slowly reset 'onlies' value to 0
|
2013-03-04 00:18:04 +00:00
|
|
|
if taskObj.up==false or taskObj.down==false
|
2013-03-08 00:10:11 +00:00
|
|
|
if Math.abs(taskObj.value) < 0.1
|
2013-03-04 22:00:12 +00:00
|
|
|
batch.set "tasks.#{taskObj.id}.value", 0
|
|
|
|
|
else
|
|
|
|
|
batch.set "tasks.#{taskObj.id}.value", taskObj.value / 2
|
2013-03-03 04:38:31 +00:00
|
|
|
|
|
|
|
|
# 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 += algos.tnl(lvl)
|
|
|
|
|
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 = {
|
|
|
|
|
score: score
|
|
|
|
|
cron: cron
|
|
|
|
|
|
|
|
|
|
# testing stuff
|
|
|
|
|
expModifier: algos.expModifier
|
|
|
|
|
hpModifier: algos.hpModifier
|
|
|
|
|
taskDeltaFormula: algos.taskDeltaFormula
|
|
|
|
|
}
|