Add API calls for shop inventories (#7810)

* Add API call for market inventory

* changes to shop api calls

* optimize shop categories

* add API call for quests

* add api call for time travelers shop

* fic buying items in shops

* fix linting errors

* shop adjustments for iOS app

* add tests to shops

* fix syntax issues

* Code formatting

* correct indentation

* add tests for api routes

* fix time travelers and seasonal

* Increase test coverage for shop routes

* refactor: Pull out trinket count in time traveler route

* refactor: Clarify instructions for seasonal shop script

* lint: Remove extra new line

* Adjust shops common test
This commit is contained in:
Phillip Thelen
2016-07-26 21:36:55 +02:00
committed by Sabe Jones
parent aa00893f6c
commit 24d25026cf
15 changed files with 669 additions and 104 deletions

View File

@@ -11,6 +11,7 @@
"danielText2Broken": "Oh... If you are participating in a boss quest, the boss will still damage you for your party mates' missed Dailies... Also, your own damage to the Boss (or items collected) will not be applied until you check out of the Inn...",
"alexander": "Alexander the Merchant",
"welcomeMarket": "Welcome to the Market! Buy hard-to-find eggs and potions! Sell your extras! Commission useful services! Come see what we have to offer.",
"welcomeMarketMobile": "Welcome to the Market! Buy hard-to-find eggs and potions! Come see what we have to offer.",
"displayItemForGold": "Do you want to sell a <strong><%= itemType %></strong>?",
"displayEggForGold": "Do you want to sell a <strong><%= itemType %> Egg</strong>?",
"displayPotionForGold": "Do you want to sell a <strong><%= itemType %> Potion</strong>?",
@@ -20,6 +21,7 @@
"justin": "Justin",
"ian": "Ian",
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
"ianTextMobile": "Welcome to the Quest Shop! Be sure to check out our fine array of Quest Scrolls for purchase!",
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
"missingKeyParam": "\"req.params.key\" is required.",

View File

@@ -73,6 +73,7 @@
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> and <%= linkStartVicky %>Vicky<%= linkEnd %>",
"timeTravelersTitle": "Mysterious Time Travelers",
"timeTravelersPopoverNoSub": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! <%= linkStart %>Subscribers<%= linkEnd %> earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a rare pet, mount, or Subscriber Item Set from the past... or maybe even the future.",
"timeTravelersPopoverNoSubMobile": "You'll need a Mystic Hourglass to summon the mysterious Time Travelers! Subscribers earn one Mystic Hourglass for every three months of consecutive subscribing. Come back when you have a Mystic Hourglass, and the Time Travelers will fetch you a rare pet, mount, or Subscriber Item Set from the past... or maybe even the future.",
"timeTravelersPopover": "We see you have a Mystic Hourglass, so we will happily travel back in time for you! Please choose the pet, mount, or Mystery Item Set you would like. You can see a list of the past item sets <a href='http://habitica.wikia.com/wiki/Mystery_Item' target='_blank'>here</a>! If those don't satisfy you, perhaps you'd be interested in one of our fashionably futuristic Steampunk Item Sets?",
"timeTravelersAlreadyOwned": "Congratulations! You already own everything the Time Travelers currently offer. Thanks for supporting the site!",
"mysticHourglassPopover": "A Mystic Hourglass allows you to purchase certain limited-time items, such as monthly Mystery Item Sets and awards from world bosses, from the past!",

View File

@@ -94,6 +94,9 @@ api.count = count;
import statsComputed from './libs/statsComputed';
api.statsComputed = statsComputed;
import shops from './libs/shops';
api.shops = shops;
import autoAllocate from './fns/autoAllocate';
import crit from './fns/crit';
import handleTwoHanded from './fns/handleTwoHanded';

243
common/script/libs/shops.js Normal file
View File

@@ -0,0 +1,243 @@
import _ from 'lodash';
import content from '../content/index';
import i18n from '../../../common/script/i18n';
let shops = {};
function lockQuest (quest, user) {
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;
}
shops.getMarketCategories = function getMarket (user, language) {
let categories = [];
let eggsCategory = {
identifier: 'eggs',
text: i18n.t('eggs', language),
notes: i18n.t('dropsExplanation', language),
};
eggsCategory.items = _(content.questEggs)
.values()
.filter(egg => egg.canBuy(user))
.concat(_.values(content.dropEggs))
.map(egg => {
return {
key: egg.key,
text: i18n.t('egg', {eggType: egg.text()}, language),
notes: egg.notes(language),
value: egg.value,
class: `Pet_Egg_${egg.key}`,
locked: false,
currency: 'gems',
purchaseType: 'eggs',
};
}).sortBy('key').value();
categories.push(eggsCategory);
let hatchingPotionsCategory = {
identifier: 'hatchingPotions',
text: i18n.t('hatchingPotions', language),
notes: i18n.t('dropsExplanation', language),
};
hatchingPotionsCategory.items = _(content.hatchingPotions)
.values()
.filter(hp => !hp.limited)
.map(hatchingPotion => {
return {
key: hatchingPotion.key,
text: hatchingPotion.text(language),
notes: hatchingPotion.notes(language),
class: `Pet_HatchingPotion_${hatchingPotion.key}`,
value: hatchingPotion.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingpotions',
};
}).sortBy('key').value();
categories.push(hatchingPotionsCategory);
let foodCategory = {
identifier: 'food',
text: i18n.t('food', language),
notes: i18n.t('dropsExplanation', language),
};
foodCategory.items = _(content.food)
.values()
.filter(food => food.canDrop || food.key === 'Saddle')
.map(foodItem => {
return {
key: foodItem.key,
text: foodItem.text(language),
notes: foodItem.notes(language),
class: `Pet_Food_${foodItem.key}`,
value: foodItem.value,
locked: false,
currency: 'gems',
purchaseType: 'food',
};
}).sortBy('key').value();
categories.push(foodCategory);
return categories;
};
shops.getQuestShopCategories = function getQuestShopCategories (user, language) {
let categories = [];
_.each(content.userCanOwnQuestCategories, type => {
let category = {
identifier: type,
text: i18n.t(`${type}Quests`, language),
};
category.items = _(content.questsByLevel)
.filter(quest => quest.canBuy(user) && quest.category === type)
.map(quest => {
let locked = lockQuest(quest, user);
return {
key: quest.key,
text: quest.text(language),
notes: quest.notes(language),
value: quest.goldValue ? quest.goldValue : quest.value,
currency: quest.goldValue ? 'gold' : 'gems',
locked,
unlockCondition: quest.unlockCondition,
drop: quest.drop,
boss: quest.boss,
collect: quest.collect,
lvl: quest.lvl,
class: locked ? `inventory_quest_scroll_${quest.key}_locked` : `inventory_quest_scroll_${quest.key}`,
purchaseType: 'quests',
};
}).value();
categories.push(category);
});
return categories;
};
shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, language) {
let categories = [];
let stable = {pets: 'Pet-', mounts: 'Mount_Head_'};
for (let type in stable) {
if (stable.hasOwnProperty(type)) {
let category = {
identifier: type,
text: i18n.t(type, language),
items: [],
};
for (let key in content.timeTravelStable[type]) {
if (content.timeTravelStable[type].hasOwnProperty(key)) {
if (!user.items[type][key]) {
let item = {
key,
text: content.timeTravelStable[type][key](language),
class: stable[type] + key,
type,
purchaseType: type,
value: 1,
notes: '',
locked: false,
currency: 'hourglasses',
};
category.items.push(item);
}
}
}
if (category.items.length > 0) {
categories.push(category);
}
}
}
let sets = content.timeTravelerStore(user.items.gear.owned);
for (let setKey in sets) {
if (sets.hasOwnProperty(setKey)) {
let set = sets[setKey];
let category = {
identifier: set.key,
text: set.text(language),
purchaseAll: true,
};
category.items = _.map(set.items, item => {
return {
key: item.key,
text: item.text(language),
notes: item.notes(language),
type: item.type,
purchaseType: 'gear',
value: 1,
locked: false,
currency: 'hourglasses',
class: `shop_${item.key}`,
};
});
if (category.items.length > 0) {
categories.push(category);
}
}
}
return categories;
};
// To switch seasons/available inventory, edit the availableSets object to whatever should be sold.
// let availableSets = {
// setKey: i18n.t('setTranslationString', language),
// };
shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) {
let availableSets = {
summerWarrior: i18n.t('daringSwashbucklerSet', language),
summerMage: i18n.t('emeraldMermageSet', language),
summerHealer: i18n.t('reefSeahealerSet', language),
summerRogue: i18n.t('roguishPirateSet', language),
summer2015Warrior: i18n.t('sunfishWarriorSet', language),
summer2015Mage: i18n.t('shipSoothsayerSet', language),
summer2015Healer: i18n.t('strappingSailorSet', language),
summer2015Rogue: i18n.t('reefRenegadeSet', language),
};
let categories = [];
let flatGearArray = _.toArray(content.gear.flat);
for (let key in availableSets) {
if (availableSets.hasOwnProperty(key)) {
let category = {
identifier: key,
text: availableSets[key],
};
category.items = _(flatGearArray).filter((gear) => {
if (gear.index !== key) {
return false;
}
return user.items.gear.owned[gear.key] !== true;
}).where({index: key}).map(gear => {
return {
key: gear.key,
text: gear.text(language),
notes: gear.notes(language),
value: 1,
type: gear.type,
specialClass: gear.specialClass,
locked: false,
currency: 'gems',
purchaseType: 'gear',
};
}).value();
if (category.items.length > 0) {
categories.push(category);
}
}
}
return categories;
};
module.exports = shops;

