From 3bc82a669242b3d19b851c212d78247bc0d328de Mon Sep 17 00:00:00 2001 From: Natalie L <78037386+CuriousMagpie@users.noreply.github.com> Date: Mon, 31 Oct 2022 15:19:56 -0400 Subject: [PATCH] chore(content): add Bone to Pick achievement (#14318) * chore(content): add Bone to Pick achievement * chore(content): update spritesmith-main.css * chore(content): add more bone picking * chore(content): more bone picking * chore(content): bone picking * chore(content): i gotta bone to pick here * chore(content): final bone picking * chore: add migration script * chore: update habitica-images Co-authored-by: SabreCat --- .../20221031_pet_set_group_achievements.js | 119 ++++++++++++++++++ .../client/src/components/notifications.vue | 18 +++ website/common/locales/en/achievements.json | 5 +- website/common/script/content/achievements.js | 9 ++ .../common/script/content/constants/index.js | 1 + .../constants/petCompleteSetAchievements.js | 11 ++ website/common/script/content/index.js | 2 + website/common/script/libs/achievements.js | 1 + website/common/script/ops/hatch.js | 28 +++++ website/server/models/user/schema.js | 1 + website/server/models/userNotification.js | 1 + 11 files changed, 195 insertions(+), 1 deletion(-) create mode 100644 migrations/archive/2022/20221031_pet_set_group_achievements.js create mode 100644 website/common/script/content/constants/petCompleteSetAchievements.js diff --git a/migrations/archive/2022/20221031_pet_set_group_achievements.js b/migrations/archive/2022/20221031_pet_set_group_achievements.js new file mode 100644 index 0000000000..9591df1493 --- /dev/null +++ b/migrations/archive/2022/20221031_pet_set_group_achievements.js @@ -0,0 +1,119 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20221031_pet_set_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['Wolf-Skeleton'] + && pets['TigerCub-Skeleton'] + && pets['PandaCub-Skeleton'] + && pets['LionCub-Skeleton'] + && pets['Fox-Skeleton'] + && pets['FlyingPig-Skeleton'] + && pets['Dragon-Skeleton'] + && pets['Cactus-Skeleton'] + && pets['BearCub-Skeleton'] + && pets['Gryphon-Skeleton'] + && pets['Hedgehog-Skeleton'] + && pets['Deer-Skeleton'] + && pets['Egg-Skeleton'] + && pets['Rat-Skeleton'] + && pets['Octopus-Skeleton'] + && pets['Seahorse-Skeleton'] + && pets['Parrot-Skeleton'] + && pets['Rooster-Skeleton'] + && pets['Spider-Skeleton'] + && pets['Owl-Skeleton'] + && pets['Penguin-Skeleton'] + && pets['TRex-Skeleton'] + && pets['Rock-Skeleton'] + && pets['Bunny-Skeleton'] + && pets['Slime-Skeleton'] + && pets['Sheep-Skeleton'] + && pets['Cuttlefish-Skeleton'] + && pets['Whale-Skeleton'] + && pets['Cheetah-Skeleton'] + && pets['Horse-Skeleton'] + && pets['Frog-Skeleton'] + && pets['Snake-Skeleton'] + && pets['Unicorn-Skeleton'] + && pets['Sabretooth-Skeleton'] + && pets['Monkey-Skeleton'] + && pets['Snail-Skeleton'] + && pets['Falcon-Skeleton'] + && pets['Treeling-Skeleton'] + && pets['Axolotl-Skeleton'] + && pets['Turtle-Skeleton'] + && pets['Armadillo-Skeleton'] + && pets['Cow-Skeleton'] + && pets['Beetle-Skeleton'] + && pets['Ferret-Skeleton'] + && pets['Sloth-Skeleton'] + && pets['Triceratops-Skeleton'] + && pets['GuineaPig-Skeleton'] + && pets['Peacock-Skeleton'] + && pets['Butterfly-Skeleton'] + && pets['Nudibranch-Skeleton'] + && pets['Hippo-Skeleton'] + && pets['Yarn-Skeleton'] + && pets['Pterodactyl-Skeleton'] + && pets['Badger-Skeleton'] + && pets['Squirrel-Skeleton'] + && pets['SeaSerpent-Skeleton'] + && pets['Kangaroo-Skeleton'] + && pets['Alligator-Skeleton'] + && pets['Velociraptor-Skeleton'] + && pets['Dolphin-Skeleton'] + && pets['Robot-Skeleton']) { + set['achievements.boneToPick'] = 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('2022-01-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/website/client/src/components/notifications.vue b/website/client/src/components/notifications.vue index 4a0f937ebc..071a2e85e3 100644 --- a/website/client/src/components/notifications.vue +++ b/website/client/src/components/notifications.vue @@ -241,6 +241,14 @@ const NOTIFICATIONS = { achievement: 'mountColorAchievs', }, }, + ACHIEVEMENT_PET_SET_COMPLETE: { + achievement: true, + label: $t => `${$t('achievement')}: ${$t('achievementPetSetComplete')}`, + modalId: 'generic-achievement', + data: { + achievement: 'petSetCompleteAchievs', + }, + }, }; export default { @@ -313,6 +321,7 @@ export default { 'ACHIEVEMENT_ANIMAL_SET', 'ACHIEVEMENT_PET_COLOR', 'ACHIEVEMENT_MOUNT_COLOR', + 'ACHIEVEMENT_PET_SET_COMPLETE', ].forEach(type => { handledNotifications[type] = true; }); @@ -772,6 +781,15 @@ export default { Vue.set(this.user.achievements, achievement, true); break; } + case 'ACHIEVEMENT_PET_SET_COMPLETE': { + const { achievement } = notification.data; + const upperCaseAchievement = achievement.charAt(0).toUpperCase() + achievement.slice(1); + const achievementTitleKey = `achievement${upperCaseAchievement}`; + NOTIFICATIONS.ACHIEVEMENT_PET_SET_COMPLETE.label = $t => `${$t('achievement')}: ${$t(achievementTitleKey)}`; + this.showNotificationWithModal(notification); + Vue.set(this.user.achievements, achievement, true); + break; + } case 'ACHIEVEMENT': { // generic achievement const { achievement } = notification.data; const upperCaseAchievement = achievement.charAt(0).toUpperCase() + achievement.slice(1); diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index 2c063088e0..b4b577280b 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -138,5 +138,8 @@ "achievementGroupsBeta2022ModalText":"You and your groups helped Habitica by testing and providing feedback!", "achievementWoodlandWizard": "Woodland Wizard", "achievementWoodlandWizardText": "Has hatched all standard colors of forest creatures: Badger, Bear, Deer, Fox, Frog, Hedgehog, Owl, Snail, Squirrel, and Treeling!", - "achievementWoodlandWizardModalText": "You collected all the forest pets!" + "achievementWoodlandWizardModalText": "You collected all the forest pets!", + "achievementBoneToPick": "Bone to Pick", + "achievementBoneToPickText": "Has hatched all the Classic and Quest Skeleton Pets!", + "achievementBoneToPickModalText": "You collected all the Classic and Quest Skeleton Pets!" } diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index dd0de4b14b..12849bf637 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -310,6 +310,15 @@ const mountColorAchievs = { }; Object.assign(achievementsData, mountColorAchievs); +const petSetCompleteAchievs = { + boneToPick: { + icon: 'achievement-boneToPick', + titleKey: 'achievementBoneToPick', + textKey: 'achievementBoneToPickText', + }, +}; +Object.assign(achievementsData, petSetCompleteAchievs); + const onboardingAchievs = { createdTask: { icon: 'achievement-createdTask', diff --git a/website/common/script/content/constants/index.js b/website/common/script/content/constants/index.js index b9200cac6b..64c86f22a3 100644 --- a/website/common/script/content/constants/index.js +++ b/website/common/script/content/constants/index.js @@ -32,6 +32,7 @@ export { default as SEASONAL_SETS } from './seasonalSets'; export { default as ANIMAL_COLOR_ACHIEVEMENTS } from './animalColorAchievements'; export { default as ANIMAL_SET_ACHIEVEMENTS } from './animalSetAchievements'; export { default as QUEST_SERIES_ACHIEVEMENTS } from './questSeriesAchievements'; +export { default as PET_SET_COMPLETE_ACHIEVEMENTS } from './petCompleteSetAchievements'; // eslint-disable-line import/no-cycle export { default as STABLE_ACHIEVEMENTS } from './stableAchievements'; export { default as ITEM_LIST } from './itemList'; export { default as QUEST_SERIES } from '../quests/series'; diff --git a/website/common/script/content/constants/petCompleteSetAchievements.js b/website/common/script/content/constants/petCompleteSetAchievements.js new file mode 100644 index 0000000000..773908c879 --- /dev/null +++ b/website/common/script/content/constants/petCompleteSetAchievements.js @@ -0,0 +1,11 @@ +// this achievement covers all pets of a specific type--classic & quest + +const PET_SET_COMPLETE_ACHIEVEMENTS = [ + { + color: 'Skeleton', + petAchievement: 'boneToPick', + petNotificationType: 'ACHIEVEMENT_PET_SET_COMPLETE', + }, +]; + +export default PET_SET_COMPLETE_ACHIEVEMENTS; diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 474a9f64db..e4be48f1d7 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -13,6 +13,7 @@ import { ANIMAL_COLOR_ACHIEVEMENTS, ANIMAL_SET_ACHIEVEMENTS, STABLE_ACHIEVEMENTS, + PET_SET_COMPLETE_ACHIEVEMENTS, } from './constants'; import achievements from './achievements'; @@ -43,6 +44,7 @@ api.questSeriesAchievements = QUEST_SERIES_ACHIEVEMENTS; api.animalColorAchievements = ANIMAL_COLOR_ACHIEVEMENTS; api.animalSetAchievements = ANIMAL_SET_ACHIEVEMENTS; api.stableAchievements = STABLE_ACHIEVEMENTS; +api.petSetCompleteAchievs = PET_SET_COMPLETE_ACHIEVEMENTS; api.quests = quests; api.questsByLevel = questsByLevel; diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index cbde3305ae..52af6e2ce7 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -219,6 +219,7 @@ function _getBasicAchievements (user, language) { _addSimple(result, user, { path: 'birdsOfAFeather', language }); _addSimple(result, user, { path: 'reptacularRumble', language }); _addSimple(result, user, { path: 'woodlandWizard', language }); + _addSimple(result, user, { path: 'boneToPick', language }); _addSimpleWithMasterCount(result, user, { path: 'beastMaster', language }); _addSimpleWithMasterCount(result, user, { path: 'mountMaster', language }); diff --git a/website/common/script/ops/hatch.js b/website/common/script/ops/hatch.js index 0fec8d3833..ae425ab8cc 100644 --- a/website/common/script/ops/hatch.js +++ b/website/common/script/ops/hatch.js @@ -113,6 +113,34 @@ export default function hatch (user, req = {}, analytics) { }); } + if (content.dropEggs[egg] || content.questEggs[egg]) { + forEach(content.petSetCompleteAchievs, achievement => { + if (hatchingPotion !== achievement.color) return; + if (!user.achievements[achievement.petAchievement]) { + const dropPetIndex = findIndex( + keys(content.dropEggs), + animal => !user.items.pets[`${animal}-${achievement.color}`] || user.items.pets[`${animal}-${achievement.color}`] <= 0, + ); + const questPetIndex = findIndex( + keys(content.questEggs), + animal => !user.items.pets[`${animal}-${achievement.color}`] || user.items.pets[`${animal}-${achievement.color}`] <= 0, + ); + if (dropPetIndex === -1 && questPetIndex === -1) { + user.achievements[achievement.petAchievement] = true; + if (user.addNotification) { + const achievementString = `achievement${upperFirst(achievement.petAchievement)}`; + user.addNotification(achievement.petNotificationType, { + label: `${'achievement'}: ${achievementString}`, + achievement: achievement.petAchievement, + message: `${i18n.t('modalAchievement')} ${i18n.t(achievementString)}`, + modalText: i18n.t(`${achievementString}ModalText`), + }); + } + } + } + }); + } + if (analytics && moment().diff(user.auth.timestamps.created, 'days') < 7) { analytics.track('pet hatch', { uuid: user._id, diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index 3e0b4b84c0..52bc884ef8 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -151,6 +151,7 @@ export default new Schema({ birdsOfAFeather: Boolean, reptacularRumble: Boolean, woodlandWizard: Boolean, + boneToPick: Boolean, // Onboarding Guide createdTask: Boolean, completedTask: Boolean, diff --git a/website/server/models/userNotification.js b/website/server/models/userNotification.js index 325fc672f5..ea9636a1b1 100644 --- a/website/server/models/userNotification.js +++ b/website/server/models/userNotification.js @@ -46,6 +46,7 @@ const NOTIFICATION_TYPES = [ 'ACHIEVEMENT_ANIMAL_SET', 'ACHIEVEMENT_PET_COLOR', 'ACHIEVEMENT_MOUNT_COLOR', + 'ACHIEVEMENT_PET_SET_COMPLETE', // Deprecated notification types. Can be removed once old data is cleaned out 'BOSS_DAMAGE', // deprecated 'ACHIEVEMENT_ALL_YOUR_BASE', // deprecated