diff --git a/common/locales/en/npc.json b/common/locales/en/npc.json
index e1daafb192..c387207ea1 100644
--- a/common/locales/en/npc.json
+++ b/common/locales/en/npc.json
@@ -14,6 +14,7 @@
"sellPotionForGold": "Sell <%= itemType %> Potion for <%= gold %> Gold",
"buyGems": "Buy Gems",
"justin": "Justin",
+ "ian": "Ian",
"USD": "USD",
"newStuff": "New Stuff",
"cool": "Tell Me Later",
diff --git a/common/locales/en/quests.json b/common/locales/en/quests.json
index 256ff78642..a5cdfaf5eb 100644
--- a/common/locales/en/quests.json
+++ b/common/locales/en/quests.json
@@ -1,6 +1,11 @@
{
"quests": "Quests",
"quest": "quest",
+ "yourQuests": "Your Quests",
+ "questsForSale": "Quests for Sale",
+ "petQuests": "Pet and Mount Quests",
+ "unlockableQuests": "Unlockable Quests",
+ "goldQuests": "Gold-Purchasable Quests",
"questDetails": "Quest Details",
"invitations": "Invitations",
"completed": "Completed!",
@@ -37,11 +42,13 @@
"noScrolls": "You don't have any quest scrolls.",
"scrollsText1": "Quests require parties. If you want to quest solo,",
"scrollsText2": "create an empty party",
- "scrollsPre": "You need to complete the previous quest to start this one!",
+ "scrollsPre": "You haven't unlocked this quest yet!",
"completedQuests": "Completed the following quests",
"mustComplete": "You must first complete <%= quest %>.",
"mustLevel": "You must be level <%= level %>.",
"mustLvlQuest": "You must be level <%= level %> to buy this quest!",
+ "mustInviteFriend": "To earn this Quest, invite a friend to your Party. Invite someone now?",
+ "unlockByQuesting": "To earn this Quest, complete <%= title %>.",
"sureCancel": "Are you sure you want to cancel this quest? All invitation acceptances will be lost. The quest owner will retain possession of the quest scroll.",
"sureAbort": "Are you sure you want to abort this mission? It will abort it for everyone in your party and all progress will be lost. The quest scroll will be returned to the quest owner.",
"doubleSureAbort": "Are you double sure? Make sure they won't hate you forever!",
diff --git a/common/script/content.coffee b/common/script/content.coffee
index b7a3a6c38c..315b3f0136 100644
--- a/common/script/content.coffee
+++ b/common/script/content.coffee
@@ -1094,6 +1094,7 @@ api.quests =
completion: t("questDilatoryCompletion")
value: 0
canBuy: false
+ category: 'world'
boss:
name: t("questDilatoryBoss")
# We ran an average of progress{up,down} on users over 5 days: {up:805025,down:1324423}. /5*30 (we want the
@@ -1136,6 +1137,7 @@ api.quests =
completionChat: t("questStressbeastCompletionChat")
value: 0
canBuy: false
+ category: 'world'
boss:
name: t("questStressbeastBoss")
hp: 2750000
@@ -1179,7 +1181,7 @@ api.quests =
notes: t('questEvilSantaNotes')
completion: t('questEvilSantaCompletion')
value: 4 # Gem cost to buy, GP sell-back
- #mechanic: enum['perfectDailies', ...]
+ category: 'pet'
boss:
name: t('questEvilSantaBoss') # name of the boss himself (eg, Vice)
hp: 300
@@ -1198,6 +1200,7 @@ api.quests =
completion: t('questEvilSanta2Completion')
value: 4
previous: 'evilsanta'
+ category: 'pet'
collect:
tracks: text: t('questEvilSanta2CollectTracks'), count: 20
branches: text: t('questEvilSanta2CollectBranches'), count: 10
@@ -1213,6 +1216,7 @@ api.quests =
notes: t('questGryphonNotes')
completion: t('questGryphonCompletion')
value: 4 # Gem cost to buy, GP sell-back
+ category: 'pet'
boss:
name: t('questGryphonBoss') # name of the boss himself (eg, Vice)
hp: 300
@@ -1232,6 +1236,7 @@ api.quests =
notes: t('questHedgehogNotes')
completion: t('questHedgehogCompletion')
value: 4 # Gem cost to buy, GP sell-back
+ category: 'pet'
boss:
name: t('questHedgehogBoss') # name of the boss himself (eg, Vice)
hp: 400
@@ -1251,6 +1256,7 @@ api.quests =
notes: t('questGhostStagNotes')
completion: t('questGhostStagCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questGhostStagBoss')
hp: 1200
@@ -1270,6 +1276,7 @@ api.quests =
notes: t('questVice1Notes')
value: 4
lvl: 30
+ category: 'unlockable'
boss:
name: t('questVice1Boss')
hp: 750
@@ -1285,7 +1292,8 @@ api.quests =
text: t('questVice2Text')
notes: t('questVice2Notes')
value: 4
- lvl: 35
+ lvl: 30
+ category: 'unlockable'
previous: 'vice1'
collect:
lightCrystal: text: t('questVice2CollectLightCrystal'), count: 45
@@ -1302,7 +1310,8 @@ api.quests =
completion: t('questVice3Completion')
previous: 'vice2'
value: 4
- lvl: 40
+ lvl: 30
+ category: 'unlockable'
boss:
name: t('questVice3Boss')
hp: 1500
@@ -1324,6 +1333,7 @@ api.quests =
completion: t('questEggHuntCompletion')
value: 1
canBuy: false
+ category: 'pet'
collect:
plainEgg: text: t('questEggHuntCollectPlainEgg'), count: 100
drop:
@@ -1347,6 +1357,7 @@ api.quests =
notes: t('questRatNotes')
completion: t('questRatCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRatBoss')
hp: 1200
@@ -1366,6 +1377,7 @@ api.quests =
notes: t('questOctopusNotes')
completion: t('questOctopusCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questOctopusBoss')
hp: 1200
@@ -1385,6 +1397,7 @@ api.quests =
notes: t('questSeahorseNotes')
completion: t('questSeahorseCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSeahorseBoss')
hp: 300
@@ -1404,6 +1417,7 @@ api.quests =
notes: t('questAtom1Notes')
value: 4
lvl: 15
+ category: 'unlockable'
collect:
soapBars: text: t('questAtom1CollectSoapBars'), count: 20
drop:
@@ -1418,6 +1432,7 @@ api.quests =
previous: 'atom1'
value: 4
lvl: 15
+ category: 'unlockable'
boss:
name: t('questAtom2Boss')
hp: 300
@@ -1435,6 +1450,7 @@ api.quests =
completion: t('questAtom3Completion')
value: 4
lvl: 15
+ category: 'unlockable'
boss:
name: t('questAtom3Boss')
hp: 800
@@ -1453,6 +1469,7 @@ api.quests =
notes: t('questHarpyNotes')
completion: t('questHarpyCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questHarpyBoss')
hp: 600
@@ -1472,6 +1489,7 @@ api.quests =
notes: t('questRoosterNotes')
completion: t('questRoosterCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRoosterBoss')
hp: 300
@@ -1491,6 +1509,7 @@ api.quests =
notes: t('questSpiderNotes')
completion: t('questSpiderCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSpiderBoss')
hp: 400
@@ -1510,6 +1529,7 @@ api.quests =
notes: t('questMoonstone1Notes')
value: 4
lvl: 60
+ category: 'unlockable'
collect:
moonstone: text: t('questMoonstone1CollectMoonstone'), count: 500
drop:
@@ -1522,8 +1542,9 @@ api.quests =
text: t('questMoonstone2Text')
notes: t('questMoonstone2Notes')
value: 4
- lvl: 65
+ lvl: 60
previous: 'moonstone1'
+ category: 'unlockable'
boss:
name: t('questMoonstone2Boss')
hp: 1500
@@ -1540,7 +1561,8 @@ api.quests =
completion: t('questMoonstone3Completion')
previous: 'moonstone2'
value: 4
- lvl: 70
+ lvl: 60
+ category: 'unlockable'
boss:
name: t('questMoonstone3Boss')
hp: 2000
@@ -1565,6 +1587,7 @@ api.quests =
notes: t('questGoldenknight1Notes')
value: 4
lvl: 40
+ category: 'unlockable'
collect:
testimony: text: t('questGoldenknight1CollectTestimony'), count: 300
drop:
@@ -1578,7 +1601,8 @@ api.quests =
notes: t('questGoldenknight2Notes')
value: 4
previous: 'goldenknight1'
- lvl: 45
+ lvl: 40
+ category: 'unlockable'
boss:
name: t('questGoldenknight2Boss')
hp: 1000
@@ -1595,7 +1619,8 @@ api.quests =
completion: t('questGoldenknight3Completion')
previous: 'goldenknight2'
value: 4
- lvl: 50
+ lvl: 40
+ category: 'unlockable'
boss:
name: t('questGoldenknight3Boss')
hp: 1700
@@ -1616,8 +1641,10 @@ api.quests =
text: t('questBasilistText')
notes: t('questBasilistNotes')
completion: t('questBasilistCompletion')
- canBuy: false
- value: 4
+ category: 'unlockable'
+ unlockCondition:
+ condition: 'party invite'
+ text: t('inviteFriends')
boss:
name: t('questBasilistBoss')
hp: 100
@@ -1631,6 +1658,7 @@ api.quests =
notes: t('questOwlNotes')
completion: t('questOwlCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questOwlBoss')
hp: 500
@@ -1650,6 +1678,7 @@ api.quests =
notes: t('questPenguinNotes')
completion: t('questPenguinCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questPenguinBoss')
hp: 400
@@ -1669,6 +1698,7 @@ api.quests =
notes: t('questTRexNotes')
completion: t('questTRexCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questTRexBoss')
hp: 800
@@ -1688,6 +1718,7 @@ api.quests =
notes: t('questTRexUndeadNotes')
completion: t('questTRexUndeadCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questTRexUndeadBoss')
hp: 500
@@ -1713,6 +1744,7 @@ api.quests =
notes: t('questRockNotes')
completion: t('questRockCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRockBoss')
hp: 400
@@ -1732,6 +1764,7 @@ api.quests =
notes: t('questBunnyNotes')
completion: t('questBunnyCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questBunnyBoss')
hp: 300
@@ -1751,6 +1784,7 @@ api.quests =
notes: t('questSlimeNotes')
completion: t('questSlimeCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSlimeBoss')
hp: 400
@@ -1770,6 +1804,7 @@ api.quests =
notes: t('questSheepNotes')
completion: t('questSheepCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSheepBoss')
hp: 300
@@ -1789,6 +1824,7 @@ api.quests =
notes: t('questKrakenNotes')
completion: t('questKrakenCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questKrakenBoss')
hp: 800
@@ -1811,6 +1847,10 @@ _.each api.quests, (v,key) ->
if b.rage
_.defaults b.rage, {title:t('bossRageTitle'),description:t('bossRageDescription')}
+api.questsByLevel =
+ _.sortBy api.quests, (quest) ->
+ quest.lvl || 0
+
api.backgrounds =
backgrounds062014:
beach:
diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee
index 64bda3ccf4..49205fe84a 100644
--- a/test/common/algos.mocha.coffee
+++ b/test/common/algos.mocha.coffee
@@ -574,6 +574,7 @@ describe 'User', ->
expect(quest.value).to.be.greaterThan 0 if quest.canBuy
expect(quest.drop.gp).to.not.be.lessThan 0
expect(quest.drop.exp).to.not.be.lessThan 0
+ expect(quest.category).to.match('pet|unlockable|gold|world')
if quest.drop.items
expect(quest.drop.items).to.be.an(Array)
if quest.boss
diff --git a/website/public/css/inventory.styl b/website/public/css/inventory.styl
index bcd297268d..8e86e70f5b 100644
--- a/website/public/css/inventory.styl
+++ b/website/public/css/inventory.styl
@@ -74,8 +74,8 @@ menu.pets div
padding:.3em
width:6em
margin-top:1em
+ text-align:center
p
- text-align:center
//width:6em
margin-top:-.3em
.hatchingPotion-menu > div
diff --git a/website/public/js/app.js b/website/public/js/app.js
index 36e25702d7..d0caf41c2b 100644
--- a/website/public/js/app.js
+++ b/website/public/js/app.js
@@ -193,6 +193,10 @@ window.habitrpg = angular.module('habitrpg',
url: '/drops',
templateUrl: "partials/options.inventory.drops.html"
})
+ .state('options.inventory.quests', {
+ url: '/quests',
+ templateUrl: "partials/options.inventory.quests.html"
+ })
.state('options.inventory.pets', {
url: '/pets',
templateUrl: "partials/options.inventory.pets.html"
diff --git a/website/public/js/controllers/headerCtrl.js b/website/public/js/controllers/headerCtrl.js
index 474e5446f5..68000da3b9 100644
--- a/website/public/js/controllers/headerCtrl.js
+++ b/website/public/js/controllers/headerCtrl.js
@@ -1,11 +1,13 @@
"use strict";
-habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User', '$location', '$rootScope', 'Analytics',
- function($scope, Groups, User, $location, $rootScope, Analytics) {
+habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User',
+ function($scope, Groups, User) {
$scope.Math = window.Math;
$scope.user = User.user;
+ $scope.inviteOrStartParty = Groups.inviteOrStartParty;
+
$scope.party = Groups.party(function(){
var triggerResort = function() {
$scope.partyMinusSelf = resortParty();
@@ -16,20 +18,6 @@ habitrpg.controller("HeaderCtrl", ['$scope', 'Groups', 'User', '$location', '$ro
$scope.$watch('user.party.orderAscending', triggerResort);
});
- $scope.inviteOrStartParty = function(group) {
- if (group.type === "party") {
- $rootScope.openModal('invite-friends', {
- controller:'InviteToGroupCtrl',
- resolve: {
- injectedGroup: function(){ return group; }
- }
- });
- } else {
- Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
- $location.path("/options/groups/party");
- }
- }
-
function resortParty() {
var result = _.sortBy(
_.filter($scope.party.members, function(member){
diff --git a/website/public/js/controllers/inventoryCtrl.js b/website/public/js/controllers/inventoryCtrl.js
index afd85b625b..4bc97cbc42 100644
--- a/website/public/js/controllers/inventoryCtrl.js
+++ b/website/public/js/controllers/inventoryCtrl.js
@@ -1,6 +1,6 @@
habitrpg.controller("InventoryCtrl",
- ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics',
- function($rootScope, $scope, Shared, $window, User, Content, Analytics) {
+ ['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', 'Quests',
+ function($rootScope, $scope, Shared, $window, User, Content, Analytics, Quests) {
var user = User.user;
@@ -11,6 +11,13 @@ habitrpg.controller("InventoryCtrl",
$scope.totalPets = _.size(Content.dropEggs) * _.size(Content.hatchingPotions);
$scope.totalMounts = _.size(Content.dropEggs) * _.size(Content.hatchingPotions);
+ // Functions from Quests service
+ $scope.lockQuest = Quests.lockQuest;
+ $scope.buyQuest = Quests.buyQuest;
+ $scope.questPopover = Quests.questPopover;
+ $scope.showQuest = Quests.showQuest;
+ $scope.closeQuest = Quests.closeQuest;
+
// count egg, food, hatchingPotion stack totals
var countStacks = function(items) { return _.reduce(items,function(m,v){return m+v;},0);}
@@ -139,46 +146,6 @@ habitrpg.controller("InventoryCtrl",
User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}});
}
- $scope.questPopover = function(quest) {
- // The popover gets parsed as markdown (hence the double \n for line breaks
- var text = '';
- 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) {
- var count = 0;
- for (var key in quest.collect) {
- text += '**' + window.env.t('collect') + ':** ' + quest.collect[key].count + ' ' + quest.collect[key].text() + '\n\n';
- }
- }
- text += '---\n\n';
- text += '**' + window.env.t('rewards') + ':**\n\n';
- if(quest.drop.items) {
- for (var item in quest.drop.items) {
- text += quest.drop.items[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';
-
- return text;
- }
-
- $scope.showQuest = function(quest) {
- var item = Content.quests[quest];
- var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]);
- if (!completedPrevious)
- return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text()}));
- if (item.lvl && item.lvl > user.stats.lvl)
- return alert(window.env.t('mustLevel', {level: item.lvl}));
- $rootScope.selectedQuest = item;
- $rootScope.openModal('showQuest', {controller:'InventoryCtrl'});
- }
- $scope.closeQuest = function(){
- $rootScope.selectedQuest = undefined;
- }
$scope.questInit = function(){
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'quest','owner':true,'response':'accept','questName':$scope.selectedQuest.key});
$rootScope.party.$questAccept({key:$scope.selectedQuest.key}, function(){
@@ -186,16 +153,6 @@ habitrpg.controller("InventoryCtrl",
});
$scope.closeQuest();
}
- $scope.buyQuest = function(quest) {
- var item = Content.quests[quest];
- if (item.lvl && item.lvl > user.stats.lvl)
- return alert(window.env.t('mustLvlQuest', {level: item.lvl}));
- var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]);
- if (!completedPrevious)
- return $scope.purchase("quests", item);
- $rootScope.selectedQuest = item;
- $rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
- }
$scope.getSeasonalShopArray = function(set){
var flatGearArray = _.toArray(Content.gear.flat);
diff --git a/website/public/js/services/groupServices.js b/website/public/js/services/groupServices.js
index 368c675123..6235cb9f5a 100644
--- a/website/public/js/services/groupServices.js
+++ b/website/public/js/services/groupServices.js
@@ -5,8 +5,8 @@
*/
angular.module('habitrpg').factory('Groups',
-['ApiUrl', '$resource', '$q', '$http', 'User', 'Challenges',
-function(ApiUrl, $resource, $q, $http, User, Challenges) {
+['$rootScope','ApiUrl', '$resource', '$q', '$http', 'User', 'Challenges', 'Analytics', '$location',
+function($rootScope, ApiUrl, $resource, $q, $http, User, Challenges, Analytics, $location) {
var Group = $resource(ApiUrl.get() + '/api/v2/groups/:gid',
{gid:'@_id', messageId: '@_messageId'},
{
@@ -83,6 +83,20 @@ function(ApiUrl, $resource, $q, $http, User, Challenges) {
.then(syncUser, logError);
},
+ inviteOrStartParty: function(group) {
+ if (group.type === "party") {
+ $rootScope.openModal('invite-friends', {
+ controller:'InviteToGroupCtrl',
+ resolve: {
+ injectedGroup: function(){ return group; }
+ }
+ });
+ } else {
+ Analytics.track({'hitType':'event','eventCategory':'button','eventAction':'click','eventLabel':'Invite Friends'});
+ $location.path("/options/groups/party");
+ }
+ },
+
// Pass reference to party, myGuilds, publicGuilds, tavern; inside data in order to
// be able to modify them directly (otherwise will be stick with cached version)
data: data,
diff --git a/website/public/js/services/questServices.js b/website/public/js/services/questServices.js
new file mode 100644
index 0000000000..1ca6995c38
--- /dev/null
+++ b/website/public/js/services/questServices.js
@@ -0,0 +1,95 @@
+/**
+ * Created by Sabe on 7/7/2015.
+ */
+(function(){
+ angular
+ .module('habitrpg')
+ .factory('Quests', questsFactory);
+
+ questsFactory.$inject = [
+ '$rootScope',
+ 'Content',
+ 'Groups',
+ 'User'
+ ];
+
+ function questsFactory($rootScope,Content,Groups,User) {
+
+ var user = User.user;
+
+ function lockQuest(quest,ignoreLevel) {
+ if (!ignoreLevel){
+ if (quest.lvl && user.stats.lvl < quest.lvl) return true;
+ }
+ if (user.achievements.quests) return (quest.previous && !user.achievements.quests[quest.previous]);
+ return (quest.previous);
+ }
+
+ function buyQuest(quest) {
+ var item = Content.quests[quest];
+
+ if (item.unlockCondition && item.unlockCondition.condition === 'party invite') {
+ if (!confirm(window.env.t('mustInviteFriend'))) return;
+ return Groups.inviteOrStartParty(Groups.party());
+ }
+ if (item.previous && (!User.user.achievements.quests || (User.user.achievements.quests && !User.user.achievements.quests[item.previous]))){
+ return alert(window.env.t('unlockByQuesting', {title: Content.quests[item.previous].text()}));
+ }
+ if (item.lvl && item.lvl > user.stats.lvl) {
+ return alert(window.env.t('mustLvlQuest', {level: item.lvl}));
+ }
+ $rootScope.selectedQuest = item;
+ $rootScope.openModal('buyQuest', {controller:'InventoryCtrl'});
+ }
+
+ function questPopover(quest) {
+ // The popover gets parsed as markdown (hence the double \n for line breaks
+ var text = '';
+ 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) {
+ var count = 0;
+ for (var key in quest.collect) {
+ text += '**' + window.env.t('collect') + ':** ' + quest.collect[key].count + ' ' + quest.collect[key].text() + '\n\n';
+ }
+ }
+ text += '---\n\n';
+ text += '**' + window.env.t('rewards') + ':**\n\n';
+ if(quest.drop.items) {
+ for (var item in quest.drop.items) {
+ text += quest.drop.items[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';
+
+ return text;
+ }
+
+ function showQuest(quest) {
+ var item = Content.quests[quest];
+ var completedPrevious = !item.previous || (User.user.achievements.quests && User.user.achievements.quests[item.previous]);
+ if (!completedPrevious)
+ return alert(window.env.t('mustComplete', {quest: $rootScope.Content.quests[item.previous].text()}));
+ if (item.lvl && item.lvl > user.stats.lvl)
+ return alert(window.env.t('mustLevel', {level: item.lvl}));
+ $rootScope.selectedQuest = item;
+ $rootScope.openModal('showQuest', {controller:'InventoryCtrl'});
+ }
+
+ function closeQuest(){
+ $rootScope.selectedQuest = undefined;
+ }
+
+ return {
+ lockQuest: lockQuest,
+ buyQuest: buyQuest,
+ questPopover: questPopover,
+ showQuest: showQuest,
+ closeQuest: closeQuest
+ }
+ }
+}());
diff --git a/website/public/manifest.json b/website/public/manifest.json
index da32d2cfa4..029b618a94 100644
--- a/website/public/manifest.json
+++ b/website/public/manifest.json
@@ -51,6 +51,7 @@
"js/services/taskServices.js",
"js/services/challengeServices.js",
"js/services/paymentServices.js",
+ "js/services/questServices.js",
"js/filters/money.js",
"js/filters/roundLargeNumbers.js",
diff --git a/website/views/options/inventory/equipment.jade b/website/views/options/inventory/equipment.jade
new file mode 100644
index 0000000000..7d696462c4
--- /dev/null
+++ b/website/views/options/inventory/equipment.jade
@@ -0,0 +1,30 @@
+script(type='text/ng-template', id='partials/options.inventory.equipment.html')
+ .container-fluid
+ .row
+ .col-md-6.border-right
+ h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', popover=env.t('battleGearText'))=env.t('battleGear')
+ div
+ button.btn.btn-default(type="button", ng-click='dequip("battleGear");') {{env.t("unequipBattleGear")}}
+ li.customize-menu.inventory-gear
+ menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery"), armoire:env.t("armoireText")}', ng-show='gear[klass]')
+ div(ng-repeat='item in gear[klass]')
+ button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
+ .col-md-6
+ h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', popover=env.t('costumeText'))=env.t('costume')
+ .checkbox.equipment-title
+ label
+ input(type="checkbox", ng-model="user.preferences.costume", ng-change='set({"preferences.costume":user.preferences.costume ? true : false})')
+ |
+ =env.t('useCostume')
+ div
+ button.btn.btn-default(type="button", ng-click='dequip("costume");') {{env.t("unequipCostume")}}
+ button.btn.btn-default(type="button", ng-click='dequip("petMountBackground");') {{env.t("unequipPetMountBackground")}}
+ li.customize-menu(ng-if='!user.preferences.costume')
+ .well.use-costume-info
+ p=env.t('useCostumeInfo1')
+ p(ng-if='showUseCostumeInfo')!=env.t('useCostumeInfo2')
+ p: a(ng-click='showUseCostumeInfo = !showUseCostumeInfo') {{!showUseCostumeInfo ? env.t('showMoreMore') : env.t('showMoreLess')}}
+ li.customize-menu(ng-if='user.preferences.costume')
+ menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery"), armoire:env.t("armoireText")}', ng-show='gear[klass]')
+ div(ng-repeat='item in gear[klass]')
+ button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
\ No newline at end of file
diff --git a/website/views/options/inventory/index.jade b/website/views/options/inventory/index.jade
index 63959b1708..95e601f3d2 100644
--- a/website/views/options/inventory/index.jade
+++ b/website/views/options/inventory/index.jade
@@ -1,11 +1,18 @@
+include ./equipment
include ./inventory
+include ./quests
+include ./seasonal-shop
include ./stable
+include ./time-travelers
script(type='text/ng-template', id='partials/options.inventory.html')
ul.options-menu
li(ng-class="{ active: $state.includes('options.inventory.drops') }")
a(ui-sref='options.inventory.drops')
=env.t('market')
+ li(ng-class="{ active: $state.includes('options.inventory.quests') }")
+ a(ui-sref='options.inventory.quests')
+ =env.t('quests')
li(ng-class="{ active: $state.includes('options.inventory.pets') }")
a(ui-sref='options.inventory.pets')
=env.t('pets')
diff --git a/website/views/options/inventory/inventory.jade b/website/views/options/inventory/inventory.jade
index bd841970d2..a69f0cb5b5 100644
--- a/website/views/options/inventory/inventory.jade
+++ b/website/views/options/inventory/inventory.jade
@@ -1,101 +1,3 @@
-script(type='text/ng-template', id='partials/options.inventory.equipment.html')
- .container-fluid
- .row
- .col-md-6.border-right
- h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', popover=env.t('battleGearText'))=env.t('battleGear')
- div
- button.btn.btn-default(type="button", ng-click='dequip("battleGear");') {{env.t("unequipBattleGear")}}
- li.customize-menu.inventory-gear
- menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery"), armoire:env.t("armoireText")}', ng-show='gear[klass]')
- div(ng-repeat='item in gear[klass]')
- button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='user.ops.equip({params:{key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}')
- .col-md-6
- h3.equipment-title.hint(popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', popover=env.t('costumeText'))=env.t('costume')
- .checkbox.equipment-title
- label
- input(type="checkbox", ng-model="user.preferences.costume", ng-change='set({"preferences.costume":user.preferences.costume ? true : false})')
- |
- =env.t('useCostume')
- div
- button.btn.btn-default(type="button", ng-click='dequip("costume");') {{env.t("unequipCostume")}}
- button.btn.btn-default(type="button", ng-click='dequip("petMountBackground");') {{env.t("unequipPetMountBackground")}}
- li.customize-menu(ng-if='!user.preferences.costume')
- .well.use-costume-info
- p=env.t('useCostumeInfo1')
- p(ng-if='showUseCostumeInfo')!=env.t('useCostumeInfo2')
- p: a(ng-click='showUseCostumeInfo = !showUseCostumeInfo') {{!showUseCostumeInfo ? env.t('showMoreMore') : env.t('showMoreLess')}}
- li.customize-menu(ng-if='user.preferences.costume')
- menu.pets-menu(label='{{::label}}', ng-repeat='(klass,label) in {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer"), special:env.t("special"), mystery:env.t("mystery"), armoire:env.t("armoireText")}', ng-show='gear[klass]')
- div(ng-repeat='item in gear[klass]')
- button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='user.ops.equip({params:{type:"costume", key:item.key}})', class='shop_{{::item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}')
-
-script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html')
- .container-fluid
- .stable.row
- .col-md-2
- .seasonalshop_summer2015
- .col-md-10
- .popover.static-popover.fade.right.in
- .arrow
- h3.popover-title!=env.t('seasonalShopTitle', {linkStart:"", linkEnd: ""})
- .popover-content
- p!=env.t('seasonalShopSummerText')
- .well(ng-if='User.user.achievements.rebirths > 0')=env.t('seasonalShopRebirth')
- li.customize-menu.inventory-gear
- menu.pets-menu(label='{{::label}}', ng-repeat='(set,label) in ::{summerWarrior:env.t("daringSwashbucklerSet"), summerMage:env.t("emeraldMermageSet"), summerHealer:env.t("reefSeahealerSet"), summerRogue:env.t("roguishPirateSet")}')
- div(ng-repeat='item in ::getSeasonalShopArray(set)' ng-class="{transparent: user.items.gear.owned[item.key] === true ||user.items.gear.owned[item.key] === false}")
- button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase(item.type,item)', class='shop_{{::item.key}}')
- .text-left
- | {{((item.specialClass == "wizard") && (item.type == "weapon")) + 1}}
- span.Pet_Currency_Gem1x.inline-gems
- // menu.pets-menu(label=env.t('quests'))
- div(ng-repeat='quest in ::getSeasonalShopQuests()')
- button.customize-option(data-popover-html="{{::quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-append-to-body='true', popover-title='{{::quest.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='buyQuest(quest.key)', ng-class='(quest.previous && !user.achievements.quests[quest.previous]) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
- p
- | {{::quest.value}}
- span.Pet_Currency_Gem1x.inline-gems
- menu.pets-menu(label=env.t('seasonalItems'))
- div
- button.customize-option(popover='{{::Content.spells.special.seafoam.notes()}}', popover-title='{{::Content.spells.special.seafoam.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase("special", Content.spells.special.seafoam)', class='shop_seafoam')
- p
- | {{::Content.spells.special.seafoam.value}}
- span(class='shop_gold')
- // div
- button.customize-option(popover='{{::Content.spells.special.nye.notes()}}', popover-title='{{::Content.spells.special.nye.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='castStart(Content.spells.special.nye)', class='inventory_special_nye')
- p
- | {{Content.spells.special.nye.value}}
- span(class='shop_gold')
-
-script(type='text/ng-template', id='partials/options.inventory.timetravelers.html')
- .container-fluid
- .stable.row(ng-if='user.purchased.plan.consecutive.trinkets <= 0')
- .col-md-2
- .npc_timetravelers
- .col-md-10
- .popover.static-popover.fade.right.in
- .arrow
- h3.popover-title!=env.t('timeTravelersTitleNoSub', {linkStartTyler: "", linkStartVicky: "", linkEnd: ""})
- .popover-content
- p!=env.t('timeTravelersPopoverNoSub', {linkStart: "", linkEnd: ""})
- .row.stable(ng-if='user.purchased.plan.consecutive.trinkets > 0')
- .col-md-2
- .npc_timetravelers_active
- .col-md-10
- .popover.static-popover.fade.right.in
- .arrow
- h3.popover-title=env.t('timeTravelersTitle')
- .popover-content
- .pull-right
- span.inventory_special_trinket.inline-gems
- b x{{user.purchased.plan.consecutive.trinkets}}
- p!=env.t('timeTravelersPopover', {linkStart: "", linkEnd: ""})
-
- .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(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}})', class='shop_{{::item.key}}')
-
script(type='text/ng-template', id='partials/options.inventory.drops.html')
.container-fluid
.row
@@ -120,14 +22,6 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
button.customize-option(popover='{{::Content.hatchingPotions[pot].notes()}}', popover-title!=env.t("potion", {potionType: "{{::Content.hatchingPotions[pot].text()}}"}), popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{::pot}}', ng-class='{selectableInventory: selectedEgg && !(user.items.pets[selectedEgg.key+"-"+pot]>0)}')
.badge.badge-info.stack-count {{points}}
- li.customize-menu
- menu.pets-menu(label=(env.t('quests') + ' ({{questCount}})'))
- p.muted(ng-show='questCount < 1')=env.t('noScrolls')
- p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + ''
- div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]')
- button.customize-option(data-popover-html="{{:: quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-title='{{::quest.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='showQuest(quest_key)', ng-class='(quest.previous && !user.achievements.quests[quest.previous]) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
- .badge.badge-info.stack-count {{points}}
-
li.customize-menu
menu.pets-menu(label=env.t('food') + ' ({{foodCount}})')
p.muted(ng-show='foodCount < 1')=env.t('noFood')
@@ -135,7 +29,6 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
button.customize-option(popover='{{::Content.food[food].notes()}}', popover-title='{{::Content.food[food].text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='chooseFood(food)', class='Pet_Food_{{::food}}')
.badge.badge-info.stack-count {{points}}
-
li.customize-menu
menu.pets-menu(label=env.t('special'))
mixin specialItem(k)
@@ -221,15 +114,6 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
| {{::food.value}}
span.Pet_Currency_Gem1x.inline-gems
- li.customize-menu
- menu.pets-menu(label=env.t('quests'))
- p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + ''
- div(ng-repeat='quest in Content.quests', ng-if='quest.canBuy')
- button.customize-option(data-popover-html="{{::quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-title='{{::quest.text()}}', popover-append-to-body="true", popover-trigger='mouseenter', ng-click='buyQuest(quest.key)', ng-class='(quest.previous && !user.achievements.quests[quest.previous]) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
- p
- | {{::quest.value}}
- span.Pet_Currency_Gem1x.inline-gems
-
li.customize-menu
menu.pets-menu(label=env.t('special'))
div
diff --git a/website/views/options/inventory/quests.jade b/website/views/options/inventory/quests.jade
new file mode 100644
index 0000000000..40e73b796d
--- /dev/null
+++ b/website/views/options/inventory/quests.jade
@@ -0,0 +1,38 @@
+// Created by Sabe on 7/6/2015.
+script(type='text/ng-template', id='partials/options.inventory.quests.html')
+ .container-fluid
+ .row
+ .col-md-2
+ .npc_ian
+ .col-md-10
+ .popover.static-popover.fade.right.in
+ .arrow
+ h3.popover-title=env.t('ian')
+ .popover-content
+ p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + '.'
+ .row
+ - var questCategories = {'unlockable':'unlockableQuests','pet':'petQuests','gold':'goldQuests'}
+ .col-md-6
+ h3.equipment-title=env.t('yourQuests')
+ menu.inventory-list
+ li.customize-menu
+ p.muted(ng-show='questCount < 1')=env.t('noScrolls')
+ each caption,type in questCategories
+ menu.pets-menu(label=env.t(caption))
+ div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]', ng-if='Content.quests[quest_key].category === "#{type}"')
+ button.customize-option(data-popover-html="{{::lockQuest(quest) ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-title='{{::quest.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='showQuest(quest_key)', ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
+ .badge.badge-info.stack-count {{points}}
+ .col-md-6.border-left
+ li.customize-menu
+ h3.equipment-title=env.t('questsForSale')
+ each caption,type in questCategories
+ menu.pets-menu(label=env.t(caption))
+ div(ng-repeat='quest in Content.questsByLevel', ng-if='quest.canBuy && quest.category === "#{type}"')
+ button.customize-option(data-popover-html="{{::lockQuest(quest,true) ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-title='{{::quest.text()}}', popover-append-to-body="true", popover-trigger='mouseenter', ng-click='buyQuest(quest.key)', ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
+ p(ng-if='quest.unlockCondition')
+ | {{::quest.unlockCondition.text()}}
+ p(ng-if='quest.value && !lockQuest(quest)')
+ | {{::quest.value}}
+ span.Pet_Currency_Gem1x.inline-gems
+ p(ng-if='quest.lvl && lockQuest(quest)')=env.t('level')
+ | {{::quest.lvl}}
\ No newline at end of file
diff --git a/website/views/options/inventory/seasonal-shop.jade b/website/views/options/inventory/seasonal-shop.jade
new file mode 100644
index 0000000000..9bbfd02cff
--- /dev/null
+++ b/website/views/options/inventory/seasonal-shop.jade
@@ -0,0 +1,36 @@
+script(type='text/ng-template', id='partials/options.inventory.seasonalshop.html')
+ .container-fluid
+ .stable.row
+ .col-md-2
+ .seasonalshop_summer2015
+ .col-md-10
+ .popover.static-popover.fade.right.in
+ .arrow
+ h3.popover-title!=env.t('seasonalShopTitle', {linkStart:"", linkEnd: ""})
+ .popover-content
+ p!=env.t('seasonalShopSummerText')
+ .well(ng-if='User.user.achievements.rebirths > 0')=env.t('seasonalShopRebirth')
+ li.customize-menu.inventory-gear
+ menu.pets-menu(label='{{::label}}', ng-repeat='(set,label) in ::{summerWarrior:env.t("daringSwashbucklerSet"), summerMage:env.t("emeraldMermageSet"), summerHealer:env.t("reefSeahealerSet"), summerRogue:env.t("roguishPirateSet")}')
+ div(ng-repeat='item in ::getSeasonalShopArray(set)' ng-class="{transparent: user.items.gear.owned[item.key] === true ||user.items.gear.owned[item.key] === false}")
+ button.customize-option(popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase(item.type,item)', class='shop_{{::item.key}}')
+ .text-left
+ | {{((item.specialClass == "wizard") && (item.type == "weapon")) + 1}}
+ span.Pet_Currency_Gem1x.inline-gems
+ // menu.pets-menu(label=env.t('quests'))
+ div(ng-repeat='quest in ::getSeasonalShopQuests()')
+ button.customize-option(data-popover-html="{{::quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : questPopover(quest) | markdown}}", popover-append-to-body='true', popover-title='{{::quest.text()}}', popover-trigger='mouseenter', popover-placement='right', ng-click='buyQuest(quest.key)', ng-class='(quest.previous && !user.achievements.quests[quest.previous]) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"')
+ p
+ | {{::quest.value}}
+ span.Pet_Currency_Gem1x.inline-gems
+ menu.pets-menu(label=env.t('seasonalItems'))
+ div
+ button.customize-option(popover='{{::Content.spells.special.seafoam.notes()}}', popover-title='{{::Content.spells.special.seafoam.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase("special", Content.spells.special.seafoam)', class='shop_seafoam')
+ p
+ | {{::Content.spells.special.seafoam.value}}
+ span(class='shop_gold')
+ // div
+ button.customize-option(popover='{{::Content.spells.special.nye.notes()}}', popover-title='{{::Content.spells.special.nye.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='castStart(Content.spells.special.nye)', class='inventory_special_nye')
+ p
+ | {{Content.spells.special.nye.value}}
+ span(class='shop_gold')
\ No newline at end of file
diff --git a/website/views/options/inventory/time-travelers.jade b/website/views/options/inventory/time-travelers.jade
new file mode 100644
index 0000000000..f9d419a13b
--- /dev/null
+++ b/website/views/options/inventory/time-travelers.jade
@@ -0,0 +1,29 @@
+script(type='text/ng-template', id='partials/options.inventory.timetravelers.html')
+ .container-fluid
+ .stable.row(ng-if='user.purchased.plan.consecutive.trinkets <= 0')
+ .col-md-2
+ .npc_timetravelers
+ .col-md-10
+ .popover.static-popover.fade.right.in
+ .arrow
+ h3.popover-title!=env.t('timeTravelersTitleNoSub', {linkStartTyler: "", linkStartVicky: "", linkEnd: ""})
+ .popover-content
+ p!=env.t('timeTravelersPopoverNoSub', {linkStart: "", linkEnd: ""})
+ .row.stable(ng-if='user.purchased.plan.consecutive.trinkets > 0')
+ .col-md-2
+ .npc_timetravelers_active
+ .col-md-10
+ .popover.static-popover.fade.right.in
+ .arrow
+ h3.popover-title=env.t('timeTravelersTitle')
+ .popover-content
+ .pull-right
+ span.inventory_special_trinket.inline-gems
+ b x{{user.purchased.plan.consecutive.trinkets}}
+ p!=env.t('timeTravelersPopover', {linkStart: "", linkEnd: ""})
+
+ .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(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}})', class='shop_{{::item.key}}')
\ No newline at end of file
diff --git a/website/views/shared/header/menu.jade b/website/views/shared/header/menu.jade
index 02d2d0a758..9b853a63a6 100644
--- a/website/views/shared/header/menu.jade
+++ b/website/views/shared/header/menu.jade
@@ -41,6 +41,8 @@ nav.toolbar(ng-controller='MenuCtrl', ng-class='{active: isToolbarHidden}')
ul.toolbar-submenu
li
a(ui-sref='options.inventory.drops')=env.t('market')
+ li
+ a(ui-sref='options.inventory.quests')=env.t('quests')
li
a(ui-sref='options.inventory.pets')=env.t('pets')
li
@@ -125,6 +127,8 @@ nav.toolbar(ng-controller='MenuCtrl', ng-class='{active: isToolbarHidden}')
ul.toolbar-submenu(ng-click='expandMenu(null)')
li
a(ui-sref='options.inventory.drops')=env.t('market')
+ li
+ a(ui-sref='options.inventory.quests')=env.t('quests')
li
a(ui-sref='options.inventory.pets')=env.t('pets')
li