[WIP] new client - seasonal-shop (#9018)

* extract seasonal-shop config - use summer season items (to work on)

* add suggested border to shopItems

* refactor getOfficialPinnedItems (now includes the seasonal gear)

* refactor shops.getSeasonalShop - add featured items to result - add the set to special equipment items

* feat(content): Fall 2017 seasonal gear
Also adds set keys for all prior seasonal gear.

* show item limited time (buyModal & shopItem)

* select seasonal fall sets

* WIP(seasonal-shop): placeholder Fall 2017 items

* fix lint

* sprites

* styling + fix purchase of seasonal spells

* compile sprites

* fixes: check isPinned with officialItems

* enable purchase of seasonal items for testing

* fix shop apis

* add featuredItems to market

* quest shop: add featuredItems to api

* tiem travelers shop: add featuredItems to api

* fix gear types filter

* feat(content): Fall 2017 compleat

* chore(sprites): compile

* show opened shop state (npc+background)

* add opened seasonal npc

* current seasonal users class set = purchase by gold - lock other sets of the current season

* hide event badge in seasonal shop - dot only for suggested items - cursor: pointer on shopItems

* refresh rewards column list (seasonal gear won't refresh it on purchase)

* fix duplicate seasonal gear -> remove special items from the old reward gear (which is used to reset the pinned gears)

* every current season gear is purchased by gold - prevent buyModal on locked items

* use the current event date range

* list seasonal sets by event date

* use custom method instead of updateStore to list the pinnable gear

* change daterange to 10-31

* fix start quest modal from items - disable invite quest button if a quest is already active

* toggle pin in buy-dialogs

* check if the item is not undefined/null - renamed the watch function
This commit is contained in:
negue
2017-09-21 02:28:11 +02:00
committed by Keith Holliday
parent cd0222e208
commit 0a691ffb4f
122 changed files with 18533 additions and 17300 deletions
@@ -29,6 +29,35 @@ export const EVENTS = {
winter2017: { start: '2016-12-16', end: '2017-02-02' },
spring2017: { start: '2017-03-21', end: '2017-05-02' },
summer2017: { start: '2017-06-20', end: '2017-08-02' },
fall2017: { start: '2017-08-21', end: '2017-11-02' },
};
export const SEASONAL_SETS = {
fall: [
// fall 2014
'vampireSmiterSet',
'monsterOfScienceSet',
'witchyWizardSet',
'mummyMedicSet',
// fall 2015
'battleRogueSet',
'scarecrowWarriorSet',
'stitchWitchSet',
'potionerSet',
// fall 2016
'fall2016BlackWidowSet',
'fall2016SwampThingSet',
'fall2016WickedSorcererSet',
'fall2016GorgonHealerSet',
// fall 2017
'fall2017TrickOrTreatSet',
'fall2017HabitoweenSet',
'fall2017MasqueradeSet',
'fall2017HauntedHouseSet',
],
};
export const GEAR_TYPES = [
File diff suppressed because it is too large Load Diff
@@ -1 +1,4 @@
export default [];
// { path: '', type: '', canShow?: (user) => boolean }
export default [];
+11 -6
View File
@@ -4,6 +4,7 @@ import { BadRequest } from './errors';
import count from '../count';
import isPinned from './isPinned';
import getOfficialPinnedItems from './getOfficialPinnedItems';
import _mapValues from 'lodash/mapValues';
@@ -16,9 +17,7 @@ function lockQuest (quest, user) {
return quest.previous;
}
const officialPinnedItems = content.officialPinnedItems;
function isItemSuggested (itemInfo) {
function isItemSuggested (officialPinnedItems, itemInfo) {
return officialPinnedItems.findIndex(officialItem => {
return officialItem.type === itemInfo.pinType && officialItem.path === itemInfo.path;
}) > -1;
@@ -40,10 +39,16 @@ function getDefaultGearProps (item, language) {
per: item.per,
con: item.con,
klass: item.klass,
event: item.event,
set: item.set,
};
}
module.exports = function getItemInfo (user, type, item, language = 'en') {
module.exports = function getItemInfo (user, type, item, officialPinnedItems, language = 'en') {
if (officialPinnedItems === undefined) {
officialPinnedItems = getOfficialPinnedItems(user);
}
let itemInfo;
switch (type) {
@@ -150,7 +155,7 @@ module.exports = function getItemInfo (user, type, item, language = 'en') {
break;
case 'seasonalSpell':
itemInfo = {
key: item.keyspellKey,
key: item.key,
text: item.text(language),
notes: item.notes(language),
value: item.value,
@@ -267,7 +272,7 @@ module.exports = function getItemInfo (user, type, item, language = 'en') {
}
if (itemInfo) {
itemInfo.isSuggested = isItemSuggested(itemInfo);
itemInfo.isSuggested = isItemSuggested(officialPinnedItems, itemInfo);
itemInfo.pinned = isPinned(user, itemInfo);
} else {
throw new BadRequest(i18n.t('wrongItemType', {type}, language));
@@ -0,0 +1,28 @@
import content from '../content/index';
import SeasonalShopConfig from '../libs/shops-seasonal.config';
import toArray from 'lodash/toArray';
const officialPinnedItems = content.officialPinnedItems;
let flatGearArray = toArray(content.gear.flat);
module.exports = function getOfficialPinnedItems (user) {
let officialItemsArray = [...officialPinnedItems];
if (SeasonalShopConfig.pinnedSets && Boolean(user) && user.stats.class) {
let setToAdd = SeasonalShopConfig.pinnedSets[user.stats.class];
// pinnedSets == current seasonal class set are always gold purchaseable
flatGearArray.filter((gear) => {
return user.items.gear.owned[gear.key] === undefined && gear.set === setToAdd;
}).map((gear) => {
officialItemsArray.push({
type: 'marketGear',
path: `gear.flat.${gear.key}`,
});
});
}
return officialItemsArray;
};
+4 -2
View File
@@ -2,10 +2,12 @@ import content from '../content/index';
import get from 'lodash/get';
import getItemInfo from './getItemInfo';
import shops from './shops';
import getOfficialPinnedItems from './getOfficialPinnedItems';
const officialPinnedItems = content.officialPinnedItems;
module.exports = function getPinnedItems (user) {
let officialPinnedItems = getOfficialPinnedItems(user);
const officialPinnedItemsNotUnpinned = officialPinnedItems.filter(officialPin => {
const isUnpinned = user.unpinnedItems.findIndex(unpinned => unpinned.path === officialPin.path) > -1;
return !isUnpinned;
@@ -14,7 +16,7 @@ module.exports = function getPinnedItems (user) {
const pinnedItems = officialPinnedItemsNotUnpinned.concat(user.pinnedItems);
let items = pinnedItems.map(({type, path}) => {
return getItemInfo(user, type, get(content, path));
return getItemInfo(user, type, get(content, path), officialPinnedItems);
});
shops.checkMarketGearLocked(user, items);
+7 -2
View File
@@ -1,9 +1,14 @@
module.exports = function isPinned (user, item) {
module.exports = function isPinned (user, item, checkOfficialPinnedItems /* getOfficialPinnedItems */) {
if (user === null)
return false;
const isPinnedOfficial = checkOfficialPinnedItems !== undefined && checkOfficialPinnedItems.findIndex(pinned => pinned.path === item.path) > -1;
const isItemUnpinned = user.unpinnedItems !== undefined && user.unpinnedItems.findIndex(unpinned => unpinned.path === item.path) > -1;
const isItemPinned = user.pinnedItems !== undefined && user.pinnedItems.findIndex(pinned => pinned.path === item.path) > -1;
return isItemPinned && !isItemUnpinned;
if (isPinnedOfficial && !isItemUnpinned)
return true;
return isItemPinned;
};
@@ -0,0 +1,32 @@
import { SEASONAL_SETS} from '../content/constants';
module.exports = {
// opened: false,
opened: true,
// used for the seasonalShop.notes
// currentSeason: 'Closed',
currentSeason: 'Fall',
dateRange: { start: '2017-09-21', end: '2017-10-31' },
availableSets: [
...SEASONAL_SETS.fall,
],
pinnedSets: {
warrior: 'fall2017HabitoweenSet',
wizard: 'fall2017MasqueradeSet',
rogue: 'fall2017TrickOrTreatSet',
healer: 'fall2017HauntedHouseSet',
},
availableSpells: [
'spookySparkles',
],
availableQuests: [
],
featuredSet: 'battleRogueSet',
};
+131 -28
View File
@@ -1,6 +1,7 @@
import values from 'lodash/values';
import map from 'lodash/map';
import keys from 'lodash/keys';
import get from 'lodash/get';
import each from 'lodash/each';
import filter from 'lodash/filter';
import eachRight from 'lodash/eachRight';
@@ -11,10 +12,34 @@ import content from '../content/index';
import i18n from '../i18n';
import getItemInfo from './getItemInfo';
import updateStore from './updateStore';
import seasonalShopConfig from './shops-seasonal.config';
import featuredItems from '../content/shop-featuredItems';
import getOfficialPinnedItems from './getOfficialPinnedItems';
let shops = {};
/* Market */
shops.getMarketShop = function getMarketShop (user, language) {
return {
identifier: 'market',
text: i18n.t('market'),
notes: i18n.t('welcomeMarketMobile'),
imageName: 'npc_alex',
categories: shops.getMarketCategories(user, language),
featured: {
text: i18n.t('featuredItems'),
items: featuredItems.market.map(i => {
return getItemInfo(user, i.type, get(content, i.path));
}),
},
};
};
shops.getMarketCategories = function getMarket (user, language) {
let officialPinnedItems = getOfficialPinnedItems(user);
let categories = [];
let eggsCategory = {
identifier: 'eggs',
@@ -26,7 +51,7 @@ shops.getMarketCategories = function getMarket (user, language) {
.filter(egg => egg.canBuy(user))
.concat(values(content.dropEggs))
.map(egg => {
return getItemInfo(user, 'eggs', egg, language);
return getItemInfo(user, 'eggs', egg, officialPinnedItems, language);
}), 'key');
categories.push(eggsCategory);
@@ -38,7 +63,7 @@ shops.getMarketCategories = function getMarket (user, language) {
hatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
.filter(hp => !hp.limited)
.map(hatchingPotion => {
return getItemInfo(user, 'hatchingPotions', hatchingPotion, language);
return getItemInfo(user, 'hatchingPotions', hatchingPotion, officialPinnedItems, language);
}), 'key');
categories.push(hatchingPotionsCategory);
@@ -50,7 +75,7 @@ shops.getMarketCategories = function getMarket (user, language) {
premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
.filter(hp => hp.limited && hp.canBuy())
.map(premiumHatchingPotion => {
return getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, language);
return getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language);
}), 'key');
if (premiumHatchingPotionsCategory.items.length > 0) {
categories.push(premiumHatchingPotionsCategory);
@@ -64,7 +89,7 @@ shops.getMarketCategories = function getMarket (user, language) {
foodCategory.items = sortBy(values(content.food)
.filter(food => food.canDrop || food.key === 'Saddle')
.map(foodItem => {
return getItemInfo(user, 'food', foodItem, language);
return getItemInfo(user, 'food', foodItem, officialPinnedItems, language);
}), 'key');
categories.push(foodCategory);
@@ -99,6 +124,12 @@ shops.checkMarketGearLocked = function checkMarketGearLocked (user, items) {
gear.locked = false;
}
if (Boolean(gear.specialClass) && Boolean(gear.set)) {
let currentSet = gear.set === seasonalShopConfig.pinnedSets[gear.specialClass];
gear.locked = currentSet && user.stats.class !== gear.specialClass;
}
if (gear.canOwn) {
gear.locked = !gear.canOwn(user);
}
@@ -114,6 +145,7 @@ shops.checkMarketGearLocked = function checkMarketGearLocked (user, items) {
shops.getMarketGearCategories = function getMarketGear (user, language) {
let categories = [];
let officialPinnedItems = getOfficialPinnedItems(user);
for (let classType of content.classes) {
let category = {
@@ -123,7 +155,7 @@ shops.getMarketGearCategories = function getMarketGear (user, language) {
let result = filter(content.gear.flat, ['klass', classType]);
category.items = map(result, (e) => {
let newItem = getItemInfo(user, 'marketGear', e);
let newItem = getItemInfo(user, 'marketGear', e, officialPinnedItems);
return newItem;
});
@@ -136,9 +168,27 @@ shops.getMarketGearCategories = function getMarketGear (user, language) {
return categories;
};
/* Quests */
shops.getQuestShop = function getQuestShop (user, language) {
return {
identifier: 'questShop',
text: i18n.t('quests'),
notes: i18n.t('ianTextMobile'),
imageName: 'npc_ian',
categories: shops.getQuestShopCategories(user, language),
featured: {
text: i18n.t('featuredQuests'),
items: featuredItems.quests.map(i => {
return getItemInfo(user, i.type, get(content, i.path));
}),
},
};
};
shops.getQuestShopCategories = function getQuestShopCategories (user, language) {
let categories = [];
let officialPinnedItems = getOfficialPinnedItems(user);
/*
* ---------------------------------------------------------------
@@ -202,7 +252,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
bundleCategory.items = sortBy(values(content.bundles)
.filter(bundle => bundle.type === 'quests' && bundle.canBuy())
.map(bundle => {
return getItemInfo(user, 'bundles', bundle, language);
return getItemInfo(user, 'bundles', bundle, officialPinnedItems, language);
}));
if (bundleCategory.items.length > 0) {
@@ -218,7 +268,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
category.items = content.questsByLevel
.filter(quest => quest.canBuy(user) && quest.category === type)
.map(quest => {
return getItemInfo(user, 'quests', quest, language);
return getItemInfo(user, 'quests', quest, officialPinnedItems, language);
});
categories.push(category);
@@ -227,6 +277,21 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
return categories;
};
/* Time Travelers */
shops.getTimeTravelersShop = function getTimeTravelersShop (user, language) {
let hasTrinkets = user.purchased.plan.consecutive.trinkets > 0;
return {
identifier: 'timeTravelersShop',
text: i18n.t('timeTravelers'),
opened: hasTrinkets,
notes: hasTrinkets ? i18n.t('timeTravelersPopover') : i18n.t('timeTravelersPopoverNoSubMobile'),
imageName: hasTrinkets ? 'npc_timetravelers_active' : 'npc_timetravelers',
categories: shops.getTimeTravelersCategories(user, language),
};
};
shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, language) {
let categories = [];
let stable = {pets: 'Pet-', mounts: 'Mount_Icon_'};
@@ -298,24 +363,64 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
return categories;
};
/* Seasonal */
let flatGearArray = toArray(content.gear.flat);
shops.getSeasonalGearBySet = function getSeasonalGearBySet (user, set, officialPinnedItems, language, ignoreAlreadyOwned = false) {
return flatGearArray.filter((gear) => {
if (!ignoreAlreadyOwned && user.items.gear.owned[gear.key] !== undefined)
return false;
return gear.set === set;
}).map(gear => {
let currentSet = gear.set === seasonalShopConfig.pinnedSets[gear.specialClass];
// only the current season set can be purchased by gold
let itemInfo = getItemInfo(null, currentSet ? 'marketGear' : 'gear', gear, officialPinnedItems, language);
itemInfo.locked = currentSet && user.stats.class !== gear.specialClass;
return itemInfo;
});
};
shops.getSeasonalShop = function getSeasonalShop (user, language) {
let officialPinnedItems = getOfficialPinnedItems(user);
let resObject = {
identifier: 'seasonalShop',
text: i18n.t('seasonalShop'),
notes: i18n.t(`seasonalShop${seasonalShopConfig.currentSeason}Text`),
imageName: seasonalShopConfig.opened ? 'seasonalshop_open' : 'seasonalshop_closed',
opened: seasonalShopConfig.opened,
categories: this.getSeasonalShopCategories(user, language),
featured: {
text: i18n.t(seasonalShopConfig.featuredSet),
items: shops.getSeasonalGearBySet(user, seasonalShopConfig.featuredSet, officialPinnedItems, language, true),
},
};
return resObject;
};
// To switch seasons/available inventory, edit the AVAILABLE_SETS object to whatever should be sold.
// let AVAILABLE_SETS = {
// setKey: i18n.t('setTranslationString', language),
// };
shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) {
const AVAILABLE_SETS = {
};
let officialPinnedItems = getOfficialPinnedItems(user);
const AVAILABLE_SPELLS = [
...seasonalShopConfig.availableSpells,
];
const AVAILABLE_QUESTS = [
...seasonalShopConfig.availableQuests,
];
let categories = [];
let flatGearArray = toArray(content.gear.flat);
let spells = pickBy(content.spells.special, (spell, key) => {
return AVAILABLE_SPELLS.indexOf(key) !== -1;
});
@@ -327,7 +432,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
};
category.items = map(spells, (spell) => {
return getItemInfo(user, 'seasonalSpell', spell, language);
return getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language);
});
categories.push(category);
@@ -350,23 +455,20 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
categories.push(category);
}
for (let key in AVAILABLE_SETS) {
if (AVAILABLE_SETS.hasOwnProperty(key)) {
let category = {
identifier: key,
text: AVAILABLE_SETS[key],
};
for (let set of seasonalShopConfig.availableSets) {
let category = {
identifier: set,
text: i18n.t(set),
};
category.items = flatGearArray.filter((gear) => {
return user.items.gear.owned[gear.key] === undefined && gear.index === key;
}).map(gear => {
return getItemInfo(null, 'gear', gear, language);
});
category.items = shops.getSeasonalGearBySet(user, set, officialPinnedItems, language, false);
if (category.items.length > 0) {
category.specialClass = category.items[0].specialClass;
categories.push(category);
}
if (category.items.length > 0) {
let item = category.items[0];
category.specialClass = item.specialClass;
category.event = item.event;
categories.push(category);
}
}
@@ -375,6 +477,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
let sets = [];
let officialPinnedItems = getOfficialPinnedItems();
eachRight(content.backgrounds, (group, key) => {
let set = {
@@ -383,7 +486,7 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
};
set.items = map(group, (background) => {
return getItemInfo(null, 'background', background, language);
return getItemInfo(null, 'background', background, officialPinnedItems, language);
});
sets.push(set);
@@ -6,6 +6,7 @@ import reduce from 'lodash/reduce';
import content from '../content/index';
// Return the list of gear items available for purchase
// TODO: Remove updateStore once the new client is live
let sortOrder = reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;
+30 -7
View File
@@ -1,14 +1,35 @@
import content from '../content/index';
import getItemInfo from '../libs/getItemInfo';
import get from 'lodash/get';
import { BadRequest } from '../libs/errors';
import i18n from '../i18n';
import isPinned from '../libs/isPinned';
import getOfficialPinnedItems from '../libs/getOfficialPinnedItems';
const officialPinnedItems = content.officialPinnedItems;
import get from 'lodash/get';
import each from 'lodash/each';
import sortBy from 'lodash/sortBy';
import lodashFind from 'lodash/find';
import reduce from 'lodash/reduce';
let sortOrder = reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;
return accumulator;
}, {});
function selectGearToPin (user) {
let changes = [];
each(content.gearTypes, (type) => {
let found = lodashFind(content.gear.tree[type][user.stats.class], (item) => {
return !user.items.gear.owned[item.key];
});
if (found) changes.push(found);
});
return sortBy(changes, (change) => sortOrder[change.type]);
}
import updateStore from '../libs/updateStore';
function addPinnedGear (user, type, path) {
const foundIndex = user.pinnedItems.findIndex(pinnedItem => {
@@ -25,7 +46,7 @@ function addPinnedGear (user, type, path) {
function addPinnedGearByClass (user) {
if (user.flags.classSelected) {
let newPinnedItems = updateStore(user);
let newPinnedItems = selectGearToPin(user);
for (let item of newPinnedItems) {
let itemInfo = getItemInfo(user, 'marketGear', item);
@@ -50,7 +71,7 @@ function removeItemByPath (user, path) {
function removePinnedGearByClass (user) {
if (user.flags.classSelected) {
let currentPinnedItems = updateStore(user);
let currentPinnedItems = selectGearToPin(user);
for (let item of currentPinnedItems) {
let itemInfo = getItemInfo(user, 'marketGear', item);
@@ -61,7 +82,7 @@ function removePinnedGearByClass (user) {
}
function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey) {
let currentPinnedItems = updateStore(user);
let currentPinnedItems = selectGearToPin(user);
let removeAndAddAllItems = false;
for (let item of currentPinnedItems) {
@@ -103,6 +124,8 @@ function togglePinnedItem (user, {item, type, path}, req = {}) {
throw new BadRequest(i18n.t('cannotUnpinArmoirPotion', req.language));
}
let officialPinnedItems = getOfficialPinnedItems(user);
let isOfficialPinned = officialPinnedItems.find(officialPinnedItem => {
return officialPinnedItem.path === path;
}) !== undefined;