Separated out buy functions into buyGear, buyArmoire, and buyPotion (#7065)

This commit is contained in:
Keith Holliday
2016-04-19 09:50:04 -05:00
committed by Matteo Pagliazzi
parent f44dbbbd71
commit 9e3d8ba4ac
16 changed files with 877 additions and 409 deletions

View File

@@ -113,14 +113,13 @@
"missingKeyParam": "\"req.params.key\" is required.", "missingKeyParam": "\"req.params.key\" is required.",
"mysterySetNotFound": "Mystery set not found, or set already owned.", "mysterySetNotFound": "Mystery set not found, or set already owned.",
"itemNotFound": "Item \"<%= key %>\" not found.", "itemNotFound": "Item \"<%= key %>\" not found.",
"cannoyBuyItem": "You can't buy this item.", "cannotBuyItem": "You can't buy this item.",
"missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.", "missingTypeKeyEquip": "\"key\" and \"type\" are required parameters.",
"missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.", "missingPetFoodFeed": "\"pet\" and \"food\" are required parameters.",
"invalidPetName": "Invalid pet name supplied.", "invalidPetName": "Invalid pet name supplied.",
"missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.", "missingEggHatchingPotionHatch": "\"egg\" and \"hatchingPotion\" are required parameters.",
"invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.", "invalidTypeEquip": "\"type\" must be one of 'equipped', 'pet', 'mount', 'costume'.",
"cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.", "cannotDeleteActiveAccount": "You have an active subscription, cancel your plan before deleting your account.",
"cannoyBuyItem": "You can't buy this item",
"messageRequired": "A message is required.", "messageRequired": "A message is required.",
"toUserIDRequired": "A toUserId is required", "toUserIDRequired": "A toUserId is required",
"notAuthorizedToSendMessageToThisUser": "Can't send message to this user.", "notAuthorizedToSendMessageToThisUser": "Can't send message to this user.",
@@ -170,5 +169,6 @@
"pushDeviceAdded": "Push device added successfully", "pushDeviceAdded": "Push device added successfully",
"pushDeviceAlreadyAdded": "The user already has the push device", "pushDeviceAlreadyAdded": "The user already has the push device",
"resetComplete": "Reset completed", "resetComplete": "Reset completed",
"lvl10ChangeClass": "To change class you must be at least level 10." "lvl10ChangeClass": "To change class you must be at least level 10.",
"equipmentAlreadyOwned": "You already own that piece of equipment"
} }

View File

