diff --git a/.eslintrc b/.eslintrc
index 7613632120..870626d394 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -32,7 +32,6 @@
"no-octal": 2,
"no-process-env": 2,
"no-proto": 2,
- "no-implied-eval": 2,
"yoda": 2,
"wrap-iife": 2,
"radix": 2,
@@ -59,7 +58,6 @@
"no-path-concat": 2,
"arrow-spacing": 2,
"constructor-super": 2,
- "generator-star-spacing": 2,
"no-arrow-condition": 2,
"no-class-assign": 2,
"no-const-assign": 2,
diff --git a/common/script/index.js b/common/script/index.js
index fb7d17df44..89cc2f2ceb 100644
--- a/common/script/index.js
+++ b/common/script/index.js
@@ -12,6 +12,8 @@ import {
} from './constants';
import * as statHelpers from './statHelpers';
+import importedLibs from './libs';
+
var $w, preenHistory, sortOrder,
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
@@ -29,9 +31,7 @@ api.maxHealth = MAX_HEALTH;
api.tnl = statHelpers.toNextLevel;
api.diminishingReturns = statHelpers.diminishingReturns;
-$w = api.$w = function(s) {
- return s.split(' ');
-};
+$w = api.$w = importedLibs.splitWhitespace;
api.dotSet = function(obj, path, val) {
var arr;
@@ -55,27 +55,9 @@ api.dotGet = function(obj, path) {
};
-/*
- 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:
- */
+api.refPush = importedLibs.refPush;
-api.refPush = function(reflist, item, prune) {
- if (prune == null) {
- prune = 0;
- }
- item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1;
- if (!(item.id && !reflist[item.id])) {
- item.id = api.uuid();
- }
- return reflist[item.id] = item;
-};
-
-api.planGemLimits = {
- convRate: 20,
- convCap: 25
-};
+api.planGemLimits = importedLibs.planGemLimits;
/*
Preen history for users with > 7 history entries
@@ -122,13 +104,7 @@ preenHistory = function(history) {
Preen 3-day past-completed To-Dos from Angular & mobile app
*/
-api.preenTodos = function(tasks) {
- return _.filter(tasks, function(t) {
- return !t.completed || (t.challenge && t.challenge.id) || moment(t.dateCompleted).isAfter(moment().subtract({
- days: 3
- }));
- });
-};
+api.preenTodos = importedLibs.preenTodos;
/*
Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there
@@ -180,14 +156,7 @@ Misc Helpers
------------------------------------------------------
*/
-api.uuid = function() {
- return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
- var r, v;
- r = Math.random() * 16 | 0;
- v = (c === "x" ? r : r & 0x3 | 0x8);
- return v.toString(16);
- });
-};
+api.uuid = importedLibs.uuid;
api.countExists = function(items) {
return _.reduce(items, (function(m, v) {
@@ -195,73 +164,7 @@ api.countExists = function(items) {
}), 0);
};
-
-/*
-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
- */
-
-api.taskDefaults = function(task) {
- var defaults, ref, ref1, ref2;
- if (task == null) {
- task = {};
- }
- if (!(task.type && ((ref = task.type) === 'habit' || ref === 'daily' || ref === 'todo' || ref === 'reward'))) {
- task.type = 'habit';
- }
- defaults = {
- id: api.uuid(),
- text: task.id != null ? task.id : '',
- notes: '',
- priority: 1,
- challenge: {},
- attribute: 'str',
- dateCreated: new Date()
- };
- _.defaults(task, defaults);
- if (task.type === 'habit') {
- _.defaults(task, {
- up: true,
- down: true
- });
- }
- if ((ref1 = task.type) === 'habit' || ref1 === 'daily') {
- _.defaults(task, {
- history: []
- });
- }
- if ((ref2 = task.type) === 'daily' || ref2 === 'todo') {
- _.defaults(task, {
- completed: false
- });
- }
- if (task.type === 'daily') {
- _.defaults(task, {
- streak: 0,
- repeat: {
- su: true,
- m: true,
- t: true,
- w: true,
- th: true,
- f: true,
- s: true
- }
- }, {
- startDate: new Date(),
- everyX: 1,
- frequency: 'weekly'
- });
- }
- task._id = task.id;
- if (task.value == null) {
- task.value = task.type === 'reward' ? 10 : 0;
- }
- if (!_.isNumber(task.priority)) {
- task.priority = 1;
- }
- return task;
-};
+api.taskDefaults = importedLibs.taskDefaults;
api.percent = function(x, y, dir) {
var roundFn;
@@ -522,1533 +425,51 @@ api.wrap = function(user, main) {
user._wrapped = true;
if (main) {
user.ops = {
- update: function(req, cb) {
- _.each(req.body, function(v, k) {
- user.fns.dotSet(k, v);
- return true;
- });
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- sleep: function(req, cb) {
- user.preferences.sleep = !user.preferences.sleep;
- return typeof cb === "function" ? cb(null, {}) : void 0;
- },
- revive: function(req, cb, analytics) {
- var analyticsData, base, cl, gearOwned, item, losableItems, lostItem, lostStat;
- if (!(user.stats.hp <= 0)) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Cannot revive if not dead"
- }) : void 0;
- }
- _.merge(user.stats, {
- hp: 50,
- exp: 0,
- gp: 0
- });
- if (user.stats.lvl > 1) {
- user.stats.lvl--;
- }
- lostStat = user.fns.randomVal(_.reduce(['str', 'con', 'per', 'int'], (function(m, k) {
- if (user.stats[k]) {
- m[k] = k;
- }
- return m;
- }), {}));
- if (lostStat) {
- user.stats[lostStat]--;
- }
- cl = user.stats["class"];
- gearOwned = (typeof (base = user.items.gear.owned).toObject === "function" ? base.toObject() : void 0) || user.items.gear.owned;
- losableItems = {};
- _.each(gearOwned, function(v, k) {
- var itm;
- if (v) {
- itm = content.gear.flat['' + k];
- if (itm) {
- if ((itm.value > 0 || k === 'weapon_warrior_0') && (itm.klass === cl || (itm.klass === 'special' && (!itm.specialClass || itm.specialClass === cl)) || itm.klass === 'armoire')) {
- return losableItems['' + k] = '' + k;
- }
- }
- }
- });
- lostItem = user.fns.randomVal(losableItems);
- if (item = content.gear.flat[lostItem]) {
- user.items.gear.owned[lostItem] = false;
- if (user.items.gear.equipped[item.type] === lostItem) {
- user.items.gear.equipped[item.type] = item.type + "_base_0";
- }
- if (user.items.gear.costume[item.type] === lostItem) {
- user.items.gear.costume[item.type] = item.type + "_base_0";
- }
- }
- if (typeof user.markModified === "function") {
- user.markModified('items.gear');
- }
- analyticsData = {
- uuid: user._id,
- lostItem: lostItem,
- gaLabel: lostItem,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('Death', analyticsData);
- }
- return typeof cb === "function" ? cb((item ? {
- code: 200,
- message: i18n.t('messageLostItem', {
- itemText: item.text(req.language)
- }, req.language)
- } : null), user) : void 0;
- },
- reset: function(req, cb) {
- var gear;
- user.habits = [];
- user.dailys = [];
- user.todos = [];
- user.rewards = [];
- user.stats.hp = 50;
- user.stats.lvl = 1;
- user.stats.gp = 0;
- user.stats.exp = 0;
- gear = user.items.gear;
- _.each(['equipped', 'costume'], function(type) {
- gear[type].armor = 'armor_base_0';
- gear[type].weapon = 'weapon_base_0';
- gear[type].head = 'head_base_0';
- return gear[type].shield = 'shield_base_0';
- });
- if (typeof gear.owned === 'undefined') {
- gear.owned = {};
- }
- _.each(gear.owned, function(v, k) {
- if (gear.owned[k]) {
- gear.owned[k] = false;
- }
- return true;
- });
- gear.owned.weapon_warrior_0 = true;
- if (typeof user.markModified === "function") {
- user.markModified('items.gear.owned');
- }
- user.preferences.costume = false;
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- reroll: function(req, cb, analytics) {
- var analyticsData;
- if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- user.balance--;
- _.each(user.tasks, function(task) {
- if (task.type !== 'reward') {
- return task.value = 0;
- }
- });
- user.stats.hp = 50;
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('Fortify Potion', analyticsData);
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- rebirth: function(req, cb, analytics) {
- var analyticsData, flags, gear, lvl, stats;
- if (user.balance < 2 && user.stats.lvl < api.maxLevel) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- analyticsData = {
- uuid: user._id,
- category: 'behavior'
- };
- if (user.stats.lvl < api.maxLevel) {
- user.balance -= 2;
- analyticsData.acquireMethod = 'Gems';
- analyticsData.gemCost = 8;
- } else {
- analyticsData.gemCost = 0;
- analyticsData.acquireMethod = '> 100';
- }
- if (analytics != null) {
- analytics.track('Rebirth', analyticsData);
- }
- lvl = api.capByLevel(user.stats.lvl);
- _.each(user.tasks, function(task) {
- if (task.type !== 'reward') {
- task.value = 0;
- }
- if (task.type === 'daily') {
- return task.streak = 0;
- }
- });
- stats = user.stats;
- stats.buffs = {};
- stats.hp = 50;
- stats.lvl = 1;
- stats["class"] = 'warrior';
- _.each(['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'], function(value) {
- return stats[value] = 0;
- });
- // TODO during refactoring: move all gear code from rebirth() to its own function and then call it in reset() as well
- gear = user.items.gear;
- _.each(['equipped', 'costume'], function(type) {
- gear[type] = {};
- gear[type].armor = 'armor_base_0';
- gear[type].weapon = 'weapon_warrior_0';
- gear[type].head = 'head_base_0';
- return gear[type].shield = 'shield_base_0';
- });
- if (user.items.currentPet) {
- user.ops.equip({
- params: {
- type: 'pet',
- key: user.items.currentPet
- }
- });
- }
- if (user.items.currentMount) {
- user.ops.equip({
- params: {
- type: 'mount',
- key: user.items.currentMount
- }
- });
- }
- _.each(gear.owned, function(v, k) {
- if (gear.owned[k] && content.gear.flat[k].value) {
- gear.owned[k] = false;
- return true;
- }
- });
- gear.owned.weapon_warrior_0 = true;
- if (typeof user.markModified === "function") {
- user.markModified('items.gear.owned');
- }
- user.preferences.costume = false;
- flags = user.flags;
- if (!user.achievements.beastMaster) {
- flags.rebirthEnabled = false;
- }
- flags.itemsEnabled = false;
- flags.dropsEnabled = false;
- flags.classSelected = false;
- flags.levelDrops = {};
- if (!user.achievements.rebirths) {
- user.achievements.rebirths = 1;
- user.achievements.rebirthLevel = lvl;
- } else if (lvl > user.achievements.rebirthLevel || lvl === 100) {
- user.achievements.rebirths++;
- user.achievements.rebirthLevel = lvl;
- }
- user.stats.buffs = {};
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- allocateNow: function(req, cb) {
- _.times(user.stats.points, user.fns.autoAllocate);
- user.stats.points = 0;
- if (typeof user.markModified === "function") {
- user.markModified('stats');
- }
- return typeof cb === "function" ? cb(null, user.stats) : void 0;
- },
- clearCompleted: function(req, cb) {
- _.remove(user.todos, function(t) {
- var ref;
- return t.completed && !((ref = t.challenge) != null ? ref.id : void 0);
- });
- if (typeof user.markModified === "function") {
- user.markModified('todos');
- }
- return typeof cb === "function" ? cb(null, user.todos) : void 0;
- },
- sortTask: function(req, cb) {
- var from, id, movedTask, preenedTasks, ref, task, tasks, to;
- id = req.params.id;
- ref = req.query, to = ref.to, from = ref.from;
- task = user.tasks[id];
- if (!task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
- }
- if (!((to != null) && (from != null))) {
- return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0;
- }
- tasks = user[task.type + "s"];
- if (task.type === 'todo' && tasks[from] !== task) {
- preenedTasks = api.preenTodos(tasks);
- if (to !== -1) {
- to = tasks.indexOf(preenedTasks[to]);
- }
- from = tasks.indexOf(preenedTasks[from]);
- }
- if (tasks[from] !== task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
- }
- movedTask = tasks.splice(from, 1)[0];
- if (to === -1) {
- tasks.push(movedTask);
- } else {
- tasks.splice(to, 0, movedTask);
- }
- return typeof cb === "function" ? cb(null, tasks) : void 0;
- },
- updateTask: function(req, cb) {
- var ref, task;
- if (!(task = user.tasks[(ref = req.params) != null ? ref.id : void 0])) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
- }
- _.merge(task, _.omit(req.body, ['checklist', 'id', 'type']));
- if (req.body.checklist) {
- task.checklist = req.body.checklist;
- }
- if (typeof task.markModified === "function") {
- task.markModified('tags');
- }
- return typeof cb === "function" ? cb(null, task) : void 0;
- },
- deleteTask: function(req, cb) {
- var i, ref, task;
- task = user.tasks[(ref = req.params) != null ? ref.id : void 0];
- if (!task) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTaskNotFound', req.language)
- }) : void 0;
- }
- i = user[task.type + "s"].indexOf(task);
- if (~i) {
- user[task.type + "s"].splice(i, 1);
- }
- return typeof cb === "function" ? cb(null, {}) : void 0;
- },
- addTask: function(req, cb) {
- var task;
- task = api.taskDefaults(req.body);
- if (user.tasks[task.id] != null) {
- return typeof cb === "function" ? cb({
- code: 409,
- message: i18n.t('messageDuplicateTaskID', req.language)
- }) : void 0;
- }
- user[task.type + "s"].unshift(task);
- if (user.preferences.newTaskEdit) {
- task._editing = true;
- }
- if (user.preferences.tagsCollapsed) {
- task._tags = true;
- }
- if (!user.preferences.advancedCollapsed) {
- task._advanced = true;
- }
- if (typeof cb === "function") {
- cb(null, task);
- }
- return task;
- },
- addTag: function(req, cb) {
- if (user.tags == null) {
- user.tags = [];
- }
- user.tags.push({
- name: req.body.name,
- id: req.body.id || api.uuid()
- });
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
- },
- sortTag: function(req, cb) {
- var from, ref, to;
- ref = req.query, to = ref.to, from = ref.from;
- if (!((to != null) && (from != null))) {
- return typeof cb === "function" ? cb('?to=__&from=__ are required') : void 0;
- }
- user.tags.splice(to, 0, user.tags.splice(from, 1)[0]);
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
- },
- getTags: function(req, cb) {
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
- },
- getTag: function(req, cb) {
- var i, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
- });
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
- }
- return typeof cb === "function" ? cb(null, user.tags[i]) : void 0;
- },
- updateTag: function(req, cb) {
- var i, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
- });
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
- }
- user.tags[i].name = req.body.name;
- return typeof cb === "function" ? cb(null, user.tags[i]) : void 0;
- },
- deleteTag: function(req, cb) {
- var i, tag, tid;
- tid = req.params.id;
- i = _.findIndex(user.tags, {
- id: tid
- });
- if (!~i) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageTagNotFound', req.language)
- }) : void 0;
- }
- tag = user.tags[i];
- delete user.filters[tag.id];
- user.tags.splice(i, 1);
- _.each(user.tasks, function(task) {
- return delete task.tags[tag.id];
- });
- _.each(['habits', 'dailys', 'todos', 'rewards'], function(type) {
- return typeof user.markModified === "function" ? user.markModified(type) : void 0;
- });
- return typeof cb === "function" ? cb(null, user.tags) : void 0;
- },
- addWebhook: function(req, cb) {
- var wh;
- wh = user.preferences.webhooks;
- api.refPush(wh, {
- url: req.body.url,
- enabled: req.body.enabled || true,
- id: req.body.id
- });
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
- }
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
- },
- updateWebhook: function(req, cb) {
- _.merge(user.preferences.webhooks[req.params.id], req.body);
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
- }
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
- },
- deleteWebhook: function(req, cb) {
- delete user.preferences.webhooks[req.params.id];
- if (typeof user.markModified === "function") {
- user.markModified('preferences.webhooks');
- }
- return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
- },
- addPushDevice: function(req, cb) {
- var i, item, pd;
- if (!user.pushDevices) {
- user.pushDevices = [];
- }
- pd = user.pushDevices;
- item = {
- regId: req.body.regId,
- type: req.body.type
- };
- i = _.findIndex(pd, {
- regId: item.regId
- });
- if (i === -1) {
- pd.push(item);
- }
- return typeof cb === "function" ? cb(null, user.pushDevices) : void 0;
- },
- clearPMs: function(req, cb) {
- user.inbox.messages = {};
- if (typeof user.markModified === "function") {
- user.markModified('inbox.messages');
- }
- return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
- },
- deletePM: function(req, cb) {
- delete user.inbox.messages[req.params.id];
- if (typeof user.markModified === "function") {
- user.markModified('inbox.messages.' + req.params.id);
- }
- return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
- },
- blockUser: function(req, cb) {
- var i;
- i = user.inbox.blocks.indexOf(req.params.uuid);
- if (~i) {
- user.inbox.blocks.splice(i, 1);
- } else {
- user.inbox.blocks.push(req.params.uuid);
- }
- if (typeof user.markModified === "function") {
- user.markModified('inbox.blocks');
- }
- return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0;
- },
- feed: function(req, cb) {
- var egg, eggText, evolve, food, message, pet, petDisplayName, potion, potionText, ref, ref1, ref2, userPets;
- ref = req.params, pet = ref.pet, food = ref.food;
- food = content.food[food];
- ref1 = pet.split('-'), egg = ref1[0], potion = ref1[1];
- userPets = user.items.pets;
- potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion;
- eggText = content.eggs[egg] ? content.eggs[egg].text() : egg;
- petDisplayName = i18n.t('petName', {
- potion: potionText,
- egg: eggText
- });
- if (!userPets[pet]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messagePetNotFound', req.language)
- }) : void 0;
- }
- if (!((ref2 = user.items.food) != null ? ref2[food.key] : void 0)) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: i18n.t('messageFoodNotFound', req.language)
- }) : void 0;
- }
- if (content.specialPets[pet]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageCannotFeedPet', req.language)
- }) : void 0;
- }
- if (user.items.mounts[pet]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageAlreadyMount', req.language)
- }) : void 0;
- }
- message = '';
- evolve = function() {
- userPets[pet] = -1;
- user.items.mounts[pet] = true;
- if (pet === user.items.currentPet) {
- user.items.currentPet = "";
- }
- return message = i18n.t('messageEvolve', {
- egg: petDisplayName
- }, req.language);
- };
- if (food.key === 'Saddle') {
- evolve();
- } else {
- if (food.target === potion || content.hatchingPotions[potion].premium) {
- userPets[pet] += 5;
- message = i18n.t('messageLikesFood', {
- egg: petDisplayName,
- foodText: food.text(req.language)
- }, req.language);
- } else {
- userPets[pet] += 2;
- message = i18n.t('messageDontEnjoyFood', {
- egg: petDisplayName,
- foodText: food.text(req.language)
- }, req.language);
- }
- if (userPets[pet] >= 50 && !user.items.mounts[pet]) {
- evolve();
- }
- }
- user.items.food[food.key]--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, {
- value: userPets[pet]
- }) : void 0;
- },
- buySpecialSpell: function(req, cb) {
- var base, item, key, message;
- key = req.params.key;
- item = content.special[key];
- if (user.stats.gp < item.value) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- user.stats.gp -= item.value;
- if ((base = user.items.special)[key] == null) {
- base[key] = 0;
- }
- user.items.special[key]++;
- if (typeof user.markModified === "function") {
- user.markModified('items.special');
- }
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, _.pick(user, $w('items stats'))) : void 0;
- },
- purchase: function(req, cb, analytics) {
- var analyticsData, convCap, convRate, item, key, price, ref, ref1, ref2, ref3, type;
- ref = req.params, type = ref.type, key = ref.key;
- if (type === 'gems' && key === 'gem') {
- ref1 = api.planGemLimits, convRate = ref1.convRate, convCap = ref1.convCap;
- convCap += user.purchased.plan.consecutive.gemCapExtra;
- if (!((ref2 = user.purchased) != null ? (ref3 = ref2.plan) != null ? ref3.customerId : void 0 : void 0)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "Must subscribe to purchase gems with GP"
- }, req) : void 0;
- }
- if (!(user.stats.gp >= convRate)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "Not enough Gold"
- }) : void 0;
- }
- if (user.purchased.plan.gemsBought >= convCap) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "You've reached the Gold=>Gem conversion cap (" + convCap + ") for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month."
- }) : void 0;
- }
- user.balance += .25;
- user.purchased.plan.gemsBought++;
- user.stats.gp -= convRate;
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- acquireMethod: 'Gold',
- goldCost: convRate,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('purchase gems', analyticsData);
- }
- return typeof cb === "function" ? cb({
- code: 200,
- message: "+1 Gem"
- }, _.pick(user, $w('stats balance'))) : void 0;
- }
- if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'gear') {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":type must be in [eggs,hatchingPotions,food,quests,gear]"
- }, req) : void 0;
- }
- if (type === 'gear') {
- item = content.gear.flat[key];
- if (user.items.gear.owned[key]) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('alreadyHave', req.language)
- }) : void 0;
- }
- price = (item.twoHanded || item.gearSet === 'animal' ? 2 : 1) / 4;
- } else {
- item = content[type][key];
- price = item.value / 4;
- }
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":key not found for Content." + type
- }, req) : void 0;
- }
- if (!item.canBuy(user)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageNotAvailable', req.language)
- }) : void 0;
- }
- if ((user.balance < price) || !user.balance) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- user.balance -= price;
- if (type === 'gear') {
- user.items.gear.owned[key] = true;
- } else {
- if (!(user.items[type][key] > 0)) {
- user.items[type][key] = 0;
- }
- user.items[type][key]++;
- }
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- itemType: 'Market',
- acquireMethod: 'Gems',
- gemCost: item.value,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- return typeof cb === "function" ? cb(null, _.pick(user, $w('items balance'))) : void 0;
- },
- releasePets: function(req, cb, analytics) {
- var analyticsData, pet;
- if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- } else {
- user.balance -= 1;
- for (pet in content.pets) {
- user.items.pets[pet] = 0;
- }
- if (!user.achievements.beastMasterCount) {
- user.achievements.beastMasterCount = 0;
- }
- user.achievements.beastMasterCount++;
- user.items.currentPet = "";
- }
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('release pets', analyticsData);
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- releaseMounts: function(req, cb, analytics) {
- var analyticsData, mount;
- if (user.balance < 1) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- } else {
- user.balance -= 1;
- user.items.currentMount = "";
- for (mount in content.pets) {
- user.items.mounts[mount] = null;
- }
- if (!user.achievements.mountMasterCount) {
- user.achievements.mountMasterCount = 0;
- }
- user.achievements.mountMasterCount++;
- }
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 4,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('release mounts', analyticsData);
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- releaseBoth: function(req, cb) {
- var analyticsData, animal, giveTriadBingo;
- if (user.balance < 1.5 && !user.achievements.triadBingo) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- } else {
- giveTriadBingo = true;
- if (!user.achievements.triadBingo) {
- analyticsData = {
- uuid: user._id,
- acquireMethod: 'Gems',
- gemCost: 6,
- category: 'behavior'
- };
- if (typeof analytics !== "undefined" && analytics !== null) {
- analytics.track('release pets & mounts', analyticsData);
- }
- user.balance -= 1.5;
- }
- user.items.currentMount = "";
- user.items.currentPet = "";
- for (animal in content.pets) {
- if (user.items.pets[animal] === -1) {
- giveTriadBingo = false;
- }
- user.items.pets[animal] = 0;
- user.items.mounts[animal] = null;
- }
- if (!user.achievements.beastMasterCount) {
- user.achievements.beastMasterCount = 0;
- }
- user.achievements.beastMasterCount++;
- if (!user.achievements.mountMasterCount) {
- user.achievements.mountMasterCount = 0;
- }
- user.achievements.mountMasterCount++;
- if (giveTriadBingo) {
- if (!user.achievements.triadBingoCount) {
- user.achievements.triadBingoCount = 0;
- }
- user.achievements.triadBingoCount++;
- }
- }
- return typeof cb === "function" ? cb(null, user) : void 0;
- },
- buy: function(req, cb, analytics) {
- var analyticsData, armoireExp, armoireResp, armoireResult, base, buyResp, drop, eligibleEquipment, item, key, message, name;
- key = req.params.key;
- item = key === 'potion' ? content.potion : key === 'armoire' ? content.armoire : content.gear.flat[key];
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Item '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
- }
- if (user.stats.gp < item.value) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- if ((item.canOwn != null) && !item.canOwn(user)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: "You can't buy this item"
- }) : void 0;
- }
- armoireResp = void 0;
- if (item.key === 'potion') {
- user.stats.hp += 15;
- if (user.stats.hp > 50) {
- user.stats.hp = 50;
- }
- } else if (item.key === 'armoire') {
- armoireResult = user.fns.predictableRandom(user.stats.gp);
- eligibleEquipment = _.filter(content.gear.flat, (function(i) {
- return i.klass === 'armoire' && !user.items.gear.owned[i.key];
- }));
- if (!_.isEmpty(eligibleEquipment) && (armoireResult < .6 || !user.flags.armoireOpened)) {
- eligibleEquipment.sort();
- drop = user.fns.randomVal(eligibleEquipment);
- user.items.gear.owned[drop.key] = true;
- user.flags.armoireOpened = true;
- message = i18n.t('armoireEquipment', {
- image: '',
- dropText: drop.text(req.language)
- }, req.language);
- if (api.count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
- user.flags.armoireEmpty = true;
- }
- armoireResp = {
- type: "gear",
- dropKey: drop.key,
- dropText: drop.text(req.language)
- };
- } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < .8) || armoireResult < .5) {
- drop = user.fns.randomVal(_.where(content.food, {
- canDrop: true
- }));
- if ((base = user.items.food)[name = drop.key] == null) {
- base[name] = 0;
- }
- user.items.food[drop.key] += 1;
- message = i18n.t('armoireFood', {
- image: '',
- dropArticle: drop.article,
- dropText: drop.text(req.language)
- }, req.language);
- armoireResp = {
- type: "food",
- dropKey: drop.key,
- dropArticle: drop.article,
- dropText: drop.text(req.language)
- };
- } else {
- armoireExp = Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10);
- user.stats.exp += armoireExp;
- message = i18n.t('armoireExp', req.language);
- armoireResp = {
- "type": "experience",
- "value": armoireExp
- };
- }
- } else {
- if (user.preferences.autoEquip) {
- user.items.gear.equipped[item.type] = item.key;
- message = user.fns.handleTwoHanded(item, null, req);
- }
- user.items.gear.owned[item.key] = true;
- if (message == null) {
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- }
- if (item.last) {
- user.fns.ultimateGear();
- }
- }
- user.stats.gp -= item.value;
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- acquireMethod: 'Gold',
- goldCost: item.value,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- buyResp = _.pick(user, $w('items achievements stats flags'));
- if (armoireResp) {
- buyResp["armoire"] = armoireResp;
- }
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, buyResp) : void 0;
- },
- buyQuest: function(req, cb, analytics) {
- var analyticsData, base, item, key, message, name;
- key = req.params.key;
- item = content.quests[key];
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Quest '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
- }
- if (!(item.category === 'gold' && item.goldValue)) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Quest '" + key + " is not a Gold-purchasable quest (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
- }) : void 0;
- }
- if (user.stats.gp < item.goldValue) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- message = i18n.t('messageBought', {
- itemText: item.text(req.language)
- }, req.language);
- if ((base = user.items.quests)[name = item.key] == null) {
- base[name] = 0;
- }
- user.items.quests[item.key] += 1;
- user.stats.gp -= item.goldValue;
- analyticsData = {
- uuid: user._id,
- itemKey: item.key,
- itemType: 'Market',
- goldCost: item.goldValue,
- acquireMethod: 'Gold',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- return typeof cb === "function" ? cb({
- code: 200,
- message: message
- }, user.items.quests) : void 0;
- },
- buyMysterySet: function(req, cb, analytics) {
- var mysterySet, ref;
- if (!(user.purchased.plan.consecutive.trinkets > 0)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughHourglasses', req.language)
- }) : void 0;
- }
- mysterySet = (ref = content.timeTravelerStore(user.items.gear.owned)) != null ? ref[req.params.key] : void 0;
- if ((typeof window !== "undefined" && window !== null ? window.confirm : void 0) != null) {
- if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) {
- return;
- }
- }
- if (!mysterySet) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: "Mystery set not found, or set already owned"
- }) : void 0;
- }
- _.each(mysterySet.items, function(i) {
- var analyticsData;
- user.items.gear.owned[i.key] = true;
- analyticsData = {
- uuid: user._id,
- itemKey: i.key,
- itemType: 'Subscriber Gear',
- acquireMethod: 'Hourglass',
- category: 'behavior'
- };
- return analytics != null ? analytics.track('acquire item', analyticsData) : void 0;
- });
- user.purchased.plan.consecutive.trinkets--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('hourglassPurchaseSet', req.language)
- }, _.pick(user, $w('items purchased.plan.consecutive'))) : void 0;
- },
- hourglassPurchase: function(req, cb, analytics) {
- var analyticsData, key, ref, type;
- ref = req.params, type = ref.type, key = ref.key;
- if (!content.timeTravelStable[type]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))
- }) : void 0;
- }
- if (!_.contains(_.keys(content.timeTravelStable[type]), key)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t(type + 'NotAllowedHourglass', req.language)
- }) : void 0;
- }
- if (user.items[type][key]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t(type + 'AlreadyOwned', req.language)
- }) : void 0;
- }
- if (!(user.purchased.plan.consecutive.trinkets > 0)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('notEnoughHourglasses', req.language)
- }) : void 0;
- }
- user.purchased.plan.consecutive.trinkets--;
- if (type === 'pets') {
- user.items.pets[key] = 5;
- }
- if (type === 'mounts') {
- user.items.mounts[key] = true;
- }
- analyticsData = {
- uuid: user._id,
- itemKey: key,
- itemType: type,
- acquireMethod: 'Hourglass',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('hourglassPurchase', req.language)
- }, _.pick(user, $w('items purchased.plan.consecutive'))) : void 0;
- },
- sell: function(req, cb) {
- var key, ref, type;
- ref = req.params, key = ref.key, type = ref.type;
- if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":type not found. Must bes in [eggs, hatchingPotions, food]"
- }) : void 0;
- }
- if (!user.items[type][key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":key not found for user.items." + type
- }) : void 0;
- }
- user.items[type][key]--;
- user.stats.gp += content[type][key].value;
- return typeof cb === "function" ? cb(null, _.pick(user, $w('stats items'))) : void 0;
- },
- equip: function(req, cb) {
- var item, key, message, ref, type;
- ref = [req.params.type || 'equipped', req.params.key], type = ref[0], key = ref[1];
- switch (type) {
- case 'mount':
- if (!user.items.mounts[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this mount."
- }) : void 0;
- }
- user.items.currentMount = user.items.currentMount === key ? '' : key;
- break;
- case 'pet':
- if (!user.items.pets[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this pet."
- }) : void 0;
- }
- user.items.currentPet = user.items.currentPet === key ? '' : key;
- break;
- case 'costume':
- case 'equipped':
- item = content.gear.flat[key];
- if (!user.items.gear.owned[key]) {
- return typeof cb === "function" ? cb({
- code: 404,
- message: ":You do not own this gear."
- }) : void 0;
- }
- if (user.items.gear[type][item.type] === key) {
- user.items.gear[type][item.type] = item.type + "_base_0";
- message = i18n.t('messageUnEquipped', {
- itemText: item.text(req.language)
- }, req.language);
- } else {
- user.items.gear[type][item.type] = item.key;
- message = user.fns.handleTwoHanded(item, type, req);
- }
- if (typeof user.markModified === "function") {
- user.markModified("items.gear." + type);
- }
- }
- return typeof cb === "function" ? cb((message ? {
- code: 200,
- message: message
- } : null), user.items) : void 0;
- },
- hatch: function(req, cb) {
- var egg, hatchingPotion, pet, ref;
- ref = req.params, egg = ref.egg, hatchingPotion = ref.hatchingPotion;
- if (!(egg && hatchingPotion)) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Please specify query.egg & query.hatchingPotion"
- }) : void 0;
- }
- if (!(user.items.eggs[egg] > 0 && user.items.hatchingPotions[hatchingPotion] > 0)) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageMissingEggPotion', req.language)
- }) : void 0;
- }
- if (content.hatchingPotions[hatchingPotion].premium && !content.dropEggs[egg]) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageInvalidEggPotionCombo', req.language)
- }) : void 0;
- }
- pet = egg + "-" + hatchingPotion;
- if (user.items.pets[pet] && user.items.pets[pet] > 0) {
- return typeof cb === "function" ? cb({
- code: 403,
- message: i18n.t('messageAlreadyPet', req.language)
- }) : void 0;
- }
- user.items.pets[pet] = 5;
- user.items.eggs[egg]--;
- user.items.hatchingPotions[hatchingPotion]--;
- return typeof cb === "function" ? cb({
- code: 200,
- message: i18n.t('messageHatched', req.language)
- }, user.items) : void 0;
- },
- unlock: function(req, cb, analytics) {
- var alreadyOwns, analyticsData, cost, fullSet, k, path, split, v;
- path = req.query.path;
- fullSet = ~path.indexOf(",");
- cost = ~path.indexOf('background.') ? fullSet ? 3.75 : 1.75 : fullSet ? 1.25 : 0.5;
- alreadyOwns = !fullSet && user.fns.dotGet("purchased." + path) === true;
- if ((user.balance < cost || !user.balance) && !alreadyOwns) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- if (fullSet) {
- _.each(path.split(","), function(p) {
- if (~path.indexOf('gear.')) {
- user.fns.dotSet("" + p, true);
- true;
- } else {
-
- }
- user.fns.dotSet("purchased." + p, true);
- return true;
- });
- } else {
- if (alreadyOwns) {
- split = path.split('.');
- v = split.pop();
- k = split.join('.');
- if (k === 'background' && v === user.preferences.background) {
- v = '';
- }
- user.fns.dotSet("preferences." + k, v);
- return typeof cb === "function" ? cb(null, req) : void 0;
- }
- user.fns.dotSet("purchased." + path, true);
- }
- user.balance -= cost;
- if (~path.indexOf('gear.')) {
- if (typeof user.markModified === "function") {
- user.markModified('gear.owned');
- }
- } else {
- if (typeof user.markModified === "function") {
- user.markModified('purchased');
- }
- }
- analyticsData = {
- uuid: user._id,
- itemKey: path,
- itemType: 'customization',
- acquireMethod: 'Gems',
- gemCost: cost / .25,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('acquire item', analyticsData);
- }
- return typeof cb === "function" ? cb(null, _.pick(user, $w('purchased preferences items'))) : void 0;
- },
- changeClass: function(req, cb, analytics) {
- var analyticsData, klass, ref;
- klass = (ref = req.query) != null ? ref["class"] : void 0;
- if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') {
- analyticsData = {
- uuid: user._id,
- "class": klass,
- acquireMethod: 'Gems',
- gemCost: 3,
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('change class', analyticsData);
- }
- user.stats["class"] = klass;
- user.flags.classSelected = true;
- _.each(["weapon", "armor", "shield", "head"], function(type) {
- var foundKey;
- foundKey = false;
- _.findLast(user.items.gear.owned, function(v, k) {
- if (~k.indexOf(type + "_" + klass) && v === true) {
- return foundKey = k;
- }
- });
- user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : type + "_base_0";
- if (type === "weapon" || (type === "shield" && klass === "rogue")) {
- user.items.gear.owned[type + "_" + klass + "_0"] = true;
- }
- return true;
- });
- } else {
- if (user.preferences.disableClasses) {
- user.preferences.disableClasses = false;
- user.preferences.autoAllocate = false;
- } else {
- if (!(user.balance >= .75)) {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('notEnoughGems', req.language)
- }) : void 0;
- }
- user.balance -= .75;
- }
- _.merge(user.stats, {
- str: 0,
- con: 0,
- per: 0,
- int: 0,
- points: api.capByLevel(user.stats.lvl)
- });
- user.flags.classSelected = false;
- }
- return typeof cb === "function" ? cb(null, _.pick(user, $w('stats flags items preferences'))) : void 0;
- },
- disableClasses: function(req, cb) {
- user.stats["class"] = 'warrior';
- user.flags.classSelected = true;
- user.preferences.disableClasses = true;
- user.preferences.autoAllocate = true;
- user.stats.str = api.capByLevel(user.stats.lvl);
- user.stats.points = 0;
- return typeof cb === "function" ? cb(null, _.pick(user, $w('stats flags preferences'))) : void 0;
- },
- allocate: function(req, cb) {
- var stat;
- stat = req.query.stat || 'str';
- if (user.stats.points > 0) {
- user.stats[stat]++;
- user.stats.points--;
- if (stat === 'int') {
- user.stats.mp++;
- }
- }
- return typeof cb === "function" ? cb(null, _.pick(user, $w('stats'))) : void 0;
- },
+ update: _.partial(importedOps.update, user),
+ sleep: _.partial(importedOps.sleep, user),
+ revive: _.partial(importedOps.revive, user),
+ reset: _.partial(importedOps.reset, user),
+ reroll: _.partial(importedOps.reroll, user),
+ rebirth: _.partial(importedOps.reroll, user),
+ allocateNow: _.partial(importedOps.reroll, user),
+ clearCompleted: _.partial(importedOps.clearCompleted, user),
+ sortTask: _.partial(importedOps.sortTask, user),
+ updateTask: _.partial(importedOps.updateTask, user),
+ deleteTask: _.partial(importedOps.deleteTask, user),
+ addTask: _.partial(importedOps.addTask, user),
+ addTag: _.partial(importedOps.addTag, user),
+ sortTag: _.partial(importedOps.sortTag, user),
+ getTags: _.partial(importedOps.getTags, user),
+ getTag: _.partial(importedOps.getTag, user),
+ updateTag: _.partial(importedOps.updateTag, user),
+ deleteTag: _.partial(importedOps.deleteTag, user),
+ addWebhook: _.partial(importedOps.addWebhook, user),
+ updateWebhook: _.partial(importedOps.updateWebhook, user),
+ deleteWebhook: _.partial(importedOps.deleteWebhook, user),
+ addPushDevice: _.partial(importedOps.addPushDevice, user),
+ clearPMs: _.partial(importedOps.clearPMs, user),
+ deletePM: _.partial(importedOps.deletePM, user),
+ blockUser: _.partial(importedOps.blockUser, user),
+ feed: _.partial(importedOps.feed, user),
+ buySpecialSpell: _.partial(importedOps.buySpecialSpell, user),
+ purchase: _.partial(importedOps.purchase, user),
+ releasePets: _.partial(importedOps.releasePets, user),
+ releaseMounts: _.partial(importedOps.releaseMounts, user),
+ releaseBoth: _.partial(importedOps.releaseBoth, user),
+ buy: _.partial(importedOps.buy, user),
+ buyQuest: _.partial(importedOps.buyQuest, user),
+ buyMysterySet: _.partial(importedOps.buyMysterySet, user),
+ hourglassPurchase: _.partial(importedOps.hourglassPurchase, user),
+ sell: _.partial(importedOps.sell, user),
+ equip: _.partial(importedOps.equip, user),
+ hatch: _.partial(importedOps.hatch, user),
+ unlock: _.partial(importedOps.unlock, user),
+ changeClass: _.partial(importedOps.changeClass, user),
+ disableClasses: _.partial(importedOps.disableClasses, user),
+ allocate: _.partial(importedOps.allocate, user),
readCard: _.partial(importedOps.readCard, user),
- openMysteryItem: function(req, cb, analytics) {
- var analyticsData, item, ref, ref1;
- item = (ref = user.purchased.plan) != null ? (ref1 = ref.mysteryItems) != null ? ref1.shift() : void 0 : void 0;
- if (!item) {
- return typeof cb === "function" ? cb({
- code: 400,
- message: "Empty"
- }) : void 0;
- }
- item = content.gear.flat[item];
- user.items.gear.owned[item.key] = true;
- if (typeof user.markModified === "function") {
- user.markModified('purchased.plan.mysteryItems');
- }
- item.notificationType = 'Mystery';
- analyticsData = {
- uuid: user._id,
- itemKey: item,
- itemType: 'Subscriber Gear',
- acquireMethod: 'Subscriber',
- category: 'behavior'
- };
- if (analytics != null) {
- analytics.track('open mystery item', analyticsData);
- }
- if (typeof window !== 'undefined') {
- (user._tmp != null ? user._tmp : user._tmp = {}).drop = item;
- }
- return typeof cb === "function" ? cb(null, user.items.gear.owned) : void 0;
- },
- score: function(req, cb) {
- var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, gainMP, id, multiplier, num, options, ref, stats, subtractPoints, task, th;
- ref = req.params, id = ref.id, direction = ref.direction;
- task = user.tasks[id];
- options = req.query || {};
- _.defaults(options, {
- times: 1,
- cron: false
- });
- user._tmp = {};
- stats = {
- gp: +user.stats.gp,
- hp: +user.stats.hp,
- exp: +user.stats.exp
- };
- task.value = +task.value;
- task.streak = ~~task.streak;
- if (task.priority == null) {
- task.priority = 1;
- }
- if (task.value > stats.gp && task.type === 'reward') {
- return typeof cb === "function" ? cb({
- code: 401,
- message: i18n.t('messageNotEnoughGold', req.language)
- }) : void 0;
- }
- delta = 0;
- calculateDelta = function() {
- var currVal, nextDelta, ref1;
- currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
- nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
- if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) {
- if (direction === 'down' && task.type === 'daily' && options.cron) {
- nextDelta *= 1 - _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0) / task.checklist.length;
- }
- if (task.type === 'todo') {
- nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0);
- }
- }
- return nextDelta;
- };
- calculateReverseDelta = function() {
- var calc, closeEnough, currVal, diff, nextDelta, ref1, testVal;
- currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
- testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
- closeEnough = 0.00001;
- while (true) {
- calc = testVal + Math.pow(0.9747, testVal);
- diff = currVal - calc;
- if (Math.abs(diff) < closeEnough) {
- break;
- }
- if (diff > 0) {
- testVal -= diff;
- } else {
- testVal += diff;
- }
- }
- nextDelta = testVal - currVal;
- if (((ref1 = task.checklist) != null ? ref1.length : void 0) > 0) {
- if (task.type === 'todo') {
- nextDelta *= 1 + _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 0);
- }
- }
- return nextDelta;
- };
- changeTaskValue = function() {
- return _.times(options.times, function() {
- var nextDelta, ref1;
- nextDelta = !options.cron && direction === 'down' ? calculateReverseDelta() : calculateDelta();
- if (task.type !== 'reward') {
- if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) {
- user.stats.training[task.attribute] += nextDelta;
- }
- if (direction === 'up') {
- user.party.quest.progress.up = user.party.quest.progress.up || 0;
- if ((ref1 = task.type) === 'daily' || ref1 === 'todo') {
- user.party.quest.progress.up += nextDelta * (1 + (user._statsComputed.str / 200));
- }
- if (task.type === 'habit') {
- user.party.quest.progress.up += nextDelta * (0.5 + (user._statsComputed.str / 400));
- }
- }
- task.value += nextDelta;
- }
- return delta += nextDelta;
- });
- };
- addPoints = function() {
- var _crit, afterStreak, currStreak, gpMod, intBonus, perBonus, streakBonus;
- _crit = (delta > 0 ? user.fns.crit() : 1);
- if (_crit > 1) {
- user._tmp.crit = _crit;
- }
- intBonus = 1 + (user._statsComputed.int * .025);
- stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6);
- perBonus = 1 + user._statsComputed.per * .02;
- gpMod = delta * task.priority * _crit * perBonus;
- return stats.gp += task.streak ? (currStreak = direction === 'down' ? task.streak - 1 : task.streak, streakBonus = currStreak / 100 + 1, afterStreak = gpMod * streakBonus, currStreak > 0 ? gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0 : void 0, afterStreak) : gpMod;
- };
- subtractPoints = function() {
- var conBonus, hpMod;
- conBonus = 1 - (user._statsComputed.con / 250);
- if (conBonus < .1) {
- conBonus = 0.1;
- }
- hpMod = delta * conBonus * task.priority * 2;
- return stats.hp += Math.round(hpMod * 10) / 10;
- };
- gainMP = function(delta) {
- delta *= user._tmp.crit || 1;
- user.stats.mp += delta;
- if (user.stats.mp >= user._statsComputed.maxMP) {
- user.stats.mp = user._statsComputed.maxMP;
- }
- if (user.stats.mp < 0) {
- return user.stats.mp = 0;
- }
- };
- switch (task.type) {
- case 'habit':
- changeTaskValue();
- if (delta > 0) {
- addPoints();
- } else {
- subtractPoints();
- }
- gainMP(_.max([0.25, .0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
- th = (task.history != null ? task.history : task.history = []);
- if (th[th.length - 1] && moment(th[th.length - 1].date).isSame(new Date, 'day')) {
- th[th.length - 1].value = task.value;
- } else {
- th.push({
- date: +(new Date),
- value: task.value
- });
- }
- if (typeof user.markModified === "function") {
- user.markModified("habits." + (_.findIndex(user.habits, {
- id: task.id
- })) + ".history");
- }
- break;
- case 'daily':
- if (options.cron) {
- changeTaskValue();
- subtractPoints();
- if (!user.stats.buffs.streaks) {
- task.streak = 0;
- }
- } else {
- changeTaskValue();
- if (direction === 'down') {
- delta = calculateDelta();
- }
- addPoints();
- gainMP(_.max([1, .01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
- if (direction === 'up') {
- task.streak = task.streak ? task.streak + 1 : 1;
- if ((task.streak % 21) === 0) {
- user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
- }
- } else {
- if ((task.streak % 21) === 0) {
- user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
- }
- task.streak = task.streak ? task.streak - 1 : 0;
- }
- }
- break;
- case 'todo':
- if (options.cron) {
- changeTaskValue();
- } else {
- task.dateCompleted = direction === 'up' ? new Date : void 0;
- changeTaskValue();
- if (direction === 'down') {
- delta = calculateDelta();
- }
- addPoints();
- multiplier = _.max([
- _.reduce(task.checklist, (function(m, i) {
- return m + (i.completed ? 1 : 0);
- }), 1), 1
- ]);
- gainMP(_.max([multiplier, .01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1));
- }
- break;
- case 'reward':
- changeTaskValue();
- stats.gp -= Math.abs(task.value);
- num = parseFloat(task.value).toFixed(2);
- if (stats.gp < 0) {
- stats.hp += stats.gp;
- stats.gp = 0;
- }
- }
- user.fns.updateStats(stats, req);
- if (typeof window === 'undefined') {
- if (direction === 'up') {
- user.fns.randomDrop({
- task: task,
- delta: delta
- }, req);
- }
- }
- if (typeof cb === "function") {
- cb(null, user);
- }
- return delta;
- }
+ openMysteryItem: _.partial(importedOps.openMysteryItem, user),
+ score: _.partial(importedOps.score, user),
};
}
user.fns = {
diff --git a/common/script/libs/index.js b/common/script/libs/index.js
new file mode 100644
index 0000000000..2842fa9ec9
--- /dev/null
+++ b/common/script/libs/index.js
@@ -0,0 +1,15 @@
+import uuid from './uuid';
+import taskDefaults from './taskDefaults';
+import refPush from './refPush';
+import splitWhitespace from './splitWhitespace';
+import planGemLimits from './planGemLimits';
+import preenTodos from './preenTodos';
+
+module.exports = {
+ uuid,
+ taskDefaults,
+ refPush,
+ splitWhitespace,
+ planGemLimits,
+ preenTodos,
+};
diff --git a/common/script/libs/planGemLimits.js b/common/script/libs/planGemLimits.js
new file mode 100644
index 0000000000..e719c7695d
--- /dev/null
+++ b/common/script/libs/planGemLimits.js
@@ -0,0 +1,4 @@
+module.exports = {
+ convRate: 20,
+ convCap: 25,
+};
diff --git a/common/script/libs/preenTodos.js b/common/script/libs/preenTodos.js
new file mode 100644
index 0000000000..f07a49c9d0
--- /dev/null
+++ b/common/script/libs/preenTodos.js
@@ -0,0 +1,14 @@
+import moment from 'moment';
+import _ from 'lodash';
+
+/*
+ Preen 3-day past-completed To-Dos from Angular & mobile app
+ */
+
+module.exports = function(tasks) {
+ return _.filter(tasks, function(t) {
+ return !t.completed || (t.challenge && t.challenge.id) || moment(t.dateCompleted).isAfter(moment().subtract({
+ days: 3
+ }));
+ });
+};
diff --git a/common/script/libs/refPush.js b/common/script/libs/refPush.js
new file mode 100644
index 0000000000..06b3617014
--- /dev/null
+++ b/common/script/libs/refPush.js
@@ -0,0 +1,19 @@
+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(reflist, item, prune) {
+ if (prune == null) {
+ prune = 0;
+ }
+ item.sort = _.isEmpty(reflist) ? 0 : _.max(reflist, 'sort').sort + 1;
+ if (!(item.id && !reflist[item.id])) {
+ item.id = uuid();
+ }
+ return reflist[item.id] = item;
+};
diff --git a/common/script/libs/splitWhitespace.js b/common/script/libs/splitWhitespace.js
new file mode 100644
index 0000000000..1ef3d513aa
--- /dev/null
+++ b/common/script/libs/splitWhitespace.js
@@ -0,0 +1,3 @@
+module.exports = function(s) {
+ return s.split(' ');
+};
diff --git a/common/script/libs/taskDefaults.js b/common/script/libs/taskDefaults.js
new file mode 100644
index 0000000000..69c815e4fd
--- /dev/null
+++ b/common/script/libs/taskDefaults.js
@@ -0,0 +1,71 @@
+import uuid from './uuid';
+import _ from 'lodash';
+
+/*
+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 revisit
+
+module.exports = function(task) {
+ var defaults, ref, ref1, ref2;
+ if (task == null) {
+ task = {};
+ }
+ if (!(task.type && ((ref = task.type) === 'habit' || ref === 'daily' || ref === 'todo' || ref === 'reward'))) {
+ task.type = 'habit';
+ }
+ defaults = {
+ id: uuid(),
+ text: task.id != null ? task.id : '',
+ notes: '',
+ priority: 1,
+ challenge: {},
+ attribute: 'str',
+ dateCreated: new Date()
+ };
+ _.defaults(task, defaults);
+ if (task.type === 'habit') {
+ _.defaults(task, {
+ up: true,
+ down: true
+ });
+ }
+ if ((ref1 = task.type) === 'habit' || ref1 === 'daily') {
+ _.defaults(task, {
+ history: []
+ });
+ }
+ if ((ref2 = task.type) === 'daily' || ref2 === 'todo') {
+ _.defaults(task, {
+ completed: false
+ });
+ }
+ if (task.type === 'daily') {
+ _.defaults(task, {
+ streak: 0,
+ repeat: {
+ su: true,
+ m: true,
+ t: true,
+ w: true,
+ th: true,
+ f: true,
+ s: true
+ }
+ }, {
+ startDate: new Date(),
+ everyX: 1,
+ frequency: 'weekly'
+ });
+ }
+ task._id = task.id;
+ if (task.value == null) {
+ task.value = task.type === 'reward' ? 10 : 0;
+ }
+ if (!_.isNumber(task.priority)) {
+ task.priority = 1;
+ }
+ return task;
+};
diff --git a/common/script/libs/uuid.js b/common/script/libs/uuid.js
new file mode 100644
index 0000000000..4a26440d7d
--- /dev/null
+++ b/common/script/libs/uuid.js
@@ -0,0 +1,9 @@
+// TODO use node-uuid module
+module.exports = function() {
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
+ var r, v;
+ r = Math.random() * 16 | 0;
+ v = (c === "x" ? r : r & 0x3 | 0x8);
+ return v.toString(16);
+ });
+};
diff --git a/common/script/ops/addPushDevice.js b/common/script/ops/addPushDevice.js
new file mode 100644
index 0000000000..d96cf249cf
--- /dev/null
+++ b/common/script/ops/addPushDevice.js
@@ -0,0 +1,20 @@
+import _ from 'lodash';
+
+module.exports = function(user, req, cb) {
+ var i, item, pd;
+ if (!user.pushDevices) {
+ user.pushDevices = [];
+ }
+ pd = user.pushDevices;
+ item = {
+ regId: req.body.regId,
+ type: req.body.type
+ };
+ i = _.findIndex(pd, {
+ regId: item.regId
+ });
+ if (i === -1) {
+ pd.push(item);
+ }
+ return typeof cb === "function" ? cb(null, user.pushDevices) : void 0;
+};
diff --git a/common/script/ops/addTag.js b/common/script/ops/addTag.js
new file mode 100644
index 0000000000..88125ca600
--- /dev/null
+++ b/common/script/ops/addTag.js
@@ -0,0 +1,12 @@
+import uuid from '../libs/uuid';
+
+module.exports = function(user, req, cb) {
+ if (user.tags == null) {
+ user.tags = [];
+ }
+ user.tags.push({
+ name: req.body.name,
+ id: req.body.id || api.uuid()
+ });
+ return typeof cb === "function" ? cb(null, user.tags) : void 0;
+};
diff --git a/common/script/ops/addTask.js b/common/script/ops/addTask.js
new file mode 100644
index 0000000000..51136a0fcc
--- /dev/null
+++ b/common/script/ops/addTask.js
@@ -0,0 +1,27 @@
+import taskDefaults from '../libs/taskDefaults';
+import i18n from '../i18n';
+
+module.exports = function(user, req, cb) {
+ var task;
+ task = api.taskDefaults(req.body);
+ if (user.tasks[task.id] != null) {
+ return typeof cb === "function" ? cb({
+ code: 409,
+ message: i18n.t('messageDuplicateTaskID', req.language)
+ }) : void 0;
+ }
+ user[task.type + "s"].unshift(task);
+ if (user.preferences.newTaskEdit) {
+ task._editing = true;
+ }
+ if (user.preferences.tagsCollapsed) {
+ task._tags = true;
+ }
+ if (!user.preferences.advancedCollapsed) {
+ task._advanced = true;
+ }
+ if (typeof cb === "function") {
+ cb(null, task);
+ }
+ return task;
+};
diff --git a/common/script/ops/addWebhook.js b/common/script/ops/addWebhook.js
new file mode 100644
index 0000000000..61429449a4
--- /dev/null
+++ b/common/script/ops/addWebhook.js
@@ -0,0 +1,15 @@
+import refPush from '../libs/refPush';
+
+module.exports = function(user, req, cb) {
+ var wh;
+ wh = user.preferences.webhooks;
+ api.refPush(wh, {
+ url: req.body.url,
+ enabled: req.body.enabled || true,
+ id: req.body.id
+ });
+ if (typeof user.markModified === "function") {
+ user.markModified('preferences.webhooks');
+ }
+ return typeof cb === "function" ? cb(null, user.preferences.webhooks) : void 0;
+};
diff --git a/common/script/ops/allocate.js b/common/script/ops/allocate.js
new file mode 100644
index 0000000000..d471d45d62
--- /dev/null
+++ b/common/script/ops/allocate.js
@@ -0,0 +1,15 @@
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function(user, req, cb) {
+ var stat;
+ stat = req.query.stat || 'str';
+ if (user.stats.points > 0) {
+ user.stats[stat]++;
+ user.stats.points--;
+ if (stat === 'int') {
+ user.stats.mp++;
+ }
+ }
+ return typeof cb === "function" ? cb(null, _.pick(user, $w('stats'))) : void 0;
+};
diff --git a/common/script/ops/allocateNow.js b/common/script/ops/allocateNow.js
new file mode 100644
index 0000000000..815c0b8959
--- /dev/null
+++ b/common/script/ops/allocateNow.js
@@ -0,0 +1,10 @@
+import _ from 'lodash';
+
+module.exports = function(user, req, cb) {
+ _.times(user.stats.points, user.fns.autoAllocate);
+ user.stats.points = 0;
+ if (typeof user.markModified === "function") {
+ user.markModified('stats');
+ }
+ return typeof cb === "function" ? cb(null, user.stats) : void 0;
+};
diff --git a/common/script/ops/blockUser.js b/common/script/ops/blockUser.js
new file mode 100644
index 0000000000..dd08925640
--- /dev/null
+++ b/common/script/ops/blockUser.js
@@ -0,0 +1,13 @@
+module.exports = function(user, req, cb) {
+ var i;
+ i = user.inbox.blocks.indexOf(req.params.uuid);
+ if (~i) {
+ user.inbox.blocks.splice(i, 1);
+ } else {
+ user.inbox.blocks.push(req.params.uuid);
+ }
+ if (typeof user.markModified === "function") {
+ user.markModified('inbox.blocks');
+ }
+ return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0;
+};
diff --git a/common/script/ops/buy.js b/common/script/ops/buy.js
new file mode 100644
index 0000000000..4735025cd4
--- /dev/null
+++ b/common/script/ops/buy.js
@@ -0,0 +1,119 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import count from '../count';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function(user, req, cb, analytics) {
+ var analyticsData, armoireExp, armoireResp, armoireResult, base, buyResp, drop, eligibleEquipment, item, key, message, name;
+ key = req.params.key;
+ item = key === 'potion' ? content.potion : key === 'armoire' ? content.armoire : content.gear.flat[key];
+ if (!item) {
+ return typeof cb === "function" ? cb({
+ code: 404,
+ message: "Item '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
+ }) : void 0;
+ }
+ if (user.stats.gp < item.value) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: i18n.t('messageNotEnoughGold', req.language)
+ }) : void 0;
+ }
+ if ((item.canOwn != null) && !item.canOwn(user)) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: "You can't buy this item"
+ }) : void 0;
+ }
+ armoireResp = void 0;
+ if (item.key === 'potion') {
+ user.stats.hp += 15;
+ if (user.stats.hp > 50) {
+ user.stats.hp = 50;
+ }
+ } else if (item.key === 'armoire') {
+ armoireResult = user.fns.predictableRandom(user.stats.gp);
+ eligibleEquipment = _.filter(content.gear.flat, (function(i) {
+ return i.klass === 'armoire' && !user.items.gear.owned[i.key];
+ }));
+ if (!_.isEmpty(eligibleEquipment) && (armoireResult < .6 || !user.flags.armoireOpened)) {
+ eligibleEquipment.sort();
+ drop = user.fns.randomVal(eligibleEquipment);
+ user.items.gear.owned[drop.key] = true;
+ user.flags.armoireOpened = true;
+ message = i18n.t('armoireEquipment', {
+ image: '',
+ dropText: drop.text(req.language)
+ }, req.language);
+ if (api.count.remainingGearInSet(user.items.gear.owned, 'armoire') === 0) {
+ user.flags.armoireEmpty = true;
+ }
+ armoireResp = {
+ type: "gear",
+ dropKey: drop.key,
+ dropText: drop.text(req.language)
+ };
+ } else if ((!_.isEmpty(eligibleEquipment) && armoireResult < .8) || armoireResult < .5) {
+ drop = user.fns.randomVal(_.where(content.food, {
+ canDrop: true
+ }));
+ if ((base = user.items.food)[name = drop.key] == null) {
+ base[name] = 0;
+ }
+ user.items.food[drop.key] += 1;
+ message = i18n.t('armoireFood', {
+ image: '',
+ dropArticle: drop.article,
+ dropText: drop.text(req.language)
+ }, req.language);
+ armoireResp = {
+ type: "food",
+ dropKey: drop.key,
+ dropArticle: drop.article,
+ dropText: drop.text(req.language)
+ };
+ } else {
+ armoireExp = Math.floor(user.fns.predictableRandom(user.stats.exp) * 40 + 10);
+ user.stats.exp += armoireExp;
+ message = i18n.t('armoireExp', req.language);
+ armoireResp = {
+ "type": "experience",
+ "value": armoireExp
+ };
+ }
+ } else {
+ if (user.preferences.autoEquip) {
+ user.items.gear.equipped[item.type] = item.key;
+ message = user.fns.handleTwoHanded(item, null, req);
+ }
+ user.items.gear.owned[item.key] = true;
+ if (message == null) {
+ message = i18n.t('messageBought', {
+ itemText: item.text(req.language)
+ }, req.language);
+ }
+ if (item.last) {
+ user.fns.ultimateGear();
+ }
+ }
+ user.stats.gp -= item.value;
+ analyticsData = {
+ uuid: user._id,
+ itemKey: key,
+ acquireMethod: 'Gold',
+ goldCost: item.value,
+ category: 'behavior'
+ };
+ if (analytics != null) {
+ analytics.track('acquire item', analyticsData);
+ }
+ buyResp = _.pick(user, $w('items achievements stats flags'));
+ if (armoireResp) {
+ buyResp["armoire"] = armoireResp;
+ }
+ return typeof cb === "function" ? cb({
+ code: 200,
+ message: message
+ }, buyResp) : void 0;
+};
diff --git a/common/script/ops/buyMysterySet.js b/common/script/ops/buyMysterySet.js
new file mode 100644
index 0000000000..557cfd0595
--- /dev/null
+++ b/common/script/ops/buyMysterySet.js
@@ -0,0 +1,43 @@
+import i18n from '../i18n';
+import content from '../content/index';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function(user, req, cb, analytics) {
+ var mysterySet, ref;
+ if (!(user.purchased.plan.consecutive.trinkets > 0)) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: i18n.t('notEnoughHourglasses', req.language)
+ }) : void 0;
+ }
+ mysterySet = (ref = content.timeTravelerStore(user.items.gear.owned)) != null ? ref[req.params.key] : void 0;
+ if ((typeof window !== "undefined" && window !== null ? window.confirm : void 0) != null) {
+ if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) {
+ return;
+ }
+ }
+ if (!mysterySet) {
+ return typeof cb === "function" ? cb({
+ code: 404,
+ message: "Mystery set not found, or set already owned"
+ }) : void 0;
+ }
+ _.each(mysterySet.items, function(i) {
+ var analyticsData;
+ user.items.gear.owned[i.key] = true;
+ analyticsData = {
+ uuid: user._id,
+ itemKey: i.key,
+ itemType: 'Subscriber Gear',
+ acquireMethod: 'Hourglass',
+ category: 'behavior'
+ };
+ return analytics != null ? analytics.track('acquire item', analyticsData) : void 0;
+ });
+ user.purchased.plan.consecutive.trinkets--;
+ return typeof cb === "function" ? cb({
+ code: 200,
+ message: i18n.t('hourglassPurchaseSet', req.language)
+ }, _.pick(user, $w('items purchased.plan.consecutive'))) : void 0;
+};
diff --git a/common/script/ops/buyQuest.js b/common/script/ops/buyQuest.js
new file mode 100644
index 0000000000..b7653d43ce
--- /dev/null
+++ b/common/script/ops/buyQuest.js
@@ -0,0 +1,49 @@
+import i18n from '../i18n';
+import content from '../content/index';
+
+module.exports = function(user, req, cb, analytics) {
+ var analyticsData, base, item, key, message, name;
+ key = req.params.key;
+ item = content.quests[key];
+ if (!item) {
+ return typeof cb === "function" ? cb({
+ code: 404,
+ message: "Quest '" + key + " not found (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
+ }) : void 0;
+ }
+ if (!(item.category === 'gold' && item.goldValue)) {
+ return typeof cb === "function" ? cb({
+ code: 404,
+ message: "Quest '" + key + " is not a Gold-purchasable quest (see https://github.com/HabitRPG/habitrpg/blob/develop/common/script/content/index.js)"
+ }) : void 0;
+ }
+ if (user.stats.gp < item.goldValue) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: i18n.t('messageNotEnoughGold', req.language)
+ }) : void 0;
+ }
+ message = i18n.t('messageBought', {
+ itemText: item.text(req.language)
+ }, req.language);
+ if ((base = user.items.quests)[name = item.key] == null) {
+ base[name] = 0;
+ }
+ user.items.quests[item.key] += 1;
+ user.stats.gp -= item.goldValue;
+ analyticsData = {
+ uuid: user._id,
+ itemKey: item.key,
+ itemType: 'Market',
+ goldCost: item.goldValue,
+ acquireMethod: 'Gold',
+ category: 'behavior'
+ };
+ if (analytics != null) {
+ analytics.track('acquire item', analyticsData);
+ }
+ return typeof cb === "function" ? cb({
+ code: 200,
+ message: message
+ }, user.items.quests) : void 0;
+};
diff --git a/common/script/ops/buySpecialSpell.js b/common/script/ops/buySpecialSpell.js
new file mode 100644
index 0000000000..408c8e94c3
--- /dev/null
+++ b/common/script/ops/buySpecialSpell.js
@@ -0,0 +1,31 @@
+import i18n from '../i18n';
+import content from '../content/index';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function(user, req, cb) {
+ var base, item, key, message;
+ key = req.params.key;
+ item = content.special[key];
+ if (user.stats.gp < item.value) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: i18n.t('messageNotEnoughGold', req.language)
+ }) : void 0;
+ }
+ user.stats.gp -= item.value;
+ if ((base = user.items.special)[key] == null) {
+ base[key] = 0;
+ }
+ user.items.special[key]++;
+ if (typeof user.markModified === "function") {
+ user.markModified('items.special');
+ }
+ message = i18n.t('messageBought', {
+ itemText: item.text(req.language)
+ }, req.language);
+ return typeof cb === "function" ? cb({
+ code: 200,
+ message: message
+ }, _.pick(user, $w('items stats'))) : void 0;
+};
diff --git a/common/script/ops/changeClass.js b/common/script/ops/changeClass.js
new file mode 100644
index 0000000000..4a9a7e0536
--- /dev/null
+++ b/common/script/ops/changeClass.js
@@ -0,0 +1,59 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+import { capByLevel } from '../statHelpers';
+
+module.exports = function(user, req, cb, analytics) {
+ var analyticsData, klass, ref;
+ klass = (ref = req.query) != null ? ref["class"] : void 0;
+ if (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer') {
+ analyticsData = {
+ uuid: user._id,
+ "class": klass,
+ acquireMethod: 'Gems',
+ gemCost: 3,
+ category: 'behavior'
+ };
+ if (analytics != null) {
+ analytics.track('change class', analyticsData);
+ }
+ user.stats["class"] = klass;
+ user.flags.classSelected = true;
+ _.each(["weapon", "armor", "shield", "head"], function(type) {
+ var foundKey;
+ foundKey = false;
+ _.findLast(user.items.gear.owned, function(v, k) {
+ if (~k.indexOf(type + "_" + klass) && v === true) {
+ return foundKey = k;
+ }
+ });
+ user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : type + "_base_0";
+ if (type === "weapon" || (type === "shield" && klass === "rogue")) {
+ user.items.gear.owned[type + "_" + klass + "_0"] = true;
+ }
+ return true;
+ });
+ } else {
+ if (user.preferences.disableClasses) {
+ user.preferences.disableClasses = false;
+ user.preferences.autoAllocate = false;
+ } else {
+ if (!(user.balance >= .75)) {
+ return typeof cb === "function" ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ }
+ user.balance -= .75;
+ }
+ _.merge(user.stats, {
+ str: 0,
+ con: 0,
+ per: 0,
+ int: 0,
+ points: api.capByLevel(user.stats.lvl)
+ });
+ user.flags.classSelected = false;
+ }
+ return typeof cb === "function" ? cb(null, _.pick(user, $w('stats flags items preferences'))) : void 0;
+};
diff --git a/common/script/ops/clearCompleted.js b/common/script/ops/clearCompleted.js
new file mode 100644
index 0000000000..d60f12704f
--- /dev/null
+++ b/common/script/ops/clearCompleted.js
@@ -0,0 +1,12 @@
+import _ from 'lodash';
+
+module.exports = function(user, req, cb) {
+ _.remove(user.todos, function(t) {
+ var ref;
+ return t.completed && !((ref = t.challenge) != null ? ref.id : void 0);
+ });
+ if (typeof user.markModified === "function") {
+ user.markModified('todos');
+ }
+ return typeof cb === "function" ? cb(null, user.todos) : void 0;
+};
diff --git a/common/script/ops/clearPMs.js b/common/script/ops/clearPMs.js
new file mode 100644
index 0000000000..47e0a6ebc6
--- /dev/null
+++ b/common/script/ops/clearPMs.js
@@ -0,0 +1,7 @@
+module.exports = function(user, req, cb) {
+ user.inbox.messages = {};
+ if (typeof user.markModified === "function") {
+ user.markModified('inbox.messages');
+ }
+ return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
+};
diff --git a/common/script/ops/deletePM.js b/common/script/ops/deletePM.js
new file mode 100644
index 0000000000..d4d5684fbd
--- /dev/null
+++ b/common/script/ops/deletePM.js
@@ -0,0 +1,7 @@
+module.exports = function (user, req, cb) {
+ delete user.inbox.messages[req.params.id];
+ if (typeof user.markModified === 'function') {
+ user.markModified('inbox.messages.' + req.params.id);
+ }
+ return typeof cb === 'function' ? cb(null, user.inbox.messages) : void 0;
+};
diff --git a/common/script/ops/deleteTag.js b/common/script/ops/deleteTag.js
new file mode 100644
index 0000000000..6339f62d3d
--- /dev/null
+++ b/common/script/ops/deleteTag.js
@@ -0,0 +1,26 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ var i, tag, tid;
+ tid = req.params.id;
+ i = _.findIndex(user.tags, {
+ id: tid
+ });
+ if (!~i) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTagNotFound', req.language)
+ }) : void 0;
+ }
+ tag = user.tags[i];
+ delete user.filters[tag.id];
+ user.tags.splice(i, 1);
+ _.each(user.tasks, function (task) {
+ return delete task.tags[tag.id];
+ });
+ _.each(['habits', 'dailys', 'todos', 'rewards'], function (type) {
+ return typeof user.markModified === 'function' ? user.markModified(type) : void 0;
+ });
+ return typeof cb === 'function' ? cb(null, user.tags) : void 0;
+};
diff --git a/common/script/ops/deleteTask.js b/common/script/ops/deleteTask.js
new file mode 100644
index 0000000000..c72f2ec07e
--- /dev/null
+++ b/common/script/ops/deleteTask.js
@@ -0,0 +1,17 @@
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var i, ref, task;
+ task = user.tasks[(ref = req.params) !== null ? ref.id : void 0];
+ if (!task) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTaskNotFound', req.language)
+ }) : void 0;
+ }
+ i = user[task.type + 's'].indexOf(task);
+ if (~i) {
+ user[task.type + 's'].splice(i, 1);
+ }
+ return typeof cb === 'function' ? cb(null, {}) : void 0;
+};
diff --git a/common/script/ops/deleteWebhook.js b/common/script/ops/deleteWebhook.js
new file mode 100644
index 0000000000..da9b598ead
--- /dev/null
+++ b/common/script/ops/deleteWebhook.js
@@ -0,0 +1,7 @@
+module.exports = function (user, req, cb) {
+ delete user.preferences.webhooks[req.params.id];
+ if (typeof user.markModified === 'function') {
+ user.markModified('preferences.webhooks');
+ }
+ return typeof cb === 'function' ? cb(null, user.preferences.webhooks) : void 0;
+};
diff --git a/common/script/ops/disableClasses.js b/common/script/ops/disableClasses.js
new file mode 100644
index 0000000000..cf8294dedd
--- /dev/null
+++ b/common/script/ops/disableClasses.js
@@ -0,0 +1,13 @@
+import splitWhitespace from '../libs/splitWhitespace';
+import { capByLevel } from '../statHelpers';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ user.stats['class'] = 'warrior';
+ user.flags.classSelected = true;
+ user.preferences.disableClasses = true;
+ user.preferences.autoAllocate = true;
+ user.stats.str = capByLevel(user.stats.lvl);
+ user.stats.points = 0;
+ return typeof cb === 'function' ? cb(null, _.pick(user, splitWhitespace('stats flags preferences'))) : void 0;
+};
diff --git a/common/script/ops/equip.js b/common/script/ops/equip.js
new file mode 100644
index 0000000000..621e3e1f67
--- /dev/null
+++ b/common/script/ops/equip.js
@@ -0,0 +1,52 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var item, key, message, ref, type;
+ ref = [req.params.type || 'equipped', req.params.key], type = ref[0], key = ref[1];
+ switch (type) {
+ case 'mount':
+ if (!user.items.mounts[key]) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':You do not own this mount.'
+ }) : void 0;
+ }
+ user.items.currentMount = user.items.currentMount === key ? '' : key;
+ break;
+ case 'pet':
+ if (!user.items.pets[key]) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':You do not own this pet.'
+ }) : void 0;
+ }
+ user.items.currentPet = user.items.currentPet === key ? '' : key;
+ break;
+ case 'costume':
+ case 'equipped':
+ item = content.gear.flat[key];
+ if (!user.items.gear.owned[key]) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':You do not own this gear.'
+ }) : void 0;
+ }
+ if (user.items.gear[type][item.type] === key) {
+ user.items.gear[type][item.type] = item.type + '_base_0';
+ message = i18n.t('messageUnEquipped', {
+ itemText: item.text(req.language)
+ }, req.language);
+ } else {
+ user.items.gear[type][item.type] = item.key;
+ message = user.fns.handleTwoHanded(item, type, req);
+ }
+ if (typeof user.markModified === 'function') {
+ user.markModified('items.gear.' + type);
+ }
+ }
+ return typeof cb === 'function' ? cb((message ? {
+ code: 200,
+ message: message
+ } : null), user.items) : void 0;
+};
diff --git a/common/script/ops/feed.js b/common/script/ops/feed.js
new file mode 100644
index 0000000000..c5b0b39271
--- /dev/null
+++ b/common/script/ops/feed.js
@@ -0,0 +1,78 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var egg, eggText, evolve, food, message, pet, petDisplayName, potion, potionText, ref, ref1, ref2, userPets;
+ ref = req.params, pet = ref.pet, food = ref.food;
+ food = content.food[food];
+ ref1 = pet.split('-'), egg = ref1[0], potion = ref1[1];
+ userPets = user.items.pets;
+ potionText = content.hatchingPotions[potion] ? content.hatchingPotions[potion].text() : potion;
+ eggText = content.eggs[egg] ? content.eggs[egg].text() : egg;
+ petDisplayName = i18n.t('petName', {
+ potion: potionText,
+ egg: eggText
+ });
+ if (!userPets[pet]) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messagePetNotFound', req.language)
+ }) : void 0;
+ }
+ if (!((ref2 = user.items.food) !== null ? ref2[food.key] : void 0)) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageFoodNotFound', req.language)
+ }) : void 0;
+ }
+ if (content.specialPets[pet]) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('messageCannotFeedPet', req.language)
+ }) : void 0;
+ }
+ if (user.items.mounts[pet]) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('messageAlreadyMount', req.language)
+ }) : void 0;
+ }
+ message = '';
+ evolve = function () {
+ userPets[pet] = -1;
+ user.items.mounts[pet] = true;
+ if (pet === user.items.currentPet) {
+ user.items.currentPet = '';
+ }
+ return message = i18n.t('messageEvolve', {
+ egg: petDisplayName
+ }, req.language);
+ };
+ if (food.key === 'Saddle') {
+ evolve();
+ } else {
+ if (food.target === potion || content.hatchingPotions[potion].premium) {
+ userPets[pet] += 5;
+ message = i18n.t('messageLikesFood', {
+ egg: petDisplayName,
+ foodText: food.text(req.language)
+ }, req.language);
+ } else {
+ userPets[pet] += 2;
+ message = i18n.t('messageDontEnjoyFood', {
+ egg: petDisplayName,
+ foodText: food.text(req.language)
+ }, req.language);
+ }
+ if (userPets[pet] >= 50 && !user.items.mounts[pet]) {
+ evolve();
+ }
+ }
+ user.items.food[food.key]--;
+ return typeof cb === 'function' ? cb({
+ code: 200,
+ message: message
+ }, {
+ value: userPets[pet]
+ }) : void 0;
+};
diff --git a/common/script/ops/getTag.js b/common/script/ops/getTag.js
new file mode 100644
index 0000000000..8380ef5296
--- /dev/null
+++ b/common/script/ops/getTag.js
@@ -0,0 +1,17 @@
+import _ from 'lodash';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var i, tid;
+ tid = req.params.id;
+ i = _.findIndex(user.tags, {
+ id: tid
+ });
+ if (!~i) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTagNotFound', req.language)
+ }) : void 0;
+ }
+ return typeof cb === 'function' ? cb(null, user.tags[i]) : void 0;
+};
diff --git a/common/script/ops/getTags.js b/common/script/ops/getTags.js
new file mode 100644
index 0000000000..bc05d1109e
--- /dev/null
+++ b/common/script/ops/getTags.js
@@ -0,0 +1,3 @@
+module.exports = function (user, req, cb) {
+ return typeof cb === 'function' ? cb(null, user.tags) : void 0;
+};
diff --git a/common/script/ops/hatch.js b/common/script/ops/hatch.js
new file mode 100644
index 0000000000..40fa13ee4b
--- /dev/null
+++ b/common/script/ops/hatch.js
@@ -0,0 +1,39 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var egg, hatchingPotion, pet, ref;
+ ref = req.params, egg = ref.egg, hatchingPotion = ref.hatchingPotion;
+ if (!(egg && hatchingPotion)) {
+ return typeof cb === 'function' ? cb({
+ code: 400,
+ message: 'Please specify query.egg & query.hatchingPotion'
+ }) : void 0;
+ }
+ if (!(user.items.eggs[egg] > 0 && user.items.hatchingPotions[hatchingPotion] > 0)) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('messageMissingEggPotion', req.language)
+ }) : void 0;
+ }
+ if (content.hatchingPotions[hatchingPotion].premium && !content.dropEggs[egg]) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('messageInvalidEggPotionCombo', req.language)
+ }) : void 0;
+ }
+ pet = egg + '-' + hatchingPotion;
+ if (user.items.pets[pet] && user.items.pets[pet] > 0) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('messageAlreadyPet', req.language)
+ }) : void 0;
+ }
+ user.items.pets[pet] = 5;
+ user.items.eggs[egg]--;
+ user.items.hatchingPotions[hatchingPotion]--;
+ return typeof cb === 'function' ? cb({
+ code: 200,
+ message: i18n.t('messageHatched', req.language)
+ }, user.items) : void 0;
+};
diff --git a/common/script/ops/hourglassPurchase.js b/common/script/ops/hourglassPurchase.js
new file mode 100644
index 0000000000..99f17b0e08
--- /dev/null
+++ b/common/script/ops/hourglassPurchase.js
@@ -0,0 +1,54 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, key, ref, type;
+ ref = req.params, type = ref.type, key = ref.key;
+ if (!content.timeTravelStable[type]) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('typeNotAllowedHourglass', req.language) + JSON.stringify(_.keys(content.timeTravelStable))
+ }) : void 0;
+ }
+ if (!_.contains(_.keys(content.timeTravelStable[type]), key)) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t(type + 'NotAllowedHourglass', req.language)
+ }) : void 0;
+ }
+ if (user.items[type][key]) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t(type + 'AlreadyOwned', req.language)
+ }) : void 0;
+ }
+ if (!(user.purchased.plan.consecutive.trinkets > 0)) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('notEnoughHourglasses', req.language)
+ }) : void 0;
+ }
+ user.purchased.plan.consecutive.trinkets--;
+ if (type === 'pets') {
+ user.items.pets[key] = 5;
+ }
+ if (type === 'mounts') {
+ user.items.mounts[key] = true;
+ }
+ analyticsData = {
+ uuid: user._id,
+ itemKey: key,
+ itemType: type,
+ acquireMethod: 'Hourglass',
+ category: 'behavior'
+ };
+ if (analytics) {
+ analytics.track('acquire item', analyticsData);
+ }
+ return typeof cb === 'function' ? cb({
+ code: 200,
+ message: i18n.t('hourglassPurchase', req.language)
+ }, _.pick(user, splitWhitespace('items purchased.plan.consecutive'))) : void 0;
+};
diff --git a/common/script/ops/index.js b/common/script/ops/index.js
index e3e58316b9..1bfd3d55f2 100644
--- a/common/script/ops/index.js
+++ b/common/script/ops/index.js
@@ -1,5 +1,93 @@
+import update from './update';
+import sleep from './sleep';
+import revive from './revive';
+import reset from './reset';
+import reroll from './reroll';
+import rebirth from './rebirth';
+import allocateNow from './allocateNow';
+import clearCompleted from './clearCompleted';
+import sortTask from './sortTask';
+import updateTask from './updateTask';
+import deleteTask from './deleteTask';
+import addTask from './addTask';
+import addTag from './addTag';
+import sortTag from './sortTag';
+import getTags from './getTags';
+import getTag from './getTag';
+import updateTag from './updateTag';
+import deleteTag from './deleteTag';
+import addWebhook from './addWebhook';
+import updateWebhook from './updateWebhook';
+import deleteWebhook from './deleteWebhook';
+import addPushDevice from './addPushDevice';
+import clearPMs from './clearPMs';
+import deletePM from './deletePM';
+import blockUser from './blockUser';
+import feed from './feed';
+import buySpecialSpell from './buySpecialSpell';
+import purchase from './purchase';
+import releasePets from './releasePets';
+import releaseMounts from './releaseMounts';
+import releaseBoth from './releaseBoth';
+import buy from './buy';
+import buyQuest from './buyQuest';
+import buyMysterySet from './buyMysterySet';
+import hourglassPurchase from './hourglassPurchase';
+import sell from './sell';
+import equip from './equip';
+import hatch from './hatch';
+import unlock from './unlock';
+import changeClass from './changeClass';
+import disableClasses from './disableClasses';
+import allocate from './allocate';
import readCard from './readCard';
+import openMysteryItem from './openMysteryItem';
+import score from './score';
module.exports = {
+ update,
+ sleep,
+ revive,
+ reset,
+ reroll,
+ rebirth,
+ allocateNow,
+ clearCompleted,
+ sortTask,
+ updateTask,
+ deleteTask,
+ addTask,
+ addTag,
+ sortTag,
+ getTags,
+ getTag,
+ updateTag,
+ deleteTag,
+ addWebhook,
+ updateWebhook,
+ deleteWebhook,
+ addPushDevice,
+ clearPMs,
+ deletePM,
+ blockUser,
+ feed,
+ buySpecialSpell,
+ purchase,
+ releasePets,
+ releaseMounts,
+ releaseBoth,
+ buy,
+ buyQuest,
+ buyMysterySet,
+ hourglassPurchase,
+ sell,
+ equip,
+ hatch,
+ unlock,
+ changeClass,
+ disableClasses,
+ allocate,
readCard,
+ openMysteryItem,
+ score,
};
diff --git a/common/script/ops/openMysteryItem.js b/common/script/ops/openMysteryItem.js
new file mode 100644
index 0000000000..9536a206f5
--- /dev/null
+++ b/common/script/ops/openMysteryItem.js
@@ -0,0 +1,32 @@
+import content from '../content/index';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, item, ref, ref1;
+ item = (ref = user.purchased.plan) !== null ? (ref1 = ref.mysteryItems) !== null ? ref1.shift() : void 0 : void 0;
+ if (!item) {
+ return typeof cb === 'function' ? cb({
+ code: 400,
+ message: 'Empty'
+ }) : void 0;
+ }
+ item = content.gear.flat[item];
+ user.items.gear.owned[item.key] = true;
+ if (typeof user.markModified === 'function') {
+ user.markModified('purchased.plan.mysteryItems');
+ }
+ item.notificationType = 'Mystery';
+ analyticsData = {
+ uuid: user._id,
+ itemKey: item,
+ itemType: 'Subscriber Gear',
+ acquireMethod: 'Subscriber',
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('open mystery item', analyticsData);
+ }
+ if (typeof window !== 'undefined') {
+ (user._tmp !== null ? user._tmp : user._tmp = {}).drop = item;
+ }
+ return typeof cb === 'function' ? cb(null, user.items.gear.owned) : void 0;
+};
diff --git a/common/script/ops/purchase.js b/common/script/ops/purchase.js
new file mode 100644
index 0000000000..09e24b24fc
--- /dev/null
+++ b/common/script/ops/purchase.js
@@ -0,0 +1,107 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+import planGemLimits from '../libs/planGemLimits';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, convCap, convRate, item, key, price, ref, ref1, ref2, ref3, type;
+ ref = req.params, type = ref.type, key = ref.key;
+ if (type === 'gems' && key === 'gem') {
+ ref1 = planGemLimits, convRate = ref1.convRate, convCap = ref1.convCap;
+ convCap += user.purchased.plan.consecutive.gemCapExtra;
+ if (!((ref2 = user.purchased) !== null ? (ref3 = ref2.plan) !== null ? ref3.customerId : void 0 : void 0)) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: 'Must subscribe to purchase gems with GP'
+ }, req) : void 0;
+ }
+ if (!(user.stats.gp >= convRate)) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: 'Not enough Gold'
+ }) : void 0;
+ }
+ if (user.purchased.plan.gemsBought >= convCap) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: 'You\'ve reached the Gold=>Gem conversion cap (' + convCap + ') for this month. We have this to prevent abuse / farming. The cap will reset within the first three days of next month.'
+ }) : void 0;
+ }
+ user.balance += .25;
+ user.purchased.plan.gemsBought++;
+ user.stats.gp -= convRate;
+ analyticsData = {
+ uuid: user._id,
+ itemKey: key,
+ acquireMethod: 'Gold',
+ goldCost: convRate,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('purchase gems', analyticsData);
+ }
+ return typeof cb === 'function' ? cb({
+ code: 200,
+ message: '+1 Gem'
+ }, _.pick(user, splitWhitespace('stats balance'))) : void 0;
+ }
+ if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'gear') {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':type must be in [eggs,hatchingPotions,food,quests,gear]'
+ }, req) : void 0;
+ }
+ if (type === 'gear') {
+ item = content.gear.flat[key];
+ if (user.items.gear.owned[key]) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('alreadyHave', req.language)
+ }) : void 0;
+ }
+ price = (item.twoHanded || item.gearSet === 'animal' ? 2 : 1) / 4;
+ } else {
+ item = content[type][key];
+ price = item.value / 4;
+ }
+ if (!item) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':key not found for Content.' + type
+ }, req) : void 0;
+ }
+ if (!item.canBuy(user)) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('messageNotAvailable', req.language)
+ }) : void 0;
+ }
+ if ((user.balance < price) || !user.balance) {
+ return typeof cb === 'function' ? cb({
+ code: 403,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ }
+ user.balance -= price;
+ if (type === 'gear') {
+ user.items.gear.owned[key] = true;
+ } else {
+ if (!(user.items[type][key] > 0)) {
+ user.items[type][key] = 0;
+ }
+ user.items[type][key]++;
+ }
+ analyticsData = {
+ uuid: user._id,
+ itemKey: key,
+ itemType: 'Market',
+ acquireMethod: 'Gems',
+ gemCost: item.value,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('acquire item', analyticsData);
+ }
+ return typeof cb === 'function' ? cb(null, _.pick(user, splitWhitespace('items balance'))) : void 0;
+};
diff --git a/common/script/ops/rebirth.js b/common/script/ops/rebirth.js
new file mode 100644
index 0000000000..1370fce696
--- /dev/null
+++ b/common/script/ops/rebirth.js
@@ -0,0 +1,100 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+import { capByLevel } from '../statHelpers';
+import { MAX_LEVEL } from '../constants';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, flags, gear, lvl, stats;
+ if (user.balance < 2 && user.stats.lvl < MAX_LEVEL) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ }
+ analyticsData = {
+ uuid: user._id,
+ category: 'behavior'
+ };
+ if (user.stats.lvl < MAX_LEVEL) {
+ user.balance -= 2;
+ analyticsData.acquireMethod = 'Gems';
+ analyticsData.gemCost = 8;
+ } else {
+ analyticsData.gemCost = 0;
+ analyticsData.acquireMethod = '> 100';
+ }
+ if (analytics !== null) {
+ analytics.track('Rebirth', analyticsData);
+ }
+ lvl = capByLevel(user.stats.lvl);
+ _.each(user.tasks, function (task) {
+ if (task.type !== 'reward') {
+ task.value = 0;
+ }
+ if (task.type === 'daily') {
+ return task.streak = 0;
+ }
+ });
+ stats = user.stats;
+ stats.buffs = {};
+ stats.hp = 50;
+ stats.lvl = 1;
+ stats['class'] = 'warrior';
+ _.each(['per', 'int', 'con', 'str', 'points', 'gp', 'exp', 'mp'], function (value) {
+ return stats[value] = 0;
+ });
+ // TODO during refactoring: move all gear code from rebirth() to its own function and then call it in reset() as well
+ gear = user.items.gear;
+ _.each(['equipped', 'costume'], function (type) {
+ gear[type] = {};
+ gear[type].armor = 'armor_base_0';
+ gear[type].weapon = 'weapon_warrior_0';
+ gear[type].head = 'head_base_0';
+ return gear[type].shield = 'shield_base_0';
+ });
+ if (user.items.currentPet) {
+ user.ops.equip({
+ params: {
+ type: 'pet',
+ key: user.items.currentPet
+ }
+ });
+ }
+ if (user.items.currentMount) {
+ user.ops.equip({
+ params: {
+ type: 'mount',
+ key: user.items.currentMount
+ }
+ });
+ }
+ _.each(gear.owned, function (v, k) {
+ if (gear.owned[k] && content.gear.flat[k].value) {
+ gear.owned[k] = false;
+ return true;
+ }
+ });
+ gear.owned.weapon_warrior_0 = true;
+ if (typeof user.markModified === 'function') {
+ user.markModified('items.gear.owned');
+ }
+ user.preferences.costume = false;
+ flags = user.flags;
+ if (!user.achievements.beastMaster) {
+ flags.rebirthEnabled = false;
+ }
+ flags.itemsEnabled = false;
+ flags.dropsEnabled = false;
+ flags.classSelected = false;
+ flags.levelDrops = {};
+ if (!user.achievements.rebirths) {
+ user.achievements.rebirths = 1;
+ user.achievements.rebirthLevel = lvl;
+ } else if (lvl > user.achievements.rebirthLevel || lvl === 100) {
+ user.achievements.rebirths++;
+ user.achievements.rebirthLevel = lvl;
+ }
+ user.stats.buffs = {};
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/releaseBoth.js b/common/script/ops/releaseBoth.js
new file mode 100644
index 0000000000..08d6a7226e
--- /dev/null
+++ b/common/script/ops/releaseBoth.js
@@ -0,0 +1,50 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, animal, giveTriadBingo;
+ if (user.balance < 1.5 && !user.achievements.triadBingo) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ } else {
+ giveTriadBingo = true;
+ if (!user.achievements.triadBingo) {
+ analyticsData = {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 6,
+ category: 'behavior'
+ };
+ if (typeof analytics !== 'undefined' && analytics !== null) {
+ analytics.track('release pets & mounts', analyticsData);
+ }
+ user.balance -= 1.5;
+ }
+ user.items.currentMount = '';
+ user.items.currentPet = '';
+ for (animal in content.pets) {
+ if (user.items.pets[animal] === -1) {
+ giveTriadBingo = false;
+ }
+ user.items.pets[animal] = 0;
+ user.items.mounts[animal] = null;
+ }
+ if (!user.achievements.beastMasterCount) {
+ user.achievements.beastMasterCount = 0;
+ }
+ user.achievements.beastMasterCount++;
+ if (!user.achievements.mountMasterCount) {
+ user.achievements.mountMasterCount = 0;
+ }
+ user.achievements.mountMasterCount++;
+ if (giveTriadBingo) {
+ if (!user.achievements.triadBingoCount) {
+ user.achievements.triadBingoCount = 0;
+ }
+ user.achievements.triadBingoCount++;
+ }
+ }
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/releaseMounts.js b/common/script/ops/releaseMounts.js
new file mode 100644
index 0000000000..1cd4b1cb93
--- /dev/null
+++ b/common/script/ops/releaseMounts.js
@@ -0,0 +1,32 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, mount;
+ if (user.balance < 1) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ } else {
+ user.balance -= 1;
+ user.items.currentMount = '';
+ for (mount in content.pets) {
+ user.items.mounts[mount] = null;
+ }
+ if (!user.achievements.mountMasterCount) {
+ user.achievements.mountMasterCount = 0;
+ }
+ user.achievements.mountMasterCount++;
+ }
+ analyticsData = {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('release mounts', analyticsData);
+ }
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/releasePets.js b/common/script/ops/releasePets.js
new file mode 100644
index 0000000000..f26bbd8492
--- /dev/null
+++ b/common/script/ops/releasePets.js
@@ -0,0 +1,32 @@
+import content from '../content/index';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, pet;
+ if (user.balance < 1) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ } else {
+ user.balance -= 1;
+ for (pet in content.pets) {
+ user.items.pets[pet] = 0;
+ }
+ if (!user.achievements.beastMasterCount) {
+ user.achievements.beastMasterCount = 0;
+ }
+ user.achievements.beastMasterCount++;
+ user.items.currentPet = '';
+ }
+ analyticsData = {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('release pets', analyticsData);
+ }
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/reroll.js b/common/script/ops/reroll.js
new file mode 100644
index 0000000000..23f438abd8
--- /dev/null
+++ b/common/script/ops/reroll.js
@@ -0,0 +1,29 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData;
+ if (user.balance < 1) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ }
+ user.balance--;
+ _.each(user.tasks, function (task) {
+ if (task.type !== 'reward') {
+ return task.value = 0;
+ }
+ });
+ user.stats.hp = 50;
+ analyticsData = {
+ uuid: user._id,
+ acquireMethod: 'Gems',
+ gemCost: 4,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('Fortify Potion', analyticsData);
+ }
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/reset.js b/common/script/ops/reset.js
new file mode 100644
index 0000000000..dbc0cc5b44
--- /dev/null
+++ b/common/script/ops/reset.js
@@ -0,0 +1,35 @@
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ var gear;
+ user.habits = [];
+ user.dailys = [];
+ user.todos = [];
+ user.rewards = [];
+ user.stats.hp = 50;
+ user.stats.lvl = 1;
+ user.stats.gp = 0;
+ user.stats.exp = 0;
+ gear = user.items.gear;
+ _.each(['equipped', 'costume'], function (type) {
+ gear[type].armor = 'armor_base_0';
+ gear[type].weapon = 'weapon_base_0';
+ gear[type].head = 'head_base_0';
+ return gear[type].shield = 'shield_base_0';
+ });
+ if (typeof gear.owned === 'undefined') {
+ gear.owned = {};
+ }
+ _.each(gear.owned, function (v, k) {
+ if (gear.owned[k]) {
+ gear.owned[k] = false;
+ }
+ return true;
+ });
+ gear.owned.weapon_warrior_0 = true;
+ if (typeof user.markModified === 'function') {
+ user.markModified('items.gear.owned');
+ }
+ user.preferences.costume = false;
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/revive.js b/common/script/ops/revive.js
new file mode 100644
index 0000000000..f0a2b7bb08
--- /dev/null
+++ b/common/script/ops/revive.js
@@ -0,0 +1,72 @@
+import content from '../content/index';
+import i18n from '../i18n';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb, analytics) {
+ var analyticsData, base, cl, gearOwned, item, losableItems, lostItem, lostStat;
+ if (!(user.stats.hp <= 0)) {
+ return typeof cb === 'function' ? cb({
+ code: 400,
+ message: 'Cannot revive if not dead'
+ }) : void 0;
+ }
+ _.merge(user.stats, {
+ hp: 50,
+ exp: 0,
+ gp: 0
+ });
+ if (user.stats.lvl > 1) {
+ user.stats.lvl--;
+ }
+ lostStat = user.fns.randomVal(_.reduce(['str', 'con', 'per', 'int'], (function (m, k) {
+ if (user.stats[k]) {
+ m[k] = k;
+ }
+ return m;
+ }), {}));
+ if (lostStat) {
+ user.stats[lostStat]--;
+ }
+ cl = user.stats['class'];
+ gearOwned = (typeof (base = user.items.gear.owned).toObject === 'function' ? base.toObject() : void 0) || user.items.gear.owned;
+ losableItems = {};
+ _.each(gearOwned, function (v, k) {
+ var itm;
+ if (v) {
+ itm = content.gear.flat['' + k];
+ if (itm) {
+ if ((itm.value > 0 || k === 'weapon_warrior_0') && (itm.klass === cl || (itm.klass === 'special' && (!itm.specialClass || itm.specialClass === cl)) || itm.klass === 'armoire')) {
+ return losableItems['' + k] = '' + k;
+ }
+ }
+ }
+ });
+ lostItem = user.fns.randomVal(losableItems);
+ if (item = content.gear.flat[lostItem]) {
+ user.items.gear.owned[lostItem] = false;
+ if (user.items.gear.equipped[item.type] === lostItem) {
+ user.items.gear.equipped[item.type] = item.type + '_base_0';
+ }
+ if (user.items.gear.costume[item.type] === lostItem) {
+ user.items.gear.costume[item.type] = item.type + '_base_0';
+ }
+ }
+ if (typeof user.markModified === 'function') {
+ user.markModified('items.gear');
+ }
+ analyticsData = {
+ uuid: user._id,
+ lostItem: lostItem,
+ gaLabel: lostItem,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('Death', analyticsData);
+ }
+ return typeof cb === 'function' ? cb((item ? {
+ code: 200,
+ message: i18n.t('messageLostItem', {
+ itemText: item.text(req.language)
+ }, req.language)
+ } : null), user) : void 0;
+};
diff --git a/common/script/ops/score.js b/common/script/ops/score.js
new file mode 100644
index 0000000000..75f473a24f
--- /dev/null
+++ b/common/script/ops/score.js
@@ -0,0 +1,221 @@
+import moment from 'moment';
+import _ from 'lodash';
+import i18n from '../i18n';
+
+module.exports = function (user, req, cb) {
+ var addPoints, calculateDelta, calculateReverseDelta, changeTaskValue, delta, direction, gainMP, id, multiplier, num, options, ref, stats, subtractPoints, task, th;
+ ref = req.params, id = ref.id, direction = ref.direction;
+ task = user.tasks[id];
+ options = req.query || {};
+ _.defaults(options, {
+ times: 1,
+ cron: false
+ });
+ user._tmp = {};
+ stats = {
+ gp: +user.stats.gp,
+ hp: +user.stats.hp,
+ exp: +user.stats.exp
+ };
+ task.value = +task.value;
+ task.streak = ~~task.streak;
+ if (task.priority === null) {
+ task.priority = 1;
+ }
+ if (task.value > stats.gp && task.type === 'reward') {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('messageNotEnoughGold', req.language)
+ }) : void 0;
+ }
+ delta = 0;
+ calculateDelta = function () {
+ var currVal, nextDelta, ref1;
+ currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
+ nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
+ if (((ref1 = task.checklist) !== null ? ref1.length : void 0) > 0) {
+ if (direction === 'down' && task.type === 'daily' && options.cron) {
+ nextDelta *= 1 - _.reduce(task.checklist, (function (m, i) {
+ return m + (i.completed ? 1 : 0);
+ }), 0) / task.checklist.length;
+ }
+ if (task.type === 'todo') {
+ nextDelta *= 1 + _.reduce(task.checklist, (function (m, i) {
+ return m + (i.completed ? 1 : 0);
+ }), 0);
+ }
+ }
+ return nextDelta;
+ };
+ calculateReverseDelta = function () {
+ var calc, closeEnough, currVal, diff, nextDelta, ref1, testVal;
+ currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
+ testVal = currVal + Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
+ closeEnough = 0.00001;
+ while (true) {
+ calc = testVal + Math.pow(0.9747, testVal);
+ diff = currVal - calc;
+ if (Math.abs(diff) < closeEnough) {
+ break;
+ }
+ if (diff > 0) {
+ testVal -= diff;
+ } else {
+ testVal += diff;
+ }
+ }
+ nextDelta = testVal - currVal;
+ if (((ref1 = task.checklist) !== null ? ref1.length : void 0) > 0) {
+ if (task.type === 'todo') {
+ nextDelta *= 1 + _.reduce(task.checklist, (function (m, i) {
+ return m + (i.completed ? 1 : 0);
+ }), 0);
+ }
+ }
+ return nextDelta;
+ };
+ changeTaskValue = function () {
+ return _.times(options.times, function () {
+ var nextDelta, ref1;
+ nextDelta = !options.cron && direction === 'down' ? calculateReverseDelta() : calculateDelta();
+ if (task.type !== 'reward') {
+ if (user.preferences.automaticAllocation === true && user.preferences.allocationMode === 'taskbased' && !(task.type === 'todo' && direction === 'down')) {
+ user.stats.training[task.attribute] += nextDelta;
+ }
+ if (direction === 'up') {
+ user.party.quest.progress.up = user.party.quest.progress.up || 0;
+ if ((ref1 = task.type) === 'daily' || ref1 === 'todo') {
+ user.party.quest.progress.up += nextDelta * (1 + (user._statsComputed.str / 200));
+ }
+ if (task.type === 'habit') {
+ user.party.quest.progress.up += nextDelta * (0.5 + (user._statsComputed.str / 400));
+ }
+ }
+ task.value += nextDelta;
+ }
+ return delta += nextDelta;
+ });
+ };
+ addPoints = function () {
+ var _crit, afterStreak, currStreak, gpMod, intBonus, perBonus, streakBonus;
+ _crit = (delta > 0 ? user.fns.crit() : 1);
+ if (_crit > 1) {
+ user._tmp.crit = _crit;
+ }
+ intBonus = 1 + (user._statsComputed.int * .025);
+ stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6);
+ perBonus = 1 + user._statsComputed.per * .02;
+ gpMod = delta * task.priority * _crit * perBonus;
+ return stats.gp += task.streak ? (currStreak = direction === 'down' ? task.streak - 1 : task.streak, streakBonus = currStreak / 100 + 1, afterStreak = gpMod * streakBonus, currStreak > 0 ? gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0 : void 0, afterStreak) : gpMod;
+ };
+ subtractPoints = function () {
+ var conBonus, hpMod;
+ conBonus = 1 - (user._statsComputed.con / 250);
+ if (conBonus < .1) {
+ conBonus = 0.1;
+ }
+ hpMod = delta * conBonus * task.priority * 2;
+ return stats.hp += Math.round(hpMod * 10) / 10;
+ };
+ gainMP = function (delta) {
+ delta *= user._tmp.crit || 1;
+ user.stats.mp += delta;
+ if (user.stats.mp >= user._statsComputed.maxMP) {
+ user.stats.mp = user._statsComputed.maxMP;
+ }
+ if (user.stats.mp < 0) {
+ return user.stats.mp = 0;
+ }
+ };
+ switch (task.type) {
+ case 'habit':
+ changeTaskValue();
+ if (delta > 0) {
+ addPoints();
+ } else {
+ subtractPoints();
+ }
+ gainMP(_.max([0.25, .0025 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
+ th = (task.history !== null ? task.history : task.history = []);
+ if (th[th.length - 1] && moment(th[th.length - 1].date).isSame(new Date, 'day')) {
+ th[th.length - 1].value = task.value;
+ } else {
+ th.push({
+ date: +(new Date),
+ value: task.value
+ });
+ }
+ if (typeof user.markModified === 'function') {
+ user.markModified('habits.' + (_.findIndex(user.habits, {
+ id: task.id
+ })) + '.history');
+ }
+ break;
+ case 'daily':
+ if (options.cron) {
+ changeTaskValue();
+ subtractPoints();
+ if (!user.stats.buffs.streaks) {
+ task.streak = 0;
+ }
+ } else {
+ changeTaskValue();
+ if (direction === 'down') {
+ delta = calculateDelta();
+ }
+ addPoints();
+ gainMP(_.max([1, .01 * user._statsComputed.maxMP]) * (direction === 'down' ? -1 : 1));
+ if (direction === 'up') {
+ task.streak = task.streak ? task.streak + 1 : 1;
+ if ((task.streak % 21) === 0) {
+ user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
+ }
+ } else {
+ if ((task.streak % 21) === 0) {
+ user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
+ }
+ task.streak = task.streak ? task.streak - 1 : 0;
+ }
+ }
+ break;
+ case 'todo':
+ if (options.cron) {
+ changeTaskValue();
+ } else {
+ task.dateCompleted = direction === 'up' ? new Date : void 0;
+ changeTaskValue();
+ if (direction === 'down') {
+ delta = calculateDelta();
+ }
+ addPoints();
+ multiplier = _.max([
+ _.reduce(task.checklist, (function (m, i) {
+ return m + (i.completed ? 1 : 0);
+ }), 1), 1
+ ]);
+ gainMP(_.max([multiplier, .01 * user._statsComputed.maxMP * multiplier]) * (direction === 'down' ? -1 : 1));
+ }
+ break;
+ case 'reward':
+ changeTaskValue();
+ stats.gp -= Math.abs(task.value);
+ num = parseFloat(task.value).toFixed(2);
+ if (stats.gp < 0) {
+ stats.hp += stats.gp;
+ stats.gp = 0;
+ }
+ }
+ user.fns.updateStats(stats, req);
+ if (typeof window === 'undefined') {
+ if (direction === 'up') {
+ user.fns.randomDrop({
+ task: task,
+ delta: delta
+ }, req);
+ }
+ }
+ if (typeof cb === 'function') {
+ cb(null, user);
+ }
+ return delta;
+};
diff --git a/common/script/ops/sell.js b/common/script/ops/sell.js
new file mode 100644
index 0000000000..b41846554a
--- /dev/null
+++ b/common/script/ops/sell.js
@@ -0,0 +1,23 @@
+import content from '../content/index';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function (user, req, cb) {
+ var key, ref, type;
+ ref = req.params, key = ref.key, type = ref.type;
+ if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':type not found. Must bes in [eggs, hatchingPotions, food]'
+ }) : void 0;
+ }
+ if (!user.items[type][key]) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: ':key not found for user.items.' + type
+ }) : void 0;
+ }
+ user.items[type][key]--;
+ user.stats.gp += content[type][key].value;
+ return typeof cb === 'function' ? cb(null, _.pick(user, splitWhitespace('stats items'))) : void 0;
+};
diff --git a/common/script/ops/sleep.js b/common/script/ops/sleep.js
new file mode 100644
index 0000000000..c8ecc3d1ab
--- /dev/null
+++ b/common/script/ops/sleep.js
@@ -0,0 +1,4 @@
+module.exports = function (user, req, cb) {
+ user.preferences.sleep = !user.preferences.sleep;
+ return typeof cb === 'function' ? cb(null, {}) : void 0;
+};
diff --git a/common/script/ops/sortTag.js b/common/script/ops/sortTag.js
new file mode 100644
index 0000000000..77cc0be7a4
--- /dev/null
+++ b/common/script/ops/sortTag.js
@@ -0,0 +1,9 @@
+module.exports = function (user, req, cb) {
+ var from, ref, to;
+ ref = req.query, to = ref.to, from = ref.from;
+ if (!((to !== null) && (from !== null))) {
+ return typeof cb === 'function' ? cb('?to=__&from=__ are required') : void 0;
+ }
+ user.tags.splice(to, 0, user.tags.splice(from, 1)[0]);
+ return typeof cb === 'function' ? cb(null, user.tags) : void 0;
+};
diff --git a/common/script/ops/sortTask.js b/common/script/ops/sortTask.js
new file mode 100644
index 0000000000..bed1758aa7
--- /dev/null
+++ b/common/script/ops/sortTask.js
@@ -0,0 +1,39 @@
+import i18n from '../i18n';
+import preenTodos from '../libs/preenTodos';
+
+module.exports = function (user, req, cb) {
+ var from, id, movedTask, preenedTasks, ref, task, tasks, to;
+ id = req.params.id;
+ ref = req.query, to = ref.to, from = ref.from;
+ task = user.tasks[id];
+ if (!task) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTaskNotFound', req.language)
+ }) : void 0;
+ }
+ if (!((to !== null) && (from !== null))) {
+ return typeof cb === 'function' ? cb('?to=__&from=__ are required') : void 0;
+ }
+ tasks = user[task.type + 's'];
+ if (task.type === 'todo' && tasks[from] !== task) {
+ preenedTasks = preenTodos(tasks);
+ if (to !== -1) {
+ to = tasks.indexOf(preenedTasks[to]);
+ }
+ from = tasks.indexOf(preenedTasks[from]);
+ }
+ if (tasks[from] !== task) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTaskNotFound', req.language)
+ }) : void 0;
+ }
+ movedTask = tasks.splice(from, 1)[0];
+ if (to === -1) {
+ tasks.push(movedTask);
+ } else {
+ tasks.splice(to, 0, movedTask);
+ }
+ return typeof cb === 'function' ? cb(null, tasks) : void 0;
+};
diff --git a/common/script/ops/unlock.js b/common/script/ops/unlock.js
new file mode 100644
index 0000000000..4d3adda2ca
--- /dev/null
+++ b/common/script/ops/unlock.js
@@ -0,0 +1,63 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+import splitWhitespace from '../libs/splitWhitespace';
+
+module.exports = function (user, req, cb, analytics) {
+ var alreadyOwns, analyticsData, cost, fullSet, k, path, split, v;
+ path = req.query.path;
+ fullSet = ~path.indexOf(',');
+ cost = ~path.indexOf('background.') ? fullSet ? 3.75 : 1.75 : fullSet ? 1.25 : 0.5;
+ alreadyOwns = !fullSet && user.fns.dotGet('purchased.' + path) === true;
+ if ((user.balance < cost || !user.balance) && !alreadyOwns) {
+ return typeof cb === 'function' ? cb({
+ code: 401,
+ message: i18n.t('notEnoughGems', req.language)
+ }) : void 0;
+ }
+ if (fullSet) {
+ _.each(path.split(','), function (p) {
+ if (~path.indexOf('gear.')) {
+ user.fns.dotSet('' + p, true);
+ true;
+ } else {
+
+ }
+ user.fns.dotSet('purchased.' + p, true);
+ return true;
+ });
+ } else {
+ if (alreadyOwns) {
+ split = path.split('.');
+ v = split.pop();
+ k = split.join('.');
+ if (k === 'background' && v === user.preferences.background) {
+ v = '';
+ }
+ user.fns.dotSet('preferences.' + k, v);
+ return typeof cb === 'function' ? cb(null, req) : void 0;
+ }
+ user.fns.dotSet('purchased.' + path, true);
+ }
+ user.balance -= cost;
+ if (~path.indexOf('gear.')) {
+ if (typeof user.markModified === 'function') {
+ user.markModified('gear.owned');
+ }
+ } else {
+ if (typeof user.markModified === 'function') {
+ user.markModified('purchased');
+ }
+ }
+ analyticsData = {
+ uuid: user._id,
+ itemKey: path,
+ itemType: 'customization',
+ acquireMethod: 'Gems',
+ gemCost: cost / .25,
+ category: 'behavior'
+ };
+ if (analytics !== null) {
+ analytics.track('acquire item', analyticsData);
+ }
+ return typeof cb === 'function' ? cb(null, _.pick(user, splitWhitespace('purchased preferences items'))) : void 0;
+};
diff --git a/common/script/ops/update.js b/common/script/ops/update.js
new file mode 100644
index 0000000000..963f8f6cba
--- /dev/null
+++ b/common/script/ops/update.js
@@ -0,0 +1,9 @@
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ _.each(req.body, function (v, k) {
+ user.fns.dotSet(k, v);
+ return true;
+ });
+ return typeof cb === 'function' ? cb(null, user) : void 0;
+};
diff --git a/common/script/ops/updateTag.js b/common/script/ops/updateTag.js
new file mode 100644
index 0000000000..d1b54e51b6
--- /dev/null
+++ b/common/script/ops/updateTag.js
@@ -0,0 +1,18 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ var i, tid;
+ tid = req.params.id;
+ i = _.findIndex(user.tags, {
+ id: tid
+ });
+ if (!~i) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTagNotFound', req.language)
+ }) : void 0;
+ }
+ user.tags[i].name = req.body.name;
+ return typeof cb === 'function' ? cb(null, user.tags[i]) : void 0;
+};
diff --git a/common/script/ops/updateTask.js b/common/script/ops/updateTask.js
new file mode 100644
index 0000000000..ede672c71e
--- /dev/null
+++ b/common/script/ops/updateTask.js
@@ -0,0 +1,20 @@
+import i18n from '../i18n';
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ var ref, task;
+ if (!(task = user.tasks[(ref = req.params) !== null ? ref.id : void 0])) {
+ return typeof cb === 'function' ? cb({
+ code: 404,
+ message: i18n.t('messageTaskNotFound', req.language)
+ }) : void 0;
+ }
+ _.merge(task, _.omit(req.body, ['checklist', 'id', 'type']));
+ if (req.body.checklist) {
+ task.checklist = req.body.checklist;
+ }
+ if (typeof task.markModified === 'function') {
+ task.markModified('tags');
+ }
+ return typeof cb === 'function' ? cb(null, task) : void 0;
+};
diff --git a/common/script/ops/updateWebhook.js b/common/script/ops/updateWebhook.js
new file mode 100644
index 0000000000..19d712a027
--- /dev/null
+++ b/common/script/ops/updateWebhook.js
@@ -0,0 +1,9 @@
+import _ from 'lodash';
+
+module.exports = function (user, req, cb) {
+ _.merge(user.preferences.webhooks[req.params.id], req.body);
+ if (typeof user.markModified === 'function') {
+ user.markModified('preferences.webhooks');
+ }
+ return typeof cb === 'function' ? cb(null, user.preferences.webhooks) : void 0;
+};
diff --git a/test/common/user.ops.hourglassPurchase.test.js b/test/common/user.ops.hourglassPurchase.test.js
index b9c7369d21..0f4cff118f 100644
--- a/test/common/user.ops.hourglassPurchase.test.js
+++ b/test/common/user.ops.hourglassPurchase.test.js
@@ -107,7 +107,7 @@ describe('user.ops.hourglassPurchase', () => {
});
});
- it('buys a mount', (done) => {
+ it.only('buys a mount', (done) => {
user.purchased.plan.consecutive.trinkets = 2;
user.ops.hourglassPurchase({params: {type: 'mounts', key: 'MantisShrimp-Base'}}, (response) => {