View File

@@ -0,0 +1,28 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/market', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/market');
expect(shop.identifier).to.equal('market');
expect(shop.text).to.eql(t('market'));
expect(shop.notes).to.eql(t('welcomeMarketMobile'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('eggs');
expect(categories).to.include('hatchingPotions');
expect(categories).to.include('food');
});
});

View File

@@ -0,0 +1,28 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/quests', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/quests');
expect(shop.identifier).to.equal('questShop');
expect(shop.text).to.eql(t('quests'));
expect(shop.notes).to.eql(t('ianTextMobile'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('unlockable');
expect(categories).to.include('gold');
expect(categories).to.include('pet');
});
});

View File

@@ -0,0 +1,22 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/seasonal', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/seasonal');
expect(shop.identifier).to.equal('seasonalShop');
expect(shop.text).to.eql(t('seasonalShop'));
expect(shop.notes).to.eql(t('seasonalShopSummerText'));
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
});
});

View File

@@ -0,0 +1,98 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('GET /shops/time-travelers', () => {
let user;
beforeEach(async () => {
user = await generateUser();
});
it('returns a valid shop object', async () => {
let shop = await user.get('/shops/time-travelers');
expect(shop.identifier).to.equal('timeTravelersShop');
expect(shop.text).to.eql(t('timeTravelers'));
expect(shop.notes).to.be.a('string');
expect(shop.imageName).to.be.a('string');
expect(shop.categories).to.be.an('array');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.include('pets');
expect(categories).to.include('mounts');
expect(categories).to.include('201606');
let mammothPet = shop.categories
.find(cat => cat.identifier === 'pets')
.items
.find(pet => pet.key === 'Mammoth-Base');
let mantisShrimp = shop.categories
.find(cat => cat.identifier === 'mounts')
.items
.find(pet => pet.key === 'MantisShrimp-Base');
expect(mammothPet).to.exist;
expect(mantisShrimp).to.exist;
});
it('returns active shop notes and imageName if user has trinkets', async () => {
await user.update({
'purchased.plan.consecutive.trinkets': 1,
});
let shop = await user.get('/shops/time-travelers');
expect(shop.notes).to.eql(t('timeTravelersPopover'));
expect(shop.imageName).to.eql('npc_timetravelers_active');
});
it('returns inactive shop notes and imageName if user has trinkets', async () => {
let shop = await user.get('/shops/time-travelers');
expect(shop.notes).to.eql(t('timeTravelersPopoverNoSubMobile'));
expect(shop.imageName).to.eql('npc_timetravelers');
});
it('does not return mystery sets that are already owned', async () => {
await user.update({
'items.gear.owned': {
head_mystery_201606: true, // eslint-disable-line camelcase
armor_mystery_201606: true, // eslint-disable-line camelcase
},
});
let shop = await user.get('/shops/time-travelers');
let categories = shop.categories.map(cat => cat.identifier);
expect(categories).to.not.include('201606');
});
it('does not return pets and mounts that user already owns', async () => {
await user.update({
'items.mounts': {
'MantisShrimp-Base': true,
},
'items.pets': {
'Mammoth-Base': 5,
},
});
let shop = await user.get('/shops/time-travelers');
let mammothPet = shop.categories
.find(cat => cat.identifier === 'pets')
.items
.find(pet => pet.key === 'Mammoth-Base');
let mantisShrimp = shop.categories
.find(cat => cat.identifier === 'mounts')
.items
.find(pet => pet.key === 'MantisShrimp-Base');
expect(mammothPet).to.not.exist;
expect(mantisShrimp).to.not.exist;
});
});

