diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 3ce92d9b14..772015906b 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -911,7 +911,7 @@ export default { if (['base', 'beard', 'color', 'mustache'].includes(item.type)) { return { hair: { - [item.type]: item.option, + [item.type]: item.key, }, head: 'head_base_0', }; diff --git a/website/client/src/components/shops/customizations/index.vue b/website/client/src/components/shops/customizations/index.vue index d90fb58a40..32dfa4b202 100644 --- a/website/client/src/components/shops/customizations/index.vue +++ b/website/client/src/components/shops/customizations/index.vue @@ -64,7 +64,7 @@ > true, canOwn: ownsItem('back_special_bearTail'), }, cactusTail: { @@ -780,6 +781,7 @@ const back = { text: t('backCactusTailText'), notes: t('backCactusTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_cactusTail'), }, foxTail: { @@ -787,6 +789,7 @@ const back = { text: t('backFoxTailText'), notes: t('backFoxTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_foxTail'), }, lionTail: { @@ -794,6 +797,7 @@ const back = { text: t('backLionTailText'), notes: t('backLionTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_lionTail'), }, pandaTail: { @@ -801,6 +805,7 @@ const back = { text: t('backPandaTailText'), notes: t('backPandaTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_pandaTail'), }, pigTail: { @@ -808,6 +813,7 @@ const back = { text: t('backPigTailText'), notes: t('backPigTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_pigTail'), }, tigerTail: { @@ -815,6 +821,7 @@ const back = { text: t('backTigerTailText'), notes: t('backTigerTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_tigerTail'), }, wolfTail: { @@ -822,6 +829,7 @@ const back = { text: t('backWolfTailText'), notes: t('backWolfTailNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('back_special_wolfTail'), }, turkeyTailGilded: { @@ -1868,6 +1876,7 @@ const headAccessory = { text: t('headAccessoryBearEarsText'), notes: t('headAccessoryBearEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_bearEars'), }, cactusEars: { @@ -1875,6 +1884,7 @@ const headAccessory = { text: t('headAccessoryCactusEarsText'), notes: t('headAccessoryCactusEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_cactusEars'), }, foxEars: { @@ -1882,6 +1892,7 @@ const headAccessory = { text: t('headAccessoryFoxEarsText'), notes: t('headAccessoryFoxEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_foxEars'), }, lionEars: { @@ -1889,6 +1900,7 @@ const headAccessory = { text: t('headAccessoryLionEarsText'), notes: t('headAccessoryLionEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_lionEars'), }, pandaEars: { @@ -1896,6 +1908,7 @@ const headAccessory = { text: t('headAccessoryPandaEarsText'), notes: t('headAccessoryPandaEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_pandaEars'), }, pigEars: { @@ -1903,6 +1916,7 @@ const headAccessory = { text: t('headAccessoryPigEarsText'), notes: t('headAccessoryPigEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_pigEars'), }, tigerEars: { @@ -1910,6 +1924,7 @@ const headAccessory = { text: t('headAccessoryTigerEarsText'), notes: t('headAccessoryTigerEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_tigerEars'), }, wolfEars: { @@ -1917,6 +1932,7 @@ const headAccessory = { text: t('headAccessoryWolfEarsText'), notes: t('headAccessoryWolfEarsNotes'), value: 20, + canBuy: () => true, canOwn: ownsItem('headAccessory_special_wolfEars'), }, spring2016Rogue: { diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index a788601274..c9074cd1a6 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -56,7 +56,7 @@ function getDefaultGearProps (item, language) { }; } -export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en') { +export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en', matcher = null) { if (officialPinnedItems === undefined) { officialPinnedItems = getOfficialPinnedItems(user); // eslint-disable-line no-param-reassign } @@ -235,7 +235,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang case 'gear': // spread operator not available itemInfo = Object.assign(getDefaultGearProps(item, language), { - value: item.twoHanded || item.gearSet === 'animal' ? 2 : 1, + value: item.twoHanded ? 2 : 1, currency: 'gems', pinType: 'gear', }); @@ -379,71 +379,70 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang }; break; } - case 'hairColor': { + case 'haircolor': { itemInfo = { key: item.key, class: `hair hair_bangs_${user.preferences.hair.bangs}_${item.key}`, currency: 'gems', - option: item.key, + locked: false, + notes: '', path: `hair.color.${item.key}`, purchaseType: 'customization', + pinType: 'timeTravelersStable', + set: item.set, text: item.text(language), type: 'color', value: item.price, }; break; } - case 'hairBase': { + case 'hairbase': { itemInfo = { - key: `hair-base-${item.key}`, + key: item.key, class: `hair hair_base_${item.key}_${user.preferences.hair.color}`, currency: 'gems', - option: item.key, + locked: false, + notes: '', path: `hair.base.${item.key}`, + pinType: 'timeTravelersStable', purchaseType: 'customization', + set: item.set, text: item.text(language), type: 'base', value: item.price, }; break; } - case 'mustache': { + case 'hairmustache': { itemInfo = { - key: `mustache-${item.key}`, - class: `facial-hair hair_mustache_${item.key}_${user.preferences.hair.color}`, + key: item.key, + class: `hair hair_mustache_${item.key}_${user.preferences.hair.color}`, currency: 'gems', - option: item.key, + locked: false, + notes: '', path: `hair.mustache.${item.key}`, + pinType: 'timeTravelersStable', purchaseType: 'customization', + set: item.set, text: item.text(language), type: 'mustache', value: item.price, }; break; } - case 'beard': { - itemInfo = { - key: `beard-${item.key}`, - class: `facial-hair hair_beard_${item.key}_${user.preferences.hair.color}`, - currency: 'gems', - option: item.key, - path: `hair.beard.${item.key}`, - purchaseType: 'customization', - text: item.text(language), - type: 'beard', - value: item.price, - }; - break; - } - case 'skin': { + case 'hairbeard': { itemInfo = { key: item.key, - class: `skin skin_${item.key}`, + class: `hair hair_beard_${item.key}_${user.preferences.hair.color}`, currency: 'gems', - path: `skin.${item.key}`, + locked: false, + notes: '', + path: `hair.beard.${item.key}`, + pinType: 'timeTravelersStable', purchaseType: 'customization', + set: item.set, text: item.text(language), - type: 'skin', + type: 'beard', value: item.price, }; break; @@ -453,14 +452,35 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang key: item.key, class: `shirt ${user.preferences.size}_shirt_${item.key}`, currency: 'gems', + locked: false, + notes: '', path: `shirt.${item.key}`, + pinType: 'timeTravelersStable', purchaseType: 'customization', + set: item.set, text: item.text(language), type: 'shirt', value: item.price, }; break; } + case 'skin': { + itemInfo = { + key: item.key, + class: `skin skin_${item.key}`, + currency: 'gems', + locked: false, + path: `skin.${item.key}`, + notes: '', + pinType: 'timeTravelersStable', + purchaseType: 'customization', + set: item.set, + text: item.text(language), + type: 'skin', + value: item.price, + }; + break; + } } if (itemInfo) { @@ -470,5 +490,9 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang throw new BadRequest(i18n.t('wrongItemType', { type }, language)); } + if (matcher) { + itemInfo.end = { matcher }; + } + return itemInfo; } diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 0ca94d3159..d7d659c382 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -549,135 +549,105 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { return sets; }; +/* Customization Shop */ + +shops.getCustomizationsShop = function getCustomizationsShop (user, language) { + return { + identifier: 'customizationsShop', + text: i18n.t('titleCustomizations'), + notes: i18n.t('timeTravelersPopover'), + imageName: 'npc_timetravelers_active', + categories: shops.getCustomizationsShopCategories(user, language), + }; +}; + shops.getCustomizationsShopCategories = function getCustomizationsShopCategories (user, language) { const categories = []; - const officialPinnedItems = getOfficialPinnedItems(); + const officialPinnedItems = getOfficialPinnedItems(user); - const backgroundsCategory = { - identifier: 'background', + const backgroundCategory = { + identifier: 'backgrounds', text: i18n.t('backgrounds', language), + items: [], }; - backgroundsCategory.items = values(content.backgroundsFlat) - .filter(bg => !user.purchased.background[bg.key] && (!bg.currency || bg.currency === 'gems') - && !(bg.price === 0)) - .map(bg => getItemInfo(user, 'background', bg, officialPinnedItems, language)); - categories.push(backgroundsCategory); - const hairColorsCategory = { - identifier: 'hairColors', - text: i18n.t('hairColors', language), - }; - hairColorsCategory.items = values(content.appearances.hair.color) - .filter(color => { - const { hair } = user.purchased; - if (hair && hair.color && hair.color[color.key]) { - return false; - } - if (color.set) { - if (color.set.availableFrom) { - return moment().isBetween(color.set.availableFrom, color.set.availableUntil); + const matchers = getScheduleMatchingGroup('backgrounds'); + eachRight(content.backgrounds, (group, key) => { + if (matchers.match(key)) { + each(group, bg => { + if (!user.purchased.background[bg.key]) { + const item = getItemInfo( + user, + 'background', + bg, + officialPinnedItems, + language, + ); + backgroundCategory.items.push(item); } - if (color.set.availableUntil) { - return moment().isBefore(color.set.availableUntil); - } - return true; - } - return false; - }) - .map(color => getItemInfo(user, 'hairColor', color, officialPinnedItems, language)); - categories.push(hairColorsCategory); - - const hairStylesCategory = { - identifier: 'hairStyles', - text: i18n.t('hairStyles', language), - }; - hairStylesCategory.items = values(content.appearances.hair.base) - .filter(style => { - const { hair } = user.purchased; - if (hair && hair.base && hair.base[style.key]) { - return false; - } - if (style.set) { - if (style.set.availableFrom) { - return moment().isBetween(style.set.availableFrom, style.set.availableUntil); - } - if (style.set.availableUntil) { - return moment().isBefore(style.set.availableUntil); - } - return true; - } - return false; - }) - .map(style => getItemInfo(user, 'hairBase', style, officialPinnedItems, language)); - categories.push(hairStylesCategory); + }); + } + }); + categories.push(backgroundCategory); const facialHairCategory = { identifier: 'facialHair', - text: i18n.t('facialHairs', language), + text: i18n.t('titleFacialHair', language), + items: [], }; - facialHairCategory.items = values(content.appearances.hair.mustache) - .filter(style => { - const { hair } = user.purchased; - if (hair && hair.mustache && hair.mustache[style.key]) { - return false; + const customizationMatcher = getScheduleMatchingGroup('customizations'); + each(['color', 'base', 'mustache', 'beard'], hairType => { + let category; + if (hairType === 'beard' || hairType === 'mustache') { + category = facialHairCategory; + } else { + category = { + identifier: hairType, + text: i18n.t(`titleHair${hairType}`, language), + items: [], + }; + } + eachRight(content.appearances.hair[hairType], (hairStyle, key) => { + if (hairStyle.price > 0 && (!user.purchased.hair || !user.purchased.hair[hairType] + || !user.purchased.hair[hairType][key]) + && customizationMatcher.match(hairStyle.set.key)) { + const item = getItemInfo( + user, + `hair${hairType}`, + hairStyle, + officialPinnedItems, + language, + ); + category.items.push(item); } - if (style.set) { - if (style.set.availableFrom) { - return moment().isBetween(style.set.availableFrom, style.set.availableUntil); - } - if (style.set.availableUntil) { - return moment().isBefore(style.set.availableUntil); - } - return true; - } - return false; - }) - .map(style => getItemInfo(user, 'mustache', style, officialPinnedItems, language)) - .concat( - values(content.appearances.hair.beard) - .filter(style => { - const { hair } = user.purchased; - if (hair && hair.beard && hair.beard[style.key]) { - return false; - } - if (style.set) { - if (style.set.availableFrom) { - return moment().isBetween(style.set.availableFrom, style.set.availableUntil); - } - if (style.set.availableUntil) { - return moment().isBefore(style.set.availableUntil); - } - return true; - } - return false; - }) - .map(style => getItemInfo(user, 'beard', style, officialPinnedItems, language)), - ); - categories.push(facialHairCategory); + }); + // only add the facial hair category once + if (hairType !== 'beard') { + categories.push(category); + } + }); - const skinsCategory = { - identifier: 'skins', - text: i18n.t('skins', language), - }; - skinsCategory.items = values(content.appearances.skin) - .filter(color => { - const { skin } = user.purchased; - if (skin && skin[color.key]) { - return false; + each(['shirt', 'skin'], type => { + const category = { + identifier: type, + text: i18n.t(`${type}s`, language), + items: [], + }; + eachRight(content.appearances[type], (appearance, key) => { + if (appearance.price > 0 && (!user.purchased[type] || !user.purchased[type][key]) + && customizationMatcher.match(appearance.set.key)) { + const item = getItemInfo( + user, + type, + appearance, + officialPinnedItems, + language, + ); + category.items.push(item); } - if (color.set) { - if (color.set.availableFrom) { - return moment().isBetween(color.set.availableFrom, color.set.availableUntil); - } - if (color.set.availableUntil) { - return moment().isBefore(color.set.availableUntil); - } - return true; - } - return false; - }) - .map(color => getItemInfo(user, 'skin', color, officialPinnedItems, language)); - categories.push(skinsCategory); + }); + categories.push(category); + }); const animalEarsCategory = { identifier: 'animalEars', @@ -709,41 +679,7 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories .map(gearItem => getItemInfo(user, 'gear', gearItem, officialPinnedItems, language)); categories.push(animalTailsCategory); - const shirtsCategory = { - identifier: 'shirts', - text: i18n.t('shirts', language), - }; - shirtsCategory.items = values(content.appearances.shirt) - .filter(color => { - const { shirt } = user.purchased; - if (shirt && shirt[color.key]) { - return false; - } - if (color.set) { - if (color.set.availableFrom) { - return moment().isBetween(color.set.availableFrom, color.set.availableUntil); - } - if (color.set.availableUntil) { - return moment().isBefore(color.set.availableUntil); - } - return true; - } - return false; - }) - .map(color => getItemInfo(user, 'shirt', color, officialPinnedItems, language)); - categories.push(shirtsCategory); - return categories; }; -shops.getCustomizationsShop = function getCustomizationsShop (user, language) { - return { - identifier: 'customizations', - text: i18n.t('customizations'), - // notes: i18n.t('customizations'), - imageName: 'npc_alex', - categories: shops.getCustomizationsShopCategories(user, language), - }; -}; - export default shops; diff --git a/website/server/controllers/api-v3/shops.js b/website/server/controllers/api-v3/shops.js index 624101f502..0faa10bf12 100644 --- a/website/server/controllers/api-v3/shops.js +++ b/website/server/controllers/api-v3/shops.js @@ -150,7 +150,7 @@ api.getBackgroundShopItems = { * @apiName GetCustomizationShopItems * @apiGroup Shops * - * @apiSuccess {Object} data List of available backgrounds + * @apiSuccess {Object} data List of available avatar customizations * @apiSuccess {string} message Success message */ api.getCustomizationsShop = {