diff --git a/migrations/20170418_subscriber_jackalopes.js b/migrations/20170418_subscriber_jackalopes.js new file mode 100644 index 0000000000..d32867de98 --- /dev/null +++ b/migrations/20170418_subscriber_jackalopes.js @@ -0,0 +1,88 @@ +var migrationName = '20170418_subscriber_jackalopes.js'; +var authorName = 'Sabe'; // in case script author needs to know when their ... +var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done + +/* + * Award Royal Purple Jackalope pet to all current subscribers + */ + +var monk = require('monk'); +var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE +var dbUsers = monk(connectionString).get('users', { castIds: false }); +var now = new Date(); + +function processUsers(lastId) { + // specify a query to limit the affected users (empty for all users): + var query = { + 'purchased.plan.customerId': {$type: 2}, + $or: [ + {'purchased.plan.dateTerminated': null}, + {'purchased.plan.dateTerminated': {$exists: false}}, + {'purchased.plan.dateTerminated': {$gt: now}}, + ] + }; + + if (lastId) { + query._id = { + $gt: lastId + } + } + + dbUsers.find(query, { + sort: {_id: 1}, + limit: 250, + fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data): + }) + .then(updateUsers) + .catch(function (err) { + console.log(err); + return exiting(1, 'ERROR! ' + err); + }); +} + +var progressCount = 1000; +var count = 0; + +function updateUsers (users) { + if (!users || users.length === 0) { + console.warn('All appropriate users found and modified.'); + displayData(); + return; + } + + var userPromises = users.map(updateUser); + var lastUser = users[users.length - 1]; + + return Promise.all(userPromises) + .then(function () { + processUsers(lastUser._id); + }); +} + +function updateUser (user) { + count++; + + var set = {'items.pets.Jackalope-RoyalPurple': 5}; + + dbUsers.update({_id: user._id}, {$set:set}); + + if (count % progressCount == 0) console.warn(count + ' ' + user._id); + if (user._id == authorUuid) console.warn(authorName + ' processed'); +} + +function displayData() { + console.warn('\n' + count + ' users processed\n'); + return exiting(0); +} + +function exiting(code, msg) { + code = code || 0; // 0 = success + if (code && !msg) { msg = 'ERROR!'; } + if (msg) { + if (code) { console.error(msg); } + else { console.log( msg); } + } + process.exit(code); +} + +module.exports = processUsers; diff --git a/test/api/v3/unit/libs/payments.test.js b/test/api/v3/unit/libs/payments.test.js index 56057f8c23..bed489f490 100644 --- a/test/api/v3/unit/libs/payments.test.js +++ b/test/api/v3/unit/libs/payments.test.js @@ -83,6 +83,12 @@ describe('payments/index', () => { }; }); + it('awards the Royal Purple Jackalope pet', async () => { + await api.createSubscription(data); + + expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5); + }); + it('adds extra months to an existing subscription', async () => { recipient.purchased.plan = plan; @@ -241,6 +247,12 @@ describe('payments/index', () => { expect(user.purchased.plan.dateCreated).to.exist; }); + it('awards the Royal Purple Jackalope pet', async () => { + await api.createSubscription(data); + + expect(user.items.pets['Jackalope-RoyalPurple']).to.eql(5); + }); + it('sets extraMonths if plan has dateTerminated date', async () => { user.purchased.plan = plan; user.purchased.plan.dateTerminated = moment(new Date()).add(2, 'months'); @@ -633,5 +645,13 @@ describe('payments/index', () => { expect(updatedUser.purchased.plan.lastBillingDate).to.not.exist; expect(updatedUser.purchased.plan.dateCreated).to.exist; }); + + it('awards the Royal Purple Jackalope pet', async () => { + await api.addSubToGroupUser(user, group); + + let updatedUser = await User.findById(user._id).exec(); + + expect(updatedUser.items.pets['Jackalope-RoyalPurple']).to.eql(5); + }); }); }); diff --git a/website/assets/sprites/spritesmith/stable/pets/Pet-Jackalope-RoyalPurple.png b/website/assets/sprites/spritesmith/stable/pets/Pet-Jackalope-RoyalPurple.png new file mode 100644 index 0000000000..d9e597a627 Binary files /dev/null and b/website/assets/sprites/spritesmith/stable/pets/Pet-Jackalope-RoyalPurple.png differ diff --git a/website/assets/sprites/spritesmith_large/promo/promo_bees.png b/website/assets/sprites/spritesmith_large/promo/promo_bees.png new file mode 100644 index 0000000000..56b987f70c Binary files /dev/null and b/website/assets/sprites/spritesmith_large/promo/promo_bees.png differ diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 3c10bc8ee9..96202fbf8f 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -8,12 +8,14 @@ "reachedGoldToGemCap": "You've reached the Gold=>Gem conversion cap <%= convCap %> for this month. We have this to prevent abuse / farming. The cap resets within the first three days of each month.", "retainHistory": "Retain additional history entries", "retainHistoryText": "Makes completed To-Dos and task history available for longer.", - "doubleDrops": "Daily drop-caps doubled", + "doubleDrops": "Daily drop caps doubled", "doubleDropsText": "Complete your stable faster!", "mysteryItem": "Exclusive monthly items", "mysteryItemText": "Each month you will receive a unique cosmetic item for your avatar! Plus, for every three months of consecutive subscription, the Mysterious Time Travelers will grant you access to historic (and futuristic!) cosmetic items.", "supportDevs": "Supports the developers", "supportDevsText": "Your subscription helps keep Habitica thriving and helps fund the development of new features. Thank you for your generosity!", + "exclusiveJackalopePet": "Exclusive pet", + "exclusiveJackalopePetText": "Get the Royal Purple Jackalope pet, available only to subscribers!", "giftSubscription": "Want to gift a subscription to someone?", "giftSubscriptionText1": "Open their profile! You can do this by clicking on their avatar in your party header or by clicking on their name in chat.", "giftSubscriptionText2": "Click on the gift icon in the bottom left of their profile.", diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index a031c96490..df3ba873dc 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -125,11 +125,13 @@ api.timeTravelStable = { 'Mammoth-Base': t('mammoth'), 'MantisShrimp-Base': t('mantisShrimp'), 'Phoenix-Base': t('phoenix'), + 'MagicalBee-Base': t('magicalBee'), }, mounts: { 'Mammoth-Base': t('mammoth'), 'MantisShrimp-Base': t('mantisShrimp'), 'Phoenix-Base': t('phoenix'), + 'MagicalBee-Base': t('magicalBee'), }, }; diff --git a/website/common/script/content/stable.js b/website/common/script/content/stable.js index 942a9aaa00..80d89f5a0e 100644 --- a/website/common/script/content/stable.js +++ b/website/common/script/content/stable.js @@ -67,6 +67,7 @@ let specialPets = { 'Lion-Veteran': 'veteranLion', 'Gryphon-RoyalPurple': 'royalPurpleGryphon', 'JackOLantern-Ghost': 'ghostJackolantern', + 'Jackalope-RoyalPurple': 'royalPurpleJackalope', }; let specialMounts = { diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 871cde74a0..3deaac1c63 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -149,7 +149,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, language) { let categories = []; - let stable = {pets: 'Pet-', mounts: 'Mount_Head_'}; + let stable = {pets: 'Pet-', mounts: 'Mount_Icon_'}; for (let type in stable) { if (stable.hasOwnProperty(type)) { let category = { diff --git a/website/server/libs/payments.js b/website/server/libs/payments.js index 0eb94790a0..8cb600da48 100644 --- a/website/server/libs/payments.js +++ b/website/server/libs/payments.js @@ -289,6 +289,7 @@ api.createSubscription = async function createSubscription (data) { } if (recipient !== group) { + recipient.items.pets['Jackalope-RoyalPurple'] = 5; revealMysteryItems(recipient); } diff --git a/website/views/options/settings/index.jade b/website/views/options/settings/index.jade index 39a081b113..bf95f370f2 100644 --- a/website/views/options/settings/index.jade +++ b/website/views/options/settings/index.jade @@ -1,51 +1,53 @@ -mixin subPerks() - ul - li - span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}  - span.badge.badge-success(ng-show='_subscription.key!="basic_earned"')=env.t('buyGemsGoldCap', {amount: '{{gemGoldCap(_subscription) | min }}'}) - li - span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory') - li - span.hint(popover=env.t('doubleDropsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('doubleDrops') - li - span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}  - div(ng-show='_subscription.key!="basic_earned"') - .badge.badge-success=env.t('mysticHourglass', {amount: '+{{numberOfMysticHourglasses(_subscription)}}'}) - .small.muted=env.t('mysticHourglassText') - li - span.hint(popover=env.t('supportDevsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('supportDevs') - -script(id='partials/options.settings.html', type="text/ng-template") - ul.options-menu - li(ng-class="{ active: $state.includes('options.settings.settings') }") - a(ui-sref='options.settings.settings') - =env.t('site') - li(ng-class="{ active: $state.includes('options.settings.api') }") - a(ui-sref='options.settings.api') - =env.t('API') - li(ng-class="{ active: $state.includes('options.settings.export') }") - a(ui-sref='options.settings.export') - =env.t('dataExport') - li(ng-class="{ active: $state.includes('options.settings.promo') }") - a(ui-sref='options.settings.promo') - =env.t('promoCode') - li(ng-class="{ active: $state.includes('options.settings.subscription') }") - a(ui-sref='options.settings.subscription')=env.t('subscription') - li(ng-class="{ active: $state.includes('options.settings.notifications') }") - a(ui-sref='options.settings.notifications')=env.t('notifications') - - .tab-content - .tab-pane.active - div(ui-view) - -include ./settings -include ./promo -include ./api -include ./export -include ./notification -include ./subscription - -script(id='partials/feature-matrix-check.html',type='text/ng-template') - span.task-checker.action-yesno - input.focusable(type='checkbox', checked) - label +mixin subPerks() + ul + li + span.hint(popover=env.t('buyGemsGoldText', {gemCost: "{{Shared.planGemLimits.convRate}}", gemLimit: "{{Shared.planGemLimits.convCap}}"}),popover-trigger='mouseenter',popover-placement='right') #{env.t('buyGemsGold')}  + span.badge.badge-success(ng-show='_subscription.key!="basic_earned"')=env.t('buyGemsGoldCap', {amount: '{{gemGoldCap(_subscription) | min }}'}) + li + span.hint(popover=env.t('retainHistoryText'),popover-trigger='mouseenter',popover-placement='right')=env.t('retainHistory') + li + span.hint(popover=env.t('doubleDropsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('doubleDrops') + li + span.hint(popover=env.t('mysteryItemText'),popover-trigger='mouseenter',popover-placement='right') #{env.t('mysteryItem')}  + div(ng-show='_subscription.key!="basic_earned"') + .badge.badge-success=env.t('mysticHourglass', {amount: '+{{numberOfMysticHourglasses(_subscription)}}'}) + .small.muted=env.t('mysticHourglassText') + li + span.hint(popover=env.t('exclusiveJackalopePetText'),popover-trigger='mouseenter',popover-placement='right')=env.t('exclusiveJackalopePet') + li + span.hint(popover=env.t('supportDevsText'),popover-trigger='mouseenter',popover-placement='right')=env.t('supportDevs') + +script(id='partials/options.settings.html', type="text/ng-template") + ul.options-menu + li(ng-class="{ active: $state.includes('options.settings.settings') }") + a(ui-sref='options.settings.settings') + =env.t('site') + li(ng-class="{ active: $state.includes('options.settings.api') }") + a(ui-sref='options.settings.api') + =env.t('API') + li(ng-class="{ active: $state.includes('options.settings.export') }") + a(ui-sref='options.settings.export') + =env.t('dataExport') + li(ng-class="{ active: $state.includes('options.settings.promo') }") + a(ui-sref='options.settings.promo') + =env.t('promoCode') + li(ng-class="{ active: $state.includes('options.settings.subscription') }") + a(ui-sref='options.settings.subscription')=env.t('subscription') + li(ng-class="{ active: $state.includes('options.settings.notifications') }") + a(ui-sref='options.settings.notifications')=env.t('notifications') + + .tab-content + .tab-pane.active + div(ui-view) + +include ./settings +include ./promo +include ./api +include ./export +include ./notification +include ./subscription + +script(id='partials/feature-matrix-check.html',type='text/ng-template') + span.task-checker.action-yesno + input.focusable(type='checkbox', checked) + label diff --git a/website/views/shared/new-stuff.jade b/website/views/shared/new-stuff.jade index a871c9e024..98f8ecc337 100644 --- a/website/views/shared/new-stuff.jade +++ b/website/views/shared/new-stuff.jade @@ -1,24 +1,46 @@ -h2 4/13/2017 - BUTTERFLY PET QUEST AND SAMPLE DAILIES WIKI SPOTLIGHT +h2 4/18/2017 - ANDROID UPDATE, NEW JACKALOPE PET FOR SUBSCRIBERS, AND MAGICAL BEES IN THE TIME TRAVEL SHOP hr tr td - .quest_butterfly.pull-right - h3 New Pet Quest: Bye, Bye, Butterfry! - p A trip to the Taskan countryside's butterfly garden gets deeply dangerous when some neglected Dailies attract a Flaming Butterfry! Get the latest pet quest, "Bye, Bye Butterfry" in the Quest Shop, and earn some cute caterpillar pets by completing your real-life tasks. - p.small.muted Written by AnnDeLune - p.small.muted Art by Leephon, Megan, Eevachu, Beffymaroo, UncommonCriminal, Anna Glassman, and Lilith of Alfheim + .promo_bees.pull-right + h3 Android Update + p There's an exciting new update to our Android app! Task navigation has been redesigned, and there are more options for filtering your tasks. We've also added a snazzy new login screen and an improved tutorial for new users! + br + p Be sure to download the update now for a better Habitica experience! If you like the improvements that we’ve been making to our app, please consider reviewing this new version. It really helps us out! + p.small.muted by viirus, Sara Olson, and beffymaroo tr td - .scene_dailies.pull-left.slight-right-margin - h3 Featured Wiki Article: Sample Dailies - p This month's featured Wiki article is about Sample Dailies! We hope that it will help you as work on adding or revamping your daily tasks. Be sure to check it out, and let us know what you think by reaching out on Twitter, Tumblr, and Facebook. - p.small.muted by Beffymaroo and the Wiki Wizards + .Pet-Jackalope-RoyalPurple.pull-left.slight-right-margin + h3 New Exclusive Jackalope Pet for Subscribers + p As a thank-you for your support, all subscribers and group plan members have received Royal Purple Jackalope pets! New subscribers will receive this pet when they sign up, so check out the info on our Subscriptions page to learn more about all the great benefits of subscribing to Habitica including your very own Jackalope pet! + p.small.muted by Beffymaroo and SabreCat + tr + td + h3 Bees Available from the Time Travelers + p Magical Bee Pets and Mounts are now available from the Time Travelers! If you missed out on these happy, buzzing companions from last year, be sure to adopt them now. + br + p If bees are something you'd prefer not to see in Habitica due to a phobia, check out the Phobia Protection Extension which will hide any pets, mounts, backgrounds, quest bosses, or equipment featuring bees (as well as snakes, spiders, and rats). We hope that it helps make everyone's Habitica experience fun! + p.small.muted by Lemoness and SabreCat if menuItem !== 'oldNews' hr a(href='/static/old-news', target='_blank') Read older news mixin oldNews + h2 4/13/2017 - BUTTERFLY PET QUEST AND SAMPLE DAILIES WIKI SPOTLIGHT + tr + td + .quest_butterfly.pull-right + h3 New Pet Quest: Bye, Bye, Butterfry! + p A trip to the Taskan countryside's butterfly garden gets deeply dangerous when some neglected Dailies attract a Flaming Butterfry! Get the latest pet quest, "Bye, Bye Butterfry" in the Quest Shop, and earn some cute caterpillar pets by completing your real-life tasks. + p.small.muted Written by AnnDeLune + p.small.muted Art by Leephon, Megan, Eevachu, Beffymaroo, UncommonCriminal, Anna Glassman, and Lilith of Alfheim + tr + td + .scene_dailies.pull-left.slight-right-margin + h3 Featured Wiki Article: Sample Dailies + p This month's featured Wiki article is about Sample Dailies! We hope that it will help you as work on adding or revamping your daily tasks. Be sure to check it out, and let us know what you think by reaching out on Twitter, Tumblr, and Facebook. + p.small.muted by Beffymaroo and the Wiki Wizards h2 4/11/2017 - EGG HUNT QUEST, APRIL FOOL'S CHALLENGE WINNERS, AND CHANGE TO PARTY SIZES tr td