feat(content): new pet color achievements

This commit is contained in:
Sabe Jones
2019-09-17 16:10:32 -05:00
parent 7425120759
commit 7d732b5612
17 changed files with 225 additions and 47 deletions

View File

@@ -0,0 +1,104 @@
/* eslint-disable no-console */
const MIGRATION_NAME = '20190917_pet_color_achievements';
import { model as User } from '../../../website/server/models/user';
const progressCount = 1000;
let count = 0;
async function updateUser (user) {
count++;
let set = {
migration: MIGRATION_NAME,
};
if (user && user.items && user.items.pets) {
const pets = user.items.pets;
if (pets['Wolf-Base'] > 0
&& pets['TigerCub-Base'] > 0
&& pets['PandaCub-Base'] > 0
&& pets['LionCub-Base'] > 0
&& pets['Fox-Base'] > 0
&& pets['FlyingPig-Base'] > 0
&& pets['Dragon-Base'] > 0
&& pets['Cactus-Base'] > 0
&& pets['BearCub-Base'] > 0) {
set['achievements.backToBasics'] = true;
}
if (pets['Wolf-Desert'] > 0
&& pets['TigerCub-Desert'] > 0
&& pets['PandaCub-Desert'] > 0
&& pets['LionCub-Desert'] > 0
&& pets['Fox-Desert'] > 0
&& pets['FlyingPig-Desert'] > 0
&& pets['Dragon-Desert'] > 0
&& pets['Cactus-Desert'] > 0
&& pets['BearCub-Desert'] > 0) {
set['achievements.dustDevil'] = true;
}
}
if (user && user.items && user.items.mounts) {
const mounts = user.items.mounts;
if (mounts['Wolf-Base']
&& mounts['TigerCub-Base']
&& mounts['PandaCub-Base']
&& mounts['LionCub-Base']
&& mounts['Fox-Base']
&& mounts['FlyingPig-Base']
&& mounts['Dragon-Base']
&& mounts['Cactus-Base']
&& mounts['BearCub-Base'] ) {
set['achievements.allYourBase'] = true;
}
if (mounts['Wolf-Desert']
&& mounts['TigerCub-Desert']
&& mounts['PandaCub-Desert']
&& mounts['LionCub-Desert']
&& mounts['Fox-Desert']
&& mounts['FlyingPig-Desert']
&& mounts['Dragon-Desert']
&& mounts['Cactus-Desert']
&& mounts['BearCub-Desert'] ) {
set['achievements.aridAuthority'] = true;
}
}
if (count % progressCount === 0) console.warn(`${count} ${user._id}`);
return await User.update({ _id: user._id }, { $set: set }).exec();
}
module.exports = async function processUsers () {
let query = {
migration: { $ne: MIGRATION_NAME },
'auth.timestamps.loggedin': { $gt: new Date('2019-09-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
}
};

View File

@@ -187,6 +187,24 @@ describe('shared.ops.feed', () => {
expect(user.achievements.allYourBase).to.eql(true);
});
it('awards Arid Authority achievement', () => {
user.items.pets['Wolf-Spooky'] = 5;
user.items.food.Milk = 2;
user.items.mounts = {
'Wolf-Desert': true,
'TigerCub-Desert': true,
'PandaCub-Desert': true,
'LionCub-Desert': true,
'Fox-Desert': true,
'FlyingPig-Desert': true,
'Dragon-Desert': true,
'Cactus-Desert': true,
'BearCub-Desert': true,
};
feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}});
expect(user.achievements.aridAuthority).to.eql(true);
});
it('evolves the pet into a mount when feeding user.items.pets[pet] >= 50', () => {
user.items.pets['Wolf-Base'] = 49;
user.items.food.Milk = 2;

View File

@@ -177,6 +177,24 @@ describe('shared.ops.hatch', () => {
hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}});
expect(user.achievements.backToBasics).to.eql(true);
});
it('awards Dust Devil achievement', () => {
user.items.pets = {
'Wolf-Desert': 5,
'TigerCub-Desert': 5,
'PandaCub-Desert': 10,
'LionCub-Desert': 5,
'Fox-Desert': 5,
'FlyingPig-Desert': 5,
'Dragon-Desert': 5,
'Cactus-Desert': 15,
'BearCub-Desert': 5,
};
user.items.eggs = {Wolf: 1};
user.items.hatchingPotions = {Spooky: 1};
hatch(user, {params: {egg: 'Wolf', hatchingPotion: 'Spooky'}});
expect(user.achievements.dustDevil).to.eql(true);
});
});
});
});

