refactor much of the hodge-podge code into their own files

This commit is contained in:
Tyler Renelle 2013-02-08 17:44:06 -05:00
parent 4dd7bad81a
commit 230d672fc7
8 changed files with 284 additions and 266 deletions

View file

@ -1,10 +1,78 @@
character = require './character'
browser = require './browser'
items = require './items'
moment = require 'moment'
_ = require 'underscore'
lodash = require 'lodash'
derby = require 'derby'
module.exports.view = (view) ->
view.fn "username", (auth) ->
if auth?.facebook?.displayName?
auth.facebook.displayName
else if auth?.facebook?
fb = auth.facebook
if fb._raw then "#{fb.name.givenName} #{fb.name.familyName}" else fb.name
else if auth?.local?
auth.local.username
else
'Anonymous'
module.exports.app = (appExports, model) ->
user = model.at '_user'
revive = (batch) ->
# Reset stats
batch.set 'stats.hp', 50
batch.set 'stats.lvl', 1
batch.set 'stats.gp', 0
batch.set 'stats.exp', 0
# Reset items
batch.set 'items.armor', 0
batch.set 'items.weapon', 0
batch.set 'items.head', 0
batch.set 'items.shield', 0
# Reset item store
items.updateStore(model)
appExports.revive = (e, el) ->
batch = new character.BatchUpdate(model)
batch.startTransaction()
revive(batch)
batch.commit()
appExports.reset = (e, el) ->
batch = new character.BatchUpdate(model)
batch.startTransaction()
taskTypes = ['habit', 'daily', 'todo', 'reward']
batch.set 'tasks', {}
_.each taskTypes, (type) -> batch.set "idLists.#{type}", []
batch.set 'balance', 2 if user.get('balance') < 2 #only if they haven't manually bought tokens
revive(batch)
batch.commit()
browser.resetDom(model)
appExports.closeKickstarterNofitication = (e, el) ->
user.set('flags.kickstarter', 'hide')
appExports.customizeGender = (e, el) ->
user.set 'preferences.gender', $(el).attr('data-value')
appExports.customizeHair = (e, el) ->
user.set 'preferences.hair', $(el).attr('data-value')
appExports.customizeSkin = (e, el) ->
user.set 'preferences.skin', $(el).attr('data-value')
appExports.customizeArmorSet = (e, el) ->
user.set 'preferences.armorSet', $(el).attr('data-value')
userSchema =
# _id
# _id
stats: { gp: 0, exp: 0, lvl: 1, hp: 50 }
party: { current: null, invitation: null }
items: { weapon: 0, armor: 0, head: 0, shield: 0 }
@ -22,7 +90,7 @@ userSchema =
partyEnabled: false
itemsEnabled: false
kickstarter: 'show'
# ads: 'show' # added on registration
# ads: 'show' # added on registration
module.exports.newUserObject = ->
# deep clone, else further new users get duplicate objects
@ -79,41 +147,41 @@ module.exports.BatchUpdate = BatchUpdate = (model) ->
updates = {}
{
user: user
user: user
obj: ->
obj ?= user.get()
return obj
obj: ->
obj ?= user.get()
return obj
startTransaction: ->
# start a batch transaction - nothing between now and @commit() will be set immediately
transactionInProgress = true
model._dontPersist = true
startTransaction: ->
# start a batch transaction - nothing between now and @commit() will be set immediately
transactionInProgress = true
model._dontPersist = true
# Really strange, user.get() seems to only return attributes which have previously been accessed. So in
# many cases, userObj.tasks.{taskId}.value is undefined - so we manually .get() each attribute here.
# Additionally, for some reason after getting the user object, changing properies manually (userObj.stats.hp = 50)
# seems to actually run user.set('stats.hp',50) which we don't want to do - so we deepClone here
#_.each Object.keys(userSchema), (key) -> obj[key] = lodash.cloneDeep user.get(key)
obj = model.get('users.'+user.get('id'), true)
# Really strange, user.get() seems to only return attributes which have previously been accessed. So in
# many cases, userObj.tasks.{taskId}.value is undefined - so we manually .get() each attribute here.
# Additionally, for some reason after getting the user object, changing properies manually (userObj.stats.hp = 50)
# seems to actually run user.set('stats.hp',50) which we don't want to do - so we deepClone here
#_.each Object.keys(userSchema), (key) -> obj[key] = lodash.cloneDeep user.get(key)
obj = model.get('users.'+user.get('id'), true)
###
Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued
but not actually set to the model. It also modifies userObj in case you need to access properties manually later.
If transaction not in progress, it just runs standard model.set()
###
set: (path, val) ->
updates[path] = val if transactionInProgress
user.set(path, val)
###
Handles updating the user model. If this is an en-mass operation (eg, server cron), changes are queued
but not actually set to the model. It also modifies userObj in case you need to access properties manually later.
If transaction not in progress, it just runs standard model.set()
###
set: (path, val) ->
updates[path] = val if transactionInProgress
user.set(path, val)
###
Hack to get around dom bindings being lost if parent objects are replaced whole-sale
eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok
###
setStats: (stats) ->
stats ?= obj.stats
that = @
_.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key]
###
Hack to get around dom bindings being lost if parent objects are replaced whole-sale
eg, user.set('stats', {hp:50, exp:10...}) will break dom bindings, but user.set('stats.hp',50) is ok
###
setStats: (stats) ->
stats ?= obj.stats
that = @
_.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key]
# queue: (path, val) ->
# # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790
@ -124,10 +192,10 @@ module.exports.BatchUpdate = BatchUpdate = (model) ->
# curr[next]
# , obj
commit: ->
model._dontPersist = false
# some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js
user.set "update__", updates
transactionInProgress = false
updates = {}
commit: ->
model._dontPersist = false
# some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js
user.set "update__", updates
transactionInProgress = false
updates = {}
}

