mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-15 05:37:22 +01:00
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
This commit is contained in:
committed by
Matteo Pagliazzi
parent
85eab76a71
commit
5b57d91a9b
@@ -15,7 +15,7 @@ describe('GET /user/toggle-pinned-item', () => {
|
|||||||
.to.eventually.be.rejected.and.eql({
|
.to.eventually.be.rejected.and.eql({
|
||||||
code: 400,
|
code: 400,
|
||||||
error: 'BadRequest',
|
error: 'BadRequest',
|
||||||
message: t('cannotUnpinArmoirPotion'),
|
message: t('cannotUnpinItem'),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
47
test/common/libs/getDebuffPotionItems.test.js
Normal file
47
test/common/libs/getDebuffPotionItems.test.js
Normal file
@@ -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'},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
53
test/common/libs/setDebuffPotionItems.test.js
Normal file
53
test/common/libs/setDebuffPotionItems.test.js
Normal file
@@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -273,7 +273,6 @@ import BuyQuestModal from 'client/components/shops/quests/buyQuestModal.vue';
|
|||||||
import notifications from 'client/mixins/notifications';
|
import notifications from 'client/mixins/notifications';
|
||||||
import { shouldDo } from 'common/script/cron';
|
import { shouldDo } from 'common/script/cron';
|
||||||
import inAppRewards from 'common/script/libs/inAppRewards';
|
import inAppRewards from 'common/script/libs/inAppRewards';
|
||||||
import spells from 'common/script/content/spells';
|
|
||||||
import taskDefaults from 'common/script/libs/taskDefaults';
|
import taskDefaults from 'common/script/libs/taskDefaults';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -383,26 +382,6 @@ export default {
|
|||||||
let watchRefresh = this.forceRefresh; // eslint-disable-line
|
let watchRefresh = this.forceRefresh; // eslint-disable-line
|
||||||
let rewards = inAppRewards(this.user);
|
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;
|
return rewards;
|
||||||
},
|
},
|
||||||
hasRewardsList () {
|
hasRewardsList () {
|
||||||
|
|||||||
@@ -76,7 +76,7 @@
|
|||||||
"wrongItemType": "The item type \"<%= type %>\" is not valid.",
|
"wrongItemType": "The item type \"<%= type %>\" is not valid.",
|
||||||
"wrongItemPath": "The item path \"<%= path %>\" is not valid.",
|
"wrongItemPath": "The item path \"<%= path %>\" is not valid.",
|
||||||
"unpinnedItem": "You unpinned <%= item %>! It will no longer display in your Rewards column.",
|
"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 %>",
|
"purchasedItem": "You bought <%= itemName %>",
|
||||||
|
|
||||||
"ian": "Ian",
|
"ian": "Ian",
|
||||||
|
|||||||
@@ -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 GUILDS_PER_PAGE = 30; // number of guilds to return per page when using pagination
|
||||||
|
|
||||||
export const PARTY_LIMIT_MEMBERS = 30;
|
export const PARTY_LIMIT_MEMBERS = 30;
|
||||||
|
|
||||||
|
export const TRANSFORMATION_DEBUFFS_LIST = {
|
||||||
|
snowball: 'salt',
|
||||||
|
spookySparkles: 'opaquePotion',
|
||||||
|
shinySeed: 'petalFreePotion',
|
||||||
|
seafoam: 'sand',
|
||||||
|
};
|
||||||
|
|||||||
@@ -289,6 +289,8 @@ spells.special = {
|
|||||||
cast (user) {
|
cast (user) {
|
||||||
user.stats.buffs.snowball = false;
|
user.stats.buffs.snowball = false;
|
||||||
user.stats.gp -= 5;
|
user.stats.gp -= 5;
|
||||||
|
// Remove antidote from pinned items
|
||||||
|
user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.salt'));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
spookySparkles: {
|
spookySparkles: {
|
||||||
@@ -320,6 +322,8 @@ spells.special = {
|
|||||||
cast (user) {
|
cast (user) {
|
||||||
user.stats.buffs.spookySparkles = false;
|
user.stats.buffs.spookySparkles = false;
|
||||||
user.stats.gp -= 5;
|
user.stats.gp -= 5;
|
||||||
|
// Remove antidote from pinned items
|
||||||
|
user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.opaquePotion'));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
shinySeed: {
|
shinySeed: {
|
||||||
@@ -351,6 +355,8 @@ spells.special = {
|
|||||||
cast (user) {
|
cast (user) {
|
||||||
user.stats.buffs.shinySeed = false;
|
user.stats.buffs.shinySeed = false;
|
||||||
user.stats.gp -= 5;
|
user.stats.gp -= 5;
|
||||||
|
// Remove antidote from pinned items
|
||||||
|
user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.petalFreePotion'));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
seafoam: {
|
seafoam: {
|
||||||
@@ -382,6 +388,7 @@ spells.special = {
|
|||||||
cast (user) {
|
cast (user) {
|
||||||
user.stats.buffs.seafoam = false;
|
user.stats.buffs.seafoam = false;
|
||||||
user.stats.gp -= 5;
|
user.stats.gp -= 5;
|
||||||
|
user.pinnedItems = user.pinnedItems.filter(item => !item.path.includes('spells.special.sand'));
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
nye: {
|
nye: {
|
||||||
|
|||||||
@@ -77,6 +77,12 @@ api.updateStore = updateStore;
|
|||||||
import inAppRewards from './libs/inAppRewards';
|
import inAppRewards from './libs/inAppRewards';
|
||||||
api.inAppRewards = 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';
|
import uuid from './libs/uuid';
|
||||||
api.uuid = uuid;
|
api.uuid = uuid;
|
||||||
|
|
||||||
|
|||||||
22
website/common/script/libs/getDebuffPotionItems.js
Normal file
22
website/common/script/libs/getDebuffPotionItems.js
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -177,6 +177,25 @@ module.exports = function getItemInfo (user, type, item, officialPinnedItems, la
|
|||||||
pinType: 'seasonalSpell',
|
pinType: 'seasonalSpell',
|
||||||
};
|
};
|
||||||
break;
|
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':
|
case 'seasonalQuest':
|
||||||
itemInfo = {
|
itemInfo = {
|
||||||
key: item.key,
|
key: item.key,
|
||||||
|
|||||||
30
website/common/script/libs/setDebuffPotionItems.js
Normal file
30
website/common/script/libs/setDebuffPotionItems.js
Normal file
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -146,8 +146,9 @@ function togglePinnedItem (user, {item, type, path}, req = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (path === 'armoire' || path === 'potion') {
|
if (path === 'armoire' || path === 'potion' || type === 'debuffPotion') {
|
||||||
throw new BadRequest(i18n.t('cannotUnpinArmoirPotion', req.language));
|
// @TODO: take into considertation debuffPotion type in message
|
||||||
|
throw new BadRequest(i18n.t('cannotUnpinItem', req.language));
|
||||||
}
|
}
|
||||||
|
|
||||||
const isOfficialPinned = pathExistsInArray(officialPinnedItems, path) !== -1;
|
const isOfficialPinned = pathExistsInArray(officialPinnedItems, path) !== -1;
|
||||||
|
|||||||
@@ -456,6 +456,8 @@ export function cron (options = {}) {
|
|||||||
user.stats.buffs = _.cloneDeep(CLEAR_BUFFS);
|
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
|
// 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
|
// Adjust for fraction of dailies completed
|
||||||
if (!user.preferences.sleep) {
|
if (!user.preferences.sleep) {
|
||||||
|
|||||||
@@ -68,6 +68,9 @@ async function castSelfSpell (req, user, spell, quantity = 1) {
|
|||||||
for (let i = 0; i < quantity; i += 1) {
|
for (let i = 0; i < quantity; i += 1) {
|
||||||
spell.cast(user, null, req);
|
spell.cast(user, null, req);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
common.setDebuffPotionItems(user);
|
||||||
|
|
||||||
await user.save();
|
await user.save();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,34 +97,37 @@ async function castPartySpell (req, party, partyMembers, user, spell, quantity =
|
|||||||
return partyMembers;
|
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)) {
|
if (!party && (!targetId || user._id === targetId)) {
|
||||||
partyMembers = user;
|
partyMember = user;
|
||||||
} else {
|
} else {
|
||||||
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
|
if (!targetId) throw new BadRequest(res.t('targetIdUUID'));
|
||||||
if (!party) throw new NotFound(res.t('partyNotFound'));
|
if (!party) throw new NotFound(res.t('partyNotFound'));
|
||||||
partyMembers = await User
|
partyMember = await User
|
||||||
.findOne({_id: targetId, 'party._id': party._id})
|
.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();
|
.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) {
|
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([
|
await Promise.all([
|
||||||
user.save(),
|
user.save(),
|
||||||
partyMembers.save(),
|
partyMember.save(),
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
await partyMembers.save(); // partyMembers is user
|
await partyMember.save(); // partyMembers is user
|
||||||
}
|
}
|
||||||
|
|
||||||
return partyMembers;
|
return partyMember;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function castSpell (req, res, {isV3 = false}) {
|
async function castSpell (req, res, {isV3 = false}) {
|
||||||
|
|||||||
Reference in New Issue
Block a user