Common reorg (#8025)

* Re-organize common folder

* fix: Correct paths in tests

* fix: move new content to proper folder

* chore: Move audio folder to assets

* Move sprites to sprites assets directory

* Move css sprites to assets directory

* Split out readmes for common code and sprites

* Move images to assets directory

* Move destinatin of shared browserified file

* remove unused file

* move compiled js to client-old

* Fix karma tests

* fix: Correct paths for sprites
This commit is contained in:
Blade Barringer
2016-09-16 10:18:07 -05:00
committed by Matteo Pagliazzi
parent d971e673af
commit 81b7eeeb71
5956 changed files with 269 additions and 270 deletions

View File

@@ -0,0 +1,14 @@
/*
Are there tags applied?
*/
// TODO move to client
module.exports = function appliedTags (userTags, taskTags = []) {
let arr = userTags.filter(tag => {
return taskTags.indexOf(tag.id) !== -1;
}).map(tag => {
return tag.name;
});
return arr.join(', ');
};

View File

@@ -0,0 +1,5 @@
import _ from 'lodash';
// TODO remove completely, only used in client
module.exports = _.get;

View File

@@ -0,0 +1,5 @@
import _ from 'lodash';
// TODO remove completely, only used in client
module.exports = _.set;

View File

@@ -0,0 +1,42 @@
import extendableBuiltin from './extendableBuiltin';
// Base class for custom application errors
// It extends Error and capture the stack trace
export class CustomError extends extendableBuiltin(Error) {
constructor () {
super();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
// We specify an httpCode for all errors so that they can be used in the API too
export class NotAuthorized extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 401;
this.message = customMessage || 'Not authorized.';
}
}
export class BadRequest extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 400;
this.message = customMessage || 'Bad request.';
}
}
export class NotFound extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 404;
this.message = customMessage || 'Not found.';
}
}

View File

@@ -0,0 +1,11 @@
// 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
module.exports = function extendableBuiltin (klass) {
function ExtendableBuiltin () {
klass.apply(this, arguments);
}
ExtendableBuiltin.prototype = Object.create(klass.prototype);
Object.setPrototypeOf(ExtendableBuiltin, klass);
return ExtendableBuiltin;
};

View File

@@ -0,0 +1,9 @@
// TODO move to client
module.exports = function gold (num) {
if (num) {
return Math.floor(num);
} else {
return '0';
}
};

View File

@@ -0,0 +1,13 @@
import _ from 'lodash';
/*
are any tags active?
*/
// TODO move to client
module.exports = function noTags (tags) {
return _.isEmpty(tags) || _.isEmpty(_.filter(tags, (t) => {
return t;
}));
};

View File

@@ -0,0 +1,19 @@
// TODO move to client
module.exports = function percent (x, y, dir) {
let roundFn;
switch (dir) {
case 'up':
roundFn = Math.ceil;
break;
case 'down':
roundFn = Math.floor;
break;
default:
roundFn = Math.round;
}
if (x === 0) {
x = 1;
}
return Math.max(0, roundFn(x / y * 100));
};

View File

@@ -0,0 +1,13 @@
// An utility to pick deep properties from an object.
// Works like _.pick but supports nested props (ie pickDeep(obj, ['deep.property']))
import _ from 'lodash';
module.exports = function pickDeep (obj, properties) {
if (!_.isArray(properties)) throw new Error('"properties" must be an array');
let result = {};
_.each(properties, (prop) => _.set(result, prop, _.get(obj, prop)));
return result;
};

View File

@@ -0,0 +1,4 @@
module.exports = {
convRate: 20,
convCap: 25,
};

View File

@@ -0,0 +1,12 @@
import moment from 'moment';
import _ from 'lodash';
// TODO used only in v2
module.exports = function preenTodos (tasks) {
return _.filter(tasks, (t) => {
return !t.completed || t.challenge && t.challenge.id || moment(t.dateCompleted).isAfter(moment().subtract({
days: 3,
}));
});
};