View file

@ -7,24 +7,7 @@ module.exports.daysBetween = (a, b) ->
module.exports.dayMapping = dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s',7:'su'}
module.exports.viewHelpers = (view) ->
view.fn 'taskClasses', (type, completed, value, repeat) ->
#TODO figure out how to just pass in the task model, so i can access all these properties from one object
classes = type
# show as completed if completed (naturally) or not required for today
if completed or (repeat and repeat[dayMapping[moment().day()]]==false)
classes += " completed"
switch
when value<-8 then classes += ' color-worst'
when value>=-8 and value<-5 then classes += ' color-worse'
when value>=-5 and value<-1 then classes += ' color-bad'
when value>=-1 and value<1 then classes += ' color-neutral'
when value>=1 and value<5 then classes += ' color-good'
when value>=5 and value<10 then classes += ' color-better'
when value>=10 then classes += ' color-best'
return classes
view.fn "percent", (x, y) ->
x=1 if x==0
Math.round(x/y*100)
@ -40,13 +23,3 @@ module.exports.viewHelpers = (view) ->
return gp/0.25
view.fn "username", (auth) ->
if auth?.facebook?.displayName?
auth.facebook.displayName
else if auth?.facebook?
fb = auth.facebook
if fb._raw then "#{fb.name.givenName} #{fb.name.familyName}" else fb.name
else if auth?.local?
auth.local.username
else
'Anonymous'

View file