View File

@@ -165,6 +165,16 @@ const NOTIFICATIONS = {
label: ($t) => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
modalId: 'generic-achievement',
},
ACHIEVEMENT_DUST_DEVIL: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementDustDevil')}`,
modalId: 'generic-achievement',
},
ACHIEVEMENT_ARID_AUTHORITY: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementAridAuthority')}`,
modalId: 'generic-achievement',
},
};
export default {
@@ -220,7 +230,7 @@ export default {
'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT',
'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL',
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
'GENERIC_ACHIEVEMENT',
'ACHIEVEMENT_DUST_DEVIL', 'ACHIEVEMENT_ARID_AUTHORITY', 'GENERIC_ACHIEVEMENT',
].forEach(type => {
handledNotifications[type] = true;
});
@@ -595,6 +605,8 @@ export default {
case 'NEW_CONTRIBUTOR_LEVEL':
case 'ACHIEVEMENT_ALL_YOUR_BASE':
case 'ACHIEVEMENT_BACK_TO_BASICS':
case 'ACHIEVEMENT_DUST_DEVIL':
case 'ACHIEVEMENT_ARID_AUTHORITY':
case 'GENERIC_ACHIEVEMENT':
this.showNotificationWithModal(notification);
break;

View File

@@ -18,5 +18,11 @@
"achievementBackToBasicsModalText": "You collected all the Base Pets!",
"achievementAllYourBase": "All Your Base",
"achievementAllYourBaseText": "Has tamed all Base Mounts.",
"achievementAllYourBaseModalText": "You tamed all the Base Mounts!"
"achievementAllYourBaseModalText": "You tamed all the Base Mounts!",
"achievementDustDevil": "Dust Devil",
"achievementDustDevilText": "Has collected all Desert Pets.",
"achievementDustDevilModalText": "You collected all the Desert Pets!",
"achievementAridAuthority": "Arid Authority",
"achievementAridAuthorityText": "Has tamed all Desert Mounts.",
"achievementAridAuthorityModalText": "You tamed all the Desert Mounts!"
}

View File

@@ -147,6 +147,16 @@ let basicAchievs = {
titleKey: 'achievementAllYourBase',
textKey: 'achievementAllYourBaseText',
},
dustDevil: {
icon: 'achievement-dustDevil',
titleKey: 'achievementDustDevil',
textKey: 'achievementDustDevilText',
},
aridAuthority: {
icon: 'achievement-aridAuthority',
titleKey: 'achievementAridAuthority',
textKey: 'achievementAridAuthorityText',
},
};
Object.assign(achievementsData, basicAchievs);

View File