@@ -113,6 +113,9 @@ import scoreTask from './ops/scoreTask';
import sleep from './ops/sleep'; import sleep from './ops/sleep';
import allocate from './ops/allocate'; import allocate from './ops/allocate';
import buy from './ops/buy'; import buy from './ops/buy';
import buyGear from './ops/buyGear';
import buyPotion from './ops/buyPotion';
import buyArmoire from './ops/buyArmoire';
import buyMysterySet from './ops/buyMysterySet'; import buyMysterySet from './ops/buyMysterySet';
import buyQuest from './ops/buyQuest'; import buyQuest from './ops/buyQuest';
import buySpecialSpell from './ops/buySpecialSpell'; import buySpecialSpell from './ops/buySpecialSpell';
@@ -150,6 +153,9 @@ api.ops = {
sleep, sleep,
allocate, allocate,
buy, buy,
buyGear,
buyPotion,
buyArmoire,
buyMysterySet, buyMysterySet,
buySpecialSpell, buySpecialSpell,
buyQuest, buyQuest,
@@ -266,6 +272,9 @@ api.wrap = function wrapUser (user, main = true) {
releaseMounts: _.partial(importedOps.releaseMounts, user), releaseMounts: _.partial(importedOps.releaseMounts, user),
releaseBoth: _.partial(importedOps.releaseBoth, user), releaseBoth: _.partial(importedOps.releaseBoth, user),
buy: _.partial(importedOps.buy, user), buy: _.partial(importedOps.buy, user),
buyPotion: _.partial(importedOps.buyPotion, user),
buyArmoire: _.partial(importedOps.buyArmoire, user),
buyGear: _.partial(importedOps.buyGear, user),
buyQuest: _.partial(importedOps.buyQuest, user), buyQuest: _.partial(importedOps.buyQuest, user),
buyMysterySet: _.partial(importedOps.buyMysterySet, user), buyMysterySet: _.partial(importedOps.buyMysterySet, user),
hourglassPurchase: _.partial(importedOps.hourglassPurchase, user), hourglassPurchase: _.partial(importedOps.hourglassPurchase, user),

View File

@@ -1,142 +1,24 @@
import content from '../content/index';
import i18n from '../i18n'; import i18n from '../i18n';
import _ from 'lodash'; import _ from 'lodash';
import count from '../count';
import splitWhitespace from '../libs/splitWhitespace';
import { import {
BadRequest, BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors'; } from '../libs/errors';
import predictableRandom from '../fns/predictableRandom'; import buyPotion from './buyPotion';
import randomVal from '../fns/randomVal'; import buyArmoire from './buyArmoire';
import handleTwoHanded from '../fns/handleTwoHanded'; import buyGear from './buyGear';
import ultimateGear from '../fns/ultimateGear';
module.exports = function buy (user, req = {}, analytics) { module.exports = function buy (user, req = {}, analytics) {
let key = _.get(req, 'params.key'); let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language)); if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let item; let buyRes;
if (key === 'potion') { if (key === 'potion') {
item = content.potion; buyRes = buyPotion(user, req, analytics);
} else if (key === 'armoire') { } else if (key === 'armoire') {
item = content.armoire; buyRes = buyArmoire(user, req, analytics);
} else { } else {
item = content.gear.flat[key]; buyRes = buyGear(user, req, analytics);
}
if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language));
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
} }
if (item.canOwn && !item.canOwn(user)) { return buyRes;
throw new NotAuthorized(i18n.t('cannoyBuyItem', req.language));
}
let armoireResp;
let armoireResult;
let eligibleEquipment;
let drop;
let message;
if (item.key === 'potion') {
user.stats.hp += 15;
if (user.stats.hp > 50) {
user.stats.hp = 50;
}
} else if (item.key === 'armoire') {
armoireResult = predictableRandom(user, user.stats.gp);
eligibleEquipment = _.filter(content.gear.flat, (eligible) => {
return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key];
});
if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) {
eligibleEquipment.sort();
drop = randomVal(user, eligibleEquipment);
user.items.gear.owned[drop.key] = true;
user.flags.armoireOpened = true;
message = i18n.t('armoireEquipment', {
image: `<span class="shop_${drop.key} pull-left"></span>`,
dropText: drop.text(req.language),
}, req.language);
if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
user.flags.armoireEmpty = true;
}
armoireResp = {
type: 'gear',
dropKey: drop.key,
dropText: drop.text(req.language),
};
} else if ((!_.isEmpty(eligibleEquipment) && armoireResult < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens
drop = randomVal(user, _.where(content.food, {
canDrop: true,
}));
user.items.food[drop.key] = user.items.food[drop.key] || 0;
user.items.food[drop.key] += 1;
message = i18n.t('armoireFood', {
image: `<span class="Pet_Food_${drop.key} pull-left"></span>`,
dropArticle: drop.article,
dropText: drop.text(req.language),
}, req.language);
armoireResp = {
type: 'food',
dropKey: drop.key,
dropArticle: drop.article,
dropText: drop.text(req.language),
};
} else {
let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10);
user.stats.exp += armoireExp;
message = i18n.t('armoireExp', req.language);
armoireResp = {
type: 'experience',
value: armoireExp,
};
}
} else {
if (user.preferences.autoEquip) {
user.items.gear.equipped[item.type] = item.key;
message = handleTwoHanded(user, item, undefined, req);
}
user.items.gear.owned[item.key] = true;
if (item.last) ultimateGear(user);
}
user.stats.gp -= item.value;
if (!message) {
message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
}
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: key,
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
let res = {
data: _.pick(user, splitWhitespace('items achievements stats flags')),
message,
};
if (armoireResp) res.armoire = armoireResp;
if (req.v2 === true) {
return res.data;
} else {
return res;
}
}; };

View File

