Merge branch 'sabrecat/pet-achievements' into release

This commit is contained in:
Sabe Jones 2019-06-11 15:12:34 -05:00
commit f235a64d96
78 changed files with 600 additions and 50 deletions

View file

@ -1734,7 +1734,7 @@ describe('Group Model', () => {
let quest;
beforeEach(() => {
quest = questScrolls.whale;
quest = questScrolls.armadillo;
party.quest.key = quest.key;
party.quest.active = false;
party.quest.leader = questLeader._id;
@ -1882,6 +1882,36 @@ describe('Group Model', () => {
expect(updatedSleepingParticipatingMember.achievements.lostMasterclasser).to.not.eql(true);
});
it('gives out other pet-related quest achievements', async () => {
quest = questScrolls.rock;
party.quest.key = quest.key;
questLeader.achievements.quests = {
mayhemMistiflying1: 1,
yarn: 1,
mayhemMistiflying2: 1,
egg: 1,
mayhemMistiflying3: 1,
slime: 2,
};
await questLeader.save();
await party.finishQuest(quest);
let [
updatedLeader,
updatedParticipatingMember,
updatedSleepingParticipatingMember,
] = await Promise.all([
User.findById(questLeader._id).exec(),
User.findById(participatingMember._id).exec(),
User.findById(sleepingParticipatingMember._id).exec(),
]);
expect(updatedLeader.achievements.mindOverMatter).to.eql(true);
expect(updatedParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
expect(updatedSleepingParticipatingMember.achievements.mindOverMatter).to.not.eql(true);
});
it('gives xp and gold', async () => {
await party.finishQuest(quest);
@ -2018,13 +2048,13 @@ describe('Group Model', () => {
questLeader = await User.findById(questLeader._id);
participatingMember = await User.findById(participatingMember._id);
expect(questLeader.party.quest.completed).to.eql('whale');
expect(questLeader.party.quest.completed).to.eql('armadillo');
expect(questLeader.party.quest.progress.up).to.eql(10);
expect(questLeader.party.quest.progress.down).to.eql(8);
expect(questLeader.party.quest.progress.collectedItems).to.eql(5);
expect(questLeader.party.quest.RSVPNeeded).to.eql(false);
expect(participatingMember.party.quest.completed).to.eql('whale');
expect(participatingMember.party.quest.completed).to.eql('armadillo');
expect(participatingMember.party.quest.progress.up).to.eql(10);
expect(participatingMember.party.quest.progress.down).to.eql(8);
expect(participatingMember.party.quest.progress.collectedItems).to.eql(5);

View file

@ -169,6 +169,24 @@ describe('shared.ops.feed', () => {
expect(user.items.pets['Wolf-Base']).to.equal(7);
});
it('awards All Your Base achievement', () => {
user.items.pets['Wolf-Spooky'] = 5;
user.items.food.Milk = 2;
user.items.mounts = {
'Wolf-Base': true,
'TigerCub-Base': true,
'PandaCub-Base': true,
'LionCub-Base': true,
'Fox-Base': true,
'FlyingPig-Base': true,
'Dragon-Base': true,
'Cactus-Base': true,
'BearCub-Base': true,
};
feed(user, {params: {pet: 'Wolf-Spooky', food: 'Milk'}});
expect(user.achievements.allYourBase).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

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

View file

@ -656,5 +656,6 @@ export default {
<style src="assets/css/sprites/spritesmith-main-22.css"></style>
<style src="assets/css/sprites/spritesmith-main-23.css"></style>
<style src="assets/css/sprites/spritesmith-main-24.css"></style>
<style src="assets/css/sprites/spritesmith-main-25.css"></style>
<style src="assets/css/sprites.css"></style>
<style src="smartbanner.js/dist/smartbanner.min.css"></style>

View file

@ -0,0 +1,42 @@
<template lang="pug">
b-modal#generic-achievement(:title='data.message', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p(v-html='data.modalText')
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
props: ['data'],
computed: {
...mapState({user: 'user.data'}),
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'generic-achievement');
},
},
};
</script>

View file

@ -0,0 +1,46 @@
<template lang="pug">
b-modal#just-add-water(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementJustAddWaterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementJustAddWater')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'just-add-water');
},
},
};
</script>

View file

@ -0,0 +1,46 @@
<template lang="pug">
b-modal#lost-masterclasser(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementLostMasterclasserModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementLostMasterclasser')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'lost-masterclasser');
},
},
};
</script>

View file

@ -0,0 +1,46 @@
<template lang="pug">
b-modal#mind-over-matter(:title='title', size='md', :hide-footer='true')
.modal-body
.col-12
achievement-avatar.avatar
.col-6.offset-3.text-center
p {{ $t('achievementMindOverMatterModalText') }}
button.btn.btn-primary(@click='close()') {{ $t('huzzah') }}
achievement-footer
</template>
<style scoped>
.avatar {
width: 140px;
margin: 0 auto;
margin-bottom: 1.5em;
margin-top: 1.5em;
}
</style>
<script>
import achievementFooter from './achievementFooter';
import achievementAvatar from './achievementAvatar';
import {mapState} from 'client/libs/store';
export default {
components: {
achievementFooter,
achievementAvatar,
},
computed: {
...mapState({user: 'user.data'}),
},
data () {
return {
title: `${this.$t('modalAchievement')} ${this.$t('achievementMindOverMatter')}`,
};
},
methods: {
close () {
this.$root.$emit('bv::hide::modal', 'mind-over-matter');
},
},
};
</script>

View file

@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementJustAddWater')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'just-add-water');
},
},
};
</script>