@ -5,24 +5,21 @@ derby.use require('../../ui')
derby.use require('derby-auth/components');
# Custom requires
character = require './character'
tasks = require './tasks'
scoring = require './scoring'
schema = require './schema'
helpers = require './helpers'
browser = require './browser'
party = require './party'
items = require './items'
helpers.viewHelpers view
character.view view
tasks.view view
items.view view
moment = require('moment')
_ = require('underscore')
setupListReferences = (model) ->
taskTypes = ['habit', 'daily', 'todo', 'reward']
_.each taskTypes, (type) -> model.refList "_#{type}List", "_user.tasks", "_user.idLists.#{type}"
# ========== ROUTES ==========
get '/', (page, model, next) ->
@ -43,7 +40,7 @@ get '/', (page, model, next) ->
user = users.at(0)
model.ref '_user', user
batch = new schema.BatchUpdate(model)
batch = new character.BatchUpdate(model)
batch.startTransaction()
obj = batch.obj()
obj = user.get() unless obj.items? #why is this happening?
@ -52,10 +49,14 @@ get '/', (page, model, next) ->
items.server(model)
schema.updateUser(batch)
character.updateUser(batch)
batch.commit()
setupListReferences(model)
# refLists
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
model.refList "_#{type}List", "_user.tasks", "_user.idLists.#{type}"
# tnl function
model.fn '_tnl', '_user.stats.lvl', (lvl) ->
# see https://github.com/lefnire/habitrpg/issues/4
# also update in scoring.coffee. TODO create a function accessible in both locations
@ -73,191 +74,13 @@ ready (model) ->
lastCron = user.get('lastCron')
user.set('lastCron', +new Date) if (!lastCron? or lastCron == 'new')
# Setup model in scoring functions
scoring.cron()
browser.app(exports, model)
party.app(exports, model)
character.app(exports, model)
tasks.app(exports, model)
items.app(exports, model)
party.app(exports, model)
require('../server/private').app(exports, model)
require('./debug').app(exports, model) if model.get('_view.nodeEnv') != 'production'
user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) ->
return if passed? && passed.cron # Don't do this stuff on cron
direction = () ->
return 'up' if completed==true and previous == false
return 'down' if completed==false and previous == true
throw new Error("Direction neither 'up' nor 'down' on checkbox set.")
# Score the user based on todo task
task = user.at("tasks.#{i}")
scoring.score(i, direction())
exports.addTask = (e, el, next) ->
type = $(el).attr('data-task-type')
list = model.at "_#{type}List"
newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1))
text = newModel.get()
# Don't add a blank todo
return if /^(\s)*$/.test(text)
newModel.set ''
switch type
when 'habit'
list.push {type: type, text: text, notes: '', value: 0, up: true, down: true}
when 'reward'
list.push {type: type, text: text, notes: '', value: 20 }
when 'daily'
list.push {type: type, text: text, notes: '', value: 0, repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }
when 'todo'
list.push {type: type, text: text, notes: '', value: 0, completed: false }
# list.on 'set', '*.completed', (i, completed, previous, isLocal) ->
# # Move the item to the bottom if it was checked off
# list.move i, -1 if completed && isLocal
exports.del = (e, el) ->
# Derby extends model.at to support creation from DOM nodes
#task = model.at(e.target)
# FIXME normally that would work, and we'd later simply call `user.del task` (instead of that 4-liner down there)
# however, see https://github.com/lefnire/habitrpg/pull/226#discussion_r2810391
id = $(e.target).parents('li.task').attr('data-id')
return unless id?
task = user.at "tasks.#{id}"
type = task.get('type')
history = task.get('history')
if history and history.length>2
# prevent delete-and-recreate hack on red tasks
if task.get('value') < 0
result = confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).")
if result != true
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')
# prevent accidently deleting long-standing tasks
else
result = confirm("Are you sure you want to delete this task?")
return if result != true
#TODO bug where I have to delete from _users.tasks AND _{type}List,
# fix when query subscriptions implemented properly
$('[rel=tooltip]').tooltip('hide')
ids = user.get("idLists.#{type}")
ids.splice(ids.indexOf(id),1)
user.del('tasks.'+id)
user.set("idLists.#{type}", ids)
exports.clearCompleted = (e, el) ->
todoIds = user.get('idLists.todo')
removed = false
_.each model.get('_todoList'), (task) ->
if task.completed
removed = true
user.del('tasks.'+task.id)
todoIds.splice(todoIds.indexOf(task.id), 1)
if removed
user.set('idLists.todo', todoIds)
exports.toggleDay = (e, el) ->
task = model.at(e.target)
if /active/.test($(el).attr('class')) # previous state, not current
task.set('repeat.' + $(el).attr('data-day'), false)
else
task.set('repeat.' + $(el).attr('data-day'), true)
exports.toggleTaskEdit = (e, el) ->
hideId = $(el).attr('data-hide-id')
toggleId = $(el).attr('data-toggle-id')
$(document.getElementById(hideId)).hide()
$(document.getElementById(toggleId)).toggle()
exports.toggleChart = (e, el) ->
hideSelector = $(el).attr('data-hide-id')
chartSelector = $(el).attr('data-toggle-id')
historyPath = $(el).attr('data-history-path')
$(document.getElementById(hideSelector)).hide()
$(document.getElementById(chartSelector)).toggle()
matrix = [['Date', 'Score']]
for obj in model.get(historyPath)
date = +new Date(obj.date)
readableDate = moment(date).format('MM/DD')
matrix.push [ readableDate, obj.value ]
data = google.visualization.arrayToDataTable matrix
options = {
title: 'History'
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
backgroundColor: 'whiteSmoke'
}
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
chart.draw(data, options)
exports.score = (e, el, next) ->
direction = $(el).attr('data-direction')
direction = 'up' if direction == 'true/'
direction = 'down' if direction == 'false/'
task = model.at $(el).parents('li')[0]
scoring.score(task.get('id'), direction)
revive = (batch) ->
# Reset stats
batch.set 'stats.hp', 50
batch.set 'stats.lvl', 1
batch.set 'stats.gp', 0
batch.set 'stats.exp', 0
# Reset items
batch.set 'items.armor', 0
batch.set 'items.weapon', 0
batch.set 'items.head', 0
batch.set 'items.shield', 0
# Reset item store
items.updateStore(model)
exports.revive = (e, el) ->
batch = new schema.BatchUpdate(model)
batch.startTransaction()
revive(batch)
batch.commit()
exports.reset = (e, el) ->
batch = new schema.BatchUpdate(model)
batch.startTransaction()
taskTypes = ['habit', 'daily', 'todo', 'reward']
batch.set 'tasks', {}
_.each taskTypes, (type) -> batch.set "idLists.#{type}", []
batch.set 'balance', 2 if user.get('balance') < 2 #only if they haven't manually bought tokens
revive(batch)
batch.commit()
browser.resetDom(model)
exports.closeKickstarterNofitication = (e, el) ->
user.set('flags.kickstarter', 'hide')
exports.customizeGender = (e, el) ->
user.set 'preferences.gender', $(el).attr('data-value')
exports.customizeHair = (e, el) ->
user.set 'preferences.hair', $(el).attr('data-value')
exports.customizeSkin = (e, el) ->
user.set 'preferences.skin', $(el).attr('data-value')
exports.customizeArmorSet = (e, el) ->
user.set 'preferences.armorSet', $(el).attr('data-value')