84
test/common/libs/shops.js Normal file
View File

@@ -0,0 +1,84 @@
import shared from '../../../common';
import {
generateUser,
} from '../../helpers/common.helper';
describe('shops', () => {
let user = generateUser();
describe('market', () => {
let shopCategories = shared.shops.getMarketCategories(user);
it('contains at least the 3 default categories', () => {
expect(shopCategories.length).to.be.greaterThan(2);
});
it('does not contain an empty category', () => {
_.each(shopCategories, (category) => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('items contain required fields', () => {
_.each(shopCategories, (category) => {
_.each(category.items, (item) => {
expect(item).to.have.all.keys(['key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'class']);
});
});
});
});
describe('questShop', () => {
let shopCategories = shared.shops.getQuestShopCategories(user);
it('does not contain an empty category', () => {
_.each(shopCategories, (category) => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('items contain required fields', () => {
_.each(shopCategories, (category) => {
_.each(category.items, (item) => {
expect(item).to.have.all.keys('key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'boss', 'class', 'collect', 'drop', 'unlockCondition', 'lvl');
});
});
});
});
describe('timeTravelers', () => {
let shopCategories = shared.shops.getTimeTravelersCategories(user);
it('does not contain an empty category', () => {
_.each(shopCategories, (category) => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('items contain required fields', () => {
_.each(shopCategories, (category) => {
_.each(category.items, (item) => {
expect(item).to.have.all.keys('key', 'text', 'value', 'currency', 'locked', 'purchaseType', 'class', 'notes', 'class');
});
});
});
});
describe('seasonalShop', () => {
let shopCategories = shared.shops.getSeasonalShopCategories(user);
it('does not contain an empty category', () => {
_.each(shopCategories, (category) => {
expect(category.items.length).to.be.greaterThan(0);
});
});
it('items contain required fields', () => {
_.each(shopCategories, (category) => {
_.each(category.items, (item) => {
expect(item).to.have.all.keys('key', 'text', 'notes', 'value', 'currency', 'locked', 'purchaseType', 'specialClass', 'type');
});
});
});
});
});

View File

@@ -352,6 +352,11 @@ habitrpg.controller("InventoryCtrl",
User.hourglassPurchase({params:{type:type,key:key}});
};
$scope.marketShopCategories = Shared.shops.getMarketCategories(user);
$scope.questShopCategories = Shared.shops.getQuestShopCategories(user);
$scope.timeTravelersCategories = Shared.shops.getTimeTravelersCategories(user);
$scope.seasonalShopCategories = Shared.shops.getSeasonalShopCategories(user);
function _updateDropAnimalCount(items) {
$scope.petCount = Shared.count.beastMasterProgress(items.pets);
$scope.mountCount = Shared.count.mountMasterProgress(items.mounts);

View File

@@ -0,0 +1,123 @@
import { authWithHeaders } from '../../middlewares/api-v3/auth';
import shops from '../../../../common/script/libs/shops';
let api = {};
/**
* @apiIgnore
* @api {get} /api/v3/shops/market get the available items for the market
* @apiVersion 3.0.0
* @apiName GetMarketItems
* @apiGroup Shops
*
* @apiSuccess {Object} data List of push devices
* @apiSuccess {string} message Success message
*/
api.getMarketItems = {
method: 'GET',
url: '/shops/market',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
let resObject = {
identifier: 'market',
text: res.t('market'),
notes: res.t('welcomeMarketMobile'),
imageName: 'npc_alex',
categories: shops.getMarketCategories(user, req.language),
};
res.respond(200, resObject);
},
};
/**
* @apiIgnore
* @api {get} /api/v3/shops/quests get the available items for the quests shop
* @apiVersion 3.0.0
* @apiName GetQuestShopItems
* @apiGroup Shops
*
* @apiSuccess {Object} data List of push devices
* @apiSuccess {string} message Success message
*/
api.getQuestShopItems = {
method: 'GET',
url: '/shops/quests',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
let resObject = {
identifier: 'questShop',
text: res.t('quests'),
notes: res.t('ianTextMobile'),
imageName: 'npc_ian',
categories: shops.getQuestShopCategories(user, req.language),
};
res.respond(200, resObject);
},
};
/**
* @apiIgnore
* @api {get} /api/v3/shops/time-travelers get the available items for the time travelers shop
* @apiVersion 3.0.0
* @apiName GetTimeTravelersShopItems
* @apiGroup Shops
*
* @apiSuccess {Object} data List of push devices
* @apiSuccess {string} message Success message
*/
api.getTimeTravelerShopItems = {
method: 'GET',
url: '/shops/time-travelers',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
let hasTrinkets = user.purchased.plan.consecutive.trinkets > 0;
let resObject = {
identifier: 'timeTravelersShop',
text: res.t('timeTravelers'),
notes: hasTrinkets ? res.t('timeTravelersPopover') : res.t('timeTravelersPopoverNoSubMobile'),
imageName: hasTrinkets ? 'npc_timetravelers_active' : 'npc_timetravelers',
categories: shops.getTimeTravelersCategories(user, req.language),
};
res.respond(200, resObject);
},
};
/**
* @apiIgnore
* @api {get} /api/v3/shops/seasonal get the available items for the seasonal shop
* @apiVersion 3.0.0
* @apiName GetSeasonalShopItems
* @apiGroup Shops
*
* @apiSuccess {Object} data List of push devices
* @apiSuccess {string} message Success message
*/
api.getSeasonalShopItems = {
method: 'GET',
url: '/shops/seasonal',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
let resObject = {
identifier: 'seasonalShop',
text: res.t('seasonalShop'),
notes: res.t('seasonalShopSummerText'),
imageName: 'seasonalshop_open',
categories: shops.getSeasonalShopCategories(user, req.language),
};
res.respond(200, resObject);
},
};
module.exports = api;

View File

@@ -110,61 +110,19 @@
button.btn.btn-primary.btn-block(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text()}}", gold: "{{selectedFood.value}}"})
menu.inventory-list(type='list')
li.customize-menu
menu.pets-menu(label=env.t('eggs'))
p.muted!=env.t('dropsExplanation')
li.customize-menu(ng-repeat='category in marketShopCategories')
menu.pets-menu(label='{{category.text}}')
p.muted(ng-bind-html='category.notes')
div(ng-repeat='egg in Content.eggs', ng-if='egg.canBuy(user)')
button.customize-option(class='Pet_Egg_{{::egg.key}}',
popover='{{::egg.notes()}}', popover-append-to-body='true',
popover-title!=env.t("egg", {eggType: "{{::egg.text()}}"}),
div(ng-repeat='item in category.items')
button.customize-option(class='{{item.class}}',
popover='{{item.notes}}', popover-append-to-body='true',
popover-title!='{{item.text}}',
popover-trigger='mouseenter', popover-placement='top',
ng-click='purchase("eggs", egg)')
p {{::egg.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('hatchingPotions'))
p.muted!=env.t('dropsExplanation')
div(ng-repeat='pot in Content.hatchingPotions', ng-if='!pot.premium')
button.customize-option(class='Pet_HatchingPotion_{{::pot.key}}',
popover='{{::pot.notes()}}', popover-append-to-body='true',
popover-title!=env.t("potion", {potionType: "{{::pot.text()}}"}),
popover-trigger='mouseenter', popover-placement='top',
ng-click='purchase("hatchingPotions", pot)')
p
| {{::pot.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
// li.customize-menu
menu.pets-menu!=env.t('magicHatchingPotions') + " - " + env.t('springEventAvailability')
p.muted=env.t('premiumPotionNoDropExplanation')
div(ng-repeat='pot in Content.hatchingPotions', ng-if='pot.premium && pot.canBuy(user)')
button.customize-option(class='Pet_HatchingPotion_{{::pot.key}}',
popover='{{::pot.notes()}} {{::pot.addlNotes()}}', popover-append-to-body='true',
popover-title!=env.t("potion", {potionType: "{{::pot.text()}}"}),
popover-trigger='mouseenter', popover-placement='top',
ng-click='purchase("hatchingPotions", pot)')
p
| {{::pot.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('food'))
p.muted!=env.t('dropsExplanation')
div(ng-repeat='food in Content.food', ng-if='food.canBuy(user)')
button.customize-option(class='Pet_Food_{{::food.key}}',
popover='{{::food.notes()}}', popover-title='{{::food.text()}}',
popover-trigger='mouseenter', popover-placement='top',
popover-append-to-body='true',
ng-click='purchase("food", food)')
p
| {{::food.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('quests'))
p=env.t('whereAreMyQuests')
ng-click='purchase(category.identifier, item)')
p {{item.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems(ng-if='item.currency === "gems"')
span(class='shop_gold', ng-if='item.currency === "gold"')
li.customize-menu
menu.pets-menu(label=env.t('special'))

View File

@@ -18,20 +18,20 @@ include ../../shared/mixins
.col-md-6.border-left
li.customize-menu
h3.equipment-title=env.t('questsForSale')
div(ng-repeat='type in Content.userCanOwnQuestCategories')
menu.pets-menu(label='{{env.t(type + "Quests")}}')
div(ng-repeat='quest in Content.questsByLevel', ng-if='quest.canBuy(user) && quest.category === type')
button.customize-option(ng-class='lockQuest(quest) ? "inventory_quest_scroll_locked inventory_quest_scroll_{{::quest.key}}_locked locked" : "inventory_quest_scroll inventory_quest_scroll_{{::quest.key}}"',
data-popover-html="{{::lockQuest(quest,true) ? env.t('scrollsPre') : questPopover(quest) | markdown}}",
popover-title='{{::quest.text()}}', popover-append-to-body="true",
div(ng-repeat='category in questShopCategories')
menu.pets-menu(label='{{category.text}}')
div(ng-repeat='quest in category.items')
button.customize-option(ng-class='quest.class',
data-popover-html="{{quest.locked ? env.t('scrollsPre') : questPopover(quest) | markdown}}",
popover-title='{{quest.text}}', popover-append-to-body="true",
popover-trigger='mouseenter', ng-click='buyQuest(quest.key)')
p(ng-if='quest.unlockCondition')
| {{::quest.unlockCondition.text()}}
p(ng-if='!quest.unlockCondition && quest.category !== "gold" && !lockQuest(quest)')
p(ng-if='!quest.unlockCondition && category.identifier !== "gold" && !quest.locked')
| {{::quest.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
p(ng-if='quest.category === "gold" && !lockQuest(quest)')
| {{::quest.goldValue}}&nbsp;
p(ng-if='category.identifier === "gold" && !quest.locked')
| {{::quest.value}}&nbsp;
span.shop_gold
p(ng-if='quest.lvl && lockQuest(quest)')=env.t('level')
p(ng-if='quest.lvl && quest.locked')=env.t('level')
| {{::quest.lvl}}

View File

@@ -11,15 +11,6 @@
.well(ng-if='User.user.achievements.rebirths > 0')=env.t('seasonalShopRebirth')
li.customize-menu.inventory-gear
// menu.pets-menu(label=env.t('quests'))
div(ng-repeat='quest in ::getSeasonalShopQuests()')
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-append-to-body='true', popover-title='{{::quest.text()}}',
popover-trigger='mouseenter', popover-placement='right',
ng-click='buyQuest(quest.key)')
p {{::quest.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
menu.pets-menu(label=env.t('seasonalItems'))
div
button.customize-option(class='inventory_special_seafoam',
@@ -30,24 +21,12 @@
ng-click='purchase("special", Content.spells.special.seafoam)')
p {{::Content.spells.special.seafoam.value}}
span(class='shop_gold')
// div
button.customize-option(class='Pet_HatchingPotion_Peppermint',
popover='{{::Content.hatchingPotions.Peppermint.notes()}}',
popover-title!=env.t("potion", {potionType: "{{::Content.hatchingPotions.Peppermint.text()}}"}),
popover-trigger='mouseenter', popover-placement='right',
popover-append-to-body='true',
ng-click='purchase("hatchingPotions", Content.hatchingPotions.Peppermint)')
p {{::Content.hatchingPotions.Peppermint.value}}&nbsp;
span.Pet_Currency_Gem1x.inline-gems
// 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')
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"), summer2015Warrior:env.t("sunfishWarriorSet"), summer2015Mage:env.t("shipSoothsayerSet"), summer2015Healer:env.t("strappingSailorSet"), summer2015Rogue:env.t("reefRenegadeSet")}')
div(ng-repeat='item in ::getSeasonalShopArray(set)',
ng-class="{transparent: user.items.gear.owned[item.key] !== undefined}")
button.customize-option(class='shop_{{::item.key}}',
popover='{{::item.notes()}}', popover-title='{{::item.text()}}',
menu.pets-menu(label='{{category.text}}', ng-repeat='category in seasonalShopCategories')
div(ng-repeat='item in category.items',
ng-class="{transparent: user.items.gear.owned[item.key] !== undefined}",
ng-if='!user.items.gear.owned[item.key]')
button.customize-option(class='shop_{{item.key}}',
popover='{{item.notes}}', popover-title='{{item.text}}',
popover-trigger='mouseenter', popover-placement='right',
popover-append-to-body='true',
ng-click='purchase(item.type,item)')

View File

@@ -20,19 +20,10 @@
.row
.col-md-12
li.customize-menu.inventory-gear
each prepend, type in {pets:'Pet-', mounts:'Mount_Head_'}
menu.pets-menu(label=env.t('#{type}'), ng-if='!hasAllTimeTravelerItemsOfType("#{type}")')
div(ng-repeat='(item, text) in Content.timeTravelStable["#{type}"]', style='margin-top:0')
button.pet-button(class='#{prepend}{{::item}}', style='margin-top:0',
ng-if='!user.items["#{type}"][item]',
popover='{{::text()}}', popover-trigger='mouseenter',
popover-placement='right', popover-append-to-body='true',
ng-click='clickTimeTravelItem("#{type}",item)')
li.customize-menu.inventory-gear
menu.pets-menu(label='{{::set.text()}}', ng-repeat='set in Content.timeTravelerStore(user.items.gear.owned)')
div(ng-repeat='item in set.items')
button.customize-option(class='shop_{{::item.key}}',
popover='{{::item.notes()}}', popover-title='{{::item.text()}}',
menu.pets-menu(label='{{::category.text}}', ng-repeat='category in timeTravelersCategories')
div(ng-repeat='item in category.items', ng-if='category.identifier === "pets" || category.identifier === "mounts" ? !user.items[category.identifier][item.key] : !user.items.gear.owned[item.key]')
button.customize-option(class='{{item.class ? item.class : "shop_"+item.key}}',
popover='{{item.notes}}', popover-title='{{item.text}}',
popover-trigger='mouseenter', popover-placement='right',
popover-append-to-body='true',
ng-click='User.buyMysterySet({params:{key:set.key}})')
ng-click='category.purchaseAll ? User.buyMysterySet({params:{key:category.identifier}}) : clickTimeTravelItem(category.identifier,item.key)')