Merge branch 'develop' into release

This commit is contained in:
SabreCat
2017-09-28 16:02:47 +00:00
2213 changed files with 121882 additions and 101395 deletions

View File

@@ -6,6 +6,9 @@ export const MAX_INCENTIVES = 500;
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
export const LARGE_GROUP_COUNT_MESSAGE_CUTOFF = 5000;
export const MAX_SUMMARY_SIZE_FOR_GUILDS = 250;
export const MAX_SUMMARY_SIZE_FOR_CHALLENGES = 250;
export const MIN_SHORTNAME_SIZE_FOR_CHALLENGES = 3;
export const SUPPORTED_SOCIAL_NETWORKS = [
{key: 'facebook', name: 'Facebook'},

View File

@@ -598,10 +598,20 @@ let backgrounds = {
};
/* eslint-enable quote-props */
forOwn(backgrounds, function prefillBackgroundSet (value) {
forOwn(value, function prefillBackground (bgObject) {
bgObject.price = 7;
let flat = {};
forOwn(backgrounds, function prefillBackgroundSet (backgroundsInSet, set) {
forOwn(backgroundsInSet, function prefillBackground (background, bgKey) {
background.key = bgKey;
background.set = set;
background.price = 7;
flat[bgKey] = background;
});
});
module.exports = backgrounds;
module.exports = {
tree: backgrounds,
flat,
};

View File

@@ -32,6 +32,34 @@ export const EVENTS = {
fall2017: { start: '2017-09-21', end: '2017-11-02' },
};
export const SEASONAL_SETS = {
fall: [
// fall 2014
'vampireSmiterSet',
'monsterOfScienceSet',
'witchyWizardSet',
'mummyMedicSet',
// fall 2015
'battleRogueSet',
'scarecrowWarriorSet',
'stitchWitchSet',
'potionerSet',
// fall 2016
'fall2016BlackWidowSet',
'fall2016SwampThingSet',
'fall2016WickedSorcererSet',
'fall2016GorgonHealerSet',
// fall 2017
'fall2017TrickOrTreatSet',
'fall2017HabitoweenSet',
'fall2017MasqueradeSet',
'fall2017HauntedHouseSet',
],
};
export const GEAR_TYPES = [
'weapon',
'armor',

View File

@@ -1,6 +1,5 @@
import defaults from 'lodash/defaults';
import each from 'lodash/each';
import includes from 'lodash/includes';
import moment from 'moment';
import t from './translation';
@@ -25,7 +24,7 @@ import {
} from './quests';
import appearances from './appearance';
import backgrounds from './appearance/backgrounds.js';
import backgrounds from './appearance/backgrounds';
import spells from './spells';
import subscriptionBlocks from './subscriptionBlocks';
import faq from './faq';
@@ -33,6 +32,8 @@ import timeTravelers from './time-travelers';
import loginIncentives from './loginIncentives';
import officialPinnedItems from './officialPinnedItems';
api.achievements = achievements;
api.quests = quests;
@@ -45,9 +46,13 @@ api.gear = gear;
api.spells = spells;
api.subscriptionBlocks = subscriptionBlocks;
api.audioThemes = ['danielTheBard', 'gokulTheme', 'luneFoxTheme', 'wattsTheme', 'rosstavoTheme', 'dewinTheme', 'airuTheme', 'beatscribeNesTheme', 'arashiTheme'];
api.mystery = timeTravelers.mystery;
api.timeTravelerStore = timeTravelers.timeTravelerStore;
api.officialPinnedItems = officialPinnedItems;
/*
---------------------------------------------------------------
Discounted Item Bundles
@@ -68,6 +73,7 @@ api.bundles = {
return moment().isBetween('2017-05-16', '2017-05-31');
},
type: 'quests',
class: 'quest_bundle_featheredFriends',
value: 7,
},
splashyPals: {
@@ -83,6 +89,7 @@ api.bundles = {
return moment().isBetween('2017-07-11', '2017-08-02');
},
type: 'quests',
class: 'quest_bundle_splashyPals',
value: 7,
},
farmFriends: {
@@ -127,8 +134,8 @@ api.armoire = {
},
value: 100,
key: 'armoire',
canOwn (u) {
return includes(u.achievements.ultimateGearSets, true);
canOwn () {
return true;
},
};
@@ -533,7 +540,8 @@ each(api.food, (food, key) => {
api.appearances = appearances;
api.backgrounds = backgrounds;
api.backgrounds = backgrounds.tree;
api.backgroundsFlat = backgrounds.flat;
api.userDefaults = {
habits: [

View File

@@ -204,6 +204,7 @@ let mysterySets = {
each(mysterySets, (value, key) => {
value.key = key;
value.text = t(`mysterySet${key}`);
value.class = `shop_set_mystery_${key}`;
});
module.exports = mysterySets;

View File

@@ -0,0 +1,4 @@
// { path: '', type: '', canShow?: (user) => boolean }
export default [];

View File

@@ -1054,6 +1054,7 @@ let quests = {
basilist: {
text: t('questBasilistText'),
notes: t('questBasilistNotes'),
group: 'questGroupEarnable',
completion: t('questBasilistCompletion'),
value: 4,
category: 'unlockable',
@@ -2224,6 +2225,7 @@ let quests = {
dustbunnies: {
text: t('questDustBunniesText'),
notes: t('questDustBunniesNotes'),
group: 'questGroupEarnable',
completion: t('questDustBunniesCompletion'),
value: 4,
category: 'unlockable',

View File

@@ -1,19 +1,44 @@
const featuredItems = {
market: [
'head_armoire_vikingHelm',
'weapon_special_1',
'shield_special_0',
'armor_warrior_5',
],
quests: [
'dilatoryDistress1',
'dilatoryDistress2',
'dilatoryDistress3',
],
seasonal: 'summerMage',
timeTravelers: [
market: [
{
type: 'armoire',
path: 'armoire',
},
{
type: 'hatchingPotions',
path: 'hatchingPotions.Golden',
},
{
type: 'food',
path: 'food.Saddle',
},
{
type: 'card',
path: 'cardTypes.greeting',
},
],
quests: [
{
type: 'quests',
path: 'quests.gryphon',
},
{
type: 'quests',
path: 'quests.dilatoryDistress1',
},
{
type: 'quests',
path: 'quests.nudibranch',
},
{
type: 'quests',
path: 'quests.taskwoodsTerror1',
},
],
seasonal: 'summerMage',
timeTravelers: [
// TODO
],
],
};
export default featuredItems;

View File

@@ -2,6 +2,8 @@ import t from './translation';
import each from 'lodash/each';
import { NotAuthorized } from '../libs/errors';
import statsComputed from '../libs/statsComputed';
import crit from '../fns/crit';
import updateStats from '../fns/updateStats';
/*
---------------------------------------------------------------
@@ -15,7 +17,7 @@ import statsComputed from '../libs/statsComputed';
* {cast}: the function that's run to perform the ability's action. This is pretty slick - because this is exported to the
web, this function can be performed on the client and on the server. `user` param is self (needed for determining your
own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells,
own stats for effectiveness of cast), and `target` param is one of [task, party, user]. 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,
@@ -29,8 +31,8 @@ function diminishingReturns (bonus, max, halfway) {
return max * (bonus / (bonus + halfway));
}
function calculateBonus (value, stat, crit = 1, statScale = 0.5) {
return (value < 0 ? 1 : value + 1) + stat * statScale * crit;
function calculateBonus (value, stat, critVal = 1, statScale = 0.5) {
return (value < 0 ? 1 : value + 1) + stat * statScale * critVal;
}
let spells = {};
@@ -43,12 +45,12 @@ spells.wizard = {
target: 'task',
notes: t('spellWizardFireballNotes'),
cast (user, target, req) {
let bonus = statsComputed(user).int * user.fns.crit('per');
let bonus = statsComputed(user).int * crit.crit(user, 'per');
bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075);
user.stats.exp += diminishingReturns(bonus, 75);
if (!user.party.quest.progress.up) user.party.quest.progress.up = 0;
user.party.quest.progress.up += Math.ceil(statsComputed(user).int * 0.1);
user.fns.updateStats(user.stats, req);
updateStats(user, user.stats, req);
},
},
mpheal: { // Ethereal Surge
@@ -100,7 +102,7 @@ spells.warrior = {
target: 'task',
notes: t('spellWarriorSmashNotes'),
cast (user, target) {
let bonus = statsComputed(user).str * user.fns.crit('con');
let bonus = statsComputed(user).str * crit.crit(user, 'con');
target.value += diminishingReturns(bonus, 2.5, 35);
if (!user.party.quest.progress.up) user.party.quest.progress.up = 0;
user.party.quest.progress.up += diminishingReturns(bonus, 55, 70);
@@ -167,11 +169,11 @@ spells.rogue = {
target: 'task',
notes: t('spellRogueBackStabNotes'),
cast (user, target, req) {
let _crit = user.fns.crit('str', 0.3);
let _crit = crit.crit(user, 'str', 0.3);
let bonus = calculateBonus(target.value, statsComputed(user).str, _crit);
user.stats.exp += diminishingReturns(bonus, 75, 50);
user.stats.gp += diminishingReturns(bonus, 18, 75);
user.fns.updateStats(user.stats, req);
updateStats(user, user.stats, req);
},
},
toolsOfTrade: { // Tools of the Trade

View File

@@ -69,6 +69,7 @@ let specialPets = {
'JackOLantern-Ghost': 'ghostJackolantern',
'Jackalope-RoyalPurple': 'royalPurpleJackalope',
'Orca-Base': 'orca',
'Bear-Veteran': 'veteranBear',
};
let specialMounts = {

View File

@@ -25,6 +25,9 @@ import {
MAX_INCENTIVES,
TAVERN_ID,
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
MAX_SUMMARY_SIZE_FOR_GUILDS,
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
SUPPORTED_SOCIAL_NETWORKS,
GUILDS_PER_PAGE,
PARTY_LIMIT_MEMBERS,
@@ -33,6 +36,9 @@ import {
api.constants = {
MAX_INCENTIVES,
LARGE_GROUP_COUNT_MESSAGE_CUTOFF,
MAX_SUMMARY_SIZE_FOR_GUILDS,
MAX_SUMMARY_SIZE_FOR_CHALLENGES,
MIN_SHORTNAME_SIZE_FOR_CHALLENGES,
SUPPORTED_SOCIAL_NETWORKS,
GUILDS_PER_PAGE,
PARTY_LIMIT_MEMBERS,
@@ -64,6 +70,9 @@ api.preenTodos = preenTodos;
import updateStore from './libs/updateStore';
api.updateStore = updateStore;
import inAppRewards from './libs/inAppRewards';
api.inAppRewards = inAppRewards;
import uuid from './libs/uuid';
api.uuid = uuid;
@@ -161,6 +170,7 @@ import deletePM from './ops/deletePM';
import reroll from './ops/reroll';
import reset from './ops/reset';
import markPmsRead from './ops/markPMSRead';
import pinnedGearUtils from './ops/pinnedGearUtils';
api.ops = {
scoreTask,
@@ -198,6 +208,7 @@ api.ops = {
reroll,
reset,
markPmsRead,
pinnedGearUtils,
};
/*

View File

@@ -0,0 +1,326 @@
import i18n from '../i18n';
import content from '../content/index';
import { BadRequest } from './errors';
import count from '../count';
import isPinned from './isPinned';
import getOfficialPinnedItems from './getOfficialPinnedItems';
import _mapValues from 'lodash/mapValues';
function lockQuest (quest, user) {
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
if (quest.unlockCondition && (quest.key === 'moon1' || quest.key === 'moon2' || quest.key === 'moon3')) {
return user.loginIncentives < quest.unlockCondition.incentiveThreshold;
}
if (user.achievements.quests) return quest.previous && !user.achievements.quests[quest.previous];
return quest.previous;
}
function isItemSuggested (officialPinnedItems, itemInfo) {
return officialPinnedItems.findIndex(officialItem => {
return officialItem.type === itemInfo.pinType && officialItem.path === itemInfo.path;
}) > -1;
}
function getDefaultGearProps (item, language) {
return {
key: item.key,
text: item.text(language),
notes: item.notes(language),
type: item.type,
specialClass: item.specialClass,
locked: false,
purchaseType: 'gear',
class: `shop_${item.key}`,
path: `gear.flat.${item.key}`,
str: item.str,
int: item.int,
per: item.per,
con: item.con,
klass: item.klass,
event: item.event,
set: item.set,
};
}
module.exports = function getItemInfo (user, type, item, officialPinnedItems, language = 'en') {
if (officialPinnedItems === undefined) {
officialPinnedItems = getOfficialPinnedItems(user);
}
let itemInfo;
switch (type) {
case 'eggs':
itemInfo = {
key: item.key,
text: i18n.t('egg', {eggType: item.text(language)}, language),
notes: item.notes(language),
value: item.value,
class: `Pet_Egg_${item.key}`,
locked: false,
currency: 'gems',
purchaseType: 'eggs',
path: `eggs.${item.key}`,
pinType: 'eggs',
};
break;
case 'hatchingPotions':
itemInfo = {
key: item.key,
text: i18n.t('potion', {potionType: item.text(language)}),
notes: item.notes(language),
class: `Pet_HatchingPotion_${item.key}`,
value: item.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
path: `hatchingPotions.${item.key}`,
pinType: 'hatchingPotions',
};
break;
case 'premiumHatchingPotion':
itemInfo = {
key: item.key,
text: i18n.t('potion', {potionType: item.text(language)}),
notes: `${item.notes(language)} ${item._addlNotes(language)}`,
class: `Pet_HatchingPotion_${item.key}`,
value: item.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
path: `premiumHatchingPotions.${item.key}`,
pinType: 'premiumHatchingPotion',
};
break;
case 'food':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
class: `Pet_Food_${item.key}`,
value: item.value,
locked: false,
currency: 'gems',
purchaseType: 'food',
path: `food.${item.key}`,
pinType: 'food',
};
break;
case 'bundles':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
value: item.value,
currency: 'gems',
class: `quest_bundle_${item.key}`,
purchaseType: 'bundles',
path: `bundles.${item.key}`,
pinType: 'bundles',
};
break;
case 'quests': // eslint-disable-line no-case-declarations
const locked = lockQuest(item, user);
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
group: item.group,
value: item.goldValue ? item.goldValue : item.value,
currency: item.goldValue ? 'gold' : 'gems',
locked,
unlockCondition: item.unlockCondition,
drop: item.drop,
boss: item.boss,
collect: item.collect ? _mapValues(item.collect, (o) => {
return {
count: o.count,
text: o.text(),
};
}) : undefined,
lvl: item.lvl,
class: locked ? `inventory_quest_scroll_${item.key}_locked` : `inventory_quest_scroll_${item.key}`,
purchaseType: 'quests',
path: `quests.${item.key}`,
pinType: 'quests',
};
break;
case 'timeTravelers':
// TODO
itemInfo = {};
break;
case 'seasonalSpell':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
value: item.value,
type: 'special',
currency: 'gold',
locked: false,
purchaseType: 'spells',
class: `inventory_special_${item.key}`,
path: `spells.special.${item.key}`,
pinType: 'seasonalSpell',
};
break;
case 'seasonalQuest':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
value: item.value,
type: 'quests',
currency: 'gems',
locked: false,
drop: item.drop,
boss: item.boss,
collect: item.collect,
class: `inventory_quest_scroll_${item.key}`,
purchaseType: 'quests',
path: `quests.${item.key}`,
pinType: 'seasonalQuest',
};
break;
case 'gear':
// spread operator not available
itemInfo = Object.assign(getDefaultGearProps(item, language), {
value: item.twoHanded ? 2 : 1,
currency: 'gems',
pinType: 'gear',
});
break;
case 'marketGear':
itemInfo = Object.assign(getDefaultGearProps(item, language), {
value: item.value,
currency: 'gold',
pinType: 'marketGear',
canOwn: item.canOwn,
});
break;
case 'background':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
class: `icon_background_${item.key}`,
value: item.price,
currency: item.currency || 'gems',
purchaseType: 'backgrounds',
path: `backgrounds.${item.set}.${item.key}`,
pinType: 'background',
};
break;
case 'mystery_set':
itemInfo = {
key: item.key,
text: item.text(language),
value: 1,
currency: 'hourglasses',
purchaseType: 'mystery_set',
class: `shop_set_mystery_${item.key}`,
path: `mystery.${item.key}`,
pinType: 'mystery_set',
};
break;
case 'potion':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(language),
value: item.value,
currency: 'gold',
purchaseType: 'potions',
class: `shop_${item.key}`,
path: 'potion',
pinType: 'potion',
};
break;
case 'armoire':
itemInfo = {
key: item.key,
text: item.text(language),
notes: item.notes(user, count.remainingGearInSet(user.items.gear.owned, 'armoire')), // TODO count
value: item.value,
currency: 'gold',
purchaseType: 'armoire',
class: `shop_${item.key}`,
path: 'armoire',
pinType: 'armoire',
};
break;
case 'card': {
let spellInfo = content.spells.special[item.key];
itemInfo = {
key: item.key,
purchaseType: 'card',
class: `inventory_special_${item.key}`,
text: spellInfo.text(),
notes: spellInfo.notes(),
value: spellInfo.value,
currency: 'gold',
path: `cardTypes.${item.key}`,
pinType: 'card',
};
break;
}
case 'gem': {
itemInfo = {
key: 'gem',
purchaseType: 'gems',
class: 'gem',
text: i18n.t('subGemName'),
notes: i18n.t('subGemPop'),
value: 20,
currency: 'gold',
path: 'special.gems',
pinType: 'gem',
locked: !user.purchased.plan.customerId,
};
break;
}
case 'rebirth_orb': {
itemInfo = {
key: 'rebirth_orb',
purchaseType: 'rebirth_orb',
class: 'rebirth_orb',
text: i18n.t('rebirthName'),
notes: i18n.t('rebirthPop'),
value: user.stats.lvl < 100 ? 6 : 0,
currency: 'gems',
path: 'special.rebirth_orb',
pinType: 'rebirth_orb',
locked: !user.flags.rebirthEnabled,
};
break;
}
case 'fortify': {
itemInfo = {
key: 'fortify',
purchaseType: 'fortify',
class: 'inventory_special_fortify',
text: i18n.t('fortifyName'),
notes: i18n.t('fortifyPop'),
value: 4,
currency: 'gems',
path: 'special.fortify',
pinType: 'fortify',
};
break;
}
}
if (itemInfo) {
itemInfo.isSuggested = isItemSuggested(officialPinnedItems, itemInfo);
itemInfo.pinned = isPinned(user, itemInfo);
} else {
throw new BadRequest(i18n.t('wrongItemType', {type}, language));
}
return itemInfo;
};

View File

@@ -0,0 +1,28 @@
import content from '../content/index';
import SeasonalShopConfig from '../libs/shops-seasonal.config';
import toArray from 'lodash/toArray';
const officialPinnedItems = content.officialPinnedItems;
let flatGearArray = toArray(content.gear.flat);
module.exports = function getOfficialPinnedItems (user) {
let officialItemsArray = [...officialPinnedItems];
if (SeasonalShopConfig.pinnedSets && Boolean(user) && user.stats.class) {
let setToAdd = SeasonalShopConfig.pinnedSets[user.stats.class];
// pinnedSets == current seasonal class set are always gold purchaseable
flatGearArray.filter((gear) => {
return user.items.gear.owned[gear.key] === undefined && gear.set === setToAdd;
}).map((gear) => {
officialItemsArray.push({
type: 'marketGear',
path: `gear.flat.${gear.key}`,
});
});
}
return officialItemsArray;
};

View File

@@ -0,0 +1,25 @@
import content from '../content/index';
import get from 'lodash/get';
import getItemInfo from './getItemInfo';
import shops from './shops';
import getOfficialPinnedItems from './getOfficialPinnedItems';
module.exports = function getPinnedItems (user) {
let officialPinnedItems = getOfficialPinnedItems(user);
const officialPinnedItemsNotUnpinned = officialPinnedItems.filter(officialPin => {
const isUnpinned = user.unpinnedItems.findIndex(unpinned => unpinned.path === officialPin.path) > -1;
return !isUnpinned;
});
const pinnedItems = officialPinnedItemsNotUnpinned.concat(user.pinnedItems);
let items = pinnedItems.map(({type, path}) => {
return getItemInfo(user, type, get(content, path), officialPinnedItems);
});
shops.checkMarketGearLocked(user, items);
return items;
};

View File

@@ -0,0 +1,14 @@
module.exports = function isPinned (user, item, checkOfficialPinnedItems /* getOfficialPinnedItems */) {
if (user === null)
return false;
const isPinnedOfficial = checkOfficialPinnedItems !== undefined && checkOfficialPinnedItems.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;
return isItemPinned;
};

View File

@@ -0,0 +1,32 @@
import { SEASONAL_SETS} from '../content/constants';
module.exports = {
// opened: false,
opened: true,
// used for the seasonalShop.notes
// currentSeason: 'Closed',
currentSeason: 'Fall',
dateRange: { start: '2017-09-21', end: '2017-10-31' },
availableSets: [
...SEASONAL_SETS.fall,
],
pinnedSets: {
warrior: 'fall2017HabitoweenSet',
wizard: 'fall2017MasqueradeSet',
rogue: 'fall2017TrickOrTreatSet',
healer: 'fall2017HauntedHouseSet',
},
availableSpells: [
// 'spookySparkles',
],
availableQuests: [
],
featuredSet: 'battleRogueSet',
};

View File

@@ -1,26 +1,45 @@
import values from 'lodash/values';
import map from 'lodash/map';
import keys from 'lodash/keys';
import get from 'lodash/get';
import each from 'lodash/each';
import filter from 'lodash/filter';
import eachRight from 'lodash/eachRight';
import toArray from 'lodash/toArray';
import pickBy from 'lodash/pickBy';
import sortBy from 'lodash/sortBy';
import content from '../content/index';
import i18n from '../i18n';
import getItemInfo from './getItemInfo';
import updateStore from './updateStore';
import seasonalShopConfig from './shops-seasonal.config';
import featuredItems from '../content/shop-featuredItems';
import getOfficialPinnedItems from './getOfficialPinnedItems';
let shops = {};
function lockQuest (quest, user) {
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
if (quest.unlockCondition && (quest.key === 'moon1' || quest.key === 'moon2' || quest.key === 'moon3')) {
return user.loginIncentives < quest.unlockCondition.incentiveThreshold;
}
if (user.achievements.quests) return quest.previous && !user.achievements.quests[quest.previous];
return quest.previous;
}
/* Market */
shops.getMarketShop = function getMarketShop (user, language) {
return {
identifier: 'market',
text: i18n.t('market'),
notes: i18n.t('welcomeMarketMobile'),
imageName: 'npc_alex',
categories: shops.getMarketCategories(user, language),
featured: {
text: i18n.t('featuredItems'),
items: featuredItems.market.map(i => {
return getItemInfo(user, i.type, get(content, i.path));
}),
},
};
};
shops.getMarketCategories = function getMarket (user, language) {
let officialPinnedItems = getOfficialPinnedItems(user);
let categories = [];
let eggsCategory = {
identifier: 'eggs',
@@ -32,16 +51,7 @@ shops.getMarketCategories = function getMarket (user, language) {
.filter(egg => egg.canBuy(user))
.concat(values(content.dropEggs))
.map(egg => {
return {
key: egg.key,
text: i18n.t('egg', {eggType: egg.text()}, language),
notes: egg.notes(language),
value: egg.value,
class: `Pet_Egg_${egg.key}`,
locked: false,
currency: 'gems',
purchaseType: 'eggs',
};
return getItemInfo(user, 'eggs', egg, officialPinnedItems, language);
}), 'key');
categories.push(eggsCategory);
@@ -53,16 +63,7 @@ shops.getMarketCategories = function getMarket (user, language) {
hatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
.filter(hp => !hp.limited)
.map(hatchingPotion => {
return {
key: hatchingPotion.key,
text: i18n.t('potion', {potionType: hatchingPotion.text(language)}),
notes: hatchingPotion.notes(language),
class: `Pet_HatchingPotion_${hatchingPotion.key}`,
value: hatchingPotion.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
};
return getItemInfo(user, 'hatchingPotions', hatchingPotion, officialPinnedItems, language);
}), 'key');
categories.push(hatchingPotionsCategory);
@@ -74,16 +75,7 @@ shops.getMarketCategories = function getMarket (user, language) {
premiumHatchingPotionsCategory.items = sortBy(values(content.hatchingPotions)
.filter(hp => hp.limited && hp.canBuy())
.map(premiumHatchingPotion => {
return {
key: premiumHatchingPotion.key,
text: i18n.t('potion', {potionType: premiumHatchingPotion.text(language)}),
notes: `${premiumHatchingPotion.notes(language)} ${premiumHatchingPotion._addlNotes(language)}`,
class: `Pet_HatchingPotion_${premiumHatchingPotion.key}`,
value: premiumHatchingPotion.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
};
return getItemInfo(user, 'premiumHatchingPotion', premiumHatchingPotion, officialPinnedItems, language);
}), 'key');
if (premiumHatchingPotionsCategory.items.length > 0) {
categories.push(premiumHatchingPotionsCategory);
@@ -97,24 +89,106 @@ shops.getMarketCategories = function getMarket (user, language) {
foodCategory.items = sortBy(values(content.food)
.filter(food => food.canDrop || food.key === 'Saddle')
.map(foodItem => {
return {
key: foodItem.key,
text: foodItem.text(language),
notes: foodItem.notes(language),
class: `Pet_Food_${foodItem.key}`,
value: foodItem.value,
locked: false,
currency: 'gems',
purchaseType: 'food',
};
return getItemInfo(user, 'food', foodItem, officialPinnedItems, language);
}), 'key');
categories.push(foodCategory);
return categories;
};
function getClassName (classType, language) {
if (classType === 'wizard') {
return i18n.t('mage', language);
} else {
return i18n.t(classType, language);
}
}
shops.checkMarketGearLocked = function checkMarketGearLocked (user, items) {
let result = filter(items, ['pinType', 'marketGear']);
let availableGear = map(updateStore(user), (item) => getItemInfo(user, 'marketGear', item).path);
for (let gear of result) {
if (gear.klass !== user.stats.class) {
gear.locked = true;
}
if (!gear.locked && !availableGear.includes(gear.path)) {
gear.locked = true;
}
// @TODO: I'm not sure what the logic for locking is supposed to be
// But, I am pretty sure if we pin an armoire item, it needs to be unlocked
if (gear.klass === 'armoire') {
gear.locked = false;
}
if (Boolean(gear.specialClass) && Boolean(gear.set)) {
let currentSet = gear.set === seasonalShopConfig.pinnedSets[gear.specialClass];
gear.locked = currentSet && user.stats.class !== gear.specialClass;
}
if (gear.canOwn) {
gear.locked = !gear.canOwn(user);
}
let itemOwned = user.items.gear.owned[gear.key];
if (itemOwned === false) {
gear.locked = false;
}
}
};
shops.getMarketGearCategories = function getMarketGear (user, language) {
let categories = [];
let officialPinnedItems = getOfficialPinnedItems(user);
for (let classType of content.classes) {
let category = {
identifier: classType,
text: getClassName(classType, language),
};
let result = filter(content.gear.flat, ['klass', classType]);
category.items = map(result, (e) => {
let newItem = getItemInfo(user, 'marketGear', e, officialPinnedItems);
return newItem;
});
shops.checkMarketGearLocked(user, category.items);
categories.push(category);
}
return categories;
};
/* Quests */
shops.getQuestShop = function getQuestShop (user, language) {
return {
identifier: 'questShop',
text: i18n.t('quests'),
notes: i18n.t('ianTextMobile'),
imageName: 'npc_ian',
categories: shops.getQuestShopCategories(user, language),
featured: {
text: i18n.t('featuredQuests'),
items: featuredItems.quests.map(i => {
return getItemInfo(user, i.type, get(content, i.path));
}),
},
};
};
shops.getQuestShopCategories = function getQuestShopCategories (user, language) {
let categories = [];
let officialPinnedItems = getOfficialPinnedItems(user);
/*
* ---------------------------------------------------------------
@@ -178,15 +252,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
bundleCategory.items = sortBy(values(content.bundles)
.filter(bundle => bundle.type === 'quests' && bundle.canBuy())
.map(bundle => {
return {
key: bundle.key,
text: bundle.text(language),
notes: bundle.notes(language),
value: bundle.value,
currency: 'gems',
class: `quest_bundle_${bundle.key}`,
purchaseType: 'bundles',
};
return getItemInfo(user, 'bundles', bundle, officialPinnedItems, language);
}));
if (bundleCategory.items.length > 0) {
@@ -202,23 +268,7 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
category.items = content.questsByLevel
.filter(quest => quest.canBuy(user) && quest.category === type)
.map(quest => {
let locked = lockQuest(quest, user);
return {
key: quest.key,
text: quest.text(language),
notes: quest.notes(language),
group: quest.group,
value: quest.goldValue ? quest.goldValue : quest.value,
currency: quest.goldValue ? 'gold' : 'gems',
locked,
unlockCondition: quest.unlockCondition,
drop: quest.drop,
boss: quest.boss,
collect: quest.collect,
lvl: quest.lvl,
class: locked ? `inventory_quest_scroll_locked inventory_quest_scroll_${quest.key}_locked` : `inventory_quest_scroll inventory_quest_scroll_${quest.key}`,
purchaseType: 'quests',
};
return getItemInfo(user, 'quests', quest, officialPinnedItems, language);
});
categories.push(category);
@@ -227,6 +277,21 @@ shops.getQuestShopCategories = function getQuestShopCategories (user, language)
return categories;
};
/* Time Travelers */
shops.getTimeTravelersShop = function getTimeTravelersShop (user, language) {
let hasTrinkets = user.purchased.plan.consecutive.trinkets > 0;
return {
identifier: 'timeTravelersShop',
text: i18n.t('timeTravelers'),
opened: hasTrinkets,
notes: hasTrinkets ? i18n.t('timeTravelersPopover') : i18n.t('timeTravelersPopoverNoSubMobile'),
imageName: hasTrinkets ? 'npc_timetravelers_active' : 'npc_timetravelers',
categories: shops.getTimeTravelersCategories(user, language),
};
};
shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, language) {
let categories = [];
let stable = {pets: 'Pet-', mounts: 'Mount_Icon_'};
@@ -251,6 +316,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
notes: '',
locked: false,
currency: 'hourglasses',
pinType: 'IGNORE',
};
category.items.push(item);
}
@@ -269,6 +335,8 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
let category = {
identifier: set.key,
text: set.text(language),
path: `mystery.${set.key}`,
pinType: 'mystery_set',
purchaseAll: true,
};
@@ -283,6 +351,7 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
locked: false,
currency: 'hourglasses',
class: `shop_${item.key}`,
pinKey: `timeTravelers!gear.flat.${item.key}`,
};
});
if (category.items.length > 0) {
@@ -294,36 +363,64 @@ shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, la
return categories;
};
/* Seasonal */
let flatGearArray = toArray(content.gear.flat);
shops.getSeasonalGearBySet = function getSeasonalGearBySet (user, set, officialPinnedItems, language, ignoreAlreadyOwned = false) {
return flatGearArray.filter((gear) => {
if (!ignoreAlreadyOwned && user.items.gear.owned[gear.key] !== undefined)
return false;
return gear.set === set;
}).map(gear => {
let currentSet = gear.set === seasonalShopConfig.pinnedSets[gear.specialClass];
// only the current season set can be purchased by gold
let itemInfo = getItemInfo(null, currentSet ? 'marketGear' : 'gear', gear, officialPinnedItems, language);
itemInfo.locked = currentSet && user.stats.class !== gear.specialClass;
return itemInfo;
});
};
shops.getSeasonalShop = function getSeasonalShop (user, language) {
let officialPinnedItems = getOfficialPinnedItems(user);
let resObject = {
identifier: 'seasonalShop',
text: i18n.t('seasonalShop'),
notes: i18n.t(`seasonalShop${seasonalShopConfig.currentSeason}Text`),
imageName: seasonalShopConfig.opened ? 'seasonalshop_open' : 'seasonalshop_closed',
opened: seasonalShopConfig.opened,
categories: this.getSeasonalShopCategories(user, language),
featured: {
text: i18n.t(seasonalShopConfig.featuredSet),
items: shops.getSeasonalGearBySet(user, seasonalShopConfig.featuredSet, officialPinnedItems, language, true),
},
};
return resObject;
};
// To switch seasons/available inventory, edit the AVAILABLE_SETS object to whatever should be sold.
// let AVAILABLE_SETS = {
// setKey: i18n.t('setTranslationString', language),
// };
shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) {
const AVAILABLE_SETS = {
fallHealer: i18n.t('mummyMedicSet', language),
fall2015Healer: i18n.t('potionerSet', language),
fall2016Healer: i18n.t('fall2016GorgonHealerSet', language),
fallMage: i18n.t('witchyWizardSet', language),
fall2015Mage: i18n.t('stitchWitchSet', language),
fall2016Mage: i18n.t('fall2016WickedSorcererSet', language),
fallRogue: i18n.t('vampireSmiterSet', language),
fall2015Rogue: i18n.t('battleRogueSet', language),
fall2016Rogue: i18n.t('fall2016BlackWidowSet', language),
fallWarrior: i18n.t('monsterOfScienceSet', language),
fall2015Warrior: i18n.t('scarecrowWarriorSet', language),
fall2016Warrior: i18n.t('fall2016SwampThingSet', language),
};
let officialPinnedItems = getOfficialPinnedItems(user);
const AVAILABLE_SPELLS = [
...seasonalShopConfig.availableSpells,
];
const AVAILABLE_QUESTS = [
...seasonalShopConfig.availableQuests,
];
let categories = [];
let flatGearArray = toArray(content.gear.flat);
let spells = pickBy(content.spells.special, (spell, key) => {
return AVAILABLE_SPELLS.indexOf(key) !== -1;
});
@@ -334,18 +431,8 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
text: i18n.t('seasonalItems', language),
};
category.items = map(spells, (spell, key) => {
return {
key,
text: spell.text(language),
notes: spell.notes(language),
value: spell.value,
type: 'special',
currency: 'gold',
locked: false,
purchaseType: 'spells',
class: `inventory_special_${key}`,
};
category.items = map(spells, (spell) => {
return getItemInfo(user, 'seasonalSpell', spell, officialPinnedItems, language);
});
categories.push(category);
@@ -361,54 +448,27 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
text: i18n.t('quests', language),
};
category.items = map(quests, (quest, key) => {
return {
key,
text: quest.text(language),
notes: quest.notes(language),
value: quest.value,
type: 'quests',
currency: 'gems',
locked: false,
drop: quest.drop,
boss: quest.boss,
collect: quest.collect,
class: `inventory_quest_scroll_${key}`,
purchaseType: 'quests',
};
category.items = map(quests, (quest) => {
return getItemInfo(user, 'seasonalQuest', quest, language);
});
categories.push(category);
}
for (let key in AVAILABLE_SETS) {
if (AVAILABLE_SETS.hasOwnProperty(key)) {
let category = {
identifier: key,
text: AVAILABLE_SETS[key],
};
for (let set of seasonalShopConfig.availableSets) {
let category = {
identifier: set,
text: i18n.t(set),
};
category.items = flatGearArray.filter((gear) => {
return user.items.gear.owned[gear.key] === undefined && gear.index === key;
}).map(gear => {
return {
key: gear.key,
text: gear.text(language),
notes: gear.notes(language),
value: gear.twoHanded ? 2 : 1,
type: gear.type,
specialClass: gear.specialClass,
locked: false,
currency: 'gems',
purchaseType: 'gear',
class: `shop_${gear.key}`,
};
});
category.items = shops.getSeasonalGearBySet(user, set, officialPinnedItems, language, false);
if (category.items.length > 0) {
category.specialClass = category.items[0].specialClass;
categories.push(category);
}
if (category.items.length > 0) {
let item = category.items[0];
category.specialClass = item.specialClass;
category.event = item.event;
categories.push(category);
}
}
@@ -417,6 +477,7 @@ shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, lang
shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
let sets = [];
let officialPinnedItems = getOfficialPinnedItems();
eachRight(content.backgrounds, (group, key) => {
let set = {
@@ -424,15 +485,8 @@ shops.getBackgroundShopSets = function getBackgroundShopSets (language) {
text: i18n.t(key, language),
};
set.items = map(group, (background, bgKey) => {
return {
key: bgKey,
text: background.text(language),
notes: background.notes(language),
value: background.price,
currency: background.currency || 'gems',
purchaseType: 'backgrounds',
};
set.items = map(group, (background) => {
return getItemInfo(null, 'background', background, officialPinnedItems, language);
});
sets.push(set);

View File

@@ -16,11 +16,15 @@ function equipmentStatBonusComputed (stat, user) {
let equippedKeys = values(!equipped.toObject ? equipped : equipped.toObject());
each(equippedKeys, (equippedItem) => {
let equipmentStat = gear[equippedItem][stat];
let classBonusMultiplier = gear[equippedItem].klass === user.stats.class ||
gear[equippedItem].specialClass === user.stats.class ? 0.5 : 0;
gearBonus += equipmentStat;
classBonus += equipmentStat * classBonusMultiplier;
let item = gear[equippedItem];
if (item) {
let equipmentStat = item[stat];
let classBonusMultiplier = item.klass === user.stats.class ||
item.specialClass === user.stats.class ? 0.5 : 0;
gearBonus += equipmentStat;
classBonus += equipmentStat * classBonusMultiplier;
}
});
return {

View File

@@ -25,7 +25,13 @@ module.exports = function taskDefaults (task = {}) {
challenge: {
shortName: 'None',
},
yesterDaily: true,
group: {
approval: {
required: false,
approved: false,
requested: false,
},
},
reminders: [],
attribute: 'str',
createdAt: new Date(), // TODO these are going to be overwritten by the server...
@@ -73,6 +79,9 @@ module.exports = function taskDefaults (task = {}) {
startDate: moment().startOf('day').toDate(),
everyX: 1,
frequency: 'weekly',
daysOfMonth: [],
weeksOfMonth: [],
yesterDaily: true,
});
}

View File

@@ -6,6 +6,7 @@ import reduce from 'lodash/reduce';
import content from '../content/index';
// Return the list of gear items available for purchase
// TODO: Remove updateStore once the new client is live
let sortOrder = reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;

View File

@@ -11,6 +11,8 @@ import {
import handleTwoHanded from '../fns/handleTwoHanded';
import ultimateGear from '../fns/ultimateGear';
import { removePinnedGearAddPossibleNewOnes } from './pinnedGearUtils';
module.exports = function buyGear (user, req = {}, analytics) {
let key = get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
@@ -38,7 +40,8 @@ module.exports = function buyGear (user, req = {}, analytics) {
message = handleTwoHanded(user, item, undefined, req);
}
user.items.gear.owned[item.key] = true;
removePinnedGearAddPossibleNewOnes(user, `gear.flat.${item.key}`, item.key);
if (item.last) ultimateGear(user);

View File

@@ -23,7 +23,7 @@ module.exports = function buyMysterySet (user, req = {}, analytics) {
throw new NotFound(i18n.t('mysterySetNotFound', req.language));
}
if (typeof window !== 'undefined' && window.confirm) { // TODO move to client
if (typeof window !== 'undefined' && !req.noConfirm && window.confirm) { // TODO move to client
if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) return;
}

View File

@@ -7,8 +7,11 @@ import {
NotAuthorized,
BadRequest,
} from '../libs/errors';
import { removePinnedGearByClass, removePinnedItemsByOwnedGear, addPinnedGearByClass } from './pinnedGearUtils';
function resetClass (user, req = {}) {
removePinnedGearByClass(user);
if (user.preferences.disableClasses) {
user.preferences.disableClasses = false;
user.preferences.autoAllocate = false;
@@ -41,9 +44,13 @@ module.exports = function changeClass (user, req = {}, analytics) {
user.stats.class = klass;
user.flags.classSelected = true;
addPinnedGearByClass(user);
user.items.gear.owned[`weapon_${klass}_0`] = true;
if (klass === 'rogue') user.items.gear.owned[`shield_${klass}_0`] = true;
removePinnedItemsByOwnedGear(user);
if (analytics) {
analytics.track('change class', {
uuid: user._id,

View File

@@ -40,6 +40,7 @@ import readCard from './readCard';
import openMysteryItem from './openMysteryItem';
import scoreTask from './scoreTask';
import markPmsRead from './markPMSRead';
import * as pinnedGearUtils from './pinnedGearUtils';
module.exports = {
sleep,
@@ -84,4 +85,5 @@ module.exports = {
openMysteryItem,
scoreTask,
markPmsRead,
pinnedGearUtils,
};

View File

@@ -0,0 +1,183 @@
import content from '../content/index';
import getItemInfo from '../libs/getItemInfo';
import { BadRequest } from '../libs/errors';
import i18n from '../i18n';
import isPinned from '../libs/isPinned';
import getOfficialPinnedItems from '../libs/getOfficialPinnedItems';
import get from 'lodash/get';
import each from 'lodash/each';
import sortBy from 'lodash/sortBy';
import lodashFind from 'lodash/find';
import reduce from 'lodash/reduce';
let sortOrder = reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;
return accumulator;
}, {});
function selectGearToPin (user) {
let changes = [];
each(content.gearTypes, (type) => {
let found = lodashFind(content.gear.tree[type][user.stats.class], (item) => {
return !user.items.gear.owned[item.key];
});
if (found) changes.push(found);
});
return sortBy(changes, (change) => sortOrder[change.type]);
}
function addPinnedGear (user, type, path) {
const foundIndex = user.pinnedItems.findIndex(pinnedItem => {
return pinnedItem.path === path;
});
if (foundIndex === -1) {
user.pinnedItems.push({
type,
path,
});
}
}
function addPinnedGearByClass (user) {
let newPinnedItems = selectGearToPin(user);
for (let item of newPinnedItems) {
let itemInfo = getItemInfo(user, 'marketGear', item);
addPinnedGear(user, itemInfo.pinType, itemInfo.path);
}
}
function removeItemByPath (user, path) {
const foundIndex = user.pinnedItems.findIndex(pinnedItem => {
return pinnedItem.path === path;
});
if (foundIndex >= 0) {
user.pinnedItems.splice(foundIndex, 1);
return true;
}
return false;
}
function removePinnedGearByClass (user) {
let currentPinnedItems = selectGearToPin(user);
for (let item of currentPinnedItems) {
let itemInfo = getItemInfo(user, 'marketGear', item);
removeItemByPath(user, itemInfo.path);
}
}
function removePinnedGearAddPossibleNewOnes (user, itemPath, newItemKey) {
let currentPinnedItems = selectGearToPin(user);
let removeAndAddAllItems = false;
for (let item of currentPinnedItems) {
let itemInfo = getItemInfo(user, 'marketGear', item);
if (itemInfo.path === itemPath) {
removeAndAddAllItems = true;
break;
}
}
removeItemByPath(user, itemPath);
if (removeAndAddAllItems) {
// an item of the users current "new" gear was bought
// remove the old pinned gear items and add the new gear back
removePinnedGearByClass(user);
user.items.gear.owned[newItemKey] = true;
addPinnedGearByClass(user);
} else {
// just change the new gear to owned
user.items.gear.owned[newItemKey] = true;
}
}
/**
* removes all pinned gear that the user already owns (like class starter gear which has been pinned before)
* @param user
*/
function removePinnedItemsByOwnedGear (user) {
each(user.items.gear.owned, (bool, key) => {
if (bool) {
removeItemByPath(user, `gear.flat.${key}`);
}
});
}
const PATHS_WITHOUT_ITEM = ['special.gems', 'special.rebirth_orb', 'special.fortify'];
/**
* @returns {boolean} TRUE added the item / FALSE removed it
*/
function togglePinnedItem (user, {item, type, path}, req = {}) {
let arrayToChange;
let officialPinnedItems = getOfficialPinnedItems(user);
if (!path) {
// If path isn't passed it means an item was passed
path = getItemInfo(user, type, item, officialPinnedItems, req.language).path;
} else {
if (!item) {
item = get(content, path);
}
if (!item && PATHS_WITHOUT_ITEM.indexOf(path) === -1) {
// path not exists in our content structure
throw new BadRequest(i18n.t('wrongItemPath', {path}, req.language));
}
// check if item exists & valid to be pinned
getItemInfo(user, type, item, officialPinnedItems, req.language);
}
if (path === 'armoire' || path === 'potion') {
throw new BadRequest(i18n.t('cannotUnpinArmoirPotion', req.language));
}
let isOfficialPinned = officialPinnedItems.find(officialPinnedItem => {
return officialPinnedItem.path === path;
}) !== undefined;
if (isOfficialPinned) {
arrayToChange = user.unpinnedItems;
} else {
arrayToChange = user.pinnedItems;
}
const foundIndex = arrayToChange.findIndex(pinnedItem => {
return pinnedItem.path === path;
});
if (foundIndex >= 0) {
arrayToChange.splice(foundIndex, 1);
return isOfficialPinned;
} else {
arrayToChange.push({path, type});
return !isOfficialPinned;
}
}
module.exports = {
addPinnedGearByClass,
addPinnedGear,
removePinnedGearByClass,
removePinnedGearAddPossibleNewOnes,
removePinnedItemsByOwnedGear,
togglePinnedItem,
removeItemByPath,
isPinned,
};

View File

@@ -11,6 +11,9 @@ import {
BadRequest,
} from '../libs/errors';
import { removeItemByPath } from './pinnedGearUtils';
import getItemInfo from '../libs/getItemInfo';
module.exports = function purchase (user, req = {}, analytics) {
let type = get(req.params, 'type');
let key = get(req.params, 'key');
@@ -103,6 +106,9 @@ module.exports = function purchase (user, req = {}, analytics) {
throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
}
let itemInfo = getItemInfo(user, type, item);
removeItemByPath(user, itemInfo.path);
user.balance -= price;
if (type === 'gear') {

View File

@@ -6,6 +6,8 @@ import {
NotAuthorized,
} from '../libs/errors';
import equip from './equip';
import { removePinnedGearByClass } from './pinnedGearUtils';
const USERSTATSLIST = ['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'];
@@ -46,6 +48,8 @@ module.exports = function rebirth (user, tasks = [], req = {}, analytics) {
}
});
removePinnedGearByClass(user);
let stats = user.stats;
stats.buffs = {};
stats.hp = 50;

View File

@@ -9,6 +9,9 @@ import {
import randomVal from '../libs/randomVal';
import predictableRandom from '../fns/predictableRandom';
import { removePinnedGearByClass, addPinnedGearByClass, addPinnedGear } from './pinnedGearUtils';
import getItemInfo from '../libs/getItemInfo';
module.exports = function revive (user, req = {}, analytics) {
if (user.stats.hp > 0) {
throw new NotAuthorized(i18n.t('cannotRevive', req.language));
@@ -81,8 +84,15 @@ module.exports = function revive (user, req = {}, analytics) {
let item = content.gear.flat[lostItem];
if (item) {
removePinnedGearByClass(user);
user.items.gear.owned[lostItem] = false;
addPinnedGearByClass(user);
let itemInfo = getItemInfo(user, 'marketGear', item);
addPinnedGear(user, itemInfo.pinType, itemInfo.path);
if (user.items.gear.equipped[item.type] === lostItem) {
user.items.gear.equipped[item.type] = `${item.type}_base_0`;
}

View File

@@ -9,6 +9,10 @@ import {
BadRequest,
} from '../libs/errors';
import { removeItemByPath } from './pinnedGearUtils';
import getItemInfo from '../libs/getItemInfo';
import content from '../content/index';
// If item is already purchased -> equip it
// Otherwise unlock it
module.exports = function unlock (user, req = {}, analytics) {
@@ -75,10 +79,11 @@ module.exports = function unlock (user, req = {}, analytics) {
setWith(user, `purchased.${pathPart}`, true, Object);
});
} else {
let split = path.split('.');
let value = split.pop();
let key = split.join('.');
if (alreadyOwns) { // eslint-disable-line no-lonely-if
let split = path.split('.');
let value = split.pop();
let key = split.join('.');
if (key === 'background' && value === user.preferences.background) {
value = '';
}
@@ -88,6 +93,13 @@ module.exports = function unlock (user, req = {}, analytics) {
} else {
// Using Object so path[1] won't create an array but an object {path: {1: value}}
setWith(user, `purchased.${path}`, true, Object);
// @TODO: Test and check test coverage
if (isBackground) {
let backgroundContent = content.backgroundsFlat[value];
let itemInfo = getItemInfo(user, 'background', backgroundContent);
removeItemByPath(user, itemInfo.path);
}
}
}