Merge branch 'sabrecat/pet-achievements' into release
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
46
website/client/components/achievements/justAddWater.vue
Normal 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>
|
||||
46
website/client/components/achievements/lostMasterclasser.vue
Normal 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>
|
||||
46
website/client/components/achievements/mindOverMatter.vue
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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',
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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, we’ve 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...that’s not sunlight, and the dolphin doesn’t dip back into the sea. It fixes its gaze on @khdarkwolf.<br><br>“I’ll never finish all these Dailies,” said @khdarkwolf.<br><br>“I’m 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 it’s 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 dolphin’s 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"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
];
|
||||
|
|
|
|||
|
|
@ -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, {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
After Width: | Height: | Size: 1.9 KiB |
|
After Width: | Height: | Size: 716 B |
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 1.2 KiB |
BIN
website/raw_sprites/spritesmith/quests/bosses/quest_dolphin.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
BIN
website/raw_sprites/spritesmith/stable/eggs/Pet_Egg_Dolphin.png
Normal file
|
After Width: | Height: | Size: 502 B |
|
After Width: | Height: | Size: 572 B |
|
After Width: | Height: | Size: 575 B |
|
After Width: | Height: | Size: 571 B |
|
After Width: | Height: | Size: 577 B |
|
After Width: | Height: | Size: 555 B |
|
After Width: | Height: | Size: 557 B |
|
After Width: | Height: | Size: 518 B |
|
After Width: | Height: | Size: 400 B |
|
After Width: | Height: | Size: 564 B |
|
After Width: | Height: | Size: 594 B |
|
After Width: | Height: | Size: 768 B |
|
After Width: | Height: | Size: 788 B |
|
After Width: | Height: | Size: 780 B |
|
After Width: | Height: | Size: 755 B |
|
After Width: | Height: | Size: 775 B |
|
After Width: | Height: | Size: 774 B |
|
After Width: | Height: | Size: 715 B |
|
After Width: | Height: | Size: 689 B |
|
After Width: | Height: | Size: 778 B |
|
After Width: | Height: | Size: 821 B |
|
After Width: | Height: | Size: 534 B |
|
After Width: | Height: | Size: 541 B |
|
After Width: | Height: | Size: 535 B |
|
After Width: | Height: | Size: 546 B |
|
After Width: | Height: | Size: 531 B |
|
After Width: | Height: | Size: 513 B |
|
After Width: | Height: | Size: 503 B |
|
After Width: | Height: | Size: 402 B |
|
After Width: | Height: | Size: 521 B |
|
After Width: | Height: | Size: 541 B |
BIN
website/raw_sprites/spritesmith/stable/pets/Pet-Dolphin-Base.png
Normal file
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 725 B |
|
After Width: | Height: | Size: 733 B |
|
After Width: | Height: | Size: 674 B |
|
After Width: | Height: | Size: 741 B |
BIN
website/raw_sprites/spritesmith/stable/pets/Pet-Dolphin-Red.png
Normal file
|
After Width: | Height: | Size: 718 B |
|
After Width: | Height: | Size: 682 B |
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 640 B |
|
After Width: | Height: | Size: 791 B |
BIN
website/raw_sprites/spritesmith_large/promo_dolphin_quest.png
Normal file
|
After Width: | Height: | Size: 95 KiB |
|
Before Width: | Height: | Size: 16 KiB |
BIN
website/raw_sprites/spritesmith_large/promo_oddballs_bundle.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -120,6 +120,10 @@ let schema = new Schema({
|
|||
joinedChallenge: Boolean,
|
||||
invitedFriend: Boolean,
|
||||
lostMasterclasser: Boolean,
|
||||
mindOverMatter: Boolean,
|
||||
justAddWater: Boolean,
|
||||
backToBasics: Boolean,
|
||||
allYourBase: Boolean,
|
||||
},
|
||||
|
||||
backer: {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||