From c0bf2cffeaebb2a39edce5df3ca3e1a49b772edc Mon Sep 17 00:00:00 2001 From: Jalansh Date: Sun, 9 Aug 2020 21:55:59 +0530 Subject: [PATCH] Casting Chilling Frost and Stealth skill again will not be processed and return an error instead. Fixes #12361. (#12404) * Added logic for a repeating Chilling Frost skill. Added test case for redundant chilling frost skill cast. Added comments for the logic of repeating Stealth skill because of an error. * Added logic for a repeating Stealth skill. Avoiding MP reduction still pending because of console error. Test cases pending. * Completed the logic for a repeated Stealth skill. Added repeated frost skill cast check in common. Removed exclusive test. Test cases are pending. * Added test case for Stealth skill recast. Fixed lint errors. Fixed a flaw in if statement which led to test case failure. * Fixed lint errors in test case. * Added a common JSON entry for skil recasts in three files. Other files remaining. Added Chilling Frost recast check in common code. Modified test cases. * Added spellDisabled condition in client code. * Reverted JSON messages for three languages. Added spellAlreadyCast attribute to JSON file in locales/en. Made changes for showing appropriate message in client code. * Added an import for throwing BadRequest in common code. Modified test case accordingly. * Update website/common/script/content/spells.js Co-authored-by: Matteo Pagliazzi * Added target and req attributes in cast() method arguments. * Changed common code test case because of increased function parameters. Moved chilling frost test casse to common tests instead of server tests. * Changed the test case format in common tests. * Added a missing done statement. * Fixed a minor error which led to failing test case. Removed the exclusive test which led to lint error. * Fixed lint errors. * Added a class named 'disabled' for the frontend change. * fix(skills): style cleanup * fix(skills): unfix Co-authored-by: Matteo Pagliazzi Co-authored-by: Sabe Jones --- .../user/POST-user_class_cast_spellId.test.js | 17 +++++++++++++++ test/common/ops/spells.js | 21 ++++++++++++++++++- .../client/src/components/tasks/spells.vue | 21 +++++++++++-------- website/common/locales/en/spells.json | 5 ++--- website/common/script/content/spells.js | 11 ++++++---- website/server/libs/spells.js | 14 +++++++++++++ 6 files changed, 72 insertions(+), 17 deletions(-) diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js index 56c6a0a940..78a9d0d4b8 100644 --- a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js +++ b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js @@ -161,6 +161,23 @@ describe('POST /user/class/cast/:spellId', () => { }); }); + it('Issue #12361: returns an error if stealth has already been cast', async () => { + await user.update({ + 'stats.class': 'rogue', + 'stats.lvl': 15, + 'stats.mp': 400, + 'stats.buffs.stealth': 1, + }); + await user.sync(); + await expect(user.post('/user/class/cast/stealth')) + .to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: t('spellAlreadyCast'), + }); + expect(user.stats.mp).to.equal(400); + }); + it('returns an error if targeted party member doesn\'t exist', async () => { const { groupLeader } = await createAndPopulateGroup({ groupDetails: { type: 'party', privacy: 'private' }, diff --git a/test/common/ops/spells.js b/test/common/ops/spells.js index 939b7caab5..1f185c37f2 100644 --- a/test/common/ops/spells.js +++ b/test/common/ops/spells.js @@ -4,6 +4,7 @@ import { import spells from '../../../website/common/script/content/spells'; import { NotAuthorized, + BadRequest, } from '../../../website/common/script/libs/errors'; import i18n from '../../../website/common/script/i18n'; @@ -25,7 +26,7 @@ describe('shared.ops.spells', () => { const spell = spells.healer.heal; try { - spell.cast(user); + spell.cast(user, null, { language: 'en' }); } catch (err) { expect(err).to.be.an.instanceof(NotAuthorized); expect(err.message).to.equal(i18n.t('messageHealthAlreadyMax')); @@ -35,4 +36,22 @@ describe('shared.ops.spells', () => { done(); } }); + + it('Issue #12361: returns an error if chilling frost has already been cast', done => { + user.stats.class = 'wizard'; + user.stats.lvl = 15; + user.stats.mp = 400; + user.stats.buffs.streaks = true; + + const spell = spells.wizard.frost; + try { + spell.cast(user, null, { language: 'en' }); + } catch (err) { + expect(err).to.be.an.instanceof(BadRequest); + expect(err.message).to.equal(i18n.t('spellAlreadyCast')); + expect(user.stats.mp).to.eql(400); + + done(); + } + }); }); diff --git a/website/client/src/components/tasks/spells.vue b/website/client/src/components/tasks/spells.vue index 481539a9d6..216e85c613 100644 --- a/website/client/src/components/tasks/spells.vue +++ b/website/client/src/components/tasks/spells.vue @@ -42,12 +42,14 @@ :key="key" v-b-popover.hover.auto="skillNotes(skill)" class="col-12 col-md-3" - @click="castStart(skill)" + @click="!spellDisabled(key) ? castStart(skill) : null" > -
+
-
Number of dailies that will be avoided: <%= number %>.", - "spellRogueStealthMaxedOut": "You have already avoided all your dailies; there's no need to cast this again.", "spellHealerHealText": "Healing Light", "spellHealerHealNotes": "Shining light restores your health! (Based on: CON and INT)", @@ -76,5 +74,6 @@ "challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.", "groupTasksNoCast": "Casting a skill on group tasks is not allowed.", "spellNotOwned": "You don't own this skill.", - "spellLevelTooHigh": "You must be level <%= level %> to use this skill." + "spellLevelTooHigh": "You must be level <%= level %> to use this skill.", + "spellAlreadyCast": "You've cast this skill already. It won't have any additional effect." } diff --git a/website/common/script/content/spells.js b/website/common/script/content/spells.js index 29d6c019af..93940c3ba5 100644 --- a/website/common/script/content/spells.js +++ b/website/common/script/content/spells.js @@ -1,6 +1,6 @@ import each from 'lodash/each'; import t from './translation'; -import { NotAuthorized } from '../libs/errors'; +import { NotAuthorized, BadRequest } from '../libs/errors'; import statsComputed from '../libs/statsComputed'; // eslint-disable-line import/no-cycle import setDebuffPotionItems from '../libs/setDebuffPotionItems'; // eslint-disable-line import/no-cycle import crit from '../fns/crit'; // eslint-disable-line import/no-cycle @@ -104,7 +104,10 @@ spells.wizard = { lvl: 14, target: 'self', notes: t('spellWizardFrostNotes'), - cast (user) { + cast (user, target, req) { + // Check if chilling frost skill has been previously casted or not. + // See #12361 for more details. + if (user.stats.buffs.streaks === true) throw new BadRequest(t('spellAlreadyCast')(req.language)); user.stats.buffs.streaks = true; }, }, @@ -226,8 +229,8 @@ spells.healer = { lvl: 11, target: 'self', notes: t('spellHealerHealNotes'), - cast (user) { - if (user.stats.hp >= 50) throw new NotAuthorized(t('messageHealthAlreadyMax')(user.language)); + cast (user, target, req) { + if (user.stats.hp >= 50) throw new NotAuthorized(t('messageHealthAlreadyMax')(req.language)); user.stats.hp += (statsComputed(user).con + statsComputed(user).int + 5) * 0.075; if (user.stats.hp > 50) user.stats.hp = 50; }, diff --git a/website/server/libs/spells.js b/website/server/libs/spells.js index 231402a453..c185bbf471 100644 --- a/website/server/libs/spells.js +++ b/website/server/libs/spells.js @@ -163,6 +163,20 @@ async function castSpell (req, res, { isV3 = false }) { task: results[1], }); } else if (targetType === 'self') { + const spellName = spell.key; + // Check if stealth skill has been previously casted or not. + // See #12361 for more details. + if (spellName === 'stealth') { + const incompleteDailiesDue = await Tasks.Task.countDocuments({ + userId: user._id, + type: 'daily', + completed: false, + isDue: true, + }).exec(); + if (user.stats.buffs.stealth >= incompleteDailiesDue) { + throw new BadRequest(res.t('spellAlreadyCast')); + } + } await castSelfSpell(req, user, spell, quantity); let userToJson = user;