WIP: Improve User model performances (#10832)

* wip: define items as mixed objects

* add default owned gear

* mark modified

* more mark modified

* more mark modified

* more mark modified

* more mark modified

* fix common tests

* fix common tests

* update mongoose

* add itemsUtils

* use new util function in hall controller

* add tests for items utils

* update website/server to mark all items as modified

* start updating common code

* update login incentives

* update unlock

* remove changes to package-lock.json

* remove changes to package.json
This commit is contained in:
Matteo Pagliazzi
2019-04-01 19:24:18 +02:00
committed by GitHub
parent 95e541ae75
commit 0b8ce63c76
38 changed files with 304 additions and 42 deletions

View File

@@ -0,0 +1,67 @@
/* eslint-disable camelcase */
import {
validateItemPath,
getDefaultOwnedGear,
} from '../../../../../website/server/libs/items/utils';
describe('Items Utils', () => {
describe('getDefaultOwnedGear', () => {
it('clones the result object', () => {
const res1 = getDefaultOwnedGear();
res1.extraProperty = true;
const res2 = getDefaultOwnedGear();
expect(res2).not.to.have.property('extraProperty');
});
});
describe('validateItemPath', () => {
it('returns false if not an item path', () => {
expect(validateItemPath('notitems.gear.owned.item')).to.equal(false);
});
it('returns true if a valid schema path', () => {
expect(validateItemPath('items.gear.equipped.weapon')).to.equal(true);
expect(validateItemPath('items.currentPet')).to.equal(true);
expect(validateItemPath('items.special.snowball')).to.equal(true);
});
it('works with owned gear paths', () => {
expect(validateItemPath('items.gear.owned.head_armoire_crownOfHearts')).to.equal(true);
expect(validateItemPath('items.gear.owned.head_invalid')).to.equal(false);
});
it('works with pets paths', () => {
expect(validateItemPath('items.pets.Wolf-CottonCandyPink')).to.equal(true);
expect(validateItemPath('items.pets.Wolf-Invalid')).to.equal(false);
});
it('works with eggs paths', () => {
expect(validateItemPath('items.eggs.LionCub')).to.equal(true);
expect(validateItemPath('items.eggs.Armadillo')).to.equal(true);
expect(validateItemPath('items.eggs.NotAnArmadillo')).to.equal(false);
});
it('works with hatching potions paths', () => {
expect(validateItemPath('items.hatchingPotions.Base')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.StarryNight')).to.equal(true);
expect(validateItemPath('items.hatchingPotions.Invalid')).to.equal(false);
});
it('works with food paths', () => {
expect(validateItemPath('items.food.Cake_Base')).to.equal(true);
expect(validateItemPath('items.food.Cake_Invalid')).to.equal(false);
});
it('works with mounts paths', () => {
expect(validateItemPath('items.mounts.Cactus-Base')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invisible')).to.equal(true);
expect(validateItemPath('items.mounts.Aether-Invalid')).to.equal(false);
});
it('works with quests paths', () => {
expect(validateItemPath('items.quests.atom3')).to.equal(true);
expect(validateItemPath('items.quests.invalid')).to.equal(false);
});
});
});

View File

@@ -9,6 +9,7 @@ import {
import i18n from '../../../../website/common/script/i18n';
import content from '../../../../website/common/script/content/index';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { defaultsDeep } from 'lodash';
describe('shared.ops.buy', () => {
let user;
@@ -16,6 +17,10 @@ describe('shared.ops.buy', () => {
beforeEach(() => {
user = generateUser({
stats: { gp: 200 },
});
defaultsDeep(user, {
items: {
gear: {
owned: {
@@ -26,7 +31,6 @@ describe('shared.ops.buy', () => {
},
},
},
stats: { gp: 200 },
});
sinon.stub(analytics, 'track');

View File

@@ -11,6 +11,7 @@ import {
} from '../../../../website/common/script/libs/errors';
import i18n from '../../../../website/common/script/i18n';
import errorMessage from '../../../../website/common/script/libs/errorMessage';
import { defaultsDeep } from 'lodash';
function buyGear (user, req, analytics) {
let buyOp = new BuyMarketGearOperation(user, req, analytics);
@@ -24,6 +25,10 @@ describe('shared.ops.buyMarketGear', () => {
beforeEach(() => {
user = generateUser({
stats: { gp: 200 },
});
defaultsDeep(user, {
items: {
gear: {
owned: {
@@ -34,7 +39,6 @@ describe('shared.ops.buyMarketGear', () => {
},
},
},
stats: { gp: 200 },
});
sinon.stub(shared, 'randomVal');

View File

@@ -9,6 +9,7 @@ import {
} from '../../website/server/models/task';
export {translate} from './translate';
export function generateUser (options = {}) {
let user = new User(options).toObject();

View File

@@ -10,6 +10,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.armor_special_bardRobes],
assignReward: function assignReward (user) {
user.items.gear.owned.armor_special_bardRobes = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
2: {
@@ -30,6 +31,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.head_special_bardHat],
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_bardHat = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
4: {
@@ -38,6 +40,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
5: {
@@ -50,6 +53,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.Meat += 1;
if (!user.items.food.CottonCandyPink) user.items.food.CottonCandyPink = 0;
user.items.food.CottonCandyPink += 1;
if (user.markModified) user.markModified('items.food');
},
},
7: {
@@ -58,6 +62,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.quests.moon1) user.items.quests.moon1 = 0;
user.items.quests.moon1 += 1;
if (user.markModified) user.markModified('items.quests');
},
},
10: {
@@ -66,6 +71,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
14: {
@@ -78,6 +84,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.Potatoe += 1;
if (!user.items.food.CottonCandyBlue) user.items.food.CottonCandyBlue = 0;
user.items.food.CottonCandyBlue += 1;
if (user.markModified) user.markModified('items.food');
},
},
18: {
@@ -85,6 +92,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.weapon_special_bardInstrument],
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_bardInstrument = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
22: {
@@ -93,6 +101,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.quests.moon2) user.items.quests.moon2 = 0;
user.items.quests.moon2 += 1;
if (user.markModified) user.markModified('items.quests');
},
},
26: {
@@ -101,6 +110,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
30: {
@@ -115,6 +125,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.RottenMeat += 1;
if (!user.items.food.Honey) user.items.food.Honey = 0;
user.items.food.Honey += 1;
if (user.markModified) user.markModified('items.food');
},
},
35: {
@@ -123,6 +134,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
40: {
@@ -131,6 +143,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.quests.moon3) user.items.quests.moon3 = 0;
user.items.quests.moon3 += 1;
if (user.markModified) user.markModified('items.quests');
},
},
45: {
@@ -139,6 +152,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
50: {
@@ -147,6 +161,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.food.Saddle) user.items.food.Saddle = 0;
user.items.food.Saddle += 1;
if (user.markModified) user.markModified('items.food');
},
},
55: {
@@ -155,6 +170,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
60: {
@@ -162,6 +178,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.armor_special_pageArmor],
assignReward: function assignReward (user) {
user.items.gear.owned.armor_special_pageArmor = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
65: {
@@ -170,6 +187,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
70: {
@@ -177,6 +195,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.head_special_pageHelm],
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_pageHelm = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
75: {
@@ -185,6 +204,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
80: {
@@ -192,6 +212,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.weapon_special_pageBanner],
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_pageBanner = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
85: {
@@ -200,6 +221,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
90: {
@@ -207,6 +229,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.shield_special_diamondStave],
assignReward: function assignReward (user) {
user.items.gear.owned.shield_special_diamondStave = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
95: {
@@ -215,6 +238,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
100: {
@@ -223,6 +247,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.food.Saddle) user.items.food.Saddle = 0;
user.items.food.Saddle += 1;
if (user.markModified) user.markModified('items.food');
},
},
105: {
@@ -231,6 +256,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
110: {
@@ -256,6 +282,7 @@ module.exports = function getLoginIncentives (api) {
user.items.eggs.TigerCub += 1;
if (!user.items.eggs.Wolf) user.items.eggs.Wolf = 0;
user.items.eggs.Wolf += 1;
if (user.markModified) user.markModified('items.eggs');
},
},
115: {
@@ -264,6 +291,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
120: {
@@ -291,6 +319,7 @@ module.exports = function getLoginIncentives (api) {
user.items.hatchingPotions.White += 1;
if (!user.items.hatchingPotions.Zombie) user.items.hatchingPotions.Zombie = 0;
user.items.hatchingPotions.Zombie += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
125: {
@@ -299,6 +328,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
130: {
@@ -326,6 +356,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.Milk += 3;
if (!user.items.food.RottenMeat) user.items.food.RottenMeat = 0;
user.items.food.RottenMeat += 3;
if (user.markModified) user.markModified('items.food');
},
},
135: {
@@ -334,6 +365,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
140: {
@@ -342,6 +374,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_skeletonKey = true; // eslint-disable-line camelcase
user.items.gear.owned.shield_special_lootBag = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
145: {
@@ -350,6 +383,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
150: {
@@ -358,6 +392,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_clandestineCowl = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_sneakthiefRobes = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
160: {
@@ -366,6 +401,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
170: {
@@ -374,6 +410,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_snowSovereignCrown = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_snowSovereignRobes = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
180: {
@@ -382,6 +419,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
190: {
@@ -390,6 +428,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.shield_special_wintryMirror = true; // eslint-disable-line camelcase
user.items.gear.owned.back_special_snowdriftVeil = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
200: {
@@ -398,6 +437,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.hatchingPotions.RoyalPurple) user.items.hatchingPotions.RoyalPurple = 0;
user.items.hatchingPotions.RoyalPurple += 1;
if (user.markModified) user.markModified('items.hatchingPotions');
},
},
220: {
@@ -406,6 +446,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.food.Saddle) user.items.food.Saddle = 0;
user.items.food.Saddle += 1;
if (user.markModified) user.markModified('items.food');
},
},
240: {
@@ -414,6 +455,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_nomadsScimitar = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_nomadsCuirass = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
260: {
@@ -421,6 +463,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.head_special_spikedHelm],
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_spikedHelm = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
280: {
@@ -448,6 +491,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.Milk += 3;
if (!user.items.food.RottenMeat) user.items.food.RottenMeat = 0;
user.items.food.RottenMeat += 3;
if (user.markModified) user.markModified('items.food');
},
},
300: {
@@ -473,6 +517,7 @@ module.exports = function getLoginIncentives (api) {
user.items.eggs.TigerCub += 2;
if (!user.items.eggs.Wolf) user.items.eggs.Wolf = 0;
user.items.eggs.Wolf += 2;
if (user.markModified) user.markModified('items.eggs');
},
},
320: {
@@ -480,6 +525,7 @@ module.exports = function getLoginIncentives (api) {
reward: [api.gear.flat.head_special_dandyHat],
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_dandyHat = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
340: {
@@ -488,6 +534,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_fencingFoil = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_dandySuit = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
360: {
@@ -497,6 +544,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.food.Saddle) user.items.food.Saddle = 0;
user.items.food.Saddle += 2;
if (user.markModified) user.markModified('items.food');
},
},
380: {
@@ -522,6 +570,7 @@ module.exports = function getLoginIncentives (api) {
user.items.eggs.TigerCub += 3;
if (!user.items.eggs.Wolf) user.items.eggs.Wolf = 0;
user.items.eggs.Wolf += 3;
if (user.markModified) user.markModified('items.eggs');
},
},
400: {
@@ -549,6 +598,7 @@ module.exports = function getLoginIncentives (api) {
user.items.food.Milk += 4;
if (!user.items.food.RottenMeat) user.items.food.RottenMeat = 0;
user.items.food.RottenMeat += 4;
if (user.markModified) user.markModified('items.food');
},
},
425: {
@@ -558,6 +608,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
if (!user.items.food.Saddle) user.items.food.Saddle = 0;
user.items.food.Saddle += 3;
if (user.markModified) user.markModified('items.food');
},
},
450: {
@@ -566,6 +617,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_tachi = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_samuraiArmor = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
475: {
@@ -574,6 +626,7 @@ module.exports = function getLoginIncentives (api) {
assignReward: function assignReward (user) {
user.items.gear.owned.head_special_kabuto = true; // eslint-disable-line camelcase
user.items.gear.owned.shield_special_wakizashi = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
},
},
500: {

View File

@@ -75,6 +75,8 @@ module.exports = function randomDrop (user, options, req = {}, analytics) {
user.items.food[drop.key] = user.items.food[drop.key] || 0;
user.items.food[drop.key] += 1;
if (user.markModified) user.markModified('items.food');
drop.type = 'Food';
drop.dialog = i18n.t('messageDropFood', {
dropText: drop.textA(req.language),
@@ -82,8 +84,11 @@ module.exports = function randomDrop (user, options, req = {}, analytics) {
}, req.language);
} else if (rarity > 0.3) { // eggs 30% chance
drop = cloneDropItem(randomVal(content.dropEggs));
user.items.eggs[drop.key] = user.items.eggs[drop.key] || 0;
user.items.eggs[drop.key]++;
if (user.markModified) user.markModified('items.eggs');
drop.type = 'Egg';
drop.dialog = i18n.t('messageDropEgg', {
dropText: drop.text(req.language),
@@ -102,8 +107,11 @@ module.exports = function randomDrop (user, options, req = {}, analytics) {
drop = cloneDropItem(randomVal(pickBy(content.hatchingPotions, (v, k) => {
return acceptableDrops.indexOf(k) >= 0;
})));
user.items.hatchingPotions[drop.key] = user.items.hatchingPotions[drop.key] || 0;
user.items.hatchingPotions[drop.key]++;
if (user.markModified) user.markModified('items.hatchingPotions');
drop.type = 'HatchingPotion';
drop.dialog = i18n.t('messageDropPotion', {
dropText: drop.text(req.language),

View File

@@ -12,7 +12,7 @@ module.exports = function resetGear (user) {
gear[type].shield = 'shield_base_0';
});
// Gear.owned is a Mongo object so the _.each function iterates over hidden properties.
// Gear.owned is (was) a Mongo object so the _.each function iterates over hidden properties.
// The content.gear.flat[k] check should prevent this causing an error
each(gear.owned, function resetOwnedGear (v, k) {
if (gear.owned[k] && content.gear.flat[k] && content.gear.flat[k].value) {
@@ -21,5 +21,7 @@ module.exports = function resetGear (user) {
});
gear.owned.weapon_warrior_0 = true; // eslint-disable-line camelcase
if (user.markModified) user.markModified('items.gear.owned');
user.preferences.costume = false;
};

View File

@@ -4,7 +4,7 @@ import reduce from 'lodash/reduce';
import includes from 'lodash/includes';
module.exports = function ultimateGear (user) {
let owned = typeof window !== 'undefined' ? user.items.gear.owned : user.items.gear.owned.toObject();
let owned = user.items.gear.owned.toObject ? user.items.gear.owned.toObject() : user.items.gear.owned;
content.classes.forEach((klass) => {
if (user.achievements.ultimateGearSets[klass] !== true) {

View File

@@ -75,6 +75,8 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
} else {
user.items.eggs.Wolf = 1;
}
if (user.markModified) user.markModified('items.eggs');
}
each({
vice1: 30,
@@ -84,10 +86,12 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
}, (lvl, k) => {
if (user.stats.lvl >= lvl && !user.flags.levelDrops[k]) {
user.flags.levelDrops[k] = true;
if (!user.items.quests[k])
user.items.quests[k] = 0;
user.items.quests[k]++;
if (user.markModified) user.markModified('flags.levelDrops');
if (!user.items.quests[k]) user.items.quests[k] = 0;
user.items.quests[k]++;
if (user.markModified) user.markModified('items.quests');
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,

View File

@@ -10,7 +10,7 @@ function equipmentStatBonusComputed (stat, user) {
let classBonus = 0;
// toObject is required here due to lodash values not working well with mongoose doc objects.
// if toObject doesn't exist, we're on the client side and can assume the object is already plain JSON
// if toObject doesn't exist, we can assume the object is already plain JSON
// see http://stackoverflow.com/questions/25767334/underscore-js-keys-and-omit-not-working-as-expected
let equipped = user.items.gear.equipped;
let equippedKeys = values(!equipped.toObject ? equipped : equipped.toObject());

View File

@@ -90,6 +90,8 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation {
}
user.items.gear.owned[drop.key] = true;
if (user.markModified) user.markModified('items.gear.owned');
user.flags.armoireOpened = true;
let message = this.i18n('armoireEquipment', {
image: `<span class="shop_${drop.key} pull-left"></span>`,
@@ -125,6 +127,7 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation {
user.items.food[drop.key] = user.items.food[drop.key] || 0;
user.items.food[drop.key] += 1;
if (user.markModified) user.markModified('items.food');
if (this.analytics) {
this._trackDropAnalytics(user._id, drop.key);

View File

@@ -38,6 +38,8 @@ module.exports = function buyMysterySet (user, req = {}, analytics) {
}
});
if (user.markModified) user.markModified('items.gear.owned');
user.purchased.plan.consecutive.trinkets--;
return [

View File

@@ -68,6 +68,7 @@ export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation {
executeChanges (user, item, req) {
if (!user.items.quests[item.key] || user.items.quests[item.key] < 0) user.items.quests[item.key] = 0;
user.items.quests[item.key] += this.quantity;
if (user.markModified) user.markModified('items.quests');
this.subtractCurrency(user, item, this.quantity);

View File

@@ -48,6 +48,7 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation {
executeChanges (user, item, req) {
user.items.quests[item.key] = user.items.quests[item.key] || 0;
user.items.quests[item.key] += this.quantity;
if (user.markModified) user.markModified('items.quests');
this.subtractCurrency(user, item, this.quantity);

View File

@@ -36,10 +36,12 @@ module.exports = function purchaseHourglass (user, req = {}, analytics) {
if (type === 'pets') {
user.items.pets[key] = 5;
if (user.markModified) user.markModified('items.pets');
}
if (type === 'mounts') {
user.items.mounts[key] = true;
if (user.markModified) user.markModified('items.mounts');
}
if (analytics) {

View File

@@ -47,6 +47,7 @@ function purchaseItem (user, item, price, type, key) {
if (type === 'gear') {
user.items.gear.owned[key] = true;
if (user.markModified) user.markModified('items.gear.owned');
} else if (type === 'bundles') {
let subType = item.type;
forEach(item.bundleKeys, function addBundledItems (bundledKey) {
@@ -55,11 +56,13 @@ function purchaseItem (user, item, price, type, key) {
}
user.items[subType][bundledKey]++;
});
if (user.markModified) user.markModified(`items.${subType}`);
} else {
if (!user.items[type][key] || user.items[type][key] < 0) {
user.items[type][key] = 0;
}
user.items[type][key]++;
if (user.markModified) user.markModified(`items.${type}`);
}
}

View File

@@ -53,7 +53,8 @@ module.exports = function changeClass (user, req = {}, analytics) {
addPinnedGearByClass(user);
user.items.gear.owned[`weapon_${klass}_0`] = true;
if (klass === 'rogue') user.items.gear.owned[`shield_${klass}_0`] = true;
if (klass === 'rogue') user.items.gear.owned[`shield_${klass}_0`] = true;
if (user.markModified) user.markModified('items.gear.owned');
removePinnedItemsByOwnedGear(user);

View File

@@ -52,6 +52,8 @@ module.exports = function equip (user, req = {}) {
user.items.gear[type].toObject ? user.items.gear[type].toObject() : user.items.gear[type],
{[item.type]: `${item.type}_base_0`}
);
if (user.markModified && type === 'owned') user.markModified('items.gear.owned');
message = i18n.t('messageUnEquipped', {
itemText: item.text(req.language),
}, req.language);
@@ -61,6 +63,8 @@ module.exports = function equip (user, req = {}) {
user.items.gear[type].toObject ? user.items.gear[type].toObject() : user.items.gear[type],
{[item.type]: item.key}
);
if (user.markModified && type === 'owned') user.markModified('items.gear.owned');
message = handleTwoHanded(user, item, type, req);
}
break;

View File

@@ -12,6 +12,11 @@ function evolve (user, pet, req) {
user.items.pets[pet.key] = -1;
user.items.mounts[pet.key] = true;
if (user.markModified) {
user.markModified('items.pets');
user.markModified('items.mounts');
}
if (pet.key === user.items.currentPet) {
user.items.currentPet = '';
}
@@ -74,12 +79,15 @@ module.exports = function feed (user, req = {}) {
message = i18n.t('messageDontEnjoyFood', messageParams, req.language);
}
if (user.markModified) user.markModified('items.pets');
if (userPets[pet.key] >= 50 && !user.items.mounts[pet.key]) {
message = evolve(user, pet, req);
}
}
user.items.food[food.key]--;
if (user.markModified) user.markModified('items.food');
return [
userPets[pet.key],

View File

@@ -33,6 +33,11 @@ module.exports = function hatch (user, req = {}) {
user.items.pets[pet] = 5;
user.items.eggs[egg]--;
user.items.hatchingPotions[hatchingPotion]--;
if (user.markModified) {
user.markModified('items.pets');
user.markModified('items.eggs');
user.markModified('items.hatchingPotions');
}
return [
user.items,

View File

@@ -26,7 +26,10 @@ module.exports = function openMysteryItem (user, req = {}, analytics) {
item = cloneDeep(content.gear.flat[item]);
user.items.gear.owned[item.key] = true;
if (user.markModified) user.markModified('purchased.plan.mysteryItems');
if (user.markModified) {
user.markModified('purchased.plan.mysteryItems');
user.markModified('items.gear.owned');
}
if (analytics) {
analytics.track('open mystery item', {

View File

@@ -98,7 +98,10 @@ function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey) {
// an item of the users current "new" gear was bought
// remove the old pinned gear items and add the new gear back
removePinnedGearByClass(user);
user.items.gear.owned[newItemKey] = true;
if (user.markModified) user.markModified('items.gear.owned');
addPinnedGearByClass(user);
// update the version, so that vue can refresh the seasonal shop

View File

@@ -62,6 +62,10 @@ module.exports = function releaseBoth (user, req = {}) {
user.items.pets[animal] = 0;
user.items.mounts[animal] = null;
}
if (user.markModified) {
user.markModified('items.pets');
user.markModified('items.mounts');
}
if (giveBeastMasterAchievement) {
if (!user.achievements.beastMasterCount) {

View File

@@ -30,6 +30,7 @@ module.exports = function releaseMounts (user, req = {}, analytics) {
}
user.items.mounts[mount] = null;
}
if (user.markModified) user.markModified('items.mounts');
if (giveMountMasterAchievement) {
if (!user.achievements.mountMasterCount) {

View File

@@ -30,6 +30,7 @@ module.exports = function releasePets (user, req = {}, analytics) {
}
user.items.pets[pet] = 0;
}
if (user.markModified) user.markModified('items.pets');
if (giveBeastMasterAchievement) {
if (!user.achievements.beastMasterCount) {

View File

@@ -87,6 +87,7 @@ module.exports = function revive (user, req = {}, analytics) {
removePinnedGearByClass(user);
user.items.gear.owned[lostItem] = false;
if (user.markModified) user.markModified('items.gear.owned');
addPinnedGearByClass(user);

View File

@@ -44,6 +44,8 @@ module.exports = function sell (user, req = {}) {
}
user.items[type][key] -= amount;
if (user.markModified) user.markModified(`items.${type}`);
user.stats.gp += content[type][key].value * amount;
return [

View File

@@ -75,6 +75,7 @@ module.exports = function unlock (user, req = {}, analytics) {
setWith(user, pathPart, true, Object);
let itemName = pathPart.split('.').pop();
removeItemByPath(user, `gear.flat.${itemName}`);
if (user.markModified && path.indexOf('gear.owned') !== -1) user.markModified('items.gear.owned');
}
// Using Object so path[1] won't create an array but an object {path: {1: value}}
@@ -96,6 +97,7 @@ module.exports = function unlock (user, req = {}, analytics) {
if (path.indexOf('gear.') !== -1) {
// Using Object so path[1] won't create an array but an object {path: {1: value}}
setWith(user, path, true, Object);
if (user.markModified && path.indexOf('gear.owned') !== -1) user.markModified('items.gear.owned');
}
// Using Object so path[1] won't create an array but an object {path: {1: value}}
setWith(user, `purchased.${path}`, true, Object);

View File

@@ -204,6 +204,8 @@ api.updateUsername = {
} else {
user.items.pets['Wolf-Veteran'] = 5;
}
user.markModified('items.pets');
}
await user.save();

View File

@@ -135,6 +135,7 @@ api.modifyInventory = {
if (gear) {
user.items.gear.owned = gear;
user.markModified('items.gear.owned');
}
[
@@ -148,6 +149,7 @@ api.modifyInventory = {
].forEach((type) => {
if (req.body[type]) {
user.items[type] = req.body[type];
user.markModified(`items.${type}`);
}
});

View File

@@ -595,6 +595,7 @@ api.joinGroup = {
inviter.items.quests.basilist = 0;
}
inviter.items.quests.basilist++;
inviter.markModified('items.quests');
}
promises.push(inviter.save());
}
@@ -890,6 +891,7 @@ api.removeGroupMember = {
if (group.quest && group.quest.active && group.quest.leader === member._id) {
member.items.quests[group.quest.key] += 1;
member.markModified('items.quests');
}
} else if (isInvited) {
if (isInvited === 'guild') {

View File

@@ -7,6 +7,8 @@ import {
import _ from 'lodash';
import apiError from '../../libs/apiError';
import validator from 'validator';
import { validateItemPath } from '../../libs/items/utils';
let api = {};
@@ -264,10 +266,11 @@ api.updateHero = {
if (updateData.purchased && updateData.purchased.ads) hero.purchased.ads = updateData.purchased.ads;
// give them the Dragon Hydra pet if they're above level 6
if (hero.contributor.level >= 6) hero.items.pets['Dragon-Hydra'] = 5;
if (updateData.itemPath && updateData.itemVal &&
updateData.itemPath.indexOf('items.') === 0 &&
User.schema.paths[updateData.itemPath]) {
if (hero.contributor.level >= 6) {
hero.items.pets['Dragon-Hydra'] = 5;
hero.markModified('items.pets');
}
if (updateData.itemPath && updateData.itemVal && validateItemPath(updateData.itemPath)) {
_.set(hero, updateData.itemPath, updateData.itemVal); // Sanitization at 5c30944 (deemed unnecessary)
}

View File

@@ -0,0 +1,57 @@
import shared from '../../../common';
import { model as User } from '../../models/user';
import { last } from 'lodash';
// Build a list of gear items owned by default
const defaultOwnedGear = {};
Object.keys(shared.content.gear.flat).forEach(key => {
const item = shared.content.gear.flat[key];
if (item.key.match(/(armor|head|shield)_warrior_0/) || item.gearSet === 'glasses' || item.gearSet === 'headband') {
defaultOwnedGear[item.key] = true;
}
});
export function getDefaultOwnedGear () {
// Clone to avoid modifications to the original object
return Object.assign({}, defaultOwnedGear);
}
// When passed a path to an item in the user object it'll return true if
// it's valid, false otherwsie
// Example of an item path: `items.gear.owned.head_warrior_0`
export function validateItemPath (itemPath) {
// The item path must start with `items.`
if (itemPath.indexOf('items.') !== 0) return false;
if (User.schema.paths[itemPath]) return true;
const key = last(itemPath.split('.'));
if (itemPath.indexOf('items.gear.owned') === 0) {
return Boolean(shared.content.gear.flat[key]);
}
if (itemPath.indexOf('items.pets') === 0) {
return Boolean(shared.content.petInfo[key]);
}
if (itemPath.indexOf('items.eggs') === 0) {
return Boolean(shared.content.eggs[key]);
}
if (itemPath.indexOf('items.hatchingPotions') === 0) {
return Boolean(shared.content.hatchingPotions[key]);
}
if (itemPath.indexOf('items.food') === 0) {
return Boolean(shared.content.food[key]);
}
if (itemPath.indexOf('items.mounts') === 0) {
return Boolean(shared.content.mountInfo[key]);
}
if (itemPath.indexOf('items.quests') === 0) {
return Boolean(shared.content.quests[key]);
}
}

View File

@@ -170,6 +170,7 @@ async function addSubToGroupUser (member, group) {
member.purchased.plan = plan;
member.items.mounts['Jackalope-RoyalPurple'] = true;
member.markModified('items.mounts');
data.user = member;
await this.createSubscription(data);

View File

@@ -140,6 +140,7 @@ async function createSubscription (data) {
if (recipient !== group) {
recipient.items.pets['Jackalope-RoyalPurple'] = 5;
recipient.markModified('items.pets');
revealMysteryItems(recipient);
}

View File

@@ -46,6 +46,8 @@ schema.statics.apply = async function applyCoupon (user, req, code) {
user.items.gear.owned.body_special_wondercon_red = true;
user.items.gear.owned.body_special_wondercon_black = true;
user.items.gear.owned.body_special_wondercon_gold = true;
user.markModified('items.gear.owned');
user.extra = {signupEvent: 'wondercon'};
}

View File

@@ -125,6 +125,8 @@ function _setUpNewUser (user) {
let iterableFlags = user.flags.toObject();
user.items.quests.dustbunnies = 1;
user.markModified('items.quests');
user.purchased.background.violet = true;
user.preferences.background = 'violet';
@@ -217,6 +219,7 @@ schema.pre('save', true, function preSaveUser (next, done) {
// automatically granted an item during a certain time period:
// if (!this.items.pets['JackOLantern-Base'] && moment().isBefore('2014-11-01'))
// this.items.pets['JackOLantern-Base'] = 5;
// this.markModified('items.pets');
}
// Filter notifications, remove unvalid and not necessary, handle the ones that have special requirements

View File

@@ -1,6 +1,5 @@
import mongoose from 'mongoose';
import shared from '../../../common';
import _ from 'lodash';
import validator from 'validator';
import { schema as TagSchema } from '../tag';
import { schema as PushDeviceSchema } from '../pushDevice';
@@ -11,6 +10,9 @@ import {
import {
schema as SubscriptionPlanSchema,
} from '../subscriptionPlan';
import {
getDefaultOwnedGear,
} from '../../libs/items/utils';
const Schema = mongoose.Schema;
@@ -251,12 +253,12 @@ let schema = new Schema({
items: {
gear: {
owned: _.transform(shared.content.gear.flat, (m, v) => {
m[v.key] = {$type: Boolean};
if (v.key.match(/(armor|head|shield)_warrior_0/) || v.gearSet === 'glasses' || v.gearSet === 'headband') {
m[v.key].default = true;
}
}),
owned: {
$type: Schema.Types.Mixed,
default: () => {
return getDefaultOwnedGear();
},
},
equipped: {
weapon: String,
@@ -310,55 +312,52 @@ let schema = new Schema({
// 'PandaCub-Red': 10, // Number represents "Growth Points"
// etc...
// }
pets: _.defaults(
// First transform to a 1D eggs/potions mapping
_.transform(shared.content.pets, (m, v, k) => m[k] = Number),
// Then add additional pets (quest, backer, contributor, premium)
_.transform(shared.content.questPets, (m, v, k) => m[k] = Number),
_.transform(shared.content.specialPets, (m, v, k) => m[k] = Number),
_.transform(shared.content.premiumPets, (m, v, k) => m[k] = Number)
),
pets: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
currentPet: String, // Cactus-Desert
// eggs: {
// 'PandaCub': 0, // 0 indicates "doesn't own"
// 'Wolf': 5 // Number indicates "stacking"
// }
eggs: _.transform(shared.content.eggs, (m, v, k) => m[k] = Number),
eggs: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
// hatchingPotions: {
// 'Desert': 0, // 0 indicates "doesn't own"
// 'CottonCandyBlue': 5 // Number indicates "stacking"
// }
hatchingPotions: _.transform(shared.content.hatchingPotions, (m, v, k) => m[k] = Number),
hatchingPotions: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
// Food: {
// 'Watermelon': 0, // 0 indicates "doesn't own"
// 'RottenMeat': 5 // Number indicates "stacking"
// }
food: _.transform(shared.content.food, (m, v, k) => m[k] = Number),
food: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
// mounts: {
// 'Wolf-Desert': true,
// 'PandaCub-Red': false,
// etc...
// }
mounts: _.defaults(
// First transform to a 1D eggs/potions mapping
_.transform(shared.content.pets, (m, v, k) => m[k] = Boolean),
// Then add quest and premium pets
_.transform(shared.content.questPets, (m, v, k) => m[k] = Boolean),
_.transform(shared.content.premiumPets, (m, v, k) => m[k] = Boolean),
// Then add additional mounts (backer, contributor)
_.transform(shared.content.specialMounts, (m, v, k) => m[k] = Boolean)
),
mounts: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
currentMount: String,
// Quests: {
// 'boss_0': 0, // 0 indicates "doesn't own"
// 'collection_honey': 5 // Number indicates "stacking"
// }
quests: _.transform(shared.content.quests, (m, v, k) => m[k] = Number),
quests: {$type: Schema.Types.Mixed, default: () => {
return {};
}},
lastDrop: {
date: {$type: Date, default: Date.now},