From 7094e75dd839962e51aaa025fab5447935045b41 Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Tue, 4 Jun 2019 10:31:25 -0500 Subject: [PATCH] feat(achievements): new pet-related cheevos --- test/api/unit/models/group.test.js | 30 +++++++++ test/common/ops/feed.js | 18 ++++++ test/common/ops/hatch.js | 18 ++++++ website/common/locales/en/achievements.json | 10 ++- website/common/script/content/achievements.js | 20 ++++++ website/common/script/content/constants.js | 49 +++++++++++++++ website/common/script/content/index.js | 4 ++ website/common/script/libs/achievements.js | 4 ++ website/common/script/ops/feed.js | 10 +++ website/common/script/ops/hatch.js | 10 +++ .../achievement-allYourBase2x.png | Bin 0 -> 1977 bytes .../achievement-backToBasics2x.png | Bin 0 -> 716 bytes .../achievement-justAddWater2x.png | Bin 0 -> 1206 bytes .../achievement-mindOverMatter2x.png | Bin 0 -> 1202 bytes website/server/models/group.js | 57 +++++++----------- website/server/models/user/schema.js | 4 ++ 16 files changed, 197 insertions(+), 37 deletions(-) create mode 100644 website/raw_sprites/spritesmith/achievements/achievement-allYourBase2x.png create mode 100644 website/raw_sprites/spritesmith/achievements/achievement-backToBasics2x.png create mode 100644 website/raw_sprites/spritesmith/achievements/achievement-justAddWater2x.png create mode 100644 website/raw_sprites/spritesmith/achievements/achievement-mindOverMatter2x.png diff --git a/test/api/unit/models/group.test.js b/test/api/unit/models/group.test.js index 7d120c055a..377c6c6676 100644 --- a/test/api/unit/models/group.test.js +++ b/test/api/unit/models/group.test.js @@ -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); diff --git a/test/common/ops/feed.js b/test/common/ops/feed.js index 4aabd119df..84c3713686 100644 --- a/test/common/ops/feed.js +++ b/test/common/ops/feed.js @@ -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; diff --git a/test/common/ops/hatch.js b/test/common/ops/hatch.js index 919829d6b8..0339a8fa8d 100644 --- a/test/common/ops/hatch.js +++ b/test/common/ops/hatch.js @@ -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); + }); }); }); }); diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index 4f5dcd77c1..f84b48d46b 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -5,5 +5,13 @@ "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!", + "achievementMindOverMatter": "Mind Over Matter", + "achievementMindOverMatterText": "Has completed Rock, Egg, Slime, and Yarn pet quests.", + "achievementJustAddWater": "Just Add Water", + "achievementJustAddWaterText": "Has completed Octopus, Seahorse, Cuttlefish, Whale, Turtle, Nudibranch, Sea Serpent, and Dolphin pet quests.", + "achievementBackToBasics": "Back to Basics", + "achievementBackToBasicsText": "Has collected all Base Pets.", + "achievementAllYourBase": "All Your Base", + "achievementAllYourBaseText": "Has tamed all Base Mounts." } diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index 6250bde61b..28e9fc88be 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -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); diff --git a/website/common/script/content/constants.js b/website/common/script/content/constants.js index a345985367..f9fa398b81 100644 --- a/website/common/script/content/constants.js +++ b/website/common/script/content/constants.js @@ -212,3 +212,52 @@ 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: [ + 'egg', + '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', +]; diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index d1b1e2326a..87981b5abd 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -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; diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index 0793c45ee6..770fda23ef 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -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}); diff --git a/website/common/script/ops/feed.js b/website/common/script/ops/feed.js index 1a66770d28..e313a0418d 100644 --- a/website/common/script/ops/feed.js +++ b/website/common/script/ops/feed.js @@ -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,15 @@ 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; + } + } + return [ userPets[pet.key], message, diff --git a/website/common/script/ops/hatch.js b/website/common/script/ops/hatch.js index 672372e125..84c6140fe1 100644 --- a/website/common/script/ops/hatch.js +++ b/website/common/script/ops/hatch.js @@ -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,15 @@ 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; + } + } + return [ user.items, i18n.t('messageHatched', req.language), diff --git a/website/raw_sprites/spritesmith/achievements/achievement-allYourBase2x.png b/website/raw_sprites/spritesmith/achievements/achievement-allYourBase2x.png new file mode 100644 index 0000000000000000000000000000000000000000..dfec6af9304a181a341a2dfcad3a9c91db0d3c13 GIT binary patch literal 1977 zcmV;q2S)gbP)bw80z@)yv_KPN}EABsi+ z0G;8mki^P(1Lk+$V9ehXi&XJ7-4FMFyg<`2G0vj=LMQahH0iO zpF$|s^)uO~rENAb!RpRl*UkOS0U_Hr>Ju=w{^W8TrIyQVrBIFrd_35KM_t%L^Il zj^<`&1YK{dsdn`~;w6MRX>HJPgFj~uN06-?L24#!ZjB1V?95!lW59=zGi1|Y^4|xe z5oAuS4?Y_Jj)f}~@>d6jlu0j`H&@wed(&46!EL*12>GTK06Tp$Lf0Z64r)X(uRYPh@zaupDNx;ey0}+$`p!<3lk0*`Mas& z3-aC*JTh zz^19~j+389DcK(K^Ld7RYQ_ZlA2>(YR8&muHM^7zZ{7C^0N{`7*Qv5pTgwcg@sJFB&`O#ZMBB7E>7`emEzJBcu zl1VQcR1A>XS{d6!sC2Ds93a)(R4S!x{%Gxfj^V3SKqlVdoaFg@|I!E{w!X;k+TY9? zH*i0BUUCKT7Ih88gg(#IWnMPO47k8HVy@wd7f7!O$^@reYkLpJ$GHMzE5=AgNDy+eLGE{Pcvxw{!+*v# z(yRr-NHd#syptp~mi7YK{J*CGX$^>~iMztKxuc>20PxMX?aJ2uiwn%65&+=uyJG-= z*B31^{47^@x!tl%1eNJJ$lV{i!guFd?r zJqE~>+dS3rSeFd|_e)CDOq#YOf7CS|XTMDGKwwRfv$-_@fc_ZO1p=WEGvy8grPmzO zD@1f2UL@#LgrH|G7%ZjwKzB^7{W)uM zydsm{;lWI3Xwa1$V_c~aE#tVr=XeQQ5o?emwKNmDm1Z3Il95koCi-)7RpmXw zg&~G&n`&;@I1DdQ$ZLVJ1EsBlo({mQ!I$8VI`+ zV9I@4SD*y>Z2JghoHfCaM>xp8IaArA9nsDM6{t?wYRmOHEjLK`~7U=eI5h;F9v+XJ235( zuHl)rJ#$~58QQx*Xd4h(Do?jGHd0--vS4mjo8{DL>}d5EU>yGf7_wAw$S-VX00000 LNkvXXu0mjfrjx`D literal 0 HcmV?d00001 diff --git a/website/raw_sprites/spritesmith/achievements/achievement-backToBasics2x.png b/website/raw_sprites/spritesmith/achievements/achievement-backToBasics2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9eb48e4ce53a6c7ee5102703c3a704ed46fa6a32 GIT binary patch literal 716 zcmV;-0yF)IP)L3)64i5ea z)=3oV4-moDMH~c|uASo2C**O@OYRaO*L;_9O?nC6&-=byl6oEAM-tjOJq=8Ju(9DK zahxHfT*C=E{x3vQPr%!|yR(|$_3_bHIo|2@0^{*qjuQa@Ow7!9gH53N_7>>}H`mw7 zTIY`6K0gP>vr|(_B1D2TH$U%X1pabfs&(%8=*UQ5{N3#;iAdQ9OTt|OQEQz$t`iZl z&4zmfY->HLY_pMm0)BrrTJEn^;;QxYYBexk+uV#b8;$;2D*XgjYBiG24Q_>O9r{)$y@%`*Q#L#jP!67HEFT7ImlWxsa{%oamK?1n+V>67CYEM#5CR@`~M&bO6&1Wdcn*XL)V`%C9FxEbK< zf9ghIou&H))9&Lg0lMq&gk1MaB;s~nIxqL0%&k`{MkM$ZT1U*(`LlW*fJ;y2eDN!ysl6fui1Vb-nM6uEHWGNU%SsEv^=U9?bK zMT=+|m=w$+1xo_W5y4PUYY~=#5?Nr2_r8()%{z17q%#$B&u*UgzI*5W?sxvpGqSa{ zC8e|j1Cz#r8(qzjQm(&8%FOnan(SOxW8n2tkuE#Jkz#8Y1fN}N4E%flQiX_!jJa_UV2xmC6EOnkhKwI zvbDD-jO$DHTMc47Un7pptar9B(k>2j9ImUc^PlWypjI`aAb_|Evg3CejNE$k=?8-} zPL7s)uY9h|yPw5{MHh!dX_zyqSjzOuHnK2~jVI&&l}%&ZihrF?KvttfdV5{-1TAO0 zjBLY_dpp9Ku~){E=D3=ZJ1?w>h{%(kz5azNkHpXKh87dUYk9pp$8GF`px-Wuuc`qzxt+|vq6+<9ruOT$pDEN8raG%_&On5~}GjJujl4jEM}-c0dW0_$I= zVcP&av7D;P&sq6RJSNVakgz_3iNg3zyu}6QrFT{>6>2u(Hq(gimN^aY)U>x|895Qto3<${~{_d+v+vhj*9`dM||%U>4GEd^NQ?^vpa3~KmS-xL_{BA zaZfaD2(LHUw&Pofi0Dl^WP4m?Ks@uRl>`wHbqy|WC}|U=^R+A2q8TEhrV|Hy3sO{iSjzIta^ z+6HmA{uvD`qit4ZBy2J*$f}5r7gg(dG?t)TZwyg1lgqEpM=7IWEyA;N3$~dNp1g|a zL~2FPqd>JQ;ypv#iHPXTLA@AZ2s;_bwjN!Q$mUIyS=RXu#2ftZE2Kr*5a#xNuEKhv z35%?(RVSj0(U(L-6uf*|%c1F!j4R^Ey&L>vmY;YN77SsnY6+PXkIGKf56GTtZ6+e3 z-kv_&)v_VXdr@377#E$$>JnBFSCQsDF_=?t ztZ<_=pBWe(5}GaY>*dHGTfhJCnjHE$LDHMiZ(YxcH2kWv{9H-%I$oaHC_KWPtf~j4 z((Zbj6KSXMndc3hbm3|^U$W3{pe$&+zki@lD{8wve$}t3+!>zpwku{bE&F;HPXx1f?JX5rWPi-1B`xnh5|qfQqA%Z%^C>psDoWpeI%%q< zHTASW8dcM;vW%^k(9^EPyuv^0u zoi7#RU22Bc$AU!4BAuE}${Cmy{GF;P71={U(*jZ8s_!!q1v$2JK`OX(y)99 z5a(44wC&bFwk{%fUS{GM+YAuBHr0ABqhW5CEgT&psRPnztnG;bM?u$7tbU5H=XUu3=k1*;L*_{nQm)sM-gy(Y|aam+%3+`g1d zoOegfiulb!n3Hx|{R+b>So5!z%|_Ts)5wLpW6Pe9HH5t;y{cXF@6wI6KSKu`x0%a% Q%K!iX07*qoM6N<$g4Jj{od5s; literal 0 HcmV?d00001 diff --git a/website/server/models/group.js b/website/server/models/group.js index 636eb3ca0e..ef05e89c12 100644 --- a/website/server/models/group.js +++ b/website/server/models/group.js @@ -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,27 @@ schema.methods.finishQuest = async function finishQuest (quest) { }); }); + _.forEach(questSeriesAchievements, async (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: {}}; + questAchievementUpdate.$set[`achievements.${achievement}`] = true; + + 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) { diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index e411bfb437..de53c5a214 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -120,6 +120,10 @@ let schema = new Schema({ joinedChallenge: Boolean, invitedFriend: Boolean, lostMasterclasser: Boolean, + mindOverMatter: Boolean, + justAddWater: Boolean, + backToBasics: Boolean, + allYourBase: Boolean, }, backer: {