diff --git a/src/app/character.coffee b/src/app/character.coffee index a345fec55c..24f0cf477e 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -1,6 +1,8 @@ browser = require './browser' items = require './items' algos = require 'habitrpg-shared/script/algos' +misc = require './misc' +helpers = require 'habitrpg-shared/script/helpers' moment = require 'moment' _ = require 'lodash' @@ -31,26 +33,12 @@ module.exports.app = (appExports, model) -> items.updateStore(model) appExports.reset = (e, el) -> - batch = new BatchUpdate(model) - batch.startTransaction() - taskTypes = ['habit', 'daily', 'todo', 'reward'] - batch.set 'tasks', {} - taskTypes.forEach (type) -> batch.set "#{type}Ids", [] - #batch.set 'balance', 1 if user.get('balance') < 1 #only if they haven't manually bought gems - - # 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 - + misc.batchTxn model, (uObj, paths, batch) -> + batch.set 'tasks', {} + ['habit', 'daily', 'todo', 'reward'].forEach (type) -> batch.set("#{type}Ids", []) + _.each {hp:50, lvl:1, gp:0, exp:0}, (v,k) -> batch.set("stats.#{k}",v) + _.each {armor:0, weapon:0, head:0, shield:0}, (v,k) -> batch.set("items.#{k}",v) items.updateStore(model) - batch.commit() browser.resetDom(model) appExports.closeNewStuff = (e, el) -> @@ -68,71 +56,15 @@ 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() || 1) - batch.commit() + appExports.restoreSave = -> + misc.batchTxn model, (uObj, paths, batch) -> + $('#restore-form input').each -> + [path, val] = [$(this).attr('data-for'), parseInt($(this).val() || 1)] + batch.set(path,val) appExports.toggleHeader = (e, el) -> user.set 'preferences.hideHeader', !user.get('preferences.hideHeader') appExports.deleteAccount = (e, el) -> model.del "users.#{user.get('id')}", -> - window.location.href = "/logout" - -module.exports.BatchUpdate = BatchUpdate = (model) -> - user = model.at("_user") - transactionInProgress = false - obj = null - updates = {} - - { - user: user - - obj: -> - obj ?= model.get 'users.'+user.get('id') - return obj - - startTransaction: -> - # start a batch transaction - nothing between now and @commit() will be set immediately - transactionInProgress = true - model._dontPersist = true - @obj() - - ### - 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]; true - -# queue: (path, val) -> -# # Special function for setting object properties by string dot-notation. See http://stackoverflow.com/a/6394168/362790 -# arr = path.split('.') -# arr.reduce (curr, next, index) -> -# if (arr.length - 1) == index -# curr[next] = val -# 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 - # pass true if we have levelled to supress xp notification - user.set "update__", updates - transactionInProgress = false - updates = {} - } + location.href = "/logout" \ No newline at end of file diff --git a/src/app/index.coffee b/src/app/index.coffee index f83c9dd18a..d41b043f66 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -40,30 +40,25 @@ cleanupCorruptTasks = (model) -> delete tasks[key] true - batch = null + misc.batchTxn model, (uObj, paths, batch) -> + ## Task List Cleanup + ['habit','daily','todo','reward'].forEach (type) -> - ## Task List Cleanup - ['habit','daily','todo','reward'].forEach (type) -> + # 1. remove duplicates + # 2. restore missing zombie tasks back into list + idList = uObj["#{type}Ids"] + taskIds = _.pluck( _.where(tasks, {type:type}), 'id') + union = _.union idList, taskIds - # 1. remove duplicates - # 2. restore missing zombie tasks back into list - idList = user.get("#{type}Ids") - taskIds = _.pluck( _.where(tasks, {type:type}), 'id') - union = _.union idList, taskIds + # 2. remove empty (grey) tasks + preened = _.filter union, (id) -> id and _.contains(taskIds, id) - # 2. remove empty (grey) tasks - preened = _.filter union, (id) -> id and _.contains(taskIds, id) + # There were indeed issues found, set the new list + if !_.isEqual(idList, preened) + batch.set("#{type}Ids", preened) + console.error uObj.id + "'s #{type}s were corrupt." + true - # There were indeed issues found, set the new list - if !_.isEqual(idList, preened) - unless batch? - batch = new require('./character').BatchUpdate(model) - batch.startTransaction() - batch.set("#{type}Ids", preened) - console.error user.get('id') + "'s #{type}s were corrupt." - true - - batch.commit() if batch? ### Subscribe to the user, the users's party (meta info like party name, member ids, etc), and the party's members. 3 subscriptions. @@ -137,6 +132,7 @@ get '/', (page, model, params, next) -> ready (model) -> user = model.at('_user') browser = require './browser' + character = require './character' require('./character').app(exports, model) require('./tasks').app(exports, model) @@ -154,18 +150,14 @@ ready (model) -> ### Cron ### - uObj = misc.hydrate(user.get()) - # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks - _.each ['habit','daily','todo','reward'], (type) -> - uObj["#{type}s"] = _.where(uObj.tasks, {type:type}); true - paths = {} - algos.cron(uObj, {paths:paths}) - if !_.isEmpty(paths) - if paths['lastCron'] and _.size(paths) is 1 - user.set "lastCron", uObj.lastCron - else - lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation - _.each paths, (v,k) -> user.pass({cron:true}).set(k,helpers.dotGet(k, uObj)); true - if lostHp + misc.batchTxn model, (uObj, paths) -> + # habitrpg-shared/algos requires uObj.habits, uObj.dailys etc instead of uObj.tasks + _.each ['habit','daily','todo','reward'], (type) -> uObj["#{type}s"] = _.where(uObj.tasks, {type}); true + algos.cron uObj, {paths} + return if _.isEmpty(paths) or (paths['lastCron'] and _.size(paths) is 1) + if lostHp = delete paths['stats.hp'] # we'll set this manually so we can get a cool animation + setTimeout -> browser.resetDom(model) - setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 + user.set 'stats.hp', uObj.stats.hp + , 750 + ,{cron:true} \ No newline at end of file diff --git a/src/app/misc.coffee b/src/app/misc.coffee index 30a4246368..c93bd8d697 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -2,6 +2,24 @@ _ = require 'lodash' algos = require 'habitrpg-shared/script/algos' items = require('habitrpg-shared/script/items').items helpers = require('habitrpg-shared/script/helpers') +character = require('./character') + +module.exports.batchTxn = batchTxn = (model, cb, options) -> + user = model.at("_user") + uObj = hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 + batch = + set: (k,v) -> helpers.dotSet(k,v,uObj); paths[k] = true + get: (k) -> helpers.dotGet(k,uObj) + paths = {} + model._dontPersist = true + cb uObj, paths, batch + _.each paths, (v,k) -> user.pass({cron:options?.cron}).set(k,helpers.dotGet(k, uObj));true + model._dontPersist = false + # some hackery in our own branched racer-db-mongo, see findAndModify of lefnire/racer-db-mongo#habitrpg index.js + # pass true if we have levelled to supress xp notification + unless _.isEmpty paths + setOps = _.reduce paths, ((m,v,k)-> m[k] = helpers.dotGet(k,uObj);m), {} + user.set "update__", setOps ### algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the @@ -11,28 +29,24 @@ helpers = require('habitrpg-shared/script/helpers') ### module.exports.score = (model, taskId, direction, allowUndo=false) -> #return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 - user = model.at("_user") + batchTxn model, (uObj, paths) -> + tObj = uObj.tasks[taskId] - uObj = hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 - tObj = uObj.tasks[taskId] + # Stuff for undo + if allowUndo + tObjBefore = _.cloneDeep tObj + tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] + previousUndo = model.get('_undo') + clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId + timeoutId = setTimeout (-> model.del('_undo')), 20000 + model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId} - # Stuff for undo - if allowUndo - tObjBefore = _.cloneDeep tObj - tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] - previousUndo = model.get('_undo') - clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId - timeoutId = setTimeout (-> model.del('_undo')), 20000 - model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId} - - paths = {} - delta = algos.score(uObj, tObj, direction, {paths}) - _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)); true - model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus - if uObj._tmp?.drop and $? - model.set '_drop', uObj._tmp.drop - $('#item-dropped-modal').modal 'show' - delta + delta = algos.score(uObj, tObj, direction, {paths}) + model.set('_streakBonus', uObj._tmp.streakBonus) if uObj._tmp?.streakBonus + if uObj._tmp?.drop and $? + model.set '_drop', uObj._tmp.drop + $('#item-dropped-modal').modal 'show' + delta ### Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116 @@ -118,4 +132,4 @@ module.exports.viewHelpers = (view) -> if gType is 'party' findAttr @model.get("_party.challenges") else if gType is 'guild' - findAttr _.find(@model.get("_guilds"),{id:gid}).challenges \ No newline at end of file + findAttr _.find(@model.get("_guilds"),{id:gid}).challenges diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index a0efc2d088..2704bca3b9 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -134,19 +134,16 @@ module.exports.app = (appExports, model) -> appExports.undo = () -> undo = model.get '_undo' clearTimeout(undo.timeoutId) if undo?.timeoutId - batch = character.BatchUpdate(model) - batch.startTransaction() model.del '_undo' - _.each undo.stats, (val, key) -> batch.set "stats.#{key}", val; true + _.each undo.stats, (val, key) -> user.set "stats.#{key}", val; true taskPath = "tasks.#{undo.task.id}" _.each undo.task, (val, key) -> return true if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/ if key is 'completed' user.pass({cron:true}).set("#{taskPath}.completed",val) else - batch.set "#{taskPath}.#{key}", val + user.set "#{taskPath}.#{key}", val true - batch.commit() appExports.tasksToggleAdvanced = (e, el) -> $(el).next('.advanced-option').toggleClass('visuallyhidden') diff --git a/src/server/private.coffee b/src/server/private.coffee index 65127a51ad..ab72c8206d 100644 --- a/src/server/private.coffee +++ b/src/server/private.coffee @@ -1,5 +1,5 @@ _ = require 'lodash' -character = require "../app/character" +misc = require "../app/misc" module.exports.middleware = (req, res, next) -> model = req.getModel() @@ -33,12 +33,13 @@ module.exports.app = (appExports, model) -> ### Buy Reroll Button ### - appExports.buyReroll = (e, el, next) -> - 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 is 'reward';true - batch.commit() + appExports.buyReroll = -> + misc.batchTxn model, (uObj, paths, batch) -> + uObj.balance -= 1; paths['balance'] =1 + _.each uObj.tasks, (task) -> + batch.set("tasks.#{task.id}.value", 0) unless task.type is 'reward' + true + $('#reroll-modal').modal('hide') module.exports.routes = (expressApp) -> ###