diff --git a/website/assets/sprites/spritesmith/quests/scrolls/quest_bundle_featheredFriends.png b/website/assets/sprites/spritesmith/quests/scrolls/quest_bundle_featheredFriends.png new file mode 100644 index 0000000000..02151b2570 Binary files /dev/null and b/website/assets/sprites/spritesmith/quests/scrolls/quest_bundle_featheredFriends.png differ diff --git a/website/client-old/js/services/questServices.js b/website/client-old/js/services/questServices.js index 017b779e89..05722f710e 100644 --- a/website/client-old/js/services/questServices.js +++ b/website/client-old/js/services/questServices.js @@ -39,7 +39,7 @@ angular.module('habitrpg') function buyQuest(quest) { return $q(function(resolve, reject) { - var item = Content.quests[quest]; + var item = Content.quests[quest] || Content.bundles[quest]; var preventQuestModal = _preventQuestModal(item); if (preventQuestModal) { @@ -73,7 +73,10 @@ angular.module('habitrpg') function questPopover(quest) { // The popover gets parsed as markdown (hence the double \n for line breaks var text = ''; - if(quest.boss) { + if (quest.purchaseType === 'bundles') { + text += quest.notes; + } + if (quest.boss) { text += '**' + window.env.t('bossHP') + ':** ' + quest.boss.hp + '\n\n'; text += '**' + window.env.t('bossStrength') + ':** ' + quest.boss.str + '\n\n'; } else if(quest.collect) { @@ -82,25 +85,27 @@ angular.module('habitrpg') text += '**' + window.env.t('collect') + ':** ' + quest.collect[key].count + ' ' + quest.collect[key].text() + '\n\n'; } } - text += '---\n\n'; - text += '**' + window.env.t('rewardsAllParticipants') + ':**\n\n'; - var participantRewards = _.reject(quest.drop.items, 'onlyOwner'); - if(participantRewards.length > 0) { - _.each(participantRewards, function(item) { - text += item.text() + '\n\n'; - }); - } - if(quest.drop.exp) - text += quest.drop.exp + ' ' + window.env.t('experience') + '\n\n'; - if(quest.drop.gp) - text += quest.drop.gp + ' ' + window.env.t('gold') + '\n\n'; + if (quest.drop) { + text += '---\n\n'; + text += '**' + window.env.t('rewardsAllParticipants') + ':**\n\n'; + var participantRewards = _.reject(quest.drop.items, 'onlyOwner'); + if(participantRewards.length > 0) { + _.each(participantRewards, function(item) { + text += item.text() + '\n\n'; + }); + } + if (quest.drop.exp) + text += quest.drop.exp + ' ' + window.env.t('experience') + '\n\n'; + if (quest.drop.gp) + text += quest.drop.gp + ' ' + window.env.t('gold') + '\n\n'; - var ownerRewards = _.filter(quest.drop.items, 'onlyOwner'); - if(ownerRewards.length > 0) { - text += '**' + window.env.t('rewardsQuestOwner') + ':**\n\n'; - _.each(ownerRewards, function(item){ - text += item.text() + '\n\n'; - }); + var ownerRewards = _.filter(quest.drop.items, 'onlyOwner'); + if (ownerRewards.length > 0) { + text += '**' + window.env.t('rewardsQuestOwner') + ':**\n\n'; + _.each(ownerRewards, function(item){ + text += item.text() + '\n\n'; + }); + } } return text; @@ -108,7 +113,7 @@ angular.module('habitrpg') function showQuest(quest) { return $q(function(resolve, reject) { - var item = Content.quests[quest]; + var item = Content.quests[quest]; var preventQuestModal = _preventQuestModal(item); if (preventQuestModal) { diff --git a/website/common/locales/en/limited.json b/website/common/locales/en/limited.json index 90166ecef8..451ce916d1 100644 --- a/website/common/locales/en/limited.json +++ b/website/common/locales/en/limited.json @@ -106,5 +106,6 @@ "spring2017SneakyBunnySet": "Sneaky Bunny (Rogue)", "eventAvailability": "Available for purchase until <%= date(locale) %>.", "dateEndApril": "April 19", - "dateEndMay": "May 17" + "dateEndMay": "May 17", + "discountBundle": "bundle" } diff --git a/website/common/locales/en/quests.json b/website/common/locales/en/quests.json index 366451d5d9..91459299cb 100644 --- a/website/common/locales/en/quests.json +++ b/website/common/locales/en/quests.json @@ -112,5 +112,7 @@ "loginIncentiveQuest": "To earn this quest, check in to Habitica on <%= count %> different days!", "loginIncentiveQuestObtained": "You earned this quest by checking in to Habitica on <%= count %> different days!", "loginReward": "<%= count %> Check-ins", - "createAccountQuest": "You received this quest when you joined Habitica! If a friend joins, they'll get one too." + "createAccountQuest": "You received this quest when you joined Habitica! If a friend joins, they'll get one too.", + "questBundles": "Discounted Quest Bundles", + "buyQuestBundle": "Buy Quest Bundle" } diff --git a/website/common/locales/en/questsContent.json b/website/common/locales/en/questsContent.json index 203ef6eed8..bf35ece29c 100644 --- a/website/common/locales/en/questsContent.json +++ b/website/common/locales/en/questsContent.json @@ -553,5 +553,8 @@ "questMayhemMistiflying3Boss": "The Wind-Worker", "questMayhemMistiflying3DropPinkCottonCandy": "Pink Cotton Candy (Food)", "questMayhemMistiflying3DropShield": "Roguish Rainbow Message (Shield-Hand Weapon)", - "questMayhemMistiflying3DropWeapon": "Roguish Rainbow Message (Weapon)" + "questMayhemMistiflying3DropWeapon": "Roguish Rainbow Message (Weapon)", + + "featheredFriendsText": "Feathered Friends Quest Bundle", + "featheredFriendsNotes": "Contains 'Help! Harpy!,' 'The Night-Owl,' and 'The Birds of Preycrastination.' Available until May 31." } diff --git a/website/common/script/content/constants.js b/website/common/script/content/constants.js index 119de75666..177a3a688c 100644 --- a/website/common/script/content/constants.js +++ b/website/common/script/content/constants.js @@ -56,6 +56,7 @@ export const ITEM_LIST = { quests: { localeKey: 'quest', isEquipment: false }, food: { localeKey: 'foodText', isEquipment: false }, Saddle: { localeKey: 'foodSaddleText', isEquipment: false }, + bundles: { localeKey: 'discountBundle', isEquipment: false }, }; export const USER_CAN_OWN_QUEST_CATEGORIES = [ diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 9369ca5f0f..1d3d42d006 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -1,6 +1,7 @@ -import includes from 'lodash/includes'; import defaults from 'lodash/defaults'; import each from 'lodash/each'; +import includes from 'lodash/includes'; +import moment from 'moment'; import t from './translation'; import { @@ -47,6 +48,30 @@ api.subscriptionBlocks = subscriptionBlocks; api.mystery = timeTravelers.mystery; api.timeTravelerStore = timeTravelers.timeTravelerStore; +/* + --------------------------------------------------------------- + Discounted Item Bundles + --------------------------------------------------------------- + */ + +api.bundles = { + featheredFriends: { + key: 'featheredFriends', + text: t('featheredFriendsText'), + notes: t('featheredFriendsNotes'), + bundleKeys: [ + 'falcon', + 'harpy', + 'owl', + ], + canBuy () { + return moment().isBetween('2017-05-16','2017-06-02'); + }, + type: 'quests', + value: 7, + }, +}, + /* --------------------------------------------------------------- Unique Rewards: Potion and Armoire diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index ff96f0617c..4dba6d8ab7 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -114,6 +114,27 @@ shops.getMarketCategories = function getMarket (user, language) { shops.getQuestShopCategories = function getQuestShopCategories (user, language) { let categories = []; + let bundleCategory = { + identifier: 'bundle', + text: i18n.t('questBundles', language), + }; + + bundleCategory.items = sortBy(values(content.bundles) + .filter(bundle => bundle.type === 'quests' && bundle.canBuy()) + .map(bundle => { + return { + key: bundle.key, + text: bundle.text(language), + notes: bundle.notes(language), + value: bundle.value, + currency: 'gems', + class: `quest_bundle_${bundle.key}`, + purchaseType: 'bundles', + } + })); + + categories.push(bundleCategory); + each(content.userCanOwnQuestCategories, type => { let category = { identifier: type, diff --git a/website/common/script/ops/purchase.js b/website/common/script/ops/purchase.js index 35e3714a44..e7ac50af42 100644 --- a/website/common/script/ops/purchase.js +++ b/website/common/script/ops/purchase.js @@ -2,6 +2,7 @@ import content from '../content/index'; import i18n from '../i18n'; import get from 'lodash/get'; import pick from 'lodash/pick'; +import forEach from 'lodash/forEach'; import splitWhitespace from '../libs/splitWhitespace'; import planGemLimits from '../libs/planGemLimits'; import { @@ -62,7 +63,7 @@ module.exports = function purchase (user, req = {}, analytics) { ]; } - let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear']; + let acceptedTypes = ['eggs', 'hatchingPotions', 'food', 'quests', 'gear', 'bundles']; if (acceptedTypes.indexOf(type) === -1) { throw new NotFound(i18n.t('notAccteptedType', req.language)); } @@ -101,6 +102,14 @@ module.exports = function purchase (user, req = {}, analytics) { if (type === 'gear') { user.items.gear.owned[key] = true; + } else if (type === 'bundles') { + let subType = item.type; + forEach(item.bundleKeys, function (bundledKey) { + if (!user.items[subType][bundledKey] || user.items[subType][key] < 0) { + user.items[subType][bundledKey] = 0; + } + user.items[subType][bundledKey]++; + }); } else { if (!user.items[type][key] || user.items[type][key] < 0) { user.items[type][key] = 0; diff --git a/website/views/shared/modals/quests.jade b/website/views/shared/modals/quests.jade index 5ac90e3477..ccb2aba9c5 100644 --- a/website/views/shared/modals/quests.jade +++ b/website/views/shared/modals/quests.jade @@ -20,7 +20,7 @@ mixin questInfo p(ng-repeat='(k,v) in ::selectedQuest.collect') | {{:: env.t('collectionItems', { number: selectedQuest.collect[k].count, items: selectedQuest.collect[k].text() })}} div(ng-bind-html='::selectedQuest.notes()') - quest-rewards(key='{{::selectedQuest.key}}', header-participant=env.t('rewardsAllParticipants'), header-quest-owner=env.t('rewardsQuestOwner')) + quest-rewards(ng-if='::selectedQuest.drop', key='{{::selectedQuest.key}}', header-participant=env.t('rewardsAllParticipants'), header-quest-owner=env.t('rewardsQuestOwner')) script(type='text/ng-template', id='modals/questCompleted.html') .modal-header @@ -61,7 +61,8 @@ script(type='text/ng-template', id='modals/buyQuest.html') +questInfo .modal-footer button.btn.btn-default(ng-click='closeQuest(); $close()')=env.t('neverMind') - button.btn.btn-primary(ng-if='::selectedQuest.category !== "gold"', ng-click='purchase("quests", quest); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.value}} ' + env.t('gems') + button.btn.btn-primary(ng-if='::selectedQuest.bundleKeys', ng-click='purchase("bundles", selectedQuest); closeQuest(); $close()')=env.t('buyQuestBundle') + ': {{::selectedQuest.value}} ' + env.t('gems') + button.btn.btn-primary(ng-if='::selectedQuest.category && selectedQuest.category !== "gold"', ng-click='purchase("quests", selectedQuest); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.value}} ' + env.t('gems') button.btn.btn-primary(ng-if='::selectedQuest.category === "gold"', ng-click='User.buyQuest({params:{key:selectedQuest.key}}); closeQuest(); $close()')=env.t('buyQuest') + ': {{::selectedQuest.goldValue}} ' + env.t('gold') script(type='text/ng-template', id='modals/questInvitation.html')