View file

@ -1,5 +1,5 @@
_ = require('underscore')
schema = require './schema'
character = require './character'
browser = require './browser'
partyQ = null

View file

@ -3,7 +3,7 @@ moment = require 'moment'
_ = require 'underscore'
helpers = require './helpers'
browser = require './browser'
schema = require './schema'
character = require './character'
items = require './items'
MODIFIER = .02 # each new level, armor, weapon add 2% modifier (this number may change)
user = undefined
@ -25,7 +25,6 @@ expModifier = (value, modifiers = {}) ->
dmg = items.items.weapon[weapon].modifier # each new weapon increases exp gain
dmg += (lvl-1) * MODIFIER # same for lvls
modified = value + (value * dmg)
debugger
return modified
###
@ -40,7 +39,6 @@ hpModifier = (value, modifiers = {}) ->
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
debugger
modified = value - (value * ac)
return modified
@ -106,7 +104,7 @@ score = (taskId, direction, times, batch, cron) ->
commit = false
unless batch?
commit = true
batch = new schema.BatchUpdate(model)
batch = new character.BatchUpdate(model)
batch.startTransaction()
obj = batch.obj()
@ -194,7 +192,7 @@ cron = () ->
today = +new Date
daysPassed = helpers.daysBetween(today, user.get('lastCron'))
if daysPassed > 0
batch = new schema.BatchUpdate(model)
batch = new character.BatchUpdate(model)
batch.startTransaction()
batch.set 'lastCron', today
obj = batch.obj()

156
src/app/tasks.coffee Normal file
View file

