From 5274fd236a3e50a1dc027685a35c5583c8a60a2b Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 1 Sep 2015 17:17:50 -0400 Subject: [PATCH 01/18] WIP(time-travel): Time Travel pets/mounts Also begins process of adding tests for Mystic Hourglass purchases. --- common/locales/en/subscriber.json | 3 ++- common/script/content.coffee | 20 +++++++++++++++----- common/script/index.coffee | 8 ++++++-- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index c448afe75f..55cb801c26 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -64,5 +64,6 @@ "mysticHourglassPopover": "Mystic Hourglass allow you to purchase previous months' subscriber sets.", "subUpdateCard": "Update Card", "subUpdateTitle": "Update", - "subUpdateDescription": "Update the card to be charged." + "subUpdateDescription": "Update the card to be charged.", + "notEnoughHourglasses": "You don't have enough Mystic Hourglasses." } diff --git a/common/script/content.coffee b/common/script/content.coffee index 5a5905ef27..9aceec4823 100644 --- a/common/script/content.coffee +++ b/common/script/content.coffee @@ -538,12 +538,18 @@ _.each gearTypes, (type) -> Time Traveler Store, mystery sets need their items mapped in ### _.each api.mystery, (v,k)-> v.items = _.where api.gear.flat, {mystery:k} -api.timeTravelerStore = (owned) -> +api.timeTravelerStore = (owned, category) -> ownedKeys = _.keys owned.toObject?() or owned # mongoose workaround - _.reduce api.mystery, (m,v,k)-> - return m if k=='wondercon' or ~ownedKeys.indexOf(v.items[0].key) # skip wondercon and already-owned sets - m[k] = v;m - , {} + if not category or category is "mystery" + _.reduce api.mystery, (m,v,k)-> + return m if k=='wondercon' or ~ownedKeys.indexOf(v.items[0].key) # skip wondercon and already-owned sets + m[k] = v;m + , {} + else + _.reduce api.timeTravelStable[category], (m,v,i)-> + returm m if ~ownedKeys.indexOf(v) + m[i] = v;m + , [] ### --------------------------------------------------------------- @@ -1130,6 +1136,10 @@ api.specialMounts = 'Orca-Base': 'orca' 'Gryphon-RoyalPurple': 'royalPurpleGryphon' +api.timeTravelStable = + pets: ['Mammoth-Base','MantisShrimp-Base'] + mounts: ['Mammoth-Base','MantisShrimp-Base'] + api.hatchingPotions = Base: value: 2, text: t('hatchingPotionBase') White: value: 2, text: t('hatchingPotionWhite') diff --git a/common/script/index.coffee b/common/script/index.coffee index f0dd2536fa..f2750bf6a6 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -999,10 +999,10 @@ api.wrap = (user, main=true) -> cb? {code:200, message}, user.items.quests buyMysterySet: (req, cb, analytics)-> - return cb?({code:401, message:"You don't have enough Mystic Hourglasses"}) unless user.purchased.plan.consecutive.trinkets>0 + return cb?({code:401, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 mysterySet = content.timeTravelerStore(user.items.gear.owned)?[req.params.key] if window?.confirm? - return unless window.confirm("Buy this full set of items for 1 Mystic Hourglass?") + return unless window.confirm(i18n.t('hourglassBuyEquipSetConfirm')) return cb?({code:404, message:"Mystery set not found, or set already owned"}) unless mysterySet _.each mysterySet.items, (i)-> user.items.gear.owned[i.key]=true @@ -1018,6 +1018,10 @@ api.wrap = (user, main=true) -> user.purchased.plan.consecutive.trinkets-- cb? null, _.pick(user,$w 'items purchased.plan.consecutive') + hourglassPurchase: (req, cb, analytics)-> + return cb?({code:401, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 + + sell: (req, cb) -> {key, type} = req.params return cb?({code:404,message:":type not found. Must bes in [eggs, hatchingPotions, food]"}) unless type in ['eggs','hatchingPotions', 'food'] From 7765f9ca04197f685e4820a29f5c9a9c16ca3689 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 1 Sep 2015 17:37:39 -0400 Subject: [PATCH 02/18] test(mystery): Time Travelers test WIP --- common/locales/en/subscriber.json | 3 +- test/common/user.ops.buyMysterySet.test.js | 88 ++++++++++++++++++++++ 2 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 test/common/user.ops.buyMysterySet.test.js diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 55cb801c26..4b8fb312a1 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -65,5 +65,6 @@ "subUpdateCard": "Update Card", "subUpdateTitle": "Update", "subUpdateDescription": "Update the card to be charged.", - "notEnoughHourglasses": "You don't have enough Mystic Hourglasses." + "notEnoughHourglasses": "You don't have enough Mystic Hourglasses.", + "hourglassBuyEquipSetConfirm": "Buy this full set of items for 1 Mystic Hourglass?" } diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js new file mode 100644 index 0000000000..0fb7628967 --- /dev/null +++ b/test/common/user.ops.buyMysterySet.test.js @@ -0,0 +1,88 @@ +var sinon = require('sinon'); +var chai = require("chai") +chai.use(require("sinon-chai")) +var expect = chai.expect +var _ = require('lodash'); + +require('coffee-script'); +var shared = require('../../common/script/index.coffee'); +var Content = require('../../common/script/content.coffee'); + +describe('api.timeTravelerStore', function() { + var user; + + beforeEach(function() { + user = { + items: { + gear: { + owned: { + weapon_warrior_0: true + } + }, + pets: {}, + mounts: {} + }, + purchased: { + plan: { + planId: "basic", + consecutive: { + trinkets: 0 + } + } + } + }; + + shared.wrap(user); + }); + + context('Mystery Sets', function() { + + context('failure conditions', function() { + + it('does not grant mystery sets without Mystic Hourglasses', function(done) { + user.ops.buyMysterySet({params:{key:'201501'}}, function(response) { + expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); + expect(user.items.gear.owned).to.eql({'weapon_warrior_0': true}); + done(); + }); + }); + + it('does not grant mystery set that has already been purchased', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + user.items.gear.owned = { + weapon_warrior_0: true, + weapon_mystery_301404: true, + armor_mystery_301404: true, + head_mystery_301404: true, + eyewear_mystery_301404: true + }; + + user.ops.buyMysterySet({params:{key:'301404'}}, function(response) { + expect(response.message).to.eql("Mystery set not found, or set already owned"); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + }); + + context('successful purchases', function() { + + it('buys Steampunk Accessories Set', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.buyMysterySet({params:{key:'301404'}}, function() { + expect(user.purchased.plan.consecutive.trinkets).to.eql(0); + expect(user.items.gear.owned).to.eql({ + weapon_warrior_0: true, + weapon_mystery_301404: true, + armor_mystery_301404: true, + head_mystery_301404: true, + eyewear_mystery_301404: true + }); + done(); + }); + }); + }); + }); +}); + From 9eaa35ba2fcc0fbd5a5bfbcf010764c791cd477d Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 2 Sep 2015 13:47:37 -0400 Subject: [PATCH 03/18] test(time-travel): Rename test --- test/common/user.ops.buyMysterySet.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js index 0fb7628967..570aecf022 100644 --- a/test/common/user.ops.buyMysterySet.test.js +++ b/test/common/user.ops.buyMysterySet.test.js @@ -8,7 +8,7 @@ require('coffee-script'); var shared = require('../../common/script/index.coffee'); var Content = require('../../common/script/content.coffee'); -describe('api.timeTravelerStore', function() { +describe('user.ops.buyMysterySet', function() { var user; beforeEach(function() { From c3703441649ddd42bdd1549d6edfb06b92a3ca79 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 2 Sep 2015 15:03:57 -0400 Subject: [PATCH 04/18] test(time-travel): TDD for pets/mounts --- test/common/user.ops.buyMysterySet.test.js | 5 +- .../common/user.ops.hourglassPurchase.test.js | 123 ++++++++++++++++++ 2 files changed, 124 insertions(+), 4 deletions(-) create mode 100644 test/common/user.ops.hourglassPurchase.test.js diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js index 570aecf022..7e9bbd315d 100644 --- a/test/common/user.ops.buyMysterySet.test.js +++ b/test/common/user.ops.buyMysterySet.test.js @@ -18,13 +18,10 @@ describe('user.ops.buyMysterySet', function() { owned: { weapon_warrior_0: true } - }, - pets: {}, - mounts: {} + } }, purchased: { plan: { - planId: "basic", consecutive: { trinkets: 0 } diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js new file mode 100644 index 0000000000..d1b63f4b2c --- /dev/null +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -0,0 +1,123 @@ +var sinon = require('sinon'); +var chai = require("chai") +chai.use(require("sinon-chai")) +var expect = chai.expect +var _ = require('lodash'); + +require('coffee-script'); +var shared = require('../../common/script/index.coffee'); +var Content = require('../../common/script/content.coffee'); + +describe('user.ops.hourglassPurchase', function() { + var user; + + beforeEach(function() { + user = { + items: { + pets: {}, + mounts: {} + }, + purchased: { + plan: { + consecutive: { + trinkets: 0 + } + } + } + }; + + shared.wrap(user); + }); + + context('Time Travel Stable', function() { + + context('failure conditions', function() { + + it('does not grant pets without Mystic Hourglasses', function(done) { + user.ops.hourglassPurchase({params:{category: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); + expect(user.items.pets).to.eql({}); + done(); + }); + }); + + it('does not grant mounts without Mystic Hourglasses', function(done) { + user.ops.hourglassPurchase({params:{category: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); + expect(user.items.mounts).to.eql({}); + done(); + }); + }); + + it('does not grant pet that has already been purchased', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + user.items.pets = { + 'MantisShrimp-Base': true + }; + + user.ops.hourglassPurchase({params:{category: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql("Pet already owned."); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant mount that has already been purchased', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + user.items.mounts = { + 'MantisShrimp-Base': true + }; + + user.ops.hourglassPurchase({params:{category: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql("Mount already owned."); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant pet that is not part of the Time Travel Stable', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.hourglassPurchase({params: {category: 'pets', key: 'Wolf-Veteran'}}, function(response) { + expect(response.message).to.eql('Pet not available for purchase with Mystic Hourglass.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + + it('does not grant mount that is not part of the Time Travel Stable', function(done) { + user.purchased.plan.consecutive.trinkets = 1; + + user.ops.hourglassPurchase({params: {category: 'mounts', key: 'Orca-Base'}}, function(response) { + expect(response.message).to.eql('Mount not available for purchase with Mystic Hourglass.'); + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + done(); + }); + }); + }); + + context('successful purchases', function() { + + it('buys a pet', function(done) { + user.purchased.plan.consecutive.trinkets = 2; + + user.ops.hourglassPurchase({params: {category: 'pets', key: 'MantisShrimp-Base'}}, function() { + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.pets).to.eql({'MantisShrimp-Base':true}); + done(); + }); + }); + + it('buys a mount', function(done) { + user.purchased.plan.consecutive.trinkets = 2; + + user.ops.hourglassPurchase({params: {category: 'mounts', key: 'MantisShrimp-Base'}}, function() { + expect(user.purchased.plan.consecutive.trinkets).to.eql(1); + expect(user.items.mounts).to.eql({'MantisShrimp-Base':true}); + done(); + }); + }); + }); + }); +}); + From 66a8be526b387cb81ef97334cf21859928703de3 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 15 Sep 2015 12:40:01 -0500 Subject: [PATCH 05/18] WIP(hourglass): Error catches --- common/locales/en/subscriber.json | 7 +++- common/script/content/index.coffee | 18 ++++------ common/script/index.coffee | 7 ++-- .../common/user.ops.hourglassPurchase.test.js | 34 ++++++++++++------- website/src/routes/apiv2.coffee | 11 +++++- 5 files changed, 48 insertions(+), 29 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 253812a483..b5a94d47c2 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -67,5 +67,10 @@ "subUpdateTitle": "Update", "subUpdateDescription": "Update the card to be charged.", "notEnoughHourglasses": "You don't have enough Mystic Hourglasses.", - "hourglassBuyEquipSetConfirm": "Buy this full set of items for 1 Mystic Hourglass?" + "hourglassBuyEquipSetConfirm": "Buy this full set of items for 1 Mystic Hourglass?", + "petsAlreadyOwned": "Pet already owned.", + "mountsAlreadyOwned": "Mount already owned.", + "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ", + "petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.", + "mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass." } diff --git a/common/script/content/index.coffee b/common/script/content/index.coffee index 3a1234de71..300c918b04 100644 --- a/common/script/content/index.coffee +++ b/common/script/content/index.coffee @@ -533,18 +533,12 @@ _.each gearTypes, (type) -> Time Traveler Store, mystery sets need their items mapped in ### _.each api.mystery, (v,k)-> v.items = _.where api.gear.flat, {mystery:k} -api.timeTravelerStore = (owned, category) -> +api.timeTravelerStore = (owned) -> ownedKeys = _.keys owned.toObject?() or owned # mongoose workaround - if not category or category is "mystery" - _.reduce api.mystery, (m,v,k)-> - return m if k=='wondercon' or ~ownedKeys.indexOf(v.items[0].key) # skip wondercon and already-owned sets - m[k] = v;m - , {} - else - _.reduce api.timeTravelStable[category], (m,v,i)-> - returm m if ~ownedKeys.indexOf(v) - m[i] = v;m - , [] + _.reduce api.mystery, (m,v,k)-> + return m if k=='wondercon' or ~ownedKeys.indexOf(v.items[0].key) # skip wondercon and already-owned sets + m[k] = v;m + , {} ### --------------------------------------------------------------- @@ -2052,7 +2046,7 @@ api.quests = ] gp: 0 exp: 650 - + cheetah: text: t('questCheetahText') notes: t('questCheetahNotes') diff --git a/common/script/index.coffee b/common/script/index.coffee index b056552a41..1a1f915d0b 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -1019,8 +1019,11 @@ api.wrap = (user, main=true) -> cb? null, _.pick(user,$w 'items purchased.plan.consecutive') hourglassPurchase: (req, cb, analytics)-> - return cb?({code:401, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 - + {type, key} = req.params + return cb?({code:400, message:i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))}) unless content.timeTravelStable[type] + return cb?({code:400, message:i18n.t(type+'NotAllowedHourglass', req.language)}) if not _.includes(content.timeTravelStable[type], key) + return cb?({code:400, message:i18n.t(type+'AlreadyOwned', req.language)}) if user.items[type][key] + return cb?({code:400, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 sell: (req, cb) -> {key, type} = req.params diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js index d1b63f4b2c..5603154534 100644 --- a/test/common/user.ops.hourglassPurchase.test.js +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -15,7 +15,8 @@ describe('user.ops.hourglassPurchase', function() { user = { items: { pets: {}, - mounts: {} + mounts: {}, + hatchingPotions: {} }, purchased: { plan: { @@ -33,8 +34,16 @@ describe('user.ops.hourglassPurchase', function() { context('failure conditions', function() { + it('does not allow purchase of unsupported item types', function(done) { + user.ops.hourglassPurchase({params:{type: 'hatchingPotions', key: 'Base'}}, function(response) { + expect(response.message).to.eql("Item type not supported for purchase with Mystic Hourglass. Allowed types: [pets, mounts]"); + expect(user.items.hatchingPotions).to.eql({}); + done(); + }); + }); + it('does not grant pets without Mystic Hourglasses', function(done) { - user.ops.hourglassPurchase({params:{category: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params:{type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); expect(user.items.pets).to.eql({}); done(); @@ -42,20 +51,20 @@ describe('user.ops.hourglassPurchase', function() { }); it('does not grant mounts without Mystic Hourglasses', function(done) { - user.ops.hourglassPurchase({params:{category: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params:{type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { expect(response.message).to.eql("You don't have enough Mystic Hourglasses."); expect(user.items.mounts).to.eql({}); done(); }); }); - + it('does not grant pet that has already been purchased', function(done) { user.purchased.plan.consecutive.trinkets = 1; user.items.pets = { 'MantisShrimp-Base': true - }; + }; - user.ops.hourglassPurchase({params:{category: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params:{type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { expect(response.message).to.eql("Pet already owned."); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); @@ -66,9 +75,9 @@ describe('user.ops.hourglassPurchase', function() { user.purchased.plan.consecutive.trinkets = 1; user.items.mounts = { 'MantisShrimp-Base': true - }; + }; - user.ops.hourglassPurchase({params:{category: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + user.ops.hourglassPurchase({params:{type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { expect(response.message).to.eql("Mount already owned."); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); @@ -78,7 +87,7 @@ describe('user.ops.hourglassPurchase', function() { it('does not grant pet that is not part of the Time Travel Stable', function(done) { user.purchased.plan.consecutive.trinkets = 1; - user.ops.hourglassPurchase({params: {category: 'pets', key: 'Wolf-Veteran'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'Wolf-Veteran'}}, function(response) { expect(response.message).to.eql('Pet not available for purchase with Mystic Hourglass.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); @@ -88,7 +97,7 @@ describe('user.ops.hourglassPurchase', function() { it('does not grant mount that is not part of the Time Travel Stable', function(done) { user.purchased.plan.consecutive.trinkets = 1; - user.ops.hourglassPurchase({params: {category: 'mounts', key: 'Orca-Base'}}, function(response) { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'Orca-Base'}}, function(response) { expect(response.message).to.eql('Mount not available for purchase with Mystic Hourglass.'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); done(); @@ -101,7 +110,7 @@ describe('user.ops.hourglassPurchase', function() { it('buys a pet', function(done) { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {category: 'pets', key: 'MantisShrimp-Base'}}, function() { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, function() { expect(user.purchased.plan.consecutive.trinkets).to.eql(1); expect(user.items.pets).to.eql({'MantisShrimp-Base':true}); done(); @@ -111,7 +120,7 @@ describe('user.ops.hourglassPurchase', function() { it('buys a mount', function(done) { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {category: 'mounts', key: 'MantisShrimp-Base'}}, function() { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, function() { expect(user.purchased.plan.consecutive.trinkets).to.eql(1); expect(user.items.mounts).to.eql({'MantisShrimp-Base':true}); done(); @@ -120,4 +129,3 @@ describe('user.ops.hourglassPurchase', function() { }); }); }); - diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee index db26ac57a7..3757b3142a 100644 --- a/website/src/routes/apiv2.coffee +++ b/website/src/routes/apiv2.coffee @@ -180,13 +180,22 @@ module.exports = (swagger, v2) -> "/user/inventory/purchase/{type}/{key}": spec: method: 'POST' - description: "Purchase a gem-purchaseable item from Alexander" + description: "Purchase a Gem-purchasable item from Alexander" parameters:[ path('type',"The type of object you're purchasing.",'string',['eggs','hatchingPotions','food','quests','special']) path('key',"The object key you're purchasing (call /content route for available keys)",'string') ] action: user.purchase + "/user/inventory/hourglass/{type}/{key}" + spec: + method: 'POST' + description: "Purchase a pet or mount using a Mystic Hourglass" + parameters:[ + path('type',"The type of object you're purchasing.",'string',['pets','mounts']) + path('key',"The object key you're purchasing (call /content route for available keys)",'string') + ] + action: user.hourglassPurchase "/user/inventory/feed/{pet}/{food}": spec: From 2ce52e980d9dc89fe384fa55ddd245562fe3cc0b Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 15 Sep 2015 14:55:38 -0400 Subject: [PATCH 06/18] feat(hourglass): Buy pets/mounts --- common/locales/en/subscriber.json | 3 ++- common/script/index.coffee | 14 ++++++++++---- test/common/user.ops.buyMysterySet.test.js | 2 +- test/common/user.ops.hourglassPurchase.test.js | 12 +++++++----- 4 files changed, 20 insertions(+), 11 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index b5a94d47c2..e496bf2364 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -72,5 +72,6 @@ "mountsAlreadyOwned": "Mount already owned.", "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ", "petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.", - "mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass." + "mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.", + "hourglassPurchase": "Purchased an item using a Mystic Hourglass!" } diff --git a/common/script/index.coffee b/common/script/index.coffee index 1a1f915d0b..a11376201f 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -1020,10 +1020,16 @@ api.wrap = (user, main=true) -> hourglassPurchase: (req, cb, analytics)-> {type, key} = req.params - return cb?({code:400, message:i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))}) unless content.timeTravelStable[type] - return cb?({code:400, message:i18n.t(type+'NotAllowedHourglass', req.language)}) if not _.includes(content.timeTravelStable[type], key) - return cb?({code:400, message:i18n.t(type+'AlreadyOwned', req.language)}) if user.items[type][key] - return cb?({code:400, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 + return cb?({code:403, message:i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))}) unless content.timeTravelStable[type] + return cb?({code:403, message:i18n.t(type+'NotAllowedHourglass', req.language)}) unless _.contains(content.timeTravelStable[type], key) + return cb?({code:403, message:i18n.t(type+'AlreadyOwned', req.language)}) if user.items[type][key] + return cb?({code:403, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 + user.purchased.plan.consecutive.trinkets-- + if type is 'pets' + user.items.pets[key] = 5 + if type is 'mounts' + user.items.mounts[key] = true + cb? {code:200, message:i18n.t('hourglassPurchase', req.language)}, _.pick(user,$w 'items purchased.plan.consecutive') sell: (req, cb) -> {key, type} = req.params diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js index 7e9bbd315d..e16daa3755 100644 --- a/test/common/user.ops.buyMysterySet.test.js +++ b/test/common/user.ops.buyMysterySet.test.js @@ -6,7 +6,7 @@ var _ = require('lodash'); require('coffee-script'); var shared = require('../../common/script/index.coffee'); -var Content = require('../../common/script/content.coffee'); +var Content = require('../../common/script/content/index.coffee'); describe('user.ops.buyMysterySet', function() { var user; diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js index 5603154534..3d57d63702 100644 --- a/test/common/user.ops.hourglassPurchase.test.js +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -6,7 +6,7 @@ var _ = require('lodash'); require('coffee-script'); var shared = require('../../common/script/index.coffee'); -var Content = require('../../common/script/content.coffee'); +var Content = require('../../common/script/content/index.coffee'); describe('user.ops.hourglassPurchase', function() { var user; @@ -36,7 +36,7 @@ describe('user.ops.hourglassPurchase', function() { it('does not allow purchase of unsupported item types', function(done) { user.ops.hourglassPurchase({params:{type: 'hatchingPotions', key: 'Base'}}, function(response) { - expect(response.message).to.eql("Item type not supported for purchase with Mystic Hourglass. Allowed types: [pets, mounts]"); + expect(response.message).to.eql('Item type not supported for purchase with Mystic Hourglass. Allowed types: ["pets","mounts"]'); expect(user.items.hatchingPotions).to.eql({}); done(); }); @@ -110,9 +110,10 @@ describe('user.ops.hourglassPurchase', function() { it('buys a pet', function(done) { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, function() { + user.ops.hourglassPurchase({params: {type: 'pets', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); - expect(user.items.pets).to.eql({'MantisShrimp-Base':true}); + expect(user.items.pets).to.eql({'MantisShrimp-Base':5}); done(); }); }); @@ -120,7 +121,8 @@ describe('user.ops.hourglassPurchase', function() { it('buys a mount', function(done) { user.purchased.plan.consecutive.trinkets = 2; - user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, function() { + user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, function(response) { + expect(response.message).to.eql('Purchased an item using a Mystic Hourglass!'); expect(user.purchased.plan.consecutive.trinkets).to.eql(1); expect(user.items.mounts).to.eql({'MantisShrimp-Base':true}); done(); From ce764ca28ea3d5d1855929bcedf820dbe6ddb74a Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 15 Sep 2015 15:41:57 -0400 Subject: [PATCH 07/18] WIP(hourglass): Mystery sets API route --- common/locales/en/subscriber.json | 3 ++- common/script/index.coffee | 10 +++++++++- website/src/routes/apiv2.coffee | 9 +++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index e496bf2364..a47ca9ab76 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -73,5 +73,6 @@ "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ", "petsNotAllowedHourglass": "Pet not available for purchase with Mystic Hourglass.", "mountsNotAllowedHourglass": "Mount not available for purchase with Mystic Hourglass.", - "hourglassPurchase": "Purchased an item using a Mystic Hourglass!" + "hourglassPurchase": "Purchased an item using a Mystic Hourglass!", + "hourglassPurchaseSet": "Purchased an item set using a Mystic Hourglass!" } diff --git a/common/script/index.coffee b/common/script/index.coffee index a11376201f..8714ff24ac 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -1016,7 +1016,7 @@ api.wrap = (user, main=true) -> analytics?.track('acquire item', analyticsData) user.purchased.plan.consecutive.trinkets-- - cb? null, _.pick(user,$w 'items purchased.plan.consecutive') + cb? {code:200, message:i18n.t('hourglassPurchaseSet', req.language)}, _.pick(user,$w 'items purchased.plan.consecutive') hourglassPurchase: (req, cb, analytics)-> {type, key} = req.params @@ -1029,6 +1029,14 @@ api.wrap = (user, main=true) -> user.items.pets[key] = 5 if type is 'mounts' user.items.mounts[key] = true + analyticsData = { + uuid: user._id, + itemKey: key, + itemType: type, + acquireMethod: 'Hourglass', + category: 'behavior' + } + analytics?.track('acquire item', analyticsData) cb? {code:200, message:i18n.t('hourglassPurchase', req.language)}, _.pick(user,$w 'items purchased.plan.consecutive') sell: (req, cb) -> diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee index 3757b3142a..4612509ed3 100644 --- a/website/src/routes/apiv2.coffee +++ b/website/src/routes/apiv2.coffee @@ -197,6 +197,15 @@ module.exports = (swagger, v2) -> ] action: user.hourglassPurchase + "/user/inventory/hourglass/mysterySet/{key}" + spec: + method: 'POST' + description: "Purchase a Mystery Item Set using a Mystic Hourglass" + parameters:[ + path('key',"The key for the Mystery Set you're purchasing (call /content route for available keys)",'string') + ] + action: user.buyMysterySet + "/user/inventory/feed/{pet}/{food}": spec: method: 'POST' From 6e4cc4b35b4ddb06132c25df2dc1de74b25c2ae1 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 15 Sep 2015 15:47:19 -0400 Subject: [PATCH 08/18] fix(API): Missing colons --- website/src/routes/apiv2.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee index 4612509ed3..14210213e4 100644 --- a/website/src/routes/apiv2.coffee +++ b/website/src/routes/apiv2.coffee @@ -187,7 +187,7 @@ module.exports = (swagger, v2) -> ] action: user.purchase - "/user/inventory/hourglass/{type}/{key}" + "/user/inventory/hourglass/{type}/{key}": spec: method: 'POST' description: "Purchase a pet or mount using a Mystic Hourglass" @@ -197,7 +197,7 @@ module.exports = (swagger, v2) -> ] action: user.hourglassPurchase - "/user/inventory/hourglass/mysterySet/{key}" + "/user/inventory/hourglass/mysterySet/{key}": spec: method: 'POST' description: "Purchase a Mystery Item Set using a Mystic Hourglass" From c8cc01fc444faa31eac87070f5384a050ccaa785 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 14:35:43 -0400 Subject: [PATCH 09/18] feat(hourglass): Buy pets/mounts Functionally complete as of this commit. Further commits will provide more polish and refactoring. --- common/script/content/index.coffee | 8 ++++-- common/script/index.coffee | 2 +- website/src/routes/apiv2.coffee | 2 +- .../options/inventory/time-travelers.jade | 28 +++++++++++++------ 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/common/script/content/index.coffee b/common/script/content/index.coffee index 300c918b04..af2685ffca 100644 --- a/common/script/content/index.coffee +++ b/common/script/content/index.coffee @@ -1127,8 +1127,12 @@ api.specialMounts = 'Gryphon-RoyalPurple': 'royalPurpleGryphon' api.timeTravelStable = - pets: ['Mammoth-Base','MantisShrimp-Base'] - mounts: ['Mammoth-Base','MantisShrimp-Base'] + pets: + 'Mammoth-Base': t('mammoth') + 'MantisShrimp-Base': t('mantisShrimp') + mounts: + 'Mammoth-Base': t('mammoth') + 'MantisShrimp-Base': t('mantisShrimp') api.hatchingPotions = Base: value: 2, text: t('hatchingPotionBase') diff --git a/common/script/index.coffee b/common/script/index.coffee index 8714ff24ac..02b1d5c4bc 100644 --- a/common/script/index.coffee +++ b/common/script/index.coffee @@ -1021,7 +1021,7 @@ api.wrap = (user, main=true) -> hourglassPurchase: (req, cb, analytics)-> {type, key} = req.params return cb?({code:403, message:i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))}) unless content.timeTravelStable[type] - return cb?({code:403, message:i18n.t(type+'NotAllowedHourglass', req.language)}) unless _.contains(content.timeTravelStable[type], key) + return cb?({code:403, message:i18n.t(type+'NotAllowedHourglass', req.language)}) unless _.contains(_.keys(content.timeTravelStable[type]), key) return cb?({code:403, message:i18n.t(type+'AlreadyOwned', req.language)}) if user.items[type][key] return cb?({code:403, message:i18n.t('notEnoughHourglasses', req.language)}) unless user.purchased.plan.consecutive.trinkets > 0 user.purchased.plan.consecutive.trinkets-- diff --git a/website/src/routes/apiv2.coffee b/website/src/routes/apiv2.coffee index 14210213e4..876295a82f 100644 --- a/website/src/routes/apiv2.coffee +++ b/website/src/routes/apiv2.coffee @@ -197,7 +197,7 @@ module.exports = (swagger, v2) -> ] action: user.hourglassPurchase - "/user/inventory/hourglass/mysterySet/{key}": + "/user/inventory/hourglass/mystery/{key}": spec: method: 'POST' description: "Purchase a Mystery Item Set using a Mystic Hourglass" diff --git a/website/views/options/inventory/time-travelers.jade b/website/views/options/inventory/time-travelers.jade index 4b8277038f..0537c4ad00 100644 --- a/website/views/options/inventory/time-travelers.jade +++ b/website/views/options/inventory/time-travelers.jade @@ -17,12 +17,22 @@ .row: .col-md-7 .alert.alert-info(ng-if='hasAllTimeTravelerItems()')=env.t('timeTravelersAlreadyOwned') - .row: .col-md-12 - li.customize-menu.inventory-gear - menu.pets-menu(label='{{::set.text}}', ng-repeat='set in Content.timeTravelerStore(user.items.gear.owned)') - div(ng-repeat='item in set.items') - button.customize-option(class='shop_{{::item.key}}', - popover='{{::item.notes()}}', popover-title='{{::item.text()}}', - popover-trigger='mouseenter', popover-placement='right', - popover-append-to-body='true', - ng-click='user.ops.buyMysterySet({params:{key:set.key}})') + .row + .col-md-12 + li.customize-menu.inventory-gear + each prepend, type in {pets:'Pet-', mounts:'Mount_Head_'} + menu.pets-menu(label=env.t('#{type}')) + div(ng-repeat='(item, text) in Content.timeTravelStable["#{type}"]', style='margin-top:0') + button.pet-button(class='#{prepend}{{::item}}', style='margin-top:0', + ng-if='!user.items["#{type}"][item]', + popover='{{::text()}}', popover-trigger='mouseenter', + popover-placement='right', popover-append-to-body='true', + ng-click='user.ops.hourglassPurchase({params:{key:item,type:"#{type}"}})') + li.customize-menu.inventory-gear + menu.pets-menu(label='{{::set.text}}', ng-repeat='set in Content.timeTravelerStore(user.items.gear.owned)') + div(ng-repeat='item in set.items') + button.customize-option(class='shop_{{::item.key}}', + popover='{{::item.notes()}}', popover-title='{{::item.text()}}', + popover-trigger='mouseenter', popover-placement='right', + popover-append-to-body='true', + ng-click='user.ops.buyMysterySet({params:{key:set.key}})') From 3bfeb1571501b0f51ce614b935f4be5b46baf154 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 15:27:19 -0400 Subject: [PATCH 10/18] test(hourglass): TDD for Time Travel filtering --- common/locales/en/subscriber.json | 4 +- test/spec/controllers/inventoryCtrlSpec.js | 52 ++++++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index a47ca9ab76..6f11869f54 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -59,8 +59,8 @@ "timeTravelers": "Time Travelers", "timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> and <%= linkStartVicky %>Vicky<%= linkEnd %>", "timeTravelersTitle": "Mysterious Time Travelers", - "timeTravelersPopoverNoSub": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! <%= linkStart %>Subscribers<%= linkEnd %> earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a Subscriber Item Set from the past.... or maybe even the future.", - "timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the Mystery Item Set you would like. You can see a list of the past item sets <%= linkStart %>here<%= linkEnd %>! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?", + "timeTravelersPopoverNoSub": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! <%= linkStart %>Subscribers<%= linkEnd %> earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a rare pet, mount, or Subscriber Item Set from the past... or maybe even the future.", + "timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets <%= linkStart %>here<%= linkEnd %>! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?", "timeTravelersAlreadyOwned": "Congratulations! You already own all of the Mystery Items. Thanks for supporting the site!", "mysticHourglassPopover": "Mystic Hourglass allows you to purchase previous months' subscriber sets.", "subUpdateCard": "Update Card", diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index f1c5bc3b73..e4362db032 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -1,5 +1,7 @@ 'use strict'; +var _ = require('lodash'); + describe('Inventory Controller', function() { var scope, ctrl, user, rootScope; @@ -224,14 +226,56 @@ describe('Inventory Controller', function() { }); describe('#hasAllTimeTravelerItems', function() { - it('returns false if there are items left in the time traveler store', function() { - expect(scope.hasAllTimeTravelerItems()).to.eql(false); + it('returns false if items remain for purchase with Mystic Hourglasses', function() { + expect(scope.hasAllTimeTravelerItems).to.eql(false); }); - it('returns true if there are no items left to purchase', inject(function(Content) { + it('returns true if no more items remain for purchase with Mystic Hourglasses', function() { + sandbox.stub(Content, 'timeTravelerStore').returns({}); + _.forEach(Content.timeTravelStable.pets, function(v,k) { + user.items.pets[k] = 5; + }); + _.forEach(Content.timeTravelStable.mounts, function(v,k) { + user.items.mounts[k] = true; + }); + + expect(scope.hasAllTimeTravelerItems).to.eql(true); + }); + }); + + describe('#hasAllTimeTravelerItemsOfType', function() { + it('returns false for Mystery Sets if there are sets left in the time traveler store', function() { + expect(scope.hasAllTimeTravelerItemsOfType('mystery')).to.eql(false); + }); + + it('returns true for Mystery Sets if there are no sets left to purchase', inject(function(Content) { sandbox.stub(Content, 'timeTravelerStore').returns({}); - expect(scope.hasAllTimeTravelerItems()).to.eql(true); + expect(scope.hasAllTimeTravelerItems('mystery')).to.eql(true); })); + + it('returns false for pets if user does not own all pets in the Time Travel Stable', function() { + expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(false); + }); + + it('returns true for pets if user owns all pets in the Time Travel Stable', function() { + _.forEach(Content.timeTravelStable.pets, function(v,k) { + user.items.pets[k] = 5; + }); + + expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(true); + }); + + it('returns false for mounts if user does not own all mounts in the Time Travel Stable', function() { + expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(false); + }); + + it('returns true for mounts if user owns all mounts in the Time Travel Stable', function() { + _.forEach(Content.timeTravelStable.mounts, function(v,k) { + user.items.mounts[k] = true; + }); + + expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(true); + }); }); }); From 7cc29810f3e08e0b40b9bafaeac600784d95e822 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 16:01:13 -0400 Subject: [PATCH 11/18] fix(test): inject Content Also refactors to remove lodash where it's overkill. --- .../common/user.ops.hourglassPurchase.test.js | 1 - test/spec/controllers/inventoryCtrlSpec.js | 38 +++++++++---------- 2 files changed, 18 insertions(+), 21 deletions(-) diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js index 3d57d63702..458029c6df 100644 --- a/test/common/user.ops.hourglassPurchase.test.js +++ b/test/common/user.ops.hourglassPurchase.test.js @@ -2,7 +2,6 @@ var sinon = require('sinon'); var chai = require("chai") chai.use(require("sinon-chai")) var expect = chai.expect -var _ = require('lodash'); require('coffee-script'); var shared = require('../../common/script/index.coffee'); diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index e4362db032..e7e3520ba5 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -1,7 +1,5 @@ 'use strict'; -var _ = require('lodash'); - describe('Inventory Controller', function() { var scope, ctrl, user, rootScope; @@ -230,17 +228,17 @@ describe('Inventory Controller', function() { expect(scope.hasAllTimeTravelerItems).to.eql(false); }); - it('returns true if no more items remain for purchase with Mystic Hourglasses', function() { + it('returns true if there are no items left to purchase', inject(function(Content) { sandbox.stub(Content, 'timeTravelerStore').returns({}); - _.forEach(Content.timeTravelStable.pets, function(v,k) { - user.items.pets[k] = 5; - }); - _.forEach(Content.timeTravelStable.mounts, function(v,k) { - user.items.mounts[k] = true; - }); + for (var pet in Content.timeTravelStable.pets) { + user.items.pets[pet] = 5; + } + for (var mount in Content.timeTravelStable.mounts) { + user.items.mounts[mount] = true; + } expect(scope.hasAllTimeTravelerItems).to.eql(true); - }); + })); }); describe('#hasAllTimeTravelerItemsOfType', function() { @@ -258,24 +256,24 @@ describe('Inventory Controller', function() { expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(false); }); - it('returns true for pets if user owns all pets in the Time Travel Stable', function() { - _.forEach(Content.timeTravelStable.pets, function(v,k) { - user.items.pets[k] = 5; - }); + it('returns true for pets if user owns all pets in the Time Travel Stable', inject(function(Content) { + for (var pet in Content.timeTravelStable.pets) { + user.items.pets[pet] = 5; + } expect(scope.hasAllTimeTravelerItemsOfType('pets')).to.eql(true); - }); + })); it('returns false for mounts if user does not own all mounts in the Time Travel Stable', function() { expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(false); }); - it('returns true for mounts if user owns all mounts in the Time Travel Stable', function() { - _.forEach(Content.timeTravelStable.mounts, function(v,k) { - user.items.mounts[k] = true; - }); + it('returns true for mounts if user owns all mounts in the Time Travel Stable', inject(function(Content) { + for (var mount in Content.timeTravelStable.mounts) { + user.items.mounts[mount] = true; + } expect(scope.hasAllTimeTravelerItemsOfType('mounts')).to.eql(true); - }); + })); }); }); From 2bde07fa1c34db5ec9fe7ef0a824db7b2d85f963 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 16:12:29 -0400 Subject: [PATCH 12/18] fix(test): Call hasAll correctly --- test/common/user.ops.buyMysterySet.test.js | 1 - test/spec/controllers/inventoryCtrlSpec.js | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/test/common/user.ops.buyMysterySet.test.js b/test/common/user.ops.buyMysterySet.test.js index e16daa3755..f0a4a7b2f8 100644 --- a/test/common/user.ops.buyMysterySet.test.js +++ b/test/common/user.ops.buyMysterySet.test.js @@ -2,7 +2,6 @@ var sinon = require('sinon'); var chai = require("chai") chai.use(require("sinon-chai")) var expect = chai.expect -var _ = require('lodash'); require('coffee-script'); var shared = require('../../common/script/index.coffee'); diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index e7e3520ba5..a9637fa675 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -225,7 +225,7 @@ describe('Inventory Controller', function() { describe('#hasAllTimeTravelerItems', function() { it('returns false if items remain for purchase with Mystic Hourglasses', function() { - expect(scope.hasAllTimeTravelerItems).to.eql(false); + expect(scope.hasAllTimeTravelerItems()).to.eql(false); }); it('returns true if there are no items left to purchase', inject(function(Content) { @@ -237,7 +237,7 @@ describe('Inventory Controller', function() { user.items.mounts[mount] = true; } - expect(scope.hasAllTimeTravelerItems).to.eql(true); + expect(scope.hasAllTimeTravelerItems()).to.eql(true); })); }); From 98cd8364c662b7c438c9d8bf6e1344e283dacf9e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 16:35:04 -0400 Subject: [PATCH 13/18] feat(hourglass): Individual filter functions --- test/spec/controllers/inventoryCtrlSpec.js | 2 +- .../public/js/controllers/inventoryCtrl.js | 21 +++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index a9637fa675..dd6b1dc794 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -249,7 +249,7 @@ describe('Inventory Controller', function() { it('returns true for Mystery Sets if there are no sets left to purchase', inject(function(Content) { sandbox.stub(Content, 'timeTravelerStore').returns({}); - expect(scope.hasAllTimeTravelerItems('mystery')).to.eql(true); + expect(scope.hasAllTimeTravelerItemsOfType('mystery')).to.eql(true); })); it('returns false for pets if user does not own all pets in the Time Travel Stable', function() { diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index 102303a76e..560663ee5c 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -250,11 +250,24 @@ habitrpg.controller("InventoryCtrl", }); }; - $scope.hasAllTimeTravelerItems = function(items) { - var itemsLeftInTimeTravlerStore = Content.timeTravelerStore(user.items.gear.owned); - var keys = Object.keys(itemsLeftInTimeTravlerStore); + $scope.hasAllTimeTravelerItems = function() { + return (hasAllTimeTravelerItemsOfType('mystery') && hasAllTimeTravelerItemsOfType('pets') && hasAllTimeTravelerItemsOfType('mounts')); + }; - return keys.length === 0; + $scope.hasAllTimeTravelerItemsOfType = function(type) { + if (type === 'mystery') { + var itemsLeftInTimeTravelerStore = Content.timeTravelerStore(user.items.gear.owned); + var keys = Object.keys(itemsLeftInTimeTravelerStore); + + return keys.length === 0; + } + if (type === 'pets' || type === 'mounts') { + for (var key in Content.timeTravelStable[type]) { + if (!user.items[type][key]) return false; + } + return true; + } + else return Console.log('Time Traveler item type must be in ["pets","mounts","mystery"]'); }; function _updateDropAnimalCount(items) { From fb50f27fa737e6843bbadc68397ab925707dce4a Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 16:46:12 -0400 Subject: [PATCH 14/18] fix(hourglass): Use method on $scope --- website/public/js/controllers/inventoryCtrl.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index 560663ee5c..d44dc6ae3f 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -251,7 +251,9 @@ habitrpg.controller("InventoryCtrl", }; $scope.hasAllTimeTravelerItems = function() { - return (hasAllTimeTravelerItemsOfType('mystery') && hasAllTimeTravelerItemsOfType('pets') && hasAllTimeTravelerItemsOfType('mounts')); + return ($scope.hasAllTimeTravelerItemsOfType('mystery') && + $scope.hasAllTimeTravelerItemsOfType('pets') && + $scope.hasAllTimeTravelerItemsOfType('mounts')); }; $scope.hasAllTimeTravelerItemsOfType = function(type) { From eb0dd636c17714d931c33ef03f669542fa9685c3 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 17:40:25 -0400 Subject: [PATCH 15/18] feat(hourglass): Confirm before buy --- common/locales/en/subscriber.json | 3 ++- website/public/js/controllers/inventoryCtrl.js | 6 ++++++ website/views/options/inventory/time-travelers.jade | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/common/locales/en/subscriber.json b/common/locales/en/subscriber.json index 6f11869f54..9c68a31979 100644 --- a/common/locales/en/subscriber.json +++ b/common/locales/en/subscriber.json @@ -61,13 +61,14 @@ "timeTravelersTitle": "Mysterious Time Travelers", "timeTravelersPopoverNoSub": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! <%= linkStart %>Subscribers<%= linkEnd %> earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a rare pet, mount, or Subscriber Item Set from the past... or maybe even the future.", "timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets <%= linkStart %>here<%= linkEnd %>! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?", - "timeTravelersAlreadyOwned": "Congratulations! You already own all of the Mystery Items. Thanks for supporting the site!", + "timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!", "mysticHourglassPopover": "Mystic Hourglass allows you to purchase previous months' subscriber sets.", "subUpdateCard": "Update Card", "subUpdateTitle": "Update", "subUpdateDescription": "Update the card to be charged.", "notEnoughHourglasses": "You don't have enough Mystic Hourglasses.", "hourglassBuyEquipSetConfirm": "Buy this full set of items for 1 Mystic Hourglass?", + "hourglassBuyItemConfirm": "Buy this item for 1 Mystic Hourglass?", "petsAlreadyOwned": "Pet already owned.", "mountsAlreadyOwned": "Mount already owned.", "typeNotAllowedHourglass": "Item type not supported for purchase with Mystic Hourglass. Allowed types: ", diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js index d44dc6ae3f..70e1d5c9fc 100644 --- a/website/public/js/controllers/inventoryCtrl.js +++ b/website/public/js/controllers/inventoryCtrl.js @@ -272,6 +272,12 @@ habitrpg.controller("InventoryCtrl", else return Console.log('Time Traveler item type must be in ["pets","mounts","mystery"]'); }; + $scope.clickTimeTravelItem = function(type,key) { + if (user.purchased.plan.consecutive.trinkets < 1) return user.ops.hourglassPurchase({params:{type:type,key:key}}); + if (!window.confirm(window.env.t('hourglassBuyItemConfirm'))) return; + user.ops.hourglassPurchase({params:{type:type,key:key}}); + }; + function _updateDropAnimalCount(items) { $scope.petCount = Shared.count.beastMasterProgress(items.pets); $scope.mountCount = Shared.count.mountMasterProgress(items.mounts); diff --git a/website/views/options/inventory/time-travelers.jade b/website/views/options/inventory/time-travelers.jade index 0537c4ad00..82dd6227b4 100644 --- a/website/views/options/inventory/time-travelers.jade +++ b/website/views/options/inventory/time-travelers.jade @@ -21,13 +21,13 @@ .col-md-12 li.customize-menu.inventory-gear each prepend, type in {pets:'Pet-', mounts:'Mount_Head_'} - menu.pets-menu(label=env.t('#{type}')) + menu.pets-menu(label=env.t('#{type}'), ng-if='!hasAllTimeTravelerItemsOfType("#{type}")') div(ng-repeat='(item, text) in Content.timeTravelStable["#{type}"]', style='margin-top:0') button.pet-button(class='#{prepend}{{::item}}', style='margin-top:0', ng-if='!user.items["#{type}"][item]', popover='{{::text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', - ng-click='user.ops.hourglassPurchase({params:{key:item,type:"#{type}"}})') + ng-click='clickTimeTravelItem("#{type}",item)') li.customize-menu.inventory-gear menu.pets-menu(label='{{::set.text}}', ng-repeat='set in Content.timeTravelerStore(user.items.gear.owned)') div(ng-repeat='item in set.items') From 9be0990217481524a1acc9921ea651f33484c3bd Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 18:34:20 -0400 Subject: [PATCH 16/18] chore(news): Bailey 2015-09-16 --- website/views/shared/new-stuff.jade | 55 +++++++++++++++++++---------- 1 file changed, 37 insertions(+), 18 deletions(-) diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index 008429081d..928878ff69 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,32 +1,51 @@ -h2 CHALLENGE SPOTLIGHT, NEW SOUND THEME, START QUEST BUTTON, AND CUSTOM DAY START FIXES +h2 9/16/2015 - MAMMOTHS AND MANTIS SHRIMPS IN TIME TRAVELER SHOP! PLUS, FALL FESTIVAL PLOT-LINE CONTINUES hr tr td - h3 Challenge Spotlight - p There's a new Challenge Spotlight on our blog! Check it out for some great recommended Challenges, including some about studying and self-discipline. - p.small.muted by Lemoness - p.small.muted Challenges by shanaqui, keyes, White Rose Duelist, and Elo + .Pet-MantisShrimp-Base.pull-right + .Pet-Mammoth-Base.pull-right + h3 Woolly Mammoths and Mantis Shrimps in Time Traveler Shop + p The Time Travelers have traveled back to Summer 2014 and Winter 2015 to obtain some Mammoth and Mantis Shrimp pets and mounts! You can buy them with Mystic Hourglasses, which are awarded to long-term subscribers. Thanks for helping us to keep Habitica running! + p.small.muted by SabreCat, Blade, and cheerskevin + p.small.muted Art by Ottl and Baconsaur tr td - h3 New Sound Theme - p There's a new sound effect theme on the site: LuneFox's Theme! Select it from the megaphone in the upper right. It will play sound effects when you click things! - p.small.muted by LuneFox and Alys - tr - td - h3 Start Quest Button - p Now you can begin a boss fight or collection quest directly from your Party Page! Just click the green Start Quest button on the left. If you already have quest scrolls, they will pop up automatically. Otherwise, it will take you directly to the Quest Shop. Enjoy the added convenience! - p.small.muted by SabreCat and cheerskevin - tr - td - h3 Custom Day Start Fixes - p The Custom Day Start feature has been fixed so that it can now be changed at any time with complete safety! For those who don't know, this feature enables you to set what time your Dailies will roll over to a new day. To set a Custom Day Start, go to Settings > Site > Custom Day Start. - p.small.muted by carolstone and Blade + h3 Fall Festival Plot-Line Continues + p Last minute preparations for the Fall Festival are underway! Lemoness has been bustling about with tomes of potion-brewing, and conferring with the artisans in cheerful conspiracy. SabreCat has been seen in the smithy, toiling on projects that he has loudly announced to be "very hush-hush." Even the elusive Royal Chancellor, Redphoenix, has taken a break from sending Boring Business Letters to roast marshmallows outside the Tavern. Everyone has happily gathered around her little campfire, which she is feeding with a steady supply of envelopes. + br + p Only one thing has disrupted the anticipation. You catch Lemoness frowning as she reads a message attached to the latest shipment from the Flourishing Fields. + br + p "They're apologizing for not sending as many pumpkins as they promised, due to an 'unanticipated setback,'" she explains. "There's no need for them to be sorry, though -- they've sent us hundreds already! They said the same thing last week about the candy vegetable harvest. I told the Joyful Reaper not to worry about it, but she just promises to work harder." + br + p Well, soon the Festival will launch, and they'll be able to take a break. Nothing to be concerned about! if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 CHALLENGE SPOTLIGHT, NEW SOUND THEME, START QUEST BUTTON, AND CUSTOM DAY START FIXES + tr + td + h3 Challenge Spotlight + p There's a new Challenge Spotlight on our blog! Check it out for some great recommended Challenges, including some about studying and self-discipline. + p.small.muted by Lemoness + p.small.muted Challenges by shanaqui, keyes, White Rose Duelist, and Elo + tr + td + h3 New Sound Theme + p There's a new sound effect theme on the site: LuneFox's Theme! Select it from the megaphone in the upper right. It will play sound effects when you click things! + p.small.muted by LuneFox and Alys + tr + td + h3 Start Quest Button + p Now you can begin a boss fight or collection quest directly from your Party Page! Just click the green Start Quest button on the left. If you already have quest scrolls, they will pop up automatically. Otherwise, it will take you directly to the Quest Shop. Enjoy the added convenience! + p.small.muted by SabreCat and cheerskevin + tr + td + h3 Custom Day Start Fixes + p The Custom Day Start feature has been fixed so that it can now be changed at any time with complete safety! For those who don't know, this feature enables you to set what time your Dailies will roll over to a new day. To set a Custom Day Start, go to Settings > Site > Custom Day Start. + p.small.muted by carolstone and Blade h2 HORSE PET QUEST, NEW PRODUCTIVITY BLOG, AND FALL PLOT-LINE BEGINS tr td From f8db8f597a0fd9fec59237002ce2873b65307ee8 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 19:16:21 -0400 Subject: [PATCH 17/18] refactor(test): Give user mystery items ...instead of stubbing out timeTravelerStore --- test/spec/controllers/inventoryCtrlSpec.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index dd6b1dc794..556a182e16 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -223,13 +223,17 @@ describe('Inventory Controller', function() { }); }); - describe('#hasAllTimeTravelerItems', function() { + describe.only('#hasAllTimeTravelerItems', function() { it('returns false if items remain for purchase with Mystic Hourglasses', function() { expect(scope.hasAllTimeTravelerItems()).to.eql(false); }); it('returns true if there are no items left to purchase', inject(function(Content) { - sandbox.stub(Content, 'timeTravelerStore').returns({}); + for (var item in Content.gear.flat) { + if (item.indexOf('mystery') > -1) { + user.items.gear.owned[item] = true; + } + } for (var pet in Content.timeTravelStable.pets) { user.items.pets[pet] = 5; } @@ -247,7 +251,11 @@ describe('Inventory Controller', function() { }); it('returns true for Mystery Sets if there are no sets left to purchase', inject(function(Content) { - sandbox.stub(Content, 'timeTravelerStore').returns({}); + for (var item in Content.gear.flat) { + if (item.indexOf('mystery') > -1) { + user.items.gear.owned[item] = true; + } + } expect(scope.hasAllTimeTravelerItemsOfType('mystery')).to.eql(true); })); From 15cddabd805d5cc7c5b5b572464df9d865d69e5b Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 16 Sep 2015 19:21:05 -0400 Subject: [PATCH 18/18] fix(test): Remove only --- test/spec/controllers/inventoryCtrlSpec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/spec/controllers/inventoryCtrlSpec.js b/test/spec/controllers/inventoryCtrlSpec.js index 556a182e16..5b484ac6f8 100644 --- a/test/spec/controllers/inventoryCtrlSpec.js +++ b/test/spec/controllers/inventoryCtrlSpec.js @@ -223,7 +223,7 @@ describe('Inventory Controller', function() { }); }); - describe.only('#hasAllTimeTravelerItems', function() { + describe('#hasAllTimeTravelerItems', function() { it('returns false if items remain for purchase with Mystic Hourglasses', function() { expect(scope.hasAllTimeTravelerItems()).to.eql(false); });