diff --git a/habitica-images b/habitica-images index dac6a71d1f..3cda1c1a27 160000 --- a/habitica-images +++ b/habitica-images @@ -1 +1 @@ -Subproject commit dac6a71d1fa2500bf8f3ec18bf917cbe91d82d64 +Subproject commit 3cda1c1a27ef51e735ea0f6edd848f001321ec0f diff --git a/migrations/archive/2023/20230522_pet_group_achievements.js b/migrations/archive/2023/20230522_pet_group_achievements.js new file mode 100644 index 0000000000..59c6fe7d1d --- /dev/null +++ b/migrations/archive/2023/20230522_pet_group_achievements.js @@ -0,0 +1,158 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20230522_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['Parrot-Base'] + && pets['Parrot-CottonCandyBlue'] + && pets['Parrot-CottonCandyPink'] + && pets['Parrot-Desert'] + && pets['Parrot-Golden'] + && pets['Parrot-Red'] + && pets['Parrot-Shade'] + && pets['Parrot-Skeleton'] + && pets['Parrot-White'] + && pets['Parrot-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['Triceratops-Base'] + && pets['Triceratops-CottonCandyBlue'] + && pets['Triceratops-CottonCandyPink'] + && pets['Triceratops-Desert'] + && pets['Triceratops-Golden'] + && pets['Triceratops-Red'] + && pets['Triceratops-Shade'] + && pets['Triceratops-Skeleton'] + && pets['Triceratops-White'] + && pets['Triceratops-Zombie'] + && pets['TRex-Base'] + && pets['TRex-CottonCandyBlue'] + && pets['TRex-CottonCandyPink'] + && pets['TRex-Desert'] + && pets['TRex-Golden'] + && pets['TRex-Red'] + && pets['TRex-Shade'] + && pets['TRex-Skeleton'] + && pets['TRex-White'] + && pets['TRex-Zombie'] + && pets['Pterodactyl-Base'] + && pets['Pterodactyl-CottonCandyBlue'] + && pets['Pterodactyl-CottonCandyPink'] + && pets['Pterodactyl-Desert'] + && pets['Pterodactyl-Golden'] + && pets['Pterodactyl-Red'] + && pets['Pterodactyl-Shade'] + && pets['Pterodactyl-Skeleton'] + && pets['Pterodactyl-White'] + && pets['Pterodactyl-Zombie'] + && pets['Owl-Base'] + && pets['Owl-CottonCandyBlue'] + && pets['Owl-CottonCandyPink'] + && pets['Owl-Desert'] + && pets['Owl-Golden'] + && pets['Owl-Red'] + && pets['Owl-Shade'] + && pets['Owl-Skeleton'] + && pets['Owl-White'] + && pets['Owl-Zombie'] + && pets['Velociraptor-Base'] + && pets['Velociraptor-CottonCandyBlue'] + && pets['Velociraptor-CottonCandyPink'] + && pets['Velociraptor-Desert'] + && pets['Velociraptor-Golden'] + && pets['Velociraptor-Red'] + && pets['Velociraptor-Shade'] + && pets['Velociraptor-Skeleton'] + && pets['Velociraptor-White'] + && pets['Velociraptor-Zombie'] + && pets['Penguin-Base'] + && pets['Penguin-CottonCandyBlue'] + && pets['Penguin-CottonCandyPink'] + && pets['Penguin-Desert'] + && pets['Penguin-Golden'] + && pets['Penguin-Red'] + && pets['Penguin-Shade'] + && pets['Penguin-Skeleton'] + && pets['Penguin-White'] + && pets['Penguin-Zombie'] + && pets['Falcon-Base'] + && pets['Falcon-CottonCandyBlue'] + && pets['Falcon-CottonCandyPink'] + && pets['Falcon-Desert'] + && pets['Falcon-Golden'] + && pets['Falcon-Red'] + && pets['Falcon-Shade'] + && pets['Falcon-Skeleton'] + && pets['Falcon-White'] + && pets['Falcon-Zombie'] + && pets['Peacock-Base'] + && pets['Peacock-CottonCandyBlue'] + && pets['Peacock-CottonCandyPink'] + && pets['Peacock-Desert'] + && pets['Peacock-Golden'] + && pets['Peacock-Red'] + && pets['Peacock-Shade'] + && pets['Peacock-Skeleton'] + && pets['Peacock-White'] + && pets['Peacock-Zombie']) { + set['achievements.dinosaurDynasty'] = 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('2023-04-15') }, + }; + + 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 d5f6262415..28b14e04c9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2281,23 +2281,12 @@ } }, "@sinonjs/fake-timers": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.0.2.tgz", - "integrity": "sha512-SwUDyjWnah1AaNl7kxsa7cfLhlTYoiyhDAIgyh+El30YvXs/o7OLXpYH88Zdhyx9JExKrmHDJ+10bwIcY80Jmw==", + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.2.0.tgz", + "integrity": "sha512-OPwQlEdg40HAj5KNF8WW6q2KG4Z+cBCZb3m4ninfTZKaBmbIJodviQsDBoYMPHkOyJJMHnOJo5j2+LKDOhOACg==", "dev": true, "requires": { - "@sinonjs/commons": "^2.0.0" - }, - "dependencies": { - "@sinonjs/commons": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", - "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - } + "@sinonjs/commons": "^3.0.0" } }, "@sinonjs/samsam": { @@ -13776,13 +13765,13 @@ } }, "sinon": { - "version": "15.0.4", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.0.4.tgz", - "integrity": "sha512-uzmfN6zx3GQaria1kwgWGeKiXSSbShBbue6Dcj0SI8fiCNFbiUDqKl57WFlY5lyhxZVUKmXvzgG2pilRQCBwWg==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-15.1.0.tgz", + "integrity": "sha512-cS5FgpDdE9/zx7no8bxROHymSlPLZzq0ChbbLk1DrxBfc+eTeBK3y8nIL+nu/0QeYydhhbLIr7ecHJpywjQaoQ==", "dev": true, "requires": { "@sinonjs/commons": "^3.0.0", - "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/fake-timers": "^10.2.0", "@sinonjs/samsam": "^8.0.0", "diff": "^5.1.0", "nise": "^5.1.4", @@ -14435,18 +14424,18 @@ } }, "stripe": { - "version": "12.5.0", - "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.5.0.tgz", - "integrity": "sha512-eDBh4bv+Uo+GdhjnQ246lM5KaOReoBzxFltgW0HJqco/QEAgSYxZPOpFbd5+gJnZlRqHSB5B+Zqw273SlbdPag==", + "version": "12.6.0", + "resolved": "https://registry.npmjs.org/stripe/-/stripe-12.6.0.tgz", + "integrity": "sha512-aJat4pGA6fr5DvuojRVXMaOCAw8ceS3UQM4ux1BMUViG686NmOAf1psEh6Wu+NFUifoy6kV7+4APq66OlTWtTw==", "requires": { "@types/node": ">=8.1.0", "qs": "^6.11.0" }, "dependencies": { "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", + "version": "6.11.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.2.tgz", + "integrity": "sha512-tDNIz22aBzCDxLtVH++VnTfzxlfeK5CbqohpSqpJgj1Wg/cQbStNAz3NuqCs5vV+pjBsK4x4pN9HlVh7rcYRiA==", "requires": { "side-channel": "^1.0.4" } diff --git a/package.json b/package.json index 9b82e60da7..b82b0ba176 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "remove-markdown": "^0.5.0", "rimraf": "^3.0.2", "short-uuid": "^4.2.2", - "stripe": "^12.5.0", + "stripe": "^12.6.0", "superagent": "^8.0.9", "universal-analytics": "^0.5.3", "useragent": "^2.1.9", @@ -122,7 +122,7 @@ "monk": "^7.3.4", "require-again": "^2.0.0", "run-rs": "^0.7.7", - "sinon": "^15.0.4", + "sinon": "^15.1.0", "sinon-chai": "^3.7.0", "sinon-stub-promise": "^4.0.0" }, diff --git a/website/client/src/assets/css/sprites/spritesmith-main.css b/website/client/src/assets/css/sprites/spritesmith-main.css index b3c0c74c78..271abcba15 100644 --- a/website/client/src/assets/css/sprites/spritesmith-main.css +++ b/website/client/src/assets/css/sprites/spritesmith-main.css @@ -138,6 +138,11 @@ width: 48px; height: 52px; } +.achievement-dinosaurDynasty2x { + background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-dinosaurDynasty2x.png'); + width: 68px; + height: 68px; +} .achievement-domesticated2x { background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/achievement-domesticated2x.png'); width: 60px; diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index 7650882af5..7e4aba8355 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -147,5 +147,8 @@ "achievementPolarProModalText": "You collected all the Polar Pets!", "achievementPlantParent": "Plant Parent", "achievementPlantParentText": "Has hatched all standard colors of Plant pets: Cactus and Treeling!", - "achievementPlantParentModalText": "You collected all the Plant Pets!" + "achievementPlantParentModalText": "You collected all the Plant Pets!", + "achievementDinosaurDynasty": "Dinosaur Dynasty", + "achievementDinosaurDynastyText": "Has hatched all standard colors of bird and dinosaur pets: Falcon, Owl, Parrot, Peacock, Penguin, Rooster, Pterodactyl, T-Rex, Triceratops, and Velociraptor!", + "achievementDinosaurDynastyModalText": "You collected all the bird and dinosaur pets!" } diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index dfc0e46df4..a7e843d23d 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -178,6 +178,11 @@ const animalSetAchievs = { titleKey: 'achievementBirdsOfAFeather', textKey: 'achievementBirdsOfAFeatherText', }, + dinosaurDynasty: { + icon: 'achievement-dinosaurDynasty', + titleKey: 'achievementDinosaurDynasty', + textKey: 'achievementDinosaurDynastyText', + }, domesticated: { icon: 'achievement-domesticated', titleKey: 'achievementDomesticated', diff --git a/website/common/script/content/constants/animalSetAchievements.js b/website/common/script/content/constants/animalSetAchievements.js index 6f6569ff5b..dc49609d12 100644 --- a/website/common/script/content/constants/animalSetAchievements.js +++ b/website/common/script/content/constants/animalSetAchievements.js @@ -26,6 +26,23 @@ const ANIMAL_SET_ACHIEVEMENTS = { achievementKey: 'birdsOfAFeather', notificationType: 'ACHIEVEMENT_ANIMAL_SET', }, + dinosaurDynasty: { + type: 'pet', + species: [ + 'Falcon', + 'Owl', + 'Parrot', + 'Peacock', + 'Penguin', + 'Rooster', + 'Pterodactyl', + 'TRex', + 'Triceratops', + 'Velociraptor', + ], + achievementKey: 'dinosaurDynasty', + notificationType: 'ACHIEVEMENT_ANIMAL_SET', + }, domesticated: { type: 'pet', species: [ diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index d02f8b2c2a..d28155e6e5 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -222,6 +222,7 @@ function _getBasicAchievements (user, language) { _addSimple(result, user, { path: 'boneToPick', language }); _addSimple(result, user, { path: 'polarPro', language }); _addSimple(result, user, { path: 'plantParent', language }); + _addSimple(result, user, { path: 'dinosaurDynasty', language }); _addSimpleWithMasterCount(result, user, { path: 'beastMaster', language }); _addSimpleWithMasterCount(result, user, { path: 'mountMaster', language }); diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index a0148bc7dc..8a98ee17ce 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -154,6 +154,7 @@ export default new Schema({ boneToPick: Boolean, polarPro: Boolean, plantParent: Boolean, + dinosaurDynasty: Boolean, // Onboarding Guide createdTask: Boolean, completedTask: Boolean,