mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-08-05 03:52:14 +00:00
Merge branch 'develop'
This commit is contained in:
commit
f2cfe2436e
21 changed files with 512 additions and 439 deletions
|
|
@ -1,16 +1,15 @@
|
|||
{
|
||||
"PORT":3000,
|
||||
"IP":"0.0.0.0",
|
||||
"BASE_URL": "http://localhost",
|
||||
"BASE_URL":"http://localhost",
|
||||
"FACEBOOK_KEY":"123456789012345",
|
||||
"FACEBOOK_SECRET":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"NODE_DB_URI":"mongodb://user:pass@hostname:27017/db_name",
|
||||
"NODE_DB_URI":"mongodb://localhost/habitrpg",
|
||||
"NODE_ENV":"development",
|
||||
"SESSION_SECRET":"YOUR SECRET HERE",
|
||||
"SMTP_USER":"user@domain.com",
|
||||
"SMTP_PASS":"password",
|
||||
"SMTP_SERVICE":"Gmail",
|
||||
"STRIPE_API_KEY":"aaaabbbbccccddddeeeeffff00001111",
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999",
|
||||
"NODETIME_KEY":"abcdefg"
|
||||
"STRIPE_PUB_KEY":"22223333444455556666777788889999"
|
||||
}
|
||||
|
|
@ -18,13 +18,14 @@
|
|||
"stripe": "*",
|
||||
"async": "*",
|
||||
"lodash": "*",
|
||||
"coffee-script": "*",
|
||||
"coffee-script": "1.4.x",
|
||||
"underscore": "*",
|
||||
"mongoskin": "*",
|
||||
"nconf": "*",
|
||||
"icalendar": "git://github.com/lefnire/node-icalendar#master",
|
||||
"resolve": "~0.2.3",
|
||||
"browserify": "1.17.3"
|
||||
"browserify": "1.17.3",
|
||||
"webkit-devtools-agent": "*"
|
||||
},
|
||||
"private": true,
|
||||
"subdomain": "habitrpg",
|
||||
|
|
|
|||
11
server.js
11
server.js
|
|
@ -22,6 +22,17 @@ process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE");
|
|||
process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY");
|
||||
process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY");
|
||||
|
||||
var agent;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Follow these instructions for profiling / debugging leaks
|
||||
// * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
|
||||
// * https://developers.google.com/chrome-developer-tools/docs/memory-analysis-101
|
||||
agent = require('webkit-devtools-agent');
|
||||
console.log("To debug memory leaks:" +
|
||||
"\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
|
||||
"\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
|
||||
}
|
||||
|
||||
process.on('uncaughtException', function (error) {
|
||||
|
||||
function sendEmail(mailData) {
|
||||
|
|
|
|||
|
|
@ -72,6 +72,13 @@ module.exports.app = (appExports, model) ->
|
|||
appExports.customizeArmorSet = (e, el) ->
|
||||
user.set 'preferences.armorSet', $(el).attr('data-value')
|
||||
|
||||
appExports.restoreSave = (e, el) ->
|
||||
batch = new BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
$('#restore-form input').each ->
|
||||
batch.set $(this).attr('data-for'), parseInt($(this).val())
|
||||
batch.commit()
|
||||
|
||||
user.on 'set', 'flags.customizationsNotification', (captures, args) ->
|
||||
return unless captures == true
|
||||
$('.main-avatar').popover('destroy') #remove previous popovers
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ _ = require('underscore')
|
|||
|
||||
# ========== ROUTES ==========
|
||||
|
||||
get '/', (page, model, next) ->
|
||||
get '/', (page, model, params, next) ->
|
||||
return page.redirect '/' if page.params?.query?.play?
|
||||
|
||||
# temporary view variables, so we don't call model.set() too fast
|
||||
|
|
@ -34,7 +34,7 @@ get '/', (page, model, next) ->
|
|||
#if req.headers['x-forwarded-proto']!='https' and process.env.NODE_ENV=='production'
|
||||
# return page.redirect 'https://' + req.headers.host + req.url
|
||||
|
||||
party.partySubscribe model, ->
|
||||
party.partySubscribe page, model, params, next, ->
|
||||
character.updateUser(model)
|
||||
items.server(model)
|
||||
model.set '_view', _view
|
||||
|
|
@ -45,13 +45,13 @@ get '/', (page, model, 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,66 +16,57 @@ 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 = (model, cb) ->
|
||||
module.exports.partySubscribe = partySubscribe = (page, model, params, next, cb) ->
|
||||
|
||||
# unsubscribe from everything - we're starting over
|
||||
# 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) ->
|
||||
throw err if err
|
||||
u = self.at(0)
|
||||
uObj = u.get()
|
||||
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
|
||||
return next("User not found - this shouldn't be happening!") unless user.get()
|
||||
|
||||
## (1) User is solo, just return that subscription
|
||||
unless uObj.party?.current?
|
||||
model.ref '_user', u
|
||||
return cb()
|
||||
|
||||
###
|
||||
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) ->
|
||||
model.ref '_user', self.at(0)
|
||||
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()
|
||||
|
||||
# User in a party
|
||||
partiesQ = model.query('parties').withId(uObj.party.current)
|
||||
partiesQ.fetch (err, res) ->
|
||||
throw 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
|
||||
# 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
|
||||
|
||||
partyId = user.get('party.current')
|
||||
|
||||
# (1) Solo player
|
||||
return finished([selfQ], ['_user']) unless partyId
|
||||
|
||||
# User in a party
|
||||
partyQ = model.query('parties').withId(partyId)
|
||||
partyQ.fetch (err, party) ->
|
||||
return next(err) if err
|
||||
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) ->
|
||||
throw 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')
|
||||
|
|
@ -98,7 +89,13 @@ module.exports.app = (appExports, model) ->
|
|||
content: html
|
||||
$('.main-avatar').popover 'show'
|
||||
|
||||
model.on 'set', '_user.party.invitation', -> partySubscribe(model)
|
||||
#TODO implement this when we have unsubscribe working properly
|
||||
model.on 'set', '_user.party.invitation', (after, before) ->
|
||||
if !before? and after? # they just got invited
|
||||
# if they haven't unlocked parties yet, unlock it for them
|
||||
user.set('flags.partyEnabled',true) unless user.get('flags.partyEnabled')
|
||||
window.setTimeout (-> window.location.reload true), 1000
|
||||
#partySubscribe(null, model, null, null, null)
|
||||
|
||||
appExports.partyCreate = ->
|
||||
newParty = model.get("_newParty")
|
||||
|
|
@ -111,12 +108,10 @@ module.exports.app = (appExports, model) ->
|
|||
id = model.get('_newPartyMember').replace(/[\s"]/g, '')
|
||||
return if _.isEmpty(id)
|
||||
|
||||
obj = user.get()
|
||||
query = model.query('users').party([id])
|
||||
model.fetch query, (err, res) ->
|
||||
model.query('users').party([id]).fetch (err, res) ->
|
||||
throw err if err
|
||||
u = res.at(0).get()
|
||||
if !u?.id?
|
||||
if !u?
|
||||
model.set "_view.partyError", "User with id #{id} not found."
|
||||
return
|
||||
else if u.party.current? or u.party.invitation?
|
||||
|
|
@ -124,23 +119,24 @@ module.exports.app = (appExports, model) ->
|
|||
return
|
||||
else
|
||||
p = model.at '_party'
|
||||
p.push "invites", id
|
||||
model.set "users.#{id}.party.invitation", p.get('id')
|
||||
#p.push "invites", id
|
||||
$.bootstrapGrowl "Invitation Sent."
|
||||
$('#party-modal').modal('hide')
|
||||
model.set '_newPartyMember', '', -> window.location.reload true
|
||||
model.set "users.#{id}.party.invitation", p.get('id'), -> window.location.reload(true)
|
||||
#model.set '_newPartyMember', ''
|
||||
#partySubscribe model
|
||||
|
||||
appExports.partyAccept = ->
|
||||
partyId = user.get('party.invitation')
|
||||
user.set 'party.invitation', null
|
||||
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.push user.get('id')
|
||||
p.at(0).set 'members', members, ->
|
||||
window.location.reload true
|
||||
model.at("parties.#{partyId}.members").push user.get('id'), -> window.location.reload(true)
|
||||
# model.query('parties').withId(partyId).fetch (err, p) ->
|
||||
# members = p.get('members')
|
||||
# members.push user.get('id')
|
||||
# p.set 'members', members, ->
|
||||
# window.location.reload true
|
||||
|
||||
# partySubscribe model, ->
|
||||
# p = model.at('_party')
|
||||
# p.push 'members', user.get('id')
|
||||
|
|
@ -168,5 +164,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)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ router.post '/users/:uid/tasks/:taskId/:direction', (req, res) ->
|
|||
model = req.getModel()
|
||||
model.fetch model.query('users').withIdAndToken(uid, apiToken), (err, result) ->
|
||||
return res.send(500, err) if err
|
||||
user = result.at(0)
|
||||
user = result
|
||||
userObj = user.get()
|
||||
if _.isEmpty(userObj)
|
||||
return res.send(500, "User with uid=#{uid}, token=#{apiToken} not found. Make sure you're not using your username, but your User Id")
|
||||
|
|
@ -48,8 +48,8 @@ router.post '/users/:uid/tasks/:taskId/:direction', (req, res) ->
|
|||
down: true
|
||||
notes: "This task was created by a third-party service. Feel free to edit, it won't harm the connection to that service. Additionally, multiple services may piggy-back off this task."
|
||||
|
||||
scoring.setModel(model)
|
||||
delta = scoring.score(taskId, direction)
|
||||
score = scoring.Scoring(model)
|
||||
delta = score.score(taskId, direction)
|
||||
result = model.get ('_user.stats')
|
||||
result.delta = delta
|
||||
res.send(result)
|
||||
|
|
@ -63,7 +63,7 @@ router.get '/users/:uid/calendar.ics', (req, res) ->
|
|||
query = model.query('users').withIdAndToken(uid, apiToken)
|
||||
query.fetch (err, result) ->
|
||||
return res.send(500, 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(500, "No events found") if _.isEmpty(tasksWithDates)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ racer.set('bundleTimeout', 40000)
|
|||
# racer.use(racer.logPlugin)
|
||||
# derby.use(derby.logPlugin)
|
||||
|
||||
# Infinite stack trace
|
||||
Error.stackTraceLimit = Infinity if process.env.NODE_ENV is 'development'
|
||||
|
||||
## SERVER CONFIGURATION ##
|
||||
|
||||
expressApp = express()
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -69,7 +69,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
|
||||
|
|
@ -96,6 +96,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(@)
|
||||
|
|
|
|||
|
|
@ -42,4 +42,4 @@ casper.then ->
|
|||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
@test.renderResults true
|
||||
casper.test.renderResults true
|
||||
|
|
@ -1,38 +1,38 @@
|
|||
helpers = require('./helpers')
|
||||
helpers = new require('./test/casper/helpers')()
|
||||
casper = helpers.casper
|
||||
utils = helpers.utils
|
||||
url = helpers.url
|
||||
url = helpers.playUrl
|
||||
|
||||
# ---------- Init ------------
|
||||
# ---------- Basic Reset Test ------------
|
||||
|
||||
casper.start url, ->
|
||||
@test.assertTitle "HabitRPG | Gamify Your Life", "[√] Page Title"
|
||||
casper.test.assertTitle 'HabitRPG | Gamify Your Life', 'Page Title'
|
||||
|
||||
# ---------- Reset ------------
|
||||
# @then ->
|
||||
# utils.dump @evaluate -> window.DERBY.app.model.get('_user.auth')
|
||||
# @click '#reset-modal button:contains(Reset)'
|
||||
|
||||
# Clear tasks
|
||||
# Gain some GP and lose some HP
|
||||
casper.then ->
|
||||
#TODO test after stats have been modified
|
||||
casper.repeat 5, -> @click '.habits a[data-direction="down"]'
|
||||
casper.repeat 5, -> @click '.habits a[data-direction="up"]'
|
||||
userObj = @evaluate ->
|
||||
window.DERBY.app.reset()
|
||||
return window.DERBY.app.model.get('_user')
|
||||
casper.repeat 5, -> casper.click '.habits a[data-direction="down"]'
|
||||
casper.repeat 5, -> casper.click '.habits a[data-direction="up"]'
|
||||
|
||||
# Reset
|
||||
casper.then ->
|
||||
helpers.reset()
|
||||
|
||||
# Test that reset worked
|
||||
casper.then ->
|
||||
model = helpers.getModelDelayed (model) ->
|
||||
casper.echo 'testing user after reset'
|
||||
casper.test.assertEqual model._user.tasks, {}, 'no tasks'
|
||||
casper.test.assertEqual model._user.stats, {hp:50, gp:0, exp:0, lvl:1}, 'stats'
|
||||
|
||||
@test.assertEqual userObj.tasks.length, 0
|
||||
@test.assertEqual userObj.stats.hp, 50
|
||||
|
||||
# ---------- Misc Pages ------------
|
||||
|
||||
casper.thenOpen "#{url}/terms", ->
|
||||
@test.assertTitle "Terms Of Use", "terms page works"
|
||||
casper.thenOpen "#{helpers.baseUrl}/terms", ->
|
||||
casper.test.assertTitle "Terms Of Use", "terms page works"
|
||||
|
||||
casper.thenOpen "#{url}/privacy", ->
|
||||
@test.assertTitle "Privacy Policy", "privacy page works"
|
||||
casper.thenOpen "#{helpers.baseUrl}/privacy", ->
|
||||
casper.test.assertTitle "Privacy Policy", "privacy page works"
|
||||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
@test.renderResults true
|
||||
casper.test.renderResults true
|
||||
|
|
@ -1,38 +1,39 @@
|
|||
helper = new require('./test/casper/helpers')()
|
||||
casper = helper.casper
|
||||
utils = helper.utils
|
||||
url = helper.url
|
||||
helpers = new require('./test/casper/helpers')()
|
||||
casper = helpers.casper
|
||||
utils = helpers.utils
|
||||
url = helpers.playUrl
|
||||
|
||||
casper.start url + '/?play=1'
|
||||
casper.start url
|
||||
|
||||
# ---------- Register ------------
|
||||
user = undefined
|
||||
casper.then -> helper.register()
|
||||
casper.then -> user = helper.getUser()
|
||||
# # ---------- Register ------------
|
||||
# casper.then -> helpers.register()
|
||||
# casper.then -> user = helpers.getUser()
|
||||
|
||||
# ---------- Habits ------------
|
||||
casper.then ->
|
||||
helper.reset()
|
||||
helper.addTasks()
|
||||
helpers.reset()
|
||||
helpers.addTasks(['habit'])
|
||||
|
||||
casper.then ->
|
||||
u = helper.userBeforeAfter (-> casper.click '.habits a[data-direction="down"]')
|
||||
casper.test.assert u.before.stats.hp > u.after.stats.hp, '-habit -hp'
|
||||
casper.test.assert u.before.stats.exp == u.after.stats.exp, '-habit =exp'
|
||||
helpers.modelBeforeAfter (-> casper.click '.habits a[data-direction="down"]'), (model) ->
|
||||
casper.test.assert model.before._user.stats.hp > model.after._user.stats.hp, '-habit -hp'
|
||||
casper.test.assertEquals model.before._user.stats.exp, model.after._user.stats.exp, '-habit =exp'
|
||||
casper.test.assertEquals model.before._user.stats.gp, model.after._user.stats.gp, '-habit =gp'
|
||||
|
||||
casper.then ->
|
||||
u = helper.userBeforeAfter (-> casper.click '.habits a[data-direction="up"]')
|
||||
casper.test.assert u.before.stats.exp < u.after.stats.exp, '+habit +exp'
|
||||
casper.test.assertEquals u.before.stats.hp, u.after.stats.hp, '+habit =hp'
|
||||
helpers.modelBeforeAfter (-> casper.click '.habits a[data-direction="up"]'), (model) ->
|
||||
casper.test.assert model.before._user.stats.exp < model.after._user.stats.exp, '+habit +exp'
|
||||
casper.test.assert model.before._user.stats.gp < model.after._user.stats.gp, '+habit +gp'
|
||||
casper.test.assertEquals model.before._user.stats.hp, model.after._user.stats.hp, '+habit =hp'
|
||||
|
||||
# Test Death
|
||||
casper.then ->
|
||||
@repeat 50, (-> casper.click '.habits a[data-direction="down"]')
|
||||
casper.repeat 50, (-> casper.click '.habits a[data-direction="down"]')
|
||||
casper.then ->
|
||||
u = helper.getUser()
|
||||
@test.assertEquals u.stats.hp, 0, 'hp==0 (death by habits)'
|
||||
@test.assertEquals u.stats.lvl, 0, 'lvl==0 (death by habits)'
|
||||
@test.assert(@visible('#dead-modal'), 'Revive Modal Visible')
|
||||
helpers.getModelDelayed (model) ->
|
||||
casper.test.assertEquals model._user.stats.hp, 0, 'hp==0 (death by habits)'
|
||||
casper.test.assertEquals model._user.stats.lvl, 0, 'lvl==0 (death by habits)'
|
||||
casper.test.assertVisible '#dead-modal', 'Revive Modal Visible'
|
||||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
|
|
|
|||
|
|
@ -44,6 +44,10 @@ module.exports = ->
|
|||
casper.evaluate -> window.DERBY.app.reset()
|
||||
|
||||
getModelDelayed: (cb) ->
|
||||
# This time is needed for derby to have enough time to update all it's data.
|
||||
# It still happens sometimes that the retrieved model does not contain any
|
||||
# data. It might be worth to do some basic checks on the model here, and if
|
||||
# it doesn't look OK, wait a bit longer and get it again.
|
||||
casper.wait SYNC_WAIT_TIME, ->
|
||||
cb(getModel())
|
||||
|
||||
|
|
@ -82,9 +86,9 @@ module.exports = ->
|
|||
register: ->
|
||||
casper.fill 'form#derby-auth-register',
|
||||
username: random
|
||||
email: random + '@gmail.com'
|
||||
'email-confirmation': random + '@gmail.com'
|
||||
email: random + '@example.com'
|
||||
password: random
|
||||
'password-confirmation': random
|
||||
, true
|
||||
|
||||
login: ->
|
||||
|
|
|
|||
22
test/casper/items.casper.coffee
Normal file
22
test/casper/items.casper.coffee
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
helpers = new require('./test/casper/helpers')()
|
||||
casper = helpers.casper
|
||||
utils = helpers.utils
|
||||
url = helpers.playUrl
|
||||
|
||||
casper.start url
|
||||
|
||||
# ---------- Items (in-game rewards) ------------
|
||||
casper.then ->
|
||||
helpers.reset()
|
||||
helpers.addTasks(['habit'], 1)
|
||||
|
||||
casper.then -> casper.test.assertDoesntExist 'ul.items', 'no items after reset'
|
||||
casper.then -> casper.repeat 70, ->
|
||||
casper.click '.habits a[data-direction="up"]'
|
||||
casper.then ->
|
||||
casper.test.assertVisible '.item-store-popover', 'store popover visible'
|
||||
casper.test.assertExists 'ul.items', 'items appear after lvl up'
|
||||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
casper.test.renderResults true
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
helper = new require('./test/casper/helpers')()
|
||||
casper = helper.casper
|
||||
utils = helper.utils
|
||||
url = helper.url
|
||||
helpers = new require('./test/casper/helpers')()
|
||||
casper = helpers.casper
|
||||
utils = helpers.utils
|
||||
url = helpers.playUrl
|
||||
|
||||
casper.start url + '/?play=1'
|
||||
casper.start url
|
||||
|
||||
casper.repeat 50, ->
|
||||
casper.repeat 100, ->
|
||||
casper.reload()
|
||||
|
||||
casper.run ->
|
||||
@test.renderResults true
|
||||
casper.test.renderResults true
|
||||
|
|
@ -1,35 +1,34 @@
|
|||
helper = new require('./test/casper/helpers')()
|
||||
casper = helper.casper
|
||||
utils = helper.utils
|
||||
url = helper.url
|
||||
helpers = new require('./test/casper/helpers')()
|
||||
casper = helpers.casper
|
||||
utils = helpers.utils
|
||||
url = helpers.playUrl
|
||||
|
||||
casper.start url + '/?play=1'
|
||||
casper.start url
|
||||
|
||||
# ---------- Register ------------
|
||||
user = undefined
|
||||
casper.then -> helper.register()
|
||||
casper.then -> user = helper.getUser()
|
||||
registeredUser = undefined
|
||||
casper.then -> helpers.register()
|
||||
casper.then ->
|
||||
helpers.getModelDelayed (model) ->
|
||||
registeredUser = model
|
||||
|
||||
casper.then -> casper.reload()
|
||||
casper.then ->
|
||||
nowUser = helper.getUser()
|
||||
casper.then ->
|
||||
casper.test.assertEqual user.id, nowUser.id, 'user registered and maintained session'
|
||||
helpers.getModelDelayed (nowModel) ->
|
||||
casper.test.assertEqual registeredUser._userId, nowModel._userId, 'user registered and maintained session'
|
||||
|
||||
# ---------- Log Out ------------
|
||||
casper.thenOpen helper.url + '/logout'
|
||||
casper.thenOpen helper.url + '/?play=1'
|
||||
casper.thenOpen helpers.baseUrl + '/logout'
|
||||
casper.thenOpen helpers.playUrl
|
||||
casper.then ->
|
||||
nowUser = helper.getUser()
|
||||
casper.then ->
|
||||
casper.test.assertNotEquals user.id, nowUser.id, 'user logged out'
|
||||
helpers.getModelDelayed (nowModel) ->
|
||||
casper.test.assertNotEquals registeredUser._userId, nowModel._userId, 'user logged out'
|
||||
|
||||
# ---------- Login ------------
|
||||
casper.then -> helper.login()
|
||||
casper.then -> utils.dump casper.debugHTML '#derby-auth-login'
|
||||
casper.then -> helpers.login()
|
||||
casper.then ->
|
||||
nowUser = helper.getUser()
|
||||
casper.then ->
|
||||
casper.test.assertEqual user.id, nowUser.id, 'user logged in'
|
||||
helpers.getModelDelayed (nowModel) ->
|
||||
casper.test.assertEqual registeredUser._userId, nowModel._userId, 'user logged out'
|
||||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
helper = new require('./test/casper/helpers')()
|
||||
casper = helper.casper
|
||||
utils = helper.utils
|
||||
url = helper.url
|
||||
|
||||
casper.start url + '/?play=1'
|
||||
|
||||
# ---------- Rewardsj1 ------------
|
||||
casper.then ->
|
||||
helper.reset()
|
||||
helper.addTasks()
|
||||
|
||||
casper.then -> @test.assertDoesntExist('ul.items')
|
||||
casper.then -> @repeat 50, -> casper.click('.habits a[data-direction="up"]')
|
||||
casper.then -> @test.assertExists('ul.items')
|
||||
|
||||
# ---------- Run ------------
|
||||
casper.run ->
|
||||
casper.test.renderResults true
|
||||
|
|
@ -10,7 +10,8 @@
|
|||
<pre class=prettyprint>{_user.apiToken}</pre>
|
||||
|
||||
<hr/>
|
||||
<a class='btn btn-danger' data-target="#reset-modal" data-toggle="modal">Reset</a>
|
||||
<a class='btn btn-danger' data-target="#reset-modal" data-toggle="modal" rel=tooltip title="Resets your entire account (dangerous).">Reset</a>
|
||||
<a class='btn btn-danger' data-target="#restore-modal" data-toggle="modal" rel=tooltip title="Restores attributes to your character.">Restore</a>
|
||||
|
||||
<@footer>
|
||||
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
|
||||
|
|
@ -25,6 +26,56 @@
|
|||
<button data-dismiss="modal" x-bind=click:reset class="btn btn-danger btn-large">Reset</button>
|
||||
</@footer>
|
||||
</app:modals:modal>
|
||||
|
||||
<app:modals:modal modalId="restore-modal" header="Restore">
|
||||
<p>HabitRPG is quite Beta-quality at present, and many find they need to restore character attributes as a result. Enter your numbers here and it will be applied automatically to your character. This will be removed once Habit is more stable.</p>
|
||||
|
||||
{#with _user}
|
||||
<form id='restore-form' class="form-horizontal">
|
||||
<h3>Stats</h3>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">HP</span>
|
||||
<input class="span2" type="number" data-for='stats.hp' value="{{.stats.hp}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Exp</span>
|
||||
<input class="span2" type="number" data-for='stats.exp' value="{{.stats.exp}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">GP</span>
|
||||
<input class="span2" type="number" data-for='stats.gp' value="{{.stats.gp}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Level</span>
|
||||
<input class="span2" type="number" data-for='stats.lvl' value="{{.stats.lvl}}">
|
||||
</div>
|
||||
|
||||
<h3>Items</h3>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Weapon</span>
|
||||
<input class="span2" type="number" data-for='items.weapon' value="{{.items.weapon}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Armor</span>
|
||||
<input class="span2" type="number" data-for='items.armor' value="{{.items.armor}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Helm</span>
|
||||
<input class="span2" type="number" data-for='items.head' value="{{.items.head}}">
|
||||
</div>
|
||||
<div class="input-prepend">
|
||||
<span class="add-on">Shield</span>
|
||||
<input class="span2" type="number" data-for='items.shield' value="{{.items.shield}}">
|
||||
</div>
|
||||
|
||||
</form>
|
||||
{/}
|
||||
|
||||
<@footer>
|
||||
<button class="btn" x-bind="click:restoreSave" data-dismiss="modal" aria-hidden="true">Save & Close</button>
|
||||
</@footer>
|
||||
</app:modals:modal>
|
||||
|
||||
{{else}}
|
||||
<app:modals:modal modalId="login-modal" header="Login / Register">
|
||||
<a href="/auth/facebook"><img src='/img/facebook-login-register.jpeg' alt="Login / Register With Facebook"/></a>
|
||||
|
|
|
|||
Loading…
Reference in a new issue