diff --git a/test/api/unit/libs/items/utils.test.js b/test/api/unit/libs/items/utils.test.js new file mode 100644 index 0000000000..029d26a07c --- /dev/null +++ b/test/api/unit/libs/items/utils.test.js @@ -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); + }); + }); +}); diff --git a/test/common/ops/buy/buy.js b/test/common/ops/buy/buy.js index 478a2cae86..5ec36dfd3a 100644 --- a/test/common/ops/buy/buy.js +++ b/test/common/ops/buy/buy.js @@ -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'); diff --git a/test/common/ops/buy/buyMarketGear.js b/test/common/ops/buy/buyMarketGear.js index f546331215..602975f6a6 100644 --- a/test/common/ops/buy/buyMarketGear.js +++ b/test/common/ops/buy/buyMarketGear.js @@ -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'); diff --git a/test/helpers/common.helper.js b/test/helpers/common.helper.js index 12e4efdaec..f594e15cd0 100644 --- a/test/helpers/common.helper.js +++ b/test/helpers/common.helper.js @@ -9,6 +9,7 @@ import { } from '../../website/server/models/task'; export {translate} from './translate'; + export function generateUser (options = {}) { let user = new User(options).toObject(); diff --git a/website/common/script/content/loginIncentives.js b/website/common/script/content/loginIncentives.js index b61b639c80..091f9b108f 100644 --- a/website/common/script/content/loginIncentives.js +++ b/website/common/script/content/loginIncentives.js @@ -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: { diff --git a/website/common/script/fns/randomDrop.js b/website/common/script/fns/randomDrop.js index deaf0127b3..6061b3f83f 100644 --- a/website/common/script/fns/randomDrop.js +++ b/website/common/script/fns/randomDrop.js @@ -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), diff --git a/website/common/script/fns/resetGear.js b/website/common/script/fns/resetGear.js index d92df37da8..0a60635676 100644 --- a/website/common/script/fns/resetGear.js +++ b/website/common/script/fns/resetGear.js @@ -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; }; diff --git a/website/common/script/fns/ultimateGear.js b/website/common/script/fns/ultimateGear.js index 3c9251fb6f..ea5101bb97 100644 --- a/website/common/script/fns/ultimateGear.js +++ b/website/common/script/fns/ultimateGear.js @@ -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) { diff --git a/website/common/script/fns/updateStats.js b/website/common/script/fns/updateStats.js index 65d466f1b0..669fb46fd2 100644 --- a/website/common/script/fns/updateStats.js +++ b/website/common/script/fns/updateStats.js @@ -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, diff --git a/website/common/script/libs/statsComputed.js b/website/common/script/libs/statsComputed.js index 0997cae007..1239b6d9d5 100644 --- a/website/common/script/libs/statsComputed.js +++ b/website/common/script/libs/statsComputed.js @@ -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()); diff --git a/website/common/script/ops/buy/buyArmoire.js b/website/common/script/ops/buy/buyArmoire.js index 6416815841..cd4eb796af 100644 --- a/website/common/script/ops/buy/buyArmoire.js +++ b/website/common/script/ops/buy/buyArmoire.js @@ -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: ``, @@ -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); diff --git a/website/common/script/ops/buy/buyMysterySet.js b/website/common/script/ops/buy/buyMysterySet.js index 7e0bf0c83a..db9ca557f2 100644 --- a/website/common/script/ops/buy/buyMysterySet.js +++ b/website/common/script/ops/buy/buyMysterySet.js @@ -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 [ diff --git a/website/common/script/ops/buy/buyQuest.js b/website/common/script/ops/buy/buyQuest.js index c9fb3985fb..09f04dcb59 100644 --- a/website/common/script/ops/buy/buyQuest.js +++ b/website/common/script/ops/buy/buyQuest.js @@ -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); diff --git a/website/common/script/ops/buy/buyQuestGem.js b/website/common/script/ops/buy/buyQuestGem.js index 90cf0190e8..1a1a24d90a 100644 --- a/website/common/script/ops/buy/buyQuestGem.js +++ b/website/common/script/ops/buy/buyQuestGem.js @@ -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); diff --git a/website/common/script/ops/buy/hourglassPurchase.js b/website/common/script/ops/buy/hourglassPurchase.js index 5354ee20bd..44f6e1a6f4 100644 --- a/website/common/script/ops/buy/hourglassPurchase.js +++ b/website/common/script/ops/buy/hourglassPurchase.js @@ -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) { diff --git a/website/common/script/ops/buy/purchase.js b/website/common/script/ops/buy/purchase.js index bd07248590..752ffed231 100644 --- a/website/common/script/ops/buy/purchase.js +++ b/website/common/script/ops/buy/purchase.js @@ -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}`); } } diff --git a/website/common/script/ops/changeClass.js b/website/common/script/ops/changeClass.js index 618a617619..49af6f481a 100644 --- a/website/common/script/ops/changeClass.js +++ b/website/common/script/ops/changeClass.js @@ -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); diff --git a/website/common/script/ops/equip.js b/website/common/script/ops/equip.js index 679a5aa4a2..329eb46a8b 100644 --- a/website/common/script/ops/equip.js +++ b/website/common/script/ops/equip.js @@ -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; diff --git a/website/common/script/ops/feed.js b/website/common/script/ops/feed.js index 9e4582b71b..1a66770d28 100644 --- a/website/common/script/ops/feed.js +++ b/website/common/script/ops/feed.js @@ -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], diff --git a/website/common/script/ops/hatch.js b/website/common/script/ops/hatch.js index eacb1c0e49..121e45647e 100644 --- a/website/common/script/ops/hatch.js +++ b/website/common/script/ops/hatch.js @@ -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, diff --git a/website/common/script/ops/openMysteryItem.js b/website/common/script/ops/openMysteryItem.js index 4773565b89..c45d63e925 100644 --- a/website/common/script/ops/openMysteryItem.js +++ b/website/common/script/ops/openMysteryItem.js @@ -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', { diff --git a/website/common/script/ops/pinnedGearUtils.js b/website/common/script/ops/pinnedGearUtils.js index bc96ad2be3..e3deed562c 100644 --- a/website/common/script/ops/pinnedGearUtils.js +++ b/website/common/script/ops/pinnedGearUtils.js @@ -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 diff --git a/website/common/script/ops/releaseBoth.js b/website/common/script/ops/releaseBoth.js index c306d92793..75aa65abd7 100644 --- a/website/common/script/ops/releaseBoth.js +++ b/website/common/script/ops/releaseBoth.js @@ -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) { diff --git a/website/common/script/ops/releaseMounts.js b/website/common/script/ops/releaseMounts.js index 4462d11f44..adb9d96dfd 100644 --- a/website/common/script/ops/releaseMounts.js +++ b/website/common/script/ops/releaseMounts.js @@ -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) { diff --git a/website/common/script/ops/releasePets.js b/website/common/script/ops/releasePets.js index 0124474a37..0764fddb62 100644 --- a/website/common/script/ops/releasePets.js +++ b/website/common/script/ops/releasePets.js @@ -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) { diff --git a/website/common/script/ops/revive.js b/website/common/script/ops/revive.js index b8ddb0b637..14ad3d1ddd 100644 --- a/website/common/script/ops/revive.js +++ b/website/common/script/ops/revive.js @@ -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); diff --git a/website/common/script/ops/sell.js b/website/common/script/ops/sell.js index b777a32e76..796ed929c2 100644 --- a/website/common/script/ops/sell.js +++ b/website/common/script/ops/sell.js @@ -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 [ diff --git a/website/common/script/ops/unlock.js b/website/common/script/ops/unlock.js index bbf59c7b2a..7adbac48de 100644 --- a/website/common/script/ops/unlock.js +++ b/website/common/script/ops/unlock.js @@ -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); diff --git a/website/server/controllers/api-v3/auth.js b/website/server/controllers/api-v3/auth.js index 84c5485fe6..bdf8c17a72 100644 --- a/website/server/controllers/api-v3/auth.js +++ b/website/server/controllers/api-v3/auth.js @@ -204,6 +204,8 @@ api.updateUsername = { } else { user.items.pets['Wolf-Veteran'] = 5; } + + user.markModified('items.pets'); } await user.save(); diff --git a/website/server/controllers/api-v3/debug.js b/website/server/controllers/api-v3/debug.js index 27b7156d68..eb2cd31260 100644 --- a/website/server/controllers/api-v3/debug.js +++ b/website/server/controllers/api-v3/debug.js @@ -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}`); } }); diff --git a/website/server/controllers/api-v3/groups.js b/website/server/controllers/api-v3/groups.js index 9eef5108e9..eed4aff4cb 100644 --- a/website/server/controllers/api-v3/groups.js +++ b/website/server/controllers/api-v3/groups.js @@ -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') { diff --git a/website/server/controllers/api-v3/hall.js b/website/server/controllers/api-v3/hall.js index 2073962af0..5309b56e4f 100644 --- a/website/server/controllers/api-v3/hall.js +++ b/website/server/controllers/api-v3/hall.js @@ -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) } diff --git a/website/server/libs/items/utils.js b/website/server/libs/items/utils.js new file mode 100644 index 0000000000..0c0d9adf2f --- /dev/null +++ b/website/server/libs/items/utils.js @@ -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]); + } +} \ No newline at end of file diff --git a/website/server/libs/payments/groupPayments.js b/website/server/libs/payments/groupPayments.js index b632667284..5da9309f1f 100644 --- a/website/server/libs/payments/groupPayments.js +++ b/website/server/libs/payments/groupPayments.js @@ -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); diff --git a/website/server/libs/payments/subscriptions.js b/website/server/libs/payments/subscriptions.js index 549b41d1dc..fe0119e09a 100644 --- a/website/server/libs/payments/subscriptions.js +++ b/website/server/libs/payments/subscriptions.js @@ -140,6 +140,7 @@ async function createSubscription (data) { if (recipient !== group) { recipient.items.pets['Jackalope-RoyalPurple'] = 5; + recipient.markModified('items.pets'); revealMysteryItems(recipient); } diff --git a/website/server/models/coupon.js b/website/server/models/coupon.js index 2d99143b68..f08a8135eb 100644 --- a/website/server/models/coupon.js +++ b/website/server/models/coupon.js @@ -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'}; } diff --git a/website/server/models/user/hooks.js b/website/server/models/user/hooks.js index 75bee4d71d..ed3a7514b5 100644 --- a/website/server/models/user/hooks.js +++ b/website/server/models/user/hooks.js @@ -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 diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index 42871628c8..e411bfb437 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -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},