@@ -0,0 +1,116 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import count from '../count';
import splitWhitespace from '../libs/splitWhitespace';
import {
NotAuthorized,
} from '../libs/errors';
import predictableRandom from '../fns/predictableRandom';
import randomVal from '../fns/randomVal';
module.exports = function buyArmoire (user, req = {}, analytics) {
let item = content.armoire;
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
let armoireResp;
let armoireResult;
let eligibleEquipment;
let drop;
let message;
armoireResult = predictableRandom(user, user.stats.gp);
eligibleEquipment = _.filter(content.gear.flat, (eligible) => {
return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key];
});
if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) {
eligibleEquipment.sort();
drop = randomVal(user, eligibleEquipment);
if (user.items.gear.owned[drop.key]) {
throw new NotAuthorized(i18n.t('equipmentAlradyOwned', req.language));
}
user.items.gear.owned[drop.key] = true;
user.flags.armoireOpened = true;
message = i18n.t('armoireEquipment', {
image: `<span class="shop_${drop.key} pull-left"></span>`,
dropText: drop.text(req.language),
}, req.language);
if (count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
user.flags.armoireEmpty = true;
}
armoireResp = {
type: 'gear',
dropKey: drop.key,
dropText: drop.text(req.language),
};
} else if ((!_.isEmpty(eligibleEquipment) && armoireResult < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens
drop = randomVal(user, _.where(content.food, {
canDrop: true,
}));
user.items.food[drop.key] = user.items.food[drop.key] || 0;
user.items.food[drop.key] += 1;
message = i18n.t('armoireFood', {
image: `<span class="Pet_Food_${drop.key} pull-left"></span>`,
dropArticle: drop.article,
dropText: drop.text(req.language),
}, req.language);
armoireResp = {
type: 'food',
dropKey: drop.key,
dropArticle: drop.article,
dropText: drop.text(req.language),
};
} else {
let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10);
user.stats.exp += armoireExp;
message = i18n.t('armoireExp', req.language);
armoireResp = {
type: 'experience',
value: armoireExp,
};
}
user.stats.gp -= item.value;
if (!message) {
message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
}
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: 'Armoire',
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
let res = {
data: _.pick(user, splitWhitespace('items flags')),
message,
};
if (armoireResp) res.armoire = armoireResp;
if (req.v2 === true) {
return res.data;
} else {
return res;
}
};

View File

@@ -0,0 +1,72 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors';
import handleTwoHanded from '../fns/handleTwoHanded';
import ultimateGear from '../fns/ultimateGear';
module.exports = function buyGear (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let item = content.gear.flat[key];
if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language));
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
let message;
if (user.items.gear.owned[item.key]) {
throw new NotAuthorized(i18n.t('equipmentAlreadyOwned', req.language));
}
if (user.preferences.autoEquip) {
user.items.gear.equipped[item.type] = item.key;
message = handleTwoHanded(user, item, undefined, req);
}
user.items.gear.owned[item.key] = true;
if (item.last) ultimateGear(user);
user.stats.gp -= item.value;
if (!message) {
message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
}
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: key,
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
let res = {
data: _.pick(user, splitWhitespace('items achievements stats flags')),
message,
};
if (req.v2 === true) {
return res.data;
} else {
return res;
}
};

View File

@@ -0,0 +1,52 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import {
NotAuthorized,
} from '../libs/errors';
module.exports = function buyPotion (user, req = {}, analytics) {
let item = content.potion;
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
user.stats.hp += 15;
if (user.stats.hp > 50) {
user.stats.hp = 50;
}
user.stats.gp -= item.value;
let message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: 'Potion',
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
let res = {
data: _.pick(user, splitWhitespace('stats')),
message,
};
if (req.v2 === true) {
return res.data;
} else {
return res;
}
};

View File

@@ -30,6 +30,9 @@ import releasePets from './releasePets';
import releaseMounts from './releaseMounts'; import releaseMounts from './releaseMounts';
import releaseBoth from './releaseBoth'; import releaseBoth from './releaseBoth';
import buy from './buy'; import buy from './buy';
import buyGear from './buyGear';
import buyPotion from './buyPotion';
import buyArmoire from './buyArmoire';
import buyQuest from './buyQuest'; import buyQuest from './buyQuest';
import buyMysterySet from './buyMysterySet'; import buyMysterySet from './buyMysterySet';
import hourglassPurchase from './hourglassPurchase'; import hourglassPurchase from './hourglassPurchase';
@@ -77,6 +80,9 @@ module.exports = {
releaseMounts, releaseMounts,
releaseBoth, releaseBoth,
buy, buy,
buyGear,
buyPotion,
buyArmoire,
buyQuest, buyQuest,
buyMysterySet, buyMysterySet,
hourglassPurchase, hourglassPurchase,

View File

@@ -26,17 +26,28 @@ describe('POST /user/buy/:key', () => {
}); });
}); });
it('buys an item', async () => { it('buys a potion', async () => {
await user.update({
'stats.gp': 400,
});
let potion = content.potion; let potion = content.potion;
let res = await user.post('/user/buy/potion'); let res = await user.post('/user/buy/potion');
await user.sync(); await user.sync();
expect(user.stats.hp).to.equal(50);
expect(res.data).to.eql({ expect(res.data).to.eql({
items: JSON.parse(JSON.stringify(user.items)), // otherwise dates can't be compared
achievements: user.achievements,
stats: user.stats, stats: user.stats,
flags: JSON.parse(JSON.stringify(user.flags)), // otherwise dates can't be compared
}); });
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()})); expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
}); });
it('buys a piece of gear', async () => {
let key = 'armor_warrior_1';
await user.post(`/user/buy/${key}`);
await user.sync();
expect(user.items.gear.owned).to.eql({ armor_warrior_1: true }); // eslint-disable-line camelcase
});
}); });

