From 5b57d91a9b66b0cae015084d8f152283b356e51d Mon Sep 17 00:00:00 2001 From: Aleksey Date: Sun, 6 Oct 2019 19:41:39 +0300 Subject: [PATCH] Fix: Antidotes to Avatar Transformation Items should be added to Rewards by API (#11353) * Fix: moved debuffPotions from vue component - Move logic of choosing proper debuf potion from vue component to website commons - introduce new function to get debuffSpellItems * Fix: move debuffPotions to server * Refactoring: move setting of debuff potion to func * Fix: sanity * Refactoring & Tests: - Create test case for get and set DebuffPotionItems functions - Fix setDebuffPotionItems function to not create duplicated debuff items - Make debuff potion type of items unpinnable - Move list of debuffs to constant to reuse it in tests and functions * Fix: typo in test describe * Fix: translation of unpin * Fix: setDebuffPotionItems on cron buffs reset * Fix: use full path for debuff potions --- .../user/GET-user_toggle-pinned-item.test.js | 2 +- test/common/libs/getDebuffPotionItems.test.js | 47 ++++++++++++++++ test/common/libs/setDebuffPotionItems.test.js | 53 +++++++++++++++++++ website/client/components/tasks/column.vue | 21 -------- website/common/locales/en/npc.json | 2 +- website/common/script/constants.js | 7 +++ website/common/script/content/spells.js | 7 +++ website/common/script/index.js | 6 +++ .../script/libs/getDebuffPotionItems.js | 22 ++++++++ website/common/script/libs/getItemInfo.js | 19 +++++++ .../script/libs/setDebuffPotionItems.js | 30 +++++++++++ website/common/script/ops/pinnedGearUtils.js | 5 +- website/server/libs/cron.js | 2 + website/server/libs/spells.js | 26 +++++---- 14 files changed, 214 insertions(+), 35 deletions(-) create mode 100644 test/common/libs/getDebuffPotionItems.test.js create mode 100644 test/common/libs/setDebuffPotionItems.test.js create mode 100644 website/common/script/libs/getDebuffPotionItems.js create mode 100644 website/common/script/libs/setDebuffPotionItems.js diff --git a/test/api/v3/integration/user/GET-user_toggle-pinned-item.test.js b/test/api/v3/integration/user/GET-user_toggle-pinned-item.test.js index c958c4f979..a183cdf9e1 100644 --- a/test/api/v3/integration/user/GET-user_toggle-pinned-item.test.js +++ b/test/api/v3/integration/user/GET-user_toggle-pinned-item.test.js @@ -15,7 +15,7 @@ describe('GET /user/toggle-pinned-item', () => { .to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', - message: t('cannotUnpinArmoirPotion'), + message: t('cannotUnpinItem'), }); }); diff --git a/test/common/libs/getDebuffPotionItems.test.js b/test/common/libs/getDebuffPotionItems.test.js new file mode 100644 index 0000000000..cc7520db0c --- /dev/null +++ b/test/common/libs/getDebuffPotionItems.test.js @@ -0,0 +1,47 @@ + +import { + generateUser, +} from '../../helpers/common.helper'; + +import getDebuffPotionItems from '../../../website/common/script/libs/getDebuffPotionItems'; +import { TRANSFORMATION_DEBUFFS_LIST } from '../../../website/common/script/constants'; + +describe('getDebuffPotionItems', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + for (let key in TRANSFORMATION_DEBUFFS_LIST) { + const debuff = TRANSFORMATION_DEBUFFS_LIST[key]; + // Here we itterate whole object to dynamicaly create test suites as it described in dock of mocha + // https://mochajs.org/#dynamically-generating-tests + // That's why we have eslint-disable here + // eslint-disable-next-line no-loop-func + it(`Should return the ${debuff} on ${key} buff`, () => { + user.stats.buffs[key] = true; + + let result = getDebuffPotionItems(user); + + expect(result).to.be.an('array').that.deep.includes({path: `spells.special.${debuff}`, type: 'debuffPotion'}); + }); + } + + it('Should return all debuff potions for all buffs', () => { + user.stats.buffs.seafoam = true; + user.stats.buffs.spookySparkles = true; + user.stats.buffs.snowball = true; + user.stats.buffs.shinySeed = true; + + + let result = getDebuffPotionItems(user); + + expect(result).to.be.an('array').that.deep.include.members([ + {path: 'spells.special.sand', type: 'debuffPotion'}, + {path: 'spells.special.petalFreePotion', type: 'debuffPotion'}, + {path: 'spells.special.salt', type: 'debuffPotion'}, + {path: 'spells.special.opaquePotion', type: 'debuffPotion'}, + ]); + }); +}); diff --git a/test/common/libs/setDebuffPotionItems.test.js b/test/common/libs/setDebuffPotionItems.test.js new file mode 100644 index 0000000000..f3313b85b0 --- /dev/null +++ b/test/common/libs/setDebuffPotionItems.test.js @@ -0,0 +1,53 @@ + +import { + generateUser, +} from '../../helpers/common.helper'; + +import setDebuffPotionItems from '../../../website/common/script/libs/setDebuffPotionItems'; + +describe('setDebuffPotionItems', () => { + let user; + + beforeEach(() => { + user = generateUser(); + }); + + it('Should push the debuff item to pinned items of user', () => { + user.stats.buffs.spookySparkles = true; + const previousPinnedItemsLength = user.pinnedItems.length; + + let result = setDebuffPotionItems(user); + + expect(result.pinnedItems.length).to.be.greaterThan(previousPinnedItemsLength); + }); + + it('Shouldn\'t create duplicate of already added debuff potion', () => { + user.stats.buffs.spookySparkles = true; + + const firstSetResult = [...setDebuffPotionItems(user).pinnedItems]; + const secondSetResult = [...setDebuffPotionItems(user).pinnedItems]; + + + expect(firstSetResult).to.be.deep.equal(secondSetResult); + }); + + it('Should remove all debuff items from pinnedItems of the user if user have no buffs', () => { + user.stats.buffs.seafoam = true; + user.stats.buffs.spookySparkles = true; + user.stats.buffs.snowball = true; + user.stats.buffs.shinySeed = true; + + const firstSetResult = [...setDebuffPotionItems(user).pinnedItems]; + + expect(firstSetResult).to.have.lengthOf(4); + + user.stats.buffs.seafoam = false; + user.stats.buffs.spookySparkles = false; + user.stats.buffs.snowball = false; + user.stats.buffs.shinySeed = false; + + const secondSetResult = [...setDebuffPotionItems(user).pinnedItems]; + + expect(secondSetResult).to.have.lengthOf(0); + }); +}); diff --git a/website/client/components/tasks/column.vue b/website/client/components/tasks/column.vue index ef5faa49de..95cc8499f3 100644 --- a/website/client/components/tasks/column.vue +++ b/website/client/components/tasks/column.vue @@ -273,7 +273,6 @@ import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue'; import notifications from 'client/mixins/notifications'; import { shouldDo } from 'common/script/cron'; import inAppRewards from 'common/script/libs/inAppRewards'; -import spells from 'common/script/content/spells'; import taskDefaults from 'common/script/libs/taskDefaults'; import { @@ -383,26 +382,6 @@ export default { let watchRefresh = this.forceRefresh; // eslint-disable-line let rewards = inAppRewards(this.user); - // Add season rewards if user is affected - // @TODO: Add buff conditional - const seasonalSkills = { - snowball: 'salt', - spookySparkles: 'opaquePotion', - shinySeed: 'petalFreePotion', - seafoam: 'sand', - }; - - for (let key in seasonalSkills) { - if (this.getUserBuffs(key)) { - let debuff = seasonalSkills[key]; - let item = Object.assign({}, spells.special[debuff]); - item.text = item.text(); - item.notes = item.notes(); - item.class = `shop_${key}`; - rewards.push(item); - } - } - return rewards; }, hasRewardsList () { diff --git a/website/common/locales/en/npc.json b/website/common/locales/en/npc.json index ed1355d562..41dcbf3016 100644 --- a/website/common/locales/en/npc.json +++ b/website/common/locales/en/npc.json @@ -76,7 +76,7 @@ "wrongItemType": "The item type \"<%= type %>\" is not valid.", "wrongItemPath": "The item path \"<%= path %>\" is not valid.", "unpinnedItem": "You unpinned <%= item %>! It will no longer display in your Rewards column.", - "cannotUnpinArmoirPotion": "The Health Potion and Enchanted Armoire cannot be unpinned.", + "cannotUnpinItem": "This item cannot be unpinned.", "purchasedItem": "You bought <%= itemName %>", "ian": "Ian", diff --git a/website/common/script/constants.js b/website/common/script/constants.js index 8985bea89e..262596bf87 100644 --- a/website/common/script/constants.js +++ b/website/common/script/constants.js @@ -23,3 +23,10 @@ export const SUPPORTED_SOCIAL_NETWORKS = [ export const GUILDS_PER_PAGE = 30; // number of guilds to return per page when using pagination export const PARTY_LIMIT_MEMBERS = 30; + +export const TRANSFORMATION_DEBUFFS_LIST = { + snowball: 'salt', + spookySparkles: 'opaquePotion', + shinySeed: 'petalFreePotion', + seafoam: 'sand', +}; diff --git a/website/common/script/content/spells.js b/website/common/script/content/spells.js index 58d69d3755..d17e708730 100644 --- a/website/common/script/content/spells.js +++ b/website/common/script/content/spells.js @@ -289,6 +289,8 @@ spells.special = { cast (user) { user.stats.buffs.snowball = false; user.stats.gp -= 5; + // Remove antidote from pinned items + user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.salt')); }, }, spookySparkles: { @@ -320,6 +322,8 @@ spells.special = { cast (user) { user.stats.buffs.spookySparkles = false; user.stats.gp -= 5; + // Remove antidote from pinned items + user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.opaquePotion')); }, }, shinySeed: { @@ -351,6 +355,8 @@ spells.special = { cast (user) { user.stats.buffs.shinySeed = false; user.stats.gp -= 5; + // Remove antidote from pinned items + user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.petalFreePotion')); }, }, seafoam: { @@ -382,6 +388,7 @@ spells.special = { cast (user) { user.stats.buffs.seafoam = false; user.stats.gp -= 5; + user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.sand')); }, }, nye: { diff --git a/website/common/script/index.js b/website/common/script/index.js index b636c94bc2..ea552db6a0 100644 --- a/website/common/script/index.js +++ b/website/common/script/index.js @@ -77,6 +77,12 @@ api.updateStore = updateStore; import inAppRewards from './libs/inAppRewards'; api.inAppRewards = inAppRewards; +import setDebuffPotionItems from './libs/setDebuffPotionItems'; +api.setDebuffPotionItems = setDebuffPotionItems; + +import getDebuffPotionItems from './libs/getDebuffPotionItems'; +api.getDebuffPotionItems = getDebuffPotionItems; + import uuid from './libs/uuid'; api.uuid = uuid; diff --git a/website/common/script/libs/getDebuffPotionItems.js b/website/common/script/libs/getDebuffPotionItems.js new file mode 100644 index 0000000000..8417f437e9 --- /dev/null +++ b/website/common/script/libs/getDebuffPotionItems.js @@ -0,0 +1,22 @@ +import { TRANSFORMATION_DEBUFFS_LIST } from '../constants'; + +module.exports = function getDebuffPotionItems (user) { + const items = []; + const userBuffs = user.stats.buffs; + + if (user) { + for (let key in TRANSFORMATION_DEBUFFS_LIST) { + if (userBuffs[key]) { + let debuff = TRANSFORMATION_DEBUFFS_LIST[key]; + const item = { + path: `spells.special.${debuff}`, + type: 'debuffPotion', + }; + items.push(item); + } + } + + + return items; + } +}; diff --git a/website/common/script/libs/getItemInfo.js b/website/common/script/libs/getItemInfo.js index 85fac7ef65..d71b99f0d1 100644 --- a/website/common/script/libs/getItemInfo.js +++ b/website/common/script/libs/getItemInfo.js @@ -177,6 +177,25 @@ module.exports = function getItemInfo (user, type, item, officialPinnedItems, la pinType: 'seasonalSpell', }; break; + case 'debuffPotion': + itemInfo = { + key: item.key, + mana: item.mana, + cast: item.cast, + immediateUse: item.immediateUse, + target: item.target, + text: item.text(language), + notes: item.notes(language), + value: item.value, + type: 'debuffPotion', + currency: 'gold', + locked: false, + purchaseType: 'debuffPotion', + class: `inventory_special_${item.key}`, + path: `spells.special.${item.key}`, + pinType: 'debuffPotion', + }; + break; case 'seasonalQuest': itemInfo = { key: item.key, diff --git a/website/common/script/libs/setDebuffPotionItems.js b/website/common/script/libs/setDebuffPotionItems.js new file mode 100644 index 0000000000..5386698252 --- /dev/null +++ b/website/common/script/libs/setDebuffPotionItems.js @@ -0,0 +1,30 @@ +import getDebuffPotionItems from './getDebuffPotionItems'; + + +module.exports = function setDebuffPotionItems (user) { + const debuffPotionItems = getDebuffPotionItems(user); + + if (debuffPotionItems.length) { + let isPresent = false; + const isUserHaveDebuffInPinnedItems = user.pinnedItems.find(pinnedItem => { + debuffPotionItems.forEach(debuffPotion => { + if (!isPresent) { + isPresent = debuffPotion.path === pinnedItem.path; + } + }); + return isPresent; + }); + + if (!isUserHaveDebuffInPinnedItems) { + user.pinnedItems.push(...debuffPotionItems); + } + } else { + user.pinnedItems = user.pinnedItems.filter(item => { + return item.type !== 'debuffPotion'; + }); + } + + return user; +}; + + diff --git a/website/common/script/ops/pinnedGearUtils.js b/website/common/script/ops/pinnedGearUtils.js index e3deed562c..23dd66a9c2 100644 --- a/website/common/script/ops/pinnedGearUtils.js +++ b/website/common/script/ops/pinnedGearUtils.js @@ -146,8 +146,9 @@ function togglePinnedItem (user, {item, type, path}, req = {}) { } - if (path === 'armoire' || path === 'potion') { - throw new BadRequest(i18n.t('cannotUnpinArmoirPotion', req.language)); + if (path === 'armoire' || path === 'potion' || type === 'debuffPotion') { + // @TODO: take into considertation debuffPotion type in message + throw new BadRequest(i18n.t('cannotUnpinItem', req.language)); } const isOfficialPinned = pathExistsInArray(officialPinnedItems, path) !== -1; diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js index 2e01b1a917..83e84e8ffa 100644 --- a/website/server/libs/cron.js +++ b/website/server/libs/cron.js @@ -456,6 +456,8 @@ export function cron (options = {}) { user.stats.buffs = _.cloneDeep(CLEAR_BUFFS); } + common.setDebuffPotionItems(user); + // Add 10 MP, or 10% of max MP if that'd be more. Perform this after Perfect Day for maximum benefit // Adjust for fraction of dailies completed if (!user.preferences.sleep) { diff --git a/website/server/libs/spells.js b/website/server/libs/spells.js index 8c46fcb1ab..810366ca46 100644 --- a/website/server/libs/spells.js +++ b/website/server/libs/spells.js @@ -68,6 +68,9 @@ async function castSelfSpell (req, user, spell, quantity = 1) { for (let i = 0; i < quantity; i += 1) { spell.cast(user, null, req); } + + common.setDebuffPotionItems(user); + await user.save(); } @@ -94,34 +97,37 @@ async function castPartySpell (req, party, partyMembers, user, spell, quantity = return partyMembers; } -async function castUserSpell (res, req, party, partyMembers, targetId, user, spell, quantity = 1) { +async function castUserSpell (res, req, party, partyMember, targetId, user, spell, quantity = 1) { if (!party && (!targetId || user._id === targetId)) { - partyMembers = user; + partyMember = user; } else { if (!targetId) throw new BadRequest(res.t('targetIdUUID')); if (!party) throw new NotFound(res.t('partyNotFound')); - partyMembers = await User + partyMember = await User .findOne({_id: targetId, 'party._id': party._id}) - .select(partyMembersFields) + // We need all fields due to adding debuf spell to pinned items of target of the spell + // .select(partyMembersFields) .exec(); } - if (!partyMembers) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); + if (!partyMember) throw new NotFound(res.t('userWithIDNotFound', {userId: targetId})); for (let i = 0; i < quantity; i += 1) { - spell.cast(user, partyMembers, req); + spell.cast(user, partyMember, req); } - if (partyMembers !== user) { + common.setDebuffPotionItems(partyMember); + + if (partyMember !== user) { await Promise.all([ user.save(), - partyMembers.save(), + partyMember.save(), ]); } else { - await partyMembers.save(); // partyMembers is user + await partyMember.save(); // partyMembers is user } - return partyMembers; + return partyMember; } async function castSpell (req, res, {isV3 = false}) {