View File

@@ -0,0 +1,20 @@
import _ from 'lodash';
import uuid from './uuid';
/*
Reflists are arrays, but stored as objects. 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:
*/
module.exports = function refPush (reflist, item) {
item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1;
if (!(item.id && !reflist[item.id])) {
item.id = uuid();
}
reflist[item.id] = item;
return reflist[item.id];
};

View File

@@ -0,0 +1,257 @@
import _ from 'lodash';
import content from '../content/index';
import i18n from '../i18n';
let shops = {};
function lockQuest (quest, user) {
if (quest.lvl && user.stats.lvl < quest.lvl) return true;
if (user.achievements.quests) return quest.previous && !user.achievements.quests[quest.previous];
return quest.previous;
}
shops.getMarketCategories = function getMarket (user, language) {
let categories = [];
let eggsCategory = {
identifier: 'eggs',
text: i18n.t('eggs', language),
notes: i18n.t('dropsExplanation', language),
};
eggsCategory.items = _(content.questEggs)
.values()
.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',
};
}).sortBy('key').value();
categories.push(eggsCategory);
let hatchingPotionsCategory = {
identifier: 'hatchingPotions',
text: i18n.t('hatchingPotions', language),
notes: i18n.t('dropsExplanation', language),
};
hatchingPotionsCategory.items = _(content.hatchingPotions)
.values()
.filter(hp => !hp.limited)
.map(hatchingPotion => {
return {
key: hatchingPotion.key,
text: hatchingPotion.text(language),
notes: hatchingPotion.notes(language),
class: `Pet_HatchingPotion_${hatchingPotion.key}`,
value: hatchingPotion.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
};
}).sortBy('key').value();
categories.push(hatchingPotionsCategory);
let premiumHatchingPotionsCategory = {
identifier: 'premiumHatchingPotions',
text: i18n.t('magicHatchingPotions', language),
notes: i18n.t('premiumPotionNoDropExplanation', language),
};
premiumHatchingPotionsCategory.items = _(content.hatchingPotions)
.values()
.filter(hp => hp.limited && hp.canBuy())
.map(premiumHatchingPotion => {
return {
key: premiumHatchingPotion.key,
text: premiumHatchingPotion.text(language),
notes: `${premiumHatchingPotion.notes(language)} ${premiumHatchingPotion._addlNotes(language)}`,
class: `Pet_HatchingPotion_${premiumHatchingPotion.key}`,
value: premiumHatchingPotion.value,
locked: false,
currency: 'gems',
purchaseType: 'hatchingPotions',
};
}).sortBy('key').value();
categories.push(premiumHatchingPotionsCategory);
let foodCategory = {
identifier: 'food',
text: i18n.t('food', language),
notes: i18n.t('dropsExplanation', language),
};
foodCategory.items = _(content.food)
.values()
.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',
};
}).sortBy('key').value();
categories.push(foodCategory);
return categories;
};
shops.getQuestShopCategories = function getQuestShopCategories (user, language) {
let categories = [];
_.each(content.userCanOwnQuestCategories, type => {
let category = {
identifier: type,
text: i18n.t(`${type}Quests`, 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),
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_${quest.key}_locked` : `inventory_quest_scroll_${quest.key}`,
purchaseType: 'quests',
};
}).value();
categories.push(category);
});
return categories;
};
shops.getTimeTravelersCategories = function getTimeTravelersCategories (user, language) {
let categories = [];
let stable = {pets: 'Pet-', mounts: 'Mount_Head_'};
for (let type in stable) {
if (stable.hasOwnProperty(type)) {
let category = {
identifier: type,
text: i18n.t(type, language),
items: [],
};
for (let key in content.timeTravelStable[type]) {
if (content.timeTravelStable[type].hasOwnProperty(key)) {
if (!user.items[type][key]) {
let item = {
key,
text: content.timeTravelStable[type][key](language),
class: stable[type] + key,
type,
purchaseType: type,
value: 1,
notes: '',
locked: false,
currency: 'hourglasses',
};
category.items.push(item);
}
}
}
if (category.items.length > 0) {
categories.push(category);
}
}
}
let sets = content.timeTravelerStore(user.items.gear.owned);
for (let setKey in sets) {
if (sets.hasOwnProperty(setKey)) {
let set = sets[setKey];
let category = {
identifier: set.key,
text: set.text(language),
purchaseAll: true,
};
category.items = _.map(set.items, item => {
return {
key: item.key,
text: item.text(language),
notes: item.notes(language),
type: item.type,
purchaseType: 'gear',
value: 1,
locked: false,
currency: 'hourglasses',
class: `shop_${item.key}`,
};
});
if (category.items.length > 0) {
categories.push(category);
}
}
}
return categories;
};
// To switch seasons/available inventory, edit the availableSets object to whatever should be sold.
// let availableSets = {
// setKey: i18n.t('setTranslationString', language),
// };
shops.getSeasonalShopCategories = function getSeasonalShopCategories (user, language) {
let availableSets = {
};
let categories = [];
let flatGearArray = _.toArray(content.gear.flat);
for (let key in availableSets) {
if (availableSets.hasOwnProperty(key)) {
let category = {
identifier: key,
text: availableSets[key],
};
category.items = _(flatGearArray).filter((gear) => {
if (gear.index !== key) {
return false;
}
return user.items.gear.owned[gear.key] === undefined;
}).where({index: key}).map(gear => {
return {
key: gear.key,
text: gear.text(language),
notes: gear.notes(language),
value: 1,
type: gear.type,
specialClass: gear.specialClass,
locked: false,
currency: 'gems',
purchaseType: 'gear',
};
}).value();
if (category.items.length > 0) {
categories.push(category);
}
}
}
return categories;
};
module.exports = shops;

View File

@@ -0,0 +1,14 @@
/*
Silver amount from their money
*/
// TODO move to client
module.exports = function silver (num) {
if (num) {
let centCount = Math.floor((num - Math.floor(num)) * 100);
return `0${centCount}`.slice(-2);
} else {
return '00';
}
};

View File

@@ -0,0 +1,4 @@
module.exports = function splitWhitespace (s) {
return s.split(' ');
};

View File

@@ -0,0 +1,28 @@
import _ from 'lodash';
import content from '../content/index';
import * as statHelpers from '../statHelpers';
module.exports = function statsComputed (user) {
let paths = ['stats', 'stats.buffs', 'items.gear.equipped.weapon', 'items.gear.equipped.armor',
'items.gear.equipped.head', 'items.gear.equipped.shield'];
let computed = _.reduce(['per', 'con', 'str', 'int'], (m, stat) => {
m[stat] = _.reduce(paths, (m2, path) => {
let val = _.get(user, path);
let item = content.gear.flat[val];
if (!item) item = {};
if (!item[stat]) {
item[stat] = 0;
} else {
item[stat] = Number(item[stat]);
}
let thisMultiplier = item.klass === user.stats.class || item.specialClass === user.stats.class ? 1.5 : 1;
let thisReturn = path.indexOf('items.gear') !== -1 ? item[stat] * thisMultiplier : Number(val[stat]);
return m2 + thisReturn || 0;
}, 0);
m[stat] += Math.floor(statHelpers.capByLevel(user.stats.lvl) / 2);
return m;
}, {});
computed.maxMP = computed.int * 2 + 30;
return computed;
};

View File

@@ -0,0 +1,80 @@
import {
shouldDo,
} from '../cron';
/*
Task classes given everything about the class
*/
// TODO move to the client
module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCron = Number(new Date()), showCompleted = false, main = false) {
if (!task) {
return '';
}
let type = task.type;
let classes = task.type;
let completed = task.completed;
let value = task.value;
let priority = task.priority;
if (main && !task._editing) {
for (let filter in filters) {
let enabled = filters[filter];
if (!task.tags) task.tags = [];
if (enabled && task.tags.indexOf(filter) === -1) {
return 'hidden';
}
}
}
classes = task.type;
if (task._editing) {
classes += ' beingEdited';
}
if (type === 'todo' || type === 'daily') {
if (completed || (type === 'daily' && !shouldDo(Number(new Date()), task, { // eslint-disable-line no-extra-parens
dayStart,
}))) {
classes += ' completed';
} else {
classes += ' uncompleted';
}
} else if (type === 'habit') {
if (task.down && task.up) {
classes += ' habit-wide';
}
if (!task.down && !task.up) {
classes += ' habit-narrow';
}
}
if (priority === 0.1) {
classes += ' difficulty-trivial';
} else if (priority === 1) {
classes += ' difficulty-easy';
} else if (priority === 1.5) {
classes += ' difficulty-medium';
} else if (priority === 2) {
classes += ' difficulty-hard';
}
if (value < -20) {
classes += ' color-worst';
} else if (value < -10) {
classes += ' color-worse';
} else if (value < -1) {
classes += ' color-bad';
} else if (value < 1) {
classes += ' color-neutral';
} else if (value < 5) {
classes += ' color-good';
} else if (value < 10) {
classes += ' color-better';
} else {
classes += ' color-best';
}
return classes;
};

View File

@@ -0,0 +1,74 @@
import { v4 as uuid } from 'uuid';
import _ from 'lodash';
import moment from 'moment';
// 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
// TODO move to client code?
const tasksTypes = ['habit', 'daily', 'todo', 'reward'];
module.exports = function taskDefaults (task = {}) {
if (!task.type || tasksTypes.indexOf(task.type) === -1) {
task.type = 'habit';
}
let defaultId = uuid();
let defaults = {
_id: defaultId,
text: task._id || defaultId,
notes: '',
tags: [],
value: task.type === 'reward' ? 10 : 0,
priority: 1,
challenge: {},
reminders: [],
attribute: 'str',
createdAt: new Date(), // TODO these are going to be overwritten by the server...
updatedAt: new Date(),
};
_.defaults(task, defaults);
if (task.type === 'habit' || task.type === 'daily') {
_.defaults(task, {
history: [],
});
}
if (task.type === 'todo' || task.type === 'daily') {
_.defaults(task, {
completed: false,
collapseChecklist: false,
checklist: [],
});
}
if (task.type === 'habit') {
_.defaults(task, {
up: true,
down: true,
});
}
if (task.type === 'daily') {
_.defaults(task, {
streak: 0,
repeat: {
m: true,
t: true,
w: true,
th: true,
f: true,
s: true,
su: true,
},
startDate: moment().startOf('day').toDate(),
everyX: 1,
frequency: 'weekly',
});
}
return task;
};

View File

@@ -0,0 +1,31 @@
import _ from 'lodash';
import content from '../content/index';
// Return the list of gear items available for purchase
let sortOrder = _.reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;
return accumulator;
}, {});
module.exports = function updateStore (user) {
let changes = [];
_.each(content.gearTypes, (type) => {
let found = _.find(content.gear.tree[type][user.stats.class], (item) => {
return !user.items.gear.owned[item.key];
});
if (found) changes.push(found);
});
changes = changes.concat(_.filter(content.gear.flat, (val) => {
if (['special', 'mystery', 'armoire'].indexOf(val.klass) !== -1 && !user.items.gear.owned[val.key] && (val.canOwn ? val.canOwn(user) : false)) {
return true;
} else {
return false;
}
}));
return _.sortBy(changes, (change) => sortOrder[change.type]);
};

View File

@@ -0,0 +1,4 @@
import uuid from 'uuid';
// TODO remove this file completely
module.exports = uuid.v4;