start fixing commong

This commit is contained in:
Matteo Pagliazzi
2019-10-09 16:51:17 +02:00
parent 9cd43db401
commit 0c27fb24a5
76 changed files with 442 additions and 275 deletions

View File

@@ -72,7 +72,7 @@
"npm": "^6" "npm": "^6"
}, },
"scripts": { "scripts": {
"lint": "eslint --ext .js .", "lint": "eslint --ext .js --fix ./website/server",
"test": "npm run lint && gulp test && gulp apidoc", "test": "npm run lint && gulp test && gulp apidoc",
"test:build": "gulp test:prepare:build", "test:build": "gulp test:prepare:build",
"test:api-v3": "gulp test:api-v3", "test:api-v3": "gulp test:api-v3",

View File

@@ -7,4 +7,4 @@ if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-e
pathToCommon = './script/index'; pathToCommon = './script/index';
} }
module.exports = require(pathToCommon); module.exports = require(pathToCommon); // eslint-disable-line import/no-dynamic-require

View File

@@ -1,5 +0,0 @@
{
"extends": [
"habitrpg/lib/node",
]
}

View File

@@ -0,0 +1,5 @@
module.exports = {
extends: [
"habitrpg/lib/node",
]
}

View File

@@ -12,7 +12,8 @@ export const MIN_SHORTNAME_SIZE_FOR_CHALLENGES = 3;
export const CHAT_FLAG_LIMIT_FOR_HIDING = 2; // hide posts that have this many flags export const CHAT_FLAG_LIMIT_FOR_HIDING = 2; // hide posts that have this many flags
export const CHAT_FLAG_FROM_MOD = 5; // a flag from a moderator counts as this many flags export const CHAT_FLAG_FROM_MOD = 5; // a flag from a moderator counts as this many flags
export const CHAT_FLAG_FROM_SHADOW_MUTE = 10; // a shadow-muted user's post starts with this many flags // a shadow-muted user's post starts with this many flags
export const CHAT_FLAG_FROM_SHADOW_MUTE = 10;
// @TODO use those constants to replace hard-coded numbers // @TODO use those constants to replace hard-coded numbers
export const SUPPORTED_SOCIAL_NETWORKS = [ export const SUPPORTED_SOCIAL_NETWORKS = [

View File

@@ -1,4 +1,4 @@
import prefill from './prefill.js'; import prefill from './prefill';
export default prefill({ export default prefill({
none: {}, none: {},

View File

@@ -1,4 +1,4 @@
import prefill from '../prefill.js'; import prefill from '../prefill';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,5 +1,5 @@
import prefill from '../prefill.js'; import prefill from '../prefill';
import sets from '../sets.js'; import sets from '../sets';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,5 +1,5 @@
import sets from '../sets.js'; import sets from '../sets';
import prefill from '../prefill.js'; import prefill from '../prefill';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,5 +1,5 @@
import sets from '../sets.js'; import sets from '../sets';
import prefill from '../prefill.js'; import prefill from '../prefill';
export default prefill({ export default prefill({
white: {}, white: {},

View File

@@ -1,4 +1,4 @@
import prefill from '../prefill.js'; import prefill from '../prefill';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,9 +1,9 @@
import bangs from './bangs.js'; import bangs from './bangs';
import base from './base.js'; import base from './base';
import beard from './beard.js'; import beard from './beard';
import color from './color.js'; import color from './color';
import flower from './flower.js'; import flower from './flower';
import mustache from './mustache.js'; import mustache from './mustache';
export default { export default {
color, color,

View File

@@ -1,5 +1,5 @@
import sets from '../sets.js'; import sets from '../sets';
import prefill from '../prefill.js'; import prefill from '../prefill';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,5 +1,5 @@
import prefill from '../prefill.js'; import prefill from '../prefill';
import sets from '../sets.js'; import sets from '../sets';
export default prefill({ export default prefill({
0: {}, 0: {},

View File

@@ -1,11 +1,11 @@
import forOwn from 'lodash/forOwn'; import forOwn from 'lodash/forOwn';
import clone from 'lodash/clone'; import clone from 'lodash/clone';
import hair from './hair'; import hair from './hair';
import shirts from './shirt.js'; import shirts from './shirt';
import skins from './skin.js'; import skins from './skin';
import sizes from './size.js'; import sizes from './size';
import backgrounds from './backgrounds.js'; import backgrounds from './backgrounds';
import chairs from './chair.js'; import chairs from './chair';
const reorderedBgs = {}; const reorderedBgs = {};

View File

@@ -2,22 +2,36 @@ import t from '../translation';
import prefill from './prefill'; import prefill from './prefill';
export default prefill({ export default prefill({
baseHair1: {setPrice: 5, text: t('hairSet1')}, baseHair1: { setPrice: 5, text: t('hairSet1') },
baseHair2: {setPrice: 5, text: t('hairSet2')}, baseHair2: { setPrice: 5, text: t('hairSet2') },
baseHair3: {setPrice: 5, text: t('hairSet3')}, baseHair3: { setPrice: 5, text: t('hairSet3') },
facialHair: {setPrice: 5, text: t('bodyFacialHair')}, facialHair: { setPrice: 5, text: t('bodyFacialHair') },
specialShirts: {setPrice: 5, text: t('specialShirts')}, specialShirts: { setPrice: 5, text: t('specialShirts') },
winterHairColors: {setPrice: 5, availableUntil: '2016-01-01'}, winterHairColors: { setPrice: 5, availableUntil: '2016-01-01' },
pastelHairColors: {setPrice: 5, availableUntil: '2016-01-01'}, pastelHairColors: { setPrice: 5, availableUntil: '2016-01-01' },
rainbowHairColors: {setPrice: 5, text: t('rainbowColors')}, rainbowHairColors: { setPrice: 5, text: t('rainbowColors') },
shimmerHairColors: {setPrice: 5, availableFrom: '2019-04-09', availableUntil: '2019-05-02', text: t('shimmerColors')}, shimmerHairColors: {
hauntedHairColors: {setPrice: 5, availableFrom: '2019-10-08', availableUntil: '2019-11-02', text: t('hauntedColors')}, setPrice: 5, availableFrom: '2019-04-09', availableUntil: '2019-05-02', text: t('shimmerColors'),
winteryHairColors: {setPrice: 5, availableFrom: '2019-01-08', availableUntil: '2019-02-02', text: t('winteryColors')}, },
rainbowSkins: {setPrice: 5, text: t('rainbowSkins')}, hauntedHairColors: {
animalSkins: {setPrice: 5, text: t('animalSkins')}, setPrice: 5, availableFrom: '2019-10-08', availableUntil: '2019-11-02', text: t('hauntedColors'),
pastelSkins: {setPrice: 5, availableFrom: '2019-04-09', availableUntil: '2019-05-02', text: t('pastelSkins')}, },
spookySkins: {setPrice: 5, availableUntil: '2016-01-01', text: t('spookySkins')}, winteryHairColors: {
supernaturalSkins: {setPrice: 5, availableFrom: '2019-10-08', availableUntil: '2019-11-02', text: t('supernaturalSkins')}, setPrice: 5, availableFrom: '2019-01-08', availableUntil: '2019-02-02', text: t('winteryColors'),
splashySkins: {setPrice: 5, availableFrom: '2019-07-02', availableUntil: '2019-08-02', text: t('splashySkins')}, },
winterySkins: {setPrice: 5, availableFrom: '2019-01-08', availableUntil: '2019-02-02', text: t('winterySkins')}, rainbowSkins: { setPrice: 5, text: t('rainbowSkins') },
animalSkins: { setPrice: 5, text: t('animalSkins') },
pastelSkins: {
setPrice: 5, availableFrom: '2019-04-09', availableUntil: '2019-05-02', text: t('pastelSkins'),
},
spookySkins: { setPrice: 5, availableUntil: '2016-01-01', text: t('spookySkins') },
supernaturalSkins: {
setPrice: 5, availableFrom: '2019-10-08', availableUntil: '2019-11-02', text: t('supernaturalSkins'),
},
splashySkins: {
setPrice: 5, availableFrom: '2019-07-02', availableUntil: '2019-08-02', text: t('splashySkins'),
},
winterySkins: {
setPrice: 5, availableFrom: '2019-01-08', availableUntil: '2019-02-02', text: t('winterySkins'),
},
}); });

View File

@@ -1,5 +1,5 @@
import sets from './sets.js'; import sets from './sets';
import prefill from './prefill.js'; import prefill from './prefill';
export default prefill({ export default prefill({
black: {}, black: {},

View File

@@ -1,5 +1,5 @@
import prefill from './prefill.js'; import prefill from './prefill';
import sets from './sets.js'; import sets from './sets';
export default prefill({ export default prefill({
/* eslint-disable quote-props */ /* eslint-disable quote-props */

View File

@@ -10,7 +10,7 @@ const faq = {
}, },
}; };
for (let i = 0; i <= NUMBER_OF_QUESTIONS; i++) { for (let i = 0; i <= NUMBER_OF_QUESTIONS; i += 1) {
const question = { const question = {
question: t(`faqQuestion${i}`), question: t(`faqQuestion${i}`),
ios: t(`iosFaqAnswer${i}`), ios: t(`iosFaqAnswer${i}`),

View File

@@ -29,7 +29,8 @@ const gear = {
}; };
/* /*
The gear is exported as a tree (defined above), and a flat list (eg, {weapon_healer_1: .., shield_special_0: ...}) since The gear is exported as a tree (defined above), and a flat list
(eg, {weapon_healer_1: .., shield_special_0: ...}) since
they are needed in different forms at different points in the app they are needed in different forms at different points in the app
*/ */
const flat = {}; const flat = {};
@@ -61,8 +62,11 @@ each(GEAR_TYPES, type => {
item.canOwn = user => { item.canOwn = user => {
const userHasOwnedItem = ownsItem(key)(user); const userHasOwnedItem = ownsItem(key)(user);
const eventIsCurrent = moment().isAfter(item.event.start) && moment().isBefore(item.event.end); const eventIsCurrent = moment()
const compatibleWithUserClass = item.specialClass ? user.stats.class === item.specialClass : true; .isAfter(item.event.start) && moment().isBefore(item.event.end);
const compatibleWithUserClass = item.specialClass
? user.stats.class === item.specialClass
: true;
return _canOwn(user) && (userHasOwnedItem || eventIsCurrent) && compatibleWithUserClass; return _canOwn(user) && (userHasOwnedItem || eventIsCurrent) && compatibleWithUserClass;
}; };

View File

@@ -26,7 +26,7 @@ const weapon = {
// Add Two Handed message to all weapons // Add Two Handed message to all weapons
const rtlLanguages = [ const rtlLanguages = [
'ae', /* Avestan */ 'ae', /* Avestan */
'ar', /* 'العربية', Arabic */ 'ar', /* 'العربية', Arabic */
'arc', /* Aramaic */ 'arc', /* Aramaic */
'bcc', /* 'بلوچی مکرانی', Southern Balochi */ 'bcc', /* 'بلوچی مکرانی', Southern Balochi */

View File

@@ -261,7 +261,10 @@ export default function getLoginIncentives (api) {
}, },
110: { 110: {
rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'], rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'],
reward: [api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig, api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf], reward: [
api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig,
api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf,
],
rewardName: 'oneOfAllPetEggs', rewardName: 'oneOfAllPetEggs',
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0; if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0;
@@ -388,7 +391,10 @@ export default function getLoginIncentives (api) {
}, },
150: { 150: {
rewardKey: ['shop_head_special_clandestineCowl', 'shop_armor_special_sneakthiefRobes'], rewardKey: ['shop_head_special_clandestineCowl', 'shop_armor_special_sneakthiefRobes'],
reward: [api.gear.flat.head_special_clandestineCowl, api.gear.flat.armor_special_sneakthiefRobes], reward: [
api.gear.flat.head_special_clandestineCowl,
api.gear.flat.armor_special_sneakthiefRobes,
],
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
user.items.gear.owned.head_special_clandestineCowl = true; // eslint-disable-line camelcase user.items.gear.owned.head_special_clandestineCowl = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_sneakthiefRobes = true; // eslint-disable-line camelcase user.items.gear.owned.armor_special_sneakthiefRobes = true; // eslint-disable-line camelcase
@@ -406,10 +412,13 @@ export default function getLoginIncentives (api) {
}, },
170: { 170: {
rewardKey: ['shop_head_special_snowSovereignCrown', 'shop_armor_special_snowSovereignRobes'], rewardKey: ['shop_head_special_snowSovereignCrown', 'shop_armor_special_snowSovereignRobes'],
reward: [api.gear.flat.head_special_snowSovereignCrown, api.gear.flat.armor_special_snowSovereignRobes], reward: [
api.gear.flat.head_special_snowSovereignCrown,
api.gear.flat.armor_special_snowSovereignRobes,
],
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
user.items.gear.owned.head_special_snowSovereignCrown = true; // eslint-disable-line camelcase user.items.gear.owned.head_special_snowSovereignCrown = true; // eslint-disable-line camelcase, max-len
user.items.gear.owned.armor_special_snowSovereignRobes = true; // eslint-disable-line camelcase user.items.gear.owned.armor_special_snowSovereignRobes = true; // eslint-disable-line camelcase, max-len
if (user.markModified) user.markModified('items.gear.owned'); if (user.markModified) user.markModified('items.gear.owned');
}, },
}, },
@@ -451,7 +460,10 @@ export default function getLoginIncentives (api) {
}, },
240: { 240: {
rewardKey: ['shop_weapon_special_nomadsScimitar', 'shop_armor_special_nomadsCuirass'], rewardKey: ['shop_weapon_special_nomadsScimitar', 'shop_armor_special_nomadsCuirass'],
reward: [api.gear.flat.weapon_special_nomadsScimitar, api.gear.flat.armor_special_nomadsCuirass], reward: [
api.gear.flat.weapon_special_nomadsScimitar,
api.gear.flat.armor_special_nomadsCuirass,
],
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
user.items.gear.owned.weapon_special_nomadsScimitar = true; // eslint-disable-line camelcase user.items.gear.owned.weapon_special_nomadsScimitar = true; // eslint-disable-line camelcase
user.items.gear.owned.armor_special_nomadsCuirass = true; // eslint-disable-line camelcase user.items.gear.owned.armor_special_nomadsCuirass = true; // eslint-disable-line camelcase
@@ -468,7 +480,11 @@ export default function getLoginIncentives (api) {
}, },
280: { 280: {
rewardKey: ['Pet_Food_Meat', 'Pet_Food_Potatoe', 'Pet_Food_Milk'], rewardKey: ['Pet_Food_Meat', 'Pet_Food_Potatoe', 'Pet_Food_Milk'],
reward: [api.food.Meat, api.food.CottonCandyBlue, api.food.CottonCandyPink, api.food.Potatoe, api.food.Honey, api.food.Strawberry, api.food.Chocolate, api.food.Fish, api.food.Milk, api.food.RottenMeat], reward: [
api.food.Meat, api.food.CottonCandyBlue, api.food.CottonCandyPink,
api.food.Potatoe, api.food.Honey, api.food.Strawberry, api.food.Chocolate,
api.food.Fish, api.food.Milk, api.food.RottenMeat,
],
rewardName: 'threeOfEachFood', rewardName: 'threeOfEachFood',
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
if (!user.items.food.Meat) user.items.food.Meat = 0; if (!user.items.food.Meat) user.items.food.Meat = 0;
@@ -496,7 +512,10 @@ export default function getLoginIncentives (api) {
}, },
300: { 300: {
rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'], rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'],
reward: [api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig, api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf], reward: [
api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig,
api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf,
],
rewardName: 'twoOfAllPetEggs', rewardName: 'twoOfAllPetEggs',
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0; if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0;
@@ -549,7 +568,10 @@ export default function getLoginIncentives (api) {
}, },
380: { 380: {
rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'], rewardKey: ['Pet_Egg_Cactus', 'Pet_Egg_Dragon', 'Pet_Egg_Wolf'],
reward: [api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig, api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf], reward: [
api.eggs.BearCub, api.eggs.Cactus, api.eggs.Dragon, api.eggs.FlyingPig,
api.eggs.Fox, api.eggs.LionCub, api.eggs.PandaCub, api.eggs.TigerCub, api.eggs.Wolf,
],
rewardName: 'threeOfAllPetEggs', rewardName: 'threeOfAllPetEggs',
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0; if (!user.items.eggs.BearCub) user.items.eggs.BearCub = 0;
@@ -575,7 +597,11 @@ export default function getLoginIncentives (api) {
}, },
400: { 400: {
rewardKey: ['Pet_Food_Meat', 'Pet_Food_Potatoe', 'Pet_Food_Milk'], rewardKey: ['Pet_Food_Meat', 'Pet_Food_Potatoe', 'Pet_Food_Milk'],
reward: [api.food.Meat, api.food.CottonCandyBlue, api.food.CottonCandyPink, api.food.Potatoe, api.food.Honey, api.food.Strawberry, api.food.Chocolate, api.food.Fish, api.food.Milk, api.food.RottenMeat], reward: [
api.food.Meat, api.food.CottonCandyBlue, api.food.CottonCandyPink,
api.food.Potatoe, api.food.Honey, api.food.Strawberry, api.food.Chocolate,
api.food.Fish, api.food.Milk, api.food.RottenMeat,
],
rewardName: 'fourOfEachFood', rewardName: 'fourOfEachFood',
assignReward: function assignReward (user) { assignReward: function assignReward (user) {
if (!user.items.food.Meat) user.items.food.Meat = 0; if (!user.items.food.Meat) user.items.food.Meat = 0;
@@ -638,10 +664,12 @@ export default function getLoginIncentives (api) {
}, },
}, },
}; };
// When the final check-in prize is added here, change checkinReceivedAllRewardsMessage in website/common/locales/en/loginIncentives.json // When the final check-in prize is added here,
// change checkinReceivedAllRewardsMessage in website/common/locales/en/loginIncentives.json
// to say "You have received the final Check-In prize!". Confirm the message with Lemoness first. // to say "You have received the final Check-In prize!". Confirm the message with Lemoness first.
// Add reference link to next reward and add filler days so we have a map to reference the next reward from any day // Add reference link to next reward and add filler days
// so we have a map to reference the next reward from any day
// We could also, use a list, but then we would be cloning each of the rewards. // We could also, use a list, but then we would be cloning each of the rewards.
// Create a new array if we want the loginIncentives to be immutable in the future // Create a new array if we want the loginIncentives to be immutable in the future
let nextRewardKey; let nextRewardKey;

View File

@@ -3505,14 +3505,15 @@ const quests = {
}; };
each(quests, (v, key) => { each(quests, (v, key) => {
let b;
defaults(v, { defaults(v, {
key, key,
canBuy () { canBuy () {
return true; return true;
}, },
}); });
b = v.boss;
const b = v.boss;
if (b) { if (b) {
defaults(b, { defaults(b, {
str: 1, str: 1,

View File

@@ -11,23 +11,32 @@ import updateStats from '../fns/updateStats';
--------------------------------------------------------------- ---------------------------------------------------------------
Text, notes, and mana are obvious. The rest: Text, notes, and mana are obvious. The rest:
* {target}: one of [task, self, party, user]. This is very important, because if the cast() function is expecting one * {target}: one of [task, self, party, user].
thing and receives another, it will cause errors. `self` is used for self buffs, multi-task debuffs, AOEs (eg, meteor-shower), * This is very important, because if the cast() function is expecting one
thing and receives another, it will cause errors.
`self` is used for self buffs, multi-task debuffs, AOEs (eg, meteor-shower),
etc. Basically, use self for anything that's not [task, party, user] and is an instant-cast etc. Basically, use self for anything that's not [task, party, user] and is an instant-cast
* {cast}: the function that's run to perform the ability's action. This is pretty slick - because this is exported to the * {cast}: the function that's run to perform the ability's action.
web, this function can be performed on the client and on the server. `user` param is self (needed for determining your This is pretty slick - because this is exported to the
own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` skills, web, this function can be performed on the client and on the server.
you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the `user` param is self (needed for determining your
spell is correct. Take a look at habitrpg/website/server/models/user.js and habitrpg/website/server/models/task.js for what attributes are own stats for effectiveness of cast), and `target` param is one of [task, party, user].
available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users, In the case of `self` skills,
you act on `user` instead of `target`. You can trust these are the correct objects,
as long as the `target` attr of the
spell is correct. Take a look at habitrpg/website/server/models/user.js and
habitrpg/website/server/models/task.js for what attributes are
available on each model. Note `task.value` is its "redness".
If party is passed in, it's an array of users,
so you'll want to iterate over them like: `_.each(target,function(member){...})` so you'll want to iterate over them like: `_.each(target,function(member){...})`
Note, user.stats.mp is docked after automatically (it's appended to functions automatically down below in an _.each) Note, user.stats.mp is docked after automatically
(it's appended to functions automatically down below in an _.each)
*/ */
function diminishingReturns (bonus, max, halfway) { function diminishingReturns (bonus, max, halfway) {
if (!halfway) halfway = max / 2; if (!halfway) halfway = max / 2; // eslint-disable-line no-param-reassign
return max * (bonus / (bonus + halfway)); return max * (bonus / (bonus + halfway));
} }
@@ -274,8 +283,8 @@ spells.special = {
target.stats.buffs.shinySeed = false; target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false; target.stats.buffs.seafoam = false;
if (!target.achievements.snowball) target.achievements.snowball = 0; if (!target.achievements.snowball) target.achievements.snowball = 0;
target.achievements.snowball++; target.achievements.snowball += 1;
user.items.special.snowball--; user.items.special.snowball -= 1;
}, },
}, },
salt: { salt: {
@@ -305,8 +314,8 @@ spells.special = {
target.stats.buffs.shinySeed = false; target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false; target.stats.buffs.seafoam = false;
if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0; if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0;
target.achievements.spookySparkles++; target.achievements.spookySparkles += 1;
user.items.special.spookySparkles--; user.items.special.spookySparkles -= 1;
}, },
}, },
opaquePotion: { opaquePotion: {
@@ -336,8 +345,8 @@ spells.special = {
target.stats.buffs.shinySeed = true; target.stats.buffs.shinySeed = true;
target.stats.buffs.seafoam = false; target.stats.buffs.seafoam = false;
if (!target.achievements.shinySeed) target.achievements.shinySeed = 0; if (!target.achievements.shinySeed) target.achievements.shinySeed = 0;
target.achievements.shinySeed++; target.achievements.shinySeed += 1;
user.items.special.shinySeed--; user.items.special.shinySeed -= 1;
}, },
}, },
petalFreePotion: { petalFreePotion: {
@@ -367,8 +376,8 @@ spells.special = {
target.stats.buffs.shinySeed = false; target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = true; target.stats.buffs.seafoam = true;
if (!target.achievements.seafoam) target.achievements.seafoam = 0; if (!target.achievements.seafoam) target.achievements.seafoam = 0;
target.achievements.seafoam++; target.achievements.seafoam += 1;
user.items.special.seafoam--; user.items.special.seafoam -= 1;
}, },
}, },
sand: { sand: {
@@ -395,11 +404,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.nye) user.achievements.nye = 0; if (!user.achievements.nye) user.achievements.nye = 0;
user.achievements.nye++; user.achievements.nye += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.nye) u.achievements.nye = 0; if (!u.achievements.nye) u.achievements.nye = 0;
u.achievements.nye++; u.achievements.nye += 1;
}); });
} }
@@ -432,11 +441,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.valentine) user.achievements.valentine = 0; if (!user.achievements.valentine) user.achievements.valentine = 0;
user.achievements.valentine++; user.achievements.valentine += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.valentine) u.achievements.valentine = 0; if (!u.achievements.valentine) u.achievements.valentine = 0;
u.achievements.valentine++; u.achievements.valentine += 1;
}); });
} }
@@ -469,11 +478,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.greeting) user.achievements.greeting = 0; if (!user.achievements.greeting) user.achievements.greeting = 0;
user.achievements.greeting++; user.achievements.greeting += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.greeting) u.achievements.greeting = 0; if (!u.achievements.greeting) u.achievements.greeting = 0;
u.achievements.greeting++; u.achievements.greeting += 1;
}); });
} }
@@ -506,11 +515,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.thankyou) user.achievements.thankyou = 0; if (!user.achievements.thankyou) user.achievements.thankyou = 0;
user.achievements.thankyou++; user.achievements.thankyou += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.thankyou) u.achievements.thankyou = 0; if (!u.achievements.thankyou) u.achievements.thankyou = 0;
u.achievements.thankyou++; u.achievements.thankyou += 1;
}); });
} }
@@ -543,11 +552,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.birthday) user.achievements.birthday = 0; if (!user.achievements.birthday) user.achievements.birthday = 0;
user.achievements.birthday++; user.achievements.birthday += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.birthday) u.achievements.birthday = 0; if (!u.achievements.birthday) u.achievements.birthday = 0;
u.achievements.birthday++; u.achievements.birthday += 1;
}); });
} }
@@ -580,11 +589,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.congrats) user.achievements.congrats = 0; if (!user.achievements.congrats) user.achievements.congrats = 0;
user.achievements.congrats++; user.achievements.congrats += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.congrats) u.achievements.congrats = 0; if (!u.achievements.congrats) u.achievements.congrats = 0;
u.achievements.congrats++; u.achievements.congrats += 1;
}); });
} }
@@ -617,11 +626,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.getwell) user.achievements.getwell = 0; if (!user.achievements.getwell) user.achievements.getwell = 0;
user.achievements.getwell++; user.achievements.getwell += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.getwell) u.achievements.getwell = 0; if (!u.achievements.getwell) u.achievements.getwell = 0;
u.achievements.getwell++; u.achievements.getwell += 1;
}); });
} }
@@ -654,11 +663,11 @@ spells.special = {
cast (user, target) { cast (user, target) {
if (user === target) { if (user === target) {
if (!user.achievements.goodluck) user.achievements.goodluck = 0; if (!user.achievements.goodluck) user.achievements.goodluck = 0;
user.achievements.goodluck++; user.achievements.goodluck += 1;
} else { } else {
each([user, target], u => { each([user, target], u => {
if (!u.achievements.goodluck) u.achievements.goodluck = 0; if (!u.achievements.goodluck) u.achievements.goodluck = 0;
u.achievements.goodluck++; u.achievements.goodluck += 1;
}); });
} }

View File

@@ -50,6 +50,6 @@ const subscriptionBlocks = {
}, },
}; };
each(subscriptionBlocks, (b, k) => b.key = k); each(subscriptionBlocks, (b, k) => { b.key = k; });
export default subscriptionBlocks; export default subscriptionBlocks;

View File

@@ -1,6 +1,6 @@
import t from './translation'; import t from './translation';
export const tasksByCategory = { export const tasksByCategory = { // eslint-disable-line import/prefer-default-export
work: [ work: [
{ {
type: 'habit', type: 'habit',

View File

@@ -9,9 +9,11 @@ import gear from './gear';
const mystery = mysterySets; const mystery = mysterySets;
each(mystery, (v, k) => v.items = filter(gear.flat, { each(mystery, (v, k) => {
mystery: k, v.items = filter(gear.flat, {
})); mystery: k,
});
});
const timeTravelerStore = user => { const timeTravelerStore = user => {
let ownedKeys; let ownedKeys;

View File

@@ -10,7 +10,7 @@ export function beastMasterProgress (pets = {}) {
let count = 0; let count = 0;
each(DROP_ANIMALS, animal => { each(DROP_ANIMALS, animal => {
if (pets[animal] > 0 || pets[animal] === -1) count++; if (pets[animal] > 0 || pets[animal] === -1) count += 1;
}); });
return count; return count;
@@ -20,7 +20,7 @@ export function beastCount (pets = {}) {
let count = 0; let count = 0;
each(DROP_ANIMALS, animal => { each(DROP_ANIMALS, animal => {
if (pets[animal] > 0) count++; if (pets[animal] > 0) count += 1;
}); });
return count; return count;
@@ -30,7 +30,7 @@ export function dropPetsCurrentlyOwned (pets = {}) {
let count = 0; let count = 0;
each(DROP_ANIMALS, animal => { each(DROP_ANIMALS, animal => {
if (pets[animal] > 0) count++; if (pets[animal] > 0) count += 1;
}); });
return count; return count;
@@ -40,7 +40,7 @@ export function mountMasterProgress (mounts = {}) {
let count = 0; let count = 0;
each(DROP_ANIMALS, animal => { each(DROP_ANIMALS, animal => {
if (mounts[animal]) count++; if (mounts[animal]) count += 1;
}); });
return count; return count;

View File

@@ -23,8 +23,10 @@ export const DAY_MAPPING_STRING_TO_NUMBER = invert(DAY_MAPPING);
/* /*
Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences. Each time we perform date maths (cron, task-due-days, etc), we need to consider user preferences.
Specifically {dayStart} (custom day start) and {timezoneOffset}. This function sanitizes / defaults those values. Specifically {dayStart} (custom day start) and {timezoneOffset}.
{now} is also passed in for various purposes, one example being the test scripts scripts testing different "now" times. This function sanitizes / defaults those values.
{now} is also passed in for various purposes,
one example being the test scripts scripts testing different "now" times.
*/ */
function sanitizeOptions (o) { function sanitizeOptions (o) {
@@ -34,7 +36,7 @@ function sanitizeOptions (o) {
let timezoneOffset; let timezoneOffset;
const timezoneOffsetDefault = Number(moment().zone()); const timezoneOffsetDefault = Number(moment().zone());
if (isFinite(o.timezoneOffsetOverride)) { if (Number.isFinite(o.timezoneOffsetOverride)) {
timezoneOffset = Number(o.timezoneOffsetOverride); timezoneOffset = Number(o.timezoneOffsetOverride);
} else if (Number.isFinite(o.timezoneOffset)) { } else if (Number.isFinite(o.timezoneOffset)) {
timezoneOffset = Number(o.timezoneOffset); timezoneOffset = Number(o.timezoneOffset);
@@ -62,10 +64,17 @@ export function startOfWeek (options = {}) {
} }
/* /*
This is designed for use with any date that has an important time portion (e.g., when comparing the current date-time with the previous cron's date-time for determing if cron should run now). This is designed for use with any date that has an important time portion
It changes the time portion of the date-time to be the Custom Day Start hour, so that the date-time is now the user's correct start of day. (e.g., when comparing the current date-time with the previous cron's date-time
It SUBTRACTS a day if the date-time's original hour is before CDS (e.g., if your CDS is 5am and it's currently 4am, it's still the previous day). for determing if cron should run now).
This is NOT suitable for manipulating any dates that are displayed to the user as a date with no time portion, such as a Daily's Start Dates (e.g., a Start Date of today shows only the date, so it should be considered to be today even if the hidden time portion is before CDS). It changes the time portion of the date-time to be the Custom Day Start hour,
so that the date-time is now the user's correct start of day.
It SUBTRACTS a day if the date-time's original hour is before CDS
(e.g., if your CDS is 5am and it's currently 4am, it's still the previous day).
This is NOT suitable for manipulating any dates that are displayed to the user
as a date with no time portion, such as a Daily's Start Dates
(e.g., a Start Date of today shows only the date,
so it should be considered to be today even if the hidden time portion is before CDS).
*/ */
export function startOfDay (options = {}) { export function startOfDay (options = {}) {
@@ -92,7 +101,8 @@ export function daysSince (yesterday, options = {}) {
} }
/* /*
Should the user do this task on this date, given the task's repeat options and user.preferences.dayStart? Should the user do this task on this date,
given the task's repeat options and user.preferences.dayStart?
*/ */
export function shouldDo (day, dailyTask, options = {}) { export function shouldDo (day, dailyTask, options = {}) {
@@ -102,9 +112,12 @@ export function shouldDo (day, dailyTask, options = {}) {
const o = sanitizeOptions(options); const o = sanitizeOptions(options);
const startOfDayWithCDSTime = startOfDay(defaults({ now: day }, o)); const startOfDayWithCDSTime = startOfDay(defaults({ now: day }, o));
// The time portion of the Start Date is never visible to or modifiable by the user so we must ignore it. // The time portion of the Start Date is never visible to
// Therefore, we must also ignore the time portion of the user's day start (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times. // or modifiable by the user so we must ignore it.
// NB: The user's day start date has already been converted to the PREVIOUS day's date if the time portion was before CDS. // Therefore, we must also ignore the time portion of the user's day start
// (startOfDayWithCDSTime), otherwise the date comparison will be wrong for some times.
// NB: The user's day start date has already been converted to the PREVIOUS
// day's date if the time portion was before CDS.
const startDate = moment(dailyTask.startDate).zone(o.timezoneOffset).startOf('day'); const startDate = moment(dailyTask.startDate).zone(o.timezoneOffset).startOf('day');
@@ -115,7 +128,7 @@ export function shouldDo (day, dailyTask, options = {}) {
const daysOfTheWeek = []; const daysOfTheWeek = [];
if (dailyTask.repeat) { if (dailyTask.repeat) {
for (const [repeatDay, active] of Object.entries(dailyTask.repeat)) { for (const [repeatDay, active] of Object.entries(dailyTask.repeat)) {
if (!isFinite(DAY_MAPPING_STRING_TO_NUMBER[repeatDay])) continue; // eslint-disable-line no-continue if (!Number.isFinite(DAY_MAPPING_STRING_TO_NUMBER[repeatDay])) continue; // eslint-disable-line no-continue
if (active) daysOfTheWeek.push(parseInt(DAY_MAPPING_STRING_TO_NUMBER[repeatDay], 10)); if (active) daysOfTheWeek.push(parseInt(DAY_MAPPING_STRING_TO_NUMBER[repeatDay], 10));
} }
} }
@@ -127,7 +140,7 @@ export function shouldDo (day, dailyTask, options = {}) {
if (options.nextDue) { if (options.nextDue) {
const filteredDates = []; const filteredDates = [];
for (let i = 1; filteredDates.length < 6; i++) { for (let i = 1; filteredDates.length < 6; i += 1) {
const calcDate = moment(startDate).add(dailyTask.everyX * i, 'days'); const calcDate = moment(startDate).add(dailyTask.everyX * i, 'days');
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
} }
@@ -145,8 +158,8 @@ export function shouldDo (day, dailyTask, options = {}) {
schedule = schedule.every(daysOfTheWeek).daysOfWeek(); schedule = schedule.every(daysOfTheWeek).daysOfWeek();
if (options.nextDue) { if (options.nextDue) {
const filteredDates = []; const filteredDates = [];
for (let i = 0; filteredDates.length < 6; i++) { for (let i = 0; filteredDates.length < 6; i += 1) {
for (let j = 0; j < daysOfTheWeek.length && filteredDates.length < 6; j++) { for (let j = 0; j < daysOfTheWeek.length && filteredDates.length < 6; j += 1) {
const calcDate = moment(startDate).day(daysOfTheWeek[j]).add(dailyTask.everyX * i, 'weeks'); const calcDate = moment(startDate).day(daysOfTheWeek[j]).add(dailyTask.everyX * i, 'weeks');
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
} }
@@ -177,7 +190,7 @@ export function shouldDo (day, dailyTask, options = {}) {
if (options.nextDue) { if (options.nextDue) {
const filteredDates = []; const filteredDates = [];
for (let i = 1; filteredDates.length < 6; i++) { for (let i = 1; filteredDates.length < 6; i += 1) {
const recurDate = moment(startDate).add(dailyTask.everyX * i, 'months'); const recurDate = moment(startDate).add(dailyTask.everyX * i, 'months');
const calcDate = recurDate.clone(); const calcDate = recurDate.clone();
calcDate.day(daysOfTheWeek[0]); calcDate.day(daysOfTheWeek[0]);
@@ -193,8 +206,11 @@ export function shouldDo (day, dailyTask, options = {}) {
calcDateWeek = Math.ceil(calcDate.date() / 7); calcDateWeek = Math.ceil(calcDate.date() / 7);
if (calcDate >= startOfDayWithCDSTime if (
&& calcDateWeek === startDateWeek && calcDate.month() === recurDate.month()) filteredDates.push(calcDate); calcDate >= startOfDayWithCDSTime
&& calcDateWeek === startDateWeek
&& calcDate.month() === recurDate.month()
) filteredDates.push(calcDate);
} }
return filteredDates; return filteredDates;
} }
@@ -204,7 +220,7 @@ export function shouldDo (day, dailyTask, options = {}) {
schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth(); schedule = schedule.every(dailyTask.daysOfMonth).daysOfMonth();
if (options.nextDue) { if (options.nextDue) {
const filteredDates = []; const filteredDates = [];
for (let i = 1; filteredDates.length < 6; i++) { for (let i = 1; filteredDates.length < 6; i += 1) {
const calcDate = moment(startDate).add(dailyTask.everyX * i, 'months'); const calcDate = moment(startDate).add(dailyTask.everyX * i, 'months');
if (calcDate >= startOfDayWithCDSTime) filteredDates.push(calcDate); if (calcDate >= startOfDayWithCDSTime) filteredDates.push(calcDate);
} }
@@ -220,7 +236,7 @@ export function shouldDo (day, dailyTask, options = {}) {
if (options.nextDue) { if (options.nextDue) {
const filteredDates = []; const filteredDates = [];
for (let i = 1; filteredDates.length < 6; i++) { for (let i = 1; filteredDates.length < 6; i += 1) {
const calcDate = moment(startDate).add(dailyTask.everyX * i, 'years'); const calcDate = moment(startDate).add(dailyTask.everyX * i, 'years');
if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate); if (calcDate > startOfDayWithCDSTime) filteredDates.push(calcDate);
} }

View File

@@ -10,7 +10,8 @@ import splitWhitespace from '../libs/splitWhitespace';
/* /*
Updates user stats with new stats. Handles death, leveling up, etc Updates user stats with new stats. Handles death, leveling up, etc
{stats} new stats {stats} new stats
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately {update} if aggregated changes, pass in userObj as update.
otherwise commits will be made immediately
*/ */
function getStatToAllocate (user) { function getStatToAllocate (user) {
@@ -53,9 +54,7 @@ function getStatToAllocate (user) {
statsObj[preference[3]] - ideal[3], statsObj[preference[3]] - ideal[3],
]; ];
suggested = findIndex(diff, val => { suggested = findIndex(diff, val => val === min(diff));
if (val === min(diff)) return true;
});
return suggested !== -1 ? preference[suggested] : 'str'; return suggested !== -1 ? preference[suggested] : 'str';
} }
@@ -77,6 +76,6 @@ function getStatToAllocate (user) {
export default function autoAllocate (user) { export default function autoAllocate (user) {
const statToIncrease = getStatToAllocate(user); const statToIncrease = getStatToAllocate(user);
user.stats[statToIncrease] += 1;
return user.stats[statToIncrease]++; return user.stats[statToIncrease];
} }

View File

@@ -2,17 +2,19 @@ import omit from 'lodash/omit';
import reduce from 'lodash/reduce'; import reduce from 'lodash/reduce';
import isNumber from 'lodash/isNumber'; import isNumber from 'lodash/isNumber';
// Because the same op needs to be performed on the client and the server (critical hits, item drops, etc), // Because the same op needs to be performed on the client and the server
// (critical hits, item drops, etc),
// we need things to be "random", but technically predictable so that they don't go out-of-sync // we need things to be "random", but technically predictable so that they don't go out-of-sync
export default function predictableRandom (user, seed) { export default function predictableRandom (user, seed) {
if (!seed || seed === Math.PI) { if (!seed || seed === Math.PI) {
let stats = user.stats.toObject ? user.stats.toObject() : user.stats; let stats = user.stats.toObject ? user.stats.toObject() : user.stats;
// These items are not part of the stat object but exists on the server (see controllers/user#getUser) // These items are not part of the stat object but exists on the server
// (see controllers/user#getUser)
// we remove them in order to use the same user.stats both on server and on client // we remove them in order to use the same user.stats both on server and on client
stats = omit(stats, ['toNextLevel', 'maxHealth', 'maxMP']); stats = omit(stats, ['toNextLevel', 'maxHealth', 'maxMP']);
seed = reduce(stats, (accumulator, val) => { seed = reduce(stats, (accumulator, val) => { // eslint-disable-line no-param-reassign
if (isNumber(val)) { if (isNumber(val)) {
return accumulator + val; return accumulator + val;
} }
@@ -20,6 +22,7 @@ export default function predictableRandom (user, seed) {
}, 0); }, 0);
} }
const x = Math.sin(seed++) * 10000; seed += 1; // eslint-disable-line no-param-reassign
const x = Math.sin(seed) * 10000;
return x - Math.floor(x); return x - Math.floor(x);
} }

View File

@@ -14,10 +14,10 @@ import statsComputed from '../libs/statsComputed';
// TODO This is only used on the server // TODO This is only used on the server
// move to user model as an instance method? // move to user model as an instance method?
// Clone a drop object maintaining its functions so that we can change it without affecting the original item // Clone a drop object maintaining its functions
// so that we can change it without affecting the original item
function cloneDropItem (drop) { function cloneDropItem (drop) {
return cloneDeepWith(drop, val => (isFunction(val) ? val : undefined), // undefined will be handled by lodash return cloneDeepWith(drop, val => (isFunction(val) ? val : undefined));
);
} }
function trueRandom () { function trueRandom () {
@@ -40,14 +40,17 @@ export default function randomDrop (user, options, req = {}, analytics) {
* (1 + (user.contributor.level / 40 || 0)) // Contrib levels: +2.5% per level * (1 + (user.contributor.level / 40 || 0)) // Contrib levels: +2.5% per level
* (1 + (user.achievements.rebirths / 20 || 0)) // Rebirths: +5% per achievement * (1 + (user.achievements.rebirths / 20 || 0)) // Rebirths: +5% per achievement
* (1 + (user.achievements.streak / 200 || 0)) // Streak achievements: +0.5% per achievement * (1 + (user.achievements.streak / 200 || 0)) // Streak achievements: +0.5% per achievement
* (user._tmp.crit || 1) * (1 + 0.5 * (reduce(task.checklist, (m, i) => // +50% per checklist item complete. TODO: make this into X individual drop chances instead // +50% per checklist item complete. TODO: make this into X individual drop chances instead
m + (i.completed ? 1 : 0), // eslint-disable-line indent * (user._tmp.crit || 1)
0) || 0)); // eslint-disable-line indent * (1 + 0.5 * (reduce(
task.checklist, (m, i) => m + (i.completed ? 1 : 0), // eslint-disable-line indent
0,
) || 0)); // eslint-disable-line indent
chance = diminishingReturns(chance, 0.75); chance = diminishingReturns(chance, 0.75);
if (predictableRandom() < chance) { if (predictableRandom() < chance) {
user.party.quest.progress.collectedItems = user.party.quest.progress.collectedItems || 0; user.party.quest.progress.collectedItems = user.party.quest.progress.collectedItems || 0;
user.party.quest.progress.collectedItems++; user.party.quest.progress.collectedItems += 1;
user._tmp.quest = user._tmp.quest || {}; user._tmp.quest = user._tmp.quest || {};
user._tmp.quest.collection = 1; user._tmp.quest.collection = 1;
if (user.markModified) user.markModified('party.quest.progress'); if (user.markModified) user.markModified('party.quest.progress');
@@ -59,8 +62,10 @@ export default function randomDrop (user, options, req = {}, analytics) {
dropMultiplier = 1; dropMultiplier = 1;
} }
if (daysSince(user.items.lastDrop.date, user.preferences) === 0 if (
&& user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(statsComputed(user).per / 25) + (user.contributor.level || 0))) { daysSince(user.items.lastDrop.date, user.preferences) === 0
&& user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(statsComputed(user).per / 25) + (user.contributor.level || 0))
) {
return; return;
} }
@@ -85,7 +90,7 @@ export default function randomDrop (user, options, req = {}, analytics) {
drop = cloneDropItem(randomVal(content.dropEggs)); drop = cloneDropItem(randomVal(content.dropEggs));
user.items.eggs[drop.key] = user.items.eggs[drop.key] || 0; user.items.eggs[drop.key] = user.items.eggs[drop.key] || 0;
user.items.eggs[drop.key]++; user.items.eggs[drop.key] += 1;
if (user.markModified) user.markModified('items.eggs'); if (user.markModified) user.markModified('items.eggs');
drop.type = 'Egg'; drop.type = 'Egg';
@@ -103,10 +108,12 @@ export default function randomDrop (user, options, req = {}, analytics) {
} else { // common, 40% of 30% } else { // common, 40% of 30%
acceptableDrops = ['Base', 'White', 'Desert']; acceptableDrops = ['Base', 'White', 'Desert'];
} }
drop = cloneDropItem(randomVal(pickBy(content.hatchingPotions, (v, k) => acceptableDrops.indexOf(k) >= 0))); drop = cloneDropItem(
randomVal(pickBy(content.hatchingPotions, (v, k) => acceptableDrops.indexOf(k) >= 0)),
);
user.items.hatchingPotions[drop.key] = user.items.hatchingPotions[drop.key] || 0; user.items.hatchingPotions[drop.key] = user.items.hatchingPotions[drop.key] || 0;
user.items.hatchingPotions[drop.key]++; user.items.hatchingPotions[drop.key] += 1;
if (user.markModified) user.markModified('items.hatchingPotions'); if (user.markModified) user.markModified('items.hatchingPotions');
drop.type = 'HatchingPotion'; drop.type = 'HatchingPotion';
@@ -128,6 +135,6 @@ export default function randomDrop (user, options, req = {}, analytics) {
user._tmp.drop = drop; user._tmp.drop = drop;
user.items.lastDrop.date = Number(new Date()); user.items.lastDrop.date = Number(new Date());
user.items.lastDrop.count++; user.items.lastDrop.count += 1;
} }
} }

View File

@@ -4,7 +4,9 @@ import includes from 'lodash/includes';
import content from '../content/index'; import content from '../content/index';
export default function ultimateGear (user) { export default function ultimateGear (user) {
const owned = user.items.gear.owned.toObject ? user.items.gear.owned.toObject() : user.items.gear.owned; const owned = user.items.gear.owned.toObject
? user.items.gear.owned.toObject()
: user.items.gear.owned;
content.classes.forEach(klass => { content.classes.forEach(klass => {
if (user.achievements.ultimateGearSets[klass] !== true) { if (user.achievements.ultimateGearSets[klass] !== true) {

View File

@@ -24,7 +24,7 @@ export default function updateStats (user, stats, req = {}, analytics) {
while (stats.exp >= experienceToNextLevel) { while (stats.exp >= experienceToNextLevel) {
stats.exp -= experienceToNextLevel; stats.exp -= experienceToNextLevel;
user.stats.lvl++; user.stats.lvl += 1;
experienceToNextLevel = toNextLevel(user.stats.lvl); experienceToNextLevel = toNextLevel(user.stats.lvl);
user.stats.hp = MAX_HEALTH; user.stats.hp = MAX_HEALTH;
@@ -71,7 +71,7 @@ export default function updateStats (user, stats, req = {}, analytics) {
if (user.addNotification) user.addNotification('DROPS_ENABLED'); if (user.addNotification) user.addNotification('DROPS_ENABLED');
if (user.items.eggs.Wolf > 0) { if (user.items.eggs.Wolf > 0) {
user.items.eggs.Wolf++; user.items.eggs.Wolf += 1;
} else { } else {
user.items.eggs.Wolf = 1; user.items.eggs.Wolf = 1;
} }
@@ -89,7 +89,7 @@ export default function updateStats (user, stats, req = {}, analytics) {
if (user.markModified) user.markModified('flags.levelDrops'); if (user.markModified) user.markModified('flags.levelDrops');
if (!user.items.quests[k]) user.items.quests[k] = 0; if (!user.items.quests[k]) user.items.quests[k] = 0;
user.items.quests[k]++; user.items.quests[k] += 1;
if (user.markModified) user.markModified('items.quests'); if (user.markModified) user.markModified('items.quests');
if (analytics) { if (analytics) {

View File

@@ -9,14 +9,15 @@ const i18n = {
}; };
function t (stringName) { function t (stringName) {
let vars = arguments[1]; const args = Array.from(arguments); // eslint-disable-line prefer-rest-params
let vars = args[1];
let locale; let locale;
if (isString(arguments[1])) { if (isString(args[1])) {
vars = null; vars = null;
locale = arguments[1]; locale = args[1]; // eslint-disable-line prefer-destructuring
} else if (arguments[2]) { } else if (args[2]) {
locale = arguments[2]; locale = args[2]; // eslint-disable-line prefer-destructuring
} }
const i18nNotSetup = !i18n.strings && !i18n.translations[locale]; const i18nNotSetup = !i18n.strings && !i18n.translations[locale];

View File

@@ -7,8 +7,9 @@ const achievsContent = content.achievements;
let index = 0; let index = 0;
function contribText (contrib, backer, language) { function contribText (contrib, backer, language) {
if (!contrib && !backer) return; if (!contrib && !backer) return null;
if (backer && backer.npc) return backer.npc; if (backer && backer.npc) return backer.npc;
const lvl = contrib && contrib.level; const lvl = contrib && contrib.level;
if (lvl && lvl > 0) { if (lvl && lvl > 0) {
let contribTitle = ''; let contribTitle = '';
@@ -29,6 +30,8 @@ function contribText (contrib, backer, language) {
return `${contribTitle} ${contrib.text}`; return `${contribTitle} ${contrib.text}`;
} }
return null;
} }
function _add (result, data) { function _add (result, data) {
@@ -38,7 +41,7 @@ function _add (result, data) {
icon: data.icon, icon: data.icon,
earned: data.earned, earned: data.earned,
value: data.value, value: data.value,
index: index++, index: index += 1,
optionalCount: data.optionalCount, optionalCount: data.optionalCount,
}; };
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
import extendableBuiltin from './extendableBuiltin'; import extendableBuiltin from './extendableBuiltin';
// Base class for custom application errors // Base class for custom application errors

View File

@@ -1,8 +1,9 @@
// TODO use https://babeljs.io/docs/en/babel-plugin-transform-classes
// Babel 6 doesn't support extending native class (Error, Array, ...) // Babel 6 doesn't support extending native class (Error, Array, ...)
// This function makes it possible to extend native classes with the same results as Babel 5 // This function makes it possible to extend native classes with the same results as Babel 5
export default function extendableBuiltin (klass) { export default function extendableBuiltin (klass) {
function ExtendableBuiltin () { function ExtendableBuiltin () {
klass.apply(this, arguments); klass.apply(this, arguments); // eslint-disable-line prefer-rest-params
} }
ExtendableBuiltin.prototype = Object.create(klass.prototype); ExtendableBuiltin.prototype = Object.create(klass.prototype);
Object.setPrototypeOf(ExtendableBuiltin, klass); Object.setPrototypeOf(ExtendableBuiltin, klass);

View File

@@ -20,7 +20,9 @@ function lockQuest (quest, user) {
} }
function isItemSuggested (officialPinnedItems, itemInfo) { function isItemSuggested (officialPinnedItems, itemInfo) {
return officialPinnedItems.findIndex(officialItem => officialItem.type === itemInfo.pinType && officialItem.path === itemInfo.path) > -1; return officialPinnedItems.findIndex(officialItem => { // eslint-disable-line arrow-body-style
return officialItem.type === itemInfo.pinType && officialItem.path === itemInfo.path;
}) > -1;
} }
function getDefaultGearProps (item, language) { function getDefaultGearProps (item, language) {
@@ -46,12 +48,12 @@ function getDefaultGearProps (item, language) {
export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en') { export default function getItemInfo (user, type, item, officialPinnedItems, language = 'en') {
if (officialPinnedItems === undefined) { if (officialPinnedItems === undefined) {
officialPinnedItems = getOfficialPinnedItems(user); officialPinnedItems = getOfficialPinnedItems(user); // eslint-disable-line no-param-reassign
} }
let itemInfo; let itemInfo;
switch (type) { switch (type) { // eslint-disable-line default-case
case 'eggs': case 'eggs':
itemInfo = { itemInfo = {
key: item.key, key: item.key,
@@ -131,7 +133,9 @@ export default function getItemInfo (user, type, item, officialPinnedItems, lang
group: item.group, group: item.group,
value: item.goldValue ? item.goldValue : item.value, value: item.goldValue ? item.goldValue : item.value,
locked, locked,
previous: content.quests[item.previous] ? content.quests[item.previous].text(language) : null, previous: content.quests[item.previous]
? content.quests[item.previous].text(language)
: null,
unlockCondition: item.unlockCondition, unlockCondition: item.unlockCondition,
drop: item.drop, drop: item.drop,
boss: item.boss, boss: item.boss,

View File

@@ -14,12 +14,14 @@ export default function getOfficialPinnedItems (user) {
// pinnedSets == current seasonal class set are always gold purchaseable // pinnedSets == current seasonal class set are always gold purchaseable
flatGearArray.filter(gear => user.items.gear.owned[gear.key] === undefined && gear.set === setToAdd).map(gear => { flatGearArray
officialItemsArray.push({ .filter(gear => user.items.gear.owned[gear.key] === undefined && gear.set === setToAdd)
type: 'marketGear', .forEach(gear => {
path: `gear.flat.${gear.key}`, officialItemsArray.push({
type: 'marketGear',
path: `gear.flat.${gear.key}`,
});
}); });
});
} }
return officialItemsArray; return officialItemsArray;

View File

@@ -1,9 +1,12 @@
export default function isPinned (user, item, checkOfficialPinnedItems /* getOfficialPinnedItems */) { export default function isPinned (user, item, checkOfficialPinnedItems) {
if (user === null) return false; if (user === null) return false;
const isPinnedOfficial = checkOfficialPinnedItems !== undefined && checkOfficialPinnedItems.findIndex(pinned => pinned.path === item.path) > -1; const isPinnedOfficial = checkOfficialPinnedItems !== undefined
const isItemUnpinned = user.unpinnedItems !== undefined && user.unpinnedItems.findIndex(unpinned => unpinned.path === item.path) > -1; && checkOfficialPinnedItems.findIndex(pinned => pinned.path === item.path) > -1;
const isItemPinned = user.pinnedItems !== undefined && user.pinnedItems.findIndex(pinned => pinned.path === item.path) > -1; const isItemUnpinned = user.unpinnedItems !== undefined
&& user.unpinnedItems.findIndex(unpinned => unpinned.path === item.path) > -1;
const isItemPinned = user.pinnedItems !== undefined
&& user.pinnedItems.findIndex(pinned => pinned.path === item.path) > -1;
if (isPinnedOfficial && !isItemUnpinned) return true; if (isPinnedOfficial && !isItemUnpinned) return true;

View File

@@ -13,7 +13,7 @@ export default function percent (x, y, dir) {
roundFn = Math.round; roundFn = Math.round;
} }
if (x === 0) { if (x === 0) {
x = 1; x = 1; // eslint-disable-line no-param-reassign
} }
return Math.max(0, roundFn(x / y * 100)); return Math.max(0, roundFn((x / y) * 100));
} }

View File

@@ -4,8 +4,10 @@ import values from 'lodash/values';
import uuid from './uuid'; import uuid from './uuid';
/* /*
Reflists are arrays, but stored as objects. Mongoose has a helluvatime working with arrays (the main problem for our Reflists are arrays, but stored as objects.
syncing issues) - so the goal is to move away from arrays to objects, since mongoose can reference elements by ID Mongoose has a helluvatime working with arrays (the main problem for our
syncing issues) - so the goal is to move away from arrays to objects,
since mongoose can reference elements by ID
no problem. To maintain sorting, we use these helper functions: no problem. To maintain sorting, we use these helper functions:
*/ */

View File

@@ -1,4 +1,3 @@
export default function splitWhitespace (s) { export default function splitWhitespace (s) {
return s.split(' '); return s.split(' ');
} }

View File

@@ -2,7 +2,8 @@ import { v4 as uuid } from 'uuid';
import defaults from 'lodash/defaults'; import defaults from 'lodash/defaults';
import moment from 'moment'; import moment from 'moment';
// Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before // Even though Mongoose handles task defaults,
// we want to make sure defaults are set on the client-side before
// sending up to the server for performance // sending up to the server for performance
// TODO move to client code? // TODO move to client code?

View File

@@ -17,7 +17,10 @@ export default function updateStore (user) {
let changes = []; let changes = [];
each(content.gearTypes, type => { each(content.gearTypes, type => {
const found = lodashFind(content.gear.tree[type][user.stats.class], item => !user.items.gear.owned[item.key]); const found = lodashFind(
content.gear.tree[type][user.stats.class],
item => !user.items.gear.owned[item.key],
);
if (found) changes.push(found); if (found) changes.push(found);
}); });

View File

@@ -1,3 +1,4 @@
/* eslint-disable max-classes-per-file */
import _merge from 'lodash/merge'; import _merge from 'lodash/merge';
import _get from 'lodash/get'; import _get from 'lodash/get';
import i18n from '../../i18n'; import i18n from '../../i18n';
@@ -61,7 +62,7 @@ export class AbstractBuyOperation {
*/ */
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars
i18n (key, params = {}) { i18n (key, params = {}) {
return i18n.t.apply(null, [...arguments, this.req.language]); return i18n.t.apply(null, [...arguments, this.req.language]); // eslint-disable-line prefer-rest-params, max-len
} }
/** /**

View File

@@ -20,7 +20,9 @@ import { BuyHourglassMountOperation } from './buyMount';
// @TODO: when we are sure buy is the only function used, let's move the buy files to a folder // @TODO: when we are sure buy is the only function used, let's move the buy files to a folder
export default function buy (user, req = {}, analytics, options = { quantity: 1, hourglass: false }) { export default function buy (
user, req = {}, analytics, options = { quantity: 1, hourglass: false },
) {
const key = get(req, 'params.key'); const key = get(req, 'params.key');
const { hourglass } = options; const { hourglass } = options;
const { quantity } = options; const { quantity } = options;

View File

@@ -17,7 +17,7 @@ import { AbstractGoldItemOperation } from './abstractBuyOperation';
const YIELD_EQUIPMENT_THRESHOLD = 0.6; const YIELD_EQUIPMENT_THRESHOLD = 0.6;
const YIELD_FOOD_THRESHOLD = 0.8; const YIELD_FOOD_THRESHOLD = 0.8;
export class BuyArmoireOperation extends AbstractGoldItemOperation { export class BuyArmoireOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -39,9 +39,15 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation {
const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]); const eligibleEquipment = filter(content.gear.flat, eligible => eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key]);
const armoireHasEquipment = !isEmpty(eligibleEquipment); const armoireHasEquipment = !isEmpty(eligibleEquipment);
if (armoireHasEquipment && (armoireResult < YIELD_EQUIPMENT_THRESHOLD || !user.flags.armoireOpened)) { if (
armoireHasEquipment
&& (armoireResult < YIELD_EQUIPMENT_THRESHOLD || !user.flags.armoireOpened)
) {
result = this._gearResult(user, eligibleEquipment); result = this._gearResult(user, eligibleEquipment);
} else if ((armoireHasEquipment && armoireResult < YIELD_FOOD_THRESHOLD) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens } else if (
(armoireHasEquipment && armoireResult < YIELD_FOOD_THRESHOLD)
|| armoireResult < 0.5
) {
result = this._foodResult(user); result = this._foodResult(user);
} else { } else {
result = this._experienceResult(user); result = this._experienceResult(user);
@@ -49,7 +55,8 @@ export class BuyArmoireOperation extends AbstractGoldItemOperation {
this.subtractCurrency(user, item); this.subtractCurrency(user, item);
let { message, armoireResp } = result; let { message } = result;
const { armoireResp } = result;
if (!message) { if (!message) {
message = this.i18n('messageBought', { message = this.i18n('messageBought', {

View File

@@ -8,7 +8,7 @@ import {
import { AbstractGoldItemOperation } from './abstractBuyOperation'; import { AbstractGoldItemOperation } from './abstractBuyOperation';
import planGemLimits from '../../libs/planGemLimits'; import planGemLimits from '../../libs/planGemLimits';
export class BuyGemOperation extends AbstractGoldItemOperation { export class BuyGemOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -30,7 +30,8 @@ export class BuyGemOperation extends AbstractGoldItemOperation {
} }
extractAndValidateParams (user, req) { extractAndValidateParams (user, req) {
const key = this.key = get(req, 'params.key'); this.key = get(req, 'params.key');
const { key } = this.key;
if (!key) throw new BadRequest(this.i18n('missingKeyParam')); if (!key) throw new BadRequest(this.i18n('missingKeyParam'));
let { convCap } = planGemLimits; let { convCap } = planGemLimits;

View File

@@ -5,7 +5,7 @@ import {
import { AbstractGoldItemOperation } from './abstractBuyOperation'; import { AbstractGoldItemOperation } from './abstractBuyOperation';
export class BuyHealthPotionOperation extends AbstractGoldItemOperation { export class BuyHealthPotionOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }

View File

@@ -15,7 +15,7 @@ import { removePinnedGearAddPossibleNewOnes } from '../pinnedGearUtils';
import { AbstractGoldItemOperation } from './abstractBuyOperation'; import { AbstractGoldItemOperation } from './abstractBuyOperation';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
export class BuyMarketGearOperation extends AbstractGoldItemOperation { export class BuyMarketGearOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -37,7 +37,8 @@ export class BuyMarketGearOperation extends AbstractGoldItemOperation {
} }
extractAndValidateParams (user, req) { extractAndValidateParams (user, req) {
const key = this.key = get(req, 'params.key'); this.key = get(req, 'params.key');
const { key } = this.key;
if (!key) throw new BadRequest(errorMessage('missingKeyParam')); if (!key) throw new BadRequest(errorMessage('missingKeyParam'));
const item = content.gear.flat[key]; const item = content.gear.flat[key];

View File

@@ -9,7 +9,7 @@ import {
import { AbstractHourglassItemOperation } from './abstractBuyOperation'; import { AbstractHourglassItemOperation } from './abstractBuyOperation';
export class BuyHourglassMountOperation extends AbstractHourglassItemOperation { export class BuyHourglassMountOperation extends AbstractHourglassItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -19,7 +19,8 @@ export class BuyHourglassMountOperation extends AbstractHourglassItemOperation {
} }
extractAndValidateParams (user, req) { extractAndValidateParams (user, req) {
const key = this.key = get(req, 'params.key'); this.key = get(req, 'params.key');
const { key } = this;
if (!key) throw new BadRequest(this.i18n('missingKeyParam')); if (!key) throw new BadRequest(this.i18n('missingKeyParam'));

View File

@@ -40,7 +40,7 @@ export default function buyMysterySet (user, req = {}, analytics) {
if (user.markModified) user.markModified('items.gear.owned'); if (user.markModified) user.markModified('items.gear.owned');
user.purchased.plan.consecutive.trinkets--; user.purchased.plan.consecutive.trinkets -= 1;
return [ return [
{ items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive }, { items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive },

View File

@@ -9,7 +9,7 @@ import content from '../../content/index';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
import { AbstractGemItemOperation } from './abstractBuyOperation'; import { AbstractGemItemOperation } from './abstractBuyOperation';
export class BuyQuestWithGemOperation extends AbstractGemItemOperation { export class BuyQuestWithGemOperation extends AbstractGemItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -31,7 +31,8 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation {
} }
extractAndValidateParams (user, req) { extractAndValidateParams (user, req) {
const key = this.key = get(req, 'params.key'); this.key = get(req, 'params.key');
const { key } = this.key;
if (!key) throw new BadRequest(errorMessage('missingKeyParam')); if (!key) throw new BadRequest(errorMessage('missingKeyParam'));
const item = content.quests[key]; const item = content.quests[key];
@@ -46,7 +47,10 @@ export class BuyQuestWithGemOperation extends AbstractGemItemOperation {
} }
executeChanges (user, item, req) { executeChanges (user, item, req) {
if (!user.items.quests[item.key] || user.items.quests[item.key] < 0) user.items.quests[item.key] = 0; 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; user.items.quests[item.key] += this.quantity;
if (user.markModified) user.markModified('items.quests'); if (user.markModified) user.markModified('items.quests');

View File

@@ -9,7 +9,7 @@ import content from '../../content/index';
import { AbstractGoldItemOperation } from './abstractBuyOperation'; import { AbstractGoldItemOperation } from './abstractBuyOperation';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation { export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -66,7 +66,10 @@ export class BuyQuestWithGoldOperation extends AbstractGoldItemOperation {
} }
executeChanges (user, item, req) { executeChanges (user, item, req) {
if (!user.items.quests[item.key] || user.items.quests[item.key] < 0) user.items.quests[item.key] = 0; 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; user.items.quests[item.key] += this.quantity;
if (user.markModified) user.markModified('items.quests'); if (user.markModified) user.markModified('items.quests');

View File

@@ -9,7 +9,7 @@ import {
import { AbstractGoldItemOperation } from './abstractBuyOperation'; import { AbstractGoldItemOperation } from './abstractBuyOperation';
import errorMessage from '../../libs/errorMessage'; import errorMessage from '../../libs/errorMessage';
export class BuySpellOperation extends AbstractGoldItemOperation { export class BuySpellOperation extends AbstractGoldItemOperation { // eslint-disable-line import/prefer-default-export, max-len
constructor (user, req, analytics) { constructor (user, req, analytics) {
super(user, req, analytics); super(user, req, analytics);
} }
@@ -27,7 +27,8 @@ export class BuySpellOperation extends AbstractGoldItemOperation {
} }
extractAndValidateParams (user, req) { extractAndValidateParams (user, req) {
const key = this.key = get(req, 'params.key'); this.key = get(req, 'params.key');
const { key } = this;
if (!key) throw new BadRequest(errorMessage('missingKeyParam')); if (!key) throw new BadRequest(errorMessage('missingKeyParam'));
const item = content.special[key]; const item = content.special[key];

View File

@@ -44,7 +44,7 @@ export default function purchaseHourglass (user, req = {}, analytics, quantity =
throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language)); throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language));
} }
user.purchased.plan.consecutive.trinkets--; user.purchased.plan.consecutive.trinkets -= 1;
if (type === 'pets') { if (type === 'pets') {
user.items.pets[key] = 5; user.items.pets[key] = 5;

View File

@@ -54,14 +54,14 @@ function purchaseItem (user, item, price, type, key) {
if (!user.items[subType][bundledKey] || user.items[subType][key] < 0) { if (!user.items[subType][bundledKey] || user.items[subType][key] < 0) {
user.items[subType][bundledKey] = 0; user.items[subType][bundledKey] = 0;
} }
user.items[subType][bundledKey]++; user.items[subType][bundledKey] += 1;
}); });
if (user.markModified) user.markModified(`items.${subType}`); if (user.markModified) user.markModified(`items.${subType}`);
} else { } else {
if (!user.items[type][key] || user.items[type][key] < 0) { if (!user.items[type][key] || user.items[type][key] < 0) {
user.items[type][key] = 0; user.items[type][key] = 0;
} }
user.items[type][key]++; user.items[type][key] += 1;
if (user.markModified) user.markModified(`items.${type}`); if (user.markModified) user.markModified(`items.${type}`);
} }
} }

View File

@@ -40,7 +40,8 @@ export default function changeClass (user, req = {}, analytics) {
if (user.stats.lvl < 10) { if (user.stats.lvl < 10) {
throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language)); throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language));
} else if (!klass) { } else if (!klass) {
// if no class is specified, reset points and set user.flags.classSelected to false. User will have paid 3 gems and will be prompted to select class. // if no class is specified, reset points and set user.flags.classSelected to false.
// User will have paid 3 gems and will be prompted to select class.
balanceRemoved = resetClass(user, req); balanceRemoved = resetClass(user, req);
} else if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') { } else if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') {
if (user.flags.classSelected) { if (user.flags.classSelected) {

View File

@@ -21,7 +21,7 @@ export default function equip (user, req = {}) {
let message; let message;
switch (type) { switch (type) { // eslint-disable-line default-case
case 'mount': { case 'mount': {
if (!user.items.mounts[key]) { if (!user.items.mounts[key]) {
throw new NotFound(i18n.t('mountNotOwned', req.language)); throw new NotFound(i18n.t('mountNotOwned', req.language));
@@ -49,7 +49,11 @@ export default function equip (user, req = {}) {
if (user.items.gear[type][item.type] === key) { if (user.items.gear[type][item.type] === key) {
user.items.gear[type] = { user.items.gear[type] = {
...(user.items.gear[type].toObject ? user.items.gear[type].toObject() : user.items.gear[type]), ...(
user.items.gear[type].toObject
? user.items.gear[type].toObject()
: user.items.gear[type]
),
[item.type]: `${item.type}_base_0`, [item.type]: `${item.type}_base_0`,
}; };
if (user.markModified && type === 'owned') user.markModified('items.gear.owned'); if (user.markModified && type === 'owned') user.markModified('items.gear.owned');
@@ -60,7 +64,11 @@ export default function equip (user, req = {}) {
} else { } else {
user.items.gear[type] = { user.items.gear[type] = {
...(user.items.gear[type].toObject ? user.items.gear[type].toObject() : user.items.gear[type]), ...(
user.items.gear[type].toObject
? user.items.gear[type].toObject()
: user.items.gear[type]
),
[item.type]: item.key, [item.type]: item.key,
}; };
if (user.markModified && type === 'owned') user.markModified('items.gear.owned'); if (user.markModified && type === 'owned') user.markModified('items.gear.owned');

View File

@@ -90,7 +90,7 @@ export default function feed (user, req = {}) {
} }
} }
user.items.food[food.key]--; user.items.food[food.key] -= 1;
if (user.markModified) user.markModified('items.food'); if (user.markModified) user.markModified('items.food');
forEach(content.animalColorAchievements, achievement => { forEach(content.animalColorAchievements, achievement => {

View File

@@ -24,7 +24,13 @@ export default function hatch (user, req = {}) {
throw new NotFound(i18n.t('messageMissingEggPotion', req.language)); throw new NotFound(i18n.t('messageMissingEggPotion', req.language));
} }
if ((content.hatchingPotions[hatchingPotion].premium || content.hatchingPotions[hatchingPotion].wacky) && !content.dropEggs[egg]) { if (
(
content.hatchingPotions[hatchingPotion].premium
|| content.hatchingPotions[hatchingPotion].wacky
)
&& !content.dropEggs[egg]
) {
throw new BadRequest(i18n.t('messageInvalidEggPotionCombo', req.language)); throw new BadRequest(i18n.t('messageInvalidEggPotionCombo', req.language));
} }
@@ -35,8 +41,8 @@ export default function hatch (user, req = {}) {
} }
user.items.pets[pet] = 5; user.items.pets[pet] = 5;
user.items.eggs[egg]--; user.items.eggs[egg] -= 1;
user.items.hatchingPotions[hatchingPotion]--; user.items.hatchingPotions[hatchingPotion] -= 1;
if (user.markModified) { if (user.markModified) {
user.markModified('items.pets'); user.markModified('items.pets');
user.markModified('items.eggs'); user.markModified('items.eggs');
@@ -45,7 +51,10 @@ export default function hatch (user, req = {}) {
forEach(content.animalColorAchievements, achievement => { forEach(content.animalColorAchievements, achievement => {
if (!user.achievements[achievement.petAchievement]) { if (!user.achievements[achievement.petAchievement]) {
const petIndex = findIndex(keys(content.dropEggs), animal => isNaN(user.items.pets[`${animal}-${achievement.color}`]) || user.items.pets[`${animal}-${achievement.color}`] <= 0); const petIndex = findIndex(
keys(content.dropEggs),
animal => Number.isNaN(user.items.pets[`${animal}-${achievement.color}`]) || user.items.pets[`${animal}-${achievement.color}`] <= 0,
);
if (petIndex === -1) { if (petIndex === -1) {
user.achievements[achievement.petAchievement] = true; user.achievements[achievement.petAchievement] = true;
if (user.addNotification) { if (user.addNotification) {

View File

@@ -37,7 +37,10 @@ export function selectGearToPin (user) {
const changes = []; const changes = [];
each(content.gearTypes, type => { each(content.gearTypes, type => {
const found = lodashFind(content.gear.tree[type][user.stats.class], item => !user.items.gear.owned[item.key]); const found = lodashFind(
content.gear.tree[type][user.stats.class],
item => !user.items.gear.owned[item.key],
);
if (found) changes.push(found); if (found) changes.push(found);
}); });
@@ -59,11 +62,10 @@ export function addPinnedGear (user, type, path) {
export function addPinnedGearByClass (user) { export function addPinnedGearByClass (user) {
const newPinnedItems = selectGearToPin(user); const newPinnedItems = selectGearToPin(user);
for (const item of newPinnedItems) { newPinnedItems.forEach(item => {
const itemInfo = getItemInfo(user, 'marketGear', item); const itemInfo = getItemInfo(user, 'marketGear', item);
addPinnedGear(user, itemInfo.pinType, itemInfo.path); addPinnedGear(user, itemInfo.pinType, itemInfo.path);
} });
} }
export function removeItemByPath (user, path) { export function removeItemByPath (user, path) {
@@ -80,11 +82,10 @@ export function removeItemByPath (user, path) {
export function removePinnedGearByClass (user) { export function removePinnedGearByClass (user) {
const currentPinnedItems = selectGearToPin(user); const currentPinnedItems = selectGearToPin(user);
for (const item of currentPinnedItems) { currentPinnedItems.forEach(item => {
const itemInfo = getItemInfo(user, 'marketGear', item); const itemInfo = getItemInfo(user, 'marketGear', item);
removeItemByPath(user, itemInfo.path); removeItemByPath(user, itemInfo.path);
} });
} }
export function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey) { export function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey) {
@@ -100,11 +101,12 @@ export function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey)
addPinnedGearByClass(user); addPinnedGearByClass(user);
// update the version, so that vue can refresh the seasonal shop // update the version, so that vue can refresh the seasonal shop
user._v++; user._v += 1;
} }
/** /**
* removes all pinned gear that the user already owns (like class starter gear which has been pinned before) * removes all pinned gear that the user already owns
*(like class starter gear which has been pinned before)
* @param user * @param user
*/ */
export function removePinnedItemsByOwnedGear (user) { export function removePinnedItemsByOwnedGear (user) {
@@ -126,9 +128,9 @@ export function togglePinnedItem (user, { item, type, path }, req = {}) {
if (!path) { if (!path) {
// If path isn't passed it means an item was passed // If path isn't passed it means an item was passed
path = getItemInfo(user, type, item, officialPinnedItems, req.language).path; path = getItemInfo(user, type, item, officialPinnedItems, req.language).path; // eslint-disable-line no-param-reassign, max-len
} else { } else {
item = getItemByPathAndType(type, path); item = getItemByPathAndType(type, path); // eslint-disable-line no-param-reassign
if (!item && PATHS_WITHOUT_ITEM.indexOf(path) === -1) { if (!item && PATHS_WITHOUT_ITEM.indexOf(path) === -1) {
// path not exists in our content structure // path not exists in our content structure

View File

@@ -16,6 +16,8 @@ function markNotificationAsRead (user, cardType) {
&& notification.data && notification.data
&& notification.data.card === cardType && notification.data.card === cardType
) return true; ) return true;
return false;
}); });
if (indexToRemove !== -1) user.notifications.splice(indexToRemove, 1); if (indexToRemove !== -1) user.notifications.splice(indexToRemove, 1);

View File

@@ -97,7 +97,7 @@ export default function rebirth (user, tasks = [], req = {}, analytics) {
user.achievements.rebirths = 1; user.achievements.rebirths = 1;
user.achievements.rebirthLevel = lvl; user.achievements.rebirthLevel = lvl;
} else if (lvl > user.achievements.rebirthLevel || lvl === MAX_LEVEL) { } else if (lvl > user.achievements.rebirthLevel || lvl === MAX_LEVEL) {
user.achievements.rebirths++; user.achievements.rebirths += 1;
user.achievements.rebirthLevel = lvl; user.achievements.rebirthLevel = lvl;
} }

View File

@@ -8,13 +8,14 @@ import {
import splitWhitespace from '../libs/splitWhitespace'; import splitWhitespace from '../libs/splitWhitespace';
export default function releaseBoth (user, req = {}) { export default function releaseBoth (user, req = {}) {
let animal;
if (!user.achievements.triadBingo) { if (!user.achievements.triadBingo) {
throw new NotAuthorized(i18n.t('notEnoughPetsMounts', req.language)); throw new NotAuthorized(i18n.t('notEnoughPetsMounts', req.language));
} }
if (beastMasterProgress(user.items.pets) !== 90 || mountMasterProgress(user.items.mounts) !== 90) { if (
beastMasterProgress(user.items.pets) !== 90
|| mountMasterProgress(user.items.mounts) !== 90
) {
throw new NotAuthorized(i18n.t('notEnoughPetsMounts', req.language)); throw new NotAuthorized(i18n.t('notEnoughPetsMounts', req.language));
} }
@@ -49,7 +50,7 @@ export default function releaseBoth (user, req = {}) {
user.items.currentPet = ''; user.items.currentPet = '';
} }
for (animal in content.pets) { Object.keys(content.pets).forEach(animal => {
if (user.items.pets[animal] === -1) { if (user.items.pets[animal] === -1) {
giveTriadBingo = false; giveTriadBingo = false;
} else if (!user.items.pets[animal]) { } else if (!user.items.pets[animal]) {
@@ -61,7 +62,8 @@ export default function releaseBoth (user, req = {}) {
user.items.pets[animal] = 0; user.items.pets[animal] = 0;
user.items.mounts[animal] = null; user.items.mounts[animal] = null;
} });
if (user.markModified) { if (user.markModified) {
user.markModified('items.pets'); user.markModified('items.pets');
user.markModified('items.mounts'); user.markModified('items.mounts');
@@ -71,21 +73,21 @@ export default function releaseBoth (user, req = {}) {
if (!user.achievements.beastMasterCount) { if (!user.achievements.beastMasterCount) {
user.achievements.beastMasterCount = 0; user.achievements.beastMasterCount = 0;
} }
user.achievements.beastMasterCount++; user.achievements.beastMasterCount += 1;
} }
if (giveMountMasterAchievement) { if (giveMountMasterAchievement) {
if (!user.achievements.mountMasterCount) { if (!user.achievements.mountMasterCount) {
user.achievements.mountMasterCount = 0; user.achievements.mountMasterCount = 0;
} }
user.achievements.mountMasterCount++; user.achievements.mountMasterCount += 1;
} }
if (giveTriadBingo) { if (giveTriadBingo) {
if (!user.achievements.triadBingoCount) { if (!user.achievements.triadBingoCount) {
user.achievements.triadBingoCount = 0; user.achievements.triadBingoCount = 0;
} }
user.achievements.triadBingoCount++; user.achievements.triadBingoCount += 1;
} }
return [ return [

View File

@@ -24,19 +24,20 @@ export default function releaseMounts (user, req = {}, analytics) {
user.items.currentMount = ''; user.items.currentMount = '';
} }
for (const mount in content.pets) { Object.keys(content.pets).forEach(mount => {
if (user.items.mounts[mount] === null || user.items.mounts[mount] === undefined) { if (user.items.mounts[mount] === null || user.items.mounts[mount] === undefined) {
giveMountMasterAchievement = false; giveMountMasterAchievement = false;
} }
user.items.mounts[mount] = null; user.items.mounts[mount] = null;
} });
if (user.markModified) user.markModified('items.mounts'); if (user.markModified) user.markModified('items.mounts');
if (giveMountMasterAchievement) { if (giveMountMasterAchievement) {
if (!user.achievements.mountMasterCount) { if (!user.achievements.mountMasterCount) {
user.achievements.mountMasterCount = 0; user.achievements.mountMasterCount = 0;
} }
user.achievements.mountMasterCount++; user.achievements.mountMasterCount += 1;
} }
if (analytics) { if (analytics) {

View File

@@ -24,19 +24,20 @@ export default function releasePets (user, req = {}, analytics) {
user.items.currentPet = ''; user.items.currentPet = '';
} }
for (const pet in content.pets) { Object.keys(content.pets).forEach(pet => {
if (!user.items.pets[pet]) { if (!user.items.pets[pet]) {
giveBeastMasterAchievement = false; giveBeastMasterAchievement = false;
} }
user.items.pets[pet] = 0; user.items.pets[pet] = 0;
} });
if (user.markModified) user.markModified('items.pets'); if (user.markModified) user.markModified('items.pets');
if (giveBeastMasterAchievement) { if (giveBeastMasterAchievement) {
if (!user.achievements.beastMasterCount) { if (!user.achievements.beastMasterCount) {
user.achievements.beastMasterCount = 0; user.achievements.beastMasterCount = 0;
} }
user.achievements.beastMasterCount++; user.achievements.beastMasterCount += 1;
} }
if (analytics) { if (analytics) {

View File

@@ -9,7 +9,7 @@ export default function reroll (user, tasks = [], req = {}, analytics) {
throw new NotAuthorized(i18n.t('notEnoughGems', req.language)); throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
} }
user.balance--; user.balance -= 1;
user.stats.hp = 50; user.stats.hp = 50;
each(tasks, task => { each(tasks, task => {

View File

@@ -24,7 +24,7 @@ export default function revive (user, req = {}, analytics) {
}); });
if (user.stats.lvl > 1) { if (user.stats.lvl > 1) {
user.stats.lvl--; user.stats.lvl -= 1;
} }
const lostStat = randomVal(reduce(['str', 'con', 'per', 'int'], (m, k) => { const lostStat = randomVal(reduce(['str', 'con', 'per', 'int'], (m, k) => {
@@ -37,7 +37,7 @@ export default function revive (user, req = {}, analytics) {
}); });
if (lostStat) { if (lostStat) {
user.stats[lostStat]--; user.stats[lostStat] -= 1;
} }
const base = user.items.gear.owned; const base = user.items.gear.owned;
@@ -68,9 +68,11 @@ export default function revive (user, req = {}, analytics) {
const itemIsArmoire = itm.klass === 'armoire'; const itemIsArmoire = itm.klass === 'armoire';
if (itemHasValueOrWarrior0 && (itemClassEqualsUserClass || itemIsSpecial || itemIsArmoire)) { if (
itemHasValueOrWarrior0
&& (itemClassEqualsUserClass || itemIsSpecial || itemIsArmoire)
) {
losableItems[key] = key; losableItems[key] = key;
return losableItems[key];
} }
} }
} }

View File

@@ -28,7 +28,7 @@ function _getTaskValue (taskValue) {
function _calculateDelta (task, direction, cron) { function _calculateDelta (task, direction, cron) {
// Min/max on task redness // Min/max on task redness
const currVal = _getTaskValue(task.value); const currVal = _getTaskValue(task.value);
let nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); let nextDelta = (0.9747 ** currVal) * (direction === 'down' ? -1 : 1);
// Checklists // Checklists
if (task.checklist && task.checklist.length > 0) { if (task.checklist && task.checklist.length > 0) {
@@ -52,13 +52,13 @@ function _calculateDelta (task, direction, cron) {
// it will be a bit off // it will be a bit off
function _calculateReverseDelta (task, direction) { function _calculateReverseDelta (task, direction) {
const currVal = _getTaskValue(task.value); const currVal = _getTaskValue(task.value);
let testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1); let testVal = currVal + (0.9747 ** currVal) * (direction === 'down' ? -1 : 1);
// Now keep moving closer to the original value until we get "close enough" // Now keep moving closer to the original value until we get "close enough"
// Check how close we are to the original value by computing the delta off our guess // Check how close we are to the original value by computing the delta off our guess
// and looking at the difference between that and our current value. // and looking at the difference between that and our current value.
while (true) { // eslint-disable-line no-constant-condition while (true) { // eslint-disable-line no-constant-condition
const calc = testVal + Math.pow(0.9747, testVal); const calc = testVal + (0.9747 ** testVal);
const diff = currVal - calc; const diff = currVal - calc;
if (Math.abs(diff) < CLOSE_ENOUGH) break; if (Math.abs(diff) < CLOSE_ENOUGH) break;
@@ -84,7 +84,7 @@ function _calculateReverseDelta (task, direction) {
} }
function _gainMP (user, val) { function _gainMP (user, val) {
val *= user._tmp.crit || 1; val *= user._tmp.crit || 1; // eslint-disable-line no-param-reassign
user.stats.mp += val; user.stats.mp += val;
if (user.stats.mp >= statsComputed(user).maxMP) user.stats.mp = statsComputed(user).maxMP; if (user.stats.mp >= statsComputed(user).maxMP) user.stats.mp = statsComputed(user).maxMP;
@@ -125,7 +125,8 @@ function _addPoints (user, task, stats, direction, delta) {
const streakBonus = currStreak / 100 + 1; // eg, 1-day streak is 1.01, 2-day is 1.02, etc const streakBonus = currStreak / 100 + 1; // eg, 1-day streak is 1.01, 2-day is 1.02, etc
const afterStreak = gpMod * streakBonus; const afterStreak = gpMod * streakBonus;
if (currStreak > 0 && gpMod > 0) { if (currStreak > 0 && gpMod > 0) {
user._tmp.streakBonus = afterStreak - gpMod; // keep this on-hand for later, so we can notify streak-bonus // keep this on-hand for later, so we can notify streak-bonus
user._tmp.streakBonus = afterStreak - gpMod;
} }
stats.gp += afterStreak; stats.gp += afterStreak;
@@ -194,9 +195,13 @@ export default function scoreTask (options = {}, req = {}) {
exp: user.stats.exp, exp: user.stats.exp,
}; };
if (task.group && task.group.approval && task.group.approval.required && !task.group.approval.approved) return 0; if (
task.group && task.group.approval && task.group.approval.required
&& !task.group.approval.approved
) return 0;
// This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying // This is for setting one-time temporary flags,
// such as streakBonus or itemDropped. Useful for notifying
// the API consumer, then cleared afterwards // the API consumer, then cleared afterwards
user._tmp = {}; user._tmp = {};
@@ -236,7 +241,8 @@ export default function scoreTask (options = {}, req = {}) {
lastHistoryEntry.value = task.value; lastHistoryEntry.value = task.value;
lastHistoryEntry.date = Number(new Date()); lastHistoryEntry.date = Number(new Date());
// @TODO remove this extra check after migration has run to set scoredUp and scoredDown in every task // @TODO remove this extra check after migration
// has run to set scoredUp and scoredDown in every task
lastHistoryEntry.scoredUp = lastHistoryEntry.scoredUp || 0; lastHistoryEntry.scoredUp = lastHistoryEntry.scoredUp || 0;
lastHistoryEntry.scoredDown = lastHistoryEntry.scoredDown || 0; lastHistoryEntry.scoredDown = lastHistoryEntry.scoredDown || 0;
@@ -265,7 +271,8 @@ export default function scoreTask (options = {}, req = {}) {
} else { } else {
delta += _changeTaskValue(user, task, direction, times, cron); delta += _changeTaskValue(user, task, direction, times, cron);
if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly if (direction === 'down') delta = _calculateDelta(task, direction, cron); // recalculate delta for unchecking so the gp and exp come out correctly
_addPoints(user, task, stats, direction, delta); // obviously for delta>0, but also a trick to undo accidental checkboxes // obviously for delta>0, but also a trick to undo accidental checkboxes
_addPoints(user, task, stats, direction, delta);
_gainMP(user, max([1, 0.01 * statsComputed(user).maxMP]) * (direction === 'down' ? -1 : 1)); _gainMP(user, max([1, 0.01 * statsComputed(user).maxMP]) * (direction === 'down' ? -1 : 1));
if (direction === 'up') { if (direction === 'up') {
@@ -286,7 +293,9 @@ export default function scoreTask (options = {}, req = {}) {
task.history.push(historyEntry); task.history.push(historyEntry);
} else if (direction === 'down') { } else if (direction === 'down') {
// Remove a streak achievement if streak was a multiple of 21 and the daily was undone // Remove a streak achievement if streak was a multiple of 21 and the daily was undone
if (task.streak !== 0 && task.streak % 21 === 0) user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0; if (task.streak !== 0 && task.streak % 21 === 0) {
user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
}
task.streak -= 1; task.streak -= 1;
task.completed = false; task.completed = false;

View File

@@ -22,10 +22,10 @@ export default function allocate (user, req = {}) {
} }
if (user.stats.points > 0) { if (user.stats.points > 0) {
user.stats[stat]++; user.stats[stat] += 1;
user.stats.points--; user.stats.points -= 1;
if (stat === 'int') { if (stat === 'int') {
user.stats.mp++; user.stats.mp += 1;
} }
} else { } else {
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language)); throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));

View File

@@ -34,11 +34,11 @@ export default function allocateBulk (user, req = {}) {
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language)); throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
} }
for (const [stat, value] of Object.entries(stats)) { Object.entries(stats).forEach(([stat, value]) => {
user.stats[stat] += value; user.stats[stat] += value;
user.stats.points -= value; user.stats.points -= value;
if (stat === 'int') user.stats.mp += value; if (stat === 'int') user.stats.mp += value;
} });
return [ return [
user.stats, user.stats,

View File

@@ -45,13 +45,14 @@ export default function unlock (user, req = {}, analytics) {
each(setPaths, singlePath => { each(setPaths, singlePath => {
if (get(user, `purchased.${singlePath}`) === true) { if (get(user, `purchased.${singlePath}`) === true) {
alreadyOwnedItems++; alreadyOwnedItems += 1;
} }
}); });
if (alreadyOwnedItems === setPaths.length) { if (alreadyOwnedItems === setPaths.length) {
throw new NotAuthorized(i18n.t('alreadyUnlocked', req.language)); throw new NotAuthorized(i18n.t('alreadyUnlocked', req.language));
// TODO write math formula to check if buying the full set is cheaper than the items individually // TODO write math formula to check if buying
// the full set is cheaper than the items individually
// (item cost * number of remaining items) < setCost` // (item cost * number of remaining items) < setCost`
} /* else if (alreadyOwnedItems > 0) { } /* else if (alreadyOwnedItems > 0) {
throw new NotAuthorized(i18n.t('alreadyUnlockedPart', req.language)); throw new NotAuthorized(i18n.t('alreadyUnlockedPart', req.language));

View File

@@ -27,13 +27,15 @@ export function toNextLevel (lvl) {
} if (lvl === 5) { } if (lvl === 5) {
return 150; return 150;
} }
return Math.round((Math.pow(lvl, 2) * 0.25 + 10 * lvl + 139.75) / 10) * 10; return Math.round(((lvl ** 2) * 0.25 + 10 * lvl + 139.75) / 10) * 10;
} }
/* /*
A hyperbola function that creates diminishing returns, so you can't go to infinite (eg, with Exp gain). A hyperbola function that creates diminishing returns,
so you can't go to infinite (eg, with Exp gain).
{max} The asymptote {max} The asymptote
{bonus} All the numbers combined for your point bonus (eg, task.value * user.stats.int * critChance, etc) {bonus} All the numbers combined for your point bonus
(eg, task.value * user.stats.int * critChance, etc)
{halfway} (optional) the point at which the graph starts bending {halfway} (optional) the point at which the graph starts bending
*/ */