@ -0,0 +1,156 @@
scoring = require './scoring'
helpers = require './helpers'
_ = require 'underscore'
moment = require 'moment'
module.exports.view = (view) ->
view.fn 'taskClasses', (type, completed, value, repeat) ->
#TODO figure out how to just pass in the task model, so i can access all these properties from one object
classes = type
# show as completed if completed (naturally) or not required for today
if completed or (repeat and repeat[helpers.dayMapping[moment().day()]]==false)
classes += " completed"
switch
when value<-8 then classes += ' color-worst'
when value>=-8 and value<-5 then classes += ' color-worse'
when value>=-5 and value<-1 then classes += ' color-bad'
when value>=-1 and value<1 then classes += ' color-neutral'
when value>=1 and value<5 then classes += ' color-good'
when value>=5 and value<10 then classes += ' color-better'
when value>=10 then classes += ' color-best'
return classes
module.exports.app = (appExports, model) ->
user = model.at('_user')
user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) ->
return if passed? && passed.cron # Don't do this stuff on cron
direction = () ->
return 'up' if completed==true and previous == false
return 'down' if completed==false and previous == true
throw new Error("Direction neither 'up' nor 'down' on checkbox set.")
# Score the user based on todo task
task = user.at("tasks.#{i}")
scoring.score(i, direction())
appExports.addTask = (e, el, next) ->
type = $(el).attr('data-task-type')
list = model.at "_#{type}List"
newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1))
text = newModel.get()
# Don't add a blank todo
return if /^(\s)*$/.test(text)
newModel.set ''
switch type
when 'habit'
list.push {type: type, text: text, notes: '', value: 0, up: true, down: true}
when 'reward'
list.push {type: type, text: text, notes: '', value: 20 }
when 'daily'
list.push {type: type, text: text, notes: '', value: 0, repeat:{su:true,m:true,t:true,w:true,th:true,f:true,s:true}, completed: false }
when 'todo'
list.push {type: type, text: text, notes: '', value: 0, completed: false }
# list.on 'set', '*.completed', (i, completed, previous, isLocal) ->
# # Move the item to the bottom if it was checked off
# list.move i, -1 if completed && isLocal
appExports.del = (e, el) ->
# Derby extends model.at to support creation from DOM nodes
#task = model.at(e.target)
# FIXME normally that would work, and we'd later simply call `user.del task` (instead of that 4-liner down there)
# however, see https://github.com/lefnire/habitrpg/pull/226#discussion_r2810391
id = $(e.target).parents('li.task').attr('data-id')
return unless id?
task = user.at "tasks.#{id}"
type = task.get('type')
history = task.get('history')
if history and history.length>2
# prevent delete-and-recreate hack on red tasks
if task.get('value') < 0
result = confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).")
if result != true
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')
# prevent accidently deleting long-standing tasks
else
result = confirm("Are you sure you want to delete this task?")
return if result != true
#TODO bug where I have to delete from _users.tasks AND _{type}List,
# fix when query subscriptions implemented properly
$('[rel=tooltip]').tooltip('hide')
ids = user.get("idLists.#{type}")
ids.splice(ids.indexOf(id),1)
user.del('tasks.'+id)
user.set("idLists.#{type}", ids)
appExports.clearCompleted = (e, el) ->
todoIds = user.get('idLists.todo')
removed = false
_.each model.get('_todoList'), (task) ->
if task.completed
removed = true
user.del('tasks.'+task.id)
todoIds.splice(todoIds.indexOf(task.id), 1)
if removed
user.set('idLists.todo', todoIds)
appExports.toggleDay = (e, el) ->
task = model.at(e.target)
if /active/.test($(el).attr('class')) # previous state, not current
task.set('repeat.' + $(el).attr('data-day'), false)
else
task.set('repeat.' + $(el).attr('data-day'), true)
appExports.toggleTaskEdit = (e, el) ->
hideId = $(el).attr('data-hide-id')
toggleId = $(el).attr('data-toggle-id')
$(document.getElementById(hideId)).hide()
$(document.getElementById(toggleId)).toggle()
appExports.toggleChart = (e, el) ->
hideSelector = $(el).attr('data-hide-id')
chartSelector = $(el).attr('data-toggle-id')
historyPath = $(el).attr('data-history-path')
$(document.getElementById(hideSelector)).hide()
$(document.getElementById(chartSelector)).toggle()
matrix = [['Date', 'Score']]
for obj in model.get(historyPath)
date = +new Date(obj.date)
readableDate = moment(date).format('MM/DD')
matrix.push [ readableDate, obj.value ]
data = google.visualization.arrayToDataTable matrix
options = {
title: 'History'
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
backgroundColor: 'whiteSmoke'
}
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
chart.draw(data, options)
appExports.score = (e, el, next) ->
direction = $(el).attr('data-direction')
direction = 'up' if direction == 'true/'
direction = 'down' if direction == 'false/'
task = model.at $(el).parents('li')[0]
scoring.score(task.get('id'), direction)

View file

@ -47,7 +47,7 @@ strategies =
options =
domain: process.env.BASE_URL || 'http://localhost:3000'
allowPurl: true
schema: require('../app/schema').newUserObject()
schema: require('../app/character').newUserObject()
customAccessControl: habitrpgStore.customAccessControl
mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, ->

View file

@ -1,5 +1,5 @@
_ = require 'underscore'
schema = require "../app/schema"
character = require "../app/character"
module.exports.middleware = (req, res, next) ->
model = req.getModel()
@ -33,7 +33,7 @@ module.exports.app = (appExports, model) ->
Buy Reroll Button
###
appExports.buyReroll = (e, el, next) ->
batch = new schema.BatchUpdate(model)
batch = new character.BatchUpdate(model)
obj = model.get('_user')
batch.set 'balance', obj.balance-1
_.each obj.tasks, (task) -> batch.set("tasks.#{task.id}.value", 0) unless task.type == 'reward'