diff --git a/habitica-images b/habitica-images index 704d81edf8..db1c63db00 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit 704d81edf8d5281fa370923ad3a63a7da4e320c7 +Subproject commit db1c63db002487038e443e2c8872dfad80d136ee diff --git a/migrations/archive/2022/20220201_pet_group_achievements.js b/migrations/archive/2022/20220201_pet_group_achievements.js new file mode 100644 index 0000000000..86a2617660 --- /dev/null +++ b/migrations/archive/2022/20220201_pet_group_achievements.js @@ -0,0 +1,178 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20220201_pet_group_achievements'; +import { model as User } from '../../../website/server/models/user'; + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count++; + + const set = { + migration: MIGRATION_NAME, + }; + + if (user && user.items && user.items.pets) { + const pets = user.items.pets; + if (pets['FlyingPig-Base'] + && pets['FlyingPig-CottonCandyBlue'] + && pets['FlyingPig-CottonCandyPink'] + && pets['FlyingPig-Desert'] + && pets['FlyingPig-Golden'] + && pets['FlyingPig-Red'] + && pets['FlyingPig-Shade'] + && pets['FlyingPig-Skeleton'] + && pets['FlyingPig-White'] + && pets['FlyingPig-Zombie'] + && pets['Snake-Base'] + && pets['Snake-CottonCandyBlue'] + && pets['Snake-CottonCandyPink'] + && pets['Snake-Desert'] + && pets['Snake-Golden'] + && pets['Snake-Red'] + && pets['Snake-Shade'] + && pets['Snake-Skeleton'] + && pets['Snake-White'] + && pets['Snake-Zombie'] + && pets['Sheep-Base'] + && pets['Sheep-CottonCandyBlue'] + && pets['Sheep-CottonCandyPink'] + && pets['Sheep-Desert'] + && pets['Sheep-Golden'] + && pets['Sheep-Red'] + && pets['Sheep-Shade'] + && pets['Sheep-Skeleton'] + && pets['Sheep-White'] + && pets['Sheep-Zombie'] + && pets['Rooster-Base'] + && pets['Rooster-CottonCandyBlue'] + && pets['Rooster-CottonCandyPink'] + && pets['Rooster-Desert'] + && pets['Rooster-Golden'] + && pets['Rooster-Red'] + && pets['Rooster-Shade'] + && pets['Rooster-Skeleton'] + && pets['Rooster-White'] + && pets['Rooster-Zombie'] + && pets['Rat-Base'] + && pets['Rat-CottonCandyBlue'] + && pets['Rat-CottonCandyPink'] + && pets['Rat-Desert'] + && pets['Rat-Golden'] + && pets['Rat-Red'] + && pets['Rat-Shade'] + && pets['Rat-Skeleton'] + && pets['Rat-White'] + && pets['Rat-Zombie'] + && pets['Bunny-Base'] + && pets['Bunny-CottonCandyBlue'] + && pets['Bunny-CottonCandyPink'] + && pets['Bunny-Desert'] + && pets['Bunny-Golden'] + && pets['Bunny-Red'] + && pets['Bunny-Shade'] + && pets['Bunny-Skeleton'] + && pets['Bunny-White'] + && pets['Bunny-Zombie'] + && pets['Horse-Base'] + && pets['Horse-CottonCandyBlue'] + && pets['Horse-CottonCandyPink'] + && pets['Horse-Desert'] + && pets['Horse-Golden'] + && pets['Horse-Red'] + && pets['Horse-Shade'] + && pets['Horse-Skeleton'] + && pets['Horse-White'] + && pets['Horse-Zombie'] + && pets['Cow-Base'] + && pets['Cow-CottonCandyBlue'] + && pets['Cow-CottonCandyPink'] + && pets['Cow-Desert'] + && pets['Cow-Golden'] + && pets['Cow-Red'] + && pets['Cow-Shade'] + && pets['Cow-Skeleton'] + && pets['Cow-White'] + && pets['Cow-Zombie'] + && pets['Monkey-Base'] + && pets['Monkey-CottonCandyBlue'] + && pets['Monkey-CottonCandyPink'] + && pets['Monkey-Desert'] + && pets['Monkey-Golden'] + && pets['Monkey-Red'] + && pets['Monkey-Shade'] + && pets['Monkey-Skeleton'] + && pets['Monkey-White'] + && pets['Monkey-Zombie'] + && pets['Wolf-Base'] + && pets['Wolf-CottonCandyBlue'] + && pets['Wolf-CottonCandyPink'] + && pets['Wolf-Desert'] + && pets['Wolf-Golden'] + && pets['Wolf-Red'] + && pets['Wolf-Shade'] + && pets['Wolf-Skeleton'] + && pets['Wolf-White'] + && pets['Wolf-Zombie'] + && pets['Tiger-Base'] + && pets['Tiger-CottonCandyBlue'] + && pets['Tiger-CottonCandyPink'] + && pets['Tiger-Desert'] + && pets['Tiger-Golden'] + && pets['Tiger-Red'] + && pets['Tiger-Shade'] + && pets['Tiger-Skeleton'] + && pets['Tiger-White'] + && pets['Tiger-Zombie'] + && pets['Dragon-Base'] + && pets['Dragon-CottonCandyBlue'] + && pets['Dragon-CottonCandyPink'] + && pets['Dragon-Desert'] + && pets['Dragon-Golden'] + && pets['Dragon-Red'] + && pets['Dragon-Shade'] + && pets['Dragon-Skeleton'] + && pets['Dragon-White'] + && pets['Dragon-Zombie']) { + set['achievements.zodiacZookeeper'] = true; + } + } + + if (count % progressCount === 0) console.warn(`${count} ${user._id}`); + + return await User.update({ _id: user._id }, { $set: set }).exec(); +} + +export default async function processUsers () { + let query = { + // migration: { $ne: MIGRATION_NAME }, + 'auth.timestamps.loggedin': { $gt: new Date('2021-08-01') }, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1]._id, + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/package-lock.json b/package-lock.json index 2e629dd484..90f760d46a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "4.219.1", + "version": "4.220.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 320735f5b5..1ed03e1cae 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "4.219.1", + "version": "4.220.0", "main": "./website/server/index.js", "dependencies": { "@babel/core": "^7.16.12", diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css index befc3132f7..dbe6fc54f7 100644 --- a/website/client/src/assets/css/sprites/spritesmith-main.css +++ b/website/client/src/assets/css/sprites/spritesmith-main.css @@ -450,8 +450,8 @@ } .achievement-zodiac2x { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-zodiac2x.png'); - width: 68px; - height: 68px; + width: 60px; + height: 64px; } .background_afternoon_picnic { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_afternoon_picnic.png'); @@ -25078,6 +25078,21 @@ width: 114px; height: 90px; } +.shop_eyewear_mystery_202202 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_mystery_202202.png'); + width: 68px; + height: 68px; +} +.shop_head_mystery_202202 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202202.png'); + width: 68px; + height: 68px; +} +.shop_set_mystery_202202 { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202202.png'); + width: 68px; + height: 68px; +} .broad_armor_mystery_301404 { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_mystery_301404.png'); width: 90px; @@ -33343,21 +33358,11 @@ width: 68px; height: 68px; } -.shop_eyewear_mystery_202202 { - background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_eyewear_mystery_202202.png'); - width: 68px; - height: 68px; -} .shop_gem { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_gem.png'); width: 68px; height: 68px; } -.shop_head_mystery_202202 { - background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_head_mystery_202202.png'); - width: 68px; - height: 68px; -} .shop_opaquePotion { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_opaquePotion.png'); width: 68px; @@ -33388,11 +33393,6 @@ width: 68px; height: 68px; } -.shop_set_mystery_202202 { - background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_set_mystery_202202.png'); - width: 68px; - height: 68px; -} .shop_shinySeed { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shop_shinySeed.png'); width: 68px; diff --git a/website/client/src/components/notifications.vue b/website/client/src/components/notifications.vue index 567bb098b8..2960b0889e 100644 --- a/website/client/src/components/notifications.vue +++ b/website/client/src/components/notifications.vue @@ -442,6 +442,14 @@ const NOTIFICATIONS = { achievement: 'shadeOfItAll', }, }, + ACHIEVEMENT_ZODIAC_ZOOKEEPER: { + achievement: true, + label: $t => `${$t('achievement')}: ${$t('achievementZodiacZookeeper')}`, + modalId: 'generic-achievement', + data: { + achievement: 'zodiacZookeeper', + }, + }, }; export default { @@ -505,7 +513,7 @@ export default { 'ACHIEVEMENT_BONE_COLLECTOR', 'ACHIEVEMENT_SKELETON_CREW', 'ACHIEVEMENT_SEEING_RED', 'ACHIEVEMENT_RED_LETTER_DAY', 'ACHIEVEMENT_LEGENDARY_BESTIARY', 'ACHIEVEMENT_SEASONAL_SPECIALIST', 'ACHIEVEMENT_VIOLETS_ARE_BLUE', 'ACHIEVEMENT_WILD_BLUE_YONDER', 'ACHIEVEMENT_DOMESTICATED', - 'ACHIEVEMENT_SHADY_CUSTOMER', 'ACHIEVEMENT_SHADE_OF_IT_ALL', + 'ACHIEVEMENT_SHADY_CUSTOMER', 'ACHIEVEMENT_SHADE_OF_IT_ALL', 'ACHIEVEMENT_ZODIAC_ZOOKEEPER', ].forEach(type => { handledNotifications[type] = true; }); @@ -939,6 +947,7 @@ export default { case 'ACHIEVEMENT_DOMESTICATED': case 'ACHIEVEMENT_SHADY_CUSTOMER': case 'ACHIEVEMENT_SHADE_OF_IT_ALL': + case 'ACHIEVEMENT_ZODIAC_ZOOKEEPER': case 'GENERIC_ACHIEVEMENT': this.showNotificationWithModal(notification); break; diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index d079d7e953..a5ab0beb81 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -123,5 +123,8 @@ "achievementShadyCustomerModalText": "You collected all the Shade Pets!", "achievementShadeOfItAll": "The Shade of It All", "achievementShadeOfItAllText": "Has tamed all Shade Mounts.", - "achievementShadeOfItAllModalText": "You tamed all the Shade Mounts!" + "achievementShadeOfItAllModalText": "You tamed all the Shade Mounts!", + "achievementZodiacZookeeper": "Zodiac Zookeeper", + "achievementZodiacZookeeperText": "Has hatched all the zodiac pets: Rat, Cow, Bunny, Snake, Horse, Sheep, Monkey, Rooster, Wolf, Tiger, Flying Pig, and Dragon!", + "achievementZodiacZookeeperModalText": "You collected all the zodiac pets!" } diff --git a/website/common/locales/en/gear.json b/website/common/locales/en/gear.json index 07026f8d9b..da88c73888 100644 --- a/website/common/locales/en/gear.json +++ b/website/common/locales/en/gear.json @@ -1835,6 +1835,8 @@ "headMystery202111Notes": "A fine and fancy hat, with goggles that let you see through time. Pretty cool, right? Confers no benefit. November 2021 Subscriber Item.", "headMystery202112Text": "Antarctic Undine Crown", "headMystery202112Notes": "This frozen crown shimmers like the hidden depths of an iceberg. Confers no benefit. December 2021 Subscriber Item.", + "headMystery202202Text":"Turquoise Twintails", + "headMystery202202Notes":"You gotta have blue hair! Confers no benefit. February 2022 Subscriber Item.", "headMystery301404Text": "Fancy Top Hat", "headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.", "headMystery301405Text": "Basic Top Hat", @@ -2708,6 +2710,8 @@ "eyewearMystery202108Notes": "Stare down your enemies (or your biggest tasks!) with these and they don't stand a chance. Confers no benefit. August 2021 Subscriber Item.", "eyewearMystery202201Text": "Midnight Merrymaker's Mask", "eyewearMystery202201Notes": "Ring in the new year with an air of mystery in this stylish feathered mask. Confers no benefit. January 2022 Subscriber Item.", + "eyewearMystery202202Text":"Turquoise Eyes with Blush", + "eyewearMystery202202Notes":"Cheerful singing brings color to your cheeks. Confers no benefit. February 2022 Subscriber Item", "eyewearMystery301404Text": "Eyewear Goggles", "eyewearMystery301404Notes": "No eyewear could be fancier than a pair of goggles - except, perhaps, for a monocle. Confers no benefit. April 3015 Subscriber Item.", "eyewearMystery301405Text": "Monocle", diff --git a/website/common/locales/en/subscriber.json b/website/common/locales/en/subscriber.json index 592b4140e1..17df25205f 100644 --- a/website/common/locales/en/subscriber.json +++ b/website/common/locales/en/subscriber.json @@ -130,6 +130,7 @@ "mysterySet202111": "Cosmic Chronomancer Set", "mysterySet202112": "Antarctic Undine Set", "mysterySet202201": "Midnight Merrymaker Set", + "mysterySet202202": "Turquoise Twintails Set", "mysterySet301404": "Steampunk Standard Set", "mysterySet301405": "Steampunk Accessories Set", "mysterySet301703": "Peacock Steampunk Set", diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index 945e8f3596..7d798eb638 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -267,6 +267,11 @@ const basicAchievs = { titleKey: 'achievementShadeOfItAll', textKey: 'achievementShadeOfItAllText', }, + zodiacZookeeper: { + icon: 'achievement-zodiac', + titleKey: 'achievementZodiacZookeeper', + textKey: 'achievementZodiacZookeeperText', + }, }; Object.assign(achievementsData, basicAchievs); diff --git a/website/common/script/content/constants/animalSetAchievements.js b/website/common/script/content/constants/animalSetAchievements.js index 6119918d07..5bdd8e039d 100644 --- a/website/common/script/content/constants/animalSetAchievements.js +++ b/website/common/script/content/constants/animalSetAchievements.js @@ -26,6 +26,25 @@ const ANIMAL_SET_ACHIEVEMENTS = { achievementKey: 'domesticated', notificationType: 'ACHIEVEMENT_DOMESTICATED', }, + zodiacZookeeper: { + type: 'pet', + species: [ + 'Rat', + 'Cow', + 'Bunny', + 'Snake', + 'Horse', + 'Sheep', + 'Monkey', + 'Rooster', + 'Wolf', + 'TigerCub', + 'FlyingPig', + 'Dragon', + ], + achievementKey: 'zodiacZookeeper', + notificationType: 'ACHIEVEMENT_ZODIAC_ZOOKEEPER', + }, }; export default ANIMAL_SET_ACHIEVEMENTS; diff --git a/website/common/script/content/gear/sets/mystery.js b/website/common/script/content/gear/sets/mystery.js index c35936c011..81b3549663 100644 --- a/website/common/script/content/gear/sets/mystery.js +++ b/website/common/script/content/gear/sets/mystery.js @@ -111,6 +111,7 @@ const eyewear = { 201907: { }, 202108: { }, 202201: { }, + 202202: { }, 301404: { }, 301405: { }, 301703: { }, @@ -181,6 +182,7 @@ const head = { 202110: { }, 202111: { }, 202112: { }, + 202202: { }, 301404: { }, 301405: { }, 301703: { }, diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 4784651deb..355863c8dc 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -187,7 +187,7 @@ api.specialMounts = stable.specialMounts; api.mountInfo = stable.mountInfo; // For seasonal events, change this constant: -const FOOD_SEASON = moment().isBefore('2021-02-01T20:00-05:00') ? 'Cake' : 'Normal'; +const FOOD_SEASON = moment().isBefore('2022-02-02T20:00-05:00') ? 'Cake' : 'Normal'; api.food = { Meat: { diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index 59deb1e7f6..0718d63d40 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -215,6 +215,7 @@ function _getBasicAchievements (user, language) { _addSimple(result, user, { path: 'domesticated', language }); _addSimple(result, user, { path: 'shadyCustomer', language }); _addSimple(result, user, { path: 'shadeOfItAll', language }); + _addSimple(result, user, { path: 'zodiacZookeeper', language }); _addSimpleWithMasterCount(result, user, { path: 'beastMaster', language }); _addSimpleWithMasterCount(result, user, { path: 'mountMaster', language }); diff --git a/website/server/models/user/hooks.js b/website/server/models/user/hooks.js index 980aeebce1..ad3a2ec323 100644 --- a/website/server/models/user/hooks.js +++ b/website/server/models/user/hooks.js @@ -144,7 +144,7 @@ function _setUpNewUser (user) { user.items.quests.dustbunnies = 1; user.purchased.background.violet = true; user.preferences.background = 'violet'; - if (moment().isBefore('2022-02-01T20:00-05:00')) { + if (moment().isBefore('2022-02-02T20:00-05:00')) { user.migration = '20220131_habit_birthday'; user.items.gear.owned.armor_special_birthday = true; user.items.gear.equipped.armor = 'armor_special_birthday'; diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index d61a07ae81..1525f4f434 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -147,6 +147,7 @@ export default new Schema({ domesticated: Boolean, shadyCustomer: Boolean, shadeOfItAll: Boolean, + zodiacZookeeper: Boolean, // Onboarding Guide createdTask: Boolean, completedTask: Boolean, diff --git a/website/server/models/userNotification.js b/website/server/models/userNotification.js index cafb80ce4c..4171b14cbe 100644 --- a/website/server/models/userNotification.js +++ b/website/server/models/userNotification.js @@ -70,6 +70,7 @@ const NOTIFICATION_TYPES = [ 'ACHIEVEMENT_DOMESTICATED', 'ACHIEVEMENT_SHADY_CUSTOMER', 'ACHIEVEMENT_SHADE_OF_IT_ALL', + 'ACHIEVEMENT_ZODIAC_ZOOKEEPER', 'ACHIEVEMENT', // generic achievement notification, details inside `notification.data` 'DROP_CAP_REACHED', ];