From 5a48436eff3dcc9740e7f0df9b2900838918a875 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Wed, 14 Feb 2024 17:49:26 -0600 Subject: [PATCH 001/244] WIP(shops): customizations route --- .../components/shops/customizations/index.vue | 71 +++++++++++++++++++ website/client/src/components/shops/index.vue | 14 ++-- website/client/src/router/index.js | 2 + website/common/locales/en/generic.json | 4 +- 4 files changed, 85 insertions(+), 6 deletions(-) create mode 100644 website/client/src/components/shops/customizations/index.vue diff --git a/website/client/src/components/shops/customizations/index.vue b/website/client/src/components/shops/customizations/index.vue new file mode 100644 index 0000000000..bacdd1ddf2 --- /dev/null +++ b/website/client/src/components/shops/customizations/index.vue @@ -0,0 +1,71 @@ + + + + + diff --git a/website/client/src/components/shops/index.vue b/website/client/src/components/shops/index.vue index d12a19c46e..b6d4e3386a 100644 --- a/website/client/src/components/shops/index.vue +++ b/website/client/src/components/shops/index.vue @@ -3,26 +3,32 @@ {{ $t('market') }} {{ $t('quests') }} + {{ $t('customizations') }} + + {{ $t('titleSeasonalShop') }} {{ $t('titleTimeTravelers') }} diff --git a/website/client/src/router/index.js b/website/client/src/router/index.js index f3e87d4584..5e51cff9fc 100644 --- a/website/client/src/router/index.js +++ b/website/client/src/router/index.js @@ -60,6 +60,7 @@ const ChallengeDetail = () => import(/* webpackChunkName: "challenges" */ '@/com const ShopsContainer = () => import(/* webpackChunkName: "shops" */'@/components/shops/index'); const MarketPage = () => import(/* webpackChunkName: "shops-market" */'@/components/shops/market/index'); const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/components/shops/quests/index'); +const CustomizationsPage = () => import(/* webpackChunkName: "shops-customizations" */'@/components/shops/customizations/index'); const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index'); const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index'); @@ -111,6 +112,7 @@ const router = new VueRouter({ children: [ { name: 'market', path: 'market', component: MarketPage }, { name: 'quests', path: 'quests', component: QuestsPage }, + { name: 'customizations', path: 'customizations', component: CustomizationsPage }, { name: 'seasonal', path: 'seasonal', component: SeasonalPage }, { name: 'time', path: 'time', component: TimeTravelersPage }, ], diff --git a/website/common/locales/en/generic.json b/website/common/locales/en/generic.json index bfaa238727..3c06a7a895 100644 --- a/website/common/locales/en/generic.json +++ b/website/common/locales/en/generic.json @@ -235,8 +235,8 @@ "questionDescriptionText": "It's okay to ask your questions in your primary language if you aren't comfortable speaking in English.", "questionPlaceholder": "Ask your question here", "submitQuestion": "Submit Question", - "reportPlayer": "Report Player", "whyReportingPlayer": "Why are you reporting this player?", "whyReportingPlayerPlaceholder": "Reason for report", - "playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica’s Community Guidelines." + "playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica’s Community Guidelines.", + "customizations": "Customizations" } From 63e7ace6938c23133157c9324991d9409fec0e7e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 15 Feb 2024 17:51:14 -0600 Subject: [PATCH 002/244] WIP(shops): customization categories skeleton --- website/client/src/components/header/menu.vue | 6 ++ .../components/shops/customizations/index.vue | 74 ++++++++++++++++++- .../components/shops/timeTravelers/index.vue | 3 +- website/common/locales/en/character.json | 7 +- website/common/locales/en/generic.json | 3 +- website/common/script/libs/shops.js | 68 +++++++++++++++++ 6 files changed, 154 insertions(+), 7 deletions(-) diff --git a/website/client/src/components/header/menu.vue b/website/client/src/components/header/menu.vue index 900cf0ec93..fb9db1f1bf 100644 --- a/website/client/src/components/header/menu.vue +++ b/website/client/src/components/header/menu.vue @@ -134,6 +134,12 @@ > {{ $t('quests') }} + + {{ $t('customizations') }} +
- + + +
@@ -16,11 +24,39 @@ >
+
+

+ {{ $t('customizations') }} +

+
+

+ {{ category.text }} +

+
+ {{ item.text }} +
+
+
diff --git a/website/client/src/components/ui/itemRows.vue b/website/client/src/components/ui/itemRows.vue index f034ad6365..498fadff2e 100644 --- a/website/client/src/components/ui/itemRows.vue +++ b/website/client/src/components/ui/itemRows.vue @@ -69,6 +69,9 @@ export default { noItemsLabel: { type: String, }, + maxItemsPerRow: { + type: Number, + }, }, data () { return { @@ -80,6 +83,9 @@ export default { }, computed: { itemsPerRow () { + if (this.maxItemsPerRow > 0) { + return this.maxItemsPerRow; + } return Math.floor(this.currentWidth / (this.itemWidth + this.itemMargin)); }, }, diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 7cdb94ac24..9154c9466e 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -559,42 +559,49 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories const hairColorsCategory = { identifier: 'hairColors', text: i18n.t('hairColors', language), + items: [], }; categories.push(hairColorsCategory); const hairStylesCategory = { identifier: 'hairStyles', text: i18n.t('hairStyles', language), + items: [], }; categories.push(hairStylesCategory); const skinsCategory = { identifier: 'skins', text: i18n.t('skins', language), + items: [], }; categories.push(skinsCategory); const animalEarsCategory = { identifier: 'animalEars', text: i18n.t('animalEars', language), + items: [], }; categories.push(animalEarsCategory); const animalTailsCategory = { identifier: 'animalTails', text: i18n.t('animalTails', language), + items: [], }; categories.push(animalTailsCategory); const shirtsCategory = { identifier: 'shirts', text: i18n.t('shirts', language), + items: [], }; categories.push(shirtsCategory); const facialHairCategory = { identifier: 'facialHair', text: i18n.t('facialHairs', language), + items: [], }; categories.push(facialHairCategory); From 09ff3ee865204c89309e4859d93eb034f642c5af Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 20 Feb 2024 17:56:56 -0600 Subject: [PATCH 004/244] WIP(customizations): load hair colors --- .../components/shops/customizations/index.vue | 3 ++- .../client/src/components/shops/shopItem.vue | 5 ++++ website/common/script/libs/getItemInfo.js | 9 +++++++ website/common/script/libs/shops.js | 25 ++++++++++++++++--- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/website/client/src/components/shops/customizations/index.vue b/website/client/src/components/shops/customizations/index.vue index ecc05a689f..473b8a1c0d 100644 --- a/website/client/src/components/shops/customizations/index.vue +++ b/website/client/src/components/shops/customizations/index.vue @@ -66,6 +66,7 @@ :price="ctx.item.value" :price-type="ctx.item.currency" :empty-item="false" + :show-popover="Boolean(ctx.item.text)" /> @@ -127,7 +128,7 @@ export default { return Object.values(this.viewOptions).some(g => g.selected); }, imageURLs () { - const currentEvent = this.currentEventList.find(event => Boolean(event.season)); + const currentEvent = this.currentEventList?.find(event => Boolean(event.season)); if (!currentEvent) { return { background: 'url(/static/npc/normal/market_background.png)', diff --git a/website/client/src/components/shops/shopItem.vue b/website/client/src/components/shops/shopItem.vue index f6aeaeb9fb..3be2a2a672 100644 --- a/website/client/src/components/shops/shopItem.vue +++ b/website/client/src/components/shops/shopItem.vue @@ -142,6 +142,11 @@ &.locked .price { opacity: 0.5; } + + .hair { + height: 68px; + margin-left: -24px; + } } .image { diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index 59df678a01..a3b66b07c9 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -379,6 +379,15 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang }; break; } + case 'hairColor': { + itemInfo = { + key: item.key, + class: `hair hair_bangs_${user.preferences.hair.bangs}_${item.key}`, + value: item.price, + currency: 'gems', + }; + break; + } } if (itemInfo) { diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 9154c9466e..f445695ec9 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -1,3 +1,4 @@ +import moment from 'moment'; import values from 'lodash/values'; import map from 'lodash/map'; import keys from 'lodash/keys'; @@ -546,21 +547,39 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { shops.getCustomizationsShopCategories = function getCustomizationsShopCategories (user, language) { const categories = []; const officialPinnedItems = getOfficialPinnedItems(); + const backgroundsCategory = { identifier: 'backgrounds', text: i18n.t('backgrounds', language), }; - backgroundsCategory.items = values(content.backgroundsFlat) - .filter(bg => !user.purchased.background[bg.key] && (!bg.currency || bg.currency === 'gems')) + .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), - items: [], }; + 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); + } + if (color.set.availableUntil) { + return moment().isBefore(color.set.availableTo); + } + return true; + } + return false; + }) + .map(color => getItemInfo(user, 'hairColor', color, officialPinnedItems, language)); categories.push(hairColorsCategory); const hairStylesCategory = { From 33b54a734ed1a8b99715820c6dad252754ef8b23 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 22 Feb 2024 17:03:07 -0600 Subject: [PATCH 005/244] WIP(shop): more CSS, add hair styles --- website/client/src/assets/scss/button.scss | 4 +- .../components/shops/customizations/index.vue | 8 +++ website/common/script/libs/getItemInfo.js | 27 +++++++ website/common/script/libs/shops.js | 72 ++++++++++++++++--- 4 files changed, 99 insertions(+), 12 deletions(-) diff --git a/website/client/src/assets/scss/button.scss b/website/client/src/assets/scss/button.scss index c45f9a8396..f61e0b495a 100644 --- a/website/client/src/assets/scss/button.scss +++ b/website/client/src/assets/scss/button.scss @@ -217,9 +217,7 @@ .btn-show-more { display: block; - width: 50%; - max-width: 448px; - margin: 0 auto; + width: 100%; margin-top: 12px; padding: 8px; font-size: 14px; diff --git a/website/client/src/components/shops/customizations/index.vue b/website/client/src/components/shops/customizations/index.vue index 473b8a1c0d..45f54fae95 100644 --- a/website/client/src/components/shops/customizations/index.vue +++ b/website/client/src/components/shops/customizations/index.vue @@ -87,6 +87,14 @@ height: 216px; } + .item-rows { + max-width: 920px; + + .items > div:nth-of-type(8n) { + margin-right: 0px; + } + } + .npc { background-repeat: no-repeat; } diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index a3b66b07c9..586c700eb7 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -388,6 +388,33 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang }; break; } + case 'hairBase': { + itemInfo = { + key: `hair-base-${item.key}`, + class: `hair hair_base_${item.key}_${user.preferences.hair.color}`, + value: item.price, + currency: 'gems', + }; + break; + } + case 'mustache': { + itemInfo = { + key: `mustache-${item.key}`, + class: `hair hair_mustache_${item.key}_${user.preferences.hair.color}`, + value: item.price, + currency: 'gems', + }; + break; + } + case 'beard': { + itemInfo = { + key: `beard-${item.key}`, + class: `hair hair_beard_${item.key}_${user.preferences.hair.color}`, + value: item.price, + currency: 'gems', + }; + break; + } } if (itemInfo) { diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index f445695ec9..f5b91cd752 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -573,7 +573,7 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories return moment().isBetween(color.set.availableFrom, color.set.availableUntil); } if (color.set.availableUntil) { - return moment().isBefore(color.set.availableTo); + return moment().isBefore(color.set.availableUntil); } return true; } @@ -585,10 +585,71 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories const hairStylesCategory = { identifier: 'hairStyles', text: i18n.t('hairStyles', language), - items: [], }; + 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); + const facialHairCategory = { + identifier: 'facialHair', + text: i18n.t('facialHairs', language), + }; + facialHairCategory.items = values(content.appearances.hair.mustache) + .filter(style => { + const { hair } = user.purchased; + if (hair && hair.mustache && hair.mustache[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, '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); + const skinsCategory = { identifier: 'skins', text: i18n.t('skins', language), @@ -617,13 +678,6 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories }; categories.push(shirtsCategory); - const facialHairCategory = { - identifier: 'facialHair', - text: i18n.t('facialHairs', language), - items: [], - }; - categories.push(facialHairCategory); - return categories; }; From 28fef8df8622c14b70770be4006a1704ddb359b1 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 27 Feb 2024 15:17:10 -0600 Subject: [PATCH 006/244] WIP(shops): shirts vs skins --- .../client/src/components/shops/shopItem.vue | 15 +++++++- website/common/script/libs/getItemInfo.js | 22 ++++++++++- website/common/script/libs/shops.js | 38 ++++++++++++++++++- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/website/client/src/components/shops/shopItem.vue b/website/client/src/components/shops/shopItem.vue index 3be2a2a672..f2c1d3734d 100644 --- a/website/client/src/components/shops/shopItem.vue +++ b/website/client/src/components/shops/shopItem.vue @@ -143,9 +143,20 @@ opacity: 0.5; } - .hair { + .hair, .facial-hair, .shirt, .skin { height: 68px; - margin-left: -24px; + } + + .hair { + background-position: -24px -2px; + } + + .facial-hair, .skin { + background-position: -24px -10px; + } + + .shirt { + background-position: -23px -32px; } } diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index 586c700eb7..2f1cf56fe7 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -400,7 +400,7 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang case 'mustache': { itemInfo = { key: `mustache-${item.key}`, - class: `hair hair_mustache_${item.key}_${user.preferences.hair.color}`, + class: `facial-hair hair_mustache_${item.key}_${user.preferences.hair.color}`, value: item.price, currency: 'gems', }; @@ -409,7 +409,25 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang case 'beard': { itemInfo = { key: `beard-${item.key}`, - class: `hair hair_beard_${item.key}_${user.preferences.hair.color}`, + class: `facial-hair hair_beard_${item.key}_${user.preferences.hair.color}`, + value: item.price, + currency: 'gems', + }; + break; + } + case 'skin': { + itemInfo = { + key: item.key, + class: `skin skin_${item.key}`, + value: item.price, + currency: 'gems', + }; + break; + } + case 'shirt': { + itemInfo = { + key: item.key, + class: `shirt ${user.preferences.size}_shirt_${item.key}`, value: item.price, currency: 'gems', }; diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index f5b91cd752..caf951ee96 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -653,8 +653,25 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories const skinsCategory = { identifier: 'skins', text: i18n.t('skins', language), - items: [], }; + skinsCategory.items = values(content.appearances.skin) + .filter(color => { + const { skin } = user.purchased; + if (skin && skin[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, 'skin', color, officialPinnedItems, language)); categories.push(skinsCategory); const animalEarsCategory = { @@ -674,8 +691,25 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories const shirtsCategory = { identifier: 'shirts', text: i18n.t('shirts', language), - items: [], }; + 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; From ecc8a65d28fda94a193b826ad15036138c4b0d82 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Thu, 29 Feb 2024 15:59:31 -0600 Subject: [PATCH 007/244] WIP(customizations): animal bits --- website/common/script/libs/getItemInfo.js | 2 +- website/common/script/libs/shops.js | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index 2f1cf56fe7..5acb5e9905 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -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 ? 2 : 1, + value: item.twoHanded || item.gearSet === 'animal' ? 2 : 1, currency: 'gems', pinType: 'gear', }); diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index caf951ee96..c82138a3d0 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -677,15 +677,31 @@ shops.getCustomizationsShopCategories = function getCustomizationsShopCategories const animalEarsCategory = { identifier: 'animalEars', text: i18n.t('animalEars', language), - items: [], }; + animalEarsCategory.items = values(content.gear.tree.headAccessory.special) + .filter(gearItem => { + const { owned } = user.items.gear; + if (typeof owned[gearItem.key] !== 'undefined') { + return false; + } + return gearItem.gearSet === 'animal'; + }) + .map(gearItem => getItemInfo(user, 'gear', gearItem, officialPinnedItems, language)); categories.push(animalEarsCategory); const animalTailsCategory = { identifier: 'animalTails', text: i18n.t('animalTails', language), - items: [], }; + animalTailsCategory.items = values(content.gear.tree.back.special) + .filter(gearItem => { + const { owned } = user.items.gear; + if (typeof owned[gearItem.key] !== 'undefined') { + return false; + } + return gearItem.gearSet === 'animal'; + }) + .map(gearItem => getItemInfo(user, 'gear', gearItem, officialPinnedItems, language)); categories.push(animalTailsCategory); const shirtsCategory = { From 0aa9d4d1d53eec50d9a6962cea7f8c5741a6c398 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 11 Mar 2024 16:09:16 -0500 Subject: [PATCH 008/244] WIP(shops): start wiring up buy modals --- website/client/src/assets/scss/button.scss | 3 +-- .../src/components/avatarModal/body-settings.vue | 4 ++-- .../components/avatarModal/customize-options.vue | 4 ++-- .../components/avatarModal/extra-settings.vue | 4 ++-- .../src/components/avatarModal/hair-settings.vue | 4 ++-- .../src/components/avatarModal/skin-settings.vue | 4 ++-- website/client/src/components/creatorIntro.vue | 4 ++-- website/client/src/components/shops/buyModal.vue | 16 +++++----------- .../components/shops/customizations/index.vue | 7 +++++-- website/client/src/components/shops/shopItem.vue | 2 ++ website/client/src/components/ui/itemRows.vue | 8 -------- .../client/src/components/ui/showMoreButton.vue | 2 +- website/client/src/mixins/avatarEditUtilities.js | 2 +- website/common/script/libs/shops.js | 2 +- 14 files changed, 28 insertions(+), 38 deletions(-) diff --git a/website/client/src/assets/scss/button.scss b/website/client/src/assets/scss/button.scss index f61e0b495a..62eb95422f 100644 --- a/website/client/src/assets/scss/button.scss +++ b/website/client/src/assets/scss/button.scss @@ -218,13 +218,12 @@ .btn-show-more { display: block; width: 100%; - margin-top: 12px; padding: 8px; font-size: 14px; line-height: 1.43; font-weight: bold; text-align: center; - background: $gray-600; + background: $gray-500; color: $gray-200 !important; // Otherwise it gets ignored when used on an A element box-shadow: none; diff --git a/website/client/src/components/avatarModal/body-settings.vue b/website/client/src/components/avatarModal/body-settings.vue index 1d908c3b64..b69118c890 100644 --- a/website/client/src/components/avatarModal/body-settings.vue +++ b/website/client/src/components/avatarModal/body-settings.vue @@ -35,7 +35,7 @@ import appearance from '@/../../common/script/content/appearance'; import { subPageMixin } from '../../mixins/subPage'; import { userStateMixin } from '../../mixins/userState'; -import { avatarEditorUtilies } from '../../mixins/avatarEditUtilities'; +import { avatarEditorUtilities } from '../../mixins/avatarEditUtilities'; import subMenu from './sub-menu'; import customizeOptions from './customize-options'; import gem from '@/assets/svg/gem.svg'; @@ -51,7 +51,7 @@ export default { mixins: [ subPageMixin, userStateMixin, - avatarEditorUtilies, + avatarEditorUtilities, ], props: [ 'editing', diff --git a/website/client/src/components/avatarModal/customize-options.vue b/website/client/src/components/avatarModal/customize-options.vue index 3d155dbc98..338ef86264 100644 --- a/website/client/src/components/avatarModal/customize-options.vue +++ b/website/client/src/components/avatarModal/customize-options.vue @@ -67,11 +67,11 @@ diff --git a/website/client/src/components/shops/shopItem.vue b/website/client/src/components/shops/shopItem.vue index f2c1d3734d..22e6d78a08 100644 --- a/website/client/src/components/shops/shopItem.vue +++ b/website/client/src/components/shops/shopItem.vue @@ -193,6 +193,7 @@ font-size: 12px; font-weight: bold; line-height: 1.33; + margin-bottom: 1px; &.gems { color: $green-1; @@ -367,6 +368,7 @@ export default { this.$emit('click', {}); }, blur () { + if (!this.$refs?.popover) return; this.$refs.popover.$emit('enable'); }, getPrice () { diff --git a/website/client/src/components/ui/itemRows.vue b/website/client/src/components/ui/itemRows.vue index 498fadff2e..0dae56b50e 100644 --- a/website/client/src/components/ui/itemRows.vue +++ b/website/client/src/components/ui/itemRows.vue @@ -22,20 +22,12 @@ :show-all="showAll" @click="toggleItemsToShow()" /> -
diff --git a/website/common/locales/en/character.json b/website/common/locales/en/character.json index 943202757c..83c9ec78cc 100644 --- a/website/common/locales/en/character.json +++ b/website/common/locales/en/character.json @@ -2,7 +2,7 @@ "communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the Community Guidelines (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email <%= hrefBlankCommunityManagerEmail %>!", "profile": "Profile", "avatar": "Customize Avatar", - "editAvatar": "Edit Avatar", + "editAvatar": "Customize Avatar", "noDescription": "This Habitican hasn't added a description.", "noPhoto": "This Habitican hasn't added a photo.", "other": "Other", From 39252c7828d210b571d53d15ceee1f9b7a8c7785 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 25 Jan 2024 16:16:49 +0100 Subject: [PATCH 011/244] allow clients to filter content api call --- website/server/controllers/api-v3/content.js | 69 +++++++++++++++++++- website/server/libs/content.js | 20 ++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index 76152c4e40..33bb64328e 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -1,4 +1,5 @@ import nconf from 'nconf'; +import fs from 'fs'; import { langCodes } from '../../libs/i18n'; import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content'; @@ -6,6 +7,28 @@ const IS_PROD = nconf.get('IS_PROD'); const api = {}; +const CACHED_HASHES = [ + +]; + +const MOBILE_FILTER = `achievements,questSeriesAchievements,animalColorAchievements,animalSetAchievements,stableAchievements, +mystery,bundles,loginIncentives,pets,premiumPets,specialPets,questPets,wackyPets,mounts,premiumMounts,specialMounts,questMounts, +events,dropEggs,questEggs,dropHatchingPotions,premiumHatchingPotions,wackyHatchingPotions,backgroundsFlat,questsByLevel,gear.tree, +tasksByCategory,userDefaults,timeTravelStable,gearTypes,cardTypes`; + +function hashForFilter (filter) { + let hash = 0; + let i; let + chr; + if (filter.length === 0) return ''; + for (i = 0; i < filter.length; i++) { // eslint-disable-line + chr = filter.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; // eslint-disable-line + hash |= 0; // eslint-disable-line + } + return String(hash); +} + /** * @api {get} /api/v3/content Get all available content objects * @apiDescription Does not require authentication. @@ -65,14 +88,54 @@ api.getContent = { language = proposedLang; } + let filter = req.query.filter || ''; + // apply defaults for mobile clients + if (filter === '') { + if (req.headers['x-client'] === 'habitica-android') { + filter = `${MOBILE_FILTER},appearance.background`; + } else if (req.headers['x-client'] === 'habitica-ios') { + filter = `${MOBILE_FILTER},backgrounds`; + } + } + + // Build usable filter object + const filterObj = {}; + filter.split(',').forEach(item => { + if (item.includes('.')) { + const [key, subkey] = item.split('.'); + if (!filterObj[key]) { + filterObj[key] = {}; + } + filterObj[key][subkey.trim()] = true; + } else { + filterObj[item.trim()] = true; + } + }); + if (IS_PROD) { - res.sendFile(`${CONTENT_CACHE_PATH}${language}.json`); + const filterHash = language + hashForFilter(filter); + if (CACHED_HASHES.includes(filterHash)) { + // Content is already cached, so just send it. + res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`); + } else { + // Content is not cached, so cache it and send it. + res.set({ + 'Content-Type': 'application/json', + }); + const jsonResString = getLocalizedContentResponse(language, filterObj); + fs.writeFileSync( + `${CONTENT_CACHE_PATH}${filterHash}.json`, + jsonResString, + 'utf8', + ); + CACHED_HASHES.push(filterHash); + res.status(200).send(jsonResString); + } } else { res.set({ 'Content-Type': 'application/json', }); - - const jsonResString = getLocalizedContentResponse(language); + const jsonResString = getLocalizedContentResponse(language, filterObj); res.status(200).send(jsonResString); } }, diff --git a/website/server/libs/content.js b/website/server/libs/content.js index 8328eb0ccb..378f7a09a6 100644 --- a/website/server/libs/content.js +++ b/website/server/libs/content.js @@ -5,23 +5,31 @@ import packageInfo from '../../../package.json'; export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/'); -function walkContent (obj, lang) { +function walkContent (obj, lang, removedKeys = {}) { _.each(obj, (item, key, source) => { + if (key in removedKeys && removedKeys[key] === true) { + delete source[key]; + return; + } if (_.isPlainObject(item) || _.isArray(item)) { - walkContent(item, lang); + if (key in removedKeys && _.isPlainObject(removedKeys[key])) { + walkContent(item, lang, removedKeys[key]); + } else { + walkContent(item, lang); + } } else if (_.isFunction(item) && item.i18nLangFunc) { source[key] = item(lang); } }); } -export function localizeContentData (data, langCode) { +export function localizeContentData (data, langCode, removedKeys = {}) { const dataClone = _.cloneDeep(data); - walkContent(dataClone, langCode); + walkContent(dataClone, langCode, removedKeys); return dataClone; } -export function getLocalizedContentResponse (langCode) { - const localizedContent = localizeContentData(common.content, langCode); +export function getLocalizedContentResponse (langCode, removedKeys = {}) { + const localizedContent = localizeContentData(common.content, langCode, removedKeys); return `{"success": true, "data": ${JSON.stringify(localizedContent)}, "appVersion": "${packageInfo.version}"}`; } From ec0275e6f6e09e67a1d1f9ca50a03b7c091b6189 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Fri, 26 Jan 2024 14:58:21 +0100 Subject: [PATCH 012/244] Add tests for content filtering --- test/api/unit/libs/content.test.js | 87 +++++++++++++++++++ .../integration/content/GET-content.test.js | 34 ++++++++ test/helpers/api-unit.helper.js | 1 + website/server/controllers/api-v3/content.js | 63 +------------- website/server/libs/content.js | 61 +++++++++++++ 5 files changed, 186 insertions(+), 60 deletions(-) diff --git a/test/api/unit/libs/content.test.js b/test/api/unit/libs/content.test.js index 368d8fe9fd..36a626151f 100644 --- a/test/api/unit/libs/content.test.js +++ b/test/api/unit/libs/content.test.js @@ -1,5 +1,9 @@ +import fs from 'fs'; import * as contentLib from '../../../../website/server/libs/content'; import content from '../../../../website/common/script/content'; +import { + generateRes, +} from '../../../helpers/api-unit.helper'; describe('contentLib', () => { describe('CONTENT_CACHE_PATH', () => { @@ -13,5 +17,88 @@ describe('contentLib', () => { contentLib.getLocalizedContentResponse(); expect(typeof content.backgrounds.backgrounds062014.beach.text).to.equal('function'); }); + + it('removes keys from the content data', () => { + const response = contentLib.localizeContentData(content, 'en', { backgroundsFlat: true, dropHatchingPotions: true }); + expect(response.backgroundsFlat).to.not.exist; + expect(response.backgrounds).to.exist; + expect(response.dropHatchingPotions).to.not.exist; + expect(response.hatchingPotions).to.exist; + }); + + it('removes nested keys from the content data', () => { + const response = contentLib.localizeContentData(content, 'en', { gear: { tree: true } }); + expect(response.gear.tree).to.not.exist; + expect(response.gear.flat).to.exist; + }); + }); + + it('generates a hash for a filter', () => { + const hash = contentLib.hashForFilter('backgroundsFlat,gear.flat'); + expect(hash).to.equal('-1791877526'); + }); + + it('serves content', () => { + const resSpy = generateRes(); + contentLib.serveContent(resSpy, 'en', '', false); + expect(resSpy.send).to.have.been.calledOnce; + }); + + it('serves filtered content', () => { + const resSpy = generateRes(); + contentLib.serveContent(resSpy, 'en', 'backgroundsFlat,gear.flat', false); + expect(resSpy.send).to.have.been.calledOnce; + }); + + describe('caches content', async () => { + let resSpy; + beforeEach(() => { + resSpy = generateRes(); + fs.rmdirSync(contentLib.CONTENT_CACHE_PATH, { recursive: true }); + fs.mkdirSync(contentLib.CONTENT_CACHE_PATH); + }); + + it('does not cache requests in development mode', async () => { + contentLib.serveContent(resSpy, 'en', '', false); + expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false; + }); + + it('caches unfiltered requests', async () => { + expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.false; + contentLib.serveContent(resSpy, 'en', '', true); + expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en.json`)).to.be.true; + }); + + it('serves cached requests', async () => { + fs.writeFileSync( + `${contentLib.CONTENT_CACHE_PATH}en.json`, + '{"success": true, "data": {"all": {}}}', + 'utf8', + ); + contentLib.serveContent(resSpy, 'en', '', true); + expect(resSpy.sendFile).to.have.been.calledOnce; + expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en.json`); + }); + + it('caches filtered requests', async () => { + const filter = 'backgroundsFlat,gear.flat'; + const hash = contentLib.hashForFilter(filter); + expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.false; + contentLib.serveContent(resSpy, 'en', filter, true); + expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.true; + }); + + it('serves filtered cached requests', async () => { + const filter = 'backgroundsFlat,gear.flat'; + const hash = contentLib.hashForFilter(filter); + fs.writeFileSync( + `${contentLib.CONTENT_CACHE_PATH}en${hash}.json`, + '{"success": true, "data": {}}', + 'utf8', + ); + contentLib.serveContent(resSpy, 'en', filter, true); + expect(resSpy.sendFile).to.have.been.calledOnce; + expect(resSpy.sendFile).to.have.been.calledWith(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`); + }); }); }); diff --git a/test/api/v3/integration/content/GET-content.test.js b/test/api/v3/integration/content/GET-content.test.js index a241058be5..0dbd2779e4 100644 --- a/test/api/v3/integration/content/GET-content.test.js +++ b/test/api/v3/integration/content/GET-content.test.js @@ -22,4 +22,38 @@ describe('GET /content', () => { expect(res).to.have.nested.property('backgrounds.backgrounds062014.beach'); expect(res.backgrounds.backgrounds062014.beach.text).to.equal(t('backgroundBeachText')); }); + + it('does not filter content for regular requests', async () => { + const res = await requester().get('/content'); + expect(res).to.have.nested.property('backgrounds.backgrounds062014'); + expect(res).to.have.nested.property('gear.tree'); + }); + + it('filters content automatically for iOS requests', async () => { + const res = await requester(null, { 'x-client': 'habitica-ios' }).get('/content'); + expect(res).to.have.nested.property('appearances.background.beach'); + expect(res).to.not.have.nested.property('backgrounds.backgrounds062014'); + expect(res).to.not.have.property('backgroundsFlat'); + expect(res).to.not.have.nested.property('gear.tree'); + }); + + it('filters content automatically for Android requests', async () => { + const res = await requester(null, { 'x-client': 'habitica-android' }).get('/content'); + expect(res).to.not.have.nested.property('appearances.background.beach'); + expect(res).to.have.nested.property('backgrounds.backgrounds062014'); + expect(res).to.not.have.property('backgroundsFlat'); + expect(res).to.not.have.nested.property('gear.tree'); + }); + + it('filters content if the request specifies a filter', async () => { + const res = await requester().get('/content?filter=backgroundsFlat,gear.flat'); + expect(res).to.not.have.property('backgroundsFlat'); + expect(res).to.have.nested.property('gear.tree'); + expect(res).to.not.have.nested.property('gear.flat'); + }); + + it('filters content if the request contains invalid filters', async () => { + const res = await requester().get('/content?filter=backgroundsFlat,invalid'); + expect(res).to.not.have.property('backgroundsFlat'); + }); }); diff --git a/test/helpers/api-unit.helper.js b/test/helpers/api-unit.helper.js index db867eb391..32eb04da31 100644 --- a/test/helpers/api-unit.helper.js +++ b/test/helpers/api-unit.helper.js @@ -40,6 +40,7 @@ export function generateRes (options = {}) { redirect: sandbox.stub(), render: sandbox.stub(), send: sandbox.stub(), + sendFile: sandbox.stub(), sendStatus: sandbox.stub().returnsThis(), set: sandbox.stub(), status: sandbox.stub().returnsThis(), diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index 33bb64328e..5c3a3c5fd4 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -1,34 +1,16 @@ import nconf from 'nconf'; -import fs from 'fs'; import { langCodes } from '../../libs/i18n'; -import { CONTENT_CACHE_PATH, getLocalizedContentResponse } from '../../libs/content'; +import { serveContent } from '../../libs/content'; const IS_PROD = nconf.get('IS_PROD'); const api = {}; -const CACHED_HASHES = [ - -]; - const MOBILE_FILTER = `achievements,questSeriesAchievements,animalColorAchievements,animalSetAchievements,stableAchievements, mystery,bundles,loginIncentives,pets,premiumPets,specialPets,questPets,wackyPets,mounts,premiumMounts,specialMounts,questMounts, events,dropEggs,questEggs,dropHatchingPotions,premiumHatchingPotions,wackyHatchingPotions,backgroundsFlat,questsByLevel,gear.tree, tasksByCategory,userDefaults,timeTravelStable,gearTypes,cardTypes`; -function hashForFilter (filter) { - let hash = 0; - let i; let - chr; - if (filter.length === 0) return ''; - for (i = 0; i < filter.length; i++) { // eslint-disable-line - chr = filter.charCodeAt(i); - hash = ((hash << 5) - hash) + chr; // eslint-disable-line - hash |= 0; // eslint-disable-line - } - return String(hash); -} - /** * @api {get} /api/v3/content Get all available content objects * @apiDescription Does not require authentication. @@ -92,52 +74,13 @@ api.getContent = { // apply defaults for mobile clients if (filter === '') { if (req.headers['x-client'] === 'habitica-android') { - filter = `${MOBILE_FILTER},appearance.background`; + filter = `${MOBILE_FILTER},appearances.background`; } else if (req.headers['x-client'] === 'habitica-ios') { filter = `${MOBILE_FILTER},backgrounds`; } } - // Build usable filter object - const filterObj = {}; - filter.split(',').forEach(item => { - if (item.includes('.')) { - const [key, subkey] = item.split('.'); - if (!filterObj[key]) { - filterObj[key] = {}; - } - filterObj[key][subkey.trim()] = true; - } else { - filterObj[item.trim()] = true; - } - }); - - if (IS_PROD) { - const filterHash = language + hashForFilter(filter); - if (CACHED_HASHES.includes(filterHash)) { - // Content is already cached, so just send it. - res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`); - } else { - // Content is not cached, so cache it and send it. - res.set({ - 'Content-Type': 'application/json', - }); - const jsonResString = getLocalizedContentResponse(language, filterObj); - fs.writeFileSync( - `${CONTENT_CACHE_PATH}${filterHash}.json`, - jsonResString, - 'utf8', - ); - CACHED_HASHES.push(filterHash); - res.status(200).send(jsonResString); - } - } else { - res.set({ - 'Content-Type': 'application/json', - }); - const jsonResString = getLocalizedContentResponse(language, filterObj); - res.status(200).send(jsonResString); - } + serveContent(res, language, filter, IS_PROD); }, }; diff --git a/website/server/libs/content.js b/website/server/libs/content.js index 378f7a09a6..94321cdf72 100644 --- a/website/server/libs/content.js +++ b/website/server/libs/content.js @@ -1,10 +1,15 @@ import _ from 'lodash'; import path from 'path'; +import fs from 'fs'; import common from '../../common'; import packageInfo from '../../../package.json'; export const CONTENT_CACHE_PATH = path.join(__dirname, '/../../../content_cache/'); +const CACHED_HASHES = [ + +]; + function walkContent (obj, lang, removedKeys = {}) { _.each(obj, (item, key, source) => { if (key in removedKeys && removedKeys[key] === true) { @@ -33,3 +38,59 @@ export function getLocalizedContentResponse (langCode, removedKeys = {}) { const localizedContent = localizeContentData(common.content, langCode, removedKeys); return `{"success": true, "data": ${JSON.stringify(localizedContent)}, "appVersion": "${packageInfo.version}"}`; } + +export function hashForFilter (filter) { + let hash = 0; + let i; let + chr; + if (filter.length === 0) return ''; + for (i = 0; i < filter.length; i++) { // eslint-disable-line + chr = filter.charCodeAt(i); + hash = ((hash << 5) - hash) + chr; // eslint-disable-line + hash |= 0; // eslint-disable-line + } + return String(hash); +} + +export function serveContent (res, language, filter, isProd) { + // Build usable filter object + const filterObj = {}; + filter.split(',').forEach(item => { + if (item.includes('.')) { + const [key, subkey] = item.split('.'); + if (!filterObj[key]) { + filterObj[key] = {}; + } + filterObj[key][subkey.trim()] = true; + } else { + filterObj[item.trim()] = true; + } + }); + + if (isProd) { + const filterHash = language + hashForFilter(filter); + if (CACHED_HASHES.includes(filterHash)) { + // Content is already cached, so just send it. + res.sendFile(`${CONTENT_CACHE_PATH}${filterHash}.json`); + } else { + // Content is not cached, so cache it and send it. + res.set({ + 'Content-Type': 'application/json', + }); + const jsonResString = getLocalizedContentResponse(language, filterObj); + fs.writeFileSync( + `${CONTENT_CACHE_PATH}${filterHash}.json`, + jsonResString, + 'utf8', + ); + CACHED_HASHES.push(filterHash); + res.status(200).send(jsonResString); + } + } else { + res.set({ + 'Content-Type': 'application/json', + }); + const jsonResString = getLocalizedContentResponse(language, filterObj); + res.status(200).send(jsonResString); + } +} From ce796fa1d9b785239449b61afec2f1b4188240b5 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 16:24:58 +0100 Subject: [PATCH 013/244] begin building recurring content scheduling --- test/content/schedule.test.js | 36 +++ .../script/content/constants/schedule.js | 224 ++++++++++++++++++ 2 files changed, 260 insertions(+) create mode 100644 test/content/schedule.test.js create mode 100644 website/common/script/content/constants/schedule.js diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js new file mode 100644 index 0000000000..5d7c211dfe --- /dev/null +++ b/test/content/schedule.test.js @@ -0,0 +1,36 @@ +import { each } from 'lodash'; +import { + expectValidTranslationString, +} from '../helpers/content.helper'; + +import { assembleScheduledMatchers } from '../../website/common/script/content/constants/schedule'; + + +describe('Content Schedule', () => { + it('assembles scheduled items on january 15th', () => { + const items = assembleScheduledMatchers(new Date('2024-01-15')); + }); + + it('assembles scheduled items on january 31th', () => { + const items = assembleScheduledMatchers(new Date('2024-01-31')); + }); + + it('assembles scheduled items on march 2nd', () => { + const items = assembleScheduledMatchers(new Date('2024-03-02')); + }); + + it('assembles scheduled items on march 21st', () => { + const items = assembleScheduledMatchers(new Date('2024-03-21')); + }); + + it('assembles scheduled items on october 7th', () => { + const items = assembleScheduledMatchers(new Date('2024-10-07')); + }); + it('assembles scheduled items on november 1th', () => { + const items = assembleScheduledMatchers(new Date('2024-11-01')); + }); + + it('assembles scheduled items on december 20th', () => { + const items = assembleScheduledMatchers(new Date('2024-12-20')); + }); +}); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js new file mode 100644 index 0000000000..2627aed9cf --- /dev/null +++ b/website/common/script/content/constants/schedule.js @@ -0,0 +1,224 @@ +import moment from 'moment'; + +function backgroundMatcher(month, oddYear) { + return function (background) { + const key = background.set.key; + const keyLength = key.length; + return parseInt(key.substring(keyLength-6, keyLength-4)) === month && parseInt(key.subtring(keyLength-2, keyLength)) % 2 === oddYear; + } +} + +function timeTravelersMatcher(month1, month2) { + return function (item) { + console.log(item, month1, month2) + return item; + } +} + +export const FIRST_RELEASE_DAY = 1; +export const SECOND_RELEASE_DAY = 7; +export const THIRD_RELEASE_DAY = 14; +export const FOURTH_RELEASE_DAY = 21; + +export const MONTHLY_SCHEDULE = { + 0: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(1, 7), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 1: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(2, 8), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 2: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(3, 9), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 3: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(4, 10), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 4: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(5, 11), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 5: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(6, 12), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 6: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(7, 1), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 7: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(8, 2), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 8: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(9, 3), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 9: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(10, 4), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 10: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(11, 5), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 11: { + [FIRST_RELEASE_DAY]: [ + { + "type": "timeTravelers", + "matcher": timeTravelersMatcher(12, 6), + } + ], + [SECOND_RELEASE_DAY]: [ + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, +}; + +export const GALA_SWITCHOVER_DAY = 21; +export const GALA_SCHEDULE = { + 0: [], + 1: [], + 2: [], + 3: [], +}; + +export function assembleScheduledMatchers(date) { + const items = []; + const month = date instanceof moment ? date.month() : date.getMonth(); + const todayDay = date instanceof moment ? date.date() : date.getDate(); + const previousMonth = month === 0 ? 11 : month - 1; + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { + if (day > todayDay) { + items.push(...value); + } + } + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { + if (day <= todayDay) { + items.push(...value); + } + } + let galaMonth = month; + const galaCount = Object.keys(GALA_SCHEDULE).length; + if (todayDay >= GALA_SWITCHOVER_DAY) { + galaMonth += 1; + } + items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth)]); + return items; +} \ No newline at end of file From 278d9b74f9607b8749217a62b68f373e53fa5c4a Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 17:27:03 +0100 Subject: [PATCH 014/244] Implement new scheduling for time travelers shop --- test/content/time-travelers.test.js | 106 +++- .../script/content/constants/schedule.js | 474 ++++++++++-------- website/common/script/content/index.js | 4 + .../common/script/content/time-travelers.js | 14 +- website/common/script/libs/shops.js | 2 +- .../common/script/ops/buy/buyMysterySet.js | 2 +- 6 files changed, 378 insertions(+), 224 deletions(-) diff --git a/test/content/time-travelers.test.js b/test/content/time-travelers.test.js index 1e9f5bbd94..83dc40dd38 100644 --- a/test/content/time-travelers.test.js +++ b/test/content/time-travelers.test.js @@ -6,23 +6,105 @@ import timeTravelers from '../../website/common/script/content/time-travelers'; describe('time-travelers store', () => { let user; + let date; beforeEach(() => { user = generateUser(); }); - it('removes owned sets from the time travelers store', () => { - user.items.gear.owned.head_mystery_201602 = true; // eslint-disable-line camelcase - expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist; - expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist; + describe('on january 15th', () => { + beforeEach(() => { + date = new Date('2024-01-15'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(01|07)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201601 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201601']).to.not.exist; + expect(items['201801']).to.exist; + expect(items['202207']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['head_mystery_201601'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201601']).to.not.exist; + expect(items['201607']).to.exist; + }); }); - it('removes unopened mystery item sets from the time travelers store', () => { - user.purchased = { - plan: { - mysteryItems: ['head_mystery_201602'], - }, - }; - expect(timeTravelers.timeTravelerStore(user)['201602']).to.not.exist; - expect(timeTravelers.timeTravelerStore(user)['201603']).to.exist; + describe('on may 1st', () => { + beforeEach(() => { + date = new Date('2024-05-01'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(05|11)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201705 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201705']).to.not.exist; + expect(items['201805']).to.exist; + expect(items['202211']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['head_mystery_201705'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201705']).to.not.exist; + expect(items['201611']).to.exist; + }); + }); + + describe('on october 21st', () => { + beforeEach(() => { + date = new Date('2024-10-21'); + }); + it('returns the correct gear', () => { + const items = timeTravelers.timeTravelerStore(user, date); + for (const [key] of Object.entries(items)) { + if (key.startsWith('20')) { + expect(key).to.match(/20[0-9]{2}(10|04)/); + } + } + }); + it('removes owned sets from the time travelers store', () => { + user.items.gear.owned.head_mystery_201810 = true; // eslint-disable-line camelcase + user.items.gear.owned.armor_mystery_201810 = true; // eslint-disable-line camelcase + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201810']).to.not.exist; + expect(items['201910']).to.exist; + expect(items['202204']).to.exist; + }); + + it('removes unopened mystery item sets from the time travelers store', () => { + user.purchased = { + plan: { + mysteryItems: ['armor_mystery_201710'], + }, + }; + const items = timeTravelers.timeTravelerStore(user, date); + expect(items['201710']).to.not.exist; + expect(items['201604']).to.exist; + }); }); }); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 2627aed9cf..fcb0bc04a4 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -1,18 +1,26 @@ import moment from 'moment'; -function backgroundMatcher(month, oddYear) { - return function (background) { - const key = background.set.key; - const keyLength = key.length; - return parseInt(key.substring(keyLength-6, keyLength-4)) === month && parseInt(key.subtring(keyLength-2, keyLength)) % 2 === oddYear; - } +function backgroundMatcher (month1, month2, oddYear) { + return function call (key) { + if (!key.startsWith('backgrounds')) return true; + const keyLength = key.length; + const month = parseInt(key.substring(keyLength - 6, keyLength - 4), 10); + return (month === month1 || month === month2) + && parseInt(key.substring(keyLength - 2, keyLength), 10) % 2 === (oddYear ? 1 : 0); + }; } -function timeTravelersMatcher(month1, month2) { - return function (item) { - console.log(item, month1, month2) - return item; - } +function timeTravelersMatcher (month1, month2) { + return function call (item) { + const itemMonth = parseInt(item.substring(4, 6), 10); + return itemMonth === month1 || itemMonth === month2; + }; +} + +function inListMatcher (list) { + return function call (item) { + return list.indexOf(item) !== -1; + }; } export const FIRST_RELEASE_DAY = 1; @@ -21,204 +29,262 @@ export const THIRD_RELEASE_DAY = 14; export const FOURTH_RELEASE_DAY = 21; export const MONTHLY_SCHEDULE = { - 0: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(1, 7), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 1: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(2, 8), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 2: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(3, 9), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 3: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(4, 10), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 4: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(5, 11), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 5: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(6, 12), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 6: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(7, 1), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 7: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(8, 2), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 8: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(9, 3), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 9: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(10, 4), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 10: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(11, 5), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, - 11: { - [FIRST_RELEASE_DAY]: [ - { - "type": "timeTravelers", - "matcher": timeTravelersMatcher(12, 6), - } - ], - [SECOND_RELEASE_DAY]: [ - ], - [THIRD_RELEASE_DAY]: [ - ], - [FOURTH_RELEASE_DAY]: [ - ], - }, + 0: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(1, 7), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(1, 7, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'ghost_stag', + 'trex', + 'harpy', + 'sabretooth', + 'dolphin', + ]), + }, + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 1: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(2, 8), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(2, 8, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 2: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(3, 9), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(3, 9, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 3: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(4, 10), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(4, 10, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 4: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(5, 11), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(5, 11, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 5: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(6, 12), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(6, 12, false), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 6: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(7, 1), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(7, 1, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 7: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(8, 2), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(8, 2, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 8: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(9, 3), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(9, 3, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 9: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(10, 4), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(10, 4, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 10: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(11, 5), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(11, 5, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, + 11: { + [FIRST_RELEASE_DAY]: [ + { + type: 'timeTravelers', + matcher: timeTravelersMatcher(12, 6), + }, + ], + [SECOND_RELEASE_DAY]: [ + { + type: 'backgrounds', + matcher: backgroundMatcher(12, 6, true), + }, + ], + [THIRD_RELEASE_DAY]: [ + ], + [FOURTH_RELEASE_DAY]: [ + ], + }, }; export const GALA_SWITCHOVER_DAY = 21; export const GALA_SCHEDULE = { - 0: [], - 1: [], - 2: [], - 3: [], + 0: [], + 1: [], + 2: [], + 3: [], }; -export function assembleScheduledMatchers(date) { - const items = []; - const month = date instanceof moment ? date.month() : date.getMonth(); - const todayDay = date instanceof moment ? date.date() : date.getDate(); - const previousMonth = month === 0 ? 11 : month - 1; - for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { - if (day > todayDay) { - items.push(...value); - } +export function assembleScheduledMatchers (date) { + const items = []; + const month = date instanceof moment ? date.month() : date.getMonth(); + const todayDay = date instanceof moment ? date.date() : date.getDate(); + const previousMonth = month === 0 ? 11 : month - 1; + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[previousMonth])) { + if (day > todayDay) { + items.push(...value); } - for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { - if (day <= todayDay) { - items.push(...value); - } + } + for (const [day, value] of Object.entries(MONTHLY_SCHEDULE[month])) { + if (day <= todayDay) { + items.push(...value); } - let galaMonth = month; - const galaCount = Object.keys(GALA_SCHEDULE).length; - if (todayDay >= GALA_SWITCHOVER_DAY) { - galaMonth += 1; - } - items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth)]); - return items; -} \ No newline at end of file + } + let galaMonth = month; + const galaCount = Object.keys(GALA_SCHEDULE).length; + if (todayDay >= GALA_SWITCHOVER_DAY) { + galaMonth += 1; + } + items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth, 10)]); + return items; +} diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 6465252070..3dae798ddb 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -33,6 +33,8 @@ import gemsBlock from './gems'; import faq from './faq'; import timeTravelers from './time-travelers'; +import { assembleScheduledMatchers } from './constants/schedule'; + import loginIncentives from './loginIncentives'; import officialPinnedItems from './officialPinnedItems'; @@ -728,4 +730,6 @@ api.faq = faq; api.loginIncentives = loginIncentives(api); +api.assembleScheduledMatchers = assembleScheduledMatchers; + export default api; diff --git a/website/common/script/content/time-travelers.js b/website/common/script/content/time-travelers.js index 884ac1d5c1..cc717dbd46 100644 --- a/website/common/script/content/time-travelers.js +++ b/website/common/script/content/time-travelers.js @@ -7,6 +7,7 @@ import moment from 'moment'; import mysterySets from './mystery-sets'; import gear from './gear'; +import { assembleScheduledMatchers } from './constants/schedule'; const mystery = mysterySets; @@ -17,7 +18,8 @@ each(mystery, (v, k) => { if (v.items.length === 0) delete mystery[k]; }); -const timeTravelerStore = user => { +const timeTravelerStore = (user, date) => { + const availabilityMatchers = assembleScheduledMatchers(date).filter(matcher => matcher.type === 'timeTravelers').map(matcher => matcher.matcher); let ownedKeys; const { owned } = user.items.gear; const { mysteryItems } = user.purchased.plan; @@ -26,13 +28,13 @@ const timeTravelerStore = user => { ownedKeys = union(ownedKeys, unopenedGifts); return reduce(mystery, (m, v, k) => { if ( - k === 'wondercon' - || ownedKeys.indexOf(v.items[0].key) !== -1 - || (moment(k).add(1, 'months').isAfter() && moment(k).isBefore('3000-01-01')) + k !== 'wondercon' + && ownedKeys.indexOf(v.items[0].key) === -1 + && (moment(k).isAfter('3000-01-01') + || availabilityMatchers.map(matcher => matcher(k)).every(matcher => matcher === true)) ) { - return m; + m[k] = v; } - m[k] = v; return m; }, {}); }; diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 4dfc6c87b0..39dc7bd88e 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -369,7 +369,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la } } - const sets = content.timeTravelerStore(user); + const sets = content.timeTravelerStore(user, new Date()); for (const setKey of Object.keys(sets)) { const set = sets[setKey]; const category = { diff --git a/website/common/script/ops/buy/buyMysterySet.js b/website/common/script/ops/buy/buyMysterySet.js index 0602495683..dea4d07870 100644 --- a/website/common/script/ops/buy/buyMysterySet.js +++ b/website/common/script/ops/buy/buyMysterySet.js @@ -20,7 +20,7 @@ export default async function buyMysterySet (user, req = {}, analytics) { throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); } - const ref = content.timeTravelerStore(user); + const ref = content.timeTravelerStore(user, new Date()); const mysterySet = ref ? ref[key] : undefined; if (!mysterySet) { From 17db6a17725ca6ddd6c607d7aa2c735c80daddc6 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 17:33:10 +0100 Subject: [PATCH 015/244] Implement new release schedule for backgrounds --- test/content/schedule.test.js | 43 ++++++++++++++--------------- website/common/locales/en/npc.json | 1 + website/common/script/libs/shops.js | 16 +++++++---- website/common/script/ops/unlock.js | 12 ++++++++ 4 files changed, 44 insertions(+), 28 deletions(-) diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index 5d7c211dfe..e61e837f9e 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -5,32 +5,31 @@ import { import { assembleScheduledMatchers } from '../../website/common/script/content/constants/schedule'; - describe('Content Schedule', () => { - it('assembles scheduled items on january 15th', () => { - const items = assembleScheduledMatchers(new Date('2024-01-15')); - }); + it('assembles scheduled items on january 15th', () => { + const items = assembleScheduledMatchers(new Date('2024-01-15')); + }); - it('assembles scheduled items on january 31th', () => { - const items = assembleScheduledMatchers(new Date('2024-01-31')); - }); + it('assembles scheduled items on january 31th', () => { + const items = assembleScheduledMatchers(new Date('2024-01-31')); + }); - it('assembles scheduled items on march 2nd', () => { - const items = assembleScheduledMatchers(new Date('2024-03-02')); - }); + it('assembles scheduled items on march 2nd', () => { + const items = assembleScheduledMatchers(new Date('2024-03-02')); + }); - it('assembles scheduled items on march 21st', () => { - const items = assembleScheduledMatchers(new Date('2024-03-21')); - }); + it('assembles scheduled items on march 21st', () => { + const items = assembleScheduledMatchers(new Date('2024-03-21')); + }); - it('assembles scheduled items on october 7th', () => { - const items = assembleScheduledMatchers(new Date('2024-10-07')); - }); - it('assembles scheduled items on november 1th', () => { - const items = assembleScheduledMatchers(new Date('2024-11-01')); - }); + it('assembles scheduled items on october 7th', () => { + const items = assembleScheduledMatchers(new Date('2024-10-07')); + }); + it('assembles scheduled items on november 1th', () => { + const items = assembleScheduledMatchers(new Date('2024-11-01')); + }); - it('assembles scheduled items on december 20th', () => { - const items = assembleScheduledMatchers(new Date('2024-12-20')); - }); + it('assembles scheduled items on december 20th', () => { + const items = assembleScheduledMatchers(new Date('2024-12-20')); + }); }); diff --git a/website/common/locales/en/npc.json b/website/common/locales/en/npc.json index 19fca03acd..b92aabc71c 100644 --- a/website/common/locales/en/npc.json +++ b/website/common/locales/en/npc.json @@ -77,6 +77,7 @@ "userItemsNotEnough": "You do not have enough <%= type %>", "pathRequired": "Path string is required", "unlocked": "Items have been unlocked", + "notAvailable": "This item is not available.", "alreadyUnlocked": "Full set already unlocked.", "alreadyUnlockedPart": "Full set already partially unlocked. It is cheaper to buy the remaining items individually.", "invalidUnlockSet": "This set of items is invalid and cannot be unlocked.", diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 39dc7bd88e..44d0035a6f 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -17,6 +17,7 @@ import featuredItems from '../content/shop-featuredItems'; import getOfficialPinnedItems from './getOfficialPinnedItems'; import { getClassName } from './getClassName'; +import { assembleScheduledMatchers } from '../content/constants/schedule'; const shops = {}; @@ -529,15 +530,18 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { const sets = []; const officialPinnedItems = getOfficialPinnedItems(); + const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'backgrounds').map(matcher => matcher.matcher); eachRight(content.backgrounds, (group, key) => { - const set = { - identifier: key, - text: i18n.t(key, language), - }; + if (matchers.map(matcher => matcher(key)).every(matcher => matcher === true)) { + const set = { + identifier: key, + text: i18n.t(key, language), + }; - set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language)); + set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language)); - sets.push(set); + sets.push(set); + } }); return sets; diff --git a/website/common/script/ops/unlock.js b/website/common/script/ops/unlock.js index 342c9dc7a9..3cc4b4cf01 100644 --- a/website/common/script/ops/unlock.js +++ b/website/common/script/ops/unlock.js @@ -7,6 +7,7 @@ import { removeItemByPath } from './pinnedGearUtils'; import getItemInfo from '../libs/getItemInfo'; import content from '../content/index'; import updateUserBalance from './updateUserBalance'; +import { assembleScheduledMatchers } from '../content/constants/schedule'; const incentiveBackgrounds = ['blue', 'green', 'red', 'purple', 'yellow']; @@ -223,6 +224,17 @@ export default async function unlock (user, req = {}, analytics) { // The passed paths are not used anymore after this point for full sets const { set, items, paths } = getSet(setType, firstPath, req); + if (isBackground) { + const matchers = assembleScheduledMatchers(new Date()) + .filter(matcher => matcher.type === 'backgrounds') + .map(matcher => matcher.matcher); + const isAvailable = matchers.map(matcher => matcher(set.key)) + .every(matcher => matcher === true); + if (!isAvailable) { + throw new NotAuthorized(i18n.t('notAvailable', req.language)); + } + } + let cost; let unlockedAlready = false; From b3521be62979ac8a5d82e40c8042c92736671123 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 17:53:46 +0100 Subject: [PATCH 016/244] Implement new content schedule for potion and pet quests --- .../script/content/constants/schedule.js | 173 +++++++++++++++++- website/common/script/libs/shops.js | 14 +- website/common/script/ops/buy/buyQuestGem.js | 6 + 3 files changed, 189 insertions(+), 4 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index fcb0bc04a4..3cf37c411b 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -47,12 +47,18 @@ export const MONTHLY_SCHEDULE = { type: 'petQuests', matcher: inListMatcher([ 'ghost_stag', - 'trex', + 'trex_undead', 'harpy', 'sabretooth', 'dolphin', ]), }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'ruby', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -71,6 +77,22 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'nudibranch', + 'seaserpent', + 'gryphon', + 'yarn', + 'axolotl', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'silver', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -89,6 +111,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'rooster', + 'slime', + 'peacock', + 'bunny', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'pinkMarble', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -107,6 +144,20 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'frog', + 'spider', + 'cow', + 'pterodactyl', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -125,6 +176,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'snake', + 'monkey', + 'falcon', + 'aligator', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'mossyStone', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -143,6 +209,20 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'octopus', + 'horse', + 'kraken', + 'sloth', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -161,6 +241,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'trex', + 'unicorn', + 'veolociraptor', + 'hippo', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'turquiose', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -179,6 +274,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'whale', + 'seahorse', + 'armadillo', + 'guineapig', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'fluorite', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -197,6 +307,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'turtle', + 'penguin', + 'butterfly', + 'cheetah', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'blackPearl', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -215,6 +340,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'squirrel', + 'triceratops', + 'treeling', + 'beetle', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'bronze', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -233,6 +373,21 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'snail', + 'rock', + 'ferret', + 'hedgehog', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'onyx', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], @@ -251,6 +406,22 @@ export const MONTHLY_SCHEDULE = { }, ], [THIRD_RELEASE_DAY]: [ + { + type: 'petQuests', + matcher: inListMatcher([ + 'sheep', + 'kangaroo', + 'owl', + 'rat', + 'badger', + ]), + }, + { + type: 'hatchingPotionQuests', + matcher: inListMatcher([ + 'amber', + ]), + }, ], [FOURTH_RELEASE_DAY]: [ ], diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 44d0035a6f..b1b0574dbb 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -285,9 +285,17 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) text: i18n.t(`${type}Quests`, language), }; - category.items = content.questsByLevel - .filter(quest => quest.canBuy(user) && quest.category === type) - .map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language)); + let filteredQuests = content.questsByLevel + .filter(quest => quest.canBuy(user) && quest.category === type); + + if (type === 'pet' || type === 'hatchingPotion') { + const matchers = assembleScheduledMatchers(new Date()) + .filter(matcher => matcher.type === `${type}Quests`).map(matcher => matcher.matcher); + filteredQuests = filteredQuests.filter(quest => matchers.map(matcher => matcher(quest.key)) + .every(matcher => matcher === true)); + } + + category.items = filteredQuests.map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language)); categories.push(category); }); diff --git a/website/common/script/ops/buy/buyQuestGem.js b/website/common/script/ops/buy/buyQuestGem.js index 20ddb8e3b5..9e5521f907 100644 --- a/website/common/script/ops/buy/buyQuestGem.js +++ b/website/common/script/ops/buy/buyQuestGem.js @@ -8,6 +8,7 @@ import content from '../../content/index'; import { errorMessage } from '../../libs/errorMessage'; import { AbstractGemItemOperation } from './abstractBuyOperation'; +import { assembleScheduledMatchers } from '../../content/constants/schedule'; export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // eslint-disable-line import/prefer-default-export, max-len multiplePurchaseAllowed () { // eslint-disable-line class-methods-use-this @@ -51,6 +52,11 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // esli } } + const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === `${item.category}Quests`).map(matcher => matcher.matcher); + if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + throw new NotAuthorized(this.i18n('notAvailable', { key: item.key })); + } + super.canUserPurchase(user, item); } From f223b5dd2a5cd73ac17c1dccaaf8347889a0df79 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 18:06:50 +0100 Subject: [PATCH 017/244] Implement schedule for quest bundles --- .../script/content/constants/schedule.js | 200 ++++++++++++------ website/common/script/libs/shops.js | 8 +- website/common/script/ops/buy/buyQuestGem.js | 1 + website/common/script/ops/buy/purchase.js | 5 + 4 files changed, 151 insertions(+), 63 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 3cf37c411b..7f9a13904d 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -46,17 +46,23 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'ghost_stag', - 'trex_undead', - 'harpy', - 'sabretooth', - 'dolphin', + 'nudibranch', + 'seaserpent', + 'gryphon', + 'yarn', + 'axolotl', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'ruby', + 'silver', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'winterQuests', ]), }, ], @@ -80,17 +86,22 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'nudibranch', - 'seaserpent', - 'gryphon', - 'yarn', - 'axolotl', + 'rooster', + 'slime', + 'peacock', + 'bunny', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'silver', + 'pinkMarble', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'cuddleBuddies', ]), }, ], @@ -114,16 +125,21 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'rooster', - 'slime', - 'peacock', - 'bunny', + 'frog', + 'spider', + 'cow', + 'pterodactyl', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'pinkMarble', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'birdBuddies', ]), }, ], @@ -147,15 +163,22 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'frog', - 'spider', - 'cow', - 'pterodactyl', + 'snake', + 'monkey', + 'falcon', + 'aligator', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ + 'mossyStone', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'hugabug', ]), }, ], @@ -179,16 +202,21 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'snake', - 'monkey', - 'falcon', - 'aligator', + 'octopus', + 'horse', + 'kraken', + 'sloth', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'mossyStone', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'splashyPals', ]), }, ], @@ -212,15 +240,28 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'octopus', - 'horse', - 'kraken', - 'sloth', + 'trex', + 'unicorn', + 'veolociraptor', + 'hippo', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ + 'turquiose', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'rockingReptiles', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'delightfulDinos', ]), }, ], @@ -244,16 +285,28 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'trex', - 'unicorn', - 'veolociraptor', - 'hippo', + 'whale', + 'seahorse', + 'armadillo', + 'guineapig', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'turquiose', + 'fluorite', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'winterQuests', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'aquaticAmigos', ]), }, ], @@ -277,16 +330,22 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'whale', - 'seahorse', - 'armadillo', - 'guineapig', + 'turtle', + 'penguin', + 'butterfly', + 'cheetah', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'fluorite', + 'blackPearl', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'featheredFriends', ]), }, ], @@ -310,16 +369,22 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'turtle', - 'penguin', - 'butterfly', - 'cheetah', + 'squirrel', + 'triceratops', + 'treeling', + 'beetle', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'blackPearl', + 'bronze', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'farmFriends', ]), }, ], @@ -343,16 +408,22 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'squirrel', - 'triceratops', - 'treeling', - 'beetle', + 'snail', + 'rock', + 'ferret', + 'hedgehog', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'bronze', + 'onyx', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'witchyFamiliars', ]), }, ], @@ -376,16 +447,23 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'snail', - 'rock', - 'ferret', - 'hedgehog', + 'sheep', + 'kangaroo', + 'owl', + 'rat', + 'badger', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'onyx', + 'amber', + ]), + }, + { + type: 'bundles', + matcher: inListMatcher([ + 'forestFriends', ]), }, ], @@ -409,17 +487,17 @@ export const MONTHLY_SCHEDULE = { { type: 'petQuests', matcher: inListMatcher([ - 'sheep', - 'kangaroo', - 'owl', - 'rat', - 'badger', + 'ghost_stag', + 'trex_undead', + 'harpy', + 'sabretooth', + 'dolphin', ]), }, { type: 'hatchingPotionQuests', matcher: inListMatcher([ - 'amber', + 'ruby', ]), }, ], diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index b1b0574dbb..ab75ed6a71 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -265,14 +265,18 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) * ] * */ + const scheduledMatchers = assembleScheduledMatchers(new Date()); const bundleCategory = { identifier: 'bundle', text: i18n.t('questBundles', language), }; + const bundleMatchers = scheduledMatchers.filter(matcher => matcher.type === 'bundles').map(matcher => matcher.matcher); + console.log(bundleMatchers); bundleCategory.items = sortBy(values(content.bundles) - .filter(bundle => bundle.type === 'quests' && bundle.canBuy()) + .filter(bundle => bundle.type === 'quests' + && bundleMatchers.map(matcher => matcher(bundle.key)).every(matcher => matcher === true)) .map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language))); if (bundleCategory.items.length > 0) { @@ -289,7 +293,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) .filter(quest => quest.canBuy(user) && quest.category === type); if (type === 'pet' || type === 'hatchingPotion') { - const matchers = assembleScheduledMatchers(new Date()) + const matchers = scheduledMatchers .filter(matcher => matcher.type === `${type}Quests`).map(matcher => matcher.matcher); filteredQuests = filteredQuests.filter(quest => matchers.map(matcher => matcher(quest.key)) .every(matcher => matcher === true)); diff --git a/website/common/script/ops/buy/buyQuestGem.js b/website/common/script/ops/buy/buyQuestGem.js index 9e5521f907..392b8b5ebd 100644 --- a/website/common/script/ops/buy/buyQuestGem.js +++ b/website/common/script/ops/buy/buyQuestGem.js @@ -53,6 +53,7 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // esli } const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === `${item.category}Quests`).map(matcher => matcher.matcher); + console.log(item, matchers); if (matchers.length && !matchers.some(matcher => matcher(item.key))) { throw new NotAuthorized(this.i18n('notAvailable', { key: item.key })); } diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 64786c7516..3788eb778b 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -13,6 +13,7 @@ import { import { removeItemByPath } from '../pinnedGearUtils'; import getItemInfo from '../../libs/getItemInfo'; import updateUserBalance from '../updateUserBalance'; +import { assembleScheduledMatchers } from '../../content/constants/schedule'; function getItemAndPrice (user, type, key, req) { let item; @@ -54,6 +55,10 @@ async function purchaseItem (user, item, price, type, key) { if (user.markModified) user.markModified('items.gear.owned'); } else if (type === 'bundles') { const subType = item.type; + const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'bundles').map(matcher => matcher.matcher); + if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + throw new NotAuthorized(i18n.t('notAvailable', { key: item.key })); + } forEach(item.bundleKeys, bundledKey => { if (!user.items[subType][bundledKey] || user.items[subType][bundledKey] < 0) { user.items[subType][bundledKey] = 0; From 129cb7627c35dd75fca079e7fb9474ee4aa85b29 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 18:29:58 +0100 Subject: [PATCH 018/244] Implement new content schedule for magic hatching potions --- .../script/content/constants/schedule.js | 93 +++++++++++++++++++ website/common/script/libs/shops.js | 4 +- website/common/script/ops/buy/purchase.js | 7 +- 3 files changed, 102 insertions(+), 2 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 7f9a13904d..c7bf10fa69 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -67,6 +67,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Aurora', + 'Moonglow', + 'IcySnow', + ]), + }, ], }, 1: { @@ -106,6 +114,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'PolkaDot', + 'Cupid', + 'RoseGold', + ]), + }, ], }, 2: { @@ -144,6 +160,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Birch', + 'StainedGlass', + 'Porcelain', + ]), + }, ], }, 3: { @@ -183,6 +207,13 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Shimmer', + 'Glass', + ]), + }, ], }, 4: { @@ -221,6 +252,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Floral', + 'Fairy', + 'RoseQuartz', + ]), + }, ], }, 5: { @@ -266,6 +305,13 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Rainbow', + 'Sunshine', + ]), + }, ], }, 6: { @@ -311,6 +357,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Celestial', + 'SandCastle', + 'Watery', + ]), + }, ], }, 7: { @@ -350,6 +404,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Aquatic', + 'StarryNight', + 'Sunset', + ]), + }, ], }, 8: { @@ -389,6 +451,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Glow', + 'AutumnLeaf', + 'Shadow', + ]), + }, ], }, 9: { @@ -428,6 +498,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Vampire', + 'Ghost', + 'Spooky', + ]), + }, ], }, 10: { @@ -468,6 +546,14 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Ember', + 'Frost', + 'Thunderstorm', + ]), + }, ], }, 11: { @@ -502,6 +588,13 @@ export const MONTHLY_SCHEDULE = { }, ], [FOURTH_RELEASE_DAY]: [ + { + type: 'premiumHatchingPotions', + matcher: inListMatcher([ + 'Peppermint', + 'Holly', + ]), + }, ], }, }; diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index ab75ed6a71..aea59c3894 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -71,8 +71,10 @@ shops.getMarketCategories = function getMarket (user, language) { text: i18n.t('magicHatchingPotions', language), notes: i18n.t('premiumPotionNoDropExplanation', language), }; + const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'premiumHatchingPotions').map(matcher => matcher.matcher); premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions) - .filter(hp => hp.limited && hp.canBuy(user)) + .filter(hp => hp.limited + && matchers.map(matcher => matcher(hp.key)).every(matcher => matcher === true)) .map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language)), 'key'); if (premiumHatchingPotionsCategory.items.length > 0) { categories.push(premiumHatchingPotionsCategory); diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 3788eb778b..c1989e33f6 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -101,7 +101,12 @@ export default async function purchase (user, req = {}, analytics) { const { price, item } = getItemAndPrice(user, type, key, req); - if (!item.canBuy(user)) { + if (item.type === 'hatchingPotion' && item.premium === true) { + const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'premiumHatchingPotions').map(matcher => matcher.matcher); + if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); + } + } else if (!item.canBuy(user)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } From 736ef1643035ad4a4b26f6536affd90b38590af4 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 22:52:44 +0100 Subject: [PATCH 019/244] simplify schedule matching usage --- .../script/content/constants/schedule.js | 51 +++++++++++++++++-- website/common/script/content/index.js | 4 +- .../common/script/content/time-travelers.js | 6 +-- website/common/script/libs/shops.js | 22 ++++---- website/common/script/ops/buy/buyQuestGem.js | 7 ++- website/common/script/ops/buy/purchase.js | 10 ++-- website/common/script/ops/unlock.js | 10 ++-- 7 files changed, 72 insertions(+), 38 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index c7bf10fa69..1347ebcf8f 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -1,4 +1,5 @@ import moment from 'moment'; +import SEASONAL_SETS from './seasonalSets'; function backgroundMatcher (month1, month2, oddYear) { return function call (key) { @@ -601,13 +602,34 @@ export const MONTHLY_SCHEDULE = { export const GALA_SWITCHOVER_DAY = 21; export const GALA_SCHEDULE = { - 0: [], - 1: [], - 2: [], - 3: [], + 0: [ + { + type: 'seasonalGear', + matcher: inListMatcher(SEASONAL_SETS.winter), + }, + ], + 1: [ + { + type: 'seasonalGear', + matcher: inListMatcher(SEASONAL_SETS.spring), + }, + ], + 2: [ + { + type: 'seasonalGear', + matcher: inListMatcher(SEASONAL_SETS.fall), + }, + ], + 3: [ + { + type: 'seasonalGear', + matcher: inListMatcher(SEASONAL_SETS.summer), + }, + ], }; export function assembleScheduledMatchers (date) { + console.log('Assembling new Schedule!'); const items = []; const month = date instanceof moment ? date.month() : date.getMonth(); const todayDay = date instanceof moment ? date.date() : date.getDate(); @@ -630,3 +652,24 @@ export function assembleScheduledMatchers (date) { items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth, 10)]); return items; } + +let cachedScheduleMatchers = null; + +export function getScheduleMatchingGroup (type, date) { + if (!cachedScheduleMatchers) { + cachedScheduleMatchers = {}; + assembleScheduledMatchers(date !== undefined ? date : new Date()).forEach(matcher => { + if (cachedScheduleMatchers[matcher.type]) { + cachedScheduleMatchers[matcher.type].matchers.push(matcher.matcher); + } else { + cachedScheduleMatchers[matcher.type] = { + matchers: [matcher.matcher], + match (key) { + return this.matchers.every(m => m(key)); + }, + }; + } + }); + } + return cachedScheduleMatchers[type]; +} diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 3dae798ddb..2d1793ab7f 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -33,7 +33,7 @@ import gemsBlock from './gems'; import faq from './faq'; import timeTravelers from './time-travelers'; -import { assembleScheduledMatchers } from './constants/schedule'; +import { getScheduleMatchingGroup } from './constants/schedule'; import loginIncentives from './loginIncentives'; @@ -730,6 +730,6 @@ api.faq = faq; api.loginIncentives = loginIncentives(api); -api.assembleScheduledMatchers = assembleScheduledMatchers; +api.getScheduleMatchingGroup = getScheduleMatchingGroup; export default api; diff --git a/website/common/script/content/time-travelers.js b/website/common/script/content/time-travelers.js index cc717dbd46..2af2aaadbc 100644 --- a/website/common/script/content/time-travelers.js +++ b/website/common/script/content/time-travelers.js @@ -7,7 +7,7 @@ import moment from 'moment'; import mysterySets from './mystery-sets'; import gear from './gear'; -import { assembleScheduledMatchers } from './constants/schedule'; +import { getScheduleMatchingGroup } from './constants/schedule'; const mystery = mysterySets; @@ -19,7 +19,7 @@ each(mystery, (v, k) => { }); const timeTravelerStore = (user, date) => { - const availabilityMatchers = assembleScheduledMatchers(date).filter(matcher => matcher.type === 'timeTravelers').map(matcher => matcher.matcher); + const availabilityMatchers = getScheduleMatchingGroup('timeTravelers', date); let ownedKeys; const { owned } = user.items.gear; const { mysteryItems } = user.purchased.plan; @@ -31,7 +31,7 @@ const timeTravelerStore = (user, date) => { k !== 'wondercon' && ownedKeys.indexOf(v.items[0].key) === -1 && (moment(k).isAfter('3000-01-01') - || availabilityMatchers.map(matcher => matcher(k)).every(matcher => matcher === true)) + || availabilityMatchers.match(k)) ) { m[k] = v; } diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index aea59c3894..1dc5c0e9f8 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -17,7 +17,7 @@ import featuredItems from '../content/shop-featuredItems'; import getOfficialPinnedItems from './getOfficialPinnedItems'; import { getClassName } from './getClassName'; -import { assembleScheduledMatchers } from '../content/constants/schedule'; +import { getScheduleMatchingGroup } from '../content/constants/schedule'; const shops = {}; @@ -71,10 +71,10 @@ shops.getMarketCategories = function getMarket (user, language) { text: i18n.t('magicHatchingPotions', language), notes: i18n.t('premiumPotionNoDropExplanation', language), }; - const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'premiumHatchingPotions').map(matcher => matcher.matcher); + const matchers = getScheduleMatchingGroup('premiumHatchingPotions'); premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions) .filter(hp => hp.limited - && matchers.map(matcher => matcher(hp.key)).every(matcher => matcher === true)) + && matchers.match(hp.key)) .map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language)), 'key'); if (premiumHatchingPotionsCategory.items.length > 0) { categories.push(premiumHatchingPotionsCategory); @@ -267,18 +267,16 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) * ] * */ - const scheduledMatchers = assembleScheduledMatchers(new Date()); const bundleCategory = { identifier: 'bundle', text: i18n.t('questBundles', language), }; - const bundleMatchers = scheduledMatchers.filter(matcher => matcher.type === 'bundles').map(matcher => matcher.matcher); - console.log(bundleMatchers); + const bundleMatchers = getScheduleMatchingGroup('bundles'); bundleCategory.items = sortBy(values(content.bundles) .filter(bundle => bundle.type === 'quests' - && bundleMatchers.map(matcher => matcher(bundle.key)).every(matcher => matcher === true)) + && bundleMatchers.match(bundle.key)) .map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language))); if (bundleCategory.items.length > 0) { @@ -295,10 +293,8 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) .filter(quest => quest.canBuy(user) && quest.category === type); if (type === 'pet' || type === 'hatchingPotion') { - const matchers = scheduledMatchers - .filter(matcher => matcher.type === `${type}Quests`).map(matcher => matcher.matcher); - filteredQuests = filteredQuests.filter(quest => matchers.map(matcher => matcher(quest.key)) - .every(matcher => matcher === true)); + const matchers = getScheduleMatchingGroup(`${type}Quests`); + filteredQuests = filteredQuests.filter(quest => matchers.match(quest.key)); } category.items = filteredQuests.map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language)); @@ -544,9 +540,9 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { const sets = []; const officialPinnedItems = getOfficialPinnedItems(); - const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'backgrounds').map(matcher => matcher.matcher); + const matchers = getScheduleMatchingGroup('backgrounds'); eachRight(content.backgrounds, (group, key) => { - if (matchers.map(matcher => matcher(key)).every(matcher => matcher === true)) { + if (matchers.match(key)) { const set = { identifier: key, text: i18n.t(key, language), diff --git a/website/common/script/ops/buy/buyQuestGem.js b/website/common/script/ops/buy/buyQuestGem.js index 392b8b5ebd..dbc5c100cf 100644 --- a/website/common/script/ops/buy/buyQuestGem.js +++ b/website/common/script/ops/buy/buyQuestGem.js @@ -8,7 +8,7 @@ import content from '../../content/index'; import { errorMessage } from '../../libs/errorMessage'; import { AbstractGemItemOperation } from './abstractBuyOperation'; -import { assembleScheduledMatchers } from '../../content/constants/schedule'; +import { getScheduleMatchingGroup } from '../../content/constants/schedule'; export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // eslint-disable-line import/prefer-default-export, max-len multiplePurchaseAllowed () { // eslint-disable-line class-methods-use-this @@ -52,9 +52,8 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // esli } } - const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === `${item.category}Quests`).map(matcher => matcher.matcher); - console.log(item, matchers); - if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + const matchers = getScheduleMatchingGroup(`${item.category}Quests`); + if (matchers.match(item.key)) { throw new NotAuthorized(this.i18n('notAvailable', { key: item.key })); } diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index c1989e33f6..e28f64c3b3 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -13,7 +13,7 @@ import { import { removeItemByPath } from '../pinnedGearUtils'; import getItemInfo from '../../libs/getItemInfo'; import updateUserBalance from '../updateUserBalance'; -import { assembleScheduledMatchers } from '../../content/constants/schedule'; +import { getScheduleMatchingGroup } from '../../content/constants/schedule'; function getItemAndPrice (user, type, key, req) { let item; @@ -55,8 +55,8 @@ async function purchaseItem (user, item, price, type, key) { if (user.markModified) user.markModified('items.gear.owned'); } else if (type === 'bundles') { const subType = item.type; - const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'bundles').map(matcher => matcher.matcher); - if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + const matchers = getScheduleMatchingGroup('bundles'); + if (!matchers.match(item.key)) { throw new NotAuthorized(i18n.t('notAvailable', { key: item.key })); } forEach(item.bundleKeys, bundledKey => { @@ -102,8 +102,8 @@ export default async function purchase (user, req = {}, analytics) { const { price, item } = getItemAndPrice(user, type, key, req); if (item.type === 'hatchingPotion' && item.premium === true) { - const matchers = assembleScheduledMatchers(new Date()).filter(matcher => matcher.type === 'premiumHatchingPotions').map(matcher => matcher.matcher); - if (matchers.length && !matchers.some(matcher => matcher(item.key))) { + const matchers = getScheduleMatchingGroup('premiumHatchingPotions'); + if (!matchers.match(item.key)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } } else if (!item.canBuy(user)) { diff --git a/website/common/script/ops/unlock.js b/website/common/script/ops/unlock.js index 3cc4b4cf01..a5b0b89cad 100644 --- a/website/common/script/ops/unlock.js +++ b/website/common/script/ops/unlock.js @@ -7,7 +7,7 @@ import { removeItemByPath } from './pinnedGearUtils'; import getItemInfo from '../libs/getItemInfo'; import content from '../content/index'; import updateUserBalance from './updateUserBalance'; -import { assembleScheduledMatchers } from '../content/constants/schedule'; +import { getScheduleMatchingGroup } from '../content/constants/schedule'; const incentiveBackgrounds = ['blue', 'green', 'red', 'purple', 'yellow']; @@ -225,12 +225,8 @@ export default async function unlock (user, req = {}, analytics) { const { set, items, paths } = getSet(setType, firstPath, req); if (isBackground) { - const matchers = assembleScheduledMatchers(new Date()) - .filter(matcher => matcher.type === 'backgrounds') - .map(matcher => matcher.matcher); - const isAvailable = matchers.map(matcher => matcher(set.key)) - .every(matcher => matcher === true); - if (!isAvailable) { + const matchers = getScheduleMatchingGroup('backgrounds'); + if (!matchers.match(set.key)) { throw new NotAuthorized(i18n.t('notAvailable', req.language)); } } From db4bec37e3268f55c3cfdb667fa7bc31705cf499 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 23:30:26 +0100 Subject: [PATCH 020/244] Implement new schedule system for seasonal shop --- .../script/content/constants/schedule.js | 261 +++++++++++------- website/common/script/libs/shops.js | 16 +- website/common/script/ops/buy/purchase.js | 6 + 3 files changed, 167 insertions(+), 116 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 1347ebcf8f..37ce22e3ac 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -46,35 +46,35 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'nudibranch', 'seaserpent', 'gryphon', 'yarn', 'axolotl', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'silver', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'winterQuests', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Aurora', 'Moonglow', 'IcySnow', - ]), + ], }, ], }, @@ -94,34 +94,34 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'rooster', 'slime', 'peacock', 'bunny', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'pinkMarble', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'cuddleBuddies', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'PolkaDot', 'Cupid', 'RoseGold', - ]), + ], }, ], }, @@ -141,33 +141,33 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'frog', 'spider', 'cow', 'pterodactyl', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ - ]), + items: [ + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'birdBuddies', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Birch', 'StainedGlass', 'Porcelain', - ]), + ], }, ], }, @@ -187,33 +187,33 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'snake', 'monkey', 'falcon', 'aligator', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'mossyStone', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'hugabug', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Shimmer', 'Glass', - ]), + ], }, ], }, @@ -233,33 +233,33 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'octopus', 'horse', 'kraken', 'sloth', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ - ]), + items: [ + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'splashyPals', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Floral', 'Fairy', 'RoseQuartz', - ]), + ], }, ], }, @@ -279,39 +279,39 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'trex', 'unicorn', 'veolociraptor', 'hippo', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'turquiose', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'rockingReptiles', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'delightfulDinos', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Rainbow', 'Sunshine', - ]), + ], }, ], }, @@ -331,40 +331,40 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'whale', 'seahorse', 'armadillo', 'guineapig', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'fluorite', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'winterQuests', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'aquaticAmigos', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Celestial', 'SandCastle', 'Watery', - ]), + ], }, ], }, @@ -384,34 +384,34 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'turtle', 'penguin', 'butterfly', 'cheetah', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'blackPearl', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'featheredFriends', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Aquatic', 'StarryNight', 'Sunset', - ]), + ], }, ], }, @@ -431,34 +431,34 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'squirrel', 'triceratops', 'treeling', 'beetle', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'bronze', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'farmFriends', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Glow', 'AutumnLeaf', 'Shadow', - ]), + ], }, ], }, @@ -478,34 +478,34 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'snail', 'rock', 'ferret', 'hedgehog', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'onyx', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'witchyFamiliars', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Vampire', 'Ghost', 'Spooky', - ]), + ], }, ], }, @@ -525,35 +525,35 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'sheep', 'kangaroo', 'owl', 'rat', 'badger', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'amber', - ]), + ], }, { type: 'bundles', - matcher: inListMatcher([ + items: [ 'forestFriends', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Ember', 'Frost', 'Thunderstorm', - ]), + ], }, ], }, @@ -573,28 +573,28 @@ export const MONTHLY_SCHEDULE = { [THIRD_RELEASE_DAY]: [ { type: 'petQuests', - matcher: inListMatcher([ + items: [ 'ghost_stag', 'trex_undead', 'harpy', 'sabretooth', 'dolphin', - ]), + ], }, { type: 'hatchingPotionQuests', - matcher: inListMatcher([ + items: [ 'ruby', - ]), + ], }, ], [FOURTH_RELEASE_DAY]: [ { type: 'premiumHatchingPotions', - matcher: inListMatcher([ + items: [ 'Peppermint', 'Holly', - ]), + ], }, ], }, @@ -605,25 +605,56 @@ export const GALA_SCHEDULE = { 0: [ { type: 'seasonalGear', - matcher: inListMatcher(SEASONAL_SETS.winter), + items: SEASONAL_SETS.winter, + }, + { + type: 'seasonalSpells', + items: [ + 'snowball', + ], + }, + { + type: 'seasonalQuests', + items: [ + 'evilsanta', + 'evilsanta2', + ], }, ], 1: [ { type: 'seasonalGear', - matcher: inListMatcher(SEASONAL_SETS.spring), + items: SEASONAL_SETS.spring, + }, + { + type: 'seasonalSpells', + items: [ + 'shinySeed', + ], }, ], 2: [ { type: 'seasonalGear', - matcher: inListMatcher(SEASONAL_SETS.fall), + items: SEASONAL_SETS.fall, + }, + { + type: 'seasonalSpells', + items: [ + 'spookySparkles', + ], }, ], 3: [ { type: 'seasonalGear', - matcher: inListMatcher(SEASONAL_SETS.summer), + items: SEASONAL_SETS.summer, + }, + { + type: 'seasonalSpells', + items: [ + 'seafoam', + ], }, ], }; @@ -659,17 +690,35 @@ export function getScheduleMatchingGroup (type, date) { if (!cachedScheduleMatchers) { cachedScheduleMatchers = {}; assembleScheduledMatchers(date !== undefined ? date : new Date()).forEach(matcher => { - if (cachedScheduleMatchers[matcher.type]) { - cachedScheduleMatchers[matcher.type].matchers.push(matcher.matcher); - } else { + if (!cachedScheduleMatchers[matcher.type]) { cachedScheduleMatchers[matcher.type] = { - matchers: [matcher.matcher], + matchers: [], + items: [], match (key) { + if (this.items.length > 0 && !inListMatcher(this.items)(key)) { + console.log(this.items); + console.log(key, 'not in list'); + return false; + } + console.log(this.matchers.every(m => m(key))); return this.matchers.every(m => m(key)); }, }; } + if (matcher.matcher instanceof Function) { + cachedScheduleMatchers[matcher.type].matchers.push(matcher.matcher); + } else if (matcher.items instanceof Array) { + cachedScheduleMatchers[matcher.type].items.push(...matcher.items); + } }); } + if (!cachedScheduleMatchers[type]) { + return { + items: [], + match () { + return true; + }, + }; + } return cachedScheduleMatchers[type]; } diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 1dc5c0e9f8..d486d8e015 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -474,19 +474,15 @@ shops.getSeasonalShop = function getSeasonalShop (user, language) { shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) { const officialPinnedItems = getOfficialPinnedItems(user); - const AVAILABLE_SPELLS = [ - ...seasonalShopConfig.availableSpells, - ]; - - const AVAILABLE_QUESTS = [ - ...seasonalShopConfig.availableQuests, - ]; + const spellMatcher = getScheduleMatchingGroup('seasonalSpells'); + const questMatcher = getScheduleMatchingGroup('seasonalQuests'); + const gearMatcher = getScheduleMatchingGroup('seasonalGear'); const categories = []; const spells = pickBy( content.spells.special, - (spell, key) => AVAILABLE_SPELLS.indexOf(key) !== -1, + (spell, key) => spellMatcher.match(key), ); if (keys(spells).length > 0) { @@ -503,7 +499,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang categories.push(category); } - const quests = pickBy(content.quests, (quest, key) => AVAILABLE_QUESTS.indexOf(key) !== -1); + const quests = pickBy(content.quests, (quest, key) => questMatcher.match(key)); if (keys(quests).length > 0) { const category = { @@ -516,7 +512,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang categories.push(category); } - for (const set of seasonalShopConfig.availableSets) { + for (const set of gearMatcher.items) { const category = { identifier: set, text: i18n.t(set), diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index e28f64c3b3..7751f17e30 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -106,6 +106,12 @@ export default async function purchase (user, req = {}, analytics) { if (!matchers.match(item.key)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } + } else if (item.event && item.event.gear) { + const matchers = getScheduleMatchingGroup('seasonalGear'); + console.log(matchers); + if (!matchers.match(item.set)) { + throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); + } } else if (!item.canBuy(user)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } From 982069df361209a6de2c04c69fbf98ecffd2f2fa Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 31 Jan 2024 23:31:14 +0100 Subject: [PATCH 021/244] remove logs --- website/common/script/content/constants/schedule.js | 4 ---- website/common/script/ops/buy/purchase.js | 1 - 2 files changed, 5 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 37ce22e3ac..eed010bfc6 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -660,7 +660,6 @@ export const GALA_SCHEDULE = { }; export function assembleScheduledMatchers (date) { - console.log('Assembling new Schedule!'); const items = []; const month = date instanceof moment ? date.month() : date.getMonth(); const todayDay = date instanceof moment ? date.date() : date.getDate(); @@ -696,11 +695,8 @@ export function getScheduleMatchingGroup (type, date) { items: [], match (key) { if (this.items.length > 0 && !inListMatcher(this.items)(key)) { - console.log(this.items); - console.log(key, 'not in list'); return false; } - console.log(this.matchers.every(m => m(key))); return this.matchers.every(m => m(key)); }, }; diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 7751f17e30..028755d035 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -108,7 +108,6 @@ export default async function purchase (user, req = {}, analytics) { } } else if (item.event && item.event.gear) { const matchers = getScheduleMatchingGroup('seasonalGear'); - console.log(matchers); if (!matchers.match(item.set)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } From f99ddbe60fe2423b4a9c3c3588262669241b1042 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 1 Feb 2024 15:06:32 +0100 Subject: [PATCH 022/244] display customizations in new shop --- website/client/src/components/header/menu.vue | 6 + .../components/shops/customizations/index.vue | 499 ++++++++++++++++++ website/client/src/components/shops/index.vue | 6 + website/client/src/router/index.js | 2 + website/common/locales/en/character.json | 3 + website/common/locales/en/generic.json | 1 + .../common/script/content/constants/index.js | 1 + .../script/content/constants/schedule.js | 103 +++- website/common/script/libs/getItemInfo.js | 90 ++++ .../script/libs/shops-seasonal.config.js | 61 +-- website/common/script/libs/shops.js | 111 +++- website/server/controllers/api-v3/shops.js | 22 + 12 files changed, 836 insertions(+), 69 deletions(-) create mode 100644 website/client/src/components/shops/customizations/index.vue diff --git a/website/client/src/components/header/menu.vue b/website/client/src/components/header/menu.vue index 900cf0ec93..ae75b91be1 100644 --- a/website/client/src/components/header/menu.vue +++ b/website/client/src/components/header/menu.vue @@ -146,6 +146,12 @@ > {{ $t('titleTimeTravelers') }}
+ + {{ $t('titleCustomizations') }} + +
+
+ +
+ +
+ + + +
+

+ {{ $t('hidePinned') }} +

+ +
+
+
+
+
+
+
+ +
+
+
+
+
+ {{ $t('sortBy') }} + +
+
+ +
+ +

+ {{ category.text }} +

+ + + +
+

{{ items[0].set.text() }}

+ + + +
+
+ {{ $t('purchaseAll') }} +
+ {{ items[0].set.setPrice }} +
+
+
+
+
+
+ + + + + + + diff --git a/website/client/src/components/shops/index.vue b/website/client/src/components/shops/index.vue index d12a19c46e..953ebc5afb 100644 --- a/website/client/src/components/shops/index.vue +++ b/website/client/src/components/shops/index.vue @@ -26,6 +26,12 @@ > {{ $t('titleTimeTravelers') }} + + {{ $t('titleCustomizations') }} +
diff --git a/website/client/src/router/index.js b/website/client/src/router/index.js index f3e87d4584..7794721ee0 100644 --- a/website/client/src/router/index.js +++ b/website/client/src/router/index.js @@ -62,6 +62,7 @@ const MarketPage = () => import(/* webpackChunkName: "shops-market" */'@/compone const QuestsPage = () => import(/* webpackChunkName: "shops-quest" */'@/components/shops/quests/index'); const SeasonalPage = () => import(/* webpackChunkName: "shops-seasonal" */'@/components/shops/seasonal/index'); const TimeTravelersPage = () => import(/* webpackChunkName: "shops-timetravelers" */'@/components/shops/timeTravelers/index'); +const CustomizationsShopPage = () => import(/* webpackChunkName: "shops-customizations" */'@/components/shops/customizations/index'); Vue.use(VueRouter); @@ -113,6 +114,7 @@ const router = new VueRouter({ { name: 'quests', path: 'quests', component: QuestsPage }, { name: 'seasonal', path: 'seasonal', component: SeasonalPage }, { name: 'time', path: 'time', component: TimeTravelersPage }, + { name: 'customizations', path: 'customizations', component: CustomizationsShopPage }, ], }, { name: 'party', path: '/party', component: GroupPage }, diff --git a/website/common/locales/en/character.json b/website/common/locales/en/character.json index 4b6bdd7ecd..5a699b970e 100644 --- a/website/common/locales/en/character.json +++ b/website/common/locales/en/character.json @@ -34,6 +34,9 @@ "bodyFacialHair": "Facial Hair", "beard": "Beard", "mustache": "Mustache", + "titleFacialHair": "Facial Hair", + "titleHaircolor": "Hair Colors", + "titleHairbase": "Hair styles", "flower": "Flower", "accent": "Accent", "headband": "Headband", diff --git a/website/common/locales/en/generic.json b/website/common/locales/en/generic.json index bfaa238727..3b156d53b0 100644 --- a/website/common/locales/en/generic.json +++ b/website/common/locales/en/generic.json @@ -8,6 +8,7 @@ "gotIt": "Got it!", "titleTimeTravelers": "Time Travelers", "titleSeasonalShop": "Seasonal Shop", + "titleCustomizations": "Customizations", "saveEdits": "Save Edits", "showMore": "Show More", "showLess": "Show Less", diff --git a/website/common/script/content/constants/index.js b/website/common/script/content/constants/index.js index 358edb82b1..624285ec06 100644 --- a/website/common/script/content/constants/index.js +++ b/website/common/script/content/constants/index.js @@ -40,3 +40,4 @@ export { default as QUEST_PETS } from '../quests/pets'; export { default as QUEST_POTIONS } from '../quests/potions'; export { default as QUEST_TIME_TRAVEL } from '../quests/timeTravel'; export { default as QUEST_WORLD } from '../quests/world'; +export { getScheduleMatchingGroup, getCurrentGalaKey } from './schedule'; diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index eed010bfc6..c5a0bbbafa 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -3,7 +3,6 @@ import SEASONAL_SETS from './seasonalSets'; function backgroundMatcher (month1, month2, oddYear) { return function call (key) { - if (!key.startsWith('backgrounds')) return true; const keyLength = key.length; const month = parseInt(key.substring(keyLength - 6, keyLength - 4), 10); return (month === month1 || month === month2) @@ -24,6 +23,26 @@ function inListMatcher (list) { }; } +const ALWAYS_AVAILABLE_CUSTOMIZATIONS = [ + 'animalSkins', + 'rainbowSkins', + 'rainbowHairColors', + 'specialShirts', + 'facialHair', + 'baseHair1', + 'baseHair2', + 'baseHair3', +]; + +function customizationMatcher (list) { + return function call (item) { + if (ALWAYS_AVAILABLE_CUSTOMIZATIONS.indexOf(item) !== -1) { + return true; + } + return list.indexOf(item) !== -1; + }; +} + export const FIRST_RELEASE_DAY = 1; export const SECOND_RELEASE_DAY = 7; export const THIRD_RELEASE_DAY = 14; @@ -601,6 +620,12 @@ export const MONTHLY_SCHEDULE = { }; export const GALA_SWITCHOVER_DAY = 21; +export const GALA_KEYS = [ + 'winter', + 'spring', + 'summer', + 'fall', +]; export const GALA_SCHEDULE = { 0: [ { @@ -620,6 +645,13 @@ export const GALA_SCHEDULE = { 'evilsanta2', ], }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'winteryHairColors', + 'winterySkins', + ]), + }, ], 1: [ { @@ -632,20 +664,15 @@ export const GALA_SCHEDULE = { 'shinySeed', ], }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'shimmerHairColors', + 'pastelSkins', + ]), + }, ], 2: [ - { - type: 'seasonalGear', - items: SEASONAL_SETS.fall, - }, - { - type: 'seasonalSpells', - items: [ - 'spookySparkles', - ], - }, - ], - 3: [ { type: 'seasonalGear', items: SEASONAL_SETS.summer, @@ -656,9 +683,45 @@ export const GALA_SCHEDULE = { 'seafoam', ], }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'splashySkins', + ]), + }, + ], + 3: [ + { + type: 'seasonalGear', + items: SEASONAL_SETS.fall, + }, + { + type: 'seasonalSpells', + items: [ + 'spookySparkles', + ], + }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'hauntedHairColors', + 'supernaturalSkins', + ]), + }, ], }; +function getGalaIndex (date) { + const month = date instanceof moment ? date.month() : date.getMonth(); + const todayDay = date instanceof moment ? date.date() : date.getDate(); + let galaMonth = month; + const galaCount = Object.keys(GALA_SCHEDULE).length; + if (todayDay >= GALA_SWITCHOVER_DAY) { + galaMonth += 1; + } + return parseInt((galaCount / 12) * galaMonth, 10); +} + export function assembleScheduledMatchers (date) { const items = []; const month = date instanceof moment ? date.month() : date.getMonth(); @@ -674,12 +737,8 @@ export function assembleScheduledMatchers (date) { items.push(...value); } } - let galaMonth = month; - const galaCount = Object.keys(GALA_SCHEDULE).length; - if (todayDay >= GALA_SWITCHOVER_DAY) { - galaMonth += 1; - } - items.push(...GALA_SCHEDULE[parseInt((galaCount / 12) * galaMonth, 10)]); + + items.push(...GALA_SCHEDULE[getGalaIndex(date)]); return items; } @@ -688,7 +747,7 @@ let cachedScheduleMatchers = null; export function getScheduleMatchingGroup (type, date) { if (!cachedScheduleMatchers) { cachedScheduleMatchers = {}; - assembleScheduledMatchers(date !== undefined ? date : new Date()).forEach(matcher => { + assembleScheduledMatchers(date || new Date()).forEach(matcher => { if (!cachedScheduleMatchers[matcher.type]) { cachedScheduleMatchers[matcher.type] = { matchers: [], @@ -718,3 +777,7 @@ export function getScheduleMatchingGroup (type, date) { } return cachedScheduleMatchers[type]; } + +export function getCurrentGalaKey (date) { + return GALA_KEYS[getGalaIndex(date || new Date())]; +} diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index 59df678a01..ff9477b30c 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -379,6 +379,96 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang }; break; } + case 'haircolor': { + itemInfo = { + key: item.key, + class: `hair_bangs_${user.preferences.hair.bangs}_${item.key}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.hair.color.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } + case 'hairbase': { + itemInfo = { + key: item.key, + class: `hair_base_${item.key}_${user.preferences.hair.color}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.hair.base.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } + case 'hairmustache': { + itemInfo = { + key: item.key, + class: `hair_mustache_${item.key}_${user.preferences.hair.color}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.hair.mustache.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } + case 'hairbeard': { + itemInfo = { + key: item.key, + class: `hair_beard_${item.key}_${user.preferences.hair.color}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.hair.beard.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } + case 'shirt': { + itemInfo = { + key: item.key, + class: `${user.preferences.size}_shirt_${item.key}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.shirt.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } + case 'skin': { + itemInfo = { + key: item.key, + class: `skin_${item.key}`, + text: item.key, + notes: '', + value: item.price, + set: item.set, + locked: false, + currency: 'gems', + path: `customizations.skin.${item.key}`, + pinType: 'timeTravelersStable', + }; + break; + } } if (itemInfo) { diff --git a/website/common/script/libs/shops-seasonal.config.js b/website/common/script/libs/shops-seasonal.config.js index 62ebde0958..d625dc601f 100644 --- a/website/common/script/libs/shops-seasonal.config.js +++ b/website/common/script/libs/shops-seasonal.config.js @@ -1,50 +1,25 @@ -import find from 'lodash/find'; import upperFirst from 'lodash/upperFirst'; -import moment from 'moment'; import { - EVENTS, - SEASONAL_SETS, + getCurrentGalaKey, } from '../content/constants'; +import { + armor, +} from '../content/gear/sets/special'; -const CURRENT_EVENT = find(EVENTS, event => moment().isBetween(event.start, event.end) - && ['winter', 'spring', 'summer', 'fall'].includes(event.season)); +const CURRENT_EVENT_KEY = getCurrentGalaKey(); + +function getCurrentSeasonalSets () { + const year = new Date().getFullYear(); + return { + rogue: armor[`${CURRENT_EVENT_KEY}${year}Rogue`].set, + warrior: armor[`${CURRENT_EVENT_KEY}${year}Warrior`].set, + wizard: armor[`${CURRENT_EVENT_KEY}${year}Mage`].set, + healer: armor[`${CURRENT_EVENT_KEY}${year}Healer`].set, + }; +} export default { - opened: CURRENT_EVENT, - - currentSeason: CURRENT_EVENT ? upperFirst(CURRENT_EVENT.season) : 'Closed', - - dateRange: { - start: CURRENT_EVENT ? moment(CURRENT_EVENT.start) : moment().subtract(1, 'days').toDate(), - end: CURRENT_EVENT ? moment(CURRENT_EVENT.end) : moment().subtract(1, 'seconds').toDate(), - }, - - availableSets: CURRENT_EVENT - ? [ - ...SEASONAL_SETS[CURRENT_EVENT.season], - ] - : [], - - pinnedSets: CURRENT_EVENT - ? { - rogue: 'spring2024MeltingSnowRogueSet', - warrior: 'spring2024FluoriteWarriorSet', - wizard: 'spring2024HibiscusMageSet', - healer: 'spring2024BluebirdHealerSet', - } - : {}, - - availableSpells: CURRENT_EVENT && moment().isBetween('2024-04-18T08:00-04:00', CURRENT_EVENT.end) - ? [ - 'shinySeed', - ] - : [], - - availableQuests: CURRENT_EVENT && moment().isBetween('2024-03-26T08:00-04:00', CURRENT_EVENT.end) - ? [ - 'egg', - ] - : [], - - featuredSet: 'spring2020LapisLazuliRogueSet', + currentSeason: CURRENT_EVENT_KEY ? upperFirst(CURRENT_EVENT_KEY) : 'Closed', + pinnedSets: getCurrentSeasonalSets(), + featuredSet: 'winter2019PoinsettiaSet', }; diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index d486d8e015..944ebf489d 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -449,8 +449,8 @@ shops.getSeasonalShop = function getSeasonalShop (user, language) { identifier: 'seasonalShop', text: i18n.t('seasonalShop'), notes: i18n.t(`seasonalShop${seasonalShopConfig.currentSeason}Text`), - imageName: seasonalShopConfig.opened ? 'seasonalshop_open' : 'seasonalshop_closed', - opened: seasonalShopConfig.opened, + imageName: 'seasonalshop_open', + opened: true, categories: this.getSeasonalShopCategories(user, language), featured: { text: i18n.t(seasonalShopConfig.featuredSet), @@ -467,10 +467,6 @@ shops.getSeasonalShop = function getSeasonalShop (user, language) { 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 officialPinnedItems = getOfficialPinnedItems(user); @@ -553,4 +549,107 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { return sets; }; +/* Customization Shop */ + +shops.getCustomizationShop = function getCustomizationShop (user, language) { + return { + identifier: 'customizationShop', + text: i18n.t('titleCustomizations'), + notes: i18n.t('timeTravelersPopover'), + imageName: 'npc_timetravelers_active', + categories: shops.getCustomizationShopCategories(user, language), + }; +}; + +shops.getCustomizationShopCategories = function getCustomizationShopCategories (user, language) { + const categories = []; + const officialPinnedItems = getOfficialPinnedItems(user); + + const backgroundCategory = { + identifier: 'backgrounds', + text: i18n.t('backgrounds', language), + items: [], + }; + + 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); + } + }); + } + }); + categories.push(backgroundCategory); + + const facialHairCategory = { + identifier: 'facialHair', + text: i18n.t('titleFacialHair', language), + items: [], + }; + 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); + } + }); + // only add the facial hair category once + if (hairType !== 'beard') { + categories.push(category); + } + }); + + each(['shirt', 'skin'], type => { + const category = { + identifier: type, + text: i18n.t(type, 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); + } + }); + categories.push(category); + }); + + return categories; +}; + export default shops; diff --git a/website/server/controllers/api-v3/shops.js b/website/server/controllers/api-v3/shops.js index efaaff2395..349abd83df 100644 --- a/website/server/controllers/api-v3/shops.js +++ b/website/server/controllers/api-v3/shops.js @@ -144,4 +144,26 @@ api.getBackgroundShopItems = { }, }; +/** + * @apiIgnore + * @api {get} /api/v3/shops/customizations get the available items for the backgrounds shop + * @apiName GetCustomizationShopItems + * @apiGroup Shops + * + * @apiSuccess {Object} data List of available backgrounds + * @apiSuccess {string} message Success message + */ +api.getBackgroundShopItems = { + method: 'GET', + url: '/shops/customizations', + middlewares: [authWithHeaders()], + async handler (req, res) { + const { user } = res.locals; + + const resObject = shops.getCustomizationShop(user, req.language); + + res.respond(200, resObject); + }, +}; + export default api; From d11e95ab268abbca2553dc18b63d37166f8b8f2d Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 5 Feb 2024 11:35:22 +0100 Subject: [PATCH 023/244] change mobile filter defaults to a list --- website/server/controllers/api-v3/content.js | 21 +++++++++++++------- website/server/libs/content.js | 2 +- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index 5c3a3c5fd4..b547459d2f 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -6,10 +6,14 @@ const IS_PROD = nconf.get('IS_PROD'); const api = {}; -const MOBILE_FILTER = `achievements,questSeriesAchievements,animalColorAchievements,animalSetAchievements,stableAchievements, -mystery,bundles,loginIncentives,pets,premiumPets,specialPets,questPets,wackyPets,mounts,premiumMounts,specialMounts,questMounts, -events,dropEggs,questEggs,dropHatchingPotions,premiumHatchingPotions,wackyHatchingPotions,backgroundsFlat,questsByLevel,gear.tree, -tasksByCategory,userDefaults,timeTravelStable,gearTypes,cardTypes`; +const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements', +'stableAchievements', 'mystery', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets', +'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions', +'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory', +'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes']; + +const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background']; +const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds']; /** * @api {get} /api/v3/content Get all available content objects @@ -70,17 +74,20 @@ api.getContent = { language = proposedLang; } + let filter_list = []; let filter = req.query.filter || ''; // apply defaults for mobile clients if (filter === '') { if (req.headers['x-client'] === 'habitica-android') { - filter = `${MOBILE_FILTER},appearances.background`; + filter_list = ANDROID_FILTER; } else if (req.headers['x-client'] === 'habitica-ios') { - filter = `${MOBILE_FILTER},backgrounds`; + filter_list = IOS_FILTER; } + } else { + filter_list = filter.split(','); } - serveContent(res, language, filter, IS_PROD); + serveContent(res, language, filter_list, IS_PROD); }, }; diff --git a/website/server/libs/content.js b/website/server/libs/content.js index 94321cdf72..4cb0b3b690 100644 --- a/website/server/libs/content.js +++ b/website/server/libs/content.js @@ -55,7 +55,7 @@ export function hashForFilter (filter) { export function serveContent (res, language, filter, isProd) { // Build usable filter object const filterObj = {}; - filter.split(',').forEach(item => { + filter.forEach(item => { if (item.includes('.')) { const [key, subkey] = item.split('.'); if (!filterObj[key]) { From 93011f182f2cfed5e624da3e2429313daa684838 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 5 Feb 2024 15:03:48 +0100 Subject: [PATCH 024/244] Fix lint and test --- test/api/unit/libs/content.test.js | 8 ++++---- website/server/controllers/api-v3/content.js | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/api/unit/libs/content.test.js b/test/api/unit/libs/content.test.js index 36a626151f..3dac94d4af 100644 --- a/test/api/unit/libs/content.test.js +++ b/test/api/unit/libs/content.test.js @@ -34,7 +34,7 @@ describe('contentLib', () => { }); it('generates a hash for a filter', () => { - const hash = contentLib.hashForFilter('backgroundsFlat,gear.flat'); + const hash = contentLib.hashForFilter(['backgroundsFlat', 'gear.flat']); expect(hash).to.equal('-1791877526'); }); @@ -46,7 +46,7 @@ describe('contentLib', () => { it('serves filtered content', () => { const resSpy = generateRes(); - contentLib.serveContent(resSpy, 'en', 'backgroundsFlat,gear.flat', false); + contentLib.serveContent(resSpy, 'en', ['backgroundsFlat', 'gear.flat'], false); expect(resSpy.send).to.have.been.calledOnce; }); @@ -81,7 +81,7 @@ describe('contentLib', () => { }); it('caches filtered requests', async () => { - const filter = 'backgroundsFlat,gear.flat'; + const filter = ['backgroundsFlat', 'gear.flat']; const hash = contentLib.hashForFilter(filter); expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.false; contentLib.serveContent(resSpy, 'en', filter, true); @@ -89,7 +89,7 @@ describe('contentLib', () => { }); it('serves filtered cached requests', async () => { - const filter = 'backgroundsFlat,gear.flat'; + const filter = ['backgroundsFlat', 'gear.flat']; const hash = contentLib.hashForFilter(filter); fs.writeFileSync( `${contentLib.CONTENT_CACHE_PATH}en${hash}.json`, diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index b547459d2f..c742bed3ff 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -7,10 +7,10 @@ const IS_PROD = nconf.get('IS_PROD'); const api = {}; const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAchievements', 'animalSetAchievements', -'stableAchievements', 'mystery', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets', -'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions', -'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory', -'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes']; + 'stableAchievements', 'mystery', 'bundles', 'loginIncentives', 'pets', 'premiumPets', 'specialPets', 'questPets', + 'wackyPets', 'mounts', 'premiumMounts,specialMounts,questMounts', 'events', 'dropEggs', 'questEggs', 'dropHatchingPotions', + 'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory', + 'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes']; const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background']; const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds']; @@ -74,20 +74,20 @@ api.getContent = { language = proposedLang; } - let filter_list = []; - let filter = req.query.filter || ''; + let filterList = []; + const filter = req.query.filter || ''; // apply defaults for mobile clients if (filter === '') { if (req.headers['x-client'] === 'habitica-android') { - filter_list = ANDROID_FILTER; + filterList = ANDROID_FILTER; } else if (req.headers['x-client'] === 'habitica-ios') { - filter_list = IOS_FILTER; + filterList = IOS_FILTER; } } else { - filter_list = filter.split(','); + filterList = filter.split(','); } - serveContent(res, language, filter_list, IS_PROD); + serveContent(res, language, filterList, IS_PROD); }, }; From 2dfe5585ebb3feb2504d6b87eb7d04678930f7c3 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 5 Feb 2024 15:14:16 +0100 Subject: [PATCH 025/244] fix --- test/api/unit/libs/content.test.js | 8 ++++---- website/server/controllers/api-v3/content.js | 13 +++++-------- website/server/libs/content.js | 2 +- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/test/api/unit/libs/content.test.js b/test/api/unit/libs/content.test.js index 3dac94d4af..36a626151f 100644 --- a/test/api/unit/libs/content.test.js +++ b/test/api/unit/libs/content.test.js @@ -34,7 +34,7 @@ describe('contentLib', () => { }); it('generates a hash for a filter', () => { - const hash = contentLib.hashForFilter(['backgroundsFlat', 'gear.flat']); + const hash = contentLib.hashForFilter('backgroundsFlat,gear.flat'); expect(hash).to.equal('-1791877526'); }); @@ -46,7 +46,7 @@ describe('contentLib', () => { it('serves filtered content', () => { const resSpy = generateRes(); - contentLib.serveContent(resSpy, 'en', ['backgroundsFlat', 'gear.flat'], false); + contentLib.serveContent(resSpy, 'en', 'backgroundsFlat,gear.flat', false); expect(resSpy.send).to.have.been.calledOnce; }); @@ -81,7 +81,7 @@ describe('contentLib', () => { }); it('caches filtered requests', async () => { - const filter = ['backgroundsFlat', 'gear.flat']; + const filter = 'backgroundsFlat,gear.flat'; const hash = contentLib.hashForFilter(filter); expect(fs.existsSync(`${contentLib.CONTENT_CACHE_PATH}en${hash}.json`)).to.be.false; contentLib.serveContent(resSpy, 'en', filter, true); @@ -89,7 +89,7 @@ describe('contentLib', () => { }); it('serves filtered cached requests', async () => { - const filter = ['backgroundsFlat', 'gear.flat']; + const filter = 'backgroundsFlat,gear.flat'; const hash = contentLib.hashForFilter(filter); fs.writeFileSync( `${contentLib.CONTENT_CACHE_PATH}en${hash}.json`, diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index c742bed3ff..5c8d6ab347 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -12,8 +12,8 @@ const MOBILE_FILTER = ['achievements', 'questSeriesAchievements', 'animalColorAc 'premiumHatchingPotions', 'wackyHatchingPotions', 'backgroundsFlat', 'questsByLevel', 'gear.tree', 'tasksByCategory', 'userDefaults', 'timeTravelStable', 'gearTypes', 'cardTypes']; -const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background']; -const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds']; +const ANDROID_FILTER = [...MOBILE_FILTER, 'appearances.background'].join(','); +const IOS_FILTER = [...MOBILE_FILTER, 'backgrounds'].join(','); /** * @api {get} /api/v3/content Get all available content objects @@ -74,20 +74,17 @@ api.getContent = { language = proposedLang; } - let filterList = []; const filter = req.query.filter || ''; // apply defaults for mobile clients if (filter === '') { if (req.headers['x-client'] === 'habitica-android') { - filterList = ANDROID_FILTER; + filter = ANDROID_FILTER; } else if (req.headers['x-client'] === 'habitica-ios') { - filterList = IOS_FILTER; + filter = IOS_FILTER; } - } else { - filterList = filter.split(','); } - serveContent(res, language, filterList, IS_PROD); + serveContent(res, language, filter, IS_PROD); }, }; diff --git a/website/server/libs/content.js b/website/server/libs/content.js index 4cb0b3b690..94321cdf72 100644 --- a/website/server/libs/content.js +++ b/website/server/libs/content.js @@ -55,7 +55,7 @@ export function hashForFilter (filter) { export function serveContent (res, language, filter, isProd) { // Build usable filter object const filterObj = {}; - filter.forEach(item => { + filter.split(',').forEach(item => { if (item.includes('.')) { const [key, subkey] = item.split('.'); if (!filterObj[key]) { From 127f105934c5b57c0285bca0436edfe41983de07 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Mon, 5 Feb 2024 15:14:32 +0100 Subject: [PATCH 026/244] typo --- website/server/controllers/api-v3/content.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/server/controllers/api-v3/content.js b/website/server/controllers/api-v3/content.js index 5c8d6ab347..0666cd6d28 100644 --- a/website/server/controllers/api-v3/content.js +++ b/website/server/controllers/api-v3/content.js @@ -74,7 +74,7 @@ api.getContent = { language = proposedLang; } - const filter = req.query.filter || ''; + let filter = req.query.filter || ''; // apply defaults for mobile clients if (filter === '') { if (req.headers['x-client'] === 'habitica-android') { From 1b12e9d8b7859e4644a346ff96ef89ca47845a88 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 13 Feb 2024 09:41:39 +0100 Subject: [PATCH 027/244] proof of concept for time travel --- package-lock.json | 13 ------------- website/client/package-lock.json | 13 ------------- website/client/src/components/appFooter.vue | 3 +++ website/client/src/main.js | 17 +++++++++++++++++ website/server/server.js | 18 ++++++++++++++++++ 5 files changed, 38 insertions(+), 26 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3362e5f72b..4ede53b72a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9705,19 +9705,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/website/client/package-lock.json b/website/client/package-lock.json index 5e2ddeca8c..f1584f358c 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -6982,19 +6982,6 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index fa90719f14..b0f19266d5 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -300,6 +300,9 @@ > Toggle Debug Menu +
+ Today is {{ new Date() }} +
{ + if (jumped) { + jumped = false; + return; + } + jumped = true; + clock.jump(36000); +}, 1000); + const vueInstance = new Vue({ el: '#app', router, diff --git a/website/server/server.js b/website/server/server.js index c952da4b04..852a1ccbd9 100644 --- a/website/server/server.js +++ b/website/server/server.js @@ -2,6 +2,7 @@ import nconf from 'nconf'; import express from 'express'; import http from 'http'; import logger from './libs/logger'; +import sinon from 'sinon'; // Setup translations // Must come before attach middlewares so Mongoose validations can use translations @@ -25,6 +26,23 @@ app.set('port', nconf.get('PORT')); attachMiddlewares(app, server); +const time = new Date(2024, 2, 18); +const clock = sinon.useFakeTimers({ + now: time, + shouldAdvanceTime: true, +}); + +var jumped = false; +setInterval(() => { + if (jumped) { + jumped = false; + return; + } + jumped = true; + console.log('Jumping time'); + clock.jump(36000); +}, 1000); + server.on('request', app); server.listen(app.get('port'), () => { logger.info(`Express server listening on port ${app.get('port')}`); From 593524905e2bdd6fc744fe0e06186a357285133c Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 15 Feb 2024 13:09:46 +0100 Subject: [PATCH 028/244] allow admins to manipulate time on test servers --- config.json.example | 3 +- website/client/src/components/appFooter.vue | 40 +++++++++++++-- website/client/src/main.js | 29 ++++++----- website/client/vue.config.js | 1 + website/server/controllers/api-v3/debug.js | 55 +++++++++++++++++++++ website/server/server.js | 18 ------- 6 files changed, 109 insertions(+), 37 deletions(-) diff --git a/config.json.example b/config.json.example index 62c3439cd2..4a0bb02756 100644 --- a/config.json.example +++ b/config.json.example @@ -87,5 +87,6 @@ "REDIS_HOST": "aaabbbcccdddeeefff", "REDIS_PORT": "1234", "REDIS_PASSWORD": "12345678", - "TRUSTED_DOMAINS": "localhost,https://habitica.com" + "TRUSTED_DOMAINS": "localhost,https://habitica.com", + "ENABLE_TIME_TRAVEL": "false" } diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index b0f19266d5..f1bb0163de 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -290,6 +290,31 @@ >{{ $t('terms') }}
+
+ -1 Day + -7 Days + -30 Days +
+ Time Traveling! It is {{ new Date().toLocaleString() }} +
+ +1 Day + +7 Days + +30 Days +
+
Toggle Debug Menu -
- Today is {{ new Date() }} -
0) { + Vue.config.clock.jump(amount * 24 * 60 * 60 * 1000); + } else { + Vue.config.clock.setSystemTime(moment().add(amount, 'days').toDate()); + } + }, addExp () { // @TODO: Name these variables better let exp = 0; diff --git a/website/client/src/main.js b/website/client/src/main.js index 756e2d60d7..929d636a6e 100644 --- a/website/client/src/main.js +++ b/website/client/src/main.js @@ -1,5 +1,4 @@ import Vue from 'vue'; -import sinon from 'sinon'; import BootstrapVue from 'bootstrap-vue'; import Fragment from 'vue-fragment'; import AppComponent from './app'; @@ -14,7 +13,6 @@ import './filters/registerGlobals'; import i18n from './libs/i18n'; import 'smartbanner.js/dist/smartbanner'; -let jumped = false; const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env // Configure Vue global options, see https://vuejs.org/v2/api/#Global-Config @@ -37,20 +35,21 @@ setUpLogging(); setupAnalytics(); // just create queues for analytics, no scripts loaded at this time const store = getStore(); -const time = new Date(2024, 2, 18); -const clock = sinon.useFakeTimers({ - now: time, - shouldAdvanceTime: true, -}); +if (process.env.ENABLE_TIME_TRAVEL) { + (async () => { + const sinon = await import('sinon'); + const axios = await import('axios'); + const response = await axios.get('/api/v4/debug/time-travel-time'); + console.log(response.data.data.time); + const time = new Date(response.data.data.time); + Vue.config.clock = sinon.useFakeTimers({ + now: time, + shouldAdvanceTime: true, + }); -setInterval(() => { - if (jumped) { - jumped = false; - return; - } - jumped = true; - clock.jump(36000); -}, 1000); + console.log('Time travel mode activated. It is now', new Date()); + })(); +} const vueInstance = new Vue({ el: '#app', diff --git a/website/client/vue.config.js b/website/client/vue.config.js index 516fe93398..f6673adbcf 100644 --- a/website/client/vue.config.js +++ b/website/client/vue.config.js @@ -28,6 +28,7 @@ const envVars = [ 'AMPLITUDE_KEY', 'LOGGLY_CLIENT_TOKEN', 'TRUSTED_DOMAINS', + 'ENABLE_TIME_TRAVEL', // TODO necessary? if yes how not to mess up with vue cli? 'NODE_ENV' ]; diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 0b80667343..5c23f32f6d 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -1,4 +1,6 @@ import _ from 'lodash'; +import nconf from 'nconf'; +import moment from 'moment'; import { authWithHeaders } from '../../middlewares/auth'; import ensureDevelpmentMode from '../../middlewares/ensureDevelpmentMode'; import { BadRequest } from '../../libs/errors'; @@ -201,4 +203,57 @@ api.questProgress = { }, }; +let clock; +if (nconf.get('ENABLE_TIME_TRAVEL')) { + (async () => { + const sinon = await import('sinon'); + const time = new Date(); + clock = sinon.useFakeTimers({ + now: time, + shouldAdvanceTime: true, + }); + })(); + + api.timeTravelTime = { + method: 'GET', + url: '/debug/time-travel-time', + middlewares: [authWithHeaders()], + async handler (req, res) { + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + res.respond(200, { + time: new Date(), + }); + }, + } + + api.timeTravelAdjust = { + method: 'POST', + url: '/debug/jump-time', + middlewares: [authWithHeaders()], + async handler (req, res) { + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + const { offsetDays } = req.body; + if (offsetDays > 0) { + clock.jump(offsetDays * 24 * 60 * 60 * 1000) + } else { + clock.setSystemTime(moment().add(offsetDays, 'days').toDate()); + } + + res.respond(200, { + time: new Date(), + }); + }, + } +} + export default api; diff --git a/website/server/server.js b/website/server/server.js index 852a1ccbd9..c952da4b04 100644 --- a/website/server/server.js +++ b/website/server/server.js @@ -2,7 +2,6 @@ import nconf from 'nconf'; import express from 'express'; import http from 'http'; import logger from './libs/logger'; -import sinon from 'sinon'; // Setup translations // Must come before attach middlewares so Mongoose validations can use translations @@ -26,23 +25,6 @@ app.set('port', nconf.get('PORT')); attachMiddlewares(app, server); -const time = new Date(2024, 2, 18); -const clock = sinon.useFakeTimers({ - now: time, - shouldAdvanceTime: true, -}); - -var jumped = false; -setInterval(() => { - if (jumped) { - jumped = false; - return; - } - jumped = true; - console.log('Jumping time'); - clock.jump(36000); -}, 1000); - server.on('request', app); server.listen(app.get('port'), () => { logger.info(`Express server listening on port ${app.get('port')}`); From 962456204e55989ab052816ebc78aaeb13344284 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 15 Feb 2024 15:07:12 +0100 Subject: [PATCH 029/244] improve updating UI when time traveling --- website/client/src/components/appFooter.vue | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index f1bb0163de..d0918e96d6 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -291,7 +291,8 @@
+ v-if="ENABLE_TIME_TRAVEL && user.permissions.fullAccess" + :key="lastTimeJump"> -1 Day @@ -302,7 +303,7 @@ class="btn btn-secondary mr-1" @click="jumpTime(-30)">-30 Days
- Time Traveling! It is {{ new Date().toLocaleString() }} + Time Traveling! It is {{ new Date().toLocaleDateString() }}
0) { Vue.config.clock.jump(amount * 24 * 60 * 60 * 1000); } else { Vue.config.clock.setSystemTime(moment().add(amount, 'days').toDate()); } + this.lastTimeJump = response.data.data.time; }, addExp () { // @TODO: Name these variables better From 2a84561e00be4a381408770a9e9f1ee0d5565698 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 15 Feb 2024 15:07:19 +0100 Subject: [PATCH 030/244] fix some date issues with scheduling --- .../script/content/constants/schedule.js | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index c5a0bbbafa..d7e93cbe3a 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -711,6 +711,20 @@ export const GALA_SCHEDULE = { ], }; +function getDay (date) { + if (date === undefined) { + return 0; + } + return date instanceof moment ? date.date() : date.getDate(); +} + +function getMonth (date) { + if (date === undefined) { + return 0; + } + return date instanceof moment ? date.month() : date.getMonth(); +} + function getGalaIndex (date) { const month = date instanceof moment ? date.month() : date.getMonth(); const todayDay = date instanceof moment ? date.date() : date.getDate(); @@ -745,11 +759,22 @@ export function assembleScheduledMatchers (date) { let cachedScheduleMatchers = null; export function getScheduleMatchingGroup (type, date) { + let checkedDate = date; + if (checkedDate === undefined) { + checkedDate = new Date(); + } + if (cacheDate !== null && (getDay(checkedDate) !== getDay(cacheDate) + || getMonth(checkedDate) !== getMonth(cacheDate))) { + cacheDate = null; + cachedScheduleMatchers = null; + } + console.log('Loading content for', type, 'on', checkedDate, 'with cached date', cacheDate, 'and cached schedule'); if (!cachedScheduleMatchers) { - cachedScheduleMatchers = {}; - assembleScheduledMatchers(date || new Date()).forEach(matcher => { - if (!cachedScheduleMatchers[matcher.type]) { - cachedScheduleMatchers[matcher.type] = { + cacheDate = new Date(); + const scheduleMatchers = {}; + assembleScheduledMatchers(checkedDate).forEach(matcher => { + if (!scheduleMatchers[matcher.type]) { + scheduleMatchers[matcher.type] = { matchers: [], items: [], match (key) { From 249394b4ade78b14bba89e7016b7c288398c18bf Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 15 Feb 2024 16:14:42 +0100 Subject: [PATCH 031/244] Implement events throughout the year --- website/client/src/components/appFooter.vue | 7 +- .../components/shops/timeTravelers/index.vue | 1 + website/client/src/mixins/seasonalNPC.js | 1 + .../client/src/store/actions/worldState.js | 1 + .../common/script/content/constants/events.js | 27 +++ .../common/script/content/constants/index.js | 2 +- .../script/content/constants/schedule.js | 202 ++++++++++-------- website/server/libs/worldState.js | 33 ++- 8 files changed, 174 insertions(+), 100 deletions(-) diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index d0918e96d6..c652857fde 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -812,6 +812,7 @@ import heart from '@/assets/svg/heart.svg'; import { mapState } from '@/libs/store'; import buyGemsModal from './payments/buyGemsModal.vue'; import reportBug from '@/mixins/reportBug.js'; +import { worldStateMixin } from '@/mixins/worldState'; const IS_PRODUCTION = process.env.NODE_ENV === 'production'; // eslint-disable-line no-process-env const ENABLE_TIME_TRAVEL = process.env.ENABLE_TIME_TRAVEL === 'true'; // eslint-disable-line no-process-env @@ -819,7 +820,10 @@ export default { components: { buyGemsModal, }, - mixins: [reportBug], + mixins: [ + reportBug, + worldStateMixin, + ], data () { return { icons: Object.freeze({ @@ -903,6 +907,7 @@ export default { Vue.config.clock.setSystemTime(moment().add(amount, 'days').toDate()); } this.lastTimeJump = response.data.data.time; + this.triggerGetWorldState(true); }, addExp () { // @TODO: Name these variables better diff --git a/website/client/src/components/shops/timeTravelers/index.vue b/website/client/src/components/shops/timeTravelers/index.vue index 58b90322d7..ef6646860c 100644 --- a/website/client/src/components/shops/timeTravelers/index.vue +++ b/website/client/src/components/shops/timeTravelers/index.vue @@ -424,6 +424,7 @@ export default { this.$root.$emit('buyModal::hidden', this.selectedItemToBuy.key); } }); + console.log('setting current event'); this.currentEvent = _find(this.currentEventList, event => Boolean(['winter', 'spring', 'summer', 'fall'].includes(event.season))); }, beforeDestroy () { diff --git a/website/client/src/mixins/seasonalNPC.js b/website/client/src/mixins/seasonalNPC.js index e6640e3b83..830f78ab94 100644 --- a/website/client/src/mixins/seasonalNPC.js +++ b/website/client/src/mixins/seasonalNPC.js @@ -8,6 +8,7 @@ export default { }, methods: { npcClass (name) { + console.log('npcClass', name, this.currentEvent); if (!this.currentEvent || !this.currentEvent.season) return `npc_${name}`; return `npc_${name} npc_${name}_${this.currentEvent.season}`; }, diff --git a/website/client/src/store/actions/worldState.js b/website/client/src/store/actions/worldState.js index 367b9299a8..5192fe97d6 100644 --- a/website/client/src/store/actions/worldState.js +++ b/website/client/src/store/actions/worldState.js @@ -6,6 +6,7 @@ export async function getWorldState (store, options = {}) { path: 'worldState', url: '/api/v4/world-state', deserialize (response) { + console.log(response.data.data); return response.data.data; }, forceLoad: options.forceLoad, diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index a328cc5ae9..7dc5eb00e0 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -8,6 +8,33 @@ const gemsPromo = { '84gems': 125, }; +export const REPEATING_EVENTS = { + nye: { + start: '1970-12-28T08:00-05:00', + end: '1970-01-04T23:59-05:00', + season: 'nye', + npcImageSuffix: '_nye', + }, + valentines: { + start: '1970-02-13T08:00-05:00', + end: '1970-02-17T23:59-05:00', + season: 'valentines', + npcImageSuffix: '_valentines', + }, + birthday: { + start: '1970-01-30T08:00-05:00', + end: '1970-02-08T23:59-05:00', + season: 'birthday', + npcImageSuffix: '_birthday', + }, + harvestFeast: { + start: '1970-11-22T08:00-05:00', + end: '1970-11-27T20:00-05:00', + season: 'thanksgiving', + npcImageSuffix: '_thanksgiving', + }, +}; + export const EVENTS = { noEvent: { start: '2024-05-01T00:00-04:00', diff --git a/website/common/script/content/constants/index.js b/website/common/script/content/constants/index.js index 624285ec06..18a3ba03fb 100644 --- a/website/common/script/content/constants/index.js +++ b/website/common/script/content/constants/index.js @@ -24,7 +24,7 @@ export const USER_CAN_OWN_QUEST_CATEGORIES = [ 'pet', ]; -export { EVENTS } from './events'; +export { EVENTS, REPEATING_EVENTS } from './events'; export { default as SEASONAL_SETS } from './seasonalSets'; export { default as ANIMAL_COLOR_ACHIEVEMENTS } from './animalColorAchievements'; export { default as ANIMAL_SET_ACHIEVEMENTS } from './animalSetAchievements'; diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index d7e93cbe3a..8d47cedceb 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -627,88 +627,104 @@ export const GALA_KEYS = [ 'fall', ]; export const GALA_SCHEDULE = { - 0: [ - { - type: 'seasonalGear', - items: SEASONAL_SETS.winter, - }, - { - type: 'seasonalSpells', - items: [ - 'snowball', - ], - }, - { - type: 'seasonalQuests', - items: [ - 'evilsanta', - 'evilsanta2', - ], - }, - { - type: 'customizations', - matcher: customizationMatcher([ - 'winteryHairColors', - 'winterySkins', - ]), - }, - ], - 1: [ - { - type: 'seasonalGear', - items: SEASONAL_SETS.spring, - }, - { - type: 'seasonalSpells', - items: [ - 'shinySeed', - ], - }, - { - type: 'customizations', - matcher: customizationMatcher([ - 'shimmerHairColors', - 'pastelSkins', - ]), - }, - ], - 2: [ - { - type: 'seasonalGear', - items: SEASONAL_SETS.summer, - }, - { - type: 'seasonalSpells', - items: [ - 'seafoam', - ], - }, - { - type: 'customizations', - matcher: customizationMatcher([ - 'splashySkins', - ]), - }, - ], - 3: [ - { - type: 'seasonalGear', - items: SEASONAL_SETS.fall, - }, - { - type: 'seasonalSpells', - items: [ - 'spookySparkles', - ], - }, - { - type: 'customizations', - matcher: customizationMatcher([ - 'hauntedHairColors', - 'supernaturalSkins', - ]), - }, - ], + 0: { + startMonth: 11, + endMonth: 1, + filters: [ + { + type: 'seasonalGear', + items: SEASONAL_SETS.winter, + }, + { + type: 'seasonalSpells', + items: [ + 'snowball', + ], + }, + { + type: 'seasonalQuests', + items: [ + 'evilsanta', + 'evilsanta2', + ], + }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'winteryHairColors', + 'winterySkins', + ]), + }, + ], + }, + 1: { + startMonth: 2, + endMonth: 4, + filters: [ + { + type: 'seasonalGear', + items: SEASONAL_SETS.spring, + }, + { + type: 'seasonalSpells', + items: [ + 'shinySeed', + ], + }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'shimmerHairColors', + 'pastelSkins', + ]), + }, + ], + }, + 2: { + startMonth: 5, + endMonth: 7, + filters: [ + { + type: 'seasonalGear', + items: SEASONAL_SETS.summer, + }, + { + type: 'seasonalSpells', + items: [ + 'seafoam', + ], + }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'splashySkins', + ]), + }, + ], + }, + 3: { + startMonth: 8, + endMonth: 10, + filters: [ + { + type: 'seasonalGear', + items: SEASONAL_SETS.fall, + }, + { + type: 'seasonalSpells', + items: [ + 'spookySparkles', + ], + }, + { + type: 'customizations', + matcher: customizationMatcher([ + 'hauntedHairColors', + 'supernaturalSkins', + ]), + }, + ], + }, }; function getDay (date) { @@ -752,17 +768,14 @@ export function assembleScheduledMatchers (date) { } } - items.push(...GALA_SCHEDULE[getGalaIndex(date)]); + items.push(...GALA_SCHEDULE[getGalaIndex(date)].filters); return items; } let cachedScheduleMatchers = null; export function getScheduleMatchingGroup (type, date) { - let checkedDate = date; - if (checkedDate === undefined) { - checkedDate = new Date(); - } + const checkedDate = date || new Date(); if (cacheDate !== null && (getDay(checkedDate) !== getDay(cacheDate) || getMonth(checkedDate) !== getMonth(cacheDate))) { cacheDate = null; @@ -806,3 +819,18 @@ export function getScheduleMatchingGroup (type, date) { export function getCurrentGalaKey (date) { return GALA_KEYS[getGalaIndex(date || new Date())]; } + +export function getCurrentGalaEvent (date) { + const checkedDate = date || new Date(); + const index = getGalaIndex(checkedDate); + const key = GALA_KEYS[index]; + const gala = GALA_SCHEDULE[index]; + const today = new Date(); + return { + event: key, + npcImageSuffix: `_${key}`, + season: key, + start: `${today.getFullYear()}.${gala.startMonth + 1}.${GALA_SWITCHOVER_DAY}`, + end: `${today.getFullYear()}.${gala.endMonth + 1}.${GALA_SWITCHOVER_DAY}`, + }; +} diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index 257ab1879f..525bd07578 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -5,6 +5,8 @@ import { // eslint-disable-line import/no-cycle TAVERN_ID as tavernId, } from '../models/group'; import common from '../../common'; +import { REPEATING_EVENTS } from '../../common/script/content/constants'; +import { getCurrentGalaEvent } from '../../common/script/content/constants/schedule'; export async function getWorldBoss () { const tavern = await Group @@ -18,26 +20,32 @@ export async function getWorldBoss () { } export function getCurrentEvent () { - const currEvtKey = Object.keys(common.content.events).find(evtKey => { - const event = common.content.events[evtKey]; + const now = moment(); + const currEvtKey = Object.keys(REPEATING_EVENTS).find(evtKey => { + const event = REPEATING_EVENTS[evtKey]; + const startDate = event.start.replace('1970', now.year()); + const endDate = event.end.replace('1970', now.year()); - const now = moment(); - - return now.isBetween(event.start, event.end); // && Boolean(event.npcImageSuffix); + return now.isBetween(startDate, endDate); }); - if (!currEvtKey) return null; + if (!currEvtKey) { + return getCurrentGalaEvent() + } return { event: currEvtKey, - ...common.content.events[currEvtKey], + ...REPEATING_EVENTS[currEvtKey], }; } export function getCurrentEventList () { - const currentEventKeys = filter(Object.keys(common.content.events), eventKey => { - const eventData = common.content.events[eventKey]; + const now = moment(); + const currentEventKeys = filter(Object.keys(REPEATING_EVENTS), eventKey => { + const eventData = REPEATING_EVENTS[eventKey]; + const startDate = eventData.start.replace('1970', now.year()); + const endDate = eventData.end.replace('1970', now.year()); - return moment().isBetween(eventData.start, eventData.end); + return now.isBetween(startDate, endDate); }); const currentEventList = []; @@ -45,9 +53,12 @@ export function getCurrentEventList () { currentEventKeys.forEach(key => { currentEventList.push({ event: key, - ...common.content.events[key], + ...REPEATING_EVENTS[key], }); }); + currentEventList.push(getCurrentGalaEvent()); + + console.log(currentEventList); return currentEventList; } From 6e96085f9970984b478c8627c21735aec38d8815 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 21 Feb 2024 15:31:45 +0100 Subject: [PATCH 032/244] add cards to event content cycle --- .../src/components/shops/market/index.vue | 5 +++- .../common/script/content/constants/events.js | 29 +++++++++++++++++++ .../script/content/constants/schedule.js | 9 ++++++ website/common/script/content/index.js | 2 -- website/server/libs/worldState.js | 2 -- 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/website/client/src/components/shops/market/index.vue b/website/client/src/components/shops/market/index.vue index 7235ec8d4e..dae59eca2b 100644 --- a/website/client/src/components/shops/market/index.vue +++ b/website/client/src/components/shops/market/index.vue @@ -152,6 +152,7 @@ import _map from 'lodash/map'; import _throttle from 'lodash/throttle'; import getItemInfo from '@/../../common/script/libs/getItemInfo'; import shops from '@/../../common/script/libs/shops'; +import { getScheduleMatchingGroup } from '@/../../common/script/content/constants/schedule'; import { mapState } from '@/libs/store'; import KeysToKennel from './keysToKennel'; @@ -218,6 +219,7 @@ export default { hideLocked: false, hidePinned: false, + cardMatcher: getScheduleMatchingGroup('cards'), }; }, computed: { @@ -241,7 +243,8 @@ export default { categories.push({ identifier: 'cards', text: this.$t('cards'), - items: _map(_filter(this.content.cardTypes, value => value.yearRound), value => ({ + items: _map(_filter(this.content.cardTypes, value => value.yearRound + || this.cardMatcher.items.indexOf(value.key) !== -1), value => ({ ...getItemInfo(this.user, 'card', value), showCount: false, })), diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index 7dc5eb00e0..a5cba04539 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -1,4 +1,6 @@ /* eslint-disable key-spacing */ +import filter from 'lodash/filter'; +import moment from 'moment'; // gem block: number of gems const gemsPromo = { @@ -14,12 +16,28 @@ export const REPEATING_EVENTS = { end: '1970-01-04T23:59-05:00', season: 'nye', npcImageSuffix: '_nye', + content: [ + { + type: 'cards', + items: [ + 'nye', + ], + }, + ], }, valentines: { start: '1970-02-13T08:00-05:00', end: '1970-02-17T23:59-05:00', season: 'valentines', npcImageSuffix: '_valentines', + content: [ + { + type: 'cards', + items: [ + 'valentine', + ], + }, + ], }, birthday: { start: '1970-01-30T08:00-05:00', @@ -35,6 +53,17 @@ export const REPEATING_EVENTS = { }, }; +export function getRepeatingEvents (date) { + const momentDate = date instanceof moment ? date : moment(date); + return filter(Object.keys(REPEATING_EVENTS), eventKey => { + const eventData = REPEATING_EVENTS[eventKey]; + const startDate = eventData.start.replace('1970', momentDate.year()); + const endDate = eventData.end.replace('1970', momentDate.year()); + + return momentDate.isBetween(startDate, endDate); + }); +} + export const EVENTS = { noEvent: { start: '2024-05-01T00:00-04:00', diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 8d47cedceb..fb90846f6f 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -1,5 +1,6 @@ import moment from 'moment'; import SEASONAL_SETS from './seasonalSets'; +import { getRepeatingEvents } from './events'; function backgroundMatcher (month1, month2, oddYear) { return function call (key) { @@ -749,6 +750,9 @@ function getGalaIndex (date) { if (todayDay >= GALA_SWITCHOVER_DAY) { galaMonth += 1; } + if (galaMonth >= 12) { + return 0; + } return parseInt((galaCount / 12) * galaMonth, 10); } @@ -769,6 +773,11 @@ export function assembleScheduledMatchers (date) { } items.push(...GALA_SCHEDULE[getGalaIndex(date)].filters); + for (const event in getRepeatingEvents(date)) { + if (event.content) { + items.push(...event.content); + } + } return items; } diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 2d1793ab7f..559caf82d7 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -129,7 +129,6 @@ api.cardTypes = { nye: { key: 'nye', messageOptions: 5, - yearRound: moment().isBetween(EVENTS.nye2023.start, EVENTS.nye2023.end), }, thankyou: { key: 'thankyou', @@ -139,7 +138,6 @@ api.cardTypes = { valentine: { key: 'valentine', messageOptions: 4, - yearRound: moment().isBetween(EVENTS.valentine2024.start, EVENTS.valentine2024.end), }, birthday: { key: 'birthday', diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index 525bd07578..93901ada92 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -58,7 +58,5 @@ export function getCurrentEventList () { }); currentEventList.push(getCurrentGalaEvent()); - - console.log(currentEventList); return currentEventList; } From 041edb3042d9a4f735915e18598970d2a6be8435 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 21 Feb 2024 16:40:28 +0100 Subject: [PATCH 033/244] Preen old scheduling code --- .../components/shops/timeTravelers/index.vue | 1 - website/client/src/main.js | 2 - .../client/src/mixins/avatarEditUtilities.js | 11 +- website/client/src/mixins/seasonalNPC.js | 1 - .../client/src/store/actions/worldState.js | 1 - .../common/script/content/appearance/sets.js | 21 +- website/common/script/content/bundles.js | 62 -- .../script/content/constants/schedule.js | 1 - .../script/content/gear/sets/special/index.js | 660 ------------------ .../common/script/content/hatching-potions.js | 138 ---- website/common/script/content/index.js | 189 +---- .../common/script/content/quests/seasonal.js | 26 - 12 files changed, 30 insertions(+), 1083 deletions(-) diff --git a/website/client/src/components/shops/timeTravelers/index.vue b/website/client/src/components/shops/timeTravelers/index.vue index ef6646860c..58b90322d7 100644 --- a/website/client/src/components/shops/timeTravelers/index.vue +++ b/website/client/src/components/shops/timeTravelers/index.vue @@ -424,7 +424,6 @@ export default { this.$root.$emit('buyModal::hidden', this.selectedItemToBuy.key); } }); - console.log('setting current event'); this.currentEvent = _find(this.currentEventList, event => Boolean(['winter', 'spring', 'summer', 'fall'].includes(event.season))); }, beforeDestroy () { diff --git a/website/client/src/main.js b/website/client/src/main.js index 929d636a6e..ddf86c80a6 100644 --- a/website/client/src/main.js +++ b/website/client/src/main.js @@ -40,14 +40,12 @@ if (process.env.ENABLE_TIME_TRAVEL) { const sinon = await import('sinon'); const axios = await import('axios'); const response = await axios.get('/api/v4/debug/time-travel-time'); - console.log(response.data.data.time); const time = new Date(response.data.data.time); Vue.config.clock = sinon.useFakeTimers({ now: time, shouldAdvanceTime: true, }); - console.log('Time travel mode activated. It is now', new Date()); })(); } diff --git a/website/client/src/mixins/avatarEditUtilities.js b/website/client/src/mixins/avatarEditUtilities.js index 207defffd6..8ddc8e8ac6 100644 --- a/website/client/src/mixins/avatarEditUtilities.js +++ b/website/client/src/mixins/avatarEditUtilities.js @@ -1,4 +1,3 @@ -import moment from 'moment'; import axios from 'axios'; import get from 'lodash/get'; @@ -6,6 +5,7 @@ import unlock from '@/../../common/script/ops/unlock'; import buy from '@/../../common/script/ops/buy/buy'; import appearanceSets from '@/../../common/script/content/appearance/sets'; +import { getScheduleMatchingGroup } from '@/../../common/script/content/constants/schedule'; import { userStateMixin } from './userState'; @@ -18,13 +18,8 @@ export const avatarEditorUtilies = { // eslint-disable-line import/prefer-defaul }, methods: { hideSet (setKey) { - if (appearanceSets[setKey].availableFrom) { - return !moment().isBetween( - appearanceSets[setKey].availableFrom, - appearanceSets[setKey].availableUntil, - ); - } - return moment(appearanceSets[setKey].availableUntil).isBefore(moment()); + const matcher = getScheduleMatchingGroup('customizations'); + return !matcher.match(setKey); }, mapKeysToFreeOption (key, type, subType) { const userPreference = subType diff --git a/website/client/src/mixins/seasonalNPC.js b/website/client/src/mixins/seasonalNPC.js index 830f78ab94..e6640e3b83 100644 --- a/website/client/src/mixins/seasonalNPC.js +++ b/website/client/src/mixins/seasonalNPC.js @@ -8,7 +8,6 @@ export default { }, methods: { npcClass (name) { - console.log('npcClass', name, this.currentEvent); if (!this.currentEvent || !this.currentEvent.season) return `npc_${name}`; return `npc_${name} npc_${name}_${this.currentEvent.season}`; }, diff --git a/website/client/src/store/actions/worldState.js b/website/client/src/store/actions/worldState.js index 5192fe97d6..367b9299a8 100644 --- a/website/client/src/store/actions/worldState.js +++ b/website/client/src/store/actions/worldState.js @@ -6,7 +6,6 @@ export async function getWorldState (store, options = {}) { path: 'worldState', url: '/api/v4/world-state', deserialize (response) { - console.log(response.data.data); return response.data.data; }, forceLoad: options.forceLoad, diff --git a/website/common/script/content/appearance/sets.js b/website/common/script/content/appearance/sets.js index 05505bf35a..bd6f20384e 100644 --- a/website/common/script/content/appearance/sets.js +++ b/website/common/script/content/appearance/sets.js @@ -1,6 +1,5 @@ import t from '../translation'; import prefill from './prefill'; -import { EVENTS } from '../constants'; export default prefill({ baseHair1: { setPrice: 5, text: t('hairSet1') }, @@ -8,31 +7,31 @@ export default prefill({ baseHair3: { setPrice: 5, text: t('hairSet3') }, facialHair: { setPrice: 5, text: t('bodyFacialHair') }, specialShirts: { setPrice: 5, text: t('specialShirts') }, - winterHairColors: { setPrice: 5, availableUntil: '2016-01-01' }, - pastelHairColors: { setPrice: 5, availableUntil: '2016-01-01' }, + winterHairColors: { setPrice: 5 }, + pastelHairColors: { setPrice: 5 }, rainbowHairColors: { setPrice: 5, text: t('rainbowColors') }, shimmerHairColors: { - setPrice: 5, availableFrom: '2024-04-16T08:00-05:00', availableUntil: EVENTS.spring2024.end, text: t('shimmerColors'), + setPrice: 5, text: t('shimmerColors'), }, hauntedHairColors: { - setPrice: 5, availableFrom: '2023-10-03T08:00-04:00', availableUntil: EVENTS.fall2023.end, text: t('hauntedColors'), + setPrice: 5, text: t('hauntedColors'), }, winteryHairColors: { - setPrice: 5, availableFrom: '2023-01-16T08:00-05:00', availableUntil: EVENTS.winter2024.end, text: t('winteryColors'), + setPrice: 5, text: t('winteryColors'), }, rainbowSkins: { setPrice: 5, text: t('rainbowSkins') }, animalSkins: { setPrice: 5, text: t('animalSkins') }, pastelSkins: { - setPrice: 5, availableFrom: '2024-04-16T08:00-05:00', availableUntil: EVENTS.spring2024.end, text: t('pastelSkins'), + setPrice: 5, text: t('pastelSkins'), }, - spookySkins: { setPrice: 5, availableUntil: '2016-01-01', text: t('spookySkins') }, + spookySkins: { setPrice: 5, text: t('spookySkins') }, supernaturalSkins: { - setPrice: 5, availableFrom: '2023-10-03T08:00-04:00', availableUntil: EVENTS.fall2023.end, text: t('supernaturalSkins'), + setPrice: 5, text: t('supernaturalSkins'), }, splashySkins: { - setPrice: 5, availableFrom: '2023-07-11T08:00-05:00', availableUntil: EVENTS.summer2023.end, text: t('splashySkins'), + setPrice: 5, text: t('splashySkins'), }, winterySkins: { - setPrice: 5, availableFrom: '2023-01-16T08:00-05:00', availableUntil: EVENTS.winter2024.end, text: t('winterySkins'), + setPrice: 5, text: t('winterySkins'), }, }); diff --git a/website/common/script/content/bundles.js b/website/common/script/content/bundles.js index 049fb70f04..b90b727eae 100644 --- a/website/common/script/content/bundles.js +++ b/website/common/script/content/bundles.js @@ -18,10 +18,6 @@ const bundles = { 'harpy', 'owl', ], - event: EVENTS.bundle202309, - canBuy () { - return moment().isBetween(EVENTS.bundle202309.start, EVENTS.bundle202309.end); - }, type: 'quests', class: 'quest_bundle_featheredFriends', value: 7, @@ -35,10 +31,6 @@ const bundles = { 'turtle', 'whale', ], - event: EVENTS.bundle202306, - canBuy () { - return moment().isBetween(EVENTS.bundle202306.start, EVENTS.bundle202306.end); - }, type: 'quests', class: 'quest_bundle_splashyPals', value: 7, @@ -52,10 +44,6 @@ const bundles = { 'horse', 'sheep', ], - event: EVENTS.bundle202209, - canBuy () { - return moment().isBetween(EVENTS.bundle202209.start, EVENTS.bundle202209.end); - }, type: 'quests', value: 7, }, @@ -68,10 +56,6 @@ const bundles = { 'spider', 'frog', ], - event: EVENTS.bundle202210, - canBuy () { - return moment().isBetween(EVENTS.bundle202210.start, EVENTS.bundle202210.end); - }, type: 'quests', value: 7, }, @@ -85,10 +69,6 @@ const bundles = { 'evilsanta2', 'penguin', ], - event: EVENTS.winter2024, - canBuy () { - return moment().isBetween(EVENTS.winter2024.start, EVENTS.winter2024.end); - }, type: 'quests', value: 7, }, @@ -101,9 +81,6 @@ const bundles = { 'beetle', 'butterfly', ], - canBuy () { - return moment().isBetween(EVENTS.bundle202308.start, EVENTS.bundle202308.end); - }, type: 'quests', value: 7, }, @@ -116,10 +93,6 @@ const bundles = { 'ferret', 'guineapig', ], - event: EVENTS.bundle202403, - canBuy () { - return moment().isBetween(EVENTS.bundle202403.start, EVENTS.bundle202403.end); - }, type: 'quests', value: 7, }, @@ -132,10 +105,6 @@ const bundles = { 'kraken', 'octopus', ], - event: EVENTS.bundle202206, - canBuy () { - return moment().isBetween(EVENTS.bundle202206.start, EVENTS.bundle202206.end); - }, type: 'quests', value: 7, }, @@ -148,10 +117,6 @@ const bundles = { 'hedgehog', 'treeling', ], - event: EVENTS.bundle202208, - canBuy () { - return moment().isBetween(EVENTS.bundle202208.start, EVENTS.bundle202208.end); - }, type: 'quests', value: 7, }, @@ -164,10 +129,6 @@ const bundles = { 'rock', 'yarn', ], - event: EVENTS.bundle202311, - canBuy () { - return moment().isBetween(EVENTS.bundle202311.start, EVENTS.bundle202311.end); - }, type: 'quests', value: 7, }, @@ -180,10 +141,6 @@ const bundles = { 'penguin', 'rooster', ], - event: EVENTS.bundle202305, - canBuy () { - return moment().isBetween(EVENTS.bundle202305.start, EVENTS.bundle202305.end); - }, type: 'quests', value: 7, }, @@ -196,10 +153,6 @@ const bundles = { 'seaserpent', 'gryphon', ], - event: EVENTS.bundle202402, - canBuy () { - return moment().isBetween(EVENTS.bundle202402.start, EVENTS.bundle202402.end); - }, type: 'quests', value: 7, }, @@ -212,10 +165,6 @@ const bundles = { 'snake', 'velociraptor', ], - event: EVENTS.bundle202211, - canBuy () { - return moment().isBetween(EVENTS.bundle202211.start, EVENTS.bundle202211.end); - }, type: 'quests', value: 7, }, @@ -228,9 +177,6 @@ const bundles = { 'triceratops', 'trex_undead', ], - canBuy () { - return moment().isBetween('2022-05-16', '2022-05-31'); - }, type: 'quests', value: 7, }, @@ -243,10 +189,6 @@ const bundles = { 'sloth', 'treeling', ], - event: EVENTS.bundle202303, - canBuy () { - return moment().isBetween(EVENTS.bundle202303.start, EVENTS.bundle202303.end); - }, type: 'quests', value: 7, }, @@ -259,10 +201,6 @@ const bundles = { 'snake', 'spider', ], - event: EVENTS.bundle202310, - canBuy () { - return moment().isBetween(EVENTS.bundle202310.start, EVENTS.bundle202310.end); - }, type: 'quests', value: 7, }, diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index fb90846f6f..edcbd86cb7 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -790,7 +790,6 @@ export function getScheduleMatchingGroup (type, date) { cacheDate = null; cachedScheduleMatchers = null; } - console.log('Loading content for', type, 'on', checkedDate, 'with cached date', cacheDate, 'and cached schedule'); if (!cachedScheduleMatchers) { cacheDate = new Date(); const scheduleMatchers = {}; diff --git a/website/common/script/content/gear/sets/special/index.js b/website/common/script/content/gear/sets/special/index.js index 611a495328..de0a064e0a 100644 --- a/website/common/script/content/gear/sets/special/index.js +++ b/website/common/script/content/gear/sets/special/index.js @@ -1,7 +1,5 @@ -import moment from 'moment'; import pickBy from 'lodash/pickBy'; import defaults from 'lodash/defaults'; -import find from 'lodash/find'; import upperFirst from 'lodash/upperFirst'; import { CLASSES, @@ -15,9 +13,6 @@ import * as wonderconGear from './special-wondercon'; import t from '../../../translation'; import { getClassName } from '../../../../libs/getClassName'; -const CURRENT_EVENT = find(EVENTS, event => moment().isBetween(event.start, event.end) - && ['winter', 'spring', 'summer', 'fall'].includes(event.season)); - const gearEvents = pickBy(EVENTS, event => event.gear); const armor = { @@ -124,7 +119,6 @@ const armor = { notes: t('armorSpecialYetiNotes', { con: 9 }), con: 9, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, ski: { specialClass: 'rogue', @@ -133,7 +127,6 @@ const armor = { notes: t('armorSpecialSkiNotes', { per: 15 }), per: 15, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, candycane: { specialClass: 'wizard', @@ -142,7 +135,6 @@ const armor = { notes: t('armorSpecialCandycaneNotes', { int: 9 }), int: 9, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, snowflake: { specialClass: 'healer', @@ -151,7 +143,6 @@ const armor = { notes: t('armorSpecialSnowflakeNotes', { con: 15 }), con: 15, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday: { event: EVENTS.birthday, @@ -161,67 +152,51 @@ const armor = { }, springRogue: { set: 'stealthyKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springWarrior: { set: 'mightyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springMage: { set: 'magicMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springHealer: { set: 'lovingPupSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summerRogue: { set: 'roguishPirateSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerWarrior: { set: 'daringSwashbucklerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerMage: { set: 'emeraldMermageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerHealer: { set: 'reefSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fallRogue: { set: 'vampireSmiterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallWarrior: { set: 'monsterOfScienceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallMage: { set: 'witchyWizardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallHealer: { set: 'mummyMedicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2015Rogue: { set: 'icicleDrakeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Warrior: { set: 'gingerbreadSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Mage: { set: 'northMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Healer: { set: 'soothingSkaterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2015: { text: t('armorSpecialBirthday2015Text'), @@ -231,51 +206,39 @@ const armor = { }, spring2015Rogue: { set: 'sneakySqueakerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Warrior: { set: 'bewareDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Mage: { set: 'magicianBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Healer: { set: 'comfortingKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2015Rogue: { set: 'reefRenegadeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Warrior: { set: 'sunfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Mage: { set: 'shipSoothsayerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Healer: { set: 'strappingSailorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2015Rogue: { set: 'battleRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Warrior: { set: 'scarecrowWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Mage: { set: 'stitchWitchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Healer: { set: 'potionerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, gaymerx: { event: EVENTS.gaymerx, @@ -285,19 +248,15 @@ const armor = { }, winter2016Rogue: { set: 'cocoaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Warrior: { set: 'snowDaySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Mage: { set: 'snowboardingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Healer: { set: 'festiveFairySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2016: { text: t('armorSpecialBirthday2016Text'), @@ -307,67 +266,51 @@ const armor = { }, spring2016Rogue: { set: 'cleverDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Warrior: { set: 'braveMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Mage: { set: 'grandMalkinSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Healer: { set: 'springingBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2016Rogue: { set: 'summer2016EelSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Warrior: { set: 'summer2016SharkWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Mage: { set: 'summer2016DolphinMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Healer: { set: 'summer2016SeahorseHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2016Rogue: { set: 'fall2016BlackWidowSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Warrior: { set: 'fall2016SwampThingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Mage: { set: 'fall2016WickedSorcererSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Healer: { set: 'fall2016GorgonHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2017Rogue: { set: 'winter2017FrostyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Warrior: { set: 'winter2017IceHockeySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Mage: { set: 'winter2017WinterWolfSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Healer: { set: 'winter2017SugarPlumSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2017: { text: t('armorSpecialBirthday2017Text'), @@ -377,67 +320,51 @@ const armor = { }, spring2017Rogue: { set: 'spring2017SneakyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Warrior: { set: 'spring2017FelineWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Mage: { set: 'spring2017CanineConjurorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Healer: { set: 'spring2017FloralMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2017Rogue: { set: 'summer2017SeaDragonSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Warrior: { set: 'summer2017SandcastleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Mage: { set: 'summer2017WhirlpoolMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Healer: { set: 'summer2017SeashellSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2017Rogue: { set: 'fall2017TrickOrTreatSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Warrior: { set: 'fall2017HabitoweenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Mage: { set: 'fall2017MasqueradeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Healer: { set: 'fall2017HauntedHouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2018Rogue: { set: 'winter2018ReindeerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Warrior: { set: 'winter2018GiftWrappedSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Mage: { set: 'winter2018ConfettiSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Healer: { set: 'winter2018MistletoeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2018: { text: t('armorSpecialBirthday2018Text'), @@ -447,51 +374,39 @@ const armor = { }, spring2018Rogue: { set: 'spring2018DucklingRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Warrior: { set: 'spring2018SunriseWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Mage: { set: 'spring2018TulipMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Healer: { set: 'spring2018GarnetHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2018Rogue: { set: 'summer2018FisherRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Warrior: { set: 'summer2018BettaFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Mage: { set: 'summer2018LionfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Healer: { set: 'summer2018MerfolkMonarchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2018Rogue: { set: 'fall2018AlterEgoSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Warrior: { set: 'fall2018MinotaurWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Mage: { set: 'fall2018CandymancerMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Healer: { set: 'fall2018CarnivorousPlantSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, turkeyArmorGilded: { text: t('armorSpecialTurkeyArmorGildedText'), @@ -501,19 +416,15 @@ const armor = { }, winter2019Rogue: { set: 'winter2019PoinsettiaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Warrior: { set: 'winter2019BlizzardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Mage: { set: 'winter2019PyrotechnicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Healer: { set: 'winter2019WinterStarSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2019: { text: t('armorSpecialBirthday2019Text'), @@ -523,51 +434,39 @@ const armor = { }, spring2019Rogue: { set: 'spring2019CloudRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Warrior: { set: 'spring2019OrchidWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Mage: { set: 'spring2019AmberMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Healer: { set: 'spring2019RobinHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2019Rogue: { set: 'summer2019HammerheadRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Warrior: { set: 'summer2019SeaTurtleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Mage: { set: 'summer2019WaterLilyMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Healer: { set: 'summer2019ConchHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2019Rogue: { set: 'fall2019OperaticSpecterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Warrior: { set: 'fall2019RavenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Mage: { set: 'fall2019CyclopsSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Healer: { set: 'fall2019LichSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, ks2019: { text: t('armorSpecialKS2019Text'), @@ -578,19 +477,15 @@ const armor = { }, winter2020Rogue: { set: 'winter2020LanternSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Warrior: { set: 'winter2020EvergreenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Mage: { set: 'winter2020CarolOfTheMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Healer: { set: 'winter2020WinterSpiceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2020: { text: t('armorSpecialBirthday2020Text'), @@ -600,67 +495,51 @@ const armor = { }, spring2020Rogue: { set: 'spring2020LapisLazuliRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Warrior: { set: 'spring2020BeetleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Mage: { set: 'spring2020PuddleMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Healer: { set: 'spring2020IrisHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2020Rogue: { set: 'summer2020CrocodileRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Warrior: { set: 'summer2020RainbowTroutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Mage: { set: 'summer2020OarfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Healer: { set: 'summer2020SeaGlassHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2020Rogue: { set: 'fall2020TwoHeadedRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Warrior: { set: 'fall2020WraithWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Mage: { set: 'fall2020ThirdEyeMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Healer: { set: 'fall2020DeathsHeadMothHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2021Rogue: { set: 'winter2021HollyIvyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Warrior: { set: 'winter2021IceFishingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Mage: { set: 'winter2021WinterMoonMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Healer: { set: 'winter2021ArcticExplorerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2021: { text: t('armorSpecialBirthday2021Text'), @@ -670,83 +549,63 @@ const armor = { }, spring2021Rogue: { set: 'spring2021TwinFlowerRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Warrior: { set: 'spring2021SunstoneWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Mage: { set: 'spring2021SwanMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Healer: { set: 'spring2021WillowHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2021Rogue: { set: 'summer2021ClownfishRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Warrior: { set: 'summer2021FlyingFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Mage: { set: 'summer2021NautilusMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Healer: { set: 'summer2021ParrotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2021Rogue: { set: 'fall2021OozeRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Warrior: { set: 'fall2021HeadlessWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Mage: { set: 'fall2021BrainEaterMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Healer: { set: 'fall2021FlameSummonerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2022Rogue: { set: 'winter2022FireworksRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Warrior: { set: 'winter2022StockingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Mage: { set: 'winter2022PomegranateMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Healer: { set: 'winter2022IceCrystalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2022Rogue: { set: 'spring2022MagpieRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Warrior: { set: 'spring2022RainstormWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Mage: { set: 'spring2022ForsythiaMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Healer: { set: 'spring2022PeridotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, birthday2022: { text: t('armorSpecialBirthday2022Text'), @@ -756,51 +615,39 @@ const armor = { }, summer2022Rogue: { set: 'summer2022CrabRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Warrior: { set: 'summer2022WaterspoutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Mage: { set: 'summer2022MantaRayMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Healer: { set: 'summer2022AngelfishHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2022Rogue: { set: 'fall2022KappaRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Warrior: { set: 'fall2022OrcWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Mage: { set: 'fall2022HarpyMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Healer: { set: 'fall2022WatcherHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2023Rogue: { set: 'winter2023RibbonRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Warrior: { set: 'winter2023WalrusWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Mage: { set: 'winter2023FairyLightsMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Healer: { set: 'winter2023CardinalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, birthday2023: { text: t('armorSpecialBirthday2023Text'), @@ -810,51 +657,39 @@ const armor = { }, spring2023Rogue: { set: 'spring2023CaterpillarRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Warrior: { set: 'spring2023HummingbirdWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Mage: { set: 'spring2023MoonstoneMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Healer: { set: 'spring2023LilyHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2023Rogue: { set: 'summer2023GuppyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Warrior: { set: 'summer2023GoldfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Mage: { set: 'summer2023CoralMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Healer: { set: 'summer2023KelpHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2023Warrior: { set: 'fall2023ScaryMovieWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Healer: { set: 'fall2023BogCreatureHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Mage: { set: 'fall2023ScarletWarlockMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Rogue: { set: 'fall2023WitchsBrewRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2024Warrior: { set: 'winter2024PeppermintBarkWarriorSet', @@ -911,9 +746,6 @@ Object.keys(gearEvents).forEach(event => { notes: t(`${textString}Notes`, armorStats[klass]), value: 90, }, armorStats[klass]); - if (armor[eventString].canBuy && armor[eventString].canBuy()) { - armor[eventString].event = CURRENT_EVENT; - } }); }); @@ -946,7 +778,6 @@ const back = { notes: t('backBearTailNotes'), value: 20, canOwn: ownsItem('back_special_bearTail'), - canBuy: () => true, }, cactusTail: { gearSet: 'animal', @@ -954,7 +785,6 @@ const back = { notes: t('backCactusTailNotes'), value: 20, canOwn: ownsItem('back_special_cactusTail'), - canBuy: () => true, }, foxTail: { gearSet: 'animal', @@ -962,7 +792,6 @@ const back = { notes: t('backFoxTailNotes'), value: 20, canOwn: ownsItem('back_special_foxTail'), - canBuy: () => true, }, lionTail: { gearSet: 'animal', @@ -970,7 +799,6 @@ const back = { notes: t('backLionTailNotes'), value: 20, canOwn: ownsItem('back_special_lionTail'), - canBuy: () => true, }, pandaTail: { gearSet: 'animal', @@ -978,7 +806,6 @@ const back = { notes: t('backPandaTailNotes'), value: 20, canOwn: ownsItem('back_special_pandaTail'), - canBuy: () => true, }, pigTail: { gearSet: 'animal', @@ -986,7 +813,6 @@ const back = { notes: t('backPigTailNotes'), value: 20, canOwn: ownsItem('back_special_pigTail'), - canBuy: () => true, }, tigerTail: { gearSet: 'animal', @@ -994,7 +820,6 @@ const back = { notes: t('backTigerTailNotes'), value: 20, canOwn: ownsItem('back_special_tigerTail'), - canBuy: () => true, }, wolfTail: { gearSet: 'animal', @@ -1002,7 +827,6 @@ const back = { notes: t('backWolfTailNotes'), value: 20, canOwn: ownsItem('back_special_wolfTail'), - canBuy: () => true, }, turkeyTailGilded: { text: t('backSpecialTurkeyTailGildedText'), @@ -1035,7 +859,6 @@ const body = { text: t('bodySpecialSummerHealerText'), notes: t('bodySpecialSummerHealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerMage: { specialClass: 'wizard', @@ -1043,7 +866,6 @@ const body = { text: t('bodySpecialSummerMageText'), notes: t('bodySpecialSummerMageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Healer: { specialClass: 'healer', @@ -1051,7 +873,6 @@ const body = { text: t('bodySpecialSummer2015HealerText'), notes: t('bodySpecialSummer2015HealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Mage: { specialClass: 'wizard', @@ -1059,7 +880,6 @@ const body = { text: t('bodySpecialSummer2015MageText'), notes: t('bodySpecialSummer2015MageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Rogue: { specialClass: 'rogue', @@ -1067,7 +887,6 @@ const body = { text: t('bodySpecialSummer2015RogueText'), notes: t('bodySpecialSummer2015RogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Warrior: { specialClass: 'warrior', @@ -1075,7 +894,6 @@ const body = { text: t('bodySpecialSummer2015WarriorText'), notes: t('bodySpecialSummer2015WarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, aetherAmulet: { text: t('bodySpecialAetherAmuletText'), @@ -1108,7 +926,6 @@ const eyewear = { text: t('eyewearSpecialSummerRogueText'), notes: t('eyewearSpecialSummerRogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerWarrior: { specialClass: 'warrior', @@ -1116,7 +933,6 @@ const eyewear = { text: t('eyewearSpecialSummerWarriorText'), notes: t('eyewearSpecialSummerWarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, blackTopFrame: { gearSet: 'glasses', @@ -1229,7 +1045,6 @@ const eyewear = { text: t('eyewearSpecialFall2019RogueText'), notes: t('eyewearSpecialFall2019RogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Healer: { specialClass: 'healer', @@ -1237,7 +1052,6 @@ const eyewear = { text: t('eyewearSpecialFall2019HealerText'), notes: t('eyewearSpecialFall2019HealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, ks2019: { text: t('eyewearSpecialKS2019Text'), @@ -1363,7 +1177,6 @@ const head = { notes: t('headSpecialYetiNotes', { str: 9 }), str: 9, value: 60, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, ski: { specialClass: 'rogue', @@ -1372,7 +1185,6 @@ const head = { notes: t('headSpecialSkiNotes', { per: 9 }), per: 9, value: 60, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, candycane: { specialClass: 'wizard', @@ -1381,7 +1193,6 @@ const head = { notes: t('headSpecialCandycaneNotes', { per: 7 }), per: 7, value: 60, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, snowflake: { specialClass: 'healer', @@ -1390,71 +1201,54 @@ const head = { notes: t('headSpecialSnowflakeNotes', { int: 7 }), int: 7, value: 60, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, springRogue: { set: 'stealthyKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springWarrior: { set: 'mightyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springMage: { set: 'magicMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springHealer: { set: 'lovingPupSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summerRogue: { set: 'roguishPirateSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerWarrior: { set: 'daringSwashbucklerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerMage: { set: 'emeraldMermageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerHealer: { set: 'reefSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fallRogue: { set: 'vampireSmiterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallWarrior: { set: 'monsterOfScienceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallMage: { set: 'witchyWizardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallHealer: { set: 'mummyMedicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2015Rogue: { set: 'icicleDrakeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Warrior: { set: 'gingerbreadSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Mage: { set: 'northMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Healer: { set: 'soothingSkaterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2014: { text: t('headSpecialNye2014Text'), @@ -1464,51 +1258,39 @@ const head = { }, spring2015Rogue: { set: 'sneakySqueakerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Warrior: { set: 'bewareDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Mage: { set: 'magicianBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Healer: { set: 'comfortingKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2015Rogue: { set: 'reefRenegadeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Warrior: { set: 'sunfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Mage: { set: 'shipSoothsayerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Healer: { set: 'strappingSailorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2015Rogue: { set: 'battleRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Warrior: { set: 'scarecrowWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Mage: { set: 'stitchWitchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Healer: { set: 'potionerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, gaymerx: { event: EVENTS.gaymerx, @@ -1518,19 +1300,15 @@ const head = { }, winter2016Rogue: { set: 'cocoaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Warrior: { set: 'snowDaySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Mage: { set: 'snowboardingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Healer: { set: 'festiveFairySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2015: { text: t('headSpecialNye2015Text'), @@ -1540,67 +1318,51 @@ const head = { }, spring2016Rogue: { set: 'cleverDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Warrior: { set: 'braveMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Mage: { set: 'grandMalkinSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Healer: { set: 'springingBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2016Rogue: { set: 'summer2016EelSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Warrior: { set: 'summer2016SharkWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Mage: { set: 'summer2016DolphinMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Healer: { set: 'summer2016SeahorseHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2016Rogue: { set: 'fall2016BlackWidowSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Warrior: { set: 'fall2016SwampThingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Mage: { set: 'fall2016WickedSorcererSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Healer: { set: 'fall2016GorgonHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2017Rogue: { set: 'winter2017FrostyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Warrior: { set: 'winter2017IceHockeySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Mage: { set: 'winter2017WinterWolfSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Healer: { set: 'winter2017SugarPlumSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2016: { text: t('headSpecialNye2016Text'), @@ -1610,35 +1372,27 @@ const head = { }, spring2017Rogue: { set: 'spring2017SneakyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Warrior: { set: 'spring2017FelineWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Mage: { set: 'spring2017CanineConjurorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Healer: { set: 'spring2017FloralMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2017Rogue: { set: 'summer2017SeaDragonSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Warrior: { set: 'summer2017SandcastleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Mage: { set: 'summer2017WhirlpoolMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Healer: { set: 'summer2017SeashellSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, namingDay2017: { text: t('headSpecialNamingDay2017Text'), @@ -1648,19 +1402,15 @@ const head = { }, fall2017Rogue: { set: 'fall2017TrickOrTreatSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Warrior: { set: 'fall2017HabitoweenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Mage: { set: 'fall2017MasqueradeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Healer: { set: 'fall2017HauntedHouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, nye2017: { text: t('headSpecialNye2017Text'), @@ -1670,67 +1420,51 @@ const head = { }, winter2018Rogue: { set: 'winter2018ReindeerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Warrior: { set: 'winter2018GiftWrappedSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Mage: { set: 'winter2018ConfettiSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Healer: { set: 'winter2018MistletoeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2018Rogue: { set: 'spring2018DucklingRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Warrior: { set: 'spring2018SunriseWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Mage: { set: 'spring2018TulipMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Healer: { set: 'spring2018GarnetHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2018Rogue: { set: 'summer2018FisherRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Warrior: { set: 'summer2018BettaFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Mage: { set: 'summer2018LionfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Healer: { set: 'summer2018MerfolkMonarchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2018Rogue: { set: 'fall2018AlterEgoSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Warrior: { set: 'fall2018MinotaurWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Mage: { set: 'fall2018CandymancerMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Healer: { set: 'fall2018CarnivorousPlantSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, turkeyHelmGilded: { text: t('headSpecialTurkeyHelmGildedText'), @@ -1740,19 +1474,15 @@ const head = { }, winter2019Rogue: { set: 'winter2019PoinsettiaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Warrior: { set: 'winter2019BlizzardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Mage: { set: 'winter2019PyrotechnicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Healer: { set: 'winter2019WinterStarSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2018: { text: t('headSpecialNye2018Text'), @@ -1768,51 +1498,39 @@ const head = { }, spring2019Rogue: { set: 'spring2019CloudRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Warrior: { set: 'spring2019OrchidWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Mage: { set: 'spring2019AmberMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Healer: { set: 'spring2019RobinHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2019Rogue: { set: 'summer2019HammerheadRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Warrior: { set: 'summer2019SeaTurtleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Mage: { set: 'summer2019WaterLilyMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Healer: { set: 'summer2019ConchHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2019Rogue: { set: 'fall2019OperaticSpecterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Warrior: { set: 'fall2019RavenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Mage: { set: 'fall2019CyclopsSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Healer: { set: 'fall2019LichSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, ks2019: { text: t('headSpecialKS2019Text'), @@ -1823,19 +1541,15 @@ const head = { }, winter2020Rogue: { set: 'winter2020LanternSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Warrior: { set: 'winter2020EvergreenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Mage: { set: 'winter2020CarolOfTheMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Healer: { set: 'winter2020WinterSpiceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2019: { text: t('headSpecialNye2019Text'), @@ -1845,67 +1559,51 @@ const head = { }, spring2020Rogue: { set: 'spring2020LapisLazuliRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Warrior: { set: 'spring2020BeetleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Mage: { set: 'spring2020PuddleMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Healer: { set: 'spring2020IrisHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2020Rogue: { set: 'summer2020CrocodileRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Warrior: { set: 'summer2020RainbowTroutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Mage: { set: 'summer2020OarfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Healer: { set: 'summer2020SeaGlassHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2020Rogue: { set: 'fall2020TwoHeadedRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Warrior: { set: 'fall2020WraithWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Mage: { set: 'fall2020ThirdEyeMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Healer: { set: 'fall2020DeathsHeadMothHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2021Rogue: { set: 'winter2021HollyIvyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Warrior: { set: 'winter2021IceFishingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Mage: { set: 'winter2021WinterMoonMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Healer: { set: 'winter2021ArcticExplorerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2020: { text: t('headSpecialNye2020Text'), @@ -1915,67 +1613,51 @@ const head = { }, spring2021Rogue: { set: 'spring2021TwinFlowerRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Warrior: { set: 'spring2021SunstoneWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Mage: { set: 'spring2021SwanMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Healer: { set: 'spring2021WillowHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2021Rogue: { set: 'summer2021ClownfishRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Warrior: { set: 'summer2021FlyingFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Mage: { set: 'summer2021NautilusMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Healer: { set: 'summer2021ParrotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2021Rogue: { set: 'fall2021OozeRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Warrior: { set: 'fall2021HeadlessWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Mage: { set: 'fall2021BrainEaterMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Healer: { set: 'fall2021FlameSummonerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2022Rogue: { set: 'winter2022FireworksRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Warrior: { set: 'winter2022StockingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Mage: { set: 'winter2022PomegranateMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Healer: { set: 'winter2022IceCrystalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2021: { text: t('headSpecialNye2021Text'), @@ -1985,67 +1667,51 @@ const head = { }, spring2022Rogue: { set: 'spring2022MagpieRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Warrior: { set: 'spring2022RainstormWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Mage: { set: 'spring2022ForsythiaMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Healer: { set: 'spring2022PeridotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2022Rogue: { set: 'summer2022CrabRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Warrior: { set: 'summer2022WaterspoutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Mage: { set: 'summer2022MantaRayMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Healer: { set: 'summer2022AngelfishHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2022Rogue: { set: 'fall2022KappaRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Warrior: { set: 'fall2022OrcWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Mage: { set: 'fall2022HarpyMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Healer: { set: 'fall2022WatcherHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2023Rogue: { set: 'winter2023RibbonRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Warrior: { set: 'winter2023WalrusWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Mage: { set: 'winter2023FairyLightsMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Healer: { set: 'winter2023CardinalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, nye2022: { text: t('headSpecialNye2022Text'), @@ -2055,51 +1721,39 @@ const head = { }, spring2023Rogue: { set: 'spring2023CaterpillarRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Warrior: { set: 'spring2023HummingbirdWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Mage: { set: 'spring2023MoonstoneMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Healer: { set: 'spring2023LilyHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2023Rogue: { set: 'summer2023GuppyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Warrior: { set: 'summer2023GoldfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Mage: { set: 'summer2023CoralMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Healer: { set: 'summer2023KelpHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2023Healer: { set: 'fall2023BogCreatureHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Mage: { set: 'fall2023ScarletWarlockMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Rogue: { set: 'fall2023WitchsBrewRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Warrior: { set: 'fall2023ScaryMovieWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2024Healer: { set: 'winter2024FrozenHealerSet', @@ -2156,9 +1810,6 @@ Object.keys(gearEvents).forEach(event => { notes: t(`${textString}Notes`, headStats[klass]), value: 60, }, headStats[klass]); - if (head[eventString].canBuy && head[eventString].canBuy()) { - head[eventString].event = CURRENT_EVENT; - } }); }); @@ -2170,7 +1821,6 @@ const headAccessory = { text: t('headAccessorySpecialSpringRogueText'), notes: t('headAccessorySpecialSpringRogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springWarrior: { specialClass: 'warrior', @@ -2178,7 +1828,6 @@ const headAccessory = { text: t('headAccessorySpecialSpringWarriorText'), notes: t('headAccessorySpecialSpringWarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springMage: { specialClass: 'wizard', @@ -2186,7 +1835,6 @@ const headAccessory = { text: t('headAccessorySpecialSpringMageText'), notes: t('headAccessorySpecialSpringMageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springHealer: { specialClass: 'healer', @@ -2194,7 +1842,6 @@ const headAccessory = { text: t('headAccessorySpecialSpringHealerText'), notes: t('headAccessorySpecialSpringHealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Rogue: { specialClass: 'rogue', @@ -2202,7 +1849,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2015RogueText'), notes: t('headAccessorySpecialSpring2015RogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Warrior: { specialClass: 'warrior', @@ -2210,7 +1856,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2015WarriorText'), notes: t('headAccessorySpecialSpring2015WarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Mage: { specialClass: 'wizard', @@ -2218,7 +1863,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2015MageText'), notes: t('headAccessorySpecialSpring2015MageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Healer: { specialClass: 'healer', @@ -2226,7 +1870,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2015HealerText'), notes: t('headAccessorySpecialSpring2015HealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, bearEars: { gearSet: 'animal', @@ -2234,7 +1877,6 @@ const headAccessory = { notes: t('headAccessoryBearEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_bearEars'), - canBuy: () => true, }, cactusEars: { gearSet: 'animal', @@ -2242,7 +1884,6 @@ const headAccessory = { notes: t('headAccessoryCactusEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_cactusEars'), - canBuy: () => true, }, foxEars: { gearSet: 'animal', @@ -2250,7 +1891,6 @@ const headAccessory = { notes: t('headAccessoryFoxEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_foxEars'), - canBuy: () => true, }, lionEars: { gearSet: 'animal', @@ -2258,7 +1898,6 @@ const headAccessory = { notes: t('headAccessoryLionEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_lionEars'), - canBuy: () => true, }, pandaEars: { gearSet: 'animal', @@ -2266,7 +1905,6 @@ const headAccessory = { notes: t('headAccessoryPandaEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_pandaEars'), - canBuy: () => true, }, pigEars: { gearSet: 'animal', @@ -2274,7 +1912,6 @@ const headAccessory = { notes: t('headAccessoryPigEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_pigEars'), - canBuy: () => true, }, tigerEars: { gearSet: 'animal', @@ -2282,7 +1919,6 @@ const headAccessory = { notes: t('headAccessoryTigerEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_tigerEars'), - canBuy: () => true, }, wolfEars: { gearSet: 'animal', @@ -2290,7 +1926,6 @@ const headAccessory = { notes: t('headAccessoryWolfEarsNotes'), value: 20, canOwn: ownsItem('headAccessory_special_wolfEars'), - canBuy: () => true, }, spring2016Rogue: { specialClass: 'rogue', @@ -2298,7 +1933,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2016RogueText'), notes: t('headAccessorySpecialSpring2016RogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Warrior: { specialClass: 'warrior', @@ -2306,7 +1940,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2016WarriorText'), notes: t('headAccessorySpecialSpring2016WarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Mage: { specialClass: 'wizard', @@ -2314,7 +1947,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2016MageText'), notes: t('headAccessorySpecialSpring2016MageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Healer: { specialClass: 'healer', @@ -2322,7 +1954,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2016HealerText'), notes: t('headAccessorySpecialSpring2016HealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Rogue: { specialClass: 'rogue', @@ -2330,7 +1961,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2017RogueText'), notes: t('headAccessorySpecialSpring2017RogueNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Warrior: { specialClass: 'warrior', @@ -2338,7 +1968,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2017WarriorText'), notes: t('headAccessorySpecialSpring2017WarriorNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Mage: { specialClass: 'wizard', @@ -2346,7 +1975,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2017MageText'), notes: t('headAccessorySpecialSpring2017MageNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Healer: { specialClass: 'healer', @@ -2354,7 +1982,6 @@ const headAccessory = { text: t('headAccessorySpecialSpring2017HealerText'), notes: t('headAccessorySpecialSpring2017HealerNotes'), value: 20, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, blackHeadband: { gearSet: 'headband', @@ -2475,7 +2102,6 @@ const shield = { notes: t('shieldSpecialYetiNotes', { con: 7 }), con: 7, value: 70, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, ski: { specialClass: 'rogue', @@ -2484,7 +2110,6 @@ const shield = { notes: t('weaponSpecialSkiNotes', { str: 8 }), str: 8, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, snowflake: { specialClass: 'healer', @@ -2493,249 +2118,188 @@ const shield = { notes: t('shieldSpecialSnowflakeNotes', { con: 9 }), con: 9, value: 70, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, springRogue: { set: 'stealthyKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springWarrior: { set: 'mightyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springHealer: { set: 'lovingPupSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summerRogue: { set: 'roguishPirateSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerWarrior: { set: 'daringSwashbucklerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerHealer: { set: 'reefSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fallRogue: { set: 'vampireSmiterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallWarrior: { set: 'monsterOfScienceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallHealer: { set: 'mummyMedicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2015Rogue: { set: 'icicleDrakeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Warrior: { set: 'gingerbreadSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Healer: { set: 'soothingSkaterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2015Rogue: { set: 'sneakySqueakerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Warrior: { set: 'bewareDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Healer: { set: 'comfortingKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2015Rogue: { set: 'reefRenegadeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Warrior: { set: 'sunfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Healer: { set: 'strappingSailorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2015Rogue: { set: 'battleRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Warrior: { set: 'scarecrowWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Healer: { set: 'potionerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2016Rogue: { set: 'cocoaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Warrior: { set: 'snowDaySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Healer: { set: 'festiveFairySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2016Rogue: { set: 'cleverDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Warrior: { set: 'braveMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Healer: { set: 'springingBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2016Rogue: { set: 'summer2016EelSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Warrior: { set: 'summer2016SharkWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Healer: { set: 'summer2016SeahorseHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2016Rogue: { set: 'fall2016BlackWidowSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Warrior: { set: 'fall2016SwampThingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Healer: { set: 'fall2016GorgonHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2017Rogue: { set: 'winter2017FrostyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Warrior: { set: 'winter2017IceHockeySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Healer: { set: 'winter2017SugarPlumSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2017Rogue: { set: 'spring2017SneakyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Warrior: { set: 'spring2017FelineWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Healer: { set: 'spring2017FloralMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2017Rogue: { set: 'summer2017SeaDragonSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Warrior: { set: 'summer2017SandcastleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Healer: { set: 'summer2017SeashellSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2017Rogue: { set: 'fall2017TrickOrTreatSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Warrior: { set: 'fall2017HabitoweenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Healer: { set: 'fall2017HauntedHouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2018Rogue: { set: 'winter2018ReindeerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Warrior: { set: 'winter2018GiftWrappedSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Healer: { set: 'winter2018MistletoeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2018Rogue: { set: 'spring2018DucklingRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Warrior: { set: 'spring2018SunriseWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Healer: { set: 'spring2018GarnetHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2018Rogue: { set: 'summer2018FisherRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Warrior: { set: 'summer2018BettaFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Healer: { set: 'summer2018MerfolkMonarchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2018Rogue: { set: 'fall2018AlterEgoSet', text: t('shieldSpecialFall2018RogueText'), notes: t('shieldSpecialFall2018RogueNotes', { str: 8 }), - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Warrior: { set: 'fall2018MinotaurWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Healer: { set: 'fall2018CarnivorousPlantSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2019Rogue: { set: 'winter2019PoinsettiaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Warrior: { set: 'winter2019BlizzardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Healer: { set: 'winter2019WinterStarSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, piDay: { text: t('shieldSpecialPiDayText'), @@ -2745,27 +2309,21 @@ const shield = { }, spring2019Rogue: { set: 'spring2019CloudRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Warrior: { set: 'spring2019OrchidWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Healer: { set: 'spring2019RobinHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2019Rogue: { set: 'summer2019HammerheadRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Warrior: { set: 'summer2019SeaTurtleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Healer: { set: 'summer2019ConchHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Mage: { specialClass: 'wizard', @@ -2774,19 +2332,15 @@ const shield = { notes: t('shieldSpecialSummer2019MageNotes', { per: 7 }), value: 70, per: 7, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2019Rogue: { set: 'fall2019OperaticSpecterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Warrior: { set: 'fall2019RavenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Healer: { set: 'fall2019LichSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, ks2019: { text: t('shieldSpecialKS2019Text'), @@ -2797,199 +2351,151 @@ const shield = { }, winter2020Rogue: { set: 'winter2020LanternSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Warrior: { set: 'winter2020EvergreenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Healer: { set: 'winter2020WinterSpiceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2020Rogue: { set: 'spring2020LapisLazuliRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Warrior: { set: 'spring2020BeetleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Healer: { set: 'spring2020IrisHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2020Warrior: { set: 'summer2020RainbowTroutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Healer: { set: 'summer2020SeaGlassHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Rogue: { set: 'summer2020CrocodileRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2020Rogue: { set: 'fall2020TwoHeadedRogueSet', text: t('shieldSpecialFall2020RogueText'), notes: t('shieldSpecialFall2020RogueNotes', { str: 8 }), - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Warrior: { set: 'fall2020WraithWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Healer: { set: 'fall2020DeathsHeadMothHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2021Rogue: { set: 'winter2021HollyIvyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Warrior: { set: 'winter2021IceFishingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Healer: { set: 'winter2021ArcticExplorerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2021Rogue: { set: 'spring2021TwinFlowerRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Warrior: { set: 'spring2021SunstoneWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Healer: { set: 'spring2021WillowHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2021Rogue: { set: 'summer2021ClownfishRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Warrior: { set: 'summer2021FlyingFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Healer: { set: 'summer2021ParrotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2021Rogue: { set: 'fall2021OozeRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Warrior: { set: 'fall2021HeadlessWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Healer: { set: 'fall2021FlameSummonerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2022Rogue: { set: 'winter2022FireworksRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Warrior: { set: 'winter2022StockingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Healer: { set: 'winter2022IceCrystalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2022Rogue: { set: 'spring2022MagpieRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Warrior: { set: 'spring2022RainstormWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Healer: { set: 'spring2022PeridotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2022Rogue: { set: 'summer2022CrabRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Warrior: { set: 'summer2022WaterspoutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Healer: { set: 'summer2022AngelfishHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2022Rogue: { set: 'fall2022KappaRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Warrior: { set: 'fall2022OrcWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Healer: { set: 'fall2022WatcherHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2023Rogue: { set: 'winter2023RibbonRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Warrior: { set: 'winter2023WalrusWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Healer: { set: 'winter2023CardinalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2023Rogue: { set: 'spring2023CaterpillarRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Warrior: { set: 'spring2023HummingbirdWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Healer: { set: 'spring2023LilyHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2023Rogue: { set: 'summer2023GuppyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Warrior: { set: 'summer2023GoldfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Healer: { set: 'summer2023KelpHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2023Rogue: { set: 'fall2023WitchsBrewRogueSet', text: t('shieldSpecialFall2023RogueText'), notes: t('shieldSpecialFall2023RogueNotes', { str: 8 }), - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Warrior: { set: 'fall2023ScaryMovieWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Healer: { set: 'fall2023BogCreatureHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2024Warrior: { set: 'winter2024PeppermintBarkWarriorSet', @@ -3035,9 +2541,6 @@ Object.keys(gearEvents).forEach(event => { notes: t(`${textString}Notes`, shieldStats[klass]), value: klass === 'rogue' ? 80 : 70, }, shieldStats[klass]); - if (shield[eventString].canBuy && shield[eventString].canBuy()) { - shield[eventString].event = CURRENT_EVENT; - } }); }); @@ -3149,7 +2652,6 @@ const weapon = { notes: t('weaponSpecialYetiNotes', { str: 15 }), str: 15, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, ski: { specialClass: 'rogue', @@ -3158,7 +2660,6 @@ const weapon = { notes: t('weaponSpecialSkiNotes', { str: 8 }), str: 8, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, candycane: { specialClass: 'wizard', @@ -3169,7 +2670,6 @@ const weapon = { int: 15, per: 7, value: 160, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, snowflake: { specialClass: 'healer', @@ -3178,351 +2678,264 @@ const weapon = { notes: t('weaponSpecialSnowflakeNotes', { int: 9 }), int: 9, value: 90, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, springRogue: { set: 'stealthyKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springWarrior: { set: 'mightyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springMage: { set: 'magicMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, springHealer: { set: 'lovingPupSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summerRogue: { set: 'roguishPirateSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerWarrior: { set: 'daringSwashbucklerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerMage: { set: 'emeraldMermageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summerHealer: { set: 'reefSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fallRogue: { set: 'vampireSmiterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallWarrior: { set: 'monsterOfScienceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallMage: { set: 'witchyWizardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fallHealer: { set: 'mummyMedicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2015Rogue: { set: 'icicleDrakeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Warrior: { set: 'gingerbreadSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Mage: { set: 'northMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2015Healer: { set: 'soothingSkaterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2015Rogue: { set: 'sneakySqueakerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Warrior: { set: 'bewareDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Mage: { set: 'magicianBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2015Healer: { set: 'comfortingKittySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2015Rogue: { set: 'reefRenegadeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Warrior: { set: 'sunfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Mage: { set: 'shipSoothsayerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2015Healer: { set: 'strappingSailorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2015Rogue: { set: 'battleRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Warrior: { set: 'scarecrowWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Mage: { set: 'stitchWitchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2015Healer: { set: 'potionerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2016Rogue: { set: 'cocoaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Warrior: { set: 'snowDaySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Mage: { set: 'snowboardingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2016Healer: { set: 'festiveFairySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2016Rogue: { set: 'cleverDogSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Warrior: { set: 'braveMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Mage: { set: 'grandMalkinSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2016Healer: { set: 'springingBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2016Rogue: { set: 'summer2016EelSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Warrior: { set: 'summer2016SharkWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Mage: { set: 'summer2016DolphinMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2016Healer: { set: 'summer2016SeahorseHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2016Rogue: { set: 'fall2016BlackWidowSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Warrior: { set: 'fall2016SwampThingSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Mage: { set: 'fall2016WickedSorcererSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2016Healer: { set: 'fall2016GorgonHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2017Rogue: { set: 'winter2017FrostyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Warrior: { set: 'winter2017IceHockeySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Mage: { set: 'winter2017WinterWolfSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2017Healer: { set: 'winter2017SugarPlumSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2017Rogue: { set: 'spring2017SneakyBunnySet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Warrior: { set: 'spring2017FelineWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Mage: { set: 'spring2017CanineConjurorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2017Healer: { set: 'spring2017FloralMouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2017Rogue: { set: 'summer2017SeaDragonSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Warrior: { set: 'summer2017SandcastleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Mage: { set: 'summer2017WhirlpoolMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2017Healer: { set: 'summer2017SeashellSeahealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2017Rogue: { set: 'fall2017TrickOrTreatSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Warrior: { set: 'fall2017HabitoweenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Mage: { set: 'fall2017MasqueradeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2017Healer: { set: 'fall2017HauntedHouseSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2018Rogue: { set: 'winter2018ReindeerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Warrior: { set: 'winter2018GiftWrappedSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Mage: { set: 'winter2018ConfettiSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2018Healer: { set: 'winter2018MistletoeSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2018Rogue: { set: 'spring2018DucklingRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Warrior: { set: 'spring2018SunriseWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Mage: { set: 'spring2018TulipMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2018Healer: { set: 'spring2018GarnetHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2018Rogue: { set: 'summer2018FisherRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Warrior: { set: 'summer2018BettaFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Mage: { set: 'summer2018LionfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2018Healer: { set: 'summer2018MerfolkMonarchSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2018Rogue: { set: 'fall2018AlterEgoSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Warrior: { set: 'fall2018MinotaurWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Mage: { set: 'fall2018CandymancerMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2018Healer: { set: 'fall2018CarnivorousPlantSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2019Rogue: { set: 'winter2019PoinsettiaSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Warrior: { set: 'winter2019BlizzardSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Mage: { set: 'winter2019PyrotechnicSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2019Healer: { set: 'winter2019WinterStarSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2019Rogue: { set: 'spring2019CloudRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Warrior: { set: 'spring2019OrchidWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Mage: { set: 'spring2019AmberMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2019Healer: { set: 'spring2019RobinHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2019Rogue: { set: 'summer2019HammerheadRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Warrior: { set: 'summer2019SeaTurtleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Mage: { specialClass: 'wizard', @@ -3533,27 +2946,21 @@ const weapon = { int: 15, per: 0, twoHanded: false, - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2019Healer: { set: 'summer2019ConchHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2019Rogue: { set: 'fall2019OperaticSpecterSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Warrior: { set: 'fall2019RavenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Mage: { set: 'fall2019CyclopsSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2019Healer: { set: 'fall2019LichSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, ks2019: { text: t('weaponSpecialKS2019Text'), @@ -3564,259 +2971,195 @@ const weapon = { }, winter2020Rogue: { set: 'winter2020LanternSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Warrior: { set: 'winter2020EvergreenSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Mage: { set: 'winter2020CarolOfTheMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2020Healer: { set: 'winter2020WinterSpiceSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2020Rogue: { set: 'spring2020LapisLazuliRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Warrior: { set: 'spring2020BeetleWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Mage: { set: 'spring2020PuddleMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2020Healer: { set: 'spring2020IrisHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2020Rogue: { set: 'summer2020CrocodileRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Warrior: { set: 'summer2020RainbowTroutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Mage: { set: 'summer2020OarfishMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2020Healer: { set: 'summer2020SeaGlassHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2020Rogue: { set: 'fall2020TwoHeadedRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Warrior: { set: 'fall2020WraithWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Mage: { set: 'fall2020ThirdEyeMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2020Healer: { set: 'fall2020DeathsHeadMothHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2021Rogue: { set: 'winter2021HollyIvyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Warrior: { set: 'winter2021IceFishingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Mage: { set: 'winter2021WinterMoonMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2021Healer: { set: 'winter2021ArcticExplorerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2021Rogue: { set: 'spring2021TwinFlowerRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Warrior: { set: 'spring2021SunstoneWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Mage: { set: 'spring2021SwanMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2021Healer: { set: 'spring2021WillowHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2021Rogue: { set: 'summer2021ClownfishRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Warrior: { set: 'summer2021FlyingFishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Mage: { set: 'summer2021NautilusMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2021Healer: { set: 'summer2021ParrotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2021Rogue: { set: 'fall2021OozeRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Warrior: { set: 'fall2021HeadlessWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Mage: { set: 'fall2021BrainEaterMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2021Healer: { set: 'fall2021FlameSummonerHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2022Rogue: { set: 'winter2022FireworksRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Warrior: { set: 'winter2022StockingWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Mage: { set: 'winter2022PomegranateMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2022Healer: { set: 'winter2022IceCrystalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2022Rogue: { set: 'spring2022MagpieRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Warrior: { set: 'spring2022RainstormWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Mage: { set: 'spring2022ForsythiaMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2022Healer: { set: 'spring2022PeridotHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2022Rogue: { set: 'summer2022CrabRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Warrior: { set: 'summer2022WaterspoutWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Mage: { set: 'summer2022MantaRayMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2022Healer: { set: 'summer2022AngelfishHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2022Rogue: { set: 'fall2022KappaRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Warrior: { set: 'fall2022OrcWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Mage: { set: 'fall2022HarpyMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2022Healer: { set: 'fall2022WatcherHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2023Rogue: { set: 'winter2023RibbonRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Warrior: { set: 'winter2023WalrusWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Mage: { set: 'winter2023FairyLightsMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, winter2023Healer: { set: 'winter2023CardinalHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'winter', }, spring2023Rogue: { set: 'spring2023CaterpillarRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Warrior: { set: 'spring2023HummingbirdWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Mage: { set: 'spring2023MoonstoneMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, spring2023Healer: { set: 'spring2023LilyHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'spring', }, summer2023Rogue: { set: 'summer2023GuppyRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Warrior: { set: 'summer2023GoldfishWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Mage: { set: 'summer2023CoralMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, summer2023Healer: { set: 'summer2023KelpHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'summer', }, fall2023Rogue: { set: 'fall2023WitchsBrewRogueSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Healer: { set: 'fall2023BogCreatureHealerSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Warrior: { set: 'fall2023ScaryMovieWarriorSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, fall2023Mage: { set: 'fall2023ScarletWarlockMageSet', - canBuy: () => CURRENT_EVENT && CURRENT_EVENT.season === 'fall', }, winter2024Rogue: { set: 'winter2024SnowyOwlRogueSet', @@ -3875,9 +3218,6 @@ Object.keys(gearEvents).forEach(event => { value: weaponCosts[klass], twoHanded: klass === 'wizard', }, weaponStats[klass]); - if (weapon[eventString].canBuy && weapon[eventString].canBuy()) { - weapon[eventString].event = CURRENT_EVENT; - } }); }); diff --git a/website/common/script/content/hatching-potions.js b/website/common/script/content/hatching-potions.js index 10fb2957fc..e4184126ca 100644 --- a/website/common/script/content/hatching-potions.js +++ b/website/common/script/content/hatching-potions.js @@ -1,9 +1,7 @@ import assign from 'lodash/assign'; import defaults from 'lodash/defaults'; import each from 'lodash/each'; -import moment from 'moment'; import t from './translation'; -import { EVENTS } from './constants'; function hasQuestAchievementFunction (key) { return user => user.achievements.quests && user.achievements.quests[key] > 0; @@ -62,106 +60,70 @@ const premium = { value: 2, text: t('hatchingPotionCupid'), limited: true, - event: EVENTS.potions202402, _addlNotes: t('eventAvailability', { date: t('dateEndFebruary'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202402.start, EVENTS.potions202402.end); - }, }, Shimmer: { value: 2, text: t('hatchingPotionShimmer'), limited: true, - event: EVENTS.spring2024, _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBetween(EVENTS.spring2024.start, EVENTS.spring2024.end); - }, }, Fairy: { value: 2, text: t('hatchingPotionFairy'), limited: true, - event: EVENTS.potions202305, _addlNotes: t('eventAvailability', { date: t('dateEndMay'), }), - canBuy () { - return moment().isBefore(EVENTS.potions202305.end); - }, }, Floral: { value: 2, text: t('hatchingPotionFloral'), limited: true, - event: EVENTS.potions202305, _addlNotes: t('eventAvailability', { date: t('dateEndMay'), }), - canBuy () { - return moment().isBefore(EVENTS.potions202305.end); - }, }, Aquatic: { value: 2, text: t('hatchingPotionAquatic'), limited: true, - event: EVENTS.birthday10, _addlNotes: t('eventAvailability', { date: t('dateEndFebruary'), }), - canBuy () { - return moment().isBetween(EVENTS.birthday10.start, EVENTS.birthday10.end); - }, }, Ember: { value: 2, text: t('hatchingPotionEmber'), limited: true, - event: EVENTS.potions202311, _addlNotes: t('eventAvailability', { date: t('dateEndNovember'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end); - }, }, Thunderstorm: { value: 2, text: t('hatchingPotionThunderstorm'), limited: true, - event: EVENTS.potions202308, _addlNotes: t('eventAvailability', { date: t('dateEndAugust'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202308.start, EVENTS.potions202308.end); - }, }, Spooky: { value: 2, text: t('hatchingPotionSpooky'), limited: true, - event: EVENTS.fall2023, _addlNotes: t('eventAvailability', { date: t('dateEndOctober'), }), - canBuy () { - return moment().isBetween(EVENTS.fall2023.start, EVENTS.fall2023.end); - }, }, Ghost: { value: 2, text: t('hatchingPotionGhost'), limited: true, - event: EVENTS.fall2022, - canBuy () { - return moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end); - }, _addlNotes: t('eventAvailability', { date: t('dateEndOctober'), }), @@ -173,10 +135,6 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), - event: EVENTS.winter2023, - canBuy () { - return moment().isBetween(EVENTS.winter2023.start, EVENTS.winter2023.end); - }, }, Peppermint: { value: 2, @@ -185,19 +143,11 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), - event: EVENTS.winter2024, - canBuy () { - return moment().isBetween(EVENTS.winter2024.start, EVENTS.winter2024.end); - }, }, StarryNight: { value: 2, text: t('hatchingPotionStarryNight'), limited: true, - event: EVENTS.winter2023, - canBuy () { - return moment().isBetween(EVENTS.winter2023.start, EVENTS.winter2023.end); - }, _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), @@ -209,10 +159,6 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - event: EVENTS.spring2024, - canBuy () { - return moment().isBetween(EVENTS.spring2024.start, EVENTS.spring2024.end); - }, }, Glass: { value: 2, @@ -221,82 +167,54 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndJuly'), }), - event: EVENTS.summer2023, - canBuy () { - return moment().isBetween(EVENTS.summer2023.start, EVENTS.summer2023.end); - }, }, Glow: { value: 2, text: t('hatchingPotionGlow'), limited: true, - event: EVENTS.fall2023, _addlNotes: t('eventAvailability', { date: t('dateEndOctober'), }), - canBuy () { - return moment().isBetween(EVENTS.fall2023.start, EVENTS.fall2023.end); - }, }, Frost: { value: 2, text: t('hatchingPotionFrost'), limited: true, - event: EVENTS.potions202311, _addlNotes: t('eventAvailability', { date: t('dateEndNovember'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end); - }, }, IcySnow: { value: 2, text: t('hatchingPotionIcySnow'), limited: true, - event: EVENTS.winter2024, _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), - canBuy () { - return moment().isBetween(EVENTS.winter2024.start, EVENTS.winter2024.end); - }, }, RoseQuartz: { value: 2, text: t('hatchingPotionRoseQuartz'), limited: true, - event: EVENTS.potions202302, _addlNotes: t('eventAvailability', { date: t('dateEndFebruary'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202302.start, EVENTS.potions202302.end); - }, }, Celestial: { value: 2, text: t('hatchingPotionCelestial'), limited: true, - event: EVENTS.spring2024, _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBetween(EVENTS.spring2024.start, EVENTS.spring2024.end); - }, }, Sunshine: { value: 2, text: t('hatchingPotionSunshine'), limited: true, - event: EVENTS.potions202205, _addlNotes: t('eventAvailability', { date: t('dateEndMay'), }), - canBuy () { - return moment().isBefore(EVENTS.potions202205.end); - }, }, Bronze: { value: 2, @@ -309,13 +227,9 @@ const premium = { value: 2, text: t('hatchingPotionWatery'), limited: true, - event: EVENTS.summer2022, _addlNotes: t('eventAvailability', { date: t('dateEndJuly'), }), - canBuy () { - return moment().isBetween(EVENTS.summer2022.start, EVENTS.summer2022.end); - }, }, Silver: { value: 2, @@ -328,10 +242,6 @@ const premium = { value: 2, text: t('hatchingPotionShadow'), limited: true, - event: EVENTS.fall2022, - canBuy () { - return moment().isBetween(EVENTS.fall2022.start, EVENTS.fall2022.end); - }, _addlNotes: t('eventAvailability', { date: t('dateEndOctober'), }), @@ -350,10 +260,6 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), - event: EVENTS.winter2023, - canBuy () { - return moment().isBetween(EVENTS.winter2023.start, EVENTS.winter2023.end); - }, }, Ruby: { value: 2, @@ -366,13 +272,9 @@ const premium = { value: 2, text: t('hatchingPotionBirchBark'), limited: true, - event: EVENTS.spring2023, _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBefore(EVENTS.spring2023.end); - }, }, Fluorite: { value: 2, @@ -385,13 +287,9 @@ const premium = { value: 2, text: t('hatchingPotionSandSculpture'), limited: true, - event: EVENTS.summer2023, date: t('eventAvailability', { date: t('dateEndJuly'), }), - canBuy () { - return moment().isBetween(EVENTS.summer2023.start, EVENTS.summer2023.end); - }, }, Windup: { value: 2, @@ -411,25 +309,17 @@ const premium = { value: 2, text: t('hatchingPotionVampire'), limited: true, - event: EVENTS.fall2023, _addlNotes: t('eventAvailability', { date: t('dateEndOctober'), }), - canBuy () { - return moment().isBetween(EVENTS.fall2023.start, EVENTS.fall2023.end); - }, }, AutumnLeaf: { value: 2, text: t('hatchingPotionAutumnLeaf'), limited: true, - event: EVENTS.potions202311, _addlNotes: t('eventAvailability', { date: t('dateEndNovember'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202311.start, EVENTS.potions202311.end); - }, }, BlackPearl: { value: 2, @@ -445,22 +335,14 @@ const premium = { _addlNotes: t('eventAvailability', { date: t('dateEndJanuary'), }), - event: EVENTS.winter2024, - canBuy () { - return moment().isBetween(EVENTS.winter2024.start, EVENTS.winter2024.end); - }, }, PolkaDot: { value: 2, text: t('hatchingPotionPolkaDot'), limited: true, - event: EVENTS.spring2023, _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBefore(EVENTS.spring2023.end); - }, }, MossyStone: { value: 2, @@ -473,25 +355,17 @@ const premium = { value: 2, text: t('hatchingPotionSunset'), limited: true, - event: EVENTS.summer2023, _addlNotes: t('premiumPotionAddlNotes', { date: t('dateEndJuly'), }), - canBuy () { - return moment().isBetween(EVENTS.summer2023.start, EVENTS.summer2023.end); - }, }, Moonglow: { value: 2, text: t('hatchingPotionMoonglow'), limited: true, - event: EVENTS.potions202208, _addlNotes: t('premiumPotionAddlNotes', { date: t('dateEndAugust'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202208.start, EVENTS.potions202208.end); - }, }, SolarSystem: { value: 2, @@ -511,13 +385,9 @@ const premium = { value: 2, text: t('hatchingPotionPorcelain'), limited: true, - event: EVENTS.potions202308, _addlNotes: t('eventAvailability', { date: t('dateEndAugust'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202308.start, EVENTS.potions202308.end); - }, }, PinkMarble: { value: 2, @@ -544,13 +414,9 @@ const wacky = { Veggie: { text: t('hatchingPotionVeggie'), limited: true, - event: EVENTS.spring2023, _addlNotes: t('eventAvailability', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBetween('2023-04-06T08:00-04:00', EVENTS.spring2023.end); - }, }, Dessert: { text: t('hatchingPotionDessert'), @@ -567,13 +433,9 @@ const wacky = { TeaShop: { text: t('hatchingPotionTeaShop'), limited: true, - event: EVENTS.spring2023, _addlNotes: t('premiumPotionAddlNotes', { date: t('dateEndApril'), }), - canBuy () { - return moment().isBetween('2023-04-06T08:00-04:00', EVENTS.spring2023.end); - }, }, }; diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 559caf82d7..67fcda98f1 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -211,105 +211,62 @@ api.food = { textA: t('foodMeatA'), textThe: t('foodMeatThe'), target: 'Base', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Milk: { text: t('foodMilk'), textA: t('foodMilkA'), textThe: t('foodMilkThe'), target: 'White', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Potatoe: { text: t('foodPotatoe'), textA: t('foodPotatoeA'), textThe: t('foodPotatoeThe'), target: 'Desert', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Strawberry: { text: t('foodStrawberry'), textA: t('foodStrawberryA'), textThe: t('foodStrawberryThe'), target: 'Red', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Chocolate: { text: t('foodChocolate'), textA: t('foodChocolateA'), textThe: t('foodChocolateThe'), target: 'Shade', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Fish: { text: t('foodFish'), textA: t('foodFishA'), textThe: t('foodFishThe'), target: 'Skeleton', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, RottenMeat: { text: t('foodRottenMeat'), textA: t('foodRottenMeatA'), textThe: t('foodRottenMeatThe'), target: 'Zombie', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, CottonCandyPink: { text: t('foodCottonCandyPink'), textA: t('foodCottonCandyPinkA'), textThe: t('foodCottonCandyPinkThe'), target: 'CottonCandyPink', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, CottonCandyBlue: { text: t('foodCottonCandyBlue'), textA: t('foodCottonCandyBlueA'), textThe: t('foodCottonCandyBlueThe'), target: 'CottonCandyBlue', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Honey: { text: t('foodHoney'), textA: t('foodHoneyA'), textThe: t('foodHoneyThe'), target: 'Golden', - canBuy () { - return FOOD_SEASON === 'Normal'; - }, - canDrop: FOOD_SEASON === 'Normal', }, Saddle: { - canBuy () { - return true; - }, sellWarningNote: t('foodSaddleSellWarningNote'), text: t('foodSaddleText'), value: 5, @@ -321,313 +278,201 @@ api.food = { textA: t('foodCakeSkeletonA'), textThe: t('foodCakeSkeletonThe'), target: 'Skeleton', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Base: { text: t('foodCakeBase'), textA: t('foodCakeBaseA'), textThe: t('foodCakeBaseThe'), target: 'Base', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_CottonCandyBlue: { text: t('foodCakeCottonCandyBlue'), textA: t('foodCakeCottonCandyBlueA'), textThe: t('foodCakeCottonCandyBlueThe'), target: 'CottonCandyBlue', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_CottonCandyPink: { text: t('foodCakeCottonCandyPink'), textA: t('foodCakeCottonCandyPinkA'), textThe: t('foodCakeCottonCandyPinkThe'), target: 'CottonCandyPink', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Shade: { text: t('foodCakeShade'), textA: t('foodCakeShadeA'), textThe: t('foodCakeShadeThe'), target: 'Shade', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_White: { text: t('foodCakeWhite'), textA: t('foodCakeWhiteA'), textThe: t('foodCakeWhiteThe'), target: 'White', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Golden: { text: t('foodCakeGolden'), textA: t('foodCakeGoldenA'), textThe: t('foodCakeGoldenThe'), target: 'Golden', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Zombie: { text: t('foodCakeZombie'), textA: t('foodCakeZombieA'), textThe: t('foodCakeZombieThe'), target: 'Zombie', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Desert: { text: t('foodCakeDesert'), textA: t('foodCakeDesertA'), textThe: t('foodCakeDesertThe'), target: 'Desert', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Cake_Red: { text: t('foodCakeRed'), textA: t('foodCakeRedA'), textThe: t('foodCakeRedThe'), target: 'Red', - canBuy () { - return FOOD_SEASON === 'Cake'; - }, - canDrop: FOOD_SEASON === 'Cake', }, Candy_Skeleton: { text: t('foodCandySkeleton'), textA: t('foodCandySkeletonA'), textThe: t('foodCandySkeletonThe'), target: 'Skeleton', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Base: { text: t('foodCandyBase'), textA: t('foodCandyBaseA'), textThe: t('foodCandyBaseThe'), target: 'Base', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_CottonCandyBlue: { text: t('foodCandyCottonCandyBlue'), textA: t('foodCandyCottonCandyBlueA'), textThe: t('foodCandyCottonCandyBlueThe'), target: 'CottonCandyBlue', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_CottonCandyPink: { text: t('foodCandyCottonCandyPink'), textA: t('foodCandyCottonCandyPinkA'), textThe: t('foodCandyCottonCandyPinkThe'), target: 'CottonCandyPink', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Shade: { text: t('foodCandyShade'), textA: t('foodCandyShadeA'), textThe: t('foodCandyShadeThe'), target: 'Shade', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_White: { text: t('foodCandyWhite'), textA: t('foodCandyWhiteA'), textThe: t('foodCandyWhiteThe'), target: 'White', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Golden: { text: t('foodCandyGolden'), textA: t('foodCandyGoldenA'), textThe: t('foodCandyGoldenThe'), target: 'Golden', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Zombie: { text: t('foodCandyZombie'), textA: t('foodCandyZombieA'), textThe: t('foodCandyZombieThe'), target: 'Zombie', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Desert: { text: t('foodCandyDesert'), textA: t('foodCandyDesertA'), textThe: t('foodCandyDesertThe'), target: 'Desert', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Candy_Red: { text: t('foodCandyRed'), textA: t('foodCandyRedA'), textThe: t('foodCandyRedThe'), target: 'Red', - canBuy () { - return FOOD_SEASON === 'Candy'; - }, - canDrop: FOOD_SEASON === 'Candy', }, Pie_Skeleton: { text: t('foodPieSkeleton'), textA: t('foodPieSkeletonA'), textThe: t('foodPieSkeletonThe'), target: 'Skeleton', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Base: { text: t('foodPieBase'), textA: t('foodPieBaseA'), textThe: t('foodPieBaseThe'), target: 'Base', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_CottonCandyBlue: { text: t('foodPieCottonCandyBlue'), textA: t('foodPieCottonCandyBlueA'), textThe: t('foodPieCottonCandyBlueThe'), target: 'CottonCandyBlue', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_CottonCandyPink: { text: t('foodPieCottonCandyPink'), textA: t('foodPieCottonCandyPinkA'), textThe: t('foodPieCottonCandyPinkThe'), target: 'CottonCandyPink', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Shade: { text: t('foodPieShade'), textA: t('foodPieShadeA'), textThe: t('foodPieShadeThe'), target: 'Shade', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_White: { text: t('foodPieWhite'), textA: t('foodPieWhiteA'), textThe: t('foodPieWhiteThe'), target: 'White', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Golden: { text: t('foodPieGolden'), textA: t('foodPieGoldenA'), textThe: t('foodPieGoldenThe'), target: 'Golden', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Zombie: { text: t('foodPieZombie'), textA: t('foodPieZombieA'), textThe: t('foodPieZombieThe'), target: 'Zombie', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Desert: { text: t('foodPieDesert'), textA: t('foodPieDesertA'), textThe: t('foodPieDesertThe'), target: 'Desert', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, Pie_Red: { text: t('foodPieRed'), textA: t('foodPieRedA'), textThe: t('foodPieRedThe'), target: 'Red', - canBuy () { - return FOOD_SEASON === 'Pie'; - }, - canDrop: FOOD_SEASON === 'Pie', }, /* eslint-enable camelcase */ }; -each(api.food, (food, key) => defaults(food, { - value: 1, - key, - notes: t('foodNotes'), - canBuy () { - return false; - }, - canDrop: false, -})); +each(api.food, (food, key) => { + let foodType = 'Normal'; + if (key.startsWith('Cake_')) { + foodType = 'Cake'; + } else if (key.startsWith('Candy_')) { + foodType = 'Candy'; + } else if (key.startsWith('Pie_')) { + foodType = 'Pie'; + } + defaults(food, { + value: 1, + key, + notes: t('foodNotes'), + canBuy: () => FOOD_SEASON === foodType, + canDrop: FOOD_SEASON === foodType, + }); +}); api.appearances = appearances; diff --git a/website/common/script/content/quests/seasonal.js b/website/common/script/content/quests/seasonal.js index d74a41b418..90f49eb152 100644 --- a/website/common/script/content/quests/seasonal.js +++ b/website/common/script/content/quests/seasonal.js @@ -1,18 +1,8 @@ -import find from 'lodash/find'; -import moment from 'moment'; -import { EVENTS } from '../constants/events'; import t from '../translation'; -const CURRENT_EVENT = find(EVENTS, event => moment() - .isBetween(event.start, event.end) && Boolean(event.season)); - const QUEST_SEASONAL = { // winter evilsanta: { - event: CURRENT_EVENT && CURRENT_EVENT.season === 'winter' ? CURRENT_EVENT : null, - canBuy () { - return this.event && moment().isBetween(this.event.start, this.event.end); - }, text: t('questEvilSantaText'), notes: t('questEvilSantaNotes'), addlNotes: t('evilSantaAddlNotes'), @@ -37,10 +27,6 @@ const QUEST_SEASONAL = { }, }, evilsanta2: { - event: CURRENT_EVENT && CURRENT_EVENT.season === 'winter' ? CURRENT_EVENT : null, - canBuy () { - return this.event && moment().isBetween(this.event.start, this.event.end); - }, text: t('questEvilSanta2Text'), notes: t('questEvilSanta2Notes'), addlNotes: t('evilSantaAddlNotes'), @@ -71,10 +57,6 @@ const QUEST_SEASONAL = { }, // spring egg: { - event: CURRENT_EVENT && CURRENT_EVENT.season === 'spring' ? CURRENT_EVENT : null, - canBuy () { - return this.event && moment().isBetween('2024-03-26T08:00-04:00', this.event.end); - }, text: t('questEggHuntText'), notes: t('questEggHuntNotes'), completion: t('questEggHuntCompletion'), @@ -135,10 +117,6 @@ const QUEST_SEASONAL = { }, }, waffle: { - event: CURRENT_EVENT && CURRENT_EVENT.season === 'spring' ? CURRENT_EVENT : null, - canBuy () { - return this.event && moment().isBetween(this.event.start, this.event.end); - }, text: t('questWaffleText'), notes: t('questWaffleNotes'), completion: t('questWaffleCompletion'), @@ -178,10 +156,6 @@ const QUEST_SEASONAL = { }, }, virtualpet: { - event: CURRENT_EVENT && CURRENT_EVENT.season === 'spring' ? CURRENT_EVENT : null, - canBuy () { - return this.event && moment().isBetween(this.event.start, this.event.end); - }, text: t('questVirtualPetText'), notes: t('questVirtualPetNotes'), completion: t('questVirtualPetCompletion'), From bca3e96e9c546f64d2c42de03ee52d5f48790697 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 21 Feb 2024 17:05:21 +0100 Subject: [PATCH 034/244] Implement food seasons --- website/client/src/main.js | 1 - .../common/script/content/constants/events.js | 27 +++++++++++++++---- website/common/script/content/index.js | 10 ++++--- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/website/client/src/main.js b/website/client/src/main.js index ddf86c80a6..9c4851e28c 100644 --- a/website/client/src/main.js +++ b/website/client/src/main.js @@ -45,7 +45,6 @@ if (process.env.ENABLE_TIME_TRAVEL) { now: time, shouldAdvanceTime: true, }); - })(); } diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index a5cba04539..1ab77c3bc8 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -25,6 +25,13 @@ export const REPEATING_EVENTS = { }, ], }, + birthday: { + start: '1970-01-30T08:00-05:00', + end: '1970-02-08T23:59-05:00', + season: 'birthday', + npcImageSuffix: '_birthday', + foodSeason: 'Cake', + }, valentines: { start: '1970-02-13T08:00-05:00', end: '1970-02-17T23:59-05:00', @@ -39,17 +46,27 @@ export const REPEATING_EVENTS = { }, ], }, - birthday: { - start: '1970-01-30T08:00-05:00', - end: '1970-02-08T23:59-05:00', - season: 'birthday', - npcImageSuffix: '_birthday', + piDay: { + start: '1970-03-13T08:00-05:00', + end: '1970-03-15T23:59-05:00', + foodSeason: 'Pie', + }, + namingDay: { + start: '1970-07-30T08:00-05:00', + end: '1970-08-01T23:59-05:00', + foodSeason: 'Cake', + }, + habitoween: { + start: '1970-10-30T08:00-05:00', + end: '1970-11-01T23:59-05:00', + foodSeason: 'Candy', }, harvestFeast: { start: '1970-11-22T08:00-05:00', end: '1970-11-27T20:00-05:00', season: 'thanksgiving', npcImageSuffix: '_thanksgiving', + foodSeason: 'Pie', }, }; diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 67fcda98f1..d3691461d6 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -34,6 +34,7 @@ import faq from './faq'; import timeTravelers from './time-travelers'; import { getScheduleMatchingGroup } from './constants/schedule'; +import { getRepeatingEvents } from './constants/events'; import loginIncentives from './loginIncentives'; @@ -202,9 +203,6 @@ api.premiumMounts = stable.premiumMounts; api.specialMounts = stable.specialMounts; api.mountInfo = stable.mountInfo; -// For seasonal events, change this constant: -const FOOD_SEASON = moment().isBefore('2023-03-15T12:00-05:00') ? 'Pie' : 'Normal'; - api.food = { Meat: { text: t('foodMeat'), @@ -456,6 +454,12 @@ api.food = { /* eslint-enable camelcase */ }; +let FOOD_SEASON = 'Normal'; +getRepeatingEvents(moment()).forEach(event => { + if (event.foodSeason) { + FOOD_SEASON = event.foodSeason; + } +}); each(api.food, (food, key) => { let foodType = 'Normal'; if (key.startsWith('Cake_')) { From fe2c02679e8098556a63ca7ef48537278d77e2a2 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 21 Feb 2024 17:31:19 +0100 Subject: [PATCH 035/244] Fix buying some items --- website/common/script/ops/buy/buyQuestGem.js | 3 ++- website/common/script/ops/buy/purchase.js | 11 ++++++----- website/server/libs/worldState.js | 1 - 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/website/common/script/ops/buy/buyQuestGem.js b/website/common/script/ops/buy/buyQuestGem.js index dbc5c100cf..01b3bf7792 100644 --- a/website/common/script/ops/buy/buyQuestGem.js +++ b/website/common/script/ops/buy/buyQuestGem.js @@ -53,7 +53,8 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // esli } const matchers = getScheduleMatchingGroup(`${item.category}Quests`); - if (matchers.match(item.key)) { + console.log(matchers); + if (!matchers.match(item.key)) { throw new NotAuthorized(this.i18n('notAvailable', { key: item.key })); } diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 028755d035..8e83574850 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -55,10 +55,6 @@ async function purchaseItem (user, item, price, type, key) { if (user.markModified) user.markModified('items.gear.owned'); } else if (type === 'bundles') { const subType = item.type; - const matchers = getScheduleMatchingGroup('bundles'); - if (!matchers.match(item.key)) { - throw new NotAuthorized(i18n.t('notAvailable', { key: item.key })); - } forEach(item.bundleKeys, bundledKey => { if (!user.items[subType][bundledKey] || user.items[subType][bundledKey] < 0) { user.items[subType][bundledKey] = 0; @@ -101,7 +97,7 @@ export default async function purchase (user, req = {}, analytics) { const { price, item } = getItemAndPrice(user, type, key, req); - if (item.type === 'hatchingPotion' && item.premium === true) { + if (type === 'hatchingPotions' && item.premium === true) { const matchers = getScheduleMatchingGroup('premiumHatchingPotions'); if (!matchers.match(item.key)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); @@ -111,6 +107,11 @@ export default async function purchase (user, req = {}, analytics) { if (!matchers.match(item.set)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } + } else if (type === 'bundles') { + const matchers = getScheduleMatchingGroup('bundles'); + if (!matchers.match(item.key)) { + throw new NotAuthorized(i18n.t('notAvailable', { key: item.key })); + } } else if (!item.canBuy(user)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index 93901ada92..36b4ff8d0f 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -4,7 +4,6 @@ import { // eslint-disable-line import/no-cycle model as Group, TAVERN_ID as tavernId, } from '../models/group'; -import common from '../../common'; import { REPEATING_EVENTS } from '../../common/script/content/constants'; import { getCurrentGalaEvent } from '../../common/script/content/constants/schedule'; From 3540a274b3effbf2ecaa13139055394c6cffa034 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Thu, 15 Feb 2024 18:33:40 +0100 Subject: [PATCH 036/244] fix issue with seasonal quest scheduling --- .../script/content/constants/schedule.js | 30 +++++++++++++++++-- website/common/script/libs/shops.js | 1 + 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index edcbd86cb7..b094e32987 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -672,6 +672,13 @@ export const GALA_SCHEDULE = { 'shinySeed', ], }, + { + type: 'seasonalQuests', + items: [ + 'egg', + 'waffle', + ], + }, { type: 'customizations', matcher: customizationMatcher([ @@ -695,6 +702,11 @@ export const GALA_SCHEDULE = { 'seafoam', ], }, + { + type: 'seasonalQuests', + items: [ + ], + }, { type: 'customizations', matcher: customizationMatcher([ @@ -717,6 +729,11 @@ export const GALA_SCHEDULE = { 'spookySparkles', ], }, + { + type: 'seasonalQuests', + items: [ + ], + }, { type: 'customizations', matcher: customizationMatcher([ @@ -799,10 +816,17 @@ export function getScheduleMatchingGroup (type, date) { matchers: [], items: [], match (key) { - if (this.items.length > 0 && !inListMatcher(this.items)(key)) { - return false; + if (this.matchers.length === 0) { + if (this.items.length > 0) { + return inListMatcher(this.items)(key); + } + } else { + if (this.items.length > 0 && !inListMatcher(this.items)(key)) { + return false; + } + return this.matchers.every(m => m(key)); } - return this.matchers.every(m => m(key)); + return false; }, }; } diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 944ebf489d..9326ba5c73 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -495,6 +495,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang categories.push(category); } + console.log(questMatcher); const quests = pickBy(content.quests, (quest, key) => questMatcher.match(key)); if (keys(quests).length > 0) { From fb56f7df202446686ed1276e1580b6882ddbc654 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 28 Feb 2024 17:54:42 +0100 Subject: [PATCH 037/244] Fix various tests --- .../unit/middlewares/ensureTimeTravelMode.js | 38 ++++++ .../debug/GET-debug-timeTravelTime.test.js | 44 +++++++ .../debug/POST-debug_jumpTime.test.js | 75 ++++++++++++ test/common/ops/buy/buyQuestGems.js | 3 + test/common/ops/buy/purchase.js | 15 ++- test/common/ops/unlock.js | 94 +++++++++------ website/client/src/components/appFooter.vue | 21 ++-- website/common/script/content/index.js | 7 +- website/common/script/index.js | 5 + website/common/script/ops/buy/buyArmoire.js | 2 +- website/server/controllers/api-v3/debug.js | 113 ++++++++++-------- website/server/libs/worldState.js | 44 +++---- .../middlewares/ensureTimeTravelMode.js | 12 ++ 13 files changed, 349 insertions(+), 124 deletions(-) create mode 100644 test/api/unit/middlewares/ensureTimeTravelMode.js create mode 100644 test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js create mode 100644 test/api/v3/integration/debug/POST-debug_jumpTime.test.js create mode 100644 website/server/middlewares/ensureTimeTravelMode.js diff --git a/test/api/unit/middlewares/ensureTimeTravelMode.js b/test/api/unit/middlewares/ensureTimeTravelMode.js new file mode 100644 index 0000000000..4c24838d24 --- /dev/null +++ b/test/api/unit/middlewares/ensureTimeTravelMode.js @@ -0,0 +1,38 @@ +/* eslint-disable global-require */ +import nconf from 'nconf'; +import { + generateRes, + generateReq, + generateNext, +} from '../../../helpers/api-unit.helper'; +import ensureDevelpmentMode from '../../../../website/server/middlewares/ensureDevelpmentMode'; +import { NotFound } from '../../../../website/server/libs/errors'; + +describe('timetravelMode middleware', () => { + let res; let req; let + next; + + beforeEach(() => { + res = generateRes(); + req = generateReq(); + next = generateNext(); + }); + + it('returns not found when not in time travel mode', () => { + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(true); + + ensureDevelpmentMode(req, res, next); + + const calledWith = next.getCall(0).args; + expect(calledWith[0] instanceof NotFound).to.equal(true); + }); + + it('passes when in time travel mode', () => { + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(false); + + ensureDevelpmentMode(req, res, next); + + expect(next).to.be.calledOnce; + expect(next.args[0]).to.be.empty; + }); +}); diff --git a/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js new file mode 100644 index 0000000000..49f7464a58 --- /dev/null +++ b/test/api/v3/integration/debug/GET-debug-timeTravelTime.test.js @@ -0,0 +1,44 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('GET /debug/time-travel-time', () => { + let user; + before(async () => { + user = await generateUser({ permissions: { fullAccess: true } }); + }); + + after(() => { + nconf.set('ENABLE_TIME_TRAVEL', false); + }); + + it('returns the shifted time', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const result = await user.get('/debug/time-travel-time'); + expect(result.time).to.exist; + await user.post('/debug/jump-time', { disable: true }); + }); + + it('returns error when the user is not an admin', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const regularUser = await generateUser(); + await expect(regularUser.get('/debug/time-travel-time')) + .eventually.be.rejected.and.to.deep.equal({ + code: 400, + error: 'BadRequest', + message: 'You do not have permission to time travel.', + }); + }); + + it('returns error when not in time travel mode', async () => { + nconf.set('ENABLE_TIME_TRAVEL', false); + + await expect(user.get('/debug/time-travel-time')) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/api/v3/integration/debug/POST-debug_jumpTime.test.js b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js new file mode 100644 index 0000000000..ccfb86cc39 --- /dev/null +++ b/test/api/v3/integration/debug/POST-debug_jumpTime.test.js @@ -0,0 +1,75 @@ +import nconf from 'nconf'; +import { + generateUser, +} from '../../../../helpers/api-integration/v3'; + +describe('POST /debug/jump-time', () => { + let user; + let today; + before(async () => { + user = await generateUser({ permissions: { fullAccess: true } }); + today = new Date(); + }); + + after(async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + await user.post('/debug/jump-time', { disable: true }); + nconf.set('ENABLE_TIME_TRAVEL', false); + }); + + it('Jumps forward', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 1 })).time); + expect(newResultDate.getDate()).to.eql(today.getDate() + 1); + expect(newResultDate.getMonth()).to.eql(today.getMonth()); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear()); + }); + + it('jumps back', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: -1 })).time); + expect(newResultDate.getDate()).to.eql(today.getDate() - 1); + expect(newResultDate.getMonth()).to.eql(today.getMonth()); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear()); + }); + + it('can jump a lot', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const resultDate = new Date((await user.post('/debug/jump-time', { reset: true })).time); + expect(resultDate.getDate()).to.eql(today.getDate()); + expect(resultDate.getMonth()).to.eql(today.getMonth()); + expect(resultDate.getFullYear()).to.eql(today.getFullYear()); + const newResultDate = new Date((await user.post('/debug/jump-time', { offsetDays: 355 })).time); + expect(newResultDate.getFullYear()).to.eql(today.getFullYear() + 1); + }); + + it('returns error when the user is not an admin', async () => { + nconf.set('ENABLE_TIME_TRAVEL', true); + const regularUser = await generateUser(); + await expect(regularUser.post('/debug/jump-time', { offsetDays: 1 })) + .eventually.be.rejected.and.to.deep.equal({ + code: 400, + error: 'BadRequest', + message: 'You do not have permission to time travel.', + }); + }); + + it('returns error when not in time travel mode', async () => { + nconf.set('ENABLE_TIME_TRAVEL', false); + + await expect(user.post('/debug/jump-time', { offsetDays: 1 })) + .eventually.be.rejected.and.to.deep.equal({ + code: 404, + error: 'NotFound', + message: 'Not found.', + }); + }); +}); diff --git a/test/common/ops/buy/buyQuestGems.js b/test/common/ops/buy/buyQuestGems.js index bb5346b420..94d1bc5697 100644 --- a/test/common/ops/buy/buyQuestGems.js +++ b/test/common/ops/buy/buyQuestGems.js @@ -10,6 +10,7 @@ import { BuyQuestWithGemOperation } from '../../../../website/common/script/ops/ describe('shared.ops.buyQuestGems', () => { let user; + let clock; const goldPoints = 40; const analytics = { track () {} }; @@ -26,11 +27,13 @@ describe('shared.ops.buyQuestGems', () => { beforeEach(() => { sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); + clock = sinon.useFakeTimers(new Date('2024-01-16')); }); afterEach(() => { analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); + clock.restore(); }); context('single purchase', () => { diff --git a/test/common/ops/buy/purchase.js b/test/common/ops/buy/purchase.js index 8f70523d1c..ef10b7c182 100644 --- a/test/common/ops/buy/purchase.js +++ b/test/common/ops/buy/purchase.js @@ -15,6 +15,7 @@ import { describe('shared.ops.purchase', () => { const SEASONAL_FOOD = moment().isBefore('2021-11-02T20:00-04:00') ? 'Candy_Base' : 'Meat'; let user; + let clock; const goldPoints = 40; const analytics = { track () {} }; @@ -25,11 +26,13 @@ describe('shared.ops.purchase', () => { beforeEach(() => { sinon.stub(analytics, 'track'); sinon.spy(pinnedGearUtils, 'removeItemByPath'); + clock = sandbox.useFakeTimers(new Date('2024-01-10')); }); afterEach(() => { analytics.track.restore(); pinnedGearUtils.removeItemByPath.restore(); + clock.restore(); }); context('failure conditions', () => { @@ -62,7 +65,7 @@ describe('shared.ops.purchase', () => { } }); - it('returns error when unknown item is requested', async () => { + it.only('returns error when unknown item is requested', async () => { try { await purchase(user, { params: { type: 'gear', key: 'randomKey' } }); } catch (err) { @@ -82,7 +85,7 @@ describe('shared.ops.purchase', () => { it('returns error when user does not have enough gems to buy an item', async () => { try { - await purchase(user, { params: { type: 'gear', key: 'headAccessory_special_wolfEars' } }); + await purchase(user, { params: { type: 'gear', key: 'shield_special_winter2019Healer' } }); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.equal(i18n.t('notEnoughGems')); @@ -150,7 +153,7 @@ describe('shared.ops.purchase', () => { user.pinnedItems.push({ type: 'eggs', key: 'Wolf' }); user.pinnedItems.push({ type: 'hatchingPotions', key: 'Base' }); user.pinnedItems.push({ type: 'food', key: SEASONAL_FOOD }); - user.pinnedItems.push({ type: 'gear', key: 'headAccessory_special_tigerEars' }); + user.pinnedItems.push({ type: 'gear', key: 'shield_special_winter2019Healer' }); user.pinnedItems.push({ type: 'bundles', key: 'featheredFriends' }); }); @@ -187,7 +190,7 @@ describe('shared.ops.purchase', () => { it('purchases gear', async () => { const type = 'gear'; - const key = 'headAccessory_special_tigerEars'; + const key = 'shield_special_winter2019Healer'; await purchase(user, { params: { type, key } }); @@ -197,7 +200,8 @@ describe('shared.ops.purchase', () => { it('purchases quest bundles', async () => { const startingBalance = user.balance; - const clock = sandbox.useFakeTimers(moment('2024-03-20').valueOf()); + clock.restore(); + clock = sandbox.useFakeTimers(moment('2022-03-10').valueOf()); const type = 'bundles'; const key = 'cuddleBuddies'; const price = 1.75; @@ -216,7 +220,6 @@ describe('shared.ops.purchase', () => { expect(user.balance).to.equal(startingBalance - price); expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true); - clock.restore(); }); }); diff --git a/test/common/ops/unlock.js b/test/common/ops/unlock.js index f08608e615..6c589fe167 100644 --- a/test/common/ops/unlock.js +++ b/test/common/ops/unlock.js @@ -2,14 +2,17 @@ import get from 'lodash/get'; import unlock from '../../../website/common/script/ops/unlock'; import i18n from '../../../website/common/script/i18n'; import { generateUser } from '../../helpers/common.helper'; -import { NotAuthorized, BadRequest } from '../../../website/common/script/libs/errors'; +import { + NotAuthorized, + BadRequest, +} from '../../../website/common/script/libs/errors'; describe('shared.ops.unlock', () => { let user; + let clock; const unlockPath = 'shirt.convict,shirt.cross,shirt.fire,shirt.horizon,shirt.ocean,shirt.purple,shirt.rainbow,shirt.redblue,shirt.thunder,shirt.tropical,shirt.zombie'; const unlockGearSetPath = 'items.gear.owned.headAccessory_special_bearEars,items.gear.owned.headAccessory_special_cactusEars,items.gear.owned.headAccessory_special_foxEars,items.gear.owned.headAccessory_special_lionEars,items.gear.owned.headAccessory_special_pandaEars,items.gear.owned.headAccessory_special_pigEars,items.gear.owned.headAccessory_special_tigerEars,items.gear.owned.headAccessory_special_wolfEars'; const backgroundUnlockPath = 'background.giant_florals'; - const backgroundSetUnlockPath = 'background.archery_range,background.giant_florals,background.rainbows_end'; const hairUnlockPath = 'hair.color.rainbow,hair.color.yellow,hair.color.green,hair.color.purple,hair.color.blue,hair.color.TRUred'; const facialHairUnlockPath = 'hair.mustache.1,hair.mustache.2,hair.beard.1,hair.beard.2,hair.beard.3'; const usersStartingGems = 50 / 4; @@ -17,6 +20,11 @@ describe('shared.ops.unlock', () => { beforeEach(() => { user = generateUser(); user.balance = usersStartingGems; + clock = sandbox.useFakeTimers(new Date('2024-04-10')); + }); + + afterEach(() => { + clock.restore(); }); it('returns an error when path is not provided', async () => { @@ -31,7 +39,9 @@ describe('shared.ops.unlock', () => { it('does not unlock lost gear', async () => { user.items.gear.owned.headAccessory_special_bearEars = false; - await unlock(user, { query: { path: 'items.gear.owned.headAccessory_special_bearEars' } }); + await unlock(user, { + query: { path: 'items.gear.owned.headAccessory_special_bearEars' }, + }); expect(user.balance).to.equal(usersStartingGems); }); @@ -95,7 +105,9 @@ describe('shared.ops.unlock', () => { it('returns an error if gear is not from the animal set', async () => { try { - await unlock(user, { query: { path: 'items.gear.owned.back_mystery_202004' } }); + await unlock(user, { + query: { path: 'items.gear.owned.back_mystery_202004' }, + }); } catch (err) { expect(err).to.be.an.instanceof(BadRequest); expect(err.message).to.equal(i18n.t('invalidUnlockSet')); @@ -163,7 +175,9 @@ describe('shared.ops.unlock', () => { await unlock(user, { query: { path: backgroundUnlockPath } }); const afterBalance = user.balance; - const response = await unlock(user, { query: { path: backgroundUnlockPath } }); + const response = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(user.balance).to.equal(afterBalance); // do not bill twice expect(response.message).to.not.exist; @@ -176,7 +190,9 @@ describe('shared.ops.unlock', () => { await unlock(user, { query: { path: backgroundUnlockPath } }); // unlock const afterBalance = user.balance; await unlock(user, { query: { path: backgroundUnlockPath } }); // equip - const response = await unlock(user, { query: { path: backgroundUnlockPath } }); + const response = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(user.balance).to.equal(afterBalance); // do not bill twice expect(response.message).to.not.exist; @@ -192,8 +208,9 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.shirt).length) - .to.equal(initialShirts + individualPaths.length); + expect(Object.keys(user.purchased.shirt).length).to.equal( + initialShirts + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); @@ -208,8 +225,9 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.hair.color).length) - .to.equal(initialHairColors + individualPaths.length); + expect(Object.keys(user.purchased.hair.color).length).to.equal( + initialHairColors + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); @@ -219,21 +237,28 @@ describe('shared.ops.unlock', () => { const initialMustache = Object.keys(user.purchased.hair.mustache).length; const initialBeard = Object.keys(user.purchased.hair.mustache).length; - const [, message] = await unlock(user, { query: { path: facialHairUnlockPath } }); + const [, message] = await unlock(user, { + query: { path: facialHairUnlockPath }, + }); expect(message).to.equal(i18n.t('unlocked')); const individualPaths = facialHairUnlockPath.split(','); individualPaths.forEach(path => { expect(get(user.purchased, path)).to.be.true; }); - expect(Object.keys(user.purchased.hair.mustache).length + Object.keys(user.purchased.hair.beard).length) // eslint-disable-line max-len + expect( + Object.keys(user.purchased.hair.mustache).length + + Object.keys(user.purchased.hair.beard).length, + ) // eslint-disable-line max-len .to.equal(initialMustache + initialBeard + individualPaths.length); expect(user.balance).to.equal(usersStartingGems - 1.25); }); it('unlocks a full set of gear', async () => { const initialGear = Object.keys(user.items.gear.owned).length; - const [, message] = await unlock(user, { query: { path: unlockGearSetPath } }); + const [, message] = await unlock(user, { + query: { path: unlockGearSetPath }, + }); expect(message).to.equal(i18n.t('unlocked')); @@ -241,32 +266,21 @@ describe('shared.ops.unlock', () => { individualPaths.forEach(path => { expect(get(user, path)).to.be.true; }); - expect(Object.keys(user.items.gear.owned).length) - .to.equal(initialGear + individualPaths.length); + expect(Object.keys(user.items.gear.owned).length).to.equal( + initialGear + individualPaths.length, + ); expect(user.balance).to.equal(usersStartingGems - 1.25); }); - it('unlocks a full set of backgrounds', async () => { - const initialBackgrounds = Object.keys(user.purchased.background).length; - const [, message] = await unlock(user, { query: { path: backgroundSetUnlockPath } }); - - expect(message).to.equal(i18n.t('unlocked')); - const individualPaths = backgroundSetUnlockPath.split(','); - individualPaths.forEach(path => { - expect(get(user.purchased, path)).to.be.true; - }); - expect(Object.keys(user.purchased.background).length) - .to.equal(initialBackgrounds + individualPaths.length); - expect(user.balance).to.equal(usersStartingGems - 3.75); - }); - it('unlocks an item (appearance)', async () => { const path = unlockPath.split(',')[0]; const initialShirts = Object.keys(user.purchased.shirt).length; const [, message] = await unlock(user, { query: { path } }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.shirt).length).to.equal(initialShirts + 1); + expect(Object.keys(user.purchased.shirt).length).to.equal( + initialShirts + 1, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); }); @@ -279,7 +293,9 @@ describe('shared.ops.unlock', () => { const [, message] = await unlock(user, { query: { path } }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.hair.color).length).to.equal(initialColorHair + 1); + expect(Object.keys(user.purchased.hair.color).length).to.equal( + initialColorHair + 1, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); }); @@ -295,8 +311,12 @@ describe('shared.ops.unlock', () => { expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.hair.mustache).length).to.equal(initialMustache + 1); - expect(Object.keys(user.purchased.hair.beard).length).to.equal(initialBeard); + expect(Object.keys(user.purchased.hair.mustache).length).to.equal( + initialMustache + 1, + ); + expect(Object.keys(user.purchased.hair.beard).length).to.equal( + initialBeard, + ); expect(get(user.purchased, path)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 0.5); @@ -315,10 +335,14 @@ describe('shared.ops.unlock', () => { it('unlocks an item (background)', async () => { const initialBackgrounds = Object.keys(user.purchased.background).length; - const [, message] = await unlock(user, { query: { path: backgroundUnlockPath } }); + const [, message] = await unlock(user, { + query: { path: backgroundUnlockPath }, + }); expect(message).to.equal(i18n.t('unlocked')); - expect(Object.keys(user.purchased.background).length).to.equal(initialBackgrounds + 1); + expect(Object.keys(user.purchased.background).length).to.equal( + initialBackgrounds + 1, + ); expect(get(user.purchased, backgroundUnlockPath)).to.be.true; expect(user.balance).to.equal(usersStartingGems - 1.75); }); diff --git a/website/client/src/components/appFooter.vue b/website/client/src/components/appFooter.vue index c652857fde..a3e52140ea 100644 --- a/website/client/src/components/appFooter.vue +++ b/website/client/src/components/appFooter.vue @@ -292,28 +292,35 @@
+ :key="lastTimeJump" + > -1 Day + @click="jumpTime(-1)" + >-1 Day -7 Days + @click="jumpTime(-7)" + >-7 Days -30 Days + @click="jumpTime(-30)" + >-30 Days
Time Traveling! It is {{ new Date().toLocaleDateString() }}
+1 Day + @click="jumpTime(1)" + >+1 Day +7 Days + @click="jumpTime(7)" + >+7 Days +30 Days + @click="jumpTime(30)" + >+30 Days
`, diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 5c23f32f6d..2a37ca7692 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -1,8 +1,9 @@ import _ from 'lodash'; -import nconf from 'nconf'; +import sinon from 'sinon'; import moment from 'moment'; import { authWithHeaders } from '../../middlewares/auth'; import ensureDevelpmentMode from '../../middlewares/ensureDevelpmentMode'; +import ensureTimeTravelMode from '../../middlewares/ensureTimeTravelMode'; import { BadRequest } from '../../libs/errors'; import common from '../../../common'; @@ -204,56 +205,68 @@ api.questProgress = { }; let clock; -if (nconf.get('ENABLE_TIME_TRAVEL')) { - (async () => { - const sinon = await import('sinon'); - const time = new Date(); - clock = sinon.useFakeTimers({ - now: time, - shouldAdvanceTime: true, - }); - })(); - api.timeTravelTime = { - method: 'GET', - url: '/debug/time-travel-time', - middlewares: [authWithHeaders()], - async handler (req, res) { - const { user } = res.locals; - - if (!user.permissions.fullAccess) { - throw new BadRequest('You do not have permission to time travel.'); - } - - res.respond(200, { - time: new Date(), - }); - }, - } - - api.timeTravelAdjust = { - method: 'POST', - url: '/debug/jump-time', - middlewares: [authWithHeaders()], - async handler (req, res) { - const { user } = res.locals; - - if (!user.permissions.fullAccess) { - throw new BadRequest('You do not have permission to time travel.'); - } - - const { offsetDays } = req.body; - if (offsetDays > 0) { - clock.jump(offsetDays * 24 * 60 * 60 * 1000) - } else { - clock.setSystemTime(moment().add(offsetDays, 'days').toDate()); - } - - res.respond(200, { - time: new Date(), - }); - }, - } +function fakeClock () { + if (clock) clock.restore(); + const time = new Date(); + clock = sinon.useFakeTimers({ + now: time, + shouldAdvanceTime: true, + }); } +api.timeTravelTime = { + method: 'GET', + url: '/debug/time-travel-time', + middlewares: [ensureTimeTravelMode, authWithHeaders()], + async handler (req, res) { + if (clock === undefined) { + fakeClock(); + } + + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + res.respond(200, { + time: new Date(), + }); + }, +}; + +api.timeTravelAdjust = { + method: 'POST', + url: '/debug/jump-time', + middlewares: [ensureTimeTravelMode, authWithHeaders()], + async handler (req, res) { + const { user } = res.locals; + + if (!user.permissions.fullAccess) { + throw new BadRequest('You do not have permission to time travel.'); + } + + const { offsetDays, reset, disable } = req.body; + if (reset) { + fakeClock(); + } else if (disable) { + clock.restore(); + clock = undefined; + } else if (clock !== undefined) { + try { + clock.setSystemTime(moment().add(offsetDays, 'days').toDate()); + } catch (e) { + throw new BadRequest('Error adjusting time'); + } + } else { + throw new BadRequest('Invalid command'); + } + + res.respond(200, { + time: new Date(), + }); + }, +}; + export default api; diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index 36b4ff8d0f..d675370acc 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -4,12 +4,10 @@ import { // eslint-disable-line import/no-cycle model as Group, TAVERN_ID as tavernId, } from '../models/group'; -import { REPEATING_EVENTS } from '../../common/script/content/constants'; -import { getCurrentGalaEvent } from '../../common/script/content/constants/schedule'; +import common from '../../common'; export async function getWorldBoss () { - const tavern = await Group - .findById(tavernId) + const tavern = await Group.findById(tavernId) .select('quest.progress quest.key quest.active quest.extra') .exec(); if (tavern && tavern.quest && tavern.quest.active) { @@ -20,42 +18,46 @@ export async function getWorldBoss () { export function getCurrentEvent () { const now = moment(); - const currEvtKey = Object.keys(REPEATING_EVENTS).find(evtKey => { - const event = REPEATING_EVENTS[evtKey]; - const startDate = event.start.replace('1970', now.year()); - const endDate = event.end.replace('1970', now.year()); + const currEvtKey = Object.keys(common.content.repeatingEvents).find( + evtKey => { + const event = common.content.repeatingEventsS[evtKey]; + const startDate = event.start.replace('1970', now.year()); + const endDate = event.end.replace('1970', now.year()); - return now.isBetween(startDate, endDate); - }); + return now.isBetween(startDate, endDate); + }, + ); if (!currEvtKey) { - return getCurrentGalaEvent() + return common.schedule.getCurrentGalaEvent(); } return { event: currEvtKey, - ...REPEATING_EVENTS[currEvtKey], + ...common.content.repeatingEvents[currEvtKey], }; } export function getCurrentEventList () { const now = moment(); - const currentEventKeys = filter(Object.keys(REPEATING_EVENTS), eventKey => { - const eventData = REPEATING_EVENTS[eventKey]; - const startDate = eventData.start.replace('1970', now.year()); - const endDate = eventData.end.replace('1970', now.year()); + const currentEventKeys = filter( + Object.keys(common.content.repeatingEvents), + eventKey => { + const eventData = common.content.repeatingEvents[eventKey]; + const startDate = eventData.start.replace('1970', now.year()); + const endDate = eventData.end.replace('1970', now.year()); - return now.isBetween(startDate, endDate); - }); + return now.isBetween(startDate, endDate); + }, + ); const currentEventList = []; currentEventKeys.forEach(key => { currentEventList.push({ event: key, - ...REPEATING_EVENTS[key], + ...common.content.repeatingEvents[key], }); }); - - currentEventList.push(getCurrentGalaEvent()); + currentEventList.push(common.schedule.getCurrentGalaEvent()); return currentEventList; } diff --git a/website/server/middlewares/ensureTimeTravelMode.js b/website/server/middlewares/ensureTimeTravelMode.js new file mode 100644 index 0000000000..24427c5112 --- /dev/null +++ b/website/server/middlewares/ensureTimeTravelMode.js @@ -0,0 +1,12 @@ +import nconf from 'nconf'; +import { + NotFound, +} from '../libs/errors'; + +export default function ensureTimeTravelMode (req, res, next) { + if (nconf.get('ENABLE_TIME_TRAVEL')) { + next(); + } else { + next(new NotFound()); + } +} From a50c0eb1e7ae73f88cd1ae17dbe2e4211bb5f6bf Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 28 Feb 2024 18:22:55 +0100 Subject: [PATCH 038/244] fix test --- test/api/unit/middlewares/ensureTimeTravelMode.js | 10 +++++----- website/server/libs/worldState.js | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/test/api/unit/middlewares/ensureTimeTravelMode.js b/test/api/unit/middlewares/ensureTimeTravelMode.js index 4c24838d24..37875d8670 100644 --- a/test/api/unit/middlewares/ensureTimeTravelMode.js +++ b/test/api/unit/middlewares/ensureTimeTravelMode.js @@ -5,8 +5,8 @@ import { generateReq, generateNext, } from '../../../helpers/api-unit.helper'; -import ensureDevelpmentMode from '../../../../website/server/middlewares/ensureDevelpmentMode'; import { NotFound } from '../../../../website/server/libs/errors'; +import ensureTimeTravelMode from '../../../../website/server/middlewares/ensureTimeTravelMode'; describe('timetravelMode middleware', () => { let res; let req; let @@ -19,18 +19,18 @@ describe('timetravelMode middleware', () => { }); it('returns not found when not in time travel mode', () => { - sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(true); + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(false); - ensureDevelpmentMode(req, res, next); + ensureTimeTravelMode(req, res, next); const calledWith = next.getCall(0).args; expect(calledWith[0] instanceof NotFound).to.equal(true); }); it('passes when in time travel mode', () => { - sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(false); + sandbox.stub(nconf, 'get').withArgs('ENABLE_TIME_TRAVEL').returns(true); - ensureDevelpmentMode(req, res, next); + ensureTimeTravelMode(req, res, next); expect(next).to.be.calledOnce; expect(next.args[0]).to.be.empty; diff --git a/website/server/libs/worldState.js b/website/server/libs/worldState.js index d675370acc..9bb5d1acbd 100644 --- a/website/server/libs/worldState.js +++ b/website/server/libs/worldState.js @@ -20,7 +20,7 @@ export function getCurrentEvent () { const now = moment(); const currEvtKey = Object.keys(common.content.repeatingEvents).find( evtKey => { - const event = common.content.repeatingEventsS[evtKey]; + const event = common.content.repeatingEvents[evtKey]; const startDate = event.start.replace('1970', now.year()); const endDate = event.end.replace('1970', now.year()); From c18e06f0717b643efe2fdb37c9dff65ad1767bcc Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 6 Mar 2024 12:02:38 +0100 Subject: [PATCH 039/244] changes --- test/common/ops/buy/purchase.js | 2 +- .../client/src/components/shops/buyModal.vue | 4 +- .../components/shops/quests/buyQuestModal.vue | 2 +- .../src/components/shops/quests/questInfo.vue | 8 ++-- .../client/src/components/shops/shopItem.vue | 10 ++--- .../script/content/constants/schedule.js | 41 +++++++++++-------- website/common/script/libs/getItemInfo.js | 7 +++- website/common/script/libs/shops.js | 11 +++-- website/common/script/ops/buy/purchase.js | 2 +- 9 files changed, 49 insertions(+), 38 deletions(-) diff --git a/test/common/ops/buy/purchase.js b/test/common/ops/buy/purchase.js index ef10b7c182..b2f13553f5 100644 --- a/test/common/ops/buy/purchase.js +++ b/test/common/ops/buy/purchase.js @@ -65,7 +65,7 @@ describe('shared.ops.purchase', () => { } }); - it.only('returns error when unknown item is requested', async () => { + it('returns error when unknown item is requested', async () => { try { await purchase(user, { params: { type: 'gear', key: 'randomKey' } }); } catch (err) { diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 838989164e..d4e0851039 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -181,7 +181,7 @@
@@ -741,7 +741,7 @@ export default { return (!this.user.purchased.plan.customerId && !this.user.purchased.plan.consecutive.trinkets && this.getPriceClass() === 'hourglasses'); }, endDate () { - return moment(this.item.event.end); + return moment(this.item.end); }, totalOwned () { return this.user.items[this.item.purchaseType][this.item.key] || 0; diff --git a/website/client/src/components/shops/quests/buyQuestModal.vue b/website/client/src/components/shops/quests/buyQuestModal.vue index 7943e5d291..395303ebc5 100644 --- a/website/client/src/components/shops/quests/buyQuestModal.vue +++ b/website/client/src/components/shops/quests/buyQuestModal.vue @@ -470,7 +470,7 @@ export default { return this.icons.gems; }, endDate () { - return moment(this.item.event.end); + return moment(this.item.end); }, }, watch: { diff --git a/website/client/src/components/shops/quests/questInfo.vue b/website/client/src/components/shops/quests/questInfo.vue index ac69169b1e..d606df48b3 100644 --- a/website/client/src/components/shops/quests/questInfo.vue +++ b/website/client/src/components/shops/quests/questInfo.vue @@ -38,7 +38,7 @@
{{ limitedString }} @@ -210,14 +210,14 @@ export default { return collect.text; }, countdownString () { - if (!this.quest.event || this.purchased) return; - const diffDuration = moment.duration(moment(this.quest.event.end).diff(moment())); + if (!this.quest.end || this.purchased) return; + const diffDuration = moment.duration(moment(this.quest.end).diff(moment())); if (diffDuration.asSeconds() <= 0) { this.limitedString = this.$t('noLongerAvailable'); } else if (diffDuration.days() > 0 || diffDuration.months() > 0) { this.limitedString = this.$t('limitedAvailabilityDays', { - days: moment(this.quest.event.end).diff(moment(), 'days'), + days: moment(this.quest.end).diff(moment(), 'days'), hours: diffDuration.hours(), minutes: diffDuration.minutes(), }); diff --git a/website/client/src/components/shops/shopItem.vue b/website/client/src/components/shops/shopItem.vue index f6aeaeb9fb..4a831b57d4 100644 --- a/website/client/src/components/shops/shopItem.vue +++ b/website/client/src/components/shops/shopItem.vue @@ -18,7 +18,7 @@ :emptyItem="emptyItem" >
{{ limitedString }} @@ -370,14 +370,14 @@ export default { }; }, countdownString () { - if (!this.item.event) return; - const diffDuration = moment.duration(moment(this.item.event.end).diff(moment())); + if (!this.item.end) return; + const diffDuration = moment.duration(moment(this.item.end).diff(moment())); if (diffDuration.asSeconds() <= 0) { this.limitedString = this.$t('noLongerAvailable'); } else if (diffDuration.days() > 0 || diffDuration.months() > 0) { this.limitedString = this.$t('limitedAvailabilityDays', { - days: moment(this.item.event.end).diff(moment(), 'days'), + days: moment(this.item.end).diff(moment(), 'days'), hours: diffDuration.hours(), minutes: diffDuration.minutes(), }); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index b094e32987..c0259fb80b 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -800,6 +800,29 @@ export function assembleScheduledMatchers (date) { let cachedScheduleMatchers = null; +function makeMatcherClass () { + return { + matchers: [], + items: [], + match (key) { + if (this.matchers.length === 0) { + if (this.items.length > 0) { + return inListMatcher(this.items)(key); + } + } else { + if (this.items.length > 0 && !inListMatcher(this.items)(key)) { + return false; + } + return this.matchers.every(m => m(key)); + } + return false; + }, + getEndDate () { + return new Date(); + }, + }; +} + export function getScheduleMatchingGroup (type, date) { const checkedDate = date || new Date(); if (cacheDate !== null && (getDay(checkedDate) !== getDay(cacheDate) @@ -812,23 +835,7 @@ export function getScheduleMatchingGroup (type, date) { const scheduleMatchers = {}; assembleScheduledMatchers(checkedDate).forEach(matcher => { if (!scheduleMatchers[matcher.type]) { - scheduleMatchers[matcher.type] = { - matchers: [], - items: [], - match (key) { - if (this.matchers.length === 0) { - if (this.items.length > 0) { - return inListMatcher(this.items)(key); - } - } else { - if (this.items.length > 0 && !inListMatcher(this.items)(key)) { - return false; - } - return this.matchers.every(m => m(key)); - } - return false; - }, - }; + scheduleMatchers[matcher.type] = makeMatcherClass(); } if (matcher.matcher instanceof Function) { cachedScheduleMatchers[matcher.type].matchers.push(matcher.matcher); diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index ff9477b30c..120fc277b5 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 } @@ -478,5 +478,10 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang throw new BadRequest(i18n.t('wrongItemType', { type }, language)); } + if (matcher) { + itemInfo.end = matcher.getEndDate(); + console.log(itemInfo); + } + return itemInfo; } diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 9326ba5c73..1ef7968892 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -75,7 +75,7 @@ shops.getMarketCategories = function getMarket (user, language) { premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions) .filter(hp => hp.limited && matchers.match(hp.key)) - .map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language)), 'key'); + .map(premiumHatchingPotion => getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language, matchers)), 'key'); if (premiumHatchingPotionsCategory.items.length > 0) { categories.push(premiumHatchingPotionsCategory); } @@ -277,7 +277,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) bundleCategory.items = sortBy(values(content.bundles) .filter(bundle => bundle.type === 'quests' && bundleMatchers.match(bundle.key)) - .map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language))); + .map(bundle => getItemInfo(user, 'bundles', bundle, officialPinnedItems, language, bundleMatchers))); if (bundleCategory.items.length > 0) { categories.push(bundleCategory); @@ -489,13 +489,12 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang category.items = map( spells, - spell => getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language), + spell => getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language, spellMatcher), ); categories.push(category); } - console.log(questMatcher); const quests = pickBy(content.quests, (quest, key) => questMatcher.match(key)); if (keys(quests).length > 0) { @@ -504,7 +503,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang text: i18n.t('quests', language), }; - category.items = map(quests, quest => getItemInfo(user, 'seasonalQuest', quest, officialPinnedItems, language)); + category.items = map(quests, quest => getItemInfo(user, 'seasonalQuest', quest, officialPinnedItems, language, questMatcher)); categories.push(category); } @@ -541,7 +540,7 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) { text: i18n.t(key, language), }; - set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language)); + set.items = map(group, background => getItemInfo(null, 'background', background, officialPinnedItems, language, matchers)); sets.push(set); } diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index 8e83574850..1938939fe9 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -102,7 +102,7 @@ export default async function purchase (user, req = {}, analytics) { if (!matchers.match(item.key)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); } - } else if (item.event && item.event.gear) { + } else if (item.end && item.event.gear) { const matchers = getScheduleMatchingGroup('seasonalGear'); if (!matchers.match(item.set)) { throw new NotAuthorized(i18n.t('messageNotAvailable', req.language)); From 8b9b79db8ec57d2fb195632969006f21f658d3eb Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 1 Apr 2024 19:04:57 -0500 Subject: [PATCH 040/244] fix(potions): remove EVENT --- website/common/script/content/hatching-potions.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/website/common/script/content/hatching-potions.js b/website/common/script/content/hatching-potions.js index e4184126ca..cb91f7d700 100644 --- a/website/common/script/content/hatching-potions.js +++ b/website/common/script/content/hatching-potions.js @@ -400,13 +400,9 @@ const premium = { value: 2, text: t('hatchingPotionRoseGold'), limited: true, - event: EVENTS.potions202402, _addlNotes: t('eventAvailability', { date: t('dateEndFebruary'), }), - canBuy () { - return moment().isBetween(EVENTS.potions202402.start, EVENTS.potions202402.end); - }, }, }; From a7eda1355b89bd12d0bb01375792a2e6b95ca885 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 1 Apr 2024 19:08:07 -0500 Subject: [PATCH 041/244] fix(packages): add sinon to client --- website/client/package-lock.json | 111 +++++++++++++++++++++++++++++++ website/client/package.json | 1 + 2 files changed, 112 insertions(+) diff --git a/website/client/package-lock.json b/website/client/package-lock.json index f1584f358c..ce44299167 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -38,6 +38,7 @@ "nconf": "^0.12.1", "sass": "^1.63.4", "sass-loader": "^8.0.2", + "sinon": "^17.0.1", "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", "uuid": "^9.0.1", @@ -2120,6 +2121,45 @@ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz", "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==" }, + "node_modules/@sinonjs/commons": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", + "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/fake-timers": { + "version": "11.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-11.2.2.tgz", + "integrity": "sha512-G2piCSxQ7oWOxwGSAyFHfPIsyeJGXYtc6mFbnFA+kRXkiEnTl8c/8jul2S329iFBnDI9HGoeWWAZvuvOkZccgw==", + "dependencies": { + "@sinonjs/commons": "^3.0.0" + } + }, + "node_modules/@sinonjs/samsam": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.0.tgz", + "integrity": "sha512-Bp8KUVlLp8ibJZrnvq2foVhP0IVX2CIprMJPK0vqGqgrDa0OHVKeZyBykqskkrdxV6yKBPmGasO8LVjAKR3Gew==", + "dependencies": { + "@sinonjs/commons": "^2.0.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" + } + }, + "node_modules/@sinonjs/samsam/node_modules/@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dependencies": { + "type-detect": "4.0.8" + } + }, + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==" + }, "node_modules/@soda/friendly-errors-webpack-plugin": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@soda/friendly-errors-webpack-plugin/-/friendly-errors-webpack-plugin-1.8.1.tgz", @@ -8329,6 +8369,11 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/just-extend": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-6.2.0.tgz", + "integrity": "sha512-cYofQu2Xpom82S6qD778jBDpwvvy39s1l/hrYij2u9AMdQcGRpaBu6kY4mVhuno5kJVi1DAz4aiphA2WI1/OAw==" + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -8464,6 +8509,11 @@ "resolved": "https://registry.npmjs.org/lodash.defaultsdeep/-/lodash.defaultsdeep-4.6.1.tgz", "integrity": "sha512-3j8wdDzYuWO3lM3Reg03MuQR957t287Rpcxp1njpEa8oDrikb+FwGdW3n+FELh/A6qib6yPit0j/pv9G/yeAqA==" }, + "node_modules/lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + }, "node_modules/lodash.kebabcase": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz", @@ -9524,6 +9574,23 @@ "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, + "node_modules/nise": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.9.tgz", + "integrity": "sha512-qOnoujW4SV6e40dYxJOb3uvuoPHtmLzIk4TFo+j0jPJoC+5Z9xja5qH5JZobEPsa8+YYphMrOSwnrshEhG2qww==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/text-encoding": "^0.7.2", + "just-extend": "^6.2.0", + "path-to-regexp": "^6.2.1" + } + }, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" + }, "node_modules/no-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", @@ -11748,6 +11815,50 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/sinon": { + "version": "17.0.1", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-17.0.1.tgz", + "integrity": "sha512-wmwE19Lie0MLT+ZYNpDymasPHUKTaZHUH/pKEubRXIzySv9Atnlw+BUMGCzWgV7b7wO+Hw6f1TEOr0IUnmU8/g==", + "dependencies": { + "@sinonjs/commons": "^3.0.0", + "@sinonjs/fake-timers": "^11.2.2", + "@sinonjs/samsam": "^8.0.0", + "diff": "^5.1.0", + "nise": "^5.1.5", + "supports-color": "^7.2.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" + } + }, + "node_modules/sinon/node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/sinon/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sinon/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/sirv": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz", diff --git a/website/client/package.json b/website/client/package.json index 7cc23e40b3..7e4bd7e37c 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -40,6 +40,7 @@ "nconf": "^0.12.1", "sass": "^1.63.4", "sass-loader": "^8.0.2", + "sinon": "^17.0.1", "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", "uuid": "^9.0.1", From ee73d5b628e8e14a212ae5cc51dcfb209bd622cf Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 1 Apr 2024 19:10:59 -0500 Subject: [PATCH 042/244] fix(packages): add util --- website/client/package-lock.json | 42 ++++++++++++++++++++++++++++++++ website/client/package.json | 1 + 2 files changed, 43 insertions(+) diff --git a/website/client/package-lock.json b/website/client/package-lock.json index ce44299167..e372e7997f 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -41,6 +41,7 @@ "sinon": "^17.0.1", "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", + "util": "^0.12.5", "uuid": "^9.0.1", "validator": "^13.9.0", "vue": "^2.7.10", @@ -7825,6 +7826,21 @@ "node": ">= 10" } }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-array-buffer": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", @@ -7965,6 +7981,20 @@ "node": ">=8" } }, + "node_modules/is-generator-function": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", + "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -12823,6 +12853,18 @@ "requires-port": "^1.0.0" } }, + "node_modules/util": { + "version": "0.12.5", + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", + "dependencies": { + "inherits": "^2.0.3", + "is-arguments": "^1.0.4", + "is-generator-function": "^1.0.7", + "is-typed-array": "^1.1.3", + "which-typed-array": "^1.1.2" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/website/client/package.json b/website/client/package.json index 7e4bd7e37c..5b6f1b83f4 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -43,6 +43,7 @@ "sinon": "^17.0.1", "smartbanner.js": "^1.19.3", "stopword": "^2.0.8", + "util": "^0.12.5", "uuid": "^9.0.1", "validator": "^13.9.0", "vue": "^2.7.10", From 92eaece5eb3ddac978cf66a7536e2753a0721f4e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Mon, 1 Apr 2024 19:13:29 -0500 Subject: [PATCH 043/244] fix(packages): add assert --- website/client/package-lock.json | 123 +++++++++++++++++++++++++------ website/client/package.json | 1 + 2 files changed, 100 insertions(+), 24 deletions(-) diff --git a/website/client/package-lock.json b/website/client/package-lock.json index e372e7997f..50bfd38a51 100644 --- a/website/client/package-lock.json +++ b/website/client/package-lock.json @@ -16,6 +16,7 @@ "@vue/cli-service": "^5.0.8", "@vue/test-utils": "1.0.0-beta.29", "amplitude-js": "^8.21.3", + "assert": "^2.1.0", "axios": "^0.27.2", "axios-progress-bar": "^1.2.0", "babel-eslint": "^10.1.0", @@ -3642,6 +3643,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" + } + }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", @@ -4103,13 +4116,18 @@ } }, "node_modules/call-bind": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.5.tgz", - "integrity": "sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.1", - "set-function-length": "^1.1.1" + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -5189,16 +5207,19 @@ } }, "node_modules/define-data-property": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.1.tgz", - "integrity": "sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dependencies": { - "get-intrinsic": "^1.2.1", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.0" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/define-lazy-prop": { @@ -5569,6 +5590,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es-module-lexer": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", @@ -7086,15 +7126,19 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", - "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", "dependencies": { + "es-errors": "^1.3.0", "function-bind": "^1.1.2", "has-proto": "^1.0.1", "has-symbols": "^1.0.3", "hasown": "^2.0.0" }, + "engines": { + "node": ">= 0.4" + }, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -7288,11 +7332,11 @@ } }, "node_modules/has-property-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.1.tgz", - "integrity": "sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dependencies": { - "get-intrinsic": "^1.2.2" + "es-define-property": "^1.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8014,6 +8058,21 @@ "node": ">=8" } }, + "node_modules/is-nan": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", + "dependencies": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-negative-zero": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", @@ -9784,6 +9843,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/object-keys": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", @@ -11757,15 +11831,16 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" }, "node_modules/set-function-length": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", - "integrity": "sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dependencies": { - "define-data-property": "^1.1.1", + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.2", + "get-intrinsic": "^1.2.4", "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.1" + "has-property-descriptors": "^1.0.2" }, "engines": { "node": ">= 0.4" diff --git a/website/client/package.json b/website/client/package.json index 5b6f1b83f4..e24b368220 100644 --- a/website/client/package.json +++ b/website/client/package.json @@ -18,6 +18,7 @@ "@vue/cli-service": "^5.0.8", "@vue/test-utils": "1.0.0-beta.29", "amplitude-js": "^8.21.3", + "assert": "^2.1.0", "axios": "^0.27.2", "axios-progress-bar": "^1.2.0", "babel-eslint": "^10.1.0", From 64a500987cd48a66aa5e62b1f3c2cebf0b79533f Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 2 Apr 2024 09:32:44 -0500 Subject: [PATCH 044/244] fix(packages): move sinon to main --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b135130061..8457571696 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", + "sinon": "^15.2.0", "stripe": "^12.18.0", "superagent": "^8.1.2", "universal-analytics": "^0.5.3", @@ -120,7 +121,6 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^15.2.0", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" } From 7b46f3bc2324f5545c38c4af4bf3ea7dbb4ae2c8 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 2 Apr 2024 17:07:28 +0200 Subject: [PATCH 045/244] Fix method name --- website/server/controllers/api-v3/shops.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/website/server/controllers/api-v3/shops.js b/website/server/controllers/api-v3/shops.js index 349abd83df..624101f502 100644 --- a/website/server/controllers/api-v3/shops.js +++ b/website/server/controllers/api-v3/shops.js @@ -146,21 +146,21 @@ api.getBackgroundShopItems = { /** * @apiIgnore - * @api {get} /api/v3/shops/customizations get the available items for the backgrounds shop + * @api {get} /api/v3/shops/customizations get the available items for the customizations shop * @apiName GetCustomizationShopItems * @apiGroup Shops * * @apiSuccess {Object} data List of available backgrounds * @apiSuccess {string} message Success message */ -api.getBackgroundShopItems = { +api.getCustomizationsShop = { method: 'GET', url: '/shops/customizations', middlewares: [authWithHeaders()], async handler (req, res) { const { user } = res.locals; - const resObject = shops.getCustomizationShop(user, req.language); + const resObject = shops.getCustomizationsShop(user, req.language); res.respond(200, resObject); }, From 9befbec2b0a4645ab7e36cedf1b3fb762392ed81 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 2 Apr 2024 17:11:18 +0200 Subject: [PATCH 046/244] Update index.vue --- website/client/src/components/shops/index.vue | 6 ------ 1 file changed, 6 deletions(-) diff --git a/website/client/src/components/shops/index.vue b/website/client/src/components/shops/index.vue index 19f4a00351..b6d4e3386a 100644 --- a/website/client/src/components/shops/index.vue +++ b/website/client/src/components/shops/index.vue @@ -32,12 +32,6 @@ > {{ $t('titleTimeTravelers') }} - - {{ $t('titleCustomizations') }} -
From f8c452ae3f574b9e5da29d46421edc277b01ee71 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Tue, 2 Apr 2024 17:12:38 +0200 Subject: [PATCH 047/244] Remove duplicate menu entry --- website/client/src/components/header/menu.vue | 6 ------ 1 file changed, 6 deletions(-) diff --git a/website/client/src/components/header/menu.vue b/website/client/src/components/header/menu.vue index fc4cbe8d53..fb9db1f1bf 100644 --- a/website/client/src/components/header/menu.vue +++ b/website/client/src/components/header/menu.vue @@ -152,12 +152,6 @@ > {{ $t('titleTimeTravelers') }} - - {{ $t('titleCustomizations') }} -
Date: Tue, 2 Apr 2024 17:58:06 -0500 Subject: [PATCH 048/244] WIP(shop): add item names and unlocking --- .../client/src/components/shops/buyModal.vue | 25 ++-- .../components/shops/customizations/index.vue | 9 +- .../src/components/shops/market/index.vue | 5 + website/client/src/components/ui/itemRows.vue | 14 +- .../src/components/ui/layoutSection.vue | 19 +-- website/common/locales/en/customizations.json | 127 ++++++++++++++++++ .../script/content/appearance/hair/base.js | 41 +++--- .../script/content/appearance/hair/beard.js | 9 +- .../content/appearance/hair/mustache.js | 7 +- .../script/content/appearance/prefill.js | 4 + website/common/script/libs/getItemInfo.js | 12 ++ 11 files changed, 218 insertions(+), 54 deletions(-) create mode 100644 website/common/locales/en/customizations.json diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 1eb7fbb2bc..3396cf2621 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -541,10 +541,6 @@ margin: auto -1rem -1rem; } - // .pt-015 { - // padding-top: 0.15rem; - // } - .gems-left { height: 32px; background-color: $green-100; @@ -596,8 +592,10 @@ import moment from 'moment'; import planGemLimits from '@/../../common/script/libs/planGemLimits'; import { drops as dropEggs } from '@/../../common/script/content/eggs'; import { drops as dropPotions } from '@/../../common/script/content/hatching-potions'; -import spellsMixin from '@/mixins/spells'; +import { avatarEditorUtilities } from '@/mixins/avatarEditUtilities'; import numberInvalid from '@/mixins/numberInvalid'; +import spellsMixin from '@/mixins/spells'; +import sync from '@/mixins/sync'; import svgClose from '@/assets/svg/close.svg'; import svgGold from '@/assets/svg/gold.svg'; @@ -644,7 +642,15 @@ export default { CountdownBanner, numberIncrement, }, - mixins: [buyMixin, currencyMixin, notifications, numberInvalid, spellsMixin], + mixins: [ + avatarEditorUtilities, + buyMixin, + currencyMixin, + notifications, + numberInvalid, + spellsMixin, + sync, + ], props: { // eslint-disable-next-line vue/require-default-prop item: { @@ -754,7 +760,7 @@ export default { this.selectedAmountToBuy = 1; }, - buyItem () { + async buyItem () { // @TODO: I think we should buying to the items. // Turn the items into classes, and use polymorphism if (this.item.buy) { @@ -827,7 +833,10 @@ export default { return; } - if (this.genericPurchase) { + if (this.item.purchaseType === 'customization') { + await this.unlock(this.item.path); + this.sync(); + } else if (this.genericPurchase) { this.makeGenericPurchase(this.item, 'buyModal', this.selectedAmountToBuy); this.purchased(this.item.text); } diff --git a/website/client/src/components/shops/customizations/index.vue b/website/client/src/components/shops/customizations/index.vue index a7d0233b3b..1c7bfc57aa 100644 --- a/website/client/src/components/shops/customizations/index.vue +++ b/website/client/src/components/shops/customizations/index.vue @@ -51,6 +51,7 @@ div:nth-of-type(8n) { - margin-right: 0px; - } - } - .npc { background-repeat: no-repeat; } diff --git a/website/client/src/components/shops/market/index.vue b/website/client/src/components/shops/market/index.vue index 7235ec8d4e..25af92bc2a 100644 --- a/website/client/src/components/shops/market/index.vue +++ b/website/client/src/components/shops/market/index.vue @@ -35,6 +35,7 @@ :hide-pinned="hidePinned" :hide-locked="hideLocked" :search-by="searchTextThrottled" + class="mb-4" />
@@ -121,6 +122,10 @@ height: 112px; } + .items { + max-width: 944px; + } + .market { .avatar { cursor: default; diff --git a/website/client/src/components/ui/itemRows.vue b/website/client/src/components/ui/itemRows.vue index 0dae56b50e..52d850ab14 100644 --- a/website/client/src/components/ui/itemRows.vue +++ b/website/client/src/components/ui/itemRows.vue @@ -18,7 +18,7 @@

@@ -29,7 +29,11 @@ @import '~@/assets/scss/colors.scss'; .item-rows { - margin-right: -1.5rem; + max-width: 944px; + } + + .btn-show-more { + max-width: 920px; } @@ -64,6 +68,10 @@ export default { maxItemsPerRow: { type: Number, }, + foldButton: { + type: Boolean, + default: true, + }, }, data () { return { @@ -82,7 +90,7 @@ export default { }, }, created () { - this.showAll = this.$_openedItemRows_isToggled(this.type); + this.showAll = this.$_openedItemRows_isToggled(this.type) || !this.foldButton; }, methods: { toggleItemsToShow () { diff --git a/website/client/src/components/ui/layoutSection.vue b/website/client/src/components/ui/layoutSection.vue index cabc28e630..956f06d384 100644 --- a/website/client/src/components/ui/layoutSection.vue +++ b/website/client/src/components/ui/layoutSection.vue @@ -1,5 +1,5 @@ diff --git a/website/client/src/components/static/faq.vue b/website/client/src/components/static/faq.vue index 88e9d04a1a..ea4228c834 100644 --- a/website/client/src/components/static/faq.vue +++ b/website/client/src/components/static/faq.vue @@ -67,441 +67,22 @@
- - - + - diff --git a/website/client/src/components/shops/buyModal.vue b/website/client/src/components/shops/buyModal.vue index 4659ee184b..3d260dae8d 100644 --- a/website/client/src/components/shops/buyModal.vue +++ b/website/client/src/components/shops/buyModal.vue @@ -822,8 +822,10 @@ export default { } if (this.item.purchaseType === 'customization') { - await this.unlock(this.item.path); + const buySuccess = await this.unlock(this.item.path); + if (!buySuccess) return; this.sync(); + this.purchased(this.item.text); } else { const shouldConfirmPurchase = this.item.currency === 'gems' || this.item.currency === 'hourglasses'; if ( diff --git a/website/client/src/mixins/avatarEditUtilities.js b/website/client/src/mixins/avatarEditUtilities.js index a61eded2cc..374fa7c657 100644 --- a/website/client/src/mixins/avatarEditUtilities.js +++ b/website/client/src/mixins/avatarEditUtilities.js @@ -140,13 +140,9 @@ export const avatarEditorUtilities = { // eslint-disable-line import/prefer-defa if (loginIncentives.indexOf(path) === -1) { if (fullSet) { - if (window.confirm(this.$t('purchaseFor', { cost: cost * 4 })) !== true) return; // eslint-disable-line no-alert - // @TODO: implement gem modal - // if (this.user.balance < cost) return $rootScope.openModal('buyGems'); + if (window.confirm(this.$t('purchaseFor', { cost: cost * 4 })) !== true) return false; // eslint-disable-line no-alert } else if (!get(this.user, `purchased.${path}`)) { - if (window.confirm(this.$t('purchaseFor', { cost: cost * 4 })) !== true) return; // eslint-disable-line no-alert - // @TODO: implement gem modal - // if (this.user.balance < cost) return $rootScope.openModal('buyGems'); + if (window.confirm(this.$t('purchaseFor', { cost: cost * 4 })) !== true) return false; // eslint-disable-line no-alert } } @@ -158,8 +154,10 @@ export const avatarEditorUtilities = { // eslint-disable-line import/prefer-defa }, }); this.backgroundUpdate = new Date(); + return true; } catch (e) { window.alert(e.message); // eslint-disable-line no-alert + return false; } }, async buy (item) { diff --git a/website/common/locales/en/customizations.json b/website/common/locales/en/customizations.json index 79069158b4..d694d84a2e 100644 --- a/website/common/locales/en/customizations.json +++ b/website/common/locales/en/customizations.json @@ -18,7 +18,7 @@ "candycane": "Candy Cane", "candycorn": "Candy Corn", "clownfish": "Clownfish", - "convict": "Convict", + "convict": "Referee", "cross": "Cross", "curlyLong": "Curly Long", "curlyShort": "Curly Short", From 44a7006295f8df687d3c7c2d0f4b6e4e6ff8a61e Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 7 May 2024 17:19:14 -0500 Subject: [PATCH 100/244] fix(backgrounds): correct wrapping --- .../client/src/components/creatorIntro.vue | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/website/client/src/components/creatorIntro.vue b/website/client/src/components/creatorIntro.vue index afec0a7d11..365bce4f36 100644 --- a/website/client/src/components/creatorIntro.vue +++ b/website/client/src/components/creatorIntro.vue @@ -192,12 +192,12 @@ v-if="user.purchased.background.birthday_bash" >
{{ allBackgrounds.eventBackgrounds.text }}
{{ $t('timeTravelBackgrounds') }}
{{ $t('monthlyBackgrounds') }}
-
+
Date: Tue, 7 May 2024 19:36:39 -0500 Subject: [PATCH 101/244] WIP(schedule): add June 2024 content --- habitica-images | 2 +- .../assets/css/sprites/spritesmith-main.css | 2129 +++++++++++++++++ website/common/locales/en/content.json | 4 + website/common/locales/en/customizations.json | 6 + website/common/locales/en/questsContent.json | 9 +- .../script/content/appearance/hair/color.js | 7 + .../common/script/content/appearance/sets.js | 1 + .../script/content/constants/schedule.js | 2 + website/common/script/content/eggs.js | 6 + website/common/script/content/quests/pets.js | 32 + website/common/script/libs/shops.js | 3 + 11 files changed, 2199 insertions(+), 2 deletions(-) diff --git a/habitica-images b/habitica-images index e2ce955c24..90bb5934b6 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit e2ce955c24f9ab2d26174014ad6721bed65380bc +Subproject commit 90bb5934b6d04f7fabcf04fd2b947177a68a1aa8 diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css index 05ddb55979..0fbe79f44f 100644 --- a/website/client/src/assets/css/sprites/spritesmith-main.css +++ b/website/client/src/assets/css/sprites/spritesmith-main.css @@ -4464,6 +4464,17 @@ width: 60px; height: 60px; } +.hair_beard_1_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_lava.png'); + width: 60px; + height: 60px; +} .hair_beard_1_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_midnight.png'); width: 90px; @@ -4475,6 +4486,17 @@ width: 60px; height: 60px; } +.hair_beard_1_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_orchid.png'); + width: 60px; + height: 60px; +} .hair_beard_1_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_pblue.png'); width: 90px; @@ -4596,6 +4618,28 @@ width: 60px; height: 60px; } +.hair_beard_1_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_beard_1_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_seasprite.png'); + width: 60px; + height: 60px; +} .hair_beard_1_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_snowy.png'); width: 90px; @@ -4607,6 +4651,28 @@ width: 60px; height: 60px; } +.hair_beard_1_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_beard_1_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_1_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_sunset.png'); + width: 60px; + height: 60px; +} .hair_beard_1_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_1_white.png'); width: 90px; @@ -4827,6 +4893,17 @@ width: 60px; height: 60px; } +.hair_beard_2_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_lava.png'); + width: 60px; + height: 60px; +} .hair_beard_2_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_midnight.png'); width: 90px; @@ -4838,6 +4915,17 @@ width: 60px; height: 60px; } +.hair_beard_2_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_orchid.png'); + width: 60px; + height: 60px; +} .hair_beard_2_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_pblue.png'); width: 90px; @@ -4959,6 +5047,28 @@ width: 60px; height: 60px; } +.hair_beard_2_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_beard_2_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_seasprite.png'); + width: 60px; + height: 60px; +} .hair_beard_2_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_snowy.png'); width: 90px; @@ -4970,6 +5080,28 @@ width: 60px; height: 60px; } +.hair_beard_2_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_beard_2_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_2_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_sunset.png'); + width: 60px; + height: 60px; +} .hair_beard_2_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_2_white.png'); width: 90px; @@ -5190,6 +5322,17 @@ width: 60px; height: 60px; } +.hair_beard_3_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_lava.png'); + width: 60px; + height: 60px; +} .hair_beard_3_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_midnight.png'); width: 90px; @@ -5201,6 +5344,17 @@ width: 60px; height: 60px; } +.hair_beard_3_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_orchid.png'); + width: 60px; + height: 60px; +} .hair_beard_3_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_pblue.png'); width: 90px; @@ -5322,6 +5476,28 @@ width: 60px; height: 60px; } +.hair_beard_3_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_beard_3_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_seasprite.png'); + width: 60px; + height: 60px; +} .hair_beard_3_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_snowy.png'); width: 90px; @@ -5333,6 +5509,28 @@ width: 60px; height: 60px; } +.hair_beard_3_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_beard_3_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_beard_3_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_sunset.png'); + width: 60px; + height: 60px; +} .hair_beard_3_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_beard_3_white.png'); width: 90px; @@ -5553,6 +5751,17 @@ width: 60px; height: 60px; } +.hair_mustache_1_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_lava.png'); + width: 60px; + height: 60px; +} .hair_mustache_1_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_midnight.png'); width: 90px; @@ -5564,6 +5773,17 @@ width: 60px; height: 60px; } +.hair_mustache_1_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_orchid.png'); + width: 60px; + height: 60px; +} .hair_mustache_1_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_pblue.png'); width: 90px; @@ -5685,6 +5905,28 @@ width: 60px; height: 60px; } +.hair_mustache_1_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_mustache_1_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_seasprite.png'); + width: 60px; + height: 60px; +} .hair_mustache_1_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_snowy.png'); width: 90px; @@ -5696,6 +5938,28 @@ width: 60px; height: 60px; } +.hair_mustache_1_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_mustache_1_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_1_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_sunset.png'); + width: 60px; + height: 60px; +} .hair_mustache_1_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_1_white.png'); width: 90px; @@ -5916,6 +6180,17 @@ width: 60px; height: 60px; } +.hair_mustache_2_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_lava.png'); + width: 60px; + height: 60px; +} .hair_mustache_2_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_midnight.png'); width: 90px; @@ -5927,6 +6202,17 @@ width: 60px; height: 60px; } +.hair_mustache_2_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_orchid.png'); + width: 60px; + height: 60px; +} .hair_mustache_2_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_pblue.png'); width: 90px; @@ -6048,6 +6334,28 @@ width: 60px; height: 60px; } +.hair_mustache_2_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_mustache_2_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_seasprite.png'); + width: 60px; + height: 60px; +} .hair_mustache_2_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_snowy.png'); width: 90px; @@ -6059,6 +6367,28 @@ width: 60px; height: 60px; } +.hair_mustache_2_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_mustache_2_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_mustache_2_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_sunset.png'); + width: 60px; + height: 60px; +} .hair_mustache_2_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_mustache_2_white.png'); width: 90px; @@ -6575,6 +6905,17 @@ width: 60px; height: 60px; } +.hair_bangs_1_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_lava.png'); + width: 60px; + height: 60px; +} .hair_bangs_1_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_midnight.png'); width: 90px; @@ -6586,6 +6927,17 @@ width: 60px; height: 60px; } +.hair_bangs_1_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_orchid.png'); + width: 60px; + height: 60px; +} .hair_bangs_1_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_pblue.png'); width: 90px; @@ -6773,6 +7125,28 @@ width: 60px; height: 60px; } +.hair_bangs_1_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_bangs_1_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_seasprite.png'); + width: 60px; + height: 60px; +} .hair_bangs_1_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_snowy.png'); width: 90px; @@ -6784,6 +7158,28 @@ width: 60px; height: 60px; } +.hair_bangs_1_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_bangs_1_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_1_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_sunset.png'); + width: 60px; + height: 60px; +} .hair_bangs_1_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_1_white.png'); width: 90px; @@ -7004,6 +7400,17 @@ width: 60px; height: 60px; } +.hair_bangs_2_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_lava.png'); + width: 60px; + height: 60px; +} .hair_bangs_2_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_midnight.png'); width: 90px; @@ -7015,6 +7422,17 @@ width: 60px; height: 60px; } +.hair_bangs_2_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_orchid.png'); + width: 60px; + height: 60px; +} .hair_bangs_2_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_pblue.png'); width: 90px; @@ -7202,6 +7620,28 @@ width: 60px; height: 60px; } +.hair_bangs_2_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_bangs_2_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_seasprite.png'); + width: 60px; + height: 60px; +} .hair_bangs_2_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_snowy.png'); width: 90px; @@ -7213,6 +7653,28 @@ width: 60px; height: 60px; } +.hair_bangs_2_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_bangs_2_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_2_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_sunset.png'); + width: 60px; + height: 60px; +} .hair_bangs_2_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_2_white.png'); width: 90px; @@ -7433,6 +7895,17 @@ width: 60px; height: 60px; } +.hair_bangs_3_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_lava.png'); + width: 60px; + height: 60px; +} .hair_bangs_3_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_midnight.png'); width: 90px; @@ -7444,6 +7917,17 @@ width: 60px; height: 60px; } +.hair_bangs_3_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_orchid.png'); + width: 60px; + height: 60px; +} .hair_bangs_3_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_pblue.png'); width: 90px; @@ -7631,6 +8115,28 @@ width: 60px; height: 60px; } +.hair_bangs_3_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_bangs_3_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_seasprite.png'); + width: 60px; + height: 60px; +} .hair_bangs_3_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_snowy.png'); width: 90px; @@ -7642,6 +8148,28 @@ width: 60px; height: 60px; } +.hair_bangs_3_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_bangs_3_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_3_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_sunset.png'); + width: 60px; + height: 60px; +} .hair_bangs_3_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_3_white.png'); width: 90px; @@ -7862,6 +8390,17 @@ width: 60px; height: 60px; } +.hair_bangs_4_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_lava.png'); + width: 60px; + height: 60px; +} .hair_bangs_4_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_midnight.png'); width: 90px; @@ -7873,6 +8412,17 @@ width: 60px; height: 60px; } +.hair_bangs_4_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_orchid.png'); + width: 60px; + height: 60px; +} .hair_bangs_4_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_pblue.png'); width: 90px; @@ -8060,6 +8610,28 @@ width: 60px; height: 60px; } +.hair_bangs_4_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_bangs_4_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_seasprite.png'); + width: 60px; + height: 60px; +} .hair_bangs_4_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_snowy.png'); width: 90px; @@ -8071,6 +8643,28 @@ width: 60px; height: 60px; } +.hair_bangs_4_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_bangs_4_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_bangs_4_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_sunset.png'); + width: 60px; + height: 60px; +} .hair_bangs_4_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_bangs_4_white.png'); width: 90px; @@ -8291,6 +8885,17 @@ width: 60px; height: 60px; } +.hair_base_10_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_lava.png'); + width: 60px; + height: 60px; +} .hair_base_10_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_midnight.png'); width: 90px; @@ -8302,6 +8907,17 @@ width: 60px; height: 60px; } +.hair_base_10_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_10_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_pblue.png'); width: 90px; @@ -8489,6 +9105,28 @@ width: 60px; height: 60px; } +.hair_base_10_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_10_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_10_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_snowy.png'); width: 90px; @@ -8500,6 +9138,28 @@ width: 60px; height: 60px; } +.hair_base_10_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_10_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_10_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_10_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_10_white.png'); width: 90px; @@ -8720,6 +9380,17 @@ width: 60px; height: 60px; } +.hair_base_11_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_lava.png'); + width: 60px; + height: 60px; +} .hair_base_11_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_midnight.png'); width: 90px; @@ -8731,6 +9402,17 @@ width: 60px; height: 60px; } +.hair_base_11_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_11_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_pblue.png'); width: 90px; @@ -8918,6 +9600,28 @@ width: 60px; height: 60px; } +.hair_base_11_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_11_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_11_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_snowy.png'); width: 90px; @@ -8929,6 +9633,28 @@ width: 60px; height: 60px; } +.hair_base_11_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_11_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_11_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_11_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_11_white.png'); width: 90px; @@ -9149,6 +9875,17 @@ width: 60px; height: 60px; } +.hair_base_12_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_lava.png'); + width: 60px; + height: 60px; +} .hair_base_12_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_midnight.png'); width: 90px; @@ -9160,6 +9897,17 @@ width: 60px; height: 60px; } +.hair_base_12_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_12_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_pblue.png'); width: 90px; @@ -9347,6 +10095,28 @@ width: 60px; height: 60px; } +.hair_base_12_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_12_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_12_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_snowy.png'); width: 90px; @@ -9358,6 +10128,28 @@ width: 60px; height: 60px; } +.hair_base_12_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_12_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_12_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_12_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_12_white.png'); width: 90px; @@ -9578,6 +10370,17 @@ width: 60px; height: 60px; } +.hair_base_13_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_lava.png'); + width: 60px; + height: 60px; +} .hair_base_13_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_midnight.png'); width: 90px; @@ -9589,6 +10392,17 @@ width: 60px; height: 60px; } +.hair_base_13_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_13_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_pblue.png'); width: 90px; @@ -9776,6 +10590,28 @@ width: 60px; height: 60px; } +.hair_base_13_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_13_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_13_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_snowy.png'); width: 90px; @@ -9787,6 +10623,28 @@ width: 60px; height: 60px; } +.hair_base_13_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_13_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_13_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_13_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_13_white.png'); width: 90px; @@ -10007,6 +10865,17 @@ width: 60px; height: 60px; } +.hair_base_14_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_lava.png'); + width: 60px; + height: 60px; +} .hair_base_14_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_midnight.png'); width: 90px; @@ -10018,6 +10887,17 @@ width: 60px; height: 60px; } +.hair_base_14_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_14_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_pblue.png'); width: 90px; @@ -10205,6 +11085,28 @@ width: 60px; height: 60px; } +.hair_base_14_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_14_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_14_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_snowy.png'); width: 90px; @@ -10216,6 +11118,28 @@ width: 60px; height: 60px; } +.hair_base_14_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_14_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_14_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_14_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_14_white.png'); width: 90px; @@ -10436,6 +11360,17 @@ width: 60px; height: 60px; } +.hair_base_15_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_lava.png'); + width: 60px; + height: 60px; +} .hair_base_15_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_midnight.png'); width: 90px; @@ -10447,6 +11382,17 @@ width: 60px; height: 60px; } +.hair_base_15_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_15_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_pblue.png'); width: 90px; @@ -10634,6 +11580,28 @@ width: 60px; height: 60px; } +.hair_base_15_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_15_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_15_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_snowy.png'); width: 90px; @@ -10645,6 +11613,28 @@ width: 60px; height: 60px; } +.hair_base_15_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_15_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_15_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_15_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_15_white.png'); width: 90px; @@ -10865,6 +11855,17 @@ width: 60px; height: 60px; } +.hair_base_16_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_lava.png'); + width: 60px; + height: 60px; +} .hair_base_16_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_midnight.png'); width: 90px; @@ -10876,6 +11877,17 @@ width: 60px; height: 60px; } +.hair_base_16_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_16_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_pblue.png'); width: 90px; @@ -11063,6 +12075,28 @@ width: 60px; height: 60px; } +.hair_base_16_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_16_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_16_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_snowy.png'); width: 90px; @@ -11074,6 +12108,28 @@ width: 60px; height: 60px; } +.hair_base_16_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_16_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_16_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_16_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_16_white.png'); width: 90px; @@ -11294,6 +12350,17 @@ width: 60px; height: 60px; } +.hair_base_17_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_lava.png'); + width: 60px; + height: 60px; +} .hair_base_17_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_midnight.png'); width: 90px; @@ -11305,6 +12372,17 @@ width: 60px; height: 60px; } +.hair_base_17_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_17_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_pblue.png'); width: 90px; @@ -11492,6 +12570,28 @@ width: 60px; height: 60px; } +.hair_base_17_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_17_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_17_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_snowy.png'); width: 90px; @@ -11503,6 +12603,28 @@ width: 60px; height: 60px; } +.hair_base_17_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_17_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_17_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_17_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_17_white.png'); width: 90px; @@ -11723,6 +12845,17 @@ width: 60px; height: 60px; } +.hair_base_18_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_lava.png'); + width: 60px; + height: 60px; +} .hair_base_18_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_midnight.png'); width: 90px; @@ -11734,6 +12867,17 @@ width: 60px; height: 60px; } +.hair_base_18_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_18_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_pblue.png'); width: 90px; @@ -11921,6 +13065,28 @@ width: 60px; height: 60px; } +.hair_base_18_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_18_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_18_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_snowy.png'); width: 90px; @@ -11932,6 +13098,28 @@ width: 60px; height: 60px; } +.hair_base_18_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_18_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_18_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_18_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_18_white.png'); width: 90px; @@ -12152,6 +13340,17 @@ width: 60px; height: 60px; } +.hair_base_19_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_lava.png'); + width: 60px; + height: 60px; +} .hair_base_19_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_midnight.png'); width: 90px; @@ -12163,6 +13362,17 @@ width: 60px; height: 60px; } +.hair_base_19_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_19_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_pblue.png'); width: 90px; @@ -12350,6 +13560,28 @@ width: 60px; height: 60px; } +.hair_base_19_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_19_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_19_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_snowy.png'); width: 90px; @@ -12361,6 +13593,28 @@ width: 60px; height: 60px; } +.hair_base_19_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_19_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_19_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_19_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_19_white.png'); width: 90px; @@ -12581,6 +13835,17 @@ width: 60px; height: 60px; } +.hair_base_1_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_lava.png'); + width: 60px; + height: 60px; +} .hair_base_1_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_midnight.png'); width: 90px; @@ -12592,6 +13857,17 @@ width: 60px; height: 60px; } +.hair_base_1_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_1_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_pblue.png'); width: 90px; @@ -12779,6 +14055,28 @@ width: 60px; height: 60px; } +.hair_base_1_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_1_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_1_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_snowy.png'); width: 90px; @@ -12790,6 +14088,28 @@ width: 60px; height: 60px; } +.hair_base_1_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_1_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_1_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_1_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_1_white.png'); width: 90px; @@ -13010,6 +14330,17 @@ width: 60px; height: 60px; } +.hair_base_20_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_lava { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_lava.png'); + width: 60px; + height: 60px; +} .hair_base_20_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_midnight.png'); width: 90px; @@ -13021,6 +14352,17 @@ width: 60px; height: 60px; } +.hair_base_20_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_orchid { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_20_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_pblue.png'); width: 90px; @@ -13208,6 +14550,28 @@ width: 60px; height: 60px; } +.hair_base_20_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_sandnsea { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_20_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_seasprite { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_20_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_snowy.png'); width: 90px; @@ -13219,6 +14583,28 @@ width: 60px; height: 60px; } +.hair_base_20_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_sunlitwaves { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_20_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_20_sunset { + background-position: -25px 0; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_20_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_20_white.png'); width: 90px; @@ -13439,6 +14825,17 @@ width: 60px; height: 60px; } +.hair_base_2_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_lava.png'); + width: 60px; + height: 60px; +} .hair_base_2_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_midnight.png'); width: 90px; @@ -13450,6 +14847,17 @@ width: 60px; height: 60px; } +.hair_base_2_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_2_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_pblue.png'); width: 90px; @@ -13637,6 +15045,28 @@ width: 60px; height: 60px; } +.hair_base_2_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_2_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_2_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_snowy.png'); width: 90px; @@ -13648,6 +15078,28 @@ width: 60px; height: 60px; } +.hair_base_2_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_2_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_2_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_2_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_2_white.png'); width: 90px; @@ -13868,6 +15320,17 @@ width: 60px; height: 60px; } +.hair_base_3_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_lava.png'); + width: 60px; + height: 60px; +} .hair_base_3_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_midnight.png'); width: 90px; @@ -13879,6 +15342,17 @@ width: 60px; height: 60px; } +.hair_base_3_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_3_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_pblue.png'); width: 90px; @@ -14066,6 +15540,28 @@ width: 60px; height: 60px; } +.hair_base_3_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_3_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_3_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_snowy.png'); width: 90px; @@ -14077,6 +15573,28 @@ width: 60px; height: 60px; } +.hair_base_3_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_3_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_3_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_3_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_3_white.png'); width: 90px; @@ -14297,6 +15815,17 @@ width: 60px; height: 60px; } +.hair_base_4_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_lava.png'); + width: 60px; + height: 60px; +} .hair_base_4_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_midnight.png'); width: 90px; @@ -14308,6 +15837,17 @@ width: 60px; height: 60px; } +.hair_base_4_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_4_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_pblue.png'); width: 90px; @@ -14495,6 +16035,28 @@ width: 60px; height: 60px; } +.hair_base_4_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_4_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_4_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_snowy.png'); width: 90px; @@ -14506,6 +16068,28 @@ width: 60px; height: 60px; } +.hair_base_4_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_4_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_4_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_4_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_4_white.png'); width: 90px; @@ -14726,6 +16310,17 @@ width: 60px; height: 60px; } +.hair_base_5_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_lava.png'); + width: 60px; + height: 60px; +} .hair_base_5_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_midnight.png'); width: 90px; @@ -14737,6 +16332,17 @@ width: 60px; height: 60px; } +.hair_base_5_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_5_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_pblue.png'); width: 90px; @@ -14924,6 +16530,28 @@ width: 60px; height: 60px; } +.hair_base_5_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_5_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_5_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_snowy.png'); width: 90px; @@ -14935,6 +16563,28 @@ width: 60px; height: 60px; } +.hair_base_5_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_5_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_5_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_5_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_5_white.png'); width: 90px; @@ -15155,6 +16805,17 @@ width: 60px; height: 60px; } +.hair_base_6_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_lava.png'); + width: 60px; + height: 60px; +} .hair_base_6_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_midnight.png'); width: 90px; @@ -15166,6 +16827,17 @@ width: 60px; height: 60px; } +.hair_base_6_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_6_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_pblue.png'); width: 90px; @@ -15353,6 +17025,28 @@ width: 60px; height: 60px; } +.hair_base_6_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_6_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_6_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_snowy.png'); width: 90px; @@ -15364,6 +17058,28 @@ width: 60px; height: 60px; } +.hair_base_6_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_6_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_6_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_6_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_6_white.png'); width: 90px; @@ -15584,6 +17300,17 @@ width: 60px; height: 60px; } +.hair_base_7_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_lava.png'); + width: 60px; + height: 60px; +} .hair_base_7_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_midnight.png'); width: 90px; @@ -15595,6 +17322,17 @@ width: 60px; height: 60px; } +.hair_base_7_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_7_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_pblue.png'); width: 90px; @@ -15782,6 +17520,28 @@ width: 60px; height: 60px; } +.hair_base_7_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_7_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_7_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_snowy.png'); width: 90px; @@ -15793,6 +17553,28 @@ width: 60px; height: 60px; } +.hair_base_7_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_7_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_7_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_7_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_7_white.png'); width: 90px; @@ -16013,6 +17795,17 @@ width: 60px; height: 60px; } +.hair_base_8_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_lava.png'); + width: 60px; + height: 60px; +} .hair_base_8_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_midnight.png'); width: 90px; @@ -16024,6 +17817,17 @@ width: 60px; height: 60px; } +.hair_base_8_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_8_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_pblue.png'); width: 90px; @@ -16211,6 +18015,28 @@ width: 60px; height: 60px; } +.hair_base_8_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_8_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_8_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_snowy.png'); width: 90px; @@ -16222,6 +18048,28 @@ width: 60px; height: 60px; } +.hair_base_8_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_8_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_8_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_8_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_8_white.png'); width: 90px; @@ -16442,6 +18290,17 @@ width: 60px; height: 60px; } +.hair_base_9_lava { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_lava.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_lava { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_lava.png'); + width: 60px; + height: 60px; +} .hair_base_9_midnight { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_midnight.png'); width: 90px; @@ -16453,6 +18312,17 @@ width: 60px; height: 60px; } +.hair_base_9_orchid { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_orchid.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_orchid { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_orchid.png'); + width: 60px; + height: 60px; +} .hair_base_9_pblue { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_pblue.png'); width: 90px; @@ -16640,6 +18510,28 @@ width: 60px; height: 60px; } +.hair_base_9_sandnsea { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sandnsea.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_sandnsea { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sandnsea.png'); + width: 60px; + height: 60px; +} +.hair_base_9_seasprite { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_seasprite.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_seasprite { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_seasprite.png'); + width: 60px; + height: 60px; +} .hair_base_9_snowy { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_snowy.png'); width: 90px; @@ -16651,6 +18543,28 @@ width: 60px; height: 60px; } +.hair_base_9_sunlitwaves { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sunlitwaves.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_sunlitwaves { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sunlitwaves.png'); + width: 60px; + height: 60px; +} +.hair_base_9_sunset { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sunset.png'); + width: 90px; + height: 90px; +} +.customize-option.hair_base_9_sunset { + background-position: -25px -15px; + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_sunset.png'); + width: 60px; + height: 60px; +} .hair_base_9_white { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/hair_base_9_white.png'); width: 90px; @@ -47321,6 +49235,11 @@ width: 219px; height: 219px; } +.quest_giraffe { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_giraffe.png'); + width: 216px; + height: 216px; +} .quest_goldenknight1 { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/quest_goldenknight1.png'); width: 219px; @@ -48031,6 +49950,11 @@ width: 68px; height: 68px; } +.inventory_quest_scroll_giraffe { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_giraffe.png'); + width: 68px; + height: 68px; +} .inventory_quest_scroll_goldenknight1 { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/inventory_quest_scroll_goldenknight1.png'); width: 68px; @@ -48866,6 +50790,11 @@ width: 68px; height: 68px; } +.Pet_Egg_Giraffe { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_Egg_Giraffe.png'); + width: 68px; + height: 68px; +} .Pet_Egg_Gryphon { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet_Egg_Gryphon.png'); width: 68px; @@ -51481,6 +53410,56 @@ width: 105px; height: 114px; } +.Mount_Body_Giraffe-Base { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Base.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-CottonCandyBlue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-CottonCandyBlue.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-CottonCandyPink { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-CottonCandyPink.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Desert { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Desert.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Golden { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Golden.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Red { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Red.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Shade { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Shade.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Skeleton { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Skeleton.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-White { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-White.png'); + width: 105px; + height: 105px; +} +.Mount_Body_Giraffe-Zombie { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Giraffe-Zombie.png'); + width: 105px; + height: 105px; +} .Mount_Body_Gryphon-Base { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Body_Gryphon-Base.png'); width: 105px; @@ -56681,6 +58660,56 @@ width: 105px; height: 114px; } +.Mount_Head_Giraffe-Base { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Base.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-CottonCandyBlue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-CottonCandyBlue.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-CottonCandyPink { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-CottonCandyPink.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Desert { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Desert.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Golden { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Golden.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Red { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Red.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Shade { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Shade.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Skeleton { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Skeleton.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-White { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-White.png'); + width: 105px; + height: 105px; +} +.Mount_Head_Giraffe-Zombie { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Giraffe-Zombie.png'); + width: 105px; + height: 105px; +} .Mount_Head_Gryphon-Base { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Head_Gryphon-Base.png'); width: 105px; @@ -61886,6 +63915,56 @@ width: 81px; height: 99px; } +.Mount_Icon_Giraffe-Base { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Base.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-CottonCandyBlue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-CottonCandyBlue.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-CottonCandyPink { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-CottonCandyPink.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Desert { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Desert.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Golden { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Golden.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Red { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Red.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Shade { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Shade.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Skeleton { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Skeleton.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-White { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-White.png'); + width: 81px; + height: 99px; +} +.Mount_Icon_Giraffe-Zombie { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Giraffe-Zombie.png'); + width: 81px; + height: 99px; +} .Mount_Icon_Gryphon-Base { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Mount_Icon_Gryphon-Base.png'); width: 81px; @@ -67236,6 +69315,56 @@ width: 81px; height: 99px; } +.Pet-Giraffe-Base { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Base.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-CottonCandyBlue { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-CottonCandyBlue.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-CottonCandyPink { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-CottonCandyPink.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Desert { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Desert.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Golden { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Golden.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Red { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Red.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Shade { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Shade.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Skeleton { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Skeleton.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-White { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-White.png'); + width: 81px; + height: 99px; +} +.Pet-Giraffe-Zombie { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Giraffe-Zombie.png'); + width: 81px; + height: 99px; +} .Pet-Gryphon-Base { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/Pet-Gryphon-Base.png'); width: 81px; diff --git a/website/common/locales/en/content.json b/website/common/locales/en/content.json index e7a32b4b88..04ee666e29 100644 --- a/website/common/locales/en/content.json +++ b/website/common/locales/en/content.json @@ -251,6 +251,10 @@ "questEggRobotMountText": "Robot", "questEggRobotAdjective": "a futuristic", + "questEggGiraffeText": "Giraffe", + "questEggGiraffeMountText": "Giraffe", + "questEggGiraffeAdjective": "a towering", + "eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.", "hatchingPotionBase": "Base", diff --git a/website/common/locales/en/customizations.json b/website/common/locales/en/customizations.json index d694d84a2e..8a580fba2e 100644 --- a/website/common/locales/en/customizations.json +++ b/website/common/locales/en/customizations.json @@ -51,6 +51,7 @@ "hollygreen": "Holly Green", "horizon": "Horizon", "largeMustache": "Large Mustache", + "lava": "Lava", "leftBun": "Left Bun", "lion": "Lion", "longBeard": "Long Beard", @@ -64,6 +65,7 @@ "monster": "Monster", "ocean": "Ocean", "ogre": "Ogre", + "orchid": "Orchid", "panda": "Panda", "pastelBlue": "Pastel Blue", "pastelGreen": "Pastel Green", @@ -97,6 +99,8 @@ "redblue": "Red and Blue", "reptile": "Reptile", "rightBun": "Right Bun", + "sandnsea": "Sand 'n' Sea", + "seasprite": "Sea Sprite", "shadow": "Shadow", "shadow2": "Shade", "shark": "Shark", @@ -108,6 +112,8 @@ "straightLong": "Straight Long", "straightShort": "Straight Short", "sugar": "Sugar", + "sunlitwaves": "Sunlit Waves", + "sunset": "Sunset", "thunder": "Thunder", "tiger": "Tiger", "transparent": "Transparent", diff --git a/website/common/locales/en/questsContent.json b/website/common/locales/en/questsContent.json index 314f61ee45..be3f3a51cf 100644 --- a/website/common/locales/en/questsContent.json +++ b/website/common/locales/en/questsContent.json @@ -887,5 +887,12 @@ "questPinkMarbleRageDescription": "This bar fills when you don't complete your Dailies. When it is full, Cupido will take away some of your party's pending damage!", "questPinkMarbleRageEffect": "`Cupido uses Pink Punch!` That wasn't affectionate at all! Your partymates are taken aback. Pending damage reduced.", "questPinkMarbleDropPinkMarblePotion": "Pink Marble Hatching Potion", - "QuestPinkMarbleUnlockText": "Unlocks Pink Marble Hatching Potions for purchase in the Market." + "QuestPinkMarbleUnlockText": "Unlocks Pink Marble Hatching Potions for purchase in the Market.", + + "questGiraffeText": "The Gear-affe", + "questGiraffeNotes": "You’re strolling across the tall grass of the Sloenstedi Savannah, enjoying a nice walk in nature as a break from your tasks. As you pass through the rolling landscape, you notice a collection of items in the distance. It’s a pile of musical instruments, art supplies, electronic equipment, and more! You venture near for a better look.

“Hey, what do you think you’re doing?” yells a voice from behind an acacia. A tall and imposing giraffe emerges, wearing a fancy pair of shades. a guitar, and a fancy camera around its long neck. “This is all my gear, be careful and don’t touch anything!”

You notice dust on many of the items. “Wow, you sure have a lot of hobbies!” you say. “Can you show me some art or play me a tune?”

The giraffe’s face falls as he looks at all his supplies. “I have so much of this stuff but don’t know where to begin! Why don't you give me some of your motivation so I can have the productive energy I need to finally get started!”", + "questGiraffeCompletion": "After helping the Gear-Affe with some basic organization of his stash, you’re both feeling more energized and upbeat!

He grabs his guitar and a book of beginner exercises and strums a few notes. “It feels good to take a step in the right direction, even a small one. Thanks for helping me out! Take these, I hear you have a stash of pets and these fellas could be a nice addition!”", + "questGiraffeBoss": "Gear-affe", + "questGiraffeDropGiraffeEgg": "Giraffe (Egg)", + "QuestGiraffeUnlockText": "Unlocks Giraffe Eggs for purchase in the Market." } diff --git a/website/common/script/content/appearance/hair/color.js b/website/common/script/content/appearance/hair/color.js index afba6e21a1..e19edf5c65 100644 --- a/website/common/script/content/appearance/hair/color.js +++ b/website/common/script/content/appearance/hair/color.js @@ -34,6 +34,13 @@ export default prefill({ ppurple2: { price: 2, set: sets.shimmerHairColors }, pyellow2: { price: 2, set: sets.shimmerHairColors }, + sunset: { price: 2, set: sets.summerHairColors }, + sunlitwaves: { price: 2, set: sets.summerHairColors }, + seasprite: { price: 2, set: sets.summerHairColors }, + sandnsea: { price: 2, set: sets.summerHairColors }, + orchid: { price: 2, set: sets.summerHairColors }, + lava: { price: 2, set: sets.summerHairColors }, + candycorn: { price: 2, set: sets.hauntedHairColors }, ghostwhite: { price: 2, set: sets.hauntedHairColors }, halloween: { price: 2, set: sets.hauntedHairColors }, diff --git a/website/common/script/content/appearance/sets.js b/website/common/script/content/appearance/sets.js index 02b6e7963b..4fb7ceddd2 100644 --- a/website/common/script/content/appearance/sets.js +++ b/website/common/script/content/appearance/sets.js @@ -11,6 +11,7 @@ export default prefill({ pastelHairColors: { setPrice: 5 }, rainbowHairColors: { setPrice: 5, text: t('rainbowColors') }, shimmerHairColors: { setPrice: 5, text: t('shimmerColors') }, + summerHairColors: { setPrice: 5, text: t('summerColors') }, hauntedHairColors: { setPrice: 5, text: t('hauntedColors') }, winteryHairColors: { setPrice: 5, text: t('winteryColors') }, rainbowSkins: { setPrice: 5, text: t('rainbowSkins') }, diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 6e221eb9ad..2faab875f4 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -327,6 +327,7 @@ export const MONTHLY_SCHEDULE = { 'unicorn', 'velociraptor', 'hippo', + 'giraffe', ], }, { @@ -739,6 +740,7 @@ export const GALA_SCHEDULE = { { type: 'customizations', matcher: customizationMatcher([ + 'summerHairColors', 'splashySkins', ]), }, diff --git a/website/common/script/content/eggs.js b/website/common/script/content/eggs.js index 77ddd4a209..0a6e438e5b 100644 --- a/website/common/script/content/eggs.js +++ b/website/common/script/content/eggs.js @@ -390,6 +390,12 @@ const quests = { adjective: t('questEggRobotAdjective'), canBuy: hasQuestAchievementFunction('robot'), }, + Giraffe: { + text: t('questEggGiraffeText'), + mountText: t('questEggGiraffeMountText'), + adjective: t('questEggGiraffeAdjective'), + canBuy: hasQuestAchievementFunction('giraffe'), + }, }; applyEggDefaults(drops, { diff --git a/website/common/script/content/quests/pets.js b/website/common/script/content/quests/pets.js index e4b42da25e..7d0f6f6e3a 100644 --- a/website/common/script/content/quests/pets.js +++ b/website/common/script/content/quests/pets.js @@ -488,6 +488,38 @@ const QUEST_PETS = { unlock: t('questGhostStagUnlockText'), }, }, + giraffe: { + text: t('questGiraffeText'), + notes: t('questGiraffeNotes'), + completion: t('questGiraffeCompletion'), + value: 4, + category: 'pet', + boss: { + name: t('questGiraffeBoss'), + hp: 700, + str: 2, + }, + drop: { + items: [ + { + type: 'eggs', + key: 'Giraffe', + text: t('questGiraffeDropGiraffeEgg'), + }, { + type: 'eggs', + key: 'Giraffe', + text: t('questGiraffeDropGiraffeEgg'), + }, { + type: 'eggs', + key: 'Giraffe', + text: t('questGiraffeDropGiraffeEgg'), + }, + ], + gp: 50, + exp: 450, + unlock: t('questGiraffeUnlockText'), + }, + }, gryphon: { text: t('questGryphonText'), notes: t('questGryphonNotes'), diff --git a/website/common/script/libs/shops.js b/website/common/script/libs/shops.js index 4cf379bd5a..231959977f 100644 --- a/website/common/script/libs/shops.js +++ b/website/common/script/libs/shops.js @@ -296,6 +296,9 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language) const matchers = getScheduleMatchingGroup(`${type}Quests`); filteredQuests = filteredQuests.filter(quest => matchers.match(quest.key)) .map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language, matchers)); + } else { + filteredQuests = filteredQuests + .map(quest => getItemInfo(user, 'quests', quest, officialPinnedItems, language)); } category.items = filteredQuests; From 61d151d2bb66d3163ca964f51baded36db774285 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 7 May 2024 20:15:16 -0500 Subject: [PATCH 102/244] chore(testing): enable non prod analytics --- website/server/libs/analyticsService.js | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/website/server/libs/analyticsService.js b/website/server/libs/analyticsService.js index 2d36eeb53a..0c88a3b3b8 100644 --- a/website/server/libs/analyticsService.js +++ b/website/server/libs/analyticsService.js @@ -353,14 +353,10 @@ const mockAnalyticsService = { // Return the production or mock service based on the current environment function getServiceByEnvironment () { - if (nconf.get('IS_PROD')) { - return { - track, - trackPurchase, - }; - } - - return mockAnalyticsService; + return { + track, + trackPurchase, + }; } export { From 4a9ec734c19d3b4a43546dc29c5b4f36e3e706af Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 11:21:38 +0200 Subject: [PATCH 103/244] validate that schedule keys refer to existing content --- test/content/schedule.test.js | 80 ++++++++++++++++++- .../script/content/constants/schedule.js | 6 +- 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index f0df7da31f..573a3ab8ca 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -4,14 +4,22 @@ import { } from '../helpers/content.helper'; */ // eslint-disable-next-line max-len import moment from 'moment'; -import { getAllScheduleMatchingGroups, clearCachedMatchers } from '../../website/common/script/content/constants/schedule'; +import { + getAllScheduleMatchingGroups, clearCachedMatchers, MONTHLY_SCHEDULE, GALA_SCHEDULE, +} from '../../website/common/script/content/constants/schedule'; +import QUEST_PETS from '../../website/common/script/content/quests/pets'; +import QUEST_HATCHINGPOTIONS from '../../website/common/script/content/quests/potions'; +import QUEST_BUNDLES from '../../website/common/script/content/bundles'; +import { premium } from '../../website/common/script/content/hatching-potions'; +import SPELLS from '../../website/common/script/content/spells'; +import QUEST_SEASONAL from '../../website/common/script/content/quests/seasonal'; function validateMatcher (matcher, checkedDate) { expect(matcher.end).to.be.a('date'); expect(matcher.end).to.be.greaterThan(checkedDate); } -describe('Content Schedule', () => { +describe.only('Content Schedule', () => { beforeEach(() => { clearCachedMatchers(); }); @@ -140,6 +148,74 @@ describe('Content Schedule', () => { expect(matchers.seasonalGear.end).to.eql(moment('2024-06-21').toDate()); }); + describe('only contains valid keys for', () => { + it('pet quests', () => { + const petKeys = Object.keys(QUEST_PETS); + Object.keys(MONTHLY_SCHEDULE).forEach(key => { + const petQuests = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'petQuests'); + for (const petQuest of petQuests.items) { + expect(petQuest).to.be.a('string'); + expect(petKeys).to.include(petQuest); + } + }); + }); + + it('hatchingpotion quests', () => { + const potionKeys = Object.keys(QUEST_HATCHINGPOTIONS); + Object.keys(MONTHLY_SCHEDULE).forEach(key => { + const potionQuests = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'hatchingPotionQuests'); + for (const potionQuest of potionQuests.items) { + expect(potionQuest).to.be.a('string'); + expect(potionKeys).to.include(potionQuest); + } + }); + }); + + it('bundles', () => { + const bundleKeys = Object.keys(QUEST_BUNDLES); + Object.keys(MONTHLY_SCHEDULE).forEach(key => { + const bundles = MONTHLY_SCHEDULE[key][14].find(item => item.type === 'bundles'); + for (const bundle of bundles.items) { + expect(bundle).to.be.a('string'); + expect(bundleKeys).to.include(bundle); + } + }); + }); + + it('premium hatching potions', () => { + const potionKeys = Object.keys(premium); + Object.keys(MONTHLY_SCHEDULE).forEach(key => { + const potions = MONTHLY_SCHEDULE[key][21].find(item => item.type === 'premiumHatchingPotions'); + for (const potion of potions.items) { + expect(potion).to.be.a('string'); + expect(potionKeys).to.include(potion); + } + }); + }); + + it('seasonal quests', () => { + const questKeys = Object.keys(QUEST_SEASONAL); + Object.keys(GALA_SCHEDULE).forEach(key => { + const quests = GALA_SCHEDULE[key].matchers.find(item => item.type === 'seasonalQuests'); + for (const quest of quests.items) { + expect(quest).to.be.a('string'); + expect(questKeys).to.include(quest); + } + }); + }); + + it('seasonal spells', () => { + const spellKeys = Object.keys(SPELLS.special); + Object.keys(GALA_SCHEDULE).forEach(key => { + const petQuests = GALA_SCHEDULE[key].matchers.find(item => item.type === 'seasonalSpells'); + for (const petQuest of petQuests.items) { + expect(petQuest).to.be.a('string'); + expect(spellKeys).to.include(petQuest); + } + }); + }); + }); + describe('backgrounds matcher', () => { it('allows background matching the month for new backgrounds', () => { const date = new Date('2024-07-08'); diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index 2faab875f4..c60724300a 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -159,7 +159,6 @@ export const MONTHLY_SCHEDULE = { items: [ 'PolkaDot', 'Celestial', - 'RoseGold', ], }, ], @@ -204,7 +203,6 @@ export const MONTHLY_SCHEDULE = { { type: 'premiumHatchingPotions', items: [ - 'Birch', 'StainedGlass', 'Porcelain', ], @@ -232,7 +230,7 @@ export const MONTHLY_SCHEDULE = { 'snake', 'monkey', 'falcon', - 'aligator', + 'alligator', ], }, { @@ -397,7 +395,6 @@ export const MONTHLY_SCHEDULE = { type: 'premiumHatchingPotions', items: [ 'Moonglow', - 'SandCastle', 'Watery', ], }, @@ -582,7 +579,6 @@ export const MONTHLY_SCHEDULE = { type: 'bundles', items: [ 'forestFriends', - 'oddBalls', ], }, ], From 856ed24dcbbb37687c9835dcf580dbf509212d79 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 16:46:42 +0200 Subject: [PATCH 104/244] Add tests to check for correct food content populating --- test/content/food.test.js | 94 +++++++++++++++++++ test/content/schedule.test.js | 2 +- .../common/script/content/constants/events.js | 2 +- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 test/content/food.test.js diff --git a/test/content/food.test.js b/test/content/food.test.js new file mode 100644 index 0000000000..b0f1938f0f --- /dev/null +++ b/test/content/food.test.js @@ -0,0 +1,94 @@ +/* eslint-disable global-require */ +import { + each, +} from 'lodash'; +import { + expectValidTranslationString, +} from '../helpers/content.helper'; +import content from '../../website/common/script/content'; + +describe('food', () => { + let clock; + + afterEach(() => { + if (clock) { + clock.restore(); + } + delete require.cache[require.resolve('../../website/common/script/content')]; + }); + + describe('all', () => { + it('contains basic information about each food item', () => { + each(content.food, (foodItem, key) => { + if (foodItem.key === 'Saddle') { + expectValidTranslationString(foodItem.sellWarningNote); + } else { + expectValidTranslationString(foodItem.textA); + expectValidTranslationString(foodItem.textThe); + expectValidTranslationString(foodItem.target); + } + expectValidTranslationString(foodItem.text); + expectValidTranslationString(foodItem.notes); + expect(foodItem.canBuy).to.be.a('function'); + expect(foodItem.value).to.be.a('number'); + expect(foodItem.key).to.equal(key); + }); + }); + + it('sets canDrop for normal food if there is no food season', () => { + clock = sinon.useFakeTimers(new Date(2024, 5, 8)); + const datedContent = require('../../website/common/script/content').default; + each(datedContent.food, foodItem => { + if (foodItem.key.indexOf('Cake') === -1 && foodItem.key.indexOf('Candy_') === -1 && foodItem.key.indexOf('Pie_') === -1 && foodItem.key !== 'Saddle') { + expect(foodItem.canDrop).to.equal(true); + } else { + expect(foodItem.canDrop).to.equal(false); + } + }); + }); + + it('sets canDrop for candy if it is candy season', () => { + clock = sinon.useFakeTimers(new Date(2024, 9, 31)); + const datedContent = require('../../website/common/script/content').default; + each(datedContent.food, foodItem => { + if (foodItem.key.indexOf('Candy_') !== -1) { + expect(foodItem.canDrop).to.equal(true); + } else { + expect(foodItem.canDrop).to.equal(false); + } + }); + }); + + it('sets canDrop for cake if it is cake season', () => { + clock = sinon.useFakeTimers(new Date(2024, 0, 31)); + const datedContent = require('../../website/common/script/content').default; + each(datedContent.food, foodItem => { + if (foodItem.key.indexOf('Cake_') !== -1) { + expect(foodItem.canDrop).to.equal(true); + } else { + expect(foodItem.canDrop).to.equal(false); + } + }); + }); + + it('sets canDrop for pie if it is pie season', () => { + clock = sinon.useFakeTimers(new Date(2024, 2, 14)); + const datedContent = require('../../website/common/script/content').default; + each(datedContent.food, foodItem => { + if (foodItem.key.indexOf('Pie_') !== -1) { + expect(foodItem.canDrop).to.equal(true); + } else { + expect(foodItem.canDrop).to.equal(false); + } + }); + }); + }); + + it('sets correct values for saddles', () => { + const saddle = content.food.Saddle; + expect(saddle.canBuy).to.be.a('function'); + expect(saddle.value).to.equal(5); + expect(saddle.key).to.equal('Saddle'); + expect(saddle.canDrop).to.equal(false); + }); +}); diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index 573a3ab8ca..fc6e61b884 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -19,7 +19,7 @@ function validateMatcher (matcher, checkedDate) { expect(matcher.end).to.be.greaterThan(checkedDate); } -describe.only('Content Schedule', () => { +describe('Content Schedule', () => { beforeEach(() => { clearCachedMatchers(); }); diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index cdccf581ec..8771f87cf9 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -78,7 +78,7 @@ export function getRepeatingEvents (date) { const endDate = eventData.end.replace('1970', momentDate.year()); return momentDate.isBetween(startDate, endDate); - }); + }).map(eventKey => REPEATING_EVENTS[eventKey]); } export const EVENTS = { From 02914685dc210158d099c6d43467322b3005e897 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 17:05:09 +0200 Subject: [PATCH 105/244] improve armoire release test cases --- test/content/armoire.test.js | 77 ++++++++++++++----- .../script/content/gear/sets/armoire.js | 3 +- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/test/content/armoire.test.js b/test/content/armoire.test.js index a387d0a0b1..564339454c 100644 --- a/test/content/armoire.test.js +++ b/test/content/armoire.test.js @@ -1,34 +1,75 @@ /* eslint-disable global-require */ import forEach from 'lodash/forEach'; +import { + expectValidTranslationString, +} from '../helpers/content.helper'; + +function makeArmoireIitemList () { + const { + armor, + body, + eyewear, + head, + headAccessory, + shield, + weapon, + } = require('../../website/common/script/content/gear/sets/armoire'); + const items = []; + items.push(...Object.values(armor)); + items.push(...Object.values(body)); + items.push(...Object.values(eyewear)); + items.push(...Object.values(head)); + items.push(...Object.values(headAccessory)); + items.push(...Object.values(shield)); + items.push(...Object.values(weapon)); + return items; +} describe('armoire', () => { let clock; beforeEach(() => { - clock = sinon.useFakeTimers(new Date('2024-01-01')); + delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')]; }); afterEach(() => { clock.restore(); }); it('does not return unreleased gear', async () => { - const { - armor, - body, - eyewear, - head, - headAccessory, - shield, - weapon, - } = require('../../website/common/script/content/gear/sets/armoire'); - const items = []; - items.push(...Object.values(armor)); - items.push(...Object.values(body)); - items.push(...Object.values(eyewear)); - items.push(...Object.values(head)); - items.push(...Object.values(headAccessory)); - items.push(...Object.values(shield)); - items.push(...Object.values(weapon)); + clock = sinon.useFakeTimers(new Date('2024-01-01')); + const items = makeArmoireIitemList(); + expect(items.length).to.equal(377); forEach(items, item => { expect(item.released).to.be.true; }); }); + + it('released gear has all required properties', async () => { + clock = sinon.useFakeTimers(new Date('2024-05-08')); + const items = makeArmoireIitemList(); + expect(items.length).to.equal(392); + forEach(items, item => { + if (item.set !== undefined) { + expect(item.set).to.be.a('string'); + expect(item.set).to.not.be.empty; + } + expectValidTranslationString(item.text); + expect(item.released).to.be.a('boolean'); + expect(item.value).to.be.a('number'); + }); + }); + + it('releases gear when appropriate', async () => { + clock = sinon.useFakeTimers(new Date('2024-01-01')); + const items = makeArmoireIitemList(); + expect(items.length).to.equal(377); + clock.restore(); + delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')]; + clock = sinon.useFakeTimers(new Date('2024-01-08')); + const januaryItems = makeArmoireIitemList(); + expect(januaryItems.length).to.equal(381); + clock.restore(); + delete require.cache[require.resolve('../../website/common/script/content/gear/sets/armoire')]; + clock = sinon.useFakeTimers(new Date('2024-02-20')); + const febuaryItems = makeArmoireIitemList(); + expect(febuaryItems.length).to.equal(384); + }); }); diff --git a/website/common/script/content/gear/sets/armoire.js b/website/common/script/content/gear/sets/armoire.js index a0f42d0782..cedeb71d03 100644 --- a/website/common/script/content/gear/sets/armoire.js +++ b/website/common/script/content/gear/sets/armoire.js @@ -1846,7 +1846,8 @@ forEach({ } let released; if (releaseDates[gearItem.set]) { - released = today.isAfter(`${releaseDates[gearItem.set].year}-${String(releaseDates[gearItem.set].month).padStart(2, '0')}-${releaseDateEndPart}`); + const releaseDateString = `${releaseDates[gearItem.set].year}-${String(releaseDates[gearItem.set].month).padStart(2, '0')}-${releaseDateEndPart}`; + released = today.isAfter(releaseDateString); } else { released = true; } From 6ed422cd28f8576994fd84b789be91df3659a0f8 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 17:42:24 +0200 Subject: [PATCH 106/244] Improve repeating events handling --- test/content/events.test.js | 40 ++++++++++++++++ .../common/script/content/constants/events.js | 47 ++++++++++--------- 2 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 test/content/events.test.js diff --git a/test/content/events.test.js b/test/content/events.test.js new file mode 100644 index 0000000000..db6efd95b5 --- /dev/null +++ b/test/content/events.test.js @@ -0,0 +1,40 @@ +import { getRepeatingEvents } from '../../website/common/script/content/constants/events'; + +describe.only('events', () => { + let clock; + + afterEach(() => { + if (clock) { + clock.restore(); + } + }); + + it('returns empty array when no events are active', () => { + clock = sinon.useFakeTimers(new Date('2024-01-06')); + const events = getRepeatingEvents(); + expect(events).to.be.empty; + }); + + it('returns events when active', () => { + clock = sinon.useFakeTimers(new Date('2024-01-31')); + const events = getRepeatingEvents(); + expect(events).to.have.length(1); + expect(events[0].key).to.equal('birthday'); + expect(events[0].end).to.be.greaterThan(new Date()); + expect(events[0].start).to.be.lessThan(new Date()); + }); + + it('returns nye event at beginning of the year', () => { + clock = sinon.useFakeTimers(new Date('2025-01-01')); + const events = getRepeatingEvents(); + expect(events).to.have.length(1); + expect(events[0].key).to.equal('nye'); + }); + + it('returns nye event at end of the year', () => { + clock = sinon.useFakeTimers(new Date('2024-12-30')); + const events = getRepeatingEvents(); + expect(events).to.have.length(1); + expect(events[0].key).to.equal('nye'); + }); +}); diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index 8771f87cf9..adcc730659 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -12,8 +12,8 @@ const gemsPromo = { export const REPEATING_EVENTS = { nye: { - start: '1970-12-28T08:00-05:00', - end: '1970-01-04T23:59-05:00', + start: new Date('1970-12-28T08:00-05:00'), + end: new Date('1970-01-04T23:59-05:00'), season: 'nye', npcImageSuffix: '_nye', content: [ @@ -26,15 +26,15 @@ export const REPEATING_EVENTS = { ], }, birthday: { - start: '1970-01-30T08:00-05:00', - end: '1970-02-08T23:59-05:00', + start: new Date('1970-01-30T08:00-05:00'), + end: new Date('1970-02-08T23:59-05:00'), season: 'birthday', npcImageSuffix: '_birthday', foodSeason: 'Cake', }, valentines: { - start: '1970-02-13T08:00-05:00', - end: '1970-02-17T23:59-05:00', + start: new Date('1970-02-13T08:00-05:00'), + end: new Date('1970-02-17T23:59-05:00'), season: 'valentines', npcImageSuffix: '_valentines', content: [ @@ -47,23 +47,23 @@ export const REPEATING_EVENTS = { ], }, piDay: { - start: '1970-03-13T08:00-05:00', - end: '1970-03-15T23:59-05:00', + start: new Date('1970-03-13T08:00-05:00'), + end: new Date('1970-03-15T23:59-05:00'), foodSeason: 'Pie', }, namingDay: { - start: '1970-07-30T08:00-05:00', - end: '1970-08-01T23:59-05:00', + start: new Date('1970-07-30T08:00-05:00'), + end: new Date('1970-08-01T23:59-05:00'), foodSeason: 'Cake', }, habitoween: { - start: '1970-10-30T08:00-05:00', - end: '1970-11-01T23:59-05:00', + start: new Date('1970-10-30T08:00-05:00'), + end: new Date('1970-11-01T23:59-05:00'), foodSeason: 'Candy', }, harvestFeast: { - start: '1970-11-22T08:00-05:00', - end: '1970-11-27T20:00-05:00', + start: new Date('1970-11-22T08:00-05:00'), + end: new Date('1970-11-27T20:00-05:00'), season: 'thanksgiving', npcImageSuffix: '_thanksgiving', foodSeason: 'Pie', @@ -72,13 +72,18 @@ export const REPEATING_EVENTS = { export function getRepeatingEvents (date) { const momentDate = date instanceof moment ? date : moment(date); - return filter(Object.keys(REPEATING_EVENTS), eventKey => { - const eventData = REPEATING_EVENTS[eventKey]; - const startDate = eventData.start.replace('1970', momentDate.year()); - const endDate = eventData.end.replace('1970', momentDate.year()); - - return momentDate.isBetween(startDate, endDate); - }).map(eventKey => REPEATING_EVENTS[eventKey]); + return Object.keys(REPEATING_EVENTS).map(eventKey => { + const event = REPEATING_EVENTS[eventKey]; + event.key = eventKey; + event.start.setYear(momentDate.year()); + event.end.setYear(momentDate.year()); + if (event.end < event.start && momentDate < event.start) { + event.start.setYear(momentDate.year() - 1); + } else if (event.end < event.start && momentDate > event.end) { + event.end.setYear(momentDate.year() + 1); + } + return event; + }).filter(event => momentDate.isBetween(event.start, event.end)); } export const EVENTS = { From a9cefd284a2195c71fa88e1c09c670746423d400 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 17:42:40 +0200 Subject: [PATCH 107/244] support april fools resale in schedule --- test/content/schedule.test.js | 36 ++++++------------- .../common/script/content/constants/events.js | 20 +++++++++++ .../script/content/constants/schedule.js | 5 ++- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index fc6e61b884..b7d4620c92 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -19,7 +19,7 @@ function validateMatcher (matcher, checkedDate) { expect(matcher.end).to.be.greaterThan(checkedDate); } -describe('Content Schedule', () => { +describe.only('Content Schedule', () => { beforeEach(() => { clearCachedMatchers(); }); @@ -96,58 +96,42 @@ describe('Content Schedule', () => { it('sets the end date if its in the same month', () => { const date = new Date('2024-04-03'); const matchers = getAllScheduleMatchingGroups(date); - for (const key in matchers) { - if (matchers[key]) { - validateMatcher(matchers[key], date); - } - } expect(matchers.backgrounds.end).to.eql(moment('2024-04-07').toDate()); }); it('sets the end date if its in the next day', () => { const date = new Date('2024-05-06T14:00:00.000Z'); const matchers = getAllScheduleMatchingGroups(date); - for (const key in matchers) { - if (matchers[key]) { - validateMatcher(matchers[key], date); - } - } expect(matchers.backgrounds.end).to.eql(moment('2024-05-07').toDate()); }); it('sets the end date if its on the release day', () => { const date = new Date('2024-05-07'); const matchers = getAllScheduleMatchingGroups(date); - for (const key in matchers) { - if (matchers[key]) { - validateMatcher(matchers[key], date); - } - } expect(matchers.backgrounds.end).to.eql(moment('2024-06-07').toDate()); }); it('sets the end date if its next month', () => { const date = new Date('2024-05-20T01:00:00.000Z'); const matchers = getAllScheduleMatchingGroups(date); - for (const key in matchers) { - if (matchers[key]) { - validateMatcher(matchers[key], date); - } - } expect(matchers.backgrounds.end).to.eql(moment('2024-06-07').toDate()); }); it('sets the end date for a gala', () => { const date = new Date('2024-05-20'); const matchers = getAllScheduleMatchingGroups(date); - for (const key in matchers) { - if (matchers[key]) { - validateMatcher(matchers[key], date); - } - } expect(matchers.seasonalGear.end).to.eql(moment('2024-06-21').toDate()); }); + it('contains content for repeating events', () => { + const date = new Date('2024-04-15'); + const matchers = getAllScheduleMatchingGroups(date); + expect(matchers.premiumHatchingPotions).to.exist; + expect(matchers.premiumHatchingPotions.items.length).to.equal(4); + expect(matchers.premiumHatchingPotions.items.indexOf('Garden')).to.not.equal(-1); + expect(matchers.premiumHatchingPotions.items.indexOf('Porcelain')).to.not.equal(-1); + }); + describe('only contains valid keys for', () => { it('pet quests', () => { const petKeys = Object.keys(QUEST_PETS); diff --git a/website/common/script/content/constants/events.js b/website/common/script/content/constants/events.js index adcc730659..b7490b03cb 100644 --- a/website/common/script/content/constants/events.js +++ b/website/common/script/content/constants/events.js @@ -51,6 +51,26 @@ export const REPEATING_EVENTS = { end: new Date('1970-03-15T23:59-05:00'), foodSeason: 'Pie', }, + aprilFoolsResale: { + start: new Date('1970-04-07T08:00-05:00'), + end: new Date('1970-04-30T23:59-05:00'), + content: [ + { + type: 'hatchingPotionQuests', + items: [ + 'virtualpet', + 'waffle', + ], + }, + { + type: 'premiumHatchingPotions', + items: [ + 'Garden', + 'TeaShop', + ], + }, + ], + }, namingDay: { start: new Date('1970-07-30T08:00-05:00'), end: new Date('1970-08-01T23:59-05:00'), diff --git a/website/common/script/content/constants/schedule.js b/website/common/script/content/constants/schedule.js index c60724300a..f0635102bb 100644 --- a/website/common/script/content/constants/schedule.js +++ b/website/common/script/content/constants/schedule.js @@ -701,7 +701,6 @@ export const GALA_SCHEDULE = { type: 'seasonalQuests', items: [ 'egg', - 'waffle', ], }, { @@ -835,11 +834,11 @@ export function assembleScheduledMatchers (date) { matcher.endMonth = gala.endMonth; }); items.push(...galaMatchers); - for (const event in getRepeatingEvents(date)) { + getRepeatingEvents(date).forEach(event => { if (event.content) { items.push(...event.content); } - } + }); return items; } From fce5371fced48214e7ecc4afccd44c18fa154d75 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 17:46:40 +0200 Subject: [PATCH 108/244] fix typos --- test/content/events.test.js | 2 +- test/content/food.test.js | 2 +- test/content/schedule.test.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/test/content/events.test.js b/test/content/events.test.js index db6efd95b5..03ffd08ed9 100644 --- a/test/content/events.test.js +++ b/test/content/events.test.js @@ -1,6 +1,6 @@ import { getRepeatingEvents } from '../../website/common/script/content/constants/events'; -describe.only('events', () => { +describe('events', () => { let clock; afterEach(() => { diff --git a/test/content/food.test.js b/test/content/food.test.js index b0f1938f0f..5cdb2b80bc 100644 --- a/test/content/food.test.js +++ b/test/content/food.test.js @@ -25,7 +25,7 @@ describe('food', () => { } else { expectValidTranslationString(foodItem.textA); expectValidTranslationString(foodItem.textThe); - expectValidTranslationString(foodItem.target); + expect(foodItem.target).to.be.a('string'); } expectValidTranslationString(foodItem.text); expectValidTranslationString(foodItem.notes); diff --git a/test/content/schedule.test.js b/test/content/schedule.test.js index b7d4620c92..a585dc17a9 100644 --- a/test/content/schedule.test.js +++ b/test/content/schedule.test.js @@ -19,7 +19,7 @@ function validateMatcher (matcher, checkedDate) { expect(matcher.end).to.be.greaterThan(checkedDate); } -describe.only('Content Schedule', () => { +describe('Content Schedule', () => { beforeEach(() => { clearCachedMatchers(); }); From b713e10c1414d1d8caf77228d1c7c73c197c1eb3 Mon Sep 17 00:00:00 2001 From: Phillip Thelen Date: Wed, 8 May 2024 17:48:34 +0200 Subject: [PATCH 109/244] lint fixes --- .../src/components/shops/balanceInfo.vue | 2 +- .../components/static/contentScheduleFaq.vue | 19 +++++++++++++------ .../common/script/content/constants/events.js | 1 - 3 files changed, 14 insertions(+), 8 deletions(-) diff --git a/website/client/src/components/shops/balanceInfo.vue b/website/client/src/components/shops/balanceInfo.vue index 430d9daab0..d473034538 100644 --- a/website/client/src/components/shops/balanceInfo.vue +++ b/website/client/src/components/shops/balanceInfo.vue @@ -1,9 +1,9 @@