View file

@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementLostMasterclasser')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'lost-masterclasser');
},
},
};
</script>

View file

@ -0,0 +1,30 @@
<template lang="pug">
base-notification(
:can-remove="canRemove",
:notification="notification",
:read-after-click="true",
@click="action"
)
div(slot="content", v-html="achievementString")
</template>
<script>
import BaseNotification from './base';
export default {
props: ['notification', 'canRemove'],
components: {
BaseNotification,
},
computed: {
achievementString () {
return `<strong>${this.$t('achievement')}</strong>: ${this.$t('achievementMindOverMatter')}`;
},
},
methods: {
action () {
this.$root.$emit('bv::show::modal', 'mind-over-matter');
},
},
};
</script>

View file

@ -95,6 +95,9 @@ import NEW_INBOX_MESSAGE from './notifications/newInboxMessage';
import NEW_CHAT_MESSAGE from './notifications/newChatMessage';
import WORLD_BOSS from './notifications/worldBoss';
import VERIFY_USERNAME from './notifications/verifyUsername';
import ACHIEVEMENT_JUST_ADD_WATER from './notifications/justAddWater';
import ACHIEVEMENT_LOST_MASTERCLASSER from './notifications/lostMasterclasser';
import ACHIEVEMENT_MIND_OVER_MATTER from './notifications/mindOverMatter';
export default {
components: {
@ -106,6 +109,7 @@ export default {
QUEST_INVITATION, GROUP_TASK_APPROVAL, GROUP_TASK_APPROVED, GROUP_TASK_ASSIGNED,
UNALLOCATED_STATS_POINTS, NEW_MYSTERY_ITEMS, CARD_RECEIVED,
NEW_INBOX_MESSAGE, NEW_CHAT_MESSAGE,
ACHIEVEMENT_JUST_ADD_WATER, ACHIEVEMENT_LOST_MASTERCLASSER, ACHIEVEMENT_MIND_OVER_MATTER,
WorldBoss: WORLD_BOSS,
VERIFY_USERNAME,
},
@ -130,6 +134,7 @@ export default {
'QUEST_INVITATION', 'GROUP_TASK_ASSIGNED', 'GROUP_TASK_APPROVAL', 'GROUP_TASK_APPROVED',
'NEW_MYSTERY_ITEMS', 'CARD_RECEIVED',
'NEW_INBOX_MESSAGE', 'NEW_CHAT_MESSAGE', 'UNALLOCATED_STATS_POINTS',
'ACHIEVEMENT_JUST_ADD_WATER', 'ACHIEVEMENT_LOST_MASTERCLASSER', 'ACHIEVEMENT_MIND_OVER_MATTER',
'VERIFY_USERNAME',
],
};

View file

@ -26,6 +26,10 @@ div
quest-completed
quest-invitation
verify-username
generic-achievement(:data='notificationData')
just-add-water
lost-masterclasser
mind-over-matter
</template>
<style lang='scss'>
@ -118,6 +122,10 @@ import rebirth from './achievements/rebirth';
import streak from './achievements/streak';
import ultimateGear from './achievements/ultimateGear';
import wonChallenge from './achievements/wonChallenge';
import genericAchievement from './achievements/genericAchievement';
import justAddWater from './achievements/justAddWater';
import lostMasterclasser from './achievements/lostMasterclasser';
import mindOverMatter from './achievements/mindOverMatter';
import loginIncentives from './achievements/login-incentives';
import verifyUsername from './settings/verifyUsername';
@ -147,6 +155,16 @@ const NOTIFICATIONS = {
label: ($t) => $t('modalContribAchievement'),
modalId: 'contributor',
},
ACHIEVEMENT_ALL_YOUR_BASE: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementAllYourBase')}`,
modalId: 'generic-achievement',
},
ACHIEVEMENT_BACK_TO_BASICS: {
achievement: true,
label: ($t) => `${$t('achievement')}: ${$t('achievementBackToBasics')}`,
modalId: 'generic-achievement',
},
};
export default {
@ -176,6 +194,10 @@ export default {
contributor,
loginIncentives,
verifyUsername,
genericAchievement,
lostMasterclasser,
mindOverMatter,
justAddWater,
},
data () {
// Levels that already display modals and should not trigger generic Level Up
@ -197,7 +219,8 @@ export default {
'GUILD_PROMPT', 'DROPS_ENABLED', 'REBIRTH_ENABLED', 'WON_CHALLENGE', 'STREAK_ACHIEVEMENT',
'ULTIMATE_GEAR_ACHIEVEMENT', 'REBIRTH_ACHIEVEMENT', 'GUILD_JOINED_ACHIEVEMENT',
'CHALLENGE_JOINED_ACHIEVEMENT', 'INVITED_FRIEND_ACHIEVEMENT', 'NEW_CONTRIBUTOR_LEVEL',
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE',
'CRON', 'SCORED_TASK', 'LOGIN_INCENTIVE', 'ACHIEVEMENT_ALL_YOUR_BASE', 'ACHIEVEMENT_BACK_TO_BASICS',
'GENERIC_ACHIEVEMENT',
].forEach(type => {
handledNotifications[type] = true;
});
@ -342,8 +365,8 @@ export default {
this.playSound('Death');
this.$root.$emit('bv::show::modal', 'death');
},
showNotificationWithModal (type, forceToModal) {
const config = NOTIFICATIONS[type];
showNotificationWithModal (notification, forceToModal) {
const config = NOTIFICATIONS[notification.type];
if (!config) {
return;
@ -355,6 +378,10 @@ export default {
this.playSound(config.sound);
}
if (notification.data) {
this.notificationData = notification.data;
}
if (forceToModal) {
this.$root.$emit('bv::show::modal', config.modalId);
} else {
@ -566,7 +593,10 @@ export default {
case 'CHALLENGE_JOINED_ACHIEVEMENT':
case 'INVITED_FRIEND_ACHIEVEMENT':
case 'NEW_CONTRIBUTOR_LEVEL':
this.showNotificationWithModal(notification.type);
case 'ACHIEVEMENT_ALL_YOUR_BASE':
case 'ACHIEVEMENT_BACK_TO_BASICS':
case 'GENERIC_ACHIEVEMENT':
this.showNotificationWithModal(notification);
break;
case 'CRON':
if (notification.data) {

View file

@ -5,5 +5,18 @@
"levelup": "By accomplishing your real life goals, you leveled up and are now fully healed!",
"reachedLevel": "You Reached Level <%= level %>",
"achievementLostMasterclasser": "Quest Completionist: Masterclasser Series",
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!"
"achievementLostMasterclasserText": "Completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
"achievementLostMasterclasserModalText": "You completed all sixteen quests in the Masterclasser Quest Series and solved the mystery of the Lost Masterclasser!",
"achievementMindOverMatter": "Mind Over Matter",
"achievementMindOverMatterText": "Has completed Rock, Slime, and Yarn pet quests.",
"achievementMindOverMatterModalText": "You completed the Rock, Slime, and Yarn pet quests!",
"achievementJustAddWater": "Just Add Water",
"achievementJustAddWaterText": "Has completed Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests.",
"achievementJustAddWaterModalText": "You completed the Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests!",
"achievementBackToBasics": "Back to Basics",
"achievementBackToBasicsText": "Has collected all Base Pets.",
"achievementBackToBasicsModalText": "You collected all the Base Pets!",
"achievementAllYourBase": "All Your Base",
"achievementAllYourBaseText": "Has tamed all Base Mounts.",
"achievementAllYourBaseModalText": "You tamed all the Base Mounts!"
}

View file

@ -249,6 +249,10 @@
"questEggVelociraptorMountText": "Velociraptor",
"questEggVelociraptorAdjective": "a clever",
"questEggDolphinText": "Dolphin",
"questEggDolphinMountText": "Dolphin",
"questEggDolphinAdjective": "a chipper",
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
"hatchingPotionBase": "Base",

View file

@ -747,5 +747,12 @@
"questBronzeCompletion": "“Well met, warrior!” says the beetle as she settles to the ground. Is she smiling? It's hard to tell from those mandibles. “You've truly earned these potions!”<br<br>“Oh wow, weve never gotten a reward like this for winning a battle before!” says @UncommonCriminal, turning a shimmering bottle in their hand. “Let's go hatch our new pets!”",
"questBronzeBoss": "Brazen Beetle",
"questBronzeDropBronzePotion": "Bronze Hatching Potion",
"questBronzeUnlockText": "Unlocks purchasable Bronze hatching potions in the Market"
"questBronzeUnlockText": "Unlocks purchasable Bronze hatching potions in the Market",
"questDolphinText": "The Dolphin of Doubt",
"questDolphinNotes": "You walk upon the shores of Inkomplete Bay, pondering the daunting work ahead of you. A splash in the water catches your eye. A magnificent dolphin arcs over the waves. Sunlight glimmers off its fins and tail. But wait...thats not sunlight, and the dolphin doesnt dip back into the sea. It fixes its gaze on @khdarkwolf.<br><br>“Ill never finish all these Dailies,” said @khdarkwolf.<br><br>“Im not good enough to reach my goals,” said @confusedcicada as the dolphin turned its glare on them.<br><br>“Why did I even bother trying?” asked @mewrose, withering under the stare of the beast.<br><br>Its eyes meet yours, and feel your mind begin to sink under the rising tide of doubt. You steel yourself; someone has to defeat this creature, and its going to be you!",
"questDolphinBoss": "Dolphin of Doubt",
"questDolphinCompletion": "Your battle of wills with the dolphin has left you tired but victorious. With your determination and encouragement, @mewrose, @khdarkwolf, and @confusedcicada pick themselves up and shake off the dolphins insidious telepathy. The four of you shield yourselves with a sense of accomplishment in your consistent Dailies, strong Habits, and completed To-Dos until it closes its glowing eyes in silent acknowledgment of your successes. With that, it tumbles back into the bay. As you trade high-fives and congratulations, you notice three eggs wash ashore.<br><br>“Hm, I wonder what we can do with those,” @khdarkwolf muses.",
"questDolphinDropDolphinEgg": "Dolphin (Egg)",
"questDolphinUnlockText": "Unlocks purchasable Dolphin eggs in the Market"
}

View file

@ -127,6 +127,26 @@ let basicAchievs = {
titleKey: 'achievementLostMasterclasser',
textKey: 'achievementLostMasterclasserText',
},
mindOverMatter: {
icon: 'achievement-mindOverMatter',
titleKey: 'achievementMindOverMatter',
textKey: 'achievementMindOverMatterText',
},
justAddWater: {
icon: 'achievement-justAddWater',
titleKey: 'achievementJustAddWater',
textKey: 'achievementJustAddWaterText',
},
backToBasics: {
icon: 'achievement-backToBasics',
titleKey: 'achievementBackToBasics',
textKey: 'achievementBackToBasicsText',
},
allYourBase: {
icon: 'achievement-allYourBase',
titleKey: 'achievementAllYourBase',
textKey: 'achievementAllYourBaseText',
},
};
Object.assign(achievementsData, basicAchievs);

View file

@ -212,3 +212,51 @@ export const USER_CAN_OWN_QUEST_CATEGORIES = [
'hatchingPotion',
'pet',
];
export const QUEST_SERIES_ACHIEVEMENTS = {
lostMasterclasser: [
'dilatoryDistress1',
'dilatoryDistress2',
'dilatoryDistress3',
'mayhemMistiflying1',
'mayhemMistiflying2',
'mayhemMistiflying3',
'stoikalmCalamity1',
'stoikalmCalamity2',
'stoikalmCalamity3',
'taskwoodsTerror1',
'taskwoodsTerror2',
'taskwoodsTerror3',
'lostMasterclasser1',
'lostMasterclasser2',
'lostMasterclasser3',
'lostMasterclasser4',
],
mindOverMatter: [
'rock',
'slime',
'yarn',
],
justAddWater: [
'octopus',
'dilatory_derby',
'kraken',
'whale',
'turtle',
'nudibranch',
'seaserpent',
'dolphin',
],
};
export const BASE_PETS_MOUNTS = [
'Wolf-Base',
'TigerCub-Base',
'PandaCub-Base',
'LionCub-Base',
'Fox-Base',
'FlyingPig-Base',
'Dragon-Base',
'Cactus-Base',
'BearCub-Base',
];

View file

@ -380,6 +380,12 @@ let quests = {
adjective: t('questEggVelociraptorAdjective'),
canBuy: hasQuestAchievementFunction('velociraptor'),
},
Dolphin: {
text: t('questEggDolphinText'),
mountText: t('questEggDolphinMountText'),
adjective: t('questEggDolphinAdjective'),
canBuy: hasQuestAchievementFunction('dolphin'),
},
};
applyEggDefaults(drops, {

View file

@ -7,6 +7,8 @@ import {
CLASSES,
GEAR_TYPES,
ITEM_LIST,
QUEST_SERIES_ACHIEVEMENTS,
BASE_PETS_MOUNTS,
} from './constants';
let api = module.exports;
@ -35,6 +37,8 @@ import loginIncentives from './loginIncentives';
import officialPinnedItems from './officialPinnedItems';
api.achievements = achievements;
api.questSeriesAchievements = QUEST_SERIES_ACHIEVEMENTS;
api.basePetsMounts = BASE_PETS_MOUNTS;
api.quests = quests;
api.questsByLevel = questsByLevel;
@ -207,7 +211,7 @@ api.bundles = {
'yarn',
],
canBuy () {
return moment().isBetween('2018-11-15', '2018-12-04');
return moment().isBetween('2019-06-10', '2019-07-02');
},
type: 'quests',
value: 7,

View file

@ -3385,6 +3385,38 @@ let quests = {
unlock: t('questBronzeUnlockText'),
},
},
dolphin: {
text: t('questDolphinText'),
notes: t('questDolphinNotes'),
completion: t('questDolphinCompletion'),
value: 4,
category: 'pet',
boss: {
name: t('questDolphinBoss'),
hp: 300,
str: 1.25,
},
drop: {
items: [
{
type: 'eggs',
key: 'Dolphin',
text: t('questDolphinDropDolphinEgg'),
}, {
type: 'eggs',
key: 'Dolphin',
text: t('questDolphinDropDolphinEgg'),
}, {
type: 'eggs',
key: 'Dolphin',
text: t('questDolphinDropDolphinEgg'),
},
],
gp: 63,
exp: 575,
unlock: t('questDolphinUnlockText'),
},
},
};
each(quests, (v, key) => {

View file

@ -9,11 +9,11 @@ const featuredItems = {
},
{
type: 'eggs',
path: 'eggs.Dragon',
path: 'eggs.Fox',
},
{
type: 'hatchingPotions',
path: 'hatchingPotions.Red',
path: 'hatchingPotions.CottonCandyBlue',
},
{
type: 'card',
@ -27,11 +27,11 @@ const featuredItems = {
},
{
type: 'quests',
path: 'quests.rat',
path: 'quests.yarn',
},
{
type: 'quests',
path: 'quests.horse',
path: 'quests.dolphin',
},
],
seasonal: 'spring2018Healer',

View file

@ -184,6 +184,10 @@ function _getBasicAchievements (user, language) {
_addSimple(result, user, {path: 'joinedChallenge', language});
_addSimple(result, user, {path: 'invitedFriend', language});
_addSimple(result, user, {path: 'lostMasterclasser', language});
_addSimple(result, user, {path: 'mindOverMatter', language});
_addSimple(result, user, {path: 'justAddWater', language});
_addSimple(result, user, {path: 'backToBasics', language});
_addSimple(result, user, {path: 'allYourBase', language});
_addSimpleWithMasterCount(result, user, {path: 'beastMaster', language});
_addSimpleWithMasterCount(result, user, {path: 'mountMaster', language});

View file

@ -1,5 +1,6 @@
import content from '../content/index';
import i18n from '../i18n';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
import {
BadRequest,
@ -89,6 +90,22 @@ 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'),
});
}
}
}
return [
userPets[pet.key],
message,

View file

@ -1,5 +1,6 @@
import content from '../content/index';
import i18n from '../i18n';
import findIndex from 'lodash/findIndex';
import get from 'lodash/get';
import {
BadRequest,
@ -39,6 +40,22 @@ 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'),
});
}
}
}
return [
user.items,
i18n.t('messageHatched', req.language),

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 716 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 575 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 571 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 555 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 518 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 594 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 755 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 775 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 774 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 715 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 689 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 778 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 535 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 546 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 531 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 503 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 402 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 521 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 541 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 725 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 733 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 682 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View file

@ -41,6 +41,7 @@ import { getGroupChat, translateMessage } from '../libs/chat/group-chat';
import { model as UserNotification } from './userNotification';
const questScrolls = shared.content.quests;
const questSeriesAchievements = shared.content.questSeriesAchievements;
const Schema = mongoose.Schema;
export const INVITES_LIMIT = 100; // must not be greater than MAX_EMAIL_INVITES_BY_USER
@ -849,25 +850,6 @@ schema.methods.finishQuest = async function finishQuest (quest) {
}
});
let masterClasserQuests = [
'dilatoryDistress1',
'dilatoryDistress2',
'dilatoryDistress3',
'mayhemMistiflying1',
'mayhemMistiflying2',
'mayhemMistiflying3',
'stoikalmCalamity1',
'stoikalmCalamity2',
'stoikalmCalamity3',
'taskwoodsTerror1',
'taskwoodsTerror2',
'taskwoodsTerror3',
'lostMasterclasser1',
'lostMasterclasser2',
'lostMasterclasser3',
'lostMasterclasser4',
];
// Send webhooks in background
// @TODO move the find users part to a worker as well, not just the http request
User.find({
@ -893,24 +875,39 @@ schema.methods.finishQuest = async function finishQuest (quest) {
});
});
_.forEach(questSeriesAchievements, (questList, achievement) => {
if (questList.includes(questK)) {
let questAchievementQuery = {};
questAchievementQuery[`achievements.${achievement}`] = {$ne: true};
_.forEach(questList, (questName) => {
if (questName !== questK) {
questAchievementQuery[`achievements.quests.${questName}`] = {$gt: 0};
}
});
let questAchievementUpdate = {$set: {}, $push: {}};
questAchievementUpdate.$set[`achievements.${achievement}`] = true;
const achievementTitleCase = `${achievement.slice(0, 1).toUpperCase()}${achievement.slice(1, achievement.length)}`;
const achievementSnakeCase = `ACHIEVEMENT_${_.snakeCase(achievement).toUpperCase()}`;
questAchievementUpdate.$push = {
notifications: new UserNotification({
type: achievementSnakeCase,
data: {
achievement,
message: `${shared.i18n.t('modalAchievement')} ${shared.i18n.t(`achievement${achievementTitleCase}`)}`,
modalText: shared.i18n.t(`achievement${achievementTitleCase}ModalText`),
},
}).toObject(),
};
promises.push(participants.map(userId => {
return _updateUserWithRetries(userId, questAchievementUpdate, null, questAchievementQuery);
}));
}
});
await Promise.all(promises);
if (masterClasserQuests.includes(questK)) {
let lostMasterclasserQuery = {
'achievements.lostMasterclasser': {$ne: true},
};
masterClasserQuests.forEach(questName => {
lostMasterclasserQuery[`achievements.quests.${questName}`] = {$gt: 0};
});
let lostMasterclasserUpdate = {
$set: {'achievements.lostMasterclasser': true},
};
let lostMasterClasserPromises = participants.map(userId => {
return _updateUserWithRetries(userId, lostMasterclasserUpdate, null, lostMasterclasserQuery);
});
await Promise.all(lostMasterClasserPromises);
}
};
function _isOnQuest (user, progress, group) {

View file

@ -120,6 +120,10 @@ let schema = new Schema({
joinedChallenge: Boolean,
invitedFriend: Boolean,
lostMasterclasser: Boolean,
mindOverMatter: Boolean,
justAddWater: Boolean,
backToBasics: Boolean,
allYourBase: Boolean,
},
backer: {

View file

@ -32,6 +32,11 @@ const NOTIFICATION_TYPES = [
'NEW_STUFF',
'NEW_CHAT_MESSAGE',
'LEVELED_UP',
'ACHIEVEMENT_ALL_YOUR_BASE',
'ACHIEVEMENT_BACK_TO_BASICS',
'ACHIEVEMENT_JUST_ADD_WATER',
'ACHIEVEMENT_LOST_MASTERCLASSER',
'ACHIEVEMENT_MIND_OVER_MATTER',
];
const Schema = mongoose.Schema;