View File

@@ -0,0 +1,44 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../common/script';
let content = shared.content;
describe('POST /user/buy-armoire', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'stats.hp': 40,
});
});
// More tests in common code unit tests
it('returns an error if user does not have enough gold', async () => {
await expect(user.post('/user/buy-potion'))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageNotEnoughGold'),
});
});
xit('buys a piece of armoire', async () => {
await user.update({
'stats.gp': 400,
});
let potion = content.potion;
let res = await user.post('/user/buy-potion');
await user.sync();
expect(user.stats.hp).to.equal(50);
expect(res.data).to.eql({
stats: user.stats,
});
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
});
});

View File

@@ -0,0 +1,34 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
describe('POST /user/buy-gear/:key', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'stats.gp': 400,
});
});
// More tests in common code unit tests
it('returns an error if the item is not found', async () => {
await expect(user.post('/user/buy-gear/notExisting'))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('itemNotFound', {key: 'notExisting'}),
});
});
it('buys a piece of gear', async () => {
let key = 'armor_warrior_1';
await user.post(`/user/buy-gear/${key}`);
await user.sync();
expect(user.items.gear.owned).to.eql({ armor_warrior_1: true }); // eslint-disable-line camelcase
});
});

View File

@@ -0,0 +1,44 @@
import {
generateUser,
translate as t,
} from '../../../../helpers/api-integration/v3';
import shared from '../../../../../common/script';
let content = shared.content;
describe('POST /user/buy-potion', () => {
let user;
beforeEach(async () => {
user = await generateUser({
'stats.hp': 40,
});
});
// More tests in common code unit tests
it('returns an error if user does not have enough gold', async () => {
await expect(user.post('/user/buy-potion'))
.to.eventually.be.rejected.and.eql({
code: 401,
error: 'NotAuthorized',
message: t('messageNotEnoughGold'),
});
});
it('buys a potion', async () => {
await user.update({
'stats.gp': 400,
});
let potion = content.potion;
let res = await user.post('/user/buy-potion');
await user.sync();
expect(user.stats.hp).to.equal(50);
expect(res.data).to.eql({
stats: user.stats,
});
expect(res.message).to.equal(t('messageBought', {itemText: potion.text()}));
});
});

View File