@@ -256,14 +256,7 @@ export const QUEST_SERIES_ACHIEVEMENTS = {
],
};
export const BASE_PETS_MOUNTS = [
'Wolf-Base',
'TigerCub-Base',
'PandaCub-Base',
'LionCub-Base',
'Fox-Base',
'FlyingPig-Base',
'Dragon-Base',
'Cactus-Base',
'BearCub-Base',
export const ANIMAL_COLOR_ACHIEVEMENTS = [
{color: 'Base', petAchievement: 'backToBasics', petNotificationType: 'ACHIEVEMENT_BACK_TO_BASICS', mountAchievement: 'allYourBase', mountNotificationType: 'ACHIEVEMENT_ALL_YOUR_BASE'},
{color: 'Desert', petAchievement: 'dustDevil', petNotificationType: 'ACHIEVEMENT_DUST_DEVIL', mountAchievement: 'aridAuthority', mountNotificationType: 'ACHIEVEMENT_ARID_AUTHORITY'},
];

View File

@@ -8,7 +8,7 @@ import {
GEAR_TYPES,
ITEM_LIST,
QUEST_SERIES_ACHIEVEMENTS,
BASE_PETS_MOUNTS,
ANIMAL_COLOR_ACHIEVEMENTS,
} from './constants';
let api = module.exports;
@@ -38,7 +38,7 @@ import officialPinnedItems from './officialPinnedItems';
api.achievements = achievements;
api.questSeriesAchievements = QUEST_SERIES_ACHIEVEMENTS;
api.basePetsMounts = BASE_PETS_MOUNTS;
api.animalColorAchievements = ANIMAL_COLOR_ACHIEVEMENTS;
api.quests = quests;
api.questsByLevel = questsByLevel;

View File

@@ -188,6 +188,8 @@ function _getBasicAchievements (user, language) {
_addSimple(result, user, {path: 'justAddWater', language});
_addSimple(result, user, {path: 'backToBasics', language});
_addSimple(result, user, {path: 'allYourBase', language});
_addSimple(result, user, {path: 'dustDevil', language});
_addSimple(result, user, {path: 'aridAuthority', language});
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});
_addSimpleWithMasterCount(result, user, {path: 'mountMaster', language});

View File

