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 f3391cf976..56d4bfb4ef 100644
--- a/common/script/content.coffee
+++ b/common/script/content.coffee
@@ -1112,6 +1112,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
@@ -1154,6 +1155,7 @@ api.quests =
completionChat: t("questStressbeastCompletionChat")
value: 0
canBuy: false
+ category: 'world'
boss:
name: t("questStressbeastBoss")
hp: 2750000
@@ -1197,7 +1199,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
@@ -1216,6 +1218,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
@@ -1231,6 +1234,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
@@ -1250,6 +1254,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
@@ -1269,6 +1274,7 @@ api.quests =
notes: t('questGhostStagNotes')
completion: t('questGhostStagCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questGhostStagBoss')
hp: 1200
@@ -1288,6 +1294,7 @@ api.quests =
notes: t('questVice1Notes')
value: 4
lvl: 30
+ category: 'unlockable'
boss:
name: t('questVice1Boss')
hp: 750
@@ -1303,7 +1310,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
@@ -1320,7 +1328,8 @@ api.quests =
completion: t('questVice3Completion')
previous: 'vice2'
value: 4
- lvl: 40
+ lvl: 30
+ category: 'unlockable'
boss:
name: t('questVice3Boss')
hp: 1500
@@ -1342,6 +1351,7 @@ api.quests =
completion: t('questEggHuntCompletion')
value: 1
canBuy: false
+ category: 'pet'
collect:
plainEgg: text: t('questEggHuntCollectPlainEgg'), count: 100
drop:
@@ -1365,6 +1375,7 @@ api.quests =
notes: t('questRatNotes')
completion: t('questRatCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRatBoss')
hp: 1200
@@ -1384,6 +1395,7 @@ api.quests =
notes: t('questOctopusNotes')
completion: t('questOctopusCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questOctopusBoss')
hp: 1200
@@ -1403,6 +1415,7 @@ api.quests =
notes: t('questSeahorseNotes')
completion: t('questSeahorseCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSeahorseBoss')
hp: 300
@@ -1422,6 +1435,7 @@ api.quests =
notes: t('questAtom1Notes')
value: 4
lvl: 15
+ category: 'unlockable'
collect:
soapBars: text: t('questAtom1CollectSoapBars'), count: 20
drop:
@@ -1436,6 +1450,7 @@ api.quests =
previous: 'atom1'
value: 4
lvl: 15
+ category: 'unlockable'
boss:
name: t('questAtom2Boss')
hp: 300
@@ -1453,6 +1468,7 @@ api.quests =
completion: t('questAtom3Completion')
value: 4
lvl: 15
+ category: 'unlockable'
boss:
name: t('questAtom3Boss')
hp: 800
@@ -1471,6 +1487,7 @@ api.quests =
notes: t('questHarpyNotes')
completion: t('questHarpyCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questHarpyBoss')
hp: 600
@@ -1490,6 +1507,7 @@ api.quests =
notes: t('questRoosterNotes')
completion: t('questRoosterCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRoosterBoss')
hp: 300
@@ -1509,6 +1527,7 @@ api.quests =
notes: t('questSpiderNotes')
completion: t('questSpiderCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSpiderBoss')
hp: 400
@@ -1528,6 +1547,7 @@ api.quests =
notes: t('questMoonstone1Notes')
value: 4
lvl: 60
+ category: 'unlockable'
collect:
moonstone: text: t('questMoonstone1CollectMoonstone'), count: 500
drop:
@@ -1540,8 +1560,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
@@ -1558,7 +1579,8 @@ api.quests =
completion: t('questMoonstone3Completion')
previous: 'moonstone2'
value: 4
- lvl: 70
+ lvl: 60
+ category: 'unlockable'
boss:
name: t('questMoonstone3Boss')
hp: 2000
@@ -1583,6 +1605,7 @@ api.quests =
notes: t('questGoldenknight1Notes')
value: 4
lvl: 40
+ category: 'unlockable'
collect:
testimony: text: t('questGoldenknight1CollectTestimony'), count: 300
drop:
@@ -1596,7 +1619,8 @@ api.quests =
notes: t('questGoldenknight2Notes')
value: 4
previous: 'goldenknight1'
- lvl: 45
+ lvl: 40
+ category: 'unlockable'
boss:
name: t('questGoldenknight2Boss')
hp: 1000
@@ -1613,7 +1637,8 @@ api.quests =
completion: t('questGoldenknight3Completion')
previous: 'goldenknight2'
value: 4
- lvl: 50
+ lvl: 40
+ category: 'unlockable'
boss:
name: t('questGoldenknight3Boss')
hp: 1700
@@ -1634,8 +1659,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
@@ -1649,6 +1676,7 @@ api.quests =
notes: t('questOwlNotes')
completion: t('questOwlCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questOwlBoss')
hp: 500
@@ -1668,6 +1696,7 @@ api.quests =
notes: t('questPenguinNotes')
completion: t('questPenguinCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questPenguinBoss')
hp: 400
@@ -1687,6 +1716,7 @@ api.quests =
notes: t('questTRexNotes')
completion: t('questTRexCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questTRexBoss')
hp: 800
@@ -1706,6 +1736,7 @@ api.quests =
notes: t('questTRexUndeadNotes')
completion: t('questTRexUndeadCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questTRexUndeadBoss')
hp: 500
@@ -1731,6 +1762,7 @@ api.quests =
notes: t('questRockNotes')
completion: t('questRockCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questRockBoss')
hp: 400
@@ -1750,6 +1782,7 @@ api.quests =
notes: t('questBunnyNotes')
completion: t('questBunnyCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questBunnyBoss')
hp: 300
@@ -1769,6 +1802,7 @@ api.quests =
notes: t('questSlimeNotes')
completion: t('questSlimeCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSlimeBoss')
hp: 400
@@ -1788,6 +1822,7 @@ api.quests =
notes: t('questSheepNotes')
completion: t('questSheepCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questSheepBoss')
hp: 300
@@ -1807,6 +1842,7 @@ api.quests =
notes: t('questKrakenNotes')
completion: t('questKrakenCompletion')
value: 4
+ category: 'pet'
boss:
name: t('questKrakenBoss')
hp: 800
@@ -1848,6 +1884,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/common/script/index.coffee b/common/script/index.coffee
index 026a38be6e..83f8d043b9 100644
--- a/common/script/index.coffee
+++ b/common/script/index.coffee
@@ -883,7 +883,7 @@ api.wrap = (user, main=true) ->
item = if key is 'potion' then content.potion
else if key is 'armoire' then content.armoire
else content.gear.flat[key]
- return cb?({code:404, message:"Item '#{key} not found (see https://github.com/HabitRPG/habitrpg-shared/blob/develop/script/content.coffee)"}) unless item
+ return cb?({code:404, message:"Item '#{key} not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content.coffee)"}) unless item
return cb?({code:401, message: i18n.t('messageNotEnoughGold', req.language)}) if user.stats.gp < item.value
return cb?({code:401, message: "You can't buy this item"}) if item.canOwn? and !item.canOwn(user)
if item.key is 'potion'
@@ -921,6 +921,17 @@ api.wrap = (user, main=true) ->
mixpanel?.track("Acquire Item",{'itemName':key,'acquireMethod':'Gold','goldCost':item.value})
cb? {code:200, message}, _.pick(user,$w 'items achievements stats flags')
+ buyQuest: (req, cb) ->
+ {key} = req.params
+ item = content.quests[key]
+ return cb?({code:404, message:"Quest '#{key} not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content.coffee)"}) unless item
+ return cb?({code:401, message: i18n.t('messageNotEnoughGold', req.language)}) if user.stats.gp < item.value
+ message = i18n.t('messageBought', {itemText: item.text(req.language)}, req.language)
+ user.items.quests[item.key] ?= 0
+ user.items.quests[item.key] += 1
+ user.stats.gp -= item.value
+ cb? {code:200, message}, user.items.quests
+
buyMysterySet: (req, cb)->
return cb?({code:401, message:"You don't have enough Mystic Hourglasses"}) unless user.purchased.plan.consecutive.trinkets>0
mysterySet = content.timeTravelerStore(user.items.gear.owned)?[req.params.key]
diff --git a/karma.conf.js b/karma.conf.js
index 42e63f35db..d1d576e53b 100644
--- a/karma.conf.js
+++ b/karma.conf.js
@@ -54,6 +54,7 @@ module.exports = function(config) {
"website/public/js/services/challengeServices.js",
"website/public/js/services/taskServices.js",
"website/public/js/services/paymentServices.js",
+ "website/public/js/services/questServices.js",
"website/public/js/filters/money.js",
"website/public/js/filters/roundLargeNumbers.js",
diff --git a/test/common/algos.mocha.coffee b/test/common/algos.mocha.coffee
index 64bda3ccf4..423c182d84 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 01374f2972..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(){
@@ -187,17 +154,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/controllers/tasksCtrl.js b/website/public/js/controllers/tasksCtrl.js
index 58a992c56d..74d4c2d642 100644
--- a/website/public/js/controllers/tasksCtrl.js
+++ b/website/public/js/controllers/tasksCtrl.js
@@ -219,7 +219,6 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User','N
$scope.buy = function(item) {
User.user.ops.buy({params:{key:item.key}});
$rootScope.playSound('Reward');
- Guide.goto('intro', 4);
};
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..b634e0ace4
--- /dev/null
+++ b/website/public/js/services/questServices.js
@@ -0,0 +1,98 @@
+/**
+ * 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}));
+ }
+ if (item.category === 'gold') {
+ return User.user.ops.buyQuest({params:{key:item.key}});
+ }
+ $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/drops.jade b/website/views/options/inventory/drops.jade
index 8f879ada65..3075bd7f5e 100644
--- a/website/views/options/inventory/drops.jade
+++ b/website/views/options/inventory/drops.jade
@@ -31,18 +31,6 @@
ng-click='choosePotion(pot)')
.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(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}}"',
- 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)')
- .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')
@@ -179,19 +167,6 @@
| {{::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(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}}"',
- 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)')
- 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/equipment.jade b/website/views/options/inventory/equipment.jade
index 66aefec627..817c9e3a1d 100644
--- a/website/views/options/inventory/equipment.jade
+++ b/website/views/options/inventory/equipment.jade
@@ -6,13 +6,13 @@
popover=env.t('battleGearText'))=env.t('battleGear')
div
- button.btn.btn-default(type="button", ng-click='dequip("battleGear");') {{env.t("unequipBattleGear")}}
+ button.btn.btn-default(type="button", ng-click='dequip("battleGear");') {{env.t("unequipBattleGear")}}
li.customize-menu.inventory-gear
menu.pets-menu(label='{{::label}}', ng-show='gear[klass]',
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")}')
div(ng-repeat='item in gear[klass]')
- button.customize-option(class='shop_{{::item.key}}',
+ button.customize-option(, class='shop_{{::item.key}}',
ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}',
ng-click='equip(item.key, "equipped")',
popover='{{::item.notes()}}', popover-title='{{::item.text()}}',
@@ -28,8 +28,8 @@
label
input(type="checkbox", ng-model="user.preferences.costume",
ng-change='set({"preferences.costume":user.preferences.costume ? true : false})')
- |
- =env.t('useCostume')
+ |
+ =env.t('useCostume')
div
button.btn.btn-default(type="button", ng-click='dequip("costume");') {{env.t("unequipCostume")}}
@@ -47,7 +47,7 @@
div(ng-repeat='item in gear[klass]')
button.customize-option(class='shop_{{::item.key}}',
ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}',
- popover='{{::item.notes()}}',
- popover-title='{{::item.text()}}', popover-trigger='mouseenter',
- popover-placement='right', popover-append-to-body='true',
- ng-click='equip(item.key, "costume")')
+ ng-click='equip(item.key, "costume")',
+ popover='{{::item.notes()}}', popover-title='{{::item.text()}}',
+ popover-trigger='mouseenter', popover-placement='right',
+ popover-append-to-body='true')
diff --git a/website/views/options/inventory/index.jade b/website/views/options/inventory/index.jade
index 6df18e1e4b..1a265bbfc1 100644
--- a/website/views/options/inventory/index.jade
+++ b/website/views/options/inventory/index.jade
@@ -13,6 +13,9 @@ script(type='text/ng-template', id='partials/options.inventory.timetravelers.htm
script(type='text/ng-template', id='partials/options.inventory.drops.html')
include ./drops
+script(type='text/ng-template', id='partials/options.inventory.quests.html')
+ include ./quests
+
script(type='text/ng-template', id='partials/options.inventory.pets.html')
include ./pets
diff --git a/website/views/options/inventory/menu.jade b/website/views/options/inventory/menu.jade
index 8e44151d86..576185fd8a 100644
--- a/website/views/options/inventory/menu.jade
+++ b/website/views/options/inventory/menu.jade
@@ -2,6 +2,9 @@ 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/quests.jade b/website/views/options/inventory/quests.jade
new file mode 100644
index 0000000000..9424a53186
--- /dev/null
+++ b/website/views/options/inventory/quests.jade
@@ -0,0 +1,40 @@
+// Created by Sabe on 7/6/2015.
+.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 && quest.category !== "gold" && !lockQuest(quest)')
+ | {{::quest.value}}
+ span.Pet_Currency_Gem1x.inline-gems
+ p(ng-if='quest.value && quest.category === "gold" && !lockQuest(quest)')
+ | {{::quest.value}}
+ span.shop_gold
+ p(ng-if='quest.lvl && lockQuest(quest)')=env.t('level')
+ | {{::quest.lvl}}
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