@@ -1,15 +1,10 @@
/* eslint-disable camelcase */ /* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import { import {
generateUser, generateUser,
} from '../../helpers/common.helper'; } from '../../helpers/common.helper';
import count from '../../../common/script/count';
import buy from '../../../common/script/ops/buy'; import buy from '../../../common/script/ops/buy';
import shared from '../../../common/script';
import content from '../../../common/script/content/index';
import { import {
NotAuthorized, BadRequest,
} from '../../../common/script/libs/errors'; } from '../../../common/script/libs/errors';
import i18n from '../../../common/script/i18n'; import i18n from '../../../common/script/i18n';
@@ -30,277 +25,27 @@ describe('shared.ops.buy', () => {
}, },
stats: { gp: 200 }, stats: { gp: 200 },
}); });
sinon.stub(shared.fns, 'randomVal');
sinon.stub(shared.fns, 'predictableRandom');
}); });
afterEach(() => { it('returns error when key is not provided', (done) => {
shared.fns.randomVal.restore(); try {
shared.fns.predictableRandom.restore(); buy(user);
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('missingKeyParam'));
done();
}
}); });
context('Potion', () => {
it('recovers 15 hp', () => { it('recovers 15 hp', () => {
user.stats.hp = 30; user.stats.hp = 30;
buy(user, {params: {key: 'potion'}}); buy(user, {params: {key: 'potion'}});
expect(user.stats.hp).to.eql(45); expect(user.stats.hp).to.eql(45);
}); });
it('does not increase hp above 50', () => {
user.stats.hp = 45;
buy(user, {params: {key: 'potion'}});
expect(user.stats.hp).to.eql(50);
});
it('deducts 25 gp', () => {
user.stats.hp = 45;
buy(user, {params: {key: 'potion'}});
expect(user.stats.gp).to.eql(175);
});
it('does not purchase if not enough gp', (done) => {
user.stats.hp = 45;
user.stats.gp = 5;
try {
buy(user, {params: {key: 'potion'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.stats.hp).to.eql(45);
expect(user.stats.gp).to.eql(5);
done();
}
});
});
context('Gear', () => {
it('adds equipment to inventory', () => { it('adds equipment to inventory', () => {
user.stats.gp = 31; user.stats.gp = 31;
buy(user, {params: {key: 'armor_warrior_1'}}); buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true }); expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true });
}); });
it('deducts gold from user', () => {
user.stats.gp = 31;
buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.stats.gp).to.eql(1);
});
it('auto equips equipment if user has auto-equip preference turned on', () => {
user.stats.gp = 31;
user.preferences.autoEquip = true;
buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1');
});
it('buys equipment but does not auto-equip', () => {
user.stats.gp = 31;
user.preferences.autoEquip = false;
buy(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.equipped.property).to.not.equal('armor_warrior_1');
});
// TODO after user.ops.equip is done
xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => {
user.stats.gp = 100;
user.preferences.autoEquip = true;
buy(user, {params: {key: 'shield_warrior_1'}});
user.ops.equip({params: {key: 'shield_warrior_1'}});
buy(user, {params: {key: 'weapon_warrior_1'}});
user.ops.equip({params: {key: 'weapon_warrior_1'}});
buy(user, {params: {key: 'weapon_wizard_1'}});
expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0');
expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1');
});
// TODO after user.ops.equip is done
xit('buys two-handed equipment but does not automatically remove sword or shield', () => {
user.stats.gp = 100;
user.preferences.autoEquip = false;
buy(user, {params: {key: 'shield_warrior_1'}});
user.ops.equip({params: {key: 'shield_warrior_1'}});
buy(user, {params: {key: 'weapon_warrior_1'}});
user.ops.equip({params: {key: 'weapon_warrior_1'}});
buy(user, {params: {key: 'weapon_wizard_1'}});
expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1');
expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1');
});
it('does not buy equipment without enough Gold', (done) => {
user.stats.gp = 20;
try {
buy(user, {params: {key: 'armor_warrior_1'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.items.gear.owned).to.not.have.property('armor_warrior_1');
done();
}
});
});
context('Enchanted Armoire', () => {
let YIELD_EQUIPMENT = 0.5;
let YIELD_FOOD = 0.7;
let YIELD_EXP = 0.9;
let fullArmoire = {};
_(content.gearTypes).each((type) => {
_(content.gear.tree[type].armoire).each((gearObject) => {
let armoireKey = gearObject.key;
fullArmoire[armoireKey] = true;
}).value();
}).value();
beforeEach(() => {
user.achievements.ultimateGearSets = { rogue: true };
user.flags.armoireOpened = true;
user.stats.exp = 0;
user.items.food = {};
});
context('failure conditions', () => {
it('does not open if user does not have enough gold', (done) => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.stats.gp = 50;
try {
buy(user, {params: {key: 'armoire'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
done();
}
});
it('does not open without Ultimate Gear achievement', (done) => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false};
try {
buy(user, {params: {key: 'armoire'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('cannoyBuyItem'));
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
done();
}
});
});
context('non-gear awards', () => {
// Skipped because can't stub predictableRandom correctly
xit('gives Experience', () => {
shared.fns.predictableRandom.returns(YIELD_EXP);
buy(user, {params: {key: 'armoire'}});
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(46);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('gives food', () => {
let honey = content.food.Honey;
shared.fns.randomVal.returns(honey);
shared.fns.predictableRandom.returns(YIELD_FOOD);
buy(user, {params: {key: 'armoire'}});
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.eql({Honey: 1});
expect(user.stats.exp).to.eql(0);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('does not give equipment if all equipment has been found', () => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.items.gear.owned = fullArmoire;
user.stats.gp = 150;
buy(user, {params: {key: 'armoire'}});
expect(user.items.gear.owned).to.eql(fullArmoire);
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(0);
expect(user.stats.exp).to.eql(30);
expect(user.stats.gp).to.eql(50);
});
});
context('gear awards', () => {
beforeEach(() => {
let shield = content.gear.tree.shield.armoire.gladiatorShield;
shared.fns.randomVal.returns(shield);
});
// Skipped because can't stub predictableRandom correctly
xit('always drops equipment the first time', () => {
delete user.flags.armoireOpened;
shared.fns.predictableRandom.returns(YIELD_EXP);
buy(user, {params: {key: 'armoire'}});
expect(user.items.gear.owned).to.eql({
weapon_warrior_0: true,
shield_armoire_gladiatorShield: true,
});
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(_.size(fullArmoire) - 1);
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('gives more equipment', () => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.items.gear.owned = {
weapon_warrior_0: true,
head_armoire_hornedIronHelm: true,
};
user.stats.gp = 200;
buy(user, {params: {key: 'armoire'}});
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true});
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(_.size(fullArmoire) - 2);
expect(user.stats.gp).to.eql(100);
});
});
});
}); });

View File

@@ -0,0 +1,187 @@
/* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../helpers/common.helper';
import count from '../../../common/script/count';
import buyArmoire from '../../../common/script/ops/buyArmoire';
import shared from '../../../common/script';
import content from '../../../common/script/content/index';
import {
NotAuthorized,
} from '../../../common/script/libs/errors';
import i18n from '../../../common/script/i18n';
describe('shared.ops.buyArmoire', () => {
let user;
let YIELD_EQUIPMENT = 0.5;
let YIELD_FOOD = 0.7;
let YIELD_EXP = 0.9;
let fullArmoire = {};
_(content.gearTypes).each((type) => {
_(content.gear.tree[type].armoire).each((gearObject) => {
let armoireKey = gearObject.key;
fullArmoire[armoireKey] = true;
}).value();
}).value();
beforeEach(() => {
user = generateUser({
items: {
gear: {
owned: {
weapon_warrior_0: true,
},
equipped: {
weapon_warrior_0: true,
},
},
},
stats: { gp: 200 },
});
user.achievements.ultimateGearSets = { rogue: true };
user.flags.armoireOpened = true;
user.stats.exp = 0;
user.items.food = {};
sinon.stub(shared.fns, 'randomVal');
sinon.stub(shared.fns, 'predictableRandom');
});
afterEach(() => {
shared.fns.randomVal.restore();
shared.fns.predictableRandom.restore();
});
context('failure conditions', () => {
it('does not open if user does not have enough gold', (done) => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.stats.gp = 50;
try {
buyArmoire(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
done();
}
});
it('does not open without Ultimate Gear achievement', (done) => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.achievements.ultimateGearSets = {healer: false, wizard: false, rogue: false, warrior: false};
try {
buyArmoire(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('cannotBuyItem'));
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
done();
}
});
});
context('non-gear awards', () => {
// Skipped because can't stub predictableRandom correctly
xit('gives Experience', () => {
shared.fns.predictableRandom.returns(YIELD_EXP);
buyArmoire(user);
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(46);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('gives food', () => {
let honey = content.food.Honey;
shared.fns.randomVal.returns(honey);
shared.fns.predictableRandom.returns(YIELD_FOOD);
buyArmoire(user);
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true});
expect(user.items.food).to.eql({Honey: 1});
expect(user.stats.exp).to.eql(0);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('does not give equipment if all equipment has been found', () => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.items.gear.owned = fullArmoire;
user.stats.gp = 150;
buyArmoire(user);
expect(user.items.gear.owned).to.eql(fullArmoire);
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(0);
expect(user.stats.exp).to.eql(30);
expect(user.stats.gp).to.eql(50);
});
});
context('gear awards', () => {
beforeEach(() => {
let shield = content.gear.tree.shield.armoire.gladiatorShield;
shared.fns.randomVal.returns(shield);
});
// Skipped because can't stub predictableRandom correctly
xit('always drops equipment the first time', () => {
delete user.flags.armoireOpened;
shared.fns.predictableRandom.returns(YIELD_EXP);
buyArmoire(user);
expect(user.items.gear.owned).to.eql({
weapon_warrior_0: true,
shield_armoire_gladiatorShield: true,
});
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(_.size(fullArmoire) - 1);
expect(user.items.food).to.be.empty;
expect(user.stats.exp).to.eql(0);
expect(user.stats.gp).to.eql(100);
});
// Skipped because can't stub predictableRandom correctly
xit('gives more equipment', () => {
shared.fns.predictableRandom.returns(YIELD_EQUIPMENT);
user.items.gear.owned = {
weapon_warrior_0: true,
head_armoire_hornedIronHelm: true,
};
user.stats.gp = 200;
buyArmoire(user);
expect(user.items.gear.owned).to.eql({weapon_warrior_0: true, shield_armoire_gladiatorShield: true, head_armoire_hornedIronHelm: true});
let armoireCount = count.remainingGearInSet(user.items.gear.owned, 'armoire');
expect(armoireCount).to.eql(_.size(fullArmoire) - 2);
expect(user.stats.gp).to.eql(100);
});
});
});

132
test/common/ops/buyGear.js Normal file
View File

@@ -0,0 +1,132 @@
/* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../helpers/common.helper';
import buyGear from '../../../common/script/ops/buyGear';
import shared from '../../../common/script';
import {
NotAuthorized,
} from '../../../common/script/libs/errors';
import i18n from '../../../common/script/i18n';
describe('shared.ops.buyGear', () => {
let user;
beforeEach(() => {
user = generateUser({
items: {
gear: {
owned: {
weapon_warrior_0: true,
},
equipped: {
weapon_warrior_0: true,
},
},
},
stats: { gp: 200 },
});
sinon.stub(shared.fns, 'randomVal');
sinon.stub(shared.fns, 'predictableRandom');
});
afterEach(() => {
shared.fns.randomVal.restore();
shared.fns.predictableRandom.restore();
});
context('Gear', () => {
it('adds equipment to inventory', () => {
user.stats.gp = 31;
buyGear(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.owned).to.eql({ weapon_warrior_0: true, armor_warrior_1: true });
});
it('deducts gold from user', () => {
user.stats.gp = 31;
buyGear(user, {params: {key: 'armor_warrior_1'}});
expect(user.stats.gp).to.eql(1);
});
it('auto equips equipment if user has auto-equip preference turned on', () => {
user.stats.gp = 31;
user.preferences.autoEquip = true;
buyGear(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.equipped).to.have.property('armor', 'armor_warrior_1');
});
it('buyGears equipment but does not auto-equip', () => {
user.stats.gp = 31;
user.preferences.autoEquip = false;
buyGear(user, {params: {key: 'armor_warrior_1'}});
expect(user.items.gear.equipped.property).to.not.equal('armor_warrior_1');
});
it('does not buyGear equipment twice', (done) => {
user.stats.gp = 62;
buyGear(user, {params: {key: 'armor_warrior_1'}});
try {
buyGear(user, {params: {key: 'armor_warrior_1'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('equipmentAlreadyOwned'));
done();
}
});
// TODO after user.ops.equip is done
xit('removes one-handed weapon and shield if auto-equip is on and a two-hander is bought', () => {
user.stats.gp = 100;
user.preferences.autoEquip = true;
buyGear(user, {params: {key: 'shield_warrior_1'}});
user.ops.equip({params: {key: 'shield_warrior_1'}});
buyGear(user, {params: {key: 'weapon_warrior_1'}});
user.ops.equip({params: {key: 'weapon_warrior_1'}});
buyGear(user, {params: {key: 'weapon_wizard_1'}});
expect(user.items.gear.equipped).to.have.property('shield', 'shield_base_0');
expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_wizard_1');
});
// TODO after user.ops.equip is done
xit('buyGears two-handed equipment but does not automatically remove sword or shield', () => {
user.stats.gp = 100;
user.preferences.autoEquip = false;
buyGear(user, {params: {key: 'shield_warrior_1'}});
user.ops.equip({params: {key: 'shield_warrior_1'}});
buyGear(user, {params: {key: 'weapon_warrior_1'}});
user.ops.equip({params: {key: 'weapon_warrior_1'}});
buyGear(user, {params: {key: 'weapon_wizard_1'}});
expect(user.items.gear.equipped).to.have.property('shield', 'shield_warrior_1');
expect(user.items.gear.equipped).to.have.property('weapon', 'weapon_warrior_1');
});
it('does not buyGear equipment without enough Gold', (done) => {
user.stats.gp = 20;
try {
buyGear(user, {params: {key: 'armor_warrior_1'}});
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.items.gear.owned).to.not.have.property('armor_warrior_1');
done();
}
});
});
});

View File

@@ -0,0 +1,65 @@
/* eslint-disable camelcase */
import {
generateUser,
} from '../../helpers/common.helper';
import buyPotion from '../../../common/script/ops/buyPotion';
import {
NotAuthorized,
} from '../../../common/script/libs/errors';
import i18n from '../../../common/script/i18n';
describe('shared.ops.buyPotion', () => {
let user;
beforeEach(() => {
user = generateUser({
items: {
gear: {
owned: {
weapon_warrior_0: true,
},
equipped: {
weapon_warrior_0: true,
},
},
},
stats: { gp: 200 },
});
});
context('Potion', () => {
it('recovers 15 hp', () => {
user.stats.hp = 30;
buyPotion(user);
expect(user.stats.hp).to.eql(45);
});
it('does not increase hp above 50', () => {
user.stats.hp = 45;
buyPotion(user);
expect(user.stats.hp).to.eql(50);
});
it('deducts 25 gp', () => {
user.stats.hp = 45;
buyPotion(user);
expect(user.stats.gp).to.eql(175);
});
it('does not purchase if not enough gp', (done) => {
user.stats.hp = 45;
user.stats.gp = 5;
try {
buyPotion(user);
} catch (err) {
expect(err).to.be.an.instanceof(NotAuthorized);
expect(err.message).to.equal(i18n.t('messageNotEnoughGold'));
expect(user.stats.hp).to.eql(45);
expect(user.stats.gp).to.eql(5);
done();
}
});
});
});