@@ -1,7 +1,10 @@
import content from '../content/index';
import i18n from '../i18n';
import forEach from 'lodash/forEach';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
import keys from 'lodash/keys';
import upperFirst from 'lodash/upperFirst';
import {
BadRequest,
NotAuthorized,
@@ -90,21 +93,24 @@ module.exports = function feed (user, req = {}) {
user.items.food[food.key]--;
if (user.markModified) user.markModified('items.food');
if (!user.achievements.allYourBase) {
const mountIndex = findIndex(content.basePetsMounts, (animal) => {
return !user.items.mounts[animal];
});
if (mountIndex === -1) {
user.achievements.allYourBase = true;
if (user.addNotification) {
user.addNotification('ACHIEVEMENT_ALL_YOUR_BASE', {
achievement: 'allYourBase',
message: `${i18n.t('modalAchievement')} ${i18n.t('achievementAllYourBase')}`,
modalText: i18n.t('achievementAllYourBaseModalText'),
});
forEach(content.animalColorAchievements, (achievement) => {
if (!user.achievements[achievement.mountAchievement]) {
const mountIndex = findIndex(keys(content.dropEggs), (animal) => {
return !user.items.mounts[`${animal}-${achievement.color}`];
});
if (mountIndex === -1) {
user.achievements[achievement.mountAchievement] = true;
if (user.addNotification) {
const achievementString = `achievement${upperFirst(achievement.mountAchievement)}`;
user.addNotification(achievement.mountNotificationType, {
achievement: achievement.mountAchievement,
message: `${i18n.t('modalAchievement')} ${i18n.t(achievementString)}`,
modalText: i18n.t(`${achievementString}ModalText`),
});
}
}
}
}
});
return [
userPets[pet.key],

View File

@@ -1,7 +1,10 @@
import content from '../content/index';
import i18n from '../i18n';
import findIndex from 'lodash/findIndex';
import forEach from 'lodash/forEach';
import get from 'lodash/get';
import keys from 'lodash/keys';
import upperFirst from 'lodash/upperFirst';
import {
BadRequest,
NotAuthorized,
@@ -40,21 +43,24 @@ module.exports = function hatch (user, req = {}) {
user.markModified('items.hatchingPotions');
}
if (!user.achievements.backToBasics) {
const petIndex = findIndex(content.basePetsMounts, (animal) => {
return isNaN(user.items.pets[animal]) || user.items.pets[animal] <= 0;
});
if (petIndex === -1) {
user.achievements.backToBasics = true;
if (user.addNotification) {
user.addNotification('ACHIEVEMENT_BACK_TO_BASICS', {
achievement: 'backToBasics',
message: `${i18n.t('modalAchievement')} ${i18n.t('achievementBackToBasics')}`,
modalText: i18n.t('achievementBackToBasicsModalText'),
});
forEach(content.animalColorAchievements, (achievement) => {
if (!user.achievements[achievement.petAchievement]) {
const petIndex = findIndex(keys(content.dropEggs), (animal) => {
return isNaN(user.items.pets[`${animal}-${achievement.color}`]) || user.items.pets[`${animal}-${achievement.color}`] <= 0;
});
if (petIndex === -1) {
user.achievements[achievement.petAchievement] = true;
if (user.addNotification) {
const achievementString = `achievement${upperFirst(achievement.petAchievement)}`;
user.addNotification(achievement.petNotificationType, {
achievement: achievement.petAchievement,
message: `${i18n.t('modalAchievement')} ${i18n.t(achievementString)}`,
modalText: i18n.t(`${achievementString}ModalText`),
});
}
}
}
}
});
return [
user.items,

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -3,7 +3,7 @@ import { authWithHeaders } from '../../middlewares/auth';
let api = {};
// @TODO export this const, cannot export it from here because only routes are exported from controllers
const LAST_ANNOUNCEMENT_TITLE = 'NEW DISCOUNTED PET QUEST BUNDLE: ROCKING REPTILES';
const LAST_ANNOUNCEMENT_TITLE = 'NEW PET COLLECTION BADGES!';
const worldDmg = { // @TODO
bailey: false,
};
@@ -30,15 +30,14 @@ api.getNews = {
<div class="mr-3 ${baileyClass}"></div>
<div class="media-body">
<h1 class="align-self-center">${res.t('newStuff')}</h1>
<h2>9/10/2019 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
<h2>9/17/2019 - ${LAST_ANNOUNCEMENT_TITLE}</h2>
</div>
</div>
<hr/>
<div class="promo_rocking_reptiles_bundle center-block"></div>
<p>If you're looking to add some scaly friends to your Habitica stable, you're in luck! From now until Sept 30, you can purchase the Rocking Reptiles Pet Quest Bundle and receive the Alligator, Snake, and Velociraptor quests, all for only 7 Gems! That's a discount of 5 Gems from the price of purchasing them separately. Check it out in the <a href='/shops/quests'>Quest Shop</a> today!</p>
<div class="small">Art by Gully, Willow The Witty, mfonda, UncommonCriminal, tabbytoes, EmeraldOx, LordDarkly, PainterProphet, Seraphina, Anna Glassman, Procyon, and Lilith of Alfheim</div>
<div class="small mb-3">Writing by Mike.Antonacci, lilackbar, Daniel The Bard, and felipena</div>
<p>If snakes are something you'd prefer not to see in Habitica due to a phobia, check out the <a href='http://habitica.wikia.com/wiki/Phobia_Protection_Extension' target='_blank'>Phobia Protection Extension</a> which will hide any pets, mounts, backgrounds, quest bosses, or equipment featuring snakes (as well as spiders, rats, bees, zombies, skeletons, or any combination thereof). We hope that it helps make everyone's Habitica experience fun!</p>
<div class="promo_desert_pet_achievements center-block"></div>
<p>We're releasing a new achievement so you can celebrate your successes in the world of Habitican pet collecting! Earn the Dust Devil and Arid Authority achievements by collecting Desert pets and mounts and you'll earn a nifty badge for your profile.</p>
<p>If you already have all the Desert pets and/or mounts in your stable, you'll receive the badge automatically! Check your profile and celebrate your new achievement with pride.</p>
<div class="small mb-3">by Piyo and SabreCat</div>
</div>
`,
});

View File

@@ -124,6 +124,8 @@ let schema = new Schema({
justAddWater: Boolean,
backToBasics: Boolean,
allYourBase: Boolean,
dustDevil: Boolean,
aridAuthority: Boolean,
},
backer: {

View File

@@ -37,6 +37,8 @@ const NOTIFICATION_TYPES = [
'ACHIEVEMENT_JUST_ADD_WATER',
'ACHIEVEMENT_LOST_MASTERCLASSER',
'ACHIEVEMENT_MIND_OVER_MATTER',
'ACHIEVEMENT_DUST_DEVIL',
'ACHIEVEMENT_ARID_AUTHORITY',
];
const Schema = mongoose.Schema;