From 5cff9709472c4c199f56e63a58c65420abaf25ba Mon Sep 17 00:00:00 2001 From: Shane Lister Date: Fri, 17 May 2013 16:42:26 -0700 Subject: [PATCH 01/36] Bacon Bacon Putting some bacon into the tavern NPC graphic. The people demand bacon. --- public/img/npcs/NPC-SpriteSheet.png | Bin 9741 -> 9887 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/public/img/npcs/NPC-SpriteSheet.png b/public/img/npcs/NPC-SpriteSheet.png index c672be8be8dbee251c6c3de68599d3ed11f48c00..1fb8529fc6be1fac9845efde28ac90480065df7c 100644 GIT binary patch literal 9887 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 literal 9741 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 qouB{c?R_rh@*fWDTsMcMi~TRh?MBZ4 From ae4c5723e0f49f0d087af8d32120a9d5ec177fc4 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 19 May 2013 15:02:04 +0100 Subject: [PATCH 02/36] chat: allow admins to remove other people's messages --- src/app/party.coffee | 4 +++- views/app/party.html | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/party.coffee b/src/app/party.coffee index f84762ae5d..732459f6c1 100644 --- a/src/app/party.coffee +++ b/src/app/party.coffee @@ -134,7 +134,9 @@ module.exports.app = (appExports, model, app) -> return next() unless e.keyCode is 13 appExports.tavernSendChat() - appExports.deleteChatMessage = (e) -> e.at().remove() #requires the {#with} + appExports.deleteChatMessage = (e) -> + if confirm("Delete chat message?") is true + e.at().remove() #requires the {#with} app.on 'render', (ctx) -> $('#party-tab-link').on 'shown', (e) -> diff --git a/views/app/party.html b/views/app/party.html index a2afcc2fa3..4c5fb1d073 100644 --- a/views/app/party.html +++ b/views/app/party.html @@ -63,6 +63,6 @@ class="label {{#if @message.npc}}label-success{{else if @message.contributor}}label-inverse{{else if equal(@message.uuid,_user.id)}}label-info{{/}} chat-message" rel='tooltip' title="{{@message.contributor}}{{@message.npc}}"> {{@message.user}} {{@message.text}} - {relativeDate(@message.timestamp, _currentTime)} - {{#if equal(@message.uuid,_user.id)}}{{#with @message}}{{/}}{{/}} + {{#if or(_user.backer.admin,equal(@message.uuid,_user.id))}}{{#with @message}}{{/}}{{/}} From 5d1368e794ab2f304c83984a5c73100cef0a43f2 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 19 May 2013 18:01:50 +0100 Subject: [PATCH 03/36] habitrpg-shared: got items working (see https://github.com/HabitRPG/habitrpg-shared/commit/1feb209c2486ff3dd3ebb84dccd4f300d1c31ed6) --- src/app/helpers.coffee | 2 +- src/app/items.coffee | 132 ++++------------------------------------- src/app/pets.coffee | 2 +- src/app/scoring.coffee | 2 +- src/app/unlock.coffee | 2 +- 5 files changed, 14 insertions(+), 126 deletions(-) diff --git a/src/app/helpers.coffee b/src/app/helpers.coffee index 16fa20b286..704eb64a36 100644 --- a/src/app/helpers.coffee +++ b/src/app/helpers.coffee @@ -2,7 +2,7 @@ moment = require 'moment' _ = require 'underscore' relative = require 'relative-date' algos = require './algos' -items = require('./items').items +items = require('habitrpg-shared/script/items').items sod = (timestamp, dayStart=0) -> #sanity-check reset-time (is it 24h time?) diff --git a/src/app/items.coffee b/src/app/items.coffee index abdd9b6ac2..75e824d34a 100644 --- a/src/app/items.coffee +++ b/src/app/items.coffee @@ -1,84 +1,11 @@ +items = require 'habitrpg-shared/script/items' _ = require 'underscore' -items = module.exports.items = - weapon: [ - {index: 0, text: "Training Sword", classes: "weapon_0", notes:'Training weapon.', strength: 0, value:0} - {index: 1, text: "Sword", classes:'weapon_1', notes:'Increases experience gain by 3%.', strength: 3, value:20} - {index: 2, text: "Axe", classes:'weapon_2', notes:'Increases experience gain by 6%.', strength: 6, value:30} - {index: 3, text: "Morningstar", classes:'weapon_3', notes:'Increases experience gain by 9%.', strength: 9, value:45} - {index: 4, text: "Blue Sword", classes:'weapon_4', notes:'Increases experience gain by 12%.', strength: 12, value:65} - {index: 5, text: "Red Sword", classes:'weapon_5', notes:'Increases experience gain by 15%.', strength: 15, value:90} - {index: 6, text: "Golden Sword", classes:'weapon_6', notes:'Increases experience gain by 18%.', strength: 18, value:120} - {index: 7, text: "Dark Souls Blade", classes:'weapon_7', notes:'Increases experience gain by 21%.', strength: 21, value:150} - ] - armor: [ - {index: 0, text: "Cloth Armor", classes: 'armor_0', notes:'Training armor.', defense: 0, value:0} - {index: 1, text: "Leather Armor", classes: 'armor_1', notes:'Decreases HP loss by 4%.', defense: 4, value:30} - {index: 2, text: "Chain Mail", classes: 'armor_2', notes:'Decreases HP loss by 6%.', defense: 6, value:45} - {index: 3, text: "Plate Mail", classes: 'armor_3', notes:'Decreases HP loss by 7%.', defense: 7, value:65} - {index: 4, text: "Red Armor", classes: 'armor_4', notes:'Decreases HP loss by 8%.', defense: 8, value:90} - {index: 5, text: "Golden Armor", classes: 'armor_5', notes:'Decreases HP loss by 10%.', defense: 10, value:120} - {index: 6, text: "Shade Armor", classes: 'armor_6', notes:'Decreases HP loss by 12%.', defense: 12, value:150} - ] - head: [ - {index: 0, text: "No Helm", classes: 'head_0', notes:'Training helm.', defense: 0, value:0} - {index: 1, text: "Leather Helm", classes: 'head_1', notes:'Decreases HP loss by 2%.', defense: 2, value:15} - {index: 2, text: "Chain Coif", classes: 'head_2', notes:'Decreases HP loss by 3%.', defense: 3, value:25} - {index: 3, text: "Plate Helm", classes: 'head_3', notes:'Decreases HP loss by 4%.', defense: 4, value:45} - {index: 4, text: "Red Helm", classes: 'head_4', notes:'Decreases HP loss by 5%.', defense: 5, value:60} - {index: 5, text: "Golden Helm", classes: 'head_5', notes:'Decreases HP loss by 6%.', defense: 6, value:80} - {index: 6, text: "Shade Helm", classes: 'head_6', notes:'Decreases HP loss by 7%.', defense: 7, value:100} - ] - shield: [ - {index: 0, text: "No Shield", classes: 'shield_0', notes:'No Shield.', defense: 0, value:0} - {index: 1, text: "Wooden Shield", classes: 'shield_1', notes:'Decreases HP loss by 3%', defense: 3, value:20} - {index: 2, text: "Buckler", classes: 'shield_2', notes:'Decreases HP loss by 4%.', defense: 4, value:35} - {index: 3, text: "Enforced Shield", classes: 'shield_3', notes:'Decreases HP loss by 5%.', defense: 5, value:55} - {index: 4, text: "Red Shield", classes: 'shield_4', notes:'Decreases HP loss by 7%.', defense: 7, value:70} - {index: 5, text: "Golden Shield", classes: 'shield_5', notes:'Decreases HP loss by 8%.', defense: 8, value:90} - {index: 6, text: "Tormented Skull", classes: 'shield_6', notes:'Decreases HP loss by 9%.', defense: 9, value:120} - ] - potion: {type: 'potion', text: "Potion", notes: "Recover 15 HP", value: 25, classes: 'potion'} - reroll: {type: 'reroll', text: "Re-Roll", classes: 'reroll', notes: "Resets your task values back to 0 (yellow). Useful when everything's red and it's hard to stay alive.", value:0 } - - pets: [ - {text: 'Wolf', name: 'Wolf', value: 3} - {text: 'Tiger Cub', name: 'TigerCub', value: 3} - #{text: 'Polar Bear Cub', name: 'PolarBearCub', value: 3} #commented out because there are no polarbear modifiers yet, special drop? - {text: 'Panda Cub', name: 'PandaCub', value: 3} - {text: 'Lion Cub', name: 'LionCub', value: 3} - {text: 'Fox', name: 'Fox', value: 3} - {text: 'Flying Pig', name: 'FlyingPig', value: 3} - {text: 'Dragon', name: 'Dragon', value: 3} - {text: 'Cactus', name: 'Cactus', value: 3} - {text: 'Bear Cub', name: 'BearCub', value: 3} - ] - - hatchingPotions: [ - {text: 'Base', name: 'Base', notes: "Hatches your pet in it's base form.", value: 1} - {text: 'White', name: 'White', notes: 'Turns your animal into a White pet.', value: 2} - {text: 'Desert', name: 'Desert', notes: 'Turns your animal into a Desert pet.', value: 2} - {text: 'Red', name: 'Red', notes: 'Turns your animal into a Red pet.', value: 3} - {text: 'Shade', name: 'Shade', notes: 'Turns your animal into a Shade pet.', value: 3} - {text: 'Skeleton', name: 'Skeleton', notes: 'Turns your animal into a Skeleton.', value: 3} - {text: 'Zombie', name: 'Zombie', notes: 'Turns your animal into a Zombie.', value: 4} - {text: 'Cotton Candy Pink', name: 'CottonCandyPink', notes: 'Turns your animal into a Cotton Candy Pink pet.', value: 4} - {text: 'Cotton Candy Blue', name: 'CottonCandyBlue', notes: 'Turns your animal into a Cotton Candy Blue pet.', value: 4} - {text: 'Golden', name: 'Golden', notes: 'Turns your animal into a Golden pet.', value: 5} - ] - -# add "type" to each item, so we can reference that as "weapon" or "armor" in the html -_.each ['weapon', 'armor', 'head', 'shield'], (key) -> - _.each items[key], (item) -> item.type = key - -_.each items.pets, (pet) -> pet.notes = 'Find a hatching potion to pour on this egg, and one day it will hatch into a loyal pet.' -_.each items.hatchingPotions, (hatchingPotion) -> hatchingPotion.notes = "Pour this on an egg, and it will hatch as a #{hatchingPotion.text} pet." - ### server exports ### module.exports.server = (model) -> - model.set '_items', items + model.set '_items', items.items updateStore(model) ### @@ -87,35 +14,11 @@ module.exports.server = (model) -> module.exports.app = (appExports, model) -> user = model.at '_user' - appExports.buyItem = (e, el, next) -> - user = model.at '_user' - #TODO: this should be working but it's not. so instead, i'm passing all needed values as data-attrs - # item = model.at(e.target) - - gp = user.get 'stats.gp' + appExports.buyItem = (e, el) -> [type, value, index] = [ $(el).attr('data-type'), $(el).attr('data-value'), $(el).attr('data-index') ] - - return if gp < value - # make sure deduction doesn't happen unless purchase was successful, see https://github.com/lefnire/habitrpg/issues/233 - deductGP = -> user.set 'stats.gp', gp - value - if type == 'weapon' - user.set 'items.weapon', index, deductGP - updateStore model - else if type == 'armor' - user.set 'items.armor', index, deductGP - updateStore model - else if type == 'head' - user.set 'items.head', index, deductGP - updateStore model - else if type == 'shield' - user.set 'items.shield', index, deductGP - updateStore model - else if type == 'potion' - hp = user.get 'stats.hp' - hp += 15 - hp = 50 if hp > 50 - user.set 'stats.hp', hp, deductGP - + if changes = items.buyItem(user.get(), type, value, index) + _.each changes, (v,k) -> user.set k,v + updateStore(model) appExports.activateRewardsTab = -> model.set '_activeTabRewards', true @@ -124,26 +27,11 @@ module.exports.app = (appExports, model) -> model.set '_activeTabPets', true model.set '_activeTabRewards', false -### - update store -### module.exports.updateStore = updateStore = (model) -> - model.setNull '_items.next', {} - user = model.at('_user') - equipped = user.get('items') - - _.each ['weapon', 'armor', 'shield', 'head'], (type) -> - i = parseInt(equipped?[type] || 0) + 1 - showNext = true - if i is items[type].length - 1 - if (type in ['armor', 'shield', 'head']) - showNext = user.get('backer.tier') >= 45 # backer armor - else - showNext = user.get('backer.tier') >= 70 # backer weapon - else if i is items[type].length - showNext = false - - model.set "_items.next.#{type}", if showNext then items[type][i] else {hide:true} + nextItems = items.updateStore(model.get('_user')) + _.each nextItems, (v,k) -> model.set("_items.next.#{k}",v) + + diff --git a/src/app/pets.coffee b/src/app/pets.coffee index 9f43d82aeb..64f8d71ed7 100644 --- a/src/app/pets.coffee +++ b/src/app/pets.coffee @@ -1,6 +1,6 @@ _ = require 'underscore' { randomVal } = require './helpers' -{ pets, hatchingPotions } = require('./items').items +{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items ### app exports diff --git a/src/app/scoring.coffee b/src/app/scoring.coffee index 5d15faf2bb..f2ccfa0abe 100644 --- a/src/app/scoring.coffee +++ b/src/app/scoring.coffee @@ -3,7 +3,7 @@ _ = require 'underscore' { randomVal } = helpers = require './helpers' browser = require './browser' character = require './character' -items = require './items' +items = require 'habitrpg-shared/script/items' { pets, hatchingPotions } = items.items algos = require './algos' diff --git a/src/app/unlock.coffee b/src/app/unlock.coffee index 1680e7a9b1..8772efac9e 100644 --- a/src/app/unlock.coffee +++ b/src/app/unlock.coffee @@ -1,6 +1,6 @@ _ = require 'underscore' { randomVal } = require './helpers' -{ pets, hatchingPotions } = require('./items').items +{ pets, hatchingPotions } = require('habitrpg-shared/script/items').items ### Listeners to enabled flags, set notifications to the user when they've unlocked features From 6a36f4bd1d22a0b6974c7f1c15bf546ee6a9d9fe Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 19 May 2013 18:53:13 +0100 Subject: [PATCH 04/36] habitrpg-shared: helpers now working https://github.com/HabitRPG/habitrpg-shared/commit/d42ac5d53ca077ebc746d8d0f48cc1f51e37ae63 --- src/app/helpers.coffee | 224 ------------------------------------- src/app/index.coffee | 3 +- src/app/party.coffee | 3 +- src/app/pets.coffee | 2 +- src/app/profile.coffee | 2 +- src/app/scoring.coffee | 2 +- src/app/tasks.coffee | 2 +- src/app/unlock.coffee | 2 +- src/app/viewHelpers.coffee | 59 ++++++++++ 9 files changed, 66 insertions(+), 233 deletions(-) delete mode 100644 src/app/helpers.coffee create mode 100644 src/app/viewHelpers.coffee diff --git a/src/app/helpers.coffee b/src/app/helpers.coffee deleted file mode 100644 index 704eb64a36..0000000000 --- a/src/app/helpers.coffee +++ /dev/null @@ -1,224 +0,0 @@ -moment = require 'moment' -_ = require 'underscore' -relative = require 'relative-date' -algos = require './algos' -items = require('habitrpg-shared/script/items').items - -sod = (timestamp, dayStart=0) -> - #sanity-check reset-time (is it 24h time?) - dayStart = 0 unless (dayStart = parseInt(dayStart)) and (0 <= dayStart <= 24) - moment(timestamp).startOf('day').add('h', dayStart) - -# Absolute diff between two dates -daysBetween = (yesterday, now, dayStart) -> Math.abs sod(yesterday, dayStart).diff(now, 'days') - -dayMapping = {0:'su',1:'m',2:'t',3:'w',4:'th',5:'f',6:'s'} - -shouldDo = (day, repeat, dayStart=0) -> - return false unless repeat - now = +new Date - selected = repeat[dayMapping[sod(day, dayStart).day()]] - return selected unless moment(day).isSame(now,'d') - if dayStart <= moment(now).hour() # we're past the dayStart mark, is it due today? - return selected - else # we're not past dayStart mark, check if it was due "yesterday" - yesterday = moment(now).subtract(1,'d').day() - return repeat[dayMapping[yesterday]] - -# http://stackoverflow.com/questions/2532218/pick-random-property-from-a-javascript-object -# obj: object -# returns random property (the value) -randomVal = (obj) -> - result = undefined - count = 0 - for key, val of obj - result = val if Math.random() < (1 / ++count) - result - -removeWhitespace = (str) -> - return '' unless str - str.replace /\s/g, '' - -username = (auth, override) -> - #some people define custom profile name in Avatar -> Profile - return override if override - - 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' - -viewHelpers = (view) -> - view.fn "percent", (x, y) -> - x=1 if x==0 - Math.round(x/y*100) - - view.fn "round", Math.round - view.fn "floor", Math.floor - view.fn "ceil", Math.ceil - view.fn "lt", (a, b) -> a < b - view.fn 'gt', (a, b) -> a > b - view.fn "mod", (a, b) -> parseInt(a) % parseInt(b) == 0 - view.fn 'removeWhitespace', removeWhitespace - view.fn "notEqual", (a, b) -> (a != b) - view.fn "and", -> _.reduce arguments, (cumm, curr) -> cumm && curr - view.fn "or", -> _.reduce arguments, (cumm, curr) -> cumm || curr - view.fn "truarr", (num) -> num-1 - view.fn 'count', (arr) -> arr?.length or 0 - - view.fn "gems", (gp) -> return gp/0.25 - - view.fn "encodeiCalLink", (uid, apiToken) -> - loc = window?.location.host or process.env.BASE_URL - encodeURIComponent "http://#{loc}/v1/users/#{uid}/calendar.ics?apiToken=#{apiToken}" - - - ### - User - ### - view.fn "username", (auth, override) -> username(auth, override) - view.fn "tnl", algos.tnl - - ### - Items - ### - view.fn 'equipped', (type, item=0, preferences={gender:'m', armorSet:'v1'}, backerTier=0) -> - {gender, armorSet} = preferences - item = parseInt(item) - backerTier = parseInt(backerTier) - - switch type - when'armor' - if item > 5 - return 'armor_6' if backerTier >= 45 - item = 5 # set them back if they're trying to cheat - if gender is 'f' - return if (item is 0) then "f_armor_#{item}_#{armorSet}" else "f_armor_#{item}" - else - return "m_armor_#{item}" - - when 'head' - if item > 5 - return 'head_6' if backerTier >= 45 - item = 5 - if gender is 'f' - return if (item > 1) then "f_head_#{item}_#{armorSet}" else "f_head_#{item}" - else - return "m_head_#{item}" - - when 'shield' - if item > 5 - return 'shield_6' if backerTier >= 45 - item = 5 - return "#{preferences.gender}_shield_#{item}" - - when 'weapon' - if item > 6 - return 'weapon_7' if backerTier >= 70 - item = 6 - return "#{preferences.gender}_weapon_#{item}" - - view.fn "gold", (num) -> - if num - return (num).toFixed(1).split('.')[0] - else - return "0" - - view.fn "silver", (num) -> - if num - (num).toFixed(2).split('.')[1] - else - return "00" - - ### - Tasks - ### - view.fn 'taskClasses', (task, filters, dayStart, lastCron, showCompleted=false) -> - return unless task - {type, completed, value, repeat} = task - - # completed / remaining toggle - return 'hidden' if (type is 'todo') and (completed != showCompleted) - - for filter, enabled of filters - if enabled and not task.tags?[filter] - # All the other classes don't matter - return 'hidden' - - classes = type - - # show as completed if completed (naturally) or not required for today - if type in ['todo', 'daily'] - if completed or (type is 'daily' and !shouldDo(+new Date, task.repeat, dayStart)) - classes += " completed" - else - classes += " uncompleted" - else if type is 'habit' - classes += ' habit-wide' if task.down and task.up - - if value < -20 - classes += ' color-worst' - else if value < -10 - classes += ' color-worse' - else if value < -1 - classes += ' color-bad' - else if value < 1 - classes += ' color-neutral' - else if value < 5 - classes += ' color-good' - else if value < 10 - classes += ' color-better' - else - classes += ' color-best' - return classes - - view.fn 'ownsPet', (pet, userPets) -> _.isArray(userPets) and userPets.indexOf(pet) != -1 - - view.fn 'friendlyTimestamp', (timestamp) -> moment(timestamp).format('MM/DD h:mm:ss a') - - view.fn 'newChatMessages', (messages, lastMessageSeen) -> - return false unless messages?.length > 0 - messages?[0] and (messages[0].id != lastMessageSeen) - - view.fn 'indexOf', (str1, str2) -> - return false unless str1 && str2 - str1.indexOf(str2) != -1 - - view.fn 'relativeDate', relative - - view.fn 'noTags', (tags) -> - _.isEmpty(tags) or _.isEmpty(_.filter( tags, (t) -> t ) ) - - view.fn 'appliedTags', (userTags, taskTags) -> - arr = [] - _.each userTags, (t) -> - return unless t? - arr.push(t.name) if taskTags?[t.id] - arr.join(', ') - - view.fn 'userStr', (level) -> - str = (level-1) / 2 - view.fn 'totalStr', (level, weapon=0) -> - str = (level-1) / 2 - totalStr = (str + items.weapon[weapon].strength) - view.fn 'userDef', (level) -> - def = (level-1) / 2 - view.fn 'totalDef', (level, armor=0, helm=0, shield=0) -> - def = (level-1) / 2 - totalDef = (def + items.armor[armor].defense + items.head[helm].defense + items.shield[shield].defense) - view.fn 'itemText', (type, item=0) -> items[type][parseInt(item)].text - view.fn 'itemStat', (type, item=0) -> if type is 'weapon' then items[type][parseInt(item)].strength else items[type][parseInt(item)].defense - - -# view.fn 'activeFilters', (filters) -> -# debugger -# (_.find filters, (f) -> f)? - - - -module.exports = { viewHelpers, removeWhitespace, randomVal, daysBetween, shouldDo, username } diff --git a/src/app/index.coffee b/src/app/index.coffee index 5ef298b46f..6ff0c4e920 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -17,8 +17,7 @@ i18n.localize app, urlScheme: false checkHeader: true -helpers = require './helpers' -helpers.viewHelpers view +require('./viewHelpers').setup view _ = require('underscore') diff --git a/src/app/party.coffee b/src/app/party.coffee index 732459f6c1..533f1539d9 100644 --- a/src/app/party.coffee +++ b/src/app/party.coffee @@ -1,10 +1,9 @@ _ = require('underscore') -helpers = require './helpers' +helpers = require('habitrpg-shared/script/helpers') module.exports.app = (appExports, model, app) -> character = require './character' browser = require './browser' - helpers = require './helpers' _currentTime = model.at '_currentTime' diff --git a/src/app/pets.coffee b/src/app/pets.coffee index 64f8d71ed7..d3cb5e4330 100644 --- a/src/app/pets.coffee +++ b/src/app/pets.coffee @@ -1,5 +1,5 @@ _ = require 'underscore' -{ randomVal } = require './helpers' +{ randomVal } = require 'habitrpg-shared/script/helpers' { pets, hatchingPotions } = require('habitrpg-shared/script/items').items ### diff --git a/src/app/profile.coffee b/src/app/profile.coffee index 6df4cad588..b5260472d4 100644 --- a/src/app/profile.coffee +++ b/src/app/profile.coffee @@ -1,6 +1,6 @@ character = require './character' browser = require './browser' -helpers = require './helpers' +helpers = require 'habitrpg-shared/script/helpers' module.exports.app = (appExports, model) -> user = model.at('_user') diff --git a/src/app/scoring.coffee b/src/app/scoring.coffee index f2ccfa0abe..95c52294e6 100644 --- a/src/app/scoring.coffee +++ b/src/app/scoring.coffee @@ -1,6 +1,6 @@ moment = require 'moment' _ = require 'underscore' -{ randomVal } = helpers = require './helpers' +{ randomVal } = helpers = require 'habitrpg-shared/script/helpers' browser = require './browser' character = require './character' items = require 'habitrpg-shared/script/items' diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index d9b2811bf9..50d5c273c5 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -1,5 +1,5 @@ scoring = require './scoring' -helpers = require './helpers' +helpers = require 'habitrpg-shared/script/helpers' _ = require 'underscore' moment = require 'moment' character = require './character' diff --git a/src/app/unlock.coffee b/src/app/unlock.coffee index 8772efac9e..26cb1f4a08 100644 --- a/src/app/unlock.coffee +++ b/src/app/unlock.coffee @@ -1,5 +1,5 @@ _ = require 'underscore' -{ randomVal } = require './helpers' +{ randomVal } = require 'habitrpg-shared/script/helpers' { pets, hatchingPotions } = require('habitrpg-shared/script/items').items ### diff --git a/src/app/viewHelpers.coffee b/src/app/viewHelpers.coffee new file mode 100644 index 0000000000..c224dd308b --- /dev/null +++ b/src/app/viewHelpers.coffee @@ -0,0 +1,59 @@ +_ = require 'underscore' +algos = require './algos' +items = require('habitrpg-shared/script/items').items +helpers = require('habitrpg-shared/script/helpers') + +module.exports.setup = (view) -> + + #misc + view.fn "percent", (x, y) -> + x=1 if x==0 + Math.round(x/y*100) + view.fn 'indexOf', (str1, str2) -> + return false unless str1 && str2 + str1.indexOf(str2) != -1 + view.fn "round", Math.round + view.fn "floor", Math.floor + view.fn "ceil", Math.ceil + view.fn "lt", (a, b) -> a < b + view.fn 'gt', (a, b) -> a > b + view.fn "mod", (a, b) -> parseInt(a) % parseInt(b) == 0 + view.fn "notEqual", (a, b) -> (a != b) + view.fn "and", -> _.reduce arguments, (cumm, curr) -> cumm && curr + view.fn "or", -> _.reduce arguments, (cumm, curr) -> cumm || curr + view.fn "truarr", (num) -> num-1 + view.fn 'count', (arr) -> arr?.length or 0 + + #iCal + view.fn "encodeiCalLink", helpers.encodeiCalLink + + #User + view.fn "gems", (balance) -> return balance/0.25 + view.fn "username", helpers.username + view.fn "tnl", algos.tnl + view.fn 'equipped', helpers.equipped + view.fn "gold", helpers.gold + view.fn "silver", helpers.silver + + #Stats + view.fn 'userStr', helpers.userStr + view.fn 'totalStr', helpers.totalStr + view.fn 'userDef', helpers.userDef + view.fn 'totalDef', helpers.totalDef + view.fn 'itemText', helpers.itemText + view.fn 'itemStat', helpers.itemStat + + #Pets + view.fn 'ownsPet', helpers.ownsPet + + #Tasks + view.fn 'taskClasses', helpers.taskClasses + + #Chat + view.fn 'friendlyTimestamp',helpers.friendlyTimestamp + view.fn 'newChatMessages', helpers.newChatMessages + view.fn 'relativeDate', helpers.relativeDate + + #Tags + view.fn 'noTags', helpers.noTags + view.fn 'appliedTags', helpers.appliedTags \ No newline at end of file From 128eb8adb7b040913ad19ddedddb470487e5161d Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 19 May 2013 20:43:48 +0100 Subject: [PATCH 05/36] tavern: add submit bug / feature buttons up top, fixes #996 --- views/app/game-pane.html | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/views/app/game-pane.html b/views/app/game-pane.html index 0e9728d943..87d7ab224a 100644 --- a/views/app/game-pane.html +++ b/views/app/game-pane.html @@ -100,7 +100,7 @@
  • LFG Posts

  • Tutorial

  • FAQ

  • -
  • Submit a Bug

  • +
  • Report a Problem

  • Request a Feature

  • Community Forum

  • @@ -109,10 +109,22 @@

    Tavern Talk & LFG

    -
    -
    - -
    +
    + +
    +
    +
    + +
    +
    +
    +
      {#each _tavern.chat.messages as :message} From 0b445df909a0754d68f37b51df734e613f8ff9b5 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sun, 19 May 2013 23:48:51 +0100 Subject: [PATCH 06/36] habitrpg-shared: *almost* working wth new scoring (https://github.com/HabitRPG/habitrpg-shared/commit/c0cf9780fe8e17211d4749b9c3eb2a0fd389db64) - need to fix for cron, API, and other misc bugs --- package.json | 5 +-- src/app/algos.coffee | 74 ------------------------------------ src/app/character.coffee | 3 +- src/app/debug.coffee | 2 +- src/app/index.coffee | 2 +- src/app/tasks.coffee | 62 +++++++++++++++++------------- src/app/viewHelpers.coffee | 2 +- src/server/api.coffee | 7 ++-- src/server/deprecated.coffee | 1 - 9 files changed, 46 insertions(+), 112 deletions(-) delete mode 100644 src/app/algos.coffee diff --git a/package.json b/package.json index fd5f3a31de..f60ceb65d5 100644 --- a/package.json +++ b/package.json @@ -16,9 +16,7 @@ "guid": "*", "moment": "*", "stripe": "*", - "lodash": "1.0.x", "coffee-script": "1.4.x", - "underscore": "*", "mongoskin": "*", "nconf": "*", "icalendar": "git://github.com/lefnire/node-icalendar#master", @@ -26,7 +24,8 @@ "resolve": "~0.2.3", "expect.js": "~0.2.0", "derby-i18n": "git://github.com/switz/derby-i18n#master", - "relative-date": "~1.1.1" + "relative-date": "~1.1.1", + "lodash": "~1.2.1" }, "private": true, "subdomain": "habitrpg", diff --git a/src/app/algos.coffee b/src/app/algos.coffee deleted file mode 100644 index 1e9ab67cdf..0000000000 --- a/src/app/algos.coffee +++ /dev/null @@ -1,74 +0,0 @@ -XP = 15 -HP = 2 - -priorityValue = module.exports.priorityValue = (priority='!') -> - switch priority - when '!' then 1 - when '!!' then 1.5 - when '!!!' then 2 - else 1 - -module.exports.tnl = (level) -> - if level >= 100 - value = 0 - else - value = Math.round(((Math.pow(level,2)*0.25)+(10 * level) + 139.75)/10)*10 # round to nearest 10 - return value - -### - Calculates Exp modificaiton based on level and weapon strength - {value} task.value for exp gain - {weaponStrength) weapon strength - {level} current user level - {priority} user-defined priority multiplier -### -module.exports.expModifier = (value, weaponStr, level, priority='!') -> - str = (level-1) / 2 # ultimately get this from user - totalStr = (str + weaponStr) / 100 - strMod = 1 + totalStr - exp = value * XP * strMod * priorityValue(priority) - return Math.round(exp) - -### - Calculates HP modification based on level and armor defence - {value} task.value for hp loss - {armorDefense} defense from armor - {helmDefense} defense from helm - {level} current user level - {priority} user-defined priority multiplier -### -module.exports.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority='!') -> - def = (level-1) / 2 # ultimately get this from user? - totalDef = (def + armorDef + helmDef + shieldDef) / 100 #ultimate get this from user - defMod = 1 - totalDef - hp = value * HP * defMod * priorityValue(priority) - return Math.round(hp * 10)/10 # round to 1dp - -### - Future use - {priority} user-defined priority multiplier -### -module.exports.gpModifier = (value, modifier, priority='!', streak, model) -> - val = value * modifier * priorityValue(priority) - if streak and model - streakBonus = streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc - afterStreak = val * streakBonus - model.set('_streakBonus', afterStreak - val) if (val > 0) # can we do this without model? just global emit? - return afterStreak - else - return val - -### - Calculates the next task.value based on direction - Uses a capped inverse log y=.95^x, y>= -5 - {currentValue} the current value of the task - {direction} up or down -### -module.exports.taskDeltaFormula = (currentValue, direction) -> - if currentValue < -47.27 then currentValue = -47.27 - else if currentValue > 21.27 then currentValue = 21.27 - delta = Math.pow(0.9747,currentValue) - return delta if direction is 'up' - return -delta - - diff --git a/src/app/character.coffee b/src/app/character.coffee index 15b7563625..87159d2b56 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -1,6 +1,6 @@ browser = require './browser' items = require './items' -algos = require './algos' +algos = require 'habitrpg-shared/script/algos' moment = require 'moment' _ = require 'underscore' @@ -22,7 +22,6 @@ module.exports.app = (appExports, model) -> owned = user.get('items') # unless they're already at 0-everything if parseInt(owned.armor)>0 or parseInt(owned.head)>0 or parseInt(owned.shield)>0 or parseInt(owned.weapon)>0 - console.log 'test' # find a random item to lose until loseThisItem #candidate = {0:'items.armor', 1:'items.head', 2:'items.shield', 3:'items.weapon', 4:'stats.gp'}[Math.random()*5|0] diff --git a/src/app/debug.coffee b/src/app/debug.coffee index a11541a5fc..32a1b82589 100644 --- a/src/app/debug.coffee +++ b/src/app/debug.coffee @@ -1,5 +1,5 @@ moment = require 'moment' -algos = require './algos' +algos = require 'habitrpg-shared/script/algos' module.exports.app = (appExports, model) -> user = model.at('_user') diff --git a/src/app/index.coffee b/src/app/index.coffee index 6ff0c4e920..a3c1275b4a 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -117,7 +117,7 @@ ready (model) -> user = model.at('_user') model.setNull '_user.apiToken', derby.uuid() - require('./scoring').cron(model) + #FIXME require('habitrpg-shared/script/algos').cron(user) require('./character').app(exports, model) require('./tasks').app(exports, model) diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 50d5c273c5..44a237dd20 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -1,12 +1,35 @@ -scoring = require './scoring' +algos = require 'habitrpg-shared/script/algos' helpers = require 'habitrpg-shared/script/helpers' -_ = require 'underscore' +_ = require 'lodash' moment = require 'moment' -character = require './character' + +### + Make scoring functionality available to the app +### module.exports.app = (appExports, model) -> + character = require './character' user = model.at('_user') + ### + algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the + object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first + clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination), + perform the updates while tracking paths, then all the values at those paths + ### + score = (user, taskId, direction) -> + uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets + tObj = uObj.tasks[taskId] + + # Stuff for undo + tObjBefore = _.cloneDeep tObj + tObjBefore.completed = !tObjBefore.completed if tObj.type in ['daily', 'todo'] + setUndo uObj.stats, tObjBefore # set previous state for undo + + paths = {} + algos.score(uObj, tObj, direction, {paths:paths}) + _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)) + appExports.addTask = (e, el) -> type = $(el).attr('data-task-type') newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1)) @@ -28,26 +51,24 @@ module.exports.app = (appExports, model) -> model.unshift "_#{type}List", newTask newModel.set '' - appExports.del = (e, el) -> + appExports.del = (e) -> # Derby extends model.at to support creation from DOM nodes task = e.at() id = task.get('id') history = task.get('history') - if history and history.length>2 + 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 + if confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).") is true task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox" - scoring.score(model, id, direction:'down') + score(user, id, 'down') + else + return # Cancel. Don't delete, don't hurt user # prevent accidently deleting long-standing tasks else - result = confirm("Are you sure you want to delete this task?") - return if result != true + return unless confirm("Are you sure you want to delete this task?") is true #TODO bug where I have to delete from _users.tasks AND _{type}List, # fix when query subscriptions implemented properly @@ -113,14 +134,9 @@ module.exports.app = (appExports, model) -> Call scoring functions for habits & rewards (todos & dailies handled below) ### appExports.score = (e, el) -> - task= model.at $(el).parents('li')[0] - taskObj = task.get() + task = model.at $(el).parents('li')[0] direction = $(el).attr('data-direction') - - # set previous state for undo - setUndo _.clone(user.get('stats')), _.clone(taskObj) - - scoring.score(model, taskObj.id, direction) + score(user, task.get('id'), direction) ### This is how we handle appExports.score for todos & dailies. Due to Derby's special handling of `checked={:task.completd}`, @@ -129,13 +145,7 @@ module.exports.app = (appExports, model) -> user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) -> return if passed? && passed.cron # Don't do this stuff on cron direction = if completed then 'up' else 'down' - - # set previous state for undo - taskObj = _.clone user.get("tasks.#{i}") - taskObj.completed = previous - setUndo _.clone(user.get('stats')), taskObj - - scoring.score(model, i, direction) + score(user, i, direction) ### Undo diff --git a/src/app/viewHelpers.coffee b/src/app/viewHelpers.coffee index c224dd308b..25a0d6af16 100644 --- a/src/app/viewHelpers.coffee +++ b/src/app/viewHelpers.coffee @@ -1,5 +1,5 @@ _ = require 'underscore' -algos = require './algos' +algos = require 'habitrpg-shared/script/algos' items = require('habitrpg-shared/script/items').items helpers = require('habitrpg-shared/script/helpers') diff --git a/src/server/api.coffee b/src/server/api.coffee index b7b8af798b..1b96fd194c 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -1,9 +1,9 @@ express = require 'express' router = new express.Router() -scoring = require '../app/scoring' _ = require 'underscore' -{ tnl } = require '../app/algos' +algos = require 'habitrpg-shared/script/algos' +{ tnl } = algos validator = require 'derby-auth/node_modules/validator' check = validator.check sanitize = validator.sanitize @@ -255,7 +255,8 @@ scoreTask = (req, res, next) -> model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" model.at("_#{type}List").push task - delta = scoring.score(model, taskId, direction) + #FIXME + delta = algos.score(user.get(), taskId, direction) result = model.get '_user.stats' result.delta = delta res.json result diff --git a/src/server/deprecated.coffee b/src/server/deprecated.coffee index 3fcef2c8a3..68d5d2030d 100644 --- a/src/server/deprecated.coffee +++ b/src/server/deprecated.coffee @@ -1,7 +1,6 @@ express = require 'express' router = new express.Router() -scoring = require '../app/scoring' _ = require 'underscore' icalendar = require('icalendar') api = require './api' From 83ac2adb79af289d2942ee238f4a9d6c04fee81d Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 20 May 2013 00:53:18 +0100 Subject: [PATCH 07/36] habithabitrpg-shared: cron mostly working wiht habitrpg-shared https://github.com/HabitRPG/habitrpg-shared/commit/7805a2ba264f48f0ac80bce552bec4fefdf109e9 --- src/app/index.coffee | 26 ++- src/app/scoring.coffee | 360 ----------------------------------------- 2 files changed, 22 insertions(+), 364 deletions(-) delete mode 100644 src/app/scoring.coffee diff --git a/src/app/index.coffee b/src/app/index.coffee index a3c1275b4a..b19e177305 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -19,7 +19,9 @@ i18n.localize app, require('./viewHelpers').setup view -_ = require('underscore') +_ = require('lodash') +algos = require 'habitrpg-shared/script/algos' +helpers = require 'habitrpg-shared/script/helpers' ### Cleanup task-corruption (null tasks, rogue/invisible tasks, etc) @@ -35,6 +37,7 @@ cleanupCorruptTasks = (model) -> unless task?.id? and task?.type? user.del("tasks.#{key}") delete tasks[key] + true batch = null @@ -57,6 +60,7 @@ cleanupCorruptTasks = (model) -> batch.startTransaction() batch.set("#{type}Ids", preened) console.error user.get('id') + "'s #{type}s were corrupt." + true batch.commit() if batch? @@ -75,7 +79,7 @@ setupSubscriptions = (page, model, params, next, cb) -> 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] + _.each paths, (path, idx) -> model.ref path, refs[idx+1]; true unless model.get('_user') console.error "User not found - this shouldn't be happening!" return page.redirect('/logout') #delete model.session.userId @@ -108,6 +112,7 @@ get '/', (page, model, params, next) -> #refLists _.each ['habit', 'daily', 'todo', 'reward'], (type) -> model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" + true page.render() @@ -117,8 +122,6 @@ ready (model) -> user = model.at('_user') model.setNull '_user.apiToken', derby.uuid() - #FIXME require('habitrpg-shared/script/algos').cron(user) - require('./character').app(exports, model) require('./tasks').app(exports, model) require('./items').app(exports, model) @@ -130,3 +133,18 @@ ready (model) -> require('./browser').app(exports, model, app) require('./unlock').app(exports, model) require('./filters').app(exports, model) + + ### + Cron + ### + #FIXME optimize this - don't deepClone first, check if need to run first + uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets + # Set it up so it's uObj.habits, uObj.dailys etc instead of uObj.tasks (it's what habitrpg-shared/algos requires) + _.each ['habit','daily','todo','reward'], (type) -> + uObj["#{type}s"] = _.where(uObj.tasks, {type:type}); true + paths = {} + algos.cron(uObj, paths) + 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 + setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 1000 \ No newline at end of file diff --git a/src/app/scoring.coffee b/src/app/scoring.coffee deleted file mode 100644 index 95c52294e6..0000000000 --- a/src/app/scoring.coffee +++ /dev/null @@ -1,360 +0,0 @@ -moment = require 'moment' -_ = require 'underscore' -{ randomVal } = helpers = require 'habitrpg-shared/script/helpers' -browser = require './browser' -character = require './character' -items = require 'habitrpg-shared/script/items' -{ pets, hatchingPotions } = items.items -algos = require './algos' - -MODIFIER = algos.MODIFIER # each new level, armor, weapon add 2% modifier (this mechanism will change) - -### - Drop System -### -randomDrop = (model, delta, priority, streak=0) -> - user = model.at('_user') - - # limit drops to 2 / day - user.setNull 'items.lastDrop', - date: +moment().subtract('d',1) # trick - set it to yesterday on first run, that way they can get drops today - count: 0 - reachedDropLimit = (helpers.daysBetween(user.get('items.lastDrop.date'), +new Date, user.get('preferences.dayStart')) is 0) and user.get('items.lastDrop.count') >= 2 - return if reachedDropLimit - - # % chance of getting a pet or meat - chanceMultiplier = Math.abs(delta) - chanceMultiplier *= algos.priorityValue(priority) # multiply chance by reddness - chanceMultiplier += streak # streak bonus - - if user.get('flags.dropsEnabled') and Math.random() < (.05 * chanceMultiplier) - # current breakdown - 3% (adjustable) chance on drop - # If they got a drop: 50% chance of egg, 50% Hatching Potion. If hatchingPotion, broken down further even further - rarity = Math.random() - - # Egg, 40% chance - if rarity > .6 - drop = randomVal(pets) - user.push 'items.eggs', drop - drop.type = 'Egg' - drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}" - - # Hatching Potion, 60% chance - break down by rarity even more. FIXME this may not be the best method, so revisit - else - acceptableDrops = [] - - # Tier 5 (Blue Moon Rare) - if rarity < .1 - acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue', 'Golden'] - - # Tier 4 (Very Rare) - else if rarity < .2 - acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton', 'Zombie', 'CottonCandyPink', 'CottonCandyBlue'] - - # Tier 3 (Rare) - else if rarity < .3 - acceptableDrops = ['Base', 'White', 'Desert', 'Red', 'Shade', 'Skeleton'] - - # Tier 2 (Scarce) - else if rarity < .4 - acceptableDrops = ['Base', 'White', 'Desert'] - # Tier 1 (Common) - else - acceptableDrops = ['Base'] - - acceptableDrops = _.filter(hatchingPotions, (hatchingPotion) -> hatchingPotion.name in acceptableDrops) - drop = randomVal acceptableDrops - user.push 'items.hatchingPotions', drop.name - drop.type = 'HatchingPotion' - drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}" - - model.set '_drop', drop - $('#item-dropped-modal').modal 'show' - - user.set 'items.lastDrop.date', +new Date - user.incr 'items.lastDrop.count' - -# {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) -> - user = model.at '_user' - - 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, streak} = taskObj - priority = taskObj.priority or '!' - - # 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 = algos.taskDeltaFormula(value, direction) - value += nextDelta if adjustvalue - delta += nextDelta - - addPoints = -> - level = user.get('stats.lvl') - weaponStrength = items.items.weapon[user.get('items.weapon')].strength - exp += algos.expModifier(delta,weaponStrength,level, priority) / 2 # / 2 hack for now bcause people leveling too fast - if streak - gp += algos.gpModifier(delta, 1, priority, streak, model) - else - gp += algos.gpModifier(delta, 1, priority) - - 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 - hp += algos.hpModifier(delta,armorDefense,helmDefense,shieldDefense,level, priority) - - switch type - when 'habit' - calculateDelta() - # 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() - batch.set "#{taskPath}.streak", 0 - else - calculateDelta(false) - if delta != 0 - addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes - if direction is 'up' - streak = if streak then streak + 1 else 1 - else - streak = if streak then streak - 1 else 0 - batch.set "#{taskPath}.streak", streak - taskObj.streak = streak - - - when 'todo' - if cron? #cron - calculateDelta() - #don't touch stats on cron - else - calculateDelta() - 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, exp, gp }, batch - - # Commit - 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() - - # Drop system - randomDrop(model, delta, priority, streak) if direction is 'up' - - 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.hp <= 0 - - if newStats.hp? - # Game Over - if newStats.hp <= 0 - obj.stats.hp = 0 # signifies dead - return - else - obj.stats.hp = newStats.hp - - if newStats.exp? - tnl = algos.tnl(obj.stats.lvl) - #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 - - obj.stats.exp = newStats.exp - #if silent - #console.log("pushing silent :" + obj.stats.exp) - #user.pass(true).set('stats.exp', obj.stats.exp) - - # Set flags when they unlock features - # NOTE we have to first model.set() the flag to true, then AFTER that obj.flags.flag = true - # The reason is model.on() listeners still track object references, so if obj.flags.flags = true and then we - # model.set(), the second argument of .on() listeners will be true (in otherwords, before/after tests will fail) - 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.dropsEnabled and obj.stats.lvl >= 4 - batch.set 'flags.dropsEnabled', true - obj.flags.dropsEnabled = 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 - -### - 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 - - lastCron = user.get('lastCron') - # New user (!lastCron, lastCron==new) or it got busted somehow, maybe they went to a different timezone - if !lastCron? or lastCron is 'new' or moment(lastCron).isAfter(today) - user.set('lastCron', +new Date) - return - - daysMissed = helpers.daysBetween(user.get('lastCron'), today, user.get('preferences.dayStart')) - if daysMissed > 0 - - # User is resting at the inn. Used to be we un-checked each daily without performing calculation (see commits before fb29e35) - # but to prevent abusing the inn (http://goo.gl/GDb9x) we now do *not* calculate dailies, and simply set lastCron to today - if user.get('flags.rest') is true - return user.set('lastCron', today) - - batch = new character.BatchUpdate(model) - batch.startTransaction() - obj = batch.obj() - batch.set 'lastCron', today - - 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 - scheduleMisses = daysMissed - # for dailys which have repeat dates, need to calculate how many they've missed according to their own schedule - if (type is 'daily') and repeat - scheduleMisses = 0 - _.times daysMissed, (n) -> - thatDay = moment(today).subtract('days', n+1) - scheduleMisses++ if helpers.shouldDo(thatDay, repeat, obj.preferences?.dayStart) is true - score(model, id, 'down', scheduleMisses, batch, true) if scheduleMisses > 0 - - if type == 'daily' - if completed #set OHV for completed dailies - newValue = taskObj.value + algos.taskDeltaFormula(taskObj.value,'up') - batch.set "tasks.#{taskObj.id}.value", newValue - - taskObj.history ?= [] - taskObj.history.push { date: +new Date, value: taskObj.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 - else if type is 'habit' # slowly reset 'onlies' value to 0 - if taskObj.up==false or taskObj.down==false - if Math.abs(taskObj.value) < 0.1 - batch.set "tasks.#{taskObj.id}.value", 0 - else - batch.set "tasks.#{taskObj.id}.value", taskObj.value / 2 - - # 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 -} From d870325169299bdc50374bac6b0c91307421f4b0 Mon Sep 17 00:00:00 2001 From: Slappybag Date: Mon, 20 May 2013 09:59:36 +0100 Subject: [PATCH 08/36] Margin on Buttons for #996 & 128eb8a --- styles/app/game-pane.styl | 3 +++ views/app/game-pane.html | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/styles/app/game-pane.styl b/styles/app/game-pane.styl index a8313f0636..1c2f139106 100644 --- a/styles/app/game-pane.styl +++ b/styles/app/game-pane.styl @@ -34,3 +34,6 @@ background-position: -1200px 0px width: 40px height: 40px + +.buttonList li + margin: 5px; \ No newline at end of file diff --git a/views/app/game-pane.html b/views/app/game-pane.html index 87d7ab224a..e36441b16f 100644 --- a/views/app/game-pane.html +++ b/views/app/game-pane.html @@ -111,7 +111,7 @@

      Tavern Talk & LFG

      -
        +
        • FAQ
        • Report a Problem
        • Request a Feature
        • From 9c173e3a08aa45abe3e20a2f861cbd20212f297b Mon Sep 17 00:00:00 2001 From: Stan Lindsey Date: Mon, 20 May 2013 11:08:04 +0200 Subject: [PATCH 09/36] HP potion description clarify. People getting confused about its use. --- src/app/items.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/items.coffee b/src/app/items.coffee index abdd9b6ac2..dd6f9a64c3 100644 --- a/src/app/items.coffee +++ b/src/app/items.coffee @@ -38,7 +38,7 @@ items = module.exports.items = {index: 5, text: "Golden Shield", classes: 'shield_5', notes:'Decreases HP loss by 8%.', defense: 8, value:90} {index: 6, text: "Tormented Skull", classes: 'shield_6', notes:'Decreases HP loss by 9%.', defense: 9, value:120} ] - potion: {type: 'potion', text: "Potion", notes: "Recover 15 HP", value: 25, classes: 'potion'} + potion: {type: 'potion', text: "Potion", notes: "Recover 15 HP, instant use.", value: 25, classes: 'potion'} reroll: {type: 'reroll', text: "Re-Roll", classes: 'reroll', notes: "Resets your task values back to 0 (yellow). Useful when everything's red and it's hard to stay alive.", value:0 } pets: [ From ed057799a335696ee8fa99a59eb74e5487d4329b Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 20 May 2013 10:27:53 +0100 Subject: [PATCH 10/36] put coin_single_[silver|gold].png back in (thought we weren't using anymore, i was wrong) --- public/img/coin_single_gold.png | Bin 0 -> 1384 bytes public/img/coin_single_silver.png | Bin 0 -> 1293 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 public/img/coin_single_gold.png create mode 100644 public/img/coin_single_silver.png diff --git a/public/img/coin_single_gold.png b/public/img/coin_single_gold.png new file mode 100644 index 0000000000000000000000000000000000000000..2ed267760e1ae71b857d6540332c940582d4382b GIT binary patch literal 1384 zcmV-u1(*7XP)p2<#A0 zR)1lPZA$d&x&AYXgi{h@$0X)Gh(`6STH}KK?z-O3wu>t#-yOIle%fR-s*kNM!wm?? z-@n;^p}9H!MoZ5gv=M^QC}e^lAm+3BmEh<_79Q588}aqoPSDy zeR}rDL;Jd0hYvh)G7w3pP+giqMbE$}WueBq5KeZ(%+J6!im;4jn6)AT;Wz@J1j4aa zgjzaKURcEKYgcPy_p%3{e}8a-``r!!cIoB5-s6uS8Qs%=0_DOqa&x!g*|Z7IMZEhM zMWGd?dpF>^mWZau7hemLmEi06_o42s#r8% zUYRREYqi`(S3xsNRgC=BUF2>DJ#_)!matm6>0a1eHRX;tk4R@Fpdt_Urp(R_c6 z4{k%`o1jQ6y`I2&4Jg5;CNY_ZlINIv;5(1CAA}ldMS12|Dk`g9dcrV?CK5n$cS1zK zr6!=LD&1#^ydCq|0_ADYr?fHSdI?E z$U~C+Xpjv_32eU=^IbdoGNOrgG9dZ+RN$BvWG(Fb^;Q9E(!tj_wn?{`SW6#>Hn+nd z9q@a9OTcoGSr{KhD3L-i)*|vRlaMB^eSu2Vf|Be(kfNbR640V8L@Go+n1JgzsOdH6 zb3|4l0n;lKOOmZN%#I8R+gqLmH0;s2%H{CQTdyWMI}l5Dphml{7PClqcOpMIgsNVl zXqX}mtkH)3bZDWVKznbO*nPA)g+MHh-1IGEC#EqrU%AYEtO?q>PDnAv?(1)#7)tC) zt6hDEMHFN;KuRx@_6)d|37bO9RvN&H3`l$$wt2@OK_;y2@`&pnMV@0!10*P#PMfSQULq7sZjiZ*Q`Dkcazwm}h_C&!Dh z%_?%^bbb`7c<<}6)7-Z2W77!m3%CBjQ+=uax1QPe3Dvfm>fMcaM~WO+@F>qNl_f7t zaSn){;rOScCsf7F=nDlbPvv3NZ0o{TV=sI+v@k#m<{Pbe%frYg!J9_s$1nB0^60MU zb4ox%ES*Fwl|Ud8rn7;G!^sl_TuO^Y0xYAPEuxaqiL8yA)1}W|{OHC9WM`VN;5+xW zbsTd|5t<2Ygye;1A3n9eBhsIYsQW{j)V8()nRpwPo5_}}(b3t`!1>QduF#Dffh)g2 zFt>XguX!NxTPHn%AnqeXP&X)H-un3YTqP8Jl>D4K|MrP!b-&Pjs6MfchU1ekef;M~ qvj1&tScdoGZ`#iPf*k^X2rvMBaX=e}JjC4q0000U3cc~Tg!ILPg_-7c+#i0>)o08-n^ObtxfrS9xieSaKYn}z$F2( z_{L;1ok(#>T$GSv$$0(S4@kHmpFvJUBS`wa@4K zvc0_>IyyR_uFef+vslros;Ur;Mqz1b36__aVRv`;n~8~uujtV~C1C3B@4xBy`-4M6 zLsldJs;0rw(Get*Nw7#3u-dF}a1e)VCJX6I1{_rmaLNv_+3etSI>9UmL;%Le$FrN8 zo3|$?CpReWc?g*L`uduCdV1EnySpKi%|a;r3RFd5@3?DgL9$4&^H&HoBt_==yoT3# z3@kIp1i@^EhPUd$X0^i1%nVFVPv4rFnp!tV7;!5Vp{1>@El9y*`}^?n%-Tu zzP1WgvdnGNf-Ql3ak9R00_FWAdc9uWQdmfWo@q+G&+7q)!@>5M?rG&l!MR*6aK86G zwx=1==?qAcR46i(wtAY~7$hE#1C>F!To{BD>=e4rV&_mD z7}*fGK_#Jkg8&s1l~6-H_P(OAOrRg;9QTkrF}Gi4;9MlK?u2 z{IakBZnuj;QF1JWkAHlO890F2nrdDPG7}uT-NsT+pukk5>wl~jGD6Dfa@E58{8Mrt zIrS{Sp`esL`Z*AIc<0VLaQ)gQ-b8OR`_b8~aO zIRB7EjsK|YW88Zy$k+i_t~4->KW!)|0tc~u2!(bbl}^F@>>OupvKxtzjIJ?ai!X!KSz3S1CG5JfYruKo_seqF@2 zOy>fDK>y0h$|62~g~*M_*BM}Ha9mjcu*@O>c9OWD%J}N zhs23h{3zlijyeAGC)xitDz@Q8{7t+3UvNp_Zvh4XI+2bnb47O700000NkvXXu0mjf D--cZc literal 0 HcmV?d00001 From f412a2020319f5656ca90b15fd96d0996569e7d1 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Mon, 20 May 2013 10:56:38 +0100 Subject: [PATCH 11/36] habitrpg-shared: cron bug-fixes --- src/app/index.coffee | 26 ++++++++++++++------------ src/app/tasks.coffee | 8 ++++---- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/src/app/index.coffee b/src/app/index.coffee index b19e177305..23e060dca9 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -121,6 +121,7 @@ get '/', (page, model, params, next) -> ready (model) -> user = model.at('_user') model.setNull '_user.apiToken', derby.uuid() + browser = require './browser' require('./character').app(exports, model) require('./tasks').app(exports, model) @@ -130,21 +131,22 @@ ready (model) -> require('./pets').app(exports, model) require('../server/private').app(exports, model) require('./debug').app(exports, model) if model.flags.nodeEnv != 'production' - require('./browser').app(exports, model, app) + browser.app(exports, model, app) require('./unlock').app(exports, model) require('./filters').app(exports, model) ### Cron ### - #FIXME optimize this - don't deepClone first, check if need to run first - uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets - # Set it up so it's uObj.habits, uObj.dailys etc instead of uObj.tasks (it's what habitrpg-shared/algos requires) - _.each ['habit','daily','todo','reward'], (type) -> - uObj["#{type}s"] = _.where(uObj.tasks, {type:type}); true - paths = {} - algos.cron(uObj, paths) - 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 - setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 1000 \ No newline at end of file + if algos.shouldCron(user) + uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets + # 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) + 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 + browser.resetDom(model) + setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 \ No newline at end of file diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 44a237dd20..caace2a741 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -17,7 +17,7 @@ module.exports.app = (appExports, model) -> clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination), perform the updates while tracking paths, then all the values at those paths ### - score = (user, taskId, direction) -> + score = (taskId, direction) -> uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets tObj = uObj.tasks[taskId] @@ -62,7 +62,7 @@ module.exports.app = (appExports, model) -> if task.get('value') < 0 if confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).") is true task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox" - score(user, id, 'down') + score(id, 'down') else return # Cancel. Don't delete, don't hurt user @@ -136,7 +136,7 @@ module.exports.app = (appExports, model) -> appExports.score = (e, el) -> task = model.at $(el).parents('li')[0] direction = $(el).attr('data-direction') - score(user, task.get('id'), direction) + score(task.get('id'), direction) ### This is how we handle appExports.score for todos & dailies. Due to Derby's special handling of `checked={:task.completd}`, @@ -145,7 +145,7 @@ module.exports.app = (appExports, model) -> user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) -> return if passed? && passed.cron # Don't do this stuff on cron direction = if completed then 'up' else 'down' - score(user, i, direction) + score(i, direction) ### Undo From 105087c823f9198c491ae99a09a834bad10cbb58 Mon Sep 17 00:00:00 2001 From: Philip How Date: Mon, 20 May 2013 19:52:37 +0200 Subject: [PATCH 12/36] Removes +4 Gems on Reset exploit --- src/app/character.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/character.coffee b/src/app/character.coffee index 15b7563625..b517b0a33b 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -38,7 +38,7 @@ module.exports.app = (appExports, model) -> taskTypes = ['habit', 'daily', 'todo', 'reward'] batch.set 'tasks', {} _.each taskTypes, (type) -> batch.set "#{type}Ids", [] - batch.set 'balance', 1 if user.get('balance') < 1 #only if they haven't manually bought gems + #batch.set 'balance', 1 if user.get('balance') < 1 #only if they haven't manually bought gems # Reset stats batch.set 'stats.hp', 50 From cc097c32f755cdd17ee49033d9565c6ead8b0390 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 21 May 2013 12:53:05 +0100 Subject: [PATCH 13/36] clean up of preen_cron, which I'm re-enstating --- migrations/20130212_preen_cron.js | 36 ++++++++++++++----------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/migrations/20130212_preen_cron.js b/migrations/20130212_preen_cron.js index fc45cc0f13..59c74ed911 100644 --- a/migrations/20130212_preen_cron.js +++ b/migrations/20130212_preen_cron.js @@ -12,17 +12,16 @@ */ var un_registered = { - "auth.local": {$exists: false}, - "auth.facebook": {$exists: false} -}; -var registered = { - $or: [ - { 'auth.local': { $exists: true }}, - { 'auth.facebook': { $exists: true }} - ] -}; - -var today = +(new Date); + "auth.local": {$exists: false}, + "auth.facebook": {$exists: false} + }, + registered = { + $or: [ + { 'auth.local': { $exists: true }}, + { 'auth.facebook': { $exists: true }} + ] + }, + today = +new Date; // isValidDate = (d) -> // return false if Object::toString.call(d) isnt "[object Date]" @@ -30,12 +29,9 @@ var today = +(new Date); db.users.find(un_registered).forEach(function(user) { - var diff, lastCron; if (!user) return; if (!!user.lastCron) { - lastCron = new Date(user.lastCron); - diff = Math.abs(moment(today).startOf('day').diff(moment(lastCron).startOf('day'), "days")); - if (diff > 3) { + if (Math.abs(moment(today).diff(user.lastCron, 'd')) > 5) { return db.users.remove({_id:user._id}); } } else { @@ -49,8 +45,8 @@ db.users.find(un_registered).forEach(function(user) { * revisit if needs be */ /*db.sessions.find().forEach(function(sess){ - var uid = JSON.parse(sess.session).userId; - if (!uid || db.users.count({_id:uid}) === 0) { - db.sessions.remove({_id:sess._id}); - } -});*/ \ No newline at end of file + var uid = JSON.parse(sess.session).userId; + if (!uid || db.users.count({_id:uid}) === 0) { + db.sessions.remove({_id:sess._id}); + } + });*/ \ No newline at end of file From ff2fedda48fc26792a7031c8a3392286253d2df8 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 21 May 2013 14:33:31 +0100 Subject: [PATCH 14/36] add ability to restore lost streaks, also add int() view helper function (pretty cool actually, https://groups.google.com/forum/?fromgroups#!topic/derbyjs/otFCLu2p7yY) --- src/app/helpers.coffee | 3 +++ views/app/tasks.html | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/app/helpers.coffee b/src/app/helpers.coffee index 16fa20b286..ccc2884bc6 100644 --- a/src/app/helpers.coffee +++ b/src/app/helpers.coffee @@ -70,6 +70,9 @@ viewHelpers = (view) -> view.fn "or", -> _.reduce arguments, (cumm, curr) -> cumm || curr view.fn "truarr", (num) -> num-1 view.fn 'count', (arr) -> arr?.length or 0 + view.fn 'int', + get: (num) -> num + set: (num) -> [parseInt(num)] view.fn "gems", (gp) -> return gp/0.25 diff --git a/views/app/tasks.html b/views/app/tasks.html index cf404e8445..0d3dd7e3c2 100644 --- a/views/app/tasks.html +++ b/views/app/tasks.html @@ -285,6 +285,9 @@
      + + Restore Streak + {/} From bc49e4668c6ac9c888611aba39082b829dcac4ca Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Tue, 21 May 2013 14:39:23 +0100 Subject: [PATCH 15/36] only show restore-streak on dailies --- views/app/tasks.html | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/views/app/tasks.html b/views/app/tasks.html index 0d3dd7e3c2..2d50891f41 100644 --- a/views/app/tasks.html +++ b/views/app/tasks.html @@ -286,8 +286,10 @@
      - Restore Streak - + {{#if equal(:task.type,'daily')}} + Restore Streak + + {{/}} {/} From 2230ce66888cf0ee49ff09fd1a472fecba833c7a Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 00:40:46 +0100 Subject: [PATCH 16/36] habitpg-shared: move viewHelpers to misc, add hydrate() function (see https://github.com/codeparty/racer/issues/116) to prevent data-loss on user.get() --- src/app/index.coffee | 13 ++++++------- src/app/{viewHelpers.coffee => misc.coffee} | 16 ++++++++++++++-- src/app/tasks.coffee | 11 +++++++---- 3 files changed, 27 insertions(+), 13 deletions(-) rename src/app/{viewHelpers.coffee => misc.coffee} (80%) diff --git a/src/app/index.coffee b/src/app/index.coffee index 23e060dca9..58b760ac26 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -17,7 +17,7 @@ i18n.localize app, urlScheme: false checkHeader: true -require('./viewHelpers').setup view +require('./misc').viewHelpers view _ = require('lodash') algos = require 'habitrpg-shared/script/algos' @@ -135,11 +135,9 @@ ready (model) -> require('./unlock').app(exports, model) require('./filters').app(exports, model) - ### - Cron - ### - if algos.shouldCron(user) - uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets + cron = -> + #return setTimeout(cron, 1) if model._txnQueue.length > 0 + uObj = _.cloneDeep misc.hydrate(user.get()) # need to clone, else derby won't catch model.set()'s after obj property sets # 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 @@ -149,4 +147,5 @@ ready (model) -> _.each paths, (v,k) -> user.pass({cron:true}).set(k,helpers.dotGet(k, uObj)); true if lostHp browser.resetDom(model) - setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 \ No newline at end of file + setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 + cron() if algos.shouldCron {lastCron: user.get('lastCron'), preferences: user.get('preferences')} \ No newline at end of file diff --git a/src/app/viewHelpers.coffee b/src/app/misc.coffee similarity index 80% rename from src/app/viewHelpers.coffee rename to src/app/misc.coffee index 25a0d6af16..b39399aa97 100644 --- a/src/app/viewHelpers.coffee +++ b/src/app/misc.coffee @@ -1,9 +1,18 @@ -_ = require 'underscore' +_ = require 'lodash' algos = require 'habitrpg-shared/script/algos' items = require('habitrpg-shared/script/items').items helpers = require('habitrpg-shared/script/helpers') -module.exports.setup = (view) -> +### + Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116 +### +module.exports.hydrate = hydrate = (spec, hydrated={}) -> + if _.isPlainObject(spec) + hydrated[k] = hydrate(v, hydrated[k]) for k,v of spec + hydrated + else spec + +module.exports.viewHelpers = (view) -> #misc view.fn "percent", (x, y) -> @@ -23,6 +32,9 @@ module.exports.setup = (view) -> view.fn "or", -> _.reduce arguments, (cumm, curr) -> cumm || curr view.fn "truarr", (num) -> num-1 view.fn 'count', (arr) -> arr?.length or 0 + view.fn 'int', + get: (num) -> num + set: (num) -> parseInt(num) #iCal view.fn "encodeiCalLink", helpers.encodeiCalLink diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index caace2a741..2d18c95a77 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -2,6 +2,7 @@ algos = require 'habitrpg-shared/script/algos' helpers = require 'habitrpg-shared/script/helpers' _ = require 'lodash' moment = require 'moment' +misc = require './misc' ### @@ -18,12 +19,14 @@ module.exports.app = (appExports, model) -> perform the updates while tracking paths, then all the values at those paths ### score = (taskId, direction) -> - uObj = _.cloneDeep user.get() # need to clone, else derby won't catch model.set()'s after obj property sets +# return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 + + uObj = _.cloneDeep misc.hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 tObj = uObj.tasks[taskId] # Stuff for undo tObjBefore = _.cloneDeep tObj - tObjBefore.completed = !tObjBefore.completed if tObj.type in ['daily', 'todo'] + tObjBefore.completed = !tObj.completed if tObj.type in ['daily', 'todo'] setUndo uObj.stats, tObjBefore # set previous state for undo paths = {} @@ -134,9 +137,9 @@ module.exports.app = (appExports, model) -> Call scoring functions for habits & rewards (todos & dailies handled below) ### appExports.score = (e, el) -> - task = model.at $(el).parents('li')[0] + id = $(el).parents('li').attr('data-id') direction = $(el).attr('data-direction') - score(task.get('id'), direction) + score(id, direction) ### This is how we handle appExports.score for todos & dailies. Due to Derby's special handling of `checked={:task.completd}`, From 598f2f6c460b7e3c3907238fbd7842bd5689828c Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 11:07:56 +0100 Subject: [PATCH 17/36] habitrpg-shared: don't cloneDeep our user object for the time being. we can't access __proto__ objects as a result (see https://github.com/codeparty/racer/issues/116), the negative side-effect is this cause certain notifications not to show, because model.on() listeners can't tell the difference between $spec object properties & model.get() --- src/app/index.coffee | 5 +++-- src/app/tasks.coffee | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/index.coffee b/src/app/index.coffee index 58b760ac26..7ce10c3b1a 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -17,7 +17,8 @@ i18n.localize app, urlScheme: false checkHeader: true -require('./misc').viewHelpers view +misc = require('./misc') +misc.viewHelpers view _ = require('lodash') algos = require 'habitrpg-shared/script/algos' @@ -137,7 +138,7 @@ ready (model) -> cron = -> #return setTimeout(cron, 1) if model._txnQueue.length > 0 - uObj = _.cloneDeep misc.hydrate(user.get()) # need to clone, else derby won't catch model.set()'s after obj property sets + uObj = misc.hydrate(user.get()) # need to clone, else derby won't catch model.set()'s after obj property sets # 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 diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 2d18c95a77..dec9b27381 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -21,13 +21,13 @@ module.exports.app = (appExports, model) -> score = (taskId, direction) -> # return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 - uObj = _.cloneDeep misc.hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 + uObj = misc.hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 tObj = uObj.tasks[taskId] # Stuff for undo tObjBefore = _.cloneDeep tObj - tObjBefore.completed = !tObj.completed if tObj.type in ['daily', 'todo'] - setUndo uObj.stats, tObjBefore # set previous state for undo + tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] + setUndo _.cloneDeep(uObj.stats), tObjBefore # set previous state for undo paths = {} algos.score(uObj, tObj, direction, {paths:paths}) From a31eb184a2f981dafa9f0cfb2ab54bf95a01dcb1 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 11:27:16 +0100 Subject: [PATCH 18/36] habitrpg-shared: don't forget the package req! --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index f60ceb65d5..b13182dcdd 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "version": "0.0.0-151", "main": "./server.js", "dependencies": { + "habitrpg-shard": "git://github.com/HabitRPG/habitrpg-shared#master", "derby": "git://github.com/lefnire/derby#habitrpg", "racer": "git://github.com/lefnire/racer#habitrpg", "racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg", From 1596808c25bc20a3f152b7e09da8ef28bb56cdc9 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 11:29:24 +0100 Subject: [PATCH 19/36] move everything to lodash, since we're using it for other things anyway (cloneDeep, findIndex, etc). make sure to return `true` explicitly after all _.each functions! if you accidently return false (happens more often than you'd think), it kills the loop. As a result, I'm using .forEach() native function where i can for simplicity, but many of our _.each iterate object keys --- src/app/browser.coffee | 7 +++---- src/app/character.coffee | 9 ++++----- src/app/filters.coffee | 4 ++-- src/app/index.coffee | 4 ++-- src/app/items.coffee | 6 +++--- src/app/party.coffee | 2 +- src/app/pets.coffee | 2 +- src/app/tasks.coffee | 9 +++++---- src/app/unlock.coffee | 2 +- src/server/api.coffee | 4 ++-- src/server/deprecated.coffee | 3 ++- src/server/private.coffee | 4 ++-- 12 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/app/browser.coffee b/src/app/browser.coffee index 76656ca7d1..685621c993 100644 --- a/src/app/browser.coffee +++ b/src/app/browser.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' moment = require 'moment' ### @@ -35,7 +35,7 @@ loadJavaScripts = (model) -> ### setupSortable = (model) -> unless (model.get('_mobileDevice') is true) #don't do sortable on mobile - _.each ['habit', 'daily', 'todo', 'reward'], (type) -> + ['habit', 'daily', 'todo', 'reward'].forEach (type) -> $("ul.#{type}s").sortable dropOnEmpty: false cursor: "move" @@ -109,8 +109,7 @@ setupTour = (model) -> $('.main-herobox').popover('destroy') #remove previous popovers tour = new Tour() - _.each tourSteps, (step) -> - tour.addStep _.defaults step, {html:true} + tourSteps.forEach (step) -> tour.addStep _.defaults step, {html:true} tour._current = 0 if isNaN(tour._current) #bootstrap-tour bug tour.start() diff --git a/src/app/character.coffee b/src/app/character.coffee index 227c83d172..d87632f9c5 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -3,8 +3,7 @@ items = require './items' algos = require 'habitrpg-shared/script/algos' moment = require 'moment' -_ = require 'underscore' -lodash = require 'lodash' +_ = require 'lodash' derby = require 'derby' module.exports.app = (appExports, model) -> @@ -36,7 +35,7 @@ module.exports.app = (appExports, model) -> batch.startTransaction() taskTypes = ['habit', 'daily', 'todo', 'reward'] batch.set 'tasks', {} - _.each taskTypes, (type) -> batch.set "#{type}Ids", [] + 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 @@ -105,7 +104,7 @@ userSchema = module.exports.newUserObject = -> # deep clone, else further new users get duplicate objects - newUser = lodash.cloneDeep userSchema + newUser = _.cloneDeep userSchema newUser.apiToken = derby.uuid() repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true} @@ -180,7 +179,7 @@ module.exports.BatchUpdate = BatchUpdate = (model) -> setStats: (stats) -> stats ?= obj.stats that = @ - _.each Object.keys(stats), (key) -> that.set "stats.#{key}", stats[key] + _.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 diff --git a/src/app/filters.coffee b/src/app/filters.coffee index 15d11b09d6..87c9d0a5de 100644 --- a/src/app/filters.coffee +++ b/src/app/filters.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' browser = require './browser' module.exports.app = (appExports, model) -> @@ -37,5 +37,5 @@ module.exports.app = (appExports, model) -> tag.remove() # remove tag from all tasks - _.each user.get("tasks"), (task) -> user.del "tasks.#{task.id}.tags.#{tagId}" + _.each user.get("tasks"), (task) -> user.del "tasks.#{task.id}.tags.#{tagId}"; true diff --git a/src/app/index.coffee b/src/app/index.coffee index 7ce10c3b1a..be3bf7094c 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -38,12 +38,12 @@ cleanupCorruptTasks = (model) -> unless task?.id? and task?.type? user.del("tasks.#{key}") delete tasks[key] - true + true batch = null ## Task List Cleanup - _.each ['habit','daily','todo','reward'], (type) -> + ['habit','daily','todo','reward'].forEach (type) -> # 1. remove duplicates # 2. restore missing zombie tasks back into list diff --git a/src/app/items.coffee b/src/app/items.coffee index 75e824d34a..4244e9abf3 100644 --- a/src/app/items.coffee +++ b/src/app/items.coffee @@ -1,5 +1,5 @@ items = require 'habitrpg-shared/script/items' -_ = require 'underscore' +_ = require 'lodash' ### server exports @@ -17,7 +17,7 @@ module.exports.app = (appExports, model) -> appExports.buyItem = (e, el) -> [type, value, index] = [ $(el).attr('data-type'), $(el).attr('data-value'), $(el).attr('data-index') ] if changes = items.buyItem(user.get(), type, value, index) - _.each changes, (v,k) -> user.set k,v + _.each changes, (v,k) -> user.set k,v; true updateStore(model) appExports.activateRewardsTab = -> @@ -29,7 +29,7 @@ module.exports.app = (appExports, model) -> module.exports.updateStore = updateStore = (model) -> nextItems = items.updateStore(model.get('_user')) - _.each nextItems, (v,k) -> model.set("_items.next.#{k}",v) + _.each nextItems, (v,k) -> model.set("_items.next.#{k}",v); true diff --git a/src/app/party.coffee b/src/app/party.coffee index 533f1539d9..4a38a78ba4 100644 --- a/src/app/party.coffee +++ b/src/app/party.coffee @@ -1,4 +1,4 @@ -_ = require('underscore') +_ = require('lodash') helpers = require('habitrpg-shared/script/helpers') module.exports.app = (appExports, model, app) -> diff --git a/src/app/pets.coffee b/src/app/pets.coffee index d3cb5e4330..3e5bb344a9 100644 --- a/src/app/pets.coffee +++ b/src/app/pets.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' { randomVal } = require 'habitrpg-shared/script/helpers' { pets, hatchingPotions } = require('habitrpg-shared/script/items').items diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index dec9b27381..65f76177a2 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -31,7 +31,7 @@ module.exports.app = (appExports, model) -> paths = {} algos.score(uObj, tObj, direction, {paths:paths}) - _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)) + _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)); true appExports.addTask = (e, el) -> type = $(el).attr('data-task-type') @@ -85,7 +85,7 @@ module.exports.app = (appExports, model) -> completedIds = _.pluck( _.where(model.get('_todoList'), {completed:true}), 'id') todoIds = user.get('todoIds') - _.each completedIds, (id) -> user.del "tasks.#{id}" + _.each completedIds, (id) -> user.del "tasks.#{id}"; true user.set 'todoIds', _.difference(todoIds, completedIds) appExports.toggleDay = (e, el) -> @@ -159,14 +159,15 @@ module.exports.app = (appExports, model) -> batch = character.BatchUpdate(model) batch.startTransaction() model.del '_undo' - _.each undo.stats, (val, key) -> batch.set "stats.#{key}", val + _.each undo.stats, (val, key) -> batch.set "stats.#{key}", val; true taskPath = "tasks.#{undo.task.id}" _.each undo.task, (val, key) -> - return if key in ['id', 'type'] # strange bugs in this world: https://workflowy.com/shared/a53582ea-43d6-bcce-c719-e134f9bf71fd/ + 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 + true batch.commit() appExports.tasksToggleAdvanced = (e, el) -> diff --git a/src/app/unlock.coffee b/src/app/unlock.coffee index 26cb1f4a08..4348aa3d5e 100644 --- a/src/app/unlock.coffee +++ b/src/app/unlock.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' { randomVal } = require 'habitrpg-shared/script/helpers' { pets, hatchingPotions } = require('habitrpg-shared/script/items').items diff --git a/src/server/api.coffee b/src/server/api.coffee index 1b96fd194c..484bbb79bc 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -1,7 +1,7 @@ express = require 'express' router = new express.Router() -_ = require 'underscore' +_ = require 'lodash' algos = require 'habitrpg-shared/script/algos' { tnl } = algos validator = require 'derby-auth/node_modules/validator' @@ -83,7 +83,7 @@ router.put '/user', auth, (req, res) -> acceptableAttrs = ['flags', 'history', 'items', 'preferences', 'profile', 'stats'] user.set 'lastCron', partialUser.lastCron if partialUser.lastCron? _.each acceptableAttrs, (attr) -> - _.each partialUser[attr], (val, key) -> user.set("#{attr}.#{key}", val) + _.each partialUser[attr], (val, key) -> user.set("#{attr}.#{key}", val);true updateTasks partialUser.tasks, req.user, req.getModel() if partialUser.tasks? diff --git a/src/server/deprecated.coffee b/src/server/deprecated.coffee index 68d5d2030d..4a8f898c92 100644 --- a/src/server/deprecated.coffee +++ b/src/server/deprecated.coffee @@ -1,7 +1,7 @@ express = require 'express' router = new express.Router() -_ = require 'underscore' +_ = require 'lodash' icalendar = require('icalendar') api = require './api' @@ -44,6 +44,7 @@ router.get '/v1/users/:uid/calendar.ics', (req, res) -> d.date_only = true event.setDate d ical.addComponent event + true res.type('text/calendar') formattedIcal = ical.toString().replace(/DTSTART\:/g, 'DTSTART;VALUE=DATE:') res.send(200, formattedIcal) diff --git a/src/server/private.coffee b/src/server/private.coffee index c332dc104f..65127a51ad 100644 --- a/src/server/private.coffee +++ b/src/server/private.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' character = require "../app/character" module.exports.middleware = (req, res, next) -> @@ -37,7 +37,7 @@ module.exports.app = (appExports, 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' + _.each obj.tasks, (task) -> batch.set("tasks.#{task.id}.value", 0) unless task.type is 'reward';true batch.commit() module.exports.routes = (expressApp) -> From 258cf55abbe36c531c7ffa07a4962b4ef2223568 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 11:30:17 +0100 Subject: [PATCH 20/36] fix int() view helper (restore streak) --- src/app/misc.coffee | 2 +- views/app/tasks.html | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/misc.coffee b/src/app/misc.coffee index b39399aa97..f0d72af8b1 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -34,7 +34,7 @@ module.exports.viewHelpers = (view) -> view.fn 'count', (arr) -> arr?.length or 0 view.fn 'int', get: (num) -> num - set: (num) -> parseInt(num) + set: (num) -> [parseInt(num)] #iCal view.fn "encodeiCalLink", helpers.encodeiCalLink diff --git a/views/app/tasks.html b/views/app/tasks.html index 2d50891f41..7df21248f3 100644 --- a/views/app/tasks.html +++ b/views/app/tasks.html @@ -288,7 +288,7 @@ {{#if equal(:task.type,'daily')}} Restore Streak - + {{/}} {/} From 77e37804e86c19c21e94ce47448377bbb3d95c1e Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Wed, 22 May 2013 13:57:10 +0100 Subject: [PATCH 21/36] habitrpg-shared: bug fixes on hydrate() --- src/app/misc.coffee | 8 +++++--- src/app/tasks.coffee | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/misc.coffee b/src/app/misc.coffee index f0d72af8b1..199f7ce845 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -6,9 +6,11 @@ helpers = require('habitrpg-shared/script/helpers') ### Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116 ### -module.exports.hydrate = hydrate = (spec, hydrated={}) -> - if _.isPlainObject(spec) - hydrated[k] = hydrate(v, hydrated[k]) for k,v of spec +module.exports.hydrate = hydrate = (spec) -> + if _.isObject(spec) and !_.isArray(spec) + hydrated = {} + keys = _.keys(spec).concat(_.keys(spec.__proto__)) + keys.forEach (k) -> hydrated[k] = hydrate(spec[k]) hydrated else spec diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 65f76177a2..689f47b571 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -19,7 +19,7 @@ module.exports.app = (appExports, model) -> perform the updates while tracking paths, then all the values at those paths ### score = (taskId, direction) -> -# return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 + #return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 uObj = misc.hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 tObj = uObj.tasks[taskId] From 60a4ce5888c3d5f8ecb2b6a392934d84606112bc Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 14:40:34 +0100 Subject: [PATCH 22/36] update find_unique_user to use lodash --- migrations/find_unique_user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/find_unique_user.js b/migrations/find_unique_user.js index c72de09f36..20ee902754 100644 --- a/migrations/find_unique_user.js +++ b/migrations/find_unique_user.js @@ -1,4 +1,4 @@ -// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/find_unique_user.js +// mongo habitrpg ./node_modules/lodash/index.js ./migrations/find_unique_user.js /** * There are some rare instances of lost user accounts, due to a corrupt user auth variable (see https://github.com/lefnire/habitrpg/wiki/User-ID) @@ -6,6 +6,6 @@ */ db.users.find().forEach(function(user){ - var found = _.findWhere(user.tasks, {text: "Replace Me"}) + var found = _.any(user.tasks, {text: "Replace Me"}) if (found) printjson({id:user._id, auth:user.auth}); }) \ No newline at end of file From 73fe8ebfdd2d667e45363726c547d4c223de10db Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 15:06:42 +0100 Subject: [PATCH 23/36] fix preen_cron --- migrations/20130212_preen_cron.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/migrations/20130212_preen_cron.js b/migrations/20130212_preen_cron.js index 59c74ed911..72261f246a 100644 --- a/migrations/20130212_preen_cron.js +++ b/migrations/20130212_preen_cron.js @@ -31,7 +31,7 @@ var un_registered = { db.users.find(un_registered).forEach(function(user) { if (!user) return; if (!!user.lastCron) { - if (Math.abs(moment(today).diff(user.lastCron, 'd')) > 5) { + if (Math.abs(moment(today).diff(user.lastCron, 'days')) > 7) { return db.users.remove({_id:user._id}); } } else { From 69e1f991b7ede0905c2814c5e2981c4f8d46f00f Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 16:53:13 +0100 Subject: [PATCH 24/36] habitrpg-shared: put drops back in --- src/app/tasks.coffee | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 689f47b571..8d44c72cbf 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -32,6 +32,10 @@ module.exports.app = (appExports, model) -> paths = {} algos.score(uObj, tObj, direction, {paths:paths}) _.each paths, (v,k) -> user.set(k,helpers.dotGet(k, uObj)); true + if uObj._tmp?.drop and $? + debugger + model.set '_drop', uObj._tmp.drop + $('#item-dropped-modal').modal 'show' appExports.addTask = (e, el) -> type = $(el).attr('data-task-type') From fee39a8b7e3b2f533feca05c42dbaade7c129efa Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 17:00:54 +0100 Subject: [PATCH 25/36] habitrpg-shared: add streakBonus back in --- src/app/tasks.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 8d44c72cbf..49b52373bb 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -32,8 +32,8 @@ module.exports.app = (appExports, model) -> paths = {} algos.score(uObj, tObj, direction, {paths: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 $? - debugger model.set '_drop', uObj._tmp.drop $('#item-dropped-modal').modal 'show' From 32cf11b533a8eca7670521660472b1a21868502b Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 17:34:27 +0100 Subject: [PATCH 26/36] use lodash in tests for consistency --- test/api.mocha.coffee | 4 ++-- test/user.mocha.coffee | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 0a488d57df..c2c74319a7 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -1,4 +1,4 @@ -_ = require 'underscore' +_ = require 'lodash' request = require 'superagent' expect = require 'expect.js' require 'coffee-script' @@ -373,7 +373,7 @@ describe 'API', -> expect(res.statusCode).to.be 201 tasks = res.body.tasks - expect(_.findWhere(tasks,{id:habitId})).to.eql {id: habitId,text: 'hello2',notes: 'note2'} + expect(_.first(tasks,{id:habitId})).to.eql {id: habitId,text: 'hello2',notes: 'note2'} foundNewTask = _.findWhere(tasks,{text:'new task2'}) expect(foundNewTask.text).to.be 'new task2' diff --git a/test/user.mocha.coffee b/test/user.mocha.coffee index 33ab7ee431..0a7e1fcb76 100644 --- a/test/user.mocha.coffee +++ b/test/user.mocha.coffee @@ -1,8 +1,7 @@ {expect} = require 'derby/node_modules/racer/test/util' {BrowserModel: Model} = require 'derby/node_modules/racer/test/util/model' derby = require 'derby' -lodash = require 'lodash' -_ = require 'underscore' +_ = require 'lodash' moment = require 'moment' # Custom modules @@ -20,8 +19,8 @@ taskPath = null # Otherwise, using model.get(path) will give the same object before as after pathSnapshots = (paths) -> if _.isString(paths) - return lodash.cloneDeep(model.get(paths)) - _.map paths, (path) -> lodash.cloneDeep(model.get(path)) + return _.cloneDeep(model.get(paths)) + _.map paths, (path) -> _.cloneDeep(model.get(path)) statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task cleanUserObj = -> From 9e2c9045c5b6ab54ceec568546c5d5458fea0359 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 17:35:24 +0100 Subject: [PATCH 27/36] habitrpg-shared: move task.score => misc.score so api can use it too --- src/app/misc.coffee | 31 +++++++++++++++++++++++++++++++ src/app/tasks.coffee | 40 ++++------------------------------------ src/server/api.coffee | 2 +- 3 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/app/misc.coffee b/src/app/misc.coffee index 199f7ce845..f12571bbb7 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -3,6 +3,37 @@ algos = require 'habitrpg-shared/script/algos' items = require('habitrpg-shared/script/items').items helpers = require('habitrpg-shared/script/helpers') +### + algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the + object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first + clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination), + perform the updates while tracking paths, then all the values at those paths +### +module.exports.score = (model, taskId, direction, allowUndo=false) -> + #return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 + user = model.at("_user") + + 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} + + paths = {} + delta = algos.score(uObj, tObj, direction, {paths: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 + ### Make sure model.get() returns all properties, see https://github.com/codeparty/racer/issues/116 ### diff --git a/src/app/tasks.coffee b/src/app/tasks.coffee index 49b52373bb..50b130483d 100644 --- a/src/app/tasks.coffee +++ b/src/app/tasks.coffee @@ -12,31 +12,6 @@ module.exports.app = (appExports, model) -> character = require './character' user = model.at('_user') - ### - algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the - object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first - clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination), - perform the updates while tracking paths, then all the values at those paths - ### - score = (taskId, direction) -> - #return setTimeout( (-> score(taskId, direction)), 500) if model._txnQueue.length > 0 - - uObj = misc.hydrate(user.get()) # see https://github.com/codeparty/racer/issues/116 - tObj = uObj.tasks[taskId] - - # Stuff for undo - tObjBefore = _.cloneDeep tObj - tObjBefore.completed = !tObjBefore.completed if tObjBefore.type in ['daily', 'todo'] - setUndo _.cloneDeep(uObj.stats), tObjBefore # set previous state for undo - - paths = {} - algos.score(uObj, tObj, direction, {paths: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' - appExports.addTask = (e, el) -> type = $(el).attr('data-task-type') newModel = model.at('_new' + type.charAt(0).toUpperCase() + type.slice(1)) @@ -69,7 +44,7 @@ module.exports.app = (appExports, model) -> if task.get('value') < 0 if confirm("Are you sure? Deleting this task will hurt you (to prevent deleting, then re-creating red tasks).") is true task.set('type','habit') # hack to make sure it hits HP, instead of performing "undo checkbox" - score(id, 'down') + misc.score(model, id, 'down', true) else return # Cancel. Don't delete, don't hurt user @@ -130,29 +105,22 @@ module.exports.app = (appExports, model) -> appExports.todosShowRemaining = -> model.set '_showCompleted', false appExports.todosShowCompleted = -> model.set '_showCompleted', true - setUndo = (stats, task) -> - previousUndo = model.get('_undo') - clearTimeout(previousUndo.timeoutId) if previousUndo?.timeoutId - timeoutId = setTimeout (-> model.del('_undo')), 10000 - model.set '_undo', {stats:stats, task:task, timeoutId: timeoutId} - - ### Call scoring functions for habits & rewards (todos & dailies handled below) ### appExports.score = (e, el) -> id = $(el).parents('li').attr('data-id') direction = $(el).attr('data-direction') - score(id, direction) + misc.score(model, id, direction, true) ### This is how we handle appExports.score for todos & dailies. Due to Derby's special handling of `checked={:task.completd}`, the above function doesn't work so we need a listener here ### user.on 'set', 'tasks.*.completed', (i, completed, previous, isLocal, passed) -> - return if passed? && passed.cron # Don't do this stuff on cron + return if passed?.cron # Don't do this stuff on cron direction = if completed then 'up' else 'down' - score(i, direction) + misc.score(model, i, direction, true) ### Undo diff --git a/src/server/api.coffee b/src/server/api.coffee index 484bbb79bc..92ad0bf70e 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -256,7 +256,7 @@ scoreTask = (req, res, next) -> model.at("_#{type}List").push task #FIXME - delta = algos.score(user.get(), taskId, direction) + delta = misc.score(model, taskId, direction) result = model.get '_user.stats' result.delta = delta res.json result From 3ed2412f87f12676b4dbcf44fd5cf3abbf41e994 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Thu, 23 May 2013 18:35:32 +0100 Subject: [PATCH 28/36] habitrpg-shared: bug fix in API --- src/server/api.coffee | 1 + 1 file changed, 1 insertion(+) diff --git a/src/server/api.coffee b/src/server/api.coffee index 92ad0bf70e..8d8e329444 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -7,6 +7,7 @@ algos = require 'habitrpg-shared/script/algos' validator = require 'derby-auth/node_modules/validator' check = validator.check sanitize = validator.sanitize +misc = require '../app/misc' NO_TOKEN_OR_UID = err: "You must include a token and uid (user id) in your request" NO_USER_FOUND = err: "No user found." From 62c9e45e2e319fe3ddf14907a1475d2bf6779e05 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 24 May 2013 11:32:04 +0100 Subject: [PATCH 29/36] habitrpg-shared: use newUser from shared helpers instead of in character.coffee --- src/app/character.coffee | 62 ---------------------------------------- src/app/index.coffee | 1 - src/server/index.coffee | 4 ++- test/api.mocha.coffee | 4 +-- test/user.mocha.coffee | 6 ++-- 5 files changed, 8 insertions(+), 69 deletions(-) diff --git a/src/app/character.coffee b/src/app/character.coffee index d87632f9c5..a345fec55c 100644 --- a/src/app/character.coffee +++ b/src/app/character.coffee @@ -82,68 +82,6 @@ module.exports.app = (appExports, model) -> model.del "users.#{user.get('id')}", -> window.location.href = "/logout" -userSchema = -# _id - stats: { gp: 0, exp: 0, lvl: 1, hp: 50 } - party: { current: null, invitation: null } - items: { weapon: 0, armor: 0, head: 0, shield: 0 } - preferences: { gender: 'm', skin: 'white', hair: 'blond', armorSet: 'v1', dayStart:0, showHelm: true } - habitIds: [] - dailyIds: [] - todoIds: [] - rewardIds: [] - apiToken: null # set in newUserObject below - lastCron: 'new' #this will be replaced with `+new Date` on first run - balance: 0 - tasks: {} - flags: - partyEnabled: false - itemsEnabled: false - tags: [] -# ads: 'show' # added on registration - -module.exports.newUserObject = -> - # deep clone, else further new users get duplicate objects - newUser = _.cloneDeep userSchema - newUser.apiToken = derby.uuid() - - repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true} - defaultTasks = [ - {type: 'habit', text: '1h Productive Work', notes: '-- Habits: Constantly Track --\nFor some habits, it only makes sense to *gain* points (like this one).', value: 0, up: true, down: false } - {type: 'habit', text: 'Eat Junk Food', notes: 'For others, it only makes sense to *lose* points', value: 0, up: false, down: true} - {type: 'habit', text: 'Take The Stairs', notes: 'For the rest, both + and - make sense (stairs = gain, elevator = lose)', value: 0, up: true, down: true} - - {type: 'daily', text: '1h Personal Project', notes: '-- Dailies: Complete Once a Day --\nAt the end of each day, non-completed Dailies dock you points.', value: 0, completed: false, repeat: repeat } - {type: 'daily', text: 'Exercise', notes: "If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit.", value: 3, completed: false, repeat: repeat } - {type: 'daily', text: '45m Reading', notes: 'But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.', value: -10, completed: false, repeat: repeat } - - {type: 'todo', text: 'Call Mom', notes: "-- Todos: Complete Eventually --\nNon-completed Todos won't hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.", value: -3, completed: false } - - {type: 'reward', text: '1 Episode of Game of Thrones', notes: '-- Rewards: Treat Yourself! --\nAs you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits.', value: 20 } - {type: 'reward', text: 'Cake', notes: 'But only buy if you have enough gold - you lose HP otherwise.', value: 10 } - ] - - defaultTags = [ - {name: 'morning'} - {name: 'afternoon'} - {name: 'evening'} - ] - - for task in defaultTasks - guid = task.id = derby.uuid() - newUser.tasks[guid] = task - switch task.type - when 'habit' then newUser.habitIds.push guid - when 'daily' then newUser.dailyIds.push guid - when 'todo' then newUser.todoIds.push guid - when 'reward' then newUser.rewardIds.push guid - - for tag in defaultTags - tag.id = derby.uuid() - newUser.tags.push tag - - return newUser - module.exports.BatchUpdate = BatchUpdate = (model) -> user = model.at("_user") transactionInProgress = false diff --git a/src/app/index.coffee b/src/app/index.coffee index be3bf7094c..372830afc2 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -121,7 +121,6 @@ get '/', (page, model, params, next) -> ready (model) -> user = model.at('_user') - model.setNull '_user.apiToken', derby.uuid() browser = require './browser' require('./character').app(exports, model) diff --git a/src/server/index.coffee b/src/server/index.coffee index 7e057b7f06..152b9a79c9 100644 --- a/src/server/index.coffee +++ b/src/server/index.coffee @@ -12,6 +12,8 @@ priv = require './private' habitrpgStore = require './store' middleware = require './middleware' +helpers = require("habitrpg-shared/script/helpers") + ## RACER CONFIGURATION ## #racer.io.set('transports', ['xhr-polling']) @@ -50,7 +52,7 @@ strategies = options = domain: process.env.BASE_URL || 'http://localhost:3000' allowPurl: true - schema: require('../app/character').newUserObject() + schema: helpers.newUser(true) # This has to happen before our middleware stuff auth.store(store, habitrpgStore.customAccessControl) diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index c2c74319a7..3542a3e0fb 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -22,7 +22,7 @@ expect.Assertion::be = expect.Assertion::equal = (obj) -> origBe.call this, obj # Custom modules -character = require '../src/app/character' +helpers = require 'habitrpg-shared/script/helpers' ###### Helpers & Variables ###### @@ -48,7 +48,7 @@ describe 'API', -> #store.flush() model = store.createModel() model.set '_userId', uid = model.id() - user = character.newUserObject() + user = helpers.newUser(true) user.apiToken = model.id() model.session = {userId:uid} model.set "users.#{uid}", user diff --git a/test/user.mocha.coffee b/test/user.mocha.coffee index 0a7e1fcb76..6f4c2046c2 100644 --- a/test/user.mocha.coffee +++ b/test/user.mocha.coffee @@ -7,7 +7,7 @@ moment = require 'moment' # Custom modules scoring = require '../src/app/scoring' schema = require '../src/app/character' -helpers = require '../src/app/helpers' +helpers = require 'habitrpg-shared/script/helpers' ###### Helpers & Variables ###### @@ -24,7 +24,7 @@ pathSnapshots = (paths) -> statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task cleanUserObj = -> - userObj = schema.newUserObject() + userObj = helpers.newUser() userObj.tasks = {} userObj.habitIds = [] userObj.dailyIds = [] @@ -84,7 +84,7 @@ describe 'User', -> before -> model = new Model - model.set '_user', schema.newUserObject() + model.set '_user', helpers.newUser() scoring.setModel model it 'sets correct user defaults', -> From 467741570aad7dcd4f5ee6a48327ea6a4908f96c Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 24 May 2013 12:25:32 +0100 Subject: [PATCH 30/36] pet selection bug fix with lodash (_.findWhere => _.find), fixes #902 --- src/app/pets.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/pets.coffee b/src/app/pets.coffee index 3e5bb344a9..2528672995 100644 --- a/src/app/pets.coffee +++ b/src/app/pets.coffee @@ -46,14 +46,14 @@ module.exports.app = (appExports, model) -> return user.set 'items.currentPet', {} if user.get('items.currentPet.str') is petStr [name, modifier] = petStr.split('-') - pet = _.findWhere pets, name: name + pet = _.find pets, {name: name} pet.modifier = modifier pet.str = petStr user.set 'items.currentPet', pet appExports.buyHatchingPotion = (e, el) -> name = $(el).attr 'data-hatchingPotion' - newHatchingPotion = _.findWhere hatchingPotions, name: name + newHatchingPotion = _.find hatchingPotions, {name: name} gems = user.get('balance') * 4 if gems >= newHatchingPotion.value if confirm "Buy this hatching potion with #{newHatchingPotion.value} of your #{gems} Gems?" @@ -64,7 +64,7 @@ module.exports.app = (appExports, model) -> appExports.buyEgg = (e, el) -> name = $(el).attr 'data-egg' - newEgg = _.findWhere pets, name: name + newEgg = _.find pets, {name: name} gems = user.get('balance') * 4 if gems >= newEgg.value if confirm "Buy this egg with #{newEgg.value} of your #{gems} Gems?" From c894f873100d5698c24cd8fff9117132fe481f3e Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Fri, 24 May 2013 18:49:18 +0100 Subject: [PATCH 31/36] habitrpg-shared: fix cron to work with habitrpg-shared d293f74 --- src/app/index.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/index.coffee b/src/app/index.coffee index 372830afc2..cf65e131e9 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -135,17 +135,17 @@ ready (model) -> require('./unlock').app(exports, model) require('./filters').app(exports, model) + uObj = misc.hydrate(user.get()) cron = -> #return setTimeout(cron, 1) if model._txnQueue.length > 0 - uObj = misc.hydrate(user.get()) # need to clone, else derby won't catch model.set()'s after obj property sets # 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) + algos.cron(uObj, {paths:paths}) 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 browser.resetDom(model) setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 - cron() if algos.shouldCron {lastCron: user.get('lastCron'), preferences: user.get('preferences')} \ No newline at end of file + cron() if algos.shouldCron(uObj) \ No newline at end of file From 955c2841eadc9771d520bd95b2af242fba0d1cb6 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 25 May 2013 18:29:42 +0100 Subject: [PATCH 32/36] tests: get rid of user.mocha.coffee, now it's in habitrpg-shared --- test/user.mocha.coffee | 252 ----------------------------------------- 1 file changed, 252 deletions(-) delete mode 100644 test/user.mocha.coffee diff --git a/test/user.mocha.coffee b/test/user.mocha.coffee deleted file mode 100644 index 6f4c2046c2..0000000000 --- a/test/user.mocha.coffee +++ /dev/null @@ -1,252 +0,0 @@ -{expect} = require 'derby/node_modules/racer/test/util' -{BrowserModel: Model} = require 'derby/node_modules/racer/test/util/model' -derby = require 'derby' -_ = require 'lodash' -moment = require 'moment' - -# Custom modules -scoring = require '../src/app/scoring' -schema = require '../src/app/character' -helpers = require 'habitrpg-shared/script/helpers' - -###### Helpers & Variables ###### - -model = null -uuid = null -taskPath = null - -## Helper which clones the content at a path so tests can compare before/after values -# Otherwise, using model.get(path) will give the same object before as after -pathSnapshots = (paths) -> - if _.isString(paths) - return _.cloneDeep(model.get(paths)) - _.map paths, (path) -> _.cloneDeep(model.get(path)) -statsTask = -> pathSnapshots(['_user.stats', taskPath]) # quick snapshot of user.stats & task - -cleanUserObj = -> - userObj = helpers.newUser() - userObj.tasks = {} - userObj.habitIds = [] - userObj.dailyIds = [] - userObj.todoIds = [] - userObj.rewardIds = [] - return userObj -resetUser = -> model.set '_user', cleanUserObj() - -freshTask = (taskObj) -> - resetUser() - # create a test task - uuid = derby.uuid() - taskPath = "_user.tasks.#{uuid}" - {type} = taskObj - model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids" - [taskObj.id, taskObj.value] = [uuid, 0] - model.at("_#{type}List").push taskObj - -### -Helper function to determine if stats updates are numerically correct based on scoring -@direction: 'up' or 'down' -@options: The user stats modifiers and times to run, defaults to {times:1, modifiers:{lvl:1, weapon:0, armor:0}} -### -modificationsLookup = (direction, options = {}) -> - merged = _.defaults options, {times:1, lvl:1, weapon:0, armor:0} - {times, lvl, armor, weapon} = merged - userObj = cleanUserObj() - value = 0 - _.times times, (n) -> - delta = scoring.taskDeltaFormula(value, direction) - value += delta - if direction=='up' - gain = scoring.expModifier(delta, options) - userObj.stats.exp += gain - userObj.stats.money += gain - else - loss = scoring.hpModifier(delta, options) - userObj.stats.hp += loss - return {user:userObj, value:value} - -###### Specs ###### - -describe 'Cron', -> - it 'calculates day differences with dayStart properly', -> - dayStart = 4 - yesterday = moment().subtract('d', 1).add('h', dayStart) - now = moment().startOf('day').add('h', dayStart-1) #today - console.log {yesterday: yesterday.format('MM/DD HH:00'), now: now.format('MM/DD HH:00')} - console.log {diff: Math.abs(moment(yesterday).diff(moment(now), 'days'))} - expect(helpers.daysBetween(yesterday, now, dayStart)).to.eql 0 - now = moment().startOf('day').add('h', dayStart) - console.log {now: now.format('MM/DD HH:00')} - expect(helpers.daysBetween(yesterday, now, dayStart)).to.eql 1 - -describe 'User', -> - model = null - - before -> - model = new Model - model.set '_user', helpers.newUser() - scoring.setModel model - - it 'sets correct user defaults', -> - user = model.get '_user' - expect(user.stats).to.eql { money: 0, exp: 0, lvl: 1, hp: 50 } - expect(user.items).to.eql { itemsEnabled: false, armor: 0, weapon: 0 } - expect(user.balance).to.eql 2 - expect(_.size(user.tasks)).to.eql 9 - expect(_.size(user.habitIds)).to.eql 3 - expect(_.size(user.dailyIds)).to.eql 3 - expect(_.size(user.todoIds)).to.eql 1 - expect(_.size(user.rewardIds)).to.eql 2 - - ##### Habits ##### - describe 'Tasks', -> - - beforeEach -> - resetUser() - - describe 'Habits', -> - - beforeEach -> - freshTask {type: 'habit', text: 'Habit', up: true, down: true} - - it 'created the habit', -> - task = model.get(taskPath) - expect(task.text).to.eql 'Habit' - expect(task.value).to.eql 0 - - it 'test a few scoring numbers (this will change if constants / formulae change)', -> - {user} = modificationsLookup('down') - expect(user.stats.hp).to.eql 49 - {user} = modificationsLookup('down', {times:5}) - expect(user.stats.hp).to.be.within(42,44) - - {user} = modificationsLookup('up') - expect(user.stats.exp).to.eql 1 - expect(user.stats.money).to.eql 1 - {user} = modificationsLookup('up', {times:5}) - expect(user.stats.exp).to.be.within(4,5) - - it 'made proper modifications when down-scored', -> - ## Trial 1 - - shouldBe = modificationsLookup('down') - scoring.score(uuid,'down') - [stats, task] = statsTask() - expect(stats.hp).to.be.eql shouldBe.user.stats.hp - expect(task.value).to.eql shouldBe.value - - ## Trial 2 - freshTask {type: 'habit', text: 'Habit', completed: false} - shouldBe = modificationsLookup('down', {times:10}) - scoring.score(uuid,'down', {times:10}) - [stats, task] = statsTask() - expect(stats.hp).to.be.eql shouldBe.user.stats.hp - expect(task.value).to.eql shouldBe.value - - it 'made proper modifications when up-scored', -> - # Up-score the habit - [statsBefore, taskBefore] = statsTask() - scoring.score(uuid, 'up') - [statsAfter, taskAfter] = statsTask() - - # User should have gained Exp, GP - expect(statsAfter.exp).to.be.greaterThan statsBefore.exp - expect(statsAfter.money).to.be.greaterThan statsBefore.money - # HP should not change - expect(statsAfter.hp).to.eql statsBefore.hp - # Task should have lost value - expect(taskBefore.value).to.eql 0 - expect(taskAfter.value).to.be.greaterThan taskBefore.value - - ## Trial 2 - taskBefore = pathSnapshots(taskPath) - scoring.score(uuid, 'up') - taskAfter = pathSnapshots(taskPath) - # Should have lost in value - expect(taskAfter.value).to.be > taskBefore.value - # And lost more than trial 1 - diff = Math.abs(taskAfter.value) - Math.abs(taskBefore.value) - expect(diff).to.be.lessThan 1 - - it 'makes history entry for habit' - it 'makes proper modifications each time when clicking + / - in rapid succession' - # saw an issue here once, so test that it wasn't a fluke - - it 'should not modify certain attributes given certain conditions' - # non up+down habits - # what else? - - it 'should show "undo" notification if user unchecks completed daily' - - - describe 'Lvl & Items', -> - - beforeEach -> - freshTask {type: 'habit', text: 'Habit', up: true, down: true} - - it 'modified damage based on lvl & armor' - it 'modified exp/gp based on lvl & weapon' - it 'always decreases hp with damage, regardless of stats/items' - it 'always increases exp/gp with gain, regardless of stats/items' - - describe 'Dailies', -> - - beforeEach -> - freshTask {type: 'daily', text: 'Daily', completed: false} - - it 'created the daily', -> - task = model.get(taskPath) - expect(task.text).to.eql 'Daily' - expect(task.value).to.eql 0 - - it 'does proper calculations when daily is complete' - it 'calculates dailys properly when they have repeat dates' - - runCron = (times, pass=1) -> - # Set lastCron to days ago - today = new moment() - ago = new moment().subtract('days',times) - model.set '_user.lastCron', ago.toDate() - # Run run - scoring.cron() - [stats, task] = statsTask() - - # Should have updated cron to today - lastCron = moment(model.get('_user.lastCron')) - expect(today.diff(lastCron, 'days')).to.eql 0 - - shouldBe = modificationsLookup('down', {times:times*pass}) - # Should have updated points properly - expect(Math.round(stats.hp)).to.be.eql Math.round(shouldBe.user.stats.hp) - expect(Math.round(task.value)).to.eql Math.round(shouldBe.value) - - it 'calculates user.stats & task.value properly on cron', -> - runCron(10) - - it 'runs cron multiple times properly', -> - runCron(5) - runCron(5, 2) - - - #TODO clicking repeat dates on newly-created item doesn't refresh until you refresh the page - #TODO dates on dailies is having issues, possibility: date cusps? my saturday exempts were set to exempt at 8pm friday - - describe 'Todos', -> - describe 'Cron', -> - it 'calls cron asyncronously' - it 'should calculate user.stats & task.value properly on cron' - it 'should calculate cron based on difference between start-of-days, and not run in the middle of the day' - it 'should only run set operations once per task, even when daysPassed > 1' - # pass in daysPassed to score, multiply modification values by daysPassed before running set - it 'should only push a history point for lastCron, not each day in between' - # stop passing in tallyFor, let moment().sod().toDate() be handled in scoring.score() - it 'should defer saving user modifications until, save as aggregate values' - # pass in commit parameter to scoring func, if true save right away, otherwise return aggregated array so can save in the end (so total hp loss, etc) - - describe 'Rewards', -> - - #### Require.js stuff, might be necessary to place in casper.coffee - it "doesn't setup dependent functions until their modules are loaded, require.js callback" - # sortable, stripe, etc - -#TODO refactor as user->habits, user->dailys, user->todos, user->rewards \ No newline at end of file From e0f63860a95854642bdb0dfed548c945b0c774f6 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 25 May 2013 18:31:47 +0100 Subject: [PATCH 33/36] typo in package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b13182dcdd..cfdf575156 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "0.0.0-151", "main": "./server.js", "dependencies": { - "habitrpg-shard": "git://github.com/HabitRPG/habitrpg-shared#master", + "habitrpg-shared": "git://github.com/HabitRPG/habitrpg-shared#master", "derby": "git://github.com/lefnire/derby#habitrpg", "racer": "git://github.com/lefnire/racer#habitrpg", "racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg", From a141a02000e738b43907b1b0491f96f25c0d022a Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 25 May 2013 19:01:43 +0100 Subject: [PATCH 34/36] add bailey for habitrpg-shared --- views/app/alerts.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/views/app/alerts.html b/views/app/alerts.html index ea389b8785..ad1e091cf9 100644 --- a/views/app/alerts.html +++ b/views/app/alerts.html @@ -14,6 +14,10 @@

      +

      5/25/2013

      +
        +
      • Code logic migrated to habitrpg-shared. See details here, but two takeaways: (1) keep an eye out and report a problem if you experience any issues, (2) this is going to allow for much less buggy code (read previous link for reasoning).
      • +

      5/12/2013

        From 20addd24c7453e19d2b244914b77f211f2fae296 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 25 May 2013 19:06:40 +0100 Subject: [PATCH 35/36] parties: fetch pary-members synchronously, instead of async, to mitigate the faceless party-members bug --- src/app/index.coffee | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/app/index.coffee b/src/app/index.coffee index cf65e131e9..a7c03cd9a2 100644 --- a/src/app/index.coffee +++ b/src/app/index.coffee @@ -90,16 +90,16 @@ setupSubscriptions = (page, model, params, next, cb) -> return finished([selfQ, 'tavern'], ['_user', '_tavern']) unless party.get() ## (2) Party has members, subscribe to those users too - membersQ = model.query('users').party(party.get('members')) - - # Fetch instead of subscribe. There's nothing dynamic we need from members just yet, they'll update _party instead. - # This may change in the future. - membersQ.fetch (err, members) -> - return next(err) if err - model.ref '_partyMembers', members - - # Note - selfQ *must* come after membersQ in subscribe, otherwise _user will only get the fields restricted by party-members in store.coffee. Strang bug, but easy to get around - return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern']) + if m = party.get('members') + # Fetch instead of subscribe. There's nothing dynamic we need from members just yet, they'll update _party instead. + # This may change in the future. + model.query('users').party(m).fetch (err, members) -> + return next(err) if err + model.ref '_partyMembers', members + return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern']) + else + # Note - selfQ *must* come after membersQ in subscribe, otherwise _user will only get the fields restricted by party-members in store.coffee. Strang bug, but easy to get around + return finished([partyQ, selfQ, 'tavern'], ['_party', '_user', '_tavern']) # ========== ROUTES ========== @@ -148,4 +148,4 @@ ready (model) -> if lostHp browser.resetDom(model) setTimeout (-> user.set('stats.hp', uObj.stats.hp)), 750 - cron() if algos.shouldCron(uObj) \ No newline at end of file + cron() if algos.shouldCron(uObj) From f0c50e1cfe75230ce27b97d2da9c9bef1f9891c9 Mon Sep 17 00:00:00 2001 From: Tyler Renelle Date: Sat, 25 May 2013 19:42:59 +0100 Subject: [PATCH 36/36] fix api + tests --- src/app/misc.coffee | 2 +- src/server/api.coffee | 4 ++-- test/api.mocha.coffee | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/misc.coffee b/src/app/misc.coffee index f12571bbb7..735addb804 100644 --- a/src/app/misc.coffee +++ b/src/app/misc.coffee @@ -26,7 +26,7 @@ module.exports.score = (model, taskId, direction, allowUndo=false) -> model.set '_undo', {stats:_.cloneDeep(uObj.stats), task:tObjBefore, timeoutId: timeoutId} paths = {} - delta = algos.score(uObj, tObj, direction, {paths: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 $? diff --git a/src/server/api.coffee b/src/server/api.coffee index 8d8e329444..45b0250b17 100644 --- a/src/server/api.coffee +++ b/src/server/api.coffee @@ -3,7 +3,7 @@ router = new express.Router() _ = require 'lodash' algos = require 'habitrpg-shared/script/algos' -{ tnl } = algos +helpers = require 'habitrpg-shared/script/helpers' validator = require 'derby-auth/node_modules/validator' check = validator.check sanitize = validator.sanitize @@ -52,7 +52,7 @@ auth = (req, res, next) -> router.get '/user', auth, (req, res) -> user = req.userObj - user.stats.toNextLevel = tnl user.stats.lvl + user.stats.toNextLevel = algos.tnl user.stats.lvl user.stats.maxHealth = 50 delete user.apiToken diff --git a/test/api.mocha.coffee b/test/api.mocha.coffee index 3542a3e0fb..844da7b735 100644 --- a/test/api.mocha.coffee +++ b/test/api.mocha.coffee @@ -373,13 +373,13 @@ describe 'API', -> expect(res.statusCode).to.be 201 tasks = res.body.tasks - expect(_.first(tasks,{id:habitId})).to.eql {id: habitId,text: 'hello2',notes: 'note2'} + expect(_.find(tasks,{id:habitId})).to.eql {id: habitId,text: 'hello2',notes: 'note2'} - foundNewTask = _.findWhere(tasks,{text:'new task2'}) + foundNewTask = _.find(tasks,{text:'new task2'}) expect(foundNewTask.text).to.be 'new task2' expect(foundNewTask.notes).to.be 'notes2' - found = _.findWhere(res.body.tasks, {id:dailyId}) + found = _.find(res.body.tasks, {id:dailyId}) expect(found).to.not.be.ok() query.fetch (err, user) ->