View File

@@ -477,15 +477,14 @@ api.allocateNow = {
}; };
/** /**
* @api {post} /api/v3/user/buy/:key Buy a content item. * @api {post} /user/buy/:key Buy gear, armoire or potion
* @apiVersion 3.0.0 * @apiVersion 3.0.0
* @apiName UserBuy * @apiName UserBuy
* @apiGroup User * @apiGroup User
* *
* @apiParam {string} key The item to buy. * @apiParam {string} key The item to buy.
* *
* @apiSuccess {Object} data `items, achievements, stats, flags` * @apiSuccess {Object} data `items`
* @apiSuccess {object} armoireResp Optional extra item given by the armoire
* @apiSuccess {string} message * @apiSuccess {string} message
*/ */
api.buy = { api.buy = {
@@ -501,7 +500,77 @@ api.buy = {
}; };
/** /**
* @api {post} /api/v3/user/buy-mystery-set/:key Buy a mystery set. * @api {post} /user/buy-gear/:key Buy a piece of gear.
* @apiVersion 3.0.0
* @apiName UserBuyGear
* @apiGroup User
*
* @apiParam {string} key The item to buy.
*
* @apiSuccess {Object} data `items`
* @apiSuccess {string} message
*/
api.buyGear = {
method: 'POST',
middlewares: [authWithHeaders()],
url: '/user/buy-gear/:key',
async handler (req, res) {
let user = res.locals.user;
let buyRes = common.ops.buyGear(user, req, res.analytics);
await user.save();
res.respond(200, buyRes);
},
};
/**
* @api {post} /user/buy-armoire Buy an armoire item.
* @apiVersion 3.0.0
* @apiName UserBuyArmoire
* @apiGroup User
*
* @apiParam {string} key The item to buy.
*
* @apiSuccess {Object} data `items flags`
* @apiSuccess {object} armoireResp Optional extra item given by the armoire
* @apiSuccess {string} message
*/
api.buyArmoire = {
method: 'POST',
middlewares: [authWithHeaders()],
url: '/user/buy-armoire',
async handler (req, res) {
let user = res.locals.user;
let buyArmoireResponse = common.ops.buyArmoire(user, req, res.analytics);
await user.save();
res.respond(200, buyArmoireResponse);
},
};
/**
* @api {post} /user/buy-potion Buy a potion.
* @apiVersion 3.0.0
* @apiName UserBuyPotion
* @apiGroup User
*
* @apiParam {string} key The item to buy.
*
* @apiSuccess {Object} data `stats`
* @apiSuccess {string} message
*/
api.buyPotion = {
method: 'POST',
middlewares: [authWithHeaders()],
url: '/user/buy-potion',
async handler (req, res) {
let user = res.locals.user;
let buyPotionResponse = common.ops.buyPotion(user, req, res.analytics);
await user.save();
res.respond(200, buyPotionResponse);
},
};
/**
* @api {post} /user/buy-mystery-set/:key Buy a mystery set.
* @apiVersion 3.0.0 * @apiVersion 3.0.0
* @apiName UserBuyMysterySet * @apiName UserBuyMysterySet
* @apiGroup User * @apiGroup User