Revert "API v3 [WIP] (#6144)"

This reverts commit 28f2e9c356.
This commit is contained in:
Blade Barringer
2016-05-23 07:53:14 -05:00
parent 28f2e9c356
commit 0a7b6b65e7
993 changed files with 12914 additions and 44919 deletions
+1 -6
View File
@@ -1,9 +1,4 @@
{
"presets": ["es2015"],
"plugins": [
["transform-async-to-module-method", {
"module": "bluebird",
"method": "coroutine"
}]
]
"plugins": ["syntax-async-functions","transform-regenerator"]
}
+1 -1
View File
@@ -1,3 +1,3 @@
{
"directory": "website/client/bower_components"
"directory": "website/public/bower_components"
}
+8 -10
View File
@@ -3,26 +3,24 @@ common/dist/
common/transpiled-babel/
coverage/
database_reports/
migrations/
website/build/
website/transpiled-babel/
migrations/*
# The files in website/client/js should be moved out and browserified
website/client/
# The files in website/public/js should be moved out and browserified
website/public/
# Temporarilly disabled. These should be removed when the linting errors are fixed
common/script/index.js
common/script/content/index.js
common/script/ops/**/*.js
common/script/fns/**/*.js
common/script/libs/**/*.js
common/script/public/**/*.js
website/server/**/api-v2/**/*.js
website/server/routes/payments.js
website/server/routes/pages.js
website/server/middlewares/apiThrottle.js
website/server/middlewares/forceRefresh.js
website/src/**/*.js
debug-scripts/*
scripts/*
tasks/*.js
gulpfile.js
Gruntfile.js
+1 -4
View File
@@ -2,8 +2,5 @@
"extends": [
"habitrpg/server",
"habitrpg/babel"
],
"globals": {
"Promise": true
}
]
}
+5 -6
View File
@@ -1,7 +1,6 @@
.DS_Store
website/client/gen
website/client/common
website/client/apidoc
website/public/gen
website/public/common
website/transpiled-babel/
common/transpiled-babel/
node_modules
@@ -10,8 +9,8 @@ node_modules
config.json
npm-debug.log*
lib
website/client/bower_components
website/client/new-stuff.html
website/public/bower_components
website/public/new-stuff.html
website/build
newrelic_agent.log
.bower-tmp
@@ -25,7 +24,7 @@ src/*/*.map
src/*/*/*.map
test/*.js
test/*.map
website/client/docs
website/public/docs
*.sublime-workspace
coverage
coverage.html
+1 -1
View File
@@ -2,7 +2,7 @@ node_modules/**
.bower-cache/**
.bower-tmp/**
.bower-registry/**
website/client/**
website/public/**
website/views/**
website/build/**
.git/**
+1 -1
View File
@@ -2,7 +2,7 @@ language: node_js
node_js:
- '4.3.1'
before_install:
- "npm install -g npm@3"
- "npm install -g npm@2"
- "npm install -g gulp"
- "sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10"
- "echo 'deb http://downloads-distro.mongodb.org/repo/ubuntu-upstart dist 10gen' | sudo tee /etc/apt/sources.list.d/mongodb.list"
+13 -13
View File
@@ -43,11 +43,11 @@ module.exports = function(grunt) {
options: {
compress: false, // AFTER
'include css': true,
paths: ['website/client']
paths: ['website/public']
},
files: {
'website/build/app.css': ['website/client/css/index.styl'],
'website/build/static.css': ['website/client/css/static.styl']
'website/build/app.css': ['website/public/css/index.styl'],
'website/build/static.css': ['website/public/css/static.styl']
}
}
},
@@ -55,13 +55,13 @@ module.exports = function(grunt) {
copy: {
build: {
files: [
{expand: true, cwd: 'website/client/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/client/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: 'website/public/', src: 'favicon.ico', dest: 'website/build/'},
{expand: true, cwd: 'website/public/', src: 'favicon_192x192.png', dest: 'website/build/'},
{expand: true, cwd: '', src: 'common/dist/sprites/spritesmith*.png', dest: 'website/build/'},
{expand: true, cwd: '', src: 'common/img/sprites/backer-only/*.gif', dest: 'website/build/'},
{expand: true, cwd: '', src: 'common/img/sprites/npc_ian.gif', dest: 'website/build/'},
{expand: true, cwd: '', src: 'common/img/sprites/quest_*.gif', dest: 'website/build/'},
{expand: true, cwd: 'website/client/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
{expand: true, cwd: 'website/public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'website/build/'}
]
}
},
@@ -88,9 +88,9 @@ module.exports = function(grunt) {
}
});
//Load build files from client/manifest.json
grunt.registerTask('loadManifestFiles', 'Load all build files from client/manifest.json', function(){
var files = grunt.file.readJSON('./website/client/manifest.json');
//Load build files from public/manifest.json
grunt.registerTask('loadManifestFiles', 'Load all build files from public/manifest.json', function(){
var files = grunt.file.readJSON('./website/public/manifest.json');
var uglify = {};
var cssmin = {};
@@ -101,7 +101,7 @@ module.exports = function(grunt) {
_.each(files[key].js, function(val){
var path = "./";
if( val.indexOf('common/') == -1)
path = './website/client/';
path = './website/public/';
js.push(path + val);
});
@@ -110,7 +110,7 @@ module.exports = function(grunt) {
_.each(files[key].css, function(val){
var path = "./";
if( val.indexOf('common/') == -1) {
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/client/';
path = (val == 'app.css' || val == 'static.css') ? './website/build/' : './website/public/';
}
css.push(path + val)
});
@@ -122,7 +122,7 @@ module.exports = function(grunt) {
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'website/client/css/whatever-css.css'});
grunt.config.set('cssmin.build.options', {'target': 'website/public/css/whatever-css.css'});
});
// Register tasks.
@@ -131,7 +131,7 @@ module.exports = function(grunt) {
grunt.registerTask('build:test', ['test:prepare:translations', 'build:dev']);
grunt.registerTask('test:prepare:translations', function() {
var i18n = require('./website/server/libs/api-v3/i18n'),
var i18n = require('./website/src/libs/i18n'),
fs = require('fs');
fs.writeFileSync('test/spec/mocks/translations.js',
"if(!window.env) window.env = {};\n" +
+1 -1
View File
@@ -1 +1 @@
web: node ./website/transpiled-babel/index.js
web: node ./website/transpiled-babel/server.js
+1 -1
View File
@@ -9,7 +9,7 @@
"ignore": [
"**/.*",
"node_modules",
"website/client/bower_components",
"public/bower_components",
"test",
"tests"
],
-2
View File
@@ -1,5 +1,3 @@
require('babel-polyfill');
var shared = require('./script/index');
var _ = require('lodash');
var moment = require('moment');
+2 -2
View File
@@ -346,13 +346,13 @@
width: 48px;
height: 52px;
}
.achievement-spookySparkles {
.achievement-spookDust {
background-image: url(spritesmith-main-0.png);
background-position: -25px -1601px;
width: 24px;
height: 26px;
}
.achievement-spookySparkles2x {
.achievement-spookDust2x {
background-image: url(spritesmith-main-0.png);
background-position: -980px -1548px;
width: 48px;
+5 -5
View File
@@ -1960,31 +1960,31 @@
width: 81px;
height: 99px;
}
.Pet-Lion-Veteran {
.Pet-LionCub-Base {
background-image: url(spritesmith-main-11.png);
background-position: -1640px -700px;
width: 81px;
height: 99px;
}
.Pet-LionCub-Base {
.Pet-LionCub-CottonCandyBlue {
background-image: url(spritesmith-main-11.png);
background-position: -1640px -800px;
width: 81px;
height: 99px;
}
.Pet-LionCub-CottonCandyBlue {
.Pet-LionCub-CottonCandyPink {
background-image: url(spritesmith-main-11.png);
background-position: -1640px -900px;
width: 81px;
height: 99px;
}
.Pet-LionCub-CottonCandyPink {
.Pet-LionCub-Desert {
background-image: url(spritesmith-main-11.png);
background-position: -1640px -1000px;
width: 81px;
height: 99px;
}
.Pet-LionCub-Desert {
.Pet-LionCub-Floral {
background-image: url(spritesmith-main-11.png);
background-position: -1640px -1100px;
width: 81px;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 KiB

After

Width:  |  Height:  |  Size: 153 KiB

File diff suppressed because it is too large Load Diff
Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 KiB

After

Width:  |  Height:  |  Size: 123 KiB

+48 -48
View File
@@ -270,7 +270,7 @@
}
.shop_armor_special_candycane {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -451px;
background-position: -1674px -410px;
width: 40px;
height: 40px;
}
@@ -282,19 +282,19 @@
}
.shop_armor_special_snowflake {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -656px;
background-position: -1674px -615px;
width: 40px;
height: 40px;
}
.shop_armor_special_winter2015Healer {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1230px;
background-position: -1674px -1189px;
width: 40px;
height: 40px;
}
.shop_armor_special_winter2015Mage {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1271px;
background-position: -1674px -1230px;
width: 40px;
height: 40px;
}
@@ -456,13 +456,13 @@
}
.shop_shield_special_winter2015Warrior {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1517px;
background-position: -1674px -1558px;
width: 40px;
height: 40px;
}
.shop_shield_special_winter2016Healer {
background-image: url(spritesmith-main-5.png);
background-position: -1599px -1599px;
background-position: -1674px -1476px;
width: 40px;
height: 40px;
}
@@ -1098,103 +1098,103 @@
}
.shop_head_rogue_3 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1025px;
background-position: -1674px -984px;
width: 40px;
height: 40px;
}
.shop_head_rogue_4 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -984px;
background-position: -1674px -943px;
width: 40px;
height: 40px;
}
.shop_head_rogue_5 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -943px;
background-position: -1674px -902px;
width: 40px;
height: 40px;
}
.shop_head_special_0 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -902px;
background-position: -1674px -861px;
width: 40px;
height: 40px;
}
.shop_head_special_1 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -861px;
background-position: -1674px -820px;
width: 40px;
height: 40px;
}
.shop_head_special_2 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -779px;
background-position: -1674px -738px;
width: 40px;
height: 40px;
}
.shop_head_special_fireCoralCirclet {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -738px;
background-position: -1674px -697px;
width: 40px;
height: 40px;
}
.shop_head_warrior_1 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -697px;
background-position: -1674px -656px;
width: 40px;
height: 40px;
}
.shop_head_warrior_2 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -615px;
background-position: -1674px -574px;
width: 40px;
height: 40px;
}
.shop_head_warrior_3 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -574px;
background-position: -1674px -533px;
width: 40px;
height: 40px;
}
.shop_head_warrior_4 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -533px;
background-position: -1674px -492px;
width: 40px;
height: 40px;
}
.shop_head_warrior_5 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -492px;
background-position: -1674px -451px;
width: 40px;
height: 40px;
}
.shop_head_wizard_1 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -410px;
background-position: -1674px -369px;
width: 40px;
height: 40px;
}
.shop_head_wizard_2 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -369px;
background-position: -1674px -328px;
width: 40px;
height: 40px;
}
.shop_head_wizard_3 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -328px;
background-position: -1674px -287px;
width: 40px;
height: 40px;
}
.shop_head_wizard_4 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -287px;
background-position: -1674px -246px;
width: 40px;
height: 40px;
}
.shop_head_wizard_5 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -246px;
background-position: -1674px -205px;
width: 40px;
height: 40px;
}
@@ -1296,7 +1296,7 @@
}
.shop_headAccessory_special_bearEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -205px;
background-position: -1674px -164px;
width: 40px;
height: 40px;
}
@@ -1314,31 +1314,31 @@
}
.shop_headAccessory_special_lionEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px 0px;
background-position: -1599px -1599px;
width: 40px;
height: 40px;
}
.shop_headAccessory_special_pandaEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -41px;
background-position: -1674px 0px;
width: 40px;
height: 40px;
}
.shop_headAccessory_special_pigEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -82px;
background-position: -1674px -41px;
width: 40px;
height: 40px;
}
.shop_headAccessory_special_tigerEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -123px;
background-position: -1674px -82px;
width: 40px;
height: 40px;
}
.shop_headAccessory_special_wolfEars {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -164px;
background-position: -1674px -123px;
width: 40px;
height: 40px;
}
@@ -1464,55 +1464,55 @@
}
.shop_shield_healer_1 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1066px;
background-position: -1674px -1025px;
width: 40px;
height: 40px;
}
.shop_shield_healer_2 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1107px;
background-position: -1674px -1066px;
width: 40px;
height: 40px;
}
.shop_shield_healer_3 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1148px;
background-position: -1674px -1107px;
width: 40px;
height: 40px;
}
.shop_shield_healer_4 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1189px;
background-position: -1674px -1148px;
width: 40px;
height: 40px;
}
.shop_shield_healer_5 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1312px;
background-position: -1674px -1271px;
width: 40px;
height: 40px;
}
.shop_shield_rogue_0 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1353px;
background-position: -1674px -1312px;
width: 40px;
height: 40px;
}
.shop_shield_rogue_1 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1394px;
background-position: -1674px -1353px;
width: 40px;
height: 40px;
}
.shop_shield_rogue_2 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1435px;
background-position: -1674px -1394px;
width: 40px;
height: 40px;
}
.shop_shield_rogue_3 {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1476px;
background-position: -1674px -1435px;
width: 40px;
height: 40px;
}
@@ -2056,12 +2056,6 @@
width: 64px;
height: 54px;
}
.ghost {
background-image: url(spritesmith-main-5.png);
background-position: -455px -1144px;
width: 90px;
height: 90px;
}
.inventory_present {
background-image: url(spritesmith-main-5.png);
background-position: -1616px -1049px;
@@ -2178,7 +2172,7 @@
}
.inventory_special_opaquePotion {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -820px;
background-position: -1674px -779px;
width: 40px;
height: 40px;
}
@@ -2200,7 +2194,7 @@
width: 57px;
height: 54px;
}
.inventory_special_spookySparkles {
.inventory_special_spookDust {
background-image: url(spritesmith-main-5.png);
background-position: -1299px -1508px;
width: 57px;
@@ -2244,17 +2238,23 @@
}
.seafoam_star {
background-image: url(spritesmith-main-5.png);
background-position: -364px -1144px;
background-position: -455px -1144px;
width: 90px;
height: 90px;
}
.shop_armoire {
background-image: url(spritesmith-main-5.png);
background-position: -1674px -1558px;
background-position: -1674px -1517px;
width: 40px;
height: 40px;
}
.snowman {
background-image: url(spritesmith-main-5.png);
background-position: -364px -1144px;
width: 90px;
height: 90px;
}
.spookman {
background-image: url(spritesmith-main-5.png);
background-position: -273px -1144px;
width: 90px;
Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 KiB

After

Width:  |  Height:  |  Size: 255 KiB

+2 -2
View File
@@ -682,7 +682,7 @@
width: 32px;
height: 32px;
}
.shop_spookySparkles {
.shop_spookDust {
background-image: url(spritesmith-main-6.png);
background-position: -790px -1176px;
width: 32px;
@@ -730,7 +730,7 @@
width: 40px;
height: 40px;
}
.shop_healAll {
.shop_heallAll {
background-image: url(spritesmith-main-6.png);
background-position: -791px -1643px;
width: 40px;

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.3 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

+1 -3
View File
@@ -1,6 +1,4 @@
'use strict';
let pathToCommon;
var pathToCommon;
if (process.env.NODE_ENV === 'production') { // eslint-disable-line no-process-env
pathToCommon = './transpiled-babel/index';
+2 -3
View File
@@ -74,10 +74,9 @@
"onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
"onlyLeaderUpdateChal": "Only the challenge leader can update it.",
"winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
"noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching challenge tasks.",
"noCompletedTodosChallenge": "\"includeComepletedTodos\" is not supported when fetching a challenge tasks.",
"userTasksNoChallengeId": "When \"tasksOwner\" is \"user\" \"challengeId\" can't be passed.",
"onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
"userAlreadyInChallenge": "User is already participating in this challenge.",
"cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked.",
"shortNameTooShort": "Tag Name must have at least 3 characters."
"cantOnlyUnlinkChalTask": "Only broken challenges tasks can be unlinked."
}
+2 -2
View File
@@ -182,8 +182,8 @@
"userAlreadyInAParty": "User already in a party.",
"userWithIDNotFound": "User with id \"<%= userId %>\" not found.",
"userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
"uuidsMustBeAnArray": "User ID invites must be an array.",
"emailsMustBeAnArray": "Email address invites must be an array.",
"uuidsMustBeAnArray": "UUIDs invites must be a an Array.",
"emailsMustBeAnArray": "Email invites must be a an Array.",
"canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
"onlyCreatorOrAdminCanDeleteChat": "Not authorized to delete this message!"
}
+1 -1
View File
@@ -5,7 +5,7 @@
"annoyingFriends": "Annoying Friends",
"annoyingFriendsText": "Got snowballed <%= snowballs %> times by party members.",
"alarmingFriends": "Alarming Friends",
"alarmingFriendsText": "Got spooked <%= spookySparkles %> times by party members.",
"alarmingFriendsText": "Got spooked <%= spookDust %> times by party members.",
"agriculturalFriends": "Agricultural Friends",
"agriculturalFriendsText": "Got transformed into a flower <%= seeds %> times by party members.",
"aquaticFriends": "Aquatic Friends",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"habiticaBackSoon": "Don't worry, Habitica will be back soon!",
"importantMaintenance": "We are doing important maintenance that we estimate will last until 10pm Pacific Time (5am UTC).",
"importantMaintenance": "We are doing important maintenance that we estimate will last until <%= localDate %> in your timezone.",
"maintenance": "Maintenance",
"maintenanceMoreInfo": "Want more information about the maintenance? <%= linkStart %>Check out our info page<%= linkEnd %>.",
"noDamageKeepStreaks": "You will NOT take damage or lose streaks!",
-1
View File
@@ -12,7 +12,6 @@
"etherealLion": "Ethereal Lion",
"veteranWolf": "Veteran Wolf",
"veteranTiger": "Veteran Tiger",
"veteranLion": "Veteran Lion",
"cerberusPup": "Cerberus Pup",
"hydra": "Hydra",
"mantisShrimp": "Mantis Shrimp",
-1
View File
@@ -47,7 +47,6 @@
"customDayStart": "Custom Day Start",
"changeCustomDayStart": "Change Custom Day Start?",
"sureChangeCustomDayStart": "Are you sure you want to change your custom day start?",
"customDayStartHasChanged": "Your custom day start has changed.",
"nextCron": "Your Dailies will next reset the first time you use Habitica after <%= time %>. Make sure you have completed your Dailies before this time!",
"customDayStartInfo1": "Habitica defaults to check and reset your Dailies at midnight in your own time zone each day. You can customize that time here.",
"misc": "Misc",
+3 -3
View File
@@ -52,8 +52,8 @@
"spellSpecialSaltText": "Salt",
"spellSpecialSaltNotes": "Someone has snowballed you. Ha ha, very funny. Now get this snow off me!",
"spellSpecialSpookySparklesText": "Spooky Sparkles",
"spellSpecialSpookySparklesNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialSpookDustText": "Spooky Sparkles",
"spellSpecialSpookDustNotes": "Turn a friend into a floating blanket with eyes!",
"spellSpecialOpaquePotionText": "Opaque Potion",
"spellSpecialOpaquePotionNotes": "Cancel the effects of Spooky Sparkles.",
@@ -70,7 +70,7 @@
"spellNotFound": "Skill \"<%= spellId %>\" not found.",
"partyNotFound": "Party not found",
"targetIdUUID": "\"targetId\" must be a valid User ID.",
"challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
"challengeTasksNoCast": "Casting a skill on challenge tasks is not supported.",
"spellNotOwned": "You don't own this skill.",
"spellLevelTooHigh": "You must be level <%= level %> to use this skill."
}
+1 -1
View File
@@ -134,7 +134,7 @@
"notAllowedHourglass": "Pet/Mount not available for purchase with Mystic Hourglass.",
"readCard": "<%= cardType %> has been read",
"cardTypeRequired": "Card type required",
"cardTypeNotAllowed": "Unknown card type.",
"cardTypeNotAllowed": "Unkown card type.",
"invalidCoupon": "Invalid coupon code.",
"couponUsed": "Coupon code already used.",
"noSudoAccess": "You don't have sudo access.",
-1
View File
@@ -73,7 +73,6 @@
"clearTags": "Clear",
"hideTags": "Hide",
"showTags": "Show",
"toRequired": "You must supply a to value",
"startDate": "Start Date",
"startDateHelpTitle": "When should this task start?",
"startDateHelp": "Set the date for which this task takes effect. Will not be due on earlier days.",
-2
View File
@@ -1,5 +1,3 @@
export const MAX_HEALTH = 50;
export const MAX_LEVEL = 100;
export const MAX_STAT_POINTS = MAX_LEVEL;
export const ATTRIBUTES = ['str', 'int', 'per', 'con'];
export const TAVERN_ID = '00000000-0000-4000-A000-000000000000';
-9
View File
@@ -426,7 +426,6 @@ api.specialPets = {
'Phoenix-Base': 'phoenix',
'Turkey-Gilded': 'gildedTurkey',
'MagicalBee-Base': 'magicalBee',
'Lion-Veteran': 'veteranLion',
};
api.specialMounts = {
@@ -602,14 +601,6 @@ api.questMounts = _.transform(api.questEggs, function(m, egg) {
}));
});
api.premiumMounts = _.transform(api.dropEggs, function(m, egg) {
return _.defaults(m, _.transform(api.hatchingPotions, function(m2, pot) {
if (pot.premium) {
return m2[egg.key + "-" + pot.key] = true;
}
}));
});
api.food = {
Meat: {
text: t('foodMeat'),
+29 -50
View File
@@ -1,6 +1,6 @@
import t from './translation';
import _ from 'lodash';
import { NotAuthorized } from '../libs/errors';
/*
---------------------------------------------------------------
Spells
@@ -15,7 +15,7 @@ import { NotAuthorized } from '../libs/errors';
web, this function can be performed on the client and on the server. `user` param is self (needed for determining your
own stats for effectiveness of cast), and `target` param is one of [task, party, user]. In the case of `self` spells,
you act on `user` instead of `target`. You can trust these are the correct objects, as long as the `target` attr of the
spell is correct. Take a look at habitrpg/website/server/models/user.js and habitrpg/website/server/models/task.js for what attributes are
spell is correct. Take a look at habitrpg/src/models/user.js and habitrpg/src/models/task.js for what attributes are
available on each model. Note `task.value` is its "redness". If party is passed in, it's an array of users,
so you'll want to iterate over them like: `_.each(target,function(member){...})`
@@ -40,12 +40,14 @@ spells.wizard = {
lvl: 11,
target: 'task',
notes: t('spellWizardFireballNotes'),
cast (user, target, req) {
cast (user, target) {
let bonus = user._statsComputed.int * user.fns.crit('per');
bonus *= Math.ceil((target.value < 0 ? 1 : target.value + 1) * 0.075);
user.stats.exp += diminishingReturns(bonus, 75);
if (!user.party.quest.progress) user.party.quest.progress = 0;
user.party.quest.progress.up += Math.ceil(user._statsComputed.int * 0.1);
// TODO change, pass req to spell?
let req = {language: user.preferences.language};
user.fns.updateStats(user.stats, req);
},
},
@@ -164,11 +166,12 @@ spells.rogue = {
lvl: 12,
target: 'task',
notes: t('spellRogueBackStabNotes'),
cast (user, target, req) {
cast (user, target) {
let _crit = user.fns.crit('str', 0.3);
let bonus = calculateBonus(target.value, user._statsComputed.str, _crit);
user.stats.exp += diminishingReturns(bonus, 75, 50);
user.stats.gp += diminishingReturns(bonus, 18, 75);
let req = {language: user.preferences.language};
user.fns.updateStats(user.stats, req);
},
},
@@ -194,7 +197,7 @@ spells.rogue = {
notes: t('spellRogueStealthNotes'),
cast (user) {
if (!user.stats.buffs.stealth) user.stats.buffs.stealth = 0;
user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.tasksOrder.dailys.length * 0.64, 55));
user.stats.buffs.stealth += Math.ceil(diminishingReturns(user._statsComputed.per, user.dailys.length * 0.64, 55));
},
},
};
@@ -215,10 +218,10 @@ spells.healer = {
text: t('spellHealerBrightnessText'),
mana: 15,
lvl: 12,
target: 'tasks',
target: 'self',
notes: t('spellHealerBrightnessNotes'),
cast (user, tasks) {
_.each(tasks, (task) => {
cast (user) {
_.each(user.tasks, (task) => {
if (task.type !== 'reward') {
task.value += 4 * (user._statsComputed.int / (user._statsComputed.int + 40));
}
@@ -239,7 +242,7 @@ spells.healer = {
});
},
},
healAll: { // Blessing
heallAll: { // Blessing
text: t('spellHealerHealAllText'),
mana: 25,
lvl: 14,
@@ -259,13 +262,11 @@ spells.special = {
text: t('spellSpecialSnowballAuraText'),
mana: 0,
value: 15,
previousPurchase: true,
target: 'user',
notes: t('spellSpecialSnowballAuraNotes'),
cast (user, target, req) {
if (!user.items.special.snowball) throw new NotAuthorized(t('spellNotOwned')(req.language));
cast (user, target) {
target.stats.buffs.snowball = true;
target.stats.buffs.spookySparkles = false;
target.stats.buffs.spookDust = false;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false;
if (!target.achievements.snowball) target.achievements.snowball = 0;
@@ -285,22 +286,20 @@ spells.special = {
user.stats.gp -= 5;
},
},
spookySparkles: {
text: t('spellSpecialSpookySparklesText'),
spookDust: {
text: t('spellSpecialSpookDustText'),
mana: 0,
value: 15,
previousPurchase: true,
target: 'user',
notes: t('spellSpecialSpookySparklesNotes'),
cast (user, target, req) {
if (!user.items.special.spookySparkles) throw new NotAuthorized(t('spellNotOwned')(req.language));
notes: t('spellSpecialSpookDustNotes'),
cast (user, target) {
target.stats.buffs.snowball = false;
target.stats.buffs.spookySparkles = true;
target.stats.buffs.spookDust = true;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = false;
if (!target.achievements.spookySparkles) target.achievements.spookySparkles = 0;
target.achievements.spookySparkles++;
user.items.special.spookySparkles--;
if (!target.achievements.spookDust) target.achievements.spookDust = 0;
target.achievements.spookDust++;
user.items.special.spookDust--;
},
},
opaquePotion: {
@@ -311,7 +310,7 @@ spells.special = {
target: 'self',
notes: t('spellSpecialOpaquePotionNotes'),
cast (user) {
user.stats.buffs.spookySparkles = false;
user.stats.buffs.spookDust = false;
user.stats.gp -= 5;
},
},
@@ -319,13 +318,11 @@ spells.special = {
text: t('spellSpecialShinySeedText'),
mana: 0,
value: 15,
previousPurchase: true,
target: 'user',
notes: t('spellSpecialShinySeedNotes'),
cast (user, target, req) {
if (!user.items.special.shinySeed) throw new NotAuthorized(t('spellNotOwned')(req.language));
cast (user, target) {
target.stats.buffs.snowball = false;
target.stats.buffs.spookySparkles = false;
target.stats.buffs.spookDust = false;
target.stats.buffs.shinySeed = true;
target.stats.buffs.seafoam = false;
if (!target.achievements.shinySeed) target.achievements.shinySeed = 0;
@@ -349,13 +346,11 @@ spells.special = {
text: t('spellSpecialSeafoamText'),
mana: 0,
value: 15,
previousPurchase: true,
target: 'user',
notes: t('spellSpecialSeafoamNotes'),
cast (user, target, req) {
if (!user.items.special.seafoam) throw new NotAuthorized(t('spellNotOwned')(req.language));
cast (user, target) {
target.stats.buffs.snowball = false;
target.stats.buffs.spookySparkles = false;
target.stats.buffs.spookDust = false;
target.stats.buffs.shinySeed = false;
target.stats.buffs.seafoam = true;
if (!target.achievements.seafoam) target.achievements.seafoam = 0;
@@ -396,10 +391,7 @@ spells.special = {
if (!target.items.special.nyeReceived) target.items.special.nyeReceived = [];
target.items.special.nyeReceived.push(user.profile.name);
if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
user.stats.gp -= 10;
},
},
@@ -424,10 +416,7 @@ spells.special = {
if (!target.items.special.valentineReceived) target.items.special.valentineReceived = [];
target.items.special.valentineReceived.push(user.profile.name);
if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
user.stats.gp -= 10;
},
},
@@ -451,10 +440,7 @@ spells.special = {
if (!target.items.special.greetingReceived) target.items.special.greetingReceived = [];
target.items.special.greetingReceived.push(user.profile.name);
if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
user.stats.gp -= 10;
},
},
@@ -479,10 +465,7 @@ spells.special = {
if (!target.items.special.thankyouReceived) target.items.special.thankyouReceived = [];
target.items.special.thankyouReceived.push(user.profile.name);
if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
user.stats.gp -= 10;
},
},
@@ -504,13 +487,9 @@ spells.special = {
u.achievements.birthday++;
});
}
if (!target.items.special.birthdayReceived) target.items.special.birthdayReceived = [];
target.items.special.birthdayReceived.push(user.profile.name);
if (!target.flags) target.flags = {};
target.flags.cardReceived = true;
user.stats.gp -= 10;
},
},
@@ -520,8 +499,8 @@ _.each(spells, (spellClass) => {
_.each(spellClass, (spell, key) => {
spell.key = key;
let _cast = spell.cast;
spell.cast = function castSpell (user, target, req) {
_cast(user, target, req);
spell.cast = function castSpell (user, target) {
_cast(user, target);
user.stats.mp -= spell.mana;
};
});
-1
View File
@@ -1,4 +1,3 @@
// TODO what can be moved to /website/server?
/*
------------------------------------------------------
Cron and time / day functions
+44 -63
View File
@@ -7,69 +7,50 @@ import splitWhitespace from '../libs/splitWhitespace';
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
*/
function getStatToAllocate (user) {
let suggested;
let statsObj = user.stats.toObject ? user.stats.toObject() : user.stats;
switch (user.preferences.allocationMode) {
case 'flat': {
let stats = _.pick(statsObj, splitWhitespace('con str per int'));
return _.invert(stats)[_.min(stats)];
}
case 'classbased': {
let lvlDiv7 = statsObj.lvl / 7;
let ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7];
let preference;
switch (statsObj.class) {
case 'wizard': {
preference = ['int', 'per', 'con', 'str'];
break;
module.exports = function(user) {
return user.stats[(function() {
var diff, ideal, lvlDiv7, preference, stats, suggested;
switch (user.preferences.allocationMode) {
case "flat":
stats = _.pick(user.stats, splitWhitespace('con str per int'));
return _.invert(stats)[_.min(stats)];
case "classbased":
lvlDiv7 = user.stats.lvl / 7;
ideal = [lvlDiv7 * 3, lvlDiv7 * 2, lvlDiv7, lvlDiv7];
preference = (function() {
switch (user.stats["class"]) {
case "wizard":
return ["int", "per", "con", "str"];
case "rogue":
return ["per", "str", "int", "con"];
case "healer":
return ["con", "int", "str", "per"];
default:
return ["str", "con", "per", "int"];
}
})();
diff = [user.stats[preference[0]] - ideal[0], user.stats[preference[1]] - ideal[1], user.stats[preference[2]] - ideal[2], user.stats[preference[3]] - ideal[3]];
suggested = _.findIndex(diff, (function(val) {
if (val === _.min(diff)) {
return true;
}
}));
if (~suggested) {
return preference[suggested];
} else {
return "str";
}
case 'rogue': {
preference = ['per', 'str', 'int', 'con'];
break;
}
case 'healer': {
preference = ['con', 'int', 'str', 'per'];
break;
}
default: {
preference = ['str', 'con', 'per', 'int'];
}
}
let diff = [
statsObj[preference[0]] - ideal[0],
statsObj[preference[1]] - ideal[1],
statsObj[preference[2]] - ideal[2],
statsObj[preference[3]] - ideal[3],
];
suggested = _.findIndex(diff, (val) => {
if (val === _.min(diff)) return true;
});
return suggested !== -1 ? preference[suggested] : 'str';
case "taskbased":
suggested = _.invert(user.stats.training)[_.max(user.stats.training)];
_.merge(user.stats.training, {
str: 0,
int: 0,
con: 0,
per: 0
});
return suggested || "str";
default:
return "str";
}
case 'taskbased': {
suggested = _.invert(statsObj.training)[_.max(statsObj.training)];
let training = statsObj.training;
training.str = 0;
training.int = 0;
training.con = 0;
training.per = 0;
return suggested || 'str';
}
default: {
return 'str';
}
}
}
module.exports = function autoAllocate (user) {
return user.stats[getStatToAllocate(user)]++;
})()]++;
};
+10 -5
View File
@@ -1,8 +1,13 @@
import predictableRandom from './predictableRandom';
module.exports = function crit (user, stat = 'str', chance = 0.03) {
let s = user._statsComputed[stat];
if (predictableRandom(user) <= chance * (1 + s / 100)) {
module.exports = function(user, stat, chance) {
var s;
if (stat == null) {
stat = 'str';
}
if (chance == null) {
chance = .03;
}
s = user._statsComputed[stat];
if (user.fns.predictableRandom() <= chance * (1 + s / 100)) {
return 1.5 + 4 * s / (s + 200);
} else {
return 1;
+358
View File
@@ -0,0 +1,358 @@
import moment from 'moment';
import _ from 'lodash';
import {
daysSince,
shouldDo,
} from '../cron';
import {
capByLevel,
toNextLevel,
} from '../statHelpers';
/*
------------------------------------------------------
Cron
------------------------------------------------------
*/
/*
At end of day, add value to all incomplete Daily & Todo tasks (further incentive)
For incomplete Dailys, deduct experience
Make sure to run this function once in a while as server will not take care of overnight calculations.
And you have to run it every time client connects.
{user}
*/
module.exports = function(user, options) {
var _progress, analyticsData, base, base1, base2, base3, base4, clearBuffs, dailyChecked, dailyDueUnchecked, daysMissed, expTally, lvl, lvlDiv2, multiDaysCountAsOneDay, now, perfect, plan, progress, ref, ref1, ref2, ref3, todoTally, timezoneOffsetFromUserPrefs, timezoneOffsetFromBrowser, timezoneOffsetAtLastCron;
if (options == null) {
options = {};
}
now = +options.now || +(new Date);
// If the user's timezone has changed (due to travel or daylight savings),
// cron can be triggered twice in one day, so we check for that and use
// both timezones to work out if cron should run.
// CDS = Custom Day Start time.
timezoneOffsetFromUserPrefs = user.preferences.timezoneOffset || 0;
timezoneOffsetAtLastCron = (_.isFinite(user.preferences.timezoneOffsetAtLastCron)) ? user.preferences.timezoneOffsetAtLastCron : timezoneOffsetFromUserPrefs;
timezoneOffsetFromBrowser = (_.isFinite(+options.timezoneOffset)) ? +options.timezoneOffset : timezoneOffsetFromUserPrefs;
// NB: All timezone offsets can be 0, so can't use `... || ...` to apply non-zero defaults
if (timezoneOffsetFromBrowser !== timezoneOffsetFromUserPrefs) {
// The user's browser has just told Habitica that the user's timezone has
// changed so store and use the new zone.
user.preferences.timezoneOffset = timezoneOffsetFromBrowser;
timezoneOffsetFromUserPrefs = timezoneOffsetFromBrowser;
}
// How many days have we missed using the user's current timezone:
daysMissed = daysSince(user.lastCron, _.defaults({
now: now
}, user.preferences));
if (timezoneOffsetAtLastCron != timezoneOffsetFromUserPrefs) {
// Since cron last ran, the user's timezone has changed.
// How many days have we missed using the old timezone:
let daysMissedNewZone = daysMissed;
let daysMissedOldZone = daysSince(user.lastCron, _.defaults({
now: now,
timezoneOffsetOverride: timezoneOffsetAtLastCron,
}, user.preferences));
if (timezoneOffsetAtLastCron < timezoneOffsetFromUserPrefs) {
// The timezone change was in the unsafe direction.
// E.g., timezone changes from UTC+1 (offset -60) to UTC+0 (offset 0).
// or timezone changes from UTC-4 (offset 240) to UTC-5 (offset 300).
// Local time changed from, for example, 03:00 to 02:00.
if (daysMissedOldZone > 0 && daysMissedNewZone > 0) {
// Both old and new timezones indicate that we SHOULD run cron, so
// it is safe to do so immediately.
daysMissed = Math.min(daysMissedOldZone, daysMissedNewZone);
// use minimum value to be nice to user
}
else if (daysMissedOldZone > 0) {
// The old timezone says that cron should run; the new timezone does not.
// This should be impossible for this direction of timezone change, but
// just in case I'm wrong...
console.log("zone has changed - old zone says run cron, NEW zone says no - stop cron now only -- SHOULD NOT HAVE GOT TO HERE", timezoneOffsetAtLastCron, timezoneOffsetFromUserPrefs, now); // used in production for confirming this never happens
}
else if (daysMissedNewZone > 0) {
// The old timezone says that cron should NOT run -- i.e., cron has
// already run today, from the old timezone's point of view.
// The new timezone says that cron SHOULD run, but this is almost
// certainly incorrect.
// This happens when cron occurred at a time soon after the CDS. When
// you reinterpret that time in the new timezone, it looks like it
// was before the CDS, because local time has stepped backwards.
// To fix this, rewrite the cron time to a time that the new
// timezone interprets as being in today.
daysMissed = 0; // prevent cron running now
let timezoneOffsetDiff = timezoneOffsetAtLastCron - timezoneOffsetFromUserPrefs;
// e.g., for dangerous zone change: 240 - 300 = -60 or -660 - -600 = -60
user.lastCron = moment(user.lastCron).subtract(timezoneOffsetDiff, 'minutes');
// NB: We don't change user.auth.timestamps.loggedin so that will still record the time that the previous cron actually ran.
// From now on we can ignore the old timezone:
user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
}
else {
// Both old and new timezones indicate that cron should
// NOT run.
daysMissed = 0; // prevent cron running now
}
}
else if (timezoneOffsetAtLastCron > timezoneOffsetFromUserPrefs) {
daysMissed = daysMissedNewZone;
// TODO: Either confirm that there is nothing that could possibly go wrong here and remove the need for this else branch, or fix stuff. There are probably situations where the Dailies do not reset early enough for a user who was expecting the zone change and wants to use all their Dailies immediately in the new zone; if so, we should provide an option for easy reset of Dailies (can't be automatic because there will be other situations where the user was not prepared).
}
}
if (!(daysMissed > 0)) {
return;
}
user.auth.timestamps.loggedin = new Date();
user.lastCron = now;
user.preferences.timezoneOffsetAtLastCron = timezoneOffsetFromUserPrefs;
if (user.items.lastDrop.count > 0) {
user.items.lastDrop.count = 0;
}
perfect = true;
clearBuffs = {
str: 0,
int: 0,
per: 0,
con: 0,
stealth: 0,
streaks: false
};
plan = (ref = user.purchased) != null ? ref.plan : void 0;
if (plan != null ? plan.customerId : void 0) {
if (typeof plan.dateUpdated === "undefined") {
// partial compensation for bug in subscription creation - https://github.com/HabitRPG/habitrpg/issues/6682
plan.dateUpdated = new Date();
}
if (moment(plan.dateUpdated).format('MMYYYY') !== moment().format('MMYYYY')) {
plan.gemsBought = 0;
plan.dateUpdated = new Date();
_.defaults(plan.consecutive, {
count: 0,
offset: 0,
trinkets: 0,
gemCapExtra: 0
});
plan.consecutive.count++;
if (plan.consecutive.offset > 0) {
plan.consecutive.offset--;
} else if (plan.consecutive.count % 3 === 0) {
plan.consecutive.trinkets++;
plan.consecutive.gemCapExtra += 5;
if (plan.consecutive.gemCapExtra > 25) {
plan.consecutive.gemCapExtra = 25;
}
}
}
if (plan.dateTerminated && moment(plan.dateTerminated).isBefore(+(new Date))) {
_.merge(plan, {
planId: null,
customerId: null,
paymentMethod: null
});
_.merge(plan.consecutive, {
count: 0,
offset: 0,
gemCapExtra: 0
});
if (typeof user.markModified === "function") {
user.markModified('purchased.plan');
}
}
}
if (user.preferences.sleep === true) {
user.stats.buffs = clearBuffs;
user.dailys.forEach(function(daily) {
var completed, repeat, thatDay;
completed = daily.completed, repeat = daily.repeat;
thatDay = moment(now).subtract({
days: 1
});
if (shouldDo(thatDay.toDate(), daily, user.preferences) || completed) {
_.each(daily.checklist, (function(box) {
box.completed = false;
return true;
}));
}
return daily.completed = false;
});
return;
}
multiDaysCountAsOneDay = true;
todoTally = 0;
user.todos.forEach(function(task) {
var absVal, completed, delta, id;
if (!task) {
return;
}
id = task.id, completed = task.completed;
delta = user.ops.score({
params: {
id: task.id,
direction: 'down'
},
query: {
times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
1: daysMissed
},
cron: true
}
});
absVal = completed ? Math.abs(task.value) : task.value;
return todoTally += absVal;
});
dailyChecked = 0;
dailyDueUnchecked = 0;
if ((base = user.party.quest.progress).down == null) {
base.down = 0;
}
user.dailys.forEach(function(task) {
var EvadeTask, completed, delta, fractionChecked, id, j, n, ref1, ref2, scheduleMisses, thatDay;
if (!task) {
return;
}
id = task.id, completed = task.completed;
EvadeTask = 0;
scheduleMisses = daysMissed;
if (completed) {
dailyChecked += 1;
} else {
scheduleMisses = 0;
for (n = j = 0, ref1 = daysMissed; 0 <= ref1 ? j < ref1 : j > ref1; n = 0 <= ref1 ? ++j : --j) {
thatDay = moment(now).subtract({
days: n + 1
});
if (shouldDo(thatDay.toDate(), task, user.preferences)) {
scheduleMisses++;
if (user.stats.buffs.stealth) {
user.stats.buffs.stealth--;
EvadeTask++;
}
if (multiDaysCountAsOneDay) {
break;
}
}
}
if (scheduleMisses > EvadeTask) {
perfect = false;
if (((ref2 = task.checklist) != null ? ref2.length : void 0) > 0) {
fractionChecked = _.reduce(task.checklist, (function(m, i) {
return m + (i.completed ? 1 : 0);
}), 0) / task.checklist.length;
dailyDueUnchecked += 1 - fractionChecked;
dailyChecked += fractionChecked;
} else {
dailyDueUnchecked += 1;
}
delta = user.ops.score({
params: {
id: task.id,
direction: 'down'
},
query: {
times: multiDaysCountAsOneDay != null ? multiDaysCountAsOneDay : {
1: scheduleMisses - EvadeTask
},
cron: true
}
});
user.party.quest.progress.down += delta * (task.priority < 1 ? task.priority : 1);
}
}
(task.history != null ? task.history : task.history = []).push({
date: +(new Date),
value: task.value
});
task.completed = false;
if (completed || (scheduleMisses > 0)) {
return _.each(task.checklist, (function(i) {
i.completed = false;
return true;
}));
}
});
user.habits.forEach(function(task) {
if (task.up === false || task.down === false) {
if (Math.abs(task.value) < 0.1) {
return task.value = 0;
} else {
return task.value = task.value / 2;
}
}
});
((base1 = (user.history != null ? user.history : user.history = {})).todos != null ? base1.todos : base1.todos = []).push({
date: now,
value: todoTally
});
expTally = user.stats.exp;
lvl = 0;
while (lvl < (user.stats.lvl - 1)) {
lvl++;
expTally += toNextLevel(lvl);
}
((base2 = user.history).exp != null ? base2.exp : base2.exp = []).push({
date: now,
value: expTally
});
if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) {
user.fns.preenUserHistory();
if (typeof user.markModified === "function") {
user.markModified('history');
}
if (typeof user.markModified === "function") {
user.markModified('dailys');
}
}
user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(capByLevel(user.stats.lvl) / 2), {
str: lvlDiv2,
int: lvlDiv2,
per: lvlDiv2,
con: lvlDiv2,
stealth: 0,
streaks: false
}) : clearBuffs;
if (dailyDueUnchecked === 0 && dailyChecked === 0) {
dailyChecked = 1;
}
user.stats.mp += _.max([10, .1 * user._statsComputed.maxMP]) * dailyChecked / (dailyDueUnchecked + dailyChecked);
if (user.stats.mp > user._statsComputed.maxMP) {
user.stats.mp = user._statsComputed.maxMP;
}
progress = user.party.quest.progress;
_progress = _.cloneDeep(progress);
_.merge(progress, {
down: 0,
up: 0
});
progress.collect = _.transform(progress.collect, (function(m, v, k) {
return m[k] = 0;
}));
if ((base4 = user.flags).cronCount == null) {
base4.cronCount = 0;
}
user.flags.cronCount++;
analyticsData = {
category: 'behavior',
gaLabel: 'Cron Count',
gaValue: user.flags.cronCount,
uuid: user._id,
user: user,
resting: user.preferences.sleep,
cronCount: user.flags.cronCount,
progressUp: _.min([_progress.up, 900]),
progressDown: _progress.down
};
if ((ref3 = options.analytics) != null) {
ref3.track('Cron', analyticsData);
}
return _progress;
};
+3 -5
View File
@@ -1,7 +1,5 @@
import _ from 'lodash';
import dotGet from '../libs/dotGet';
// TODO remove completely, use _.get, only used in client
module.exports = function dotGet (user, path) {
return _.get(user, path);
module.exports = function(user, path) {
return dotGet(user, path);
};
+8 -10
View File
@@ -1,14 +1,12 @@
import _ from 'lodash';
import dotSet from '../libs/dotSet';
/*
This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
Angular sets object properties directly - in which case, this function will be used.
*/
This allows you to set object properties by dot-path. Eg, you can run pathSet('stats.hp',50,user) which is the same as
user.stats.hp = 50. This is useful because in our habitrpg-shared functions we're returning changesets as {path:value},
so that different consumers can implement setters their own way. Derby needs model.set(path, value) for example, where
Angular sets object properties directly - in which case, this function will be used.
*/
// TODO use directly _.set and remove this fn, only used in client
module.exports = function dotSet (user, path, val) {
return _.set(user, path, val);
module.exports = function(user, path, val) {
return dotSet(user, path, val);
};
+11
View File
@@ -0,0 +1,11 @@
import content from '../content/index';
import i18n from '../i18n';
module.exports = function(user, type) {
var item;
item = content.gear.flat[user.items.gear.equipped[type]];
if (!item) {
return content.gear.flat[type + "_base_0"];
}
return item;
};
+10 -9
View File
@@ -1,23 +1,24 @@
import content from '../content/index';
import i18n from '../i18n';
module.exports = function handleTwoHanded (user, item, type = 'equipped', req = {}) {
let currentShield = content.gear.flat[user.items.gear[type].shield];
let currentWeapon = content.gear.flat[user.items.gear[type].weapon];
module.exports = function(user, item, type, req) {
var message, currentWeapon, currentShield;
if (type == null) {
type = 'equipped';
}
currentShield = content.gear.flat[user.items.gear[type].shield];
currentWeapon = content.gear.flat[user.items.gear[type].weapon];
let message;
if (item.type === 'shield' && (currentWeapon ? currentWeapon.twoHanded : false)) {
if (item.type === "shield" && (currentWeapon ? currentWeapon.twoHanded : false)) {
user.items.gear[type].weapon = 'weapon_base_0';
message = i18n.t('messageTwoHandedUnequip', {
twoHandedText: currentWeapon.text(req.language), offHandedText: item.text(req.language),
}, req.language);
} else if (item.twoHanded && (currentShield && user.items.gear[type].shield !== 'shield_base_0')) {
user.items.gear[type].shield = 'shield_base_0';
} else if (item.twoHanded && (currentShield && user.items.gear[type].shield != "shield_base_0")) {
user.items.gear[type].shield = "shield_base_0";
message = i18n.t('messageTwoHandedEquip', {
twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language),
}, req.language);
}
return message;
};
+6
View File
@@ -1,3 +1,4 @@
import getItem from './getItem';
import handleTwoHanded from './handleTwoHanded';
import predictableRandom from './predictableRandom';
import crit from './crit';
@@ -7,10 +8,13 @@ import dotGet from './dotGet';
import randomDrop from './randomDrop';
import autoAllocate from './autoAllocate';
import updateStats from './updateStats';
import cron from './cron';
import preenUserHistory from './preenUserHistory';
import ultimateGear from './ultimateGear';
import nullify from './nullify';
module.exports = {
getItem,
handleTwoHanded,
predictableRandom,
crit,
@@ -20,6 +24,8 @@ module.exports = {
randomDrop,
autoAllocate,
updateStats,
cron,
preenUserHistory,
ultimateGear,
nullify,
};
+2 -4
View File
@@ -1,7 +1,5 @@
// TODO remove once v2 is retired
module.exports = function nullify (user) {
module.exports = function(user) {
user.ops = null;
user.fns = null;
user = null;
return user = null;
};
+12 -16
View File
@@ -1,24 +1,20 @@
import _ from 'lodash';
/*
Because the same op needs to be performed on the client and the server (critical hits, item drops, etc),
we need things to be "random", but technically predictable so that they don't go out-of-sync
*/
// Because the same op needs to be performed on the client and the server (critical hits, item drops, etc),
// we need things to be "random", but technically predictable so that they don't go out-of-sync
module.exports = function predictableRandom (user, seed) {
module.exports = function(user, seed) {
var x;
if (!seed || seed === Math.PI) {
let stats = user.stats.toObject ? user.stats.toObject() : user.stats;
// These items are not part of the stat object but exists on the server (see controllers/user#getUser)
// we remove them in order to use the same user.stats both on server and on client
stats = _.omit(stats, 'toNextLevel', 'maxHealth', 'maxMP');
seed = _.reduce(stats, (accumulator, val) => {
if (_.isNumber(val)) {
return accumulator + val;
seed = _.reduce(user.stats, (function(m, v) {
if (_.isNumber(v)) {
return m + v;
} else {
return accumulator;
return m;
}
}, 0);
}), 0);
}
let x = Math.sin(seed++) * 10000;
x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
};
+25
View File
@@ -0,0 +1,25 @@
import _ from 'lodash';
import preenHistory from '../libs/preenHistory';
module.exports = function(user, minHistLen) {
if (minHistLen == null) {
minHistLen = 7;
}
_.each(user.habits.concat(user.dailys), function(task) {
var ref;
if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) {
task.history = preenHistory(task.history);
}
return true;
});
_.defaults(user.history, {
todos: [],
exp: []
});
if (user.history.exp.length > minHistLen) {
user.history.exp = preenHistory(user.history.exp);
}
if (user.history.todos.length > minHistLen) {
return user.history.todos = preenHistory(user.history.todos);
}
};
+38 -76
View File
@@ -3,118 +3,80 @@ import content from '../content/index';
import i18n from '../i18n';
import { daysSince } from '../cron';
import { diminishingReturns } from '../statHelpers';
import predictableRandom from './predictableRandom';
import randomVal from './randomVal';
// Clone a drop object maintaining its functions so that we can change it without affecting the original item
function cloneDropItem (drop) {
return _.cloneDeep(drop, (val) => {
return _.cloneDeep(drop, function (val) {
return _.isFunction(val) ? val : undefined; // undefined will be handled by lodash
});
}
module.exports = function randomDrop (user, modifiers, req = {}) {
let acceptableDrops;
let chance;
let drop;
let dropK;
let dropMultiplier;
let quest;
let rarity;
let task;
module.exports = function(user, modifiers, req) {
var acceptableDrops, base, base1, base2, chance, drop, dropK, dropMultiplier, name, name1, name2, quest, rarity, ref, ref1, ref2, ref3, task;
task = modifiers.task;
chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + 0.02;
chance *= task.priority * // Task priority: +50% for Medium, +100% for Hard
(1 + (task.streak / 100 || 0)) * // Streak bonus: +1% per streak
(1 + user._statsComputed.per / 100) * // PERception: +1% per point
(1 + (user.contributor.level / 40 || 0)) * // Contrib levels: +2.5% per level
(1 + (user.achievements.rebirths / 20 || 0)) * // Rebirths: +5% per achievement
(1 + (user.achievements.streak / 200 || 0)) * // Streak achievements: +0.5% per achievement
(user._tmp.crit || 1) * (1 + 0.5 * (_.reduce(task.checklist, (m, i) => {
return m + (i.completed ? 1 : 0); // +50% per checklist item complete. TODO: make this into X individual drop chances instead
}, 0) || 0));
chance = _.min([Math.abs(task.value - 21.27), 37.5]) / 150 + .02;
chance *= task.priority * (1 + (task.streak / 100 || 0)) * (1 + (user._statsComputed.per / 100)) * (1 + (user.contributor.level / 40 || 0)) * (1 + (user.achievements.rebirths / 20 || 0)) * (1 + (user.achievements.streak / 200 || 0)) * (user._tmp.crit || 1) * (1 + .5 * (_.reduce(task.checklist, (function(m, i) {
return m + (i.completed ? 1 : 0);
}), 0) || 0));
chance = diminishingReturns(chance, 0.75);
if (user.party.quest.key)
quest = content.quests[user.party.quest.key];
if (quest && quest.collect && predictableRandom(user, user.stats.gp) < chance) {
dropK = randomVal(user, quest.collect, {
key: true,
quest = content.quests[(ref = user.party.quest) != null ? ref.key : void 0];
if ((quest != null ? quest.collect : void 0) && user.fns.predictableRandom(user.stats.gp) < chance) {
dropK = user.fns.randomVal(quest.collect, {
key: true
});
if (!user.party.quest.progress.collect[dropK])
user.party.quest.progress.collect[dropK] = 0;
user.party.quest.progress.collect[dropK]++;
user.markModified('party.quest.progress');
if (typeof user.markModified === "function") {
user.markModified('party.quest.progress');
}
}
if (user.purchased && user.purchased.plan && user.purchased.plan.customerId) {
dropMultiplier = 2;
} else {
dropMultiplier = 1;
}
if (daysSince(user.items.lastDrop.date, user.preferences) === 0 &&
user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0))) {
dropMultiplier = ((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0) ? 2 : 1;
if ((daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= dropMultiplier * (5 + Math.floor(user._statsComputed.per / 25) + (user.contributor.level || 0)))) {
return;
}
if (user.flags && user.flags.dropsEnabled && predictableRandom(user, user.stats.exp) < chance) {
rarity = predictableRandom(user, user.stats.gp);
if (rarity > 0.6) { // food 40% chance
drop = cloneDropItem(randomVal(user, _.where(content.food, {
canDrop: true,
if (((ref3 = user.flags) != null ? ref3.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) {
rarity = user.fns.predictableRandom(user.stats.gp);
if (rarity > .6) {
drop = cloneDropItem(user.fns.randomVal(_.where(content.food, {
canDrop: true
})));
if (!user.items.food[drop.key]) {
user.items.food[drop.key] = 0;
if ((base = user.items.food)[name = drop.key] == null) {
base[name] = 0;
}
user.items.food[drop.key] += 1;
drop.type = 'Food';
drop.dialog = i18n.t('messageDropFood', {
dropArticle: drop.article,
dropText: drop.text(req.language),
dropNotes: drop.notes(req.language),
dropNotes: drop.notes(req.language)
}, req.language);
} else if (rarity > 0.3) { // eggs 30% chance
drop = cloneDropItem(randomVal(user, content.dropEggs));
if (!user.items.eggs[drop.key]) {
user.items.eggs[drop.key] = 0;
} else if (rarity > .3) {
drop = cloneDropItem(user.fns.randomVal(content.dropEggs));
if ((base1 = user.items.eggs)[name1 = drop.key] == null) {
base1[name1] = 0;
}
user.items.eggs[drop.key]++;
drop.type = 'Egg';
drop.dialog = i18n.t('messageDropEgg', {
dropText: drop.text(req.language),
dropNotes: drop.notes(req.language),
dropNotes: drop.notes(req.language)
}, req.language);
} else { // Hatching Potion, 30% chance - break down by rarity.
if (rarity < 0.02) { // Very Rare: 10% (of 30%)
acceptableDrops = ['Golden'];
} else if (rarity < 0.09) { // Rare: 20% of 30%
acceptableDrops = ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'];
} else if (rarity < 0.18) { // uncommon: 30% of 30%
acceptableDrops = ['Red', 'Shade', 'Skeleton'];
} else { // common, 40% of 30%
acceptableDrops = ['Base', 'White', 'Desert'];
}
drop = cloneDropItem(randomVal(user, _.pick(content.hatchingPotions, (v, k) => {
} else {
acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
drop = cloneDropItem(user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) {
return acceptableDrops.indexOf(k) >= 0;
})));
if (!user.items.hatchingPotions[drop.key]) {
user.items.hatchingPotions[drop.key] = 0;
}))));
if ((base2 = user.items.hatchingPotions)[name2 = drop.key] == null) {
base2[name2] = 0;
}
user.items.hatchingPotions[drop.key]++;
drop.type = 'HatchingPotion';
drop.dialog = i18n.t('messageDropPotion', {
dropText: drop.text(req.language),
dropNotes: drop.notes(req.language),
dropNotes: drop.notes(req.language)
}, req.language);
}
user._tmp.drop = drop;
user.items.lastDrop.date = Number(new Date());
user.items.lastDrop.count++;
user.items.lastDrop.date = +(new Date);
return user.items.lastDrop.count++;
}
};
+8 -6
View File
@@ -1,12 +1,14 @@
import _ from 'lodash';
import predictableRandom from './predictableRandom';
// Get a random property from an object
// returns random property (the value)
/*
Get a random property from an object
returns random property (the value)
*/
module.exports = function randomVal (user, obj, options = {}) {
let array = options.key ? _.keys(obj) : _.values(obj);
let rand = predictableRandom(user, options.seed);
module.exports = function(user, obj, options) {
var array, rand;
array = (options != null ? options.key : void 0) ? _.keys(obj) : _.values(obj);
rand = user.fns.predictableRandom(options != null ? options.seed : void 0);
array.sort();
return array[Math.floor(rand * array.length)];
};
-25
View File
@@ -1,25 +0,0 @@
import _ from 'lodash';
import content from '../content/index';
module.exports = function resetGear (user) {
let gear = user.items.gear;
_.each(['equipped', 'costume'], function resetUserGear (type) {
gear[type] = {};
gear[type].armor = 'armor_base_0';
gear[type].weapon = 'weapon_warrior_0';
gear[type].head = 'head_base_0';
gear[type].shield = 'shield_base_0';
});
// Gear.owned is a Mongo object so the _.each function iterates over hidden properties.
// The content.gear.flat[k] check should prevent this causing an error
_.each(gear.owned, function resetOwnedGear (v, k) {
if (gear.owned[k] && content.gear.flat[k] && content.gear.flat[k].value) {
gear.owned[k] = false;
}
});
gear.owned.weapon_warrior_0 = true; // eslint-disable-line camelcase
user.preferences.costume = false;
};
+20 -10
View File
@@ -1,23 +1,33 @@
import content from '../content/index';
import _ from 'lodash';
module.exports = function ultimateGear (user) {
let owned = typeof window !== 'undefined' ? user.items.gear.owned : user.items.gear.owned.toObject();
content.classes.forEach((klass) => {
module.exports = function(user) {
var base, owned;
owned = typeof window !== "undefined" && window !== null ? user.items.gear.owned : user.items.gear.owned.toObject();
if ((base = user.achievements).ultimateGearSets == null) {
base.ultimateGearSets = {
healer: false,
wizard: false,
rogue: false,
warrior: false
};
}
content.classes.forEach(function(klass) {
if (user.achievements.ultimateGearSets[klass] !== true) {
user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], (soFarGood, type) => {
let found = _.find(content.gear.tree[type][klass], {
last: true,
return user.achievements.ultimateGearSets[klass] = _.reduce(['armor', 'shield', 'head', 'weapon'], function(soFarGood, type) {
var found;
found = _.find(content.gear.tree[type][klass], {
last: true
});
return soFarGood && (!found || owned[found.key] === true);
}, true);
}
});
if (typeof user.markModified === "function") {
user.markModified('achievements.ultimateGearSets');
}
if (_.contains(user.achievements.ultimateGearSets, true) && user.flags.armoireEnabled !== true) {
user.flags.armoireEnabled = true;
return typeof user.markModified === "function" ? user.markModified('flags') : void 0;
}
return;
};
+41 -29
View File
@@ -1,19 +1,21 @@
import _ from 'lodash';
import {
MAX_HEALTH,
MAX_STAT_POINTS,
MAX_STAT_POINTS
} from '../constants';
import { toNextLevel } from '../statHelpers';
import autoAllocate from './autoAllocate';
module.exports = function updateStats (user, stats, req = {}, analytics) {
module.exports = function (user, stats, req, analytics) {
let allocatedStatPoints;
let totalStatPoints;
let experienceToNextLevel;
user.stats.hp = stats.hp > 0 ? stats.hp : 0;
user.stats.gp = stats.gp > 0 ? stats.gp : 0;
if (!user._tmp) user._tmp = {};
if (stats.hp <= 0) {
user.stats.hp = 0;
return user.stats.hp;
}
user.stats.hp = stats.hp;
user.stats.gp = stats.gp >= 0 ? stats.gp : 0;
experienceToNextLevel = toNextLevel(user.stats.lvl);
@@ -33,7 +35,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
continue; // eslint-disable-line no-continue
}
if (user.preferences.automaticAllocation) {
autoAllocate(user);
user.fns.autoAllocate();
} else {
user.stats.points = user.stats.lvl - allocatedStatPoints;
totalStatPoints = user.stats.points + allocatedStatPoints;
@@ -50,6 +52,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
}
user.stats.exp = stats.exp;
user.flags = user.flags || {};
if (!user.flags.customizationsNotification && (user.stats.exp > 5 || user.stats.lvl > 1)) {
user.flags.customizationsNotification = true;
@@ -59,39 +62,48 @@ module.exports = function updateStats (user, stats, req = {}, analytics) {
}
if (!user.flags.dropsEnabled && user.stats.lvl >= 3) {
user.flags.dropsEnabled = true;
if (user.items.eggs.Wolf > 0) {
user.items.eggs.Wolf++;
if (user.items.eggs["Wolf"] > 0) {
user.items.eggs["Wolf"]++;
} else {
user.items.eggs.Wolf = 1;
user.items.eggs["Wolf"] = 1;
}
}
if (!user.flags.classSelected && user.stats.lvl >= 10) {
user.flags.classSelected;
}
_.each({
vice1: 30,
atom1: 15,
moonstone1: 60,
goldenknight1: 40,
}, (lvl, k) => {
if (user.stats.lvl >= lvl && !user.flags.levelDrops[k]) {
user.flags.levelDrops[k] = true;
if (!user.items.quests[k])
user.items.quests[k] = 0;
user.items.quests[k]++;
user.markModified('flags.levelDrops');
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: k,
acquireMethod: 'Level Drop',
category: 'behavior',
});
goldenknight1: 40
}, function(lvl, k) {
var analyticsData, base, base1, ref;
if (!((ref = user.flags.levelDrops) != null ? ref[k] : void 0) && user.stats.lvl >= lvl) {
if ((base = user.items.quests)[k] == null) {
base[k] = 0;
}
user._tmp.drop = {
user.items.quests[k]++;
((base1 = user.flags).levelDrops != null ? base1.levelDrops : base1.levelDrops = {})[k] = true;
if (typeof user.markModified === "function") {
user.markModified('flags.levelDrops');
}
analyticsData = {
uuid: user._id,
itemKey: k,
acquireMethod: 'Level Drop',
category: 'behavior'
};
if (analytics != null) {
analytics.track('acquire item', analyticsData);
}
if (!user._tmp) user._tmp = {}
return user._tmp.drop = {
type: 'Quest',
key: k,
key: k
};
}
});
if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
user.flags.rebirthEnabled = true;
return user.flags.rebirthEnabled = true;
}
};
+92 -189
View File
@@ -1,197 +1,85 @@
import moment from 'moment';
import _ from 'lodash';
// When using a common module from the website or the server NEVER import the module directly
// but access it through `api` (the main common) module, otherwise you would require the non transpiled version of the file in production.
let api = module.exports = {};
import content from './content/index';
api.content = content;
import * as errors from './libs/errors';
api.errors = errors;
import i18n from './i18n';
api.i18n = i18n;
// TODO under api.libs.cron?
import { shouldDo, daysSince } from './cron';
api.shouldDo = shouldDo;
api.daysSince = daysSince;
// TODO under api.constants? and capitalize exported names too
import {
daysSince,
shouldDo,
} from './cron';
import {
MAX_HEALTH,
MAX_LEVEL,
MAX_STAT_POINTS,
TAVERN_ID,
} from './constants';
api.maxLevel = MAX_LEVEL;
api.maxHealth = MAX_HEALTH;
api.maxStatPoints = MAX_STAT_POINTS;
api.TAVERN_ID = TAVERN_ID;
// TODO under api.libs.statHelpers?
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; };
import content from './content/index';
import i18n from './i18n';
let api = module.exports = {};
api.i18n = i18n;
api.shouldDo = shouldDo;
api.maxLevel = MAX_LEVEL;
api.capByLevel = statHelpers.capByLevel;
api.maxHealth = MAX_HEALTH;
api.tnl = statHelpers.toNextLevel;
api.diminishingReturns = statHelpers.diminishingReturns;
import splitWhitespace from './libs/splitWhitespace';
api.$w = splitWhitespace;
$w = api.$w = importedLibs.splitWhitespace;
api.dotSet = importedLibs.dotSet;
api.dotGet = importedLibs.dotGet;
api.refPush = importedLibs.refPush;
api.planGemLimits = importedLibs.planGemLimits;
import dotSet from './libs/dotSet';
api.dotSet = dotSet;
preenHistory = importedLibs.preenHistory;
import dotGet from './libs/dotGet';
api.dotGet = dotGet;
api.preenTodos = importedLibs.preenTodos;
api.updateStore = importedLibs.updateStore;
import refPush from './libs/refPush';
api.refPush = refPush;
import planGemLimits from './libs/planGemLimits';
api.planGemLimits = planGemLimits;
/*
------------------------------------------------------
Content
------------------------------------------------------
*/
import preenTodos from './libs/preenTodos';
api.preenTodos = preenTodos;
api.content = content;
import updateStore from './libs/updateStore';
api.updateStore = updateStore;
import uuid from './libs/uuid';
api.uuid = uuid;
/*
------------------------------------------------------
Misc Helpers
------------------------------------------------------
*/
import taskDefaults from './libs/taskDefaults';
api.taskDefaults = taskDefaults;
api.uuid = importedLibs.uuid;
api.countExists = importedLibs.countExists;
api.taskDefaults = importedLibs.taskDefaults;
api.percent = importedLibs.percent;
api.removeWhitespace = importedLibs.removeWhitespace;
api.encodeiCalLink = importedLibs.encodeiCalLink;
api.gold = importedLibs.gold;
api.silver = importedLibs.silver;
api.taskClasses = importedLibs.taskClasses;
api.friendlyTimestamp = importedLibs.friendlyTimestamp;
api.newChatMessages = importedLibs.newChatMessages;
api.noTags = importedLibs.noTags;
api.appliedTags = importedLibs.appliedTags;
import percent from './libs/percent';
api.percent = percent;
import gold from './libs/gold';
api.gold = gold;
import silver from './libs/silver';
api.silver = silver;
import taskClasses from './libs/taskClasses';
api.taskClasses = taskClasses;
import noTags from './libs/noTags';
api.noTags = noTags;
import appliedTags from './libs/appliedTags';
api.appliedTags = appliedTags;
import pickDeep from './libs/pickDeep';
api.pickDeep = pickDeep;
/*
Various counting functions
*/
import count from './count';
api.count = count;
import statsComputed from './libs/statsComputed';
api.statsComputed = statsComputed;
import autoAllocate from './fns/autoAllocate';
import crit from './fns/crit';
import handleTwoHanded from './fns/handleTwoHanded';
import predictableRandom from './fns/predictableRandom';
import randomDrop from './fns/randomDrop';
import randomVal from './fns/randomVal';
import resetGear from './fns/resetGear';
import ultimateGear from './fns/ultimateGear';
import updateStats from './fns/updateStats';
api.fns = {
autoAllocate,
crit,
handleTwoHanded,
predictableRandom,
randomDrop,
randomVal,
resetGear,
ultimateGear,
updateStats,
};
import scoreTask from './ops/scoreTask';
import sleep from './ops/sleep';
import allocate from './ops/allocate';
import buy from './ops/buy';
import buyGear from './ops/buyGear';
import buyHealthPotion from './ops/buyHealthPotion';
import buyArmoire from './ops/buyArmoire';
import buyMysterySet from './ops/buyMysterySet';
import buyQuest from './ops/buyQuest';
import buySpecialSpell from './ops/buySpecialSpell';
import allocateNow from './ops/allocateNow';
import hatch from './ops/hatch';
import feed from './ops/feed';
import equip from './ops/equip';
import changeClass from './ops/changeClass';
import disableClasses from './ops/disableClasses';
import purchase from './ops/purchase';
import purchaseHourglass from './ops/hourglassPurchase';
import readCard from './ops/readCard';
import openMysteryItem from './ops/openMysteryItem';
import addWebhook from './ops/addWebhook';
import updateWebhook from './ops/updateWebhook';
import deleteWebhook from './ops/deleteWebhook';
import releasePets from './ops/releasePets';
import releaseBoth from './ops/releaseBoth';
import releaseMounts from './ops/releaseMounts';
import updateTask from './ops/updateTask';
import clearCompleted from './ops/clearCompleted';
import sell from './ops/sell';
import unlock from './ops/unlock';
import revive from './ops/revive';
import rebirth from './ops/rebirth';
import blockUser from './ops/blockUser';
import clearPMs from './ops/clearPMs';
import deletePM from './ops/deletePM';
import reroll from './ops/reroll';
import addPushDevice from './ops/addPushDevice';
import reset from './ops/reset';
import markPmsRead from './ops/markPMSRead';
api.ops = {
scoreTask,
sleep,
allocate,
buy,
buyGear,
buyHealthPotion,
buyArmoire,
buyMysterySet,
buySpecialSpell,
buyQuest,
allocateNow,
hatch,
feed,
equip,
changeClass,
disableClasses,
purchase,
purchaseHourglass,
readCard,
openMysteryItem,
addWebhook,
updateWebhook,
deleteWebhook,
releasePets,
releaseBoth,
releaseMounts,
updateTask,
clearCompleted,
sell,
unlock,
revive,
rebirth,
blockUser,
clearPMs,
deletePM,
reroll,
addPushDevice,
reset,
markPmsRead,
};
/*
------------------------------------------------------
@@ -199,9 +87,10 @@ User (prototype wrapper to give it ops, helper funcs, and virtuals
------------------------------------------------------
*/
/*
User is now wrapped (both on client and server), adding a few new properties:
* getters (_statsComputed)
* getters (_statsComputed, tasks, etc)
* user.fns, which is a bunch of helper functions
These were originally up above, but they make more sense belonging to the user object so we don't have to pass
the user object all over the place. In fact, we should pull in more functions such as cron(), updateStats(), etc.
@@ -232,16 +121,14 @@ TODO
import importedOps from './ops';
import importedFns from './fns';
// TODO Kept for the client side
api.wrap = function wrapUser (user, main = true) {
if (user._wrapped) return;
user._wrapped = true;
// Make markModified available on the client side as a noop function
if (!user.markModified) {
user.markModified = function noopMarkModified () {};
api.wrap = function(user, main) {
if (main == null) {
main = true;
}
if (user._wrapped) {
return;
}
user._wrapped = true;
if (main) {
user.ops = {
update: _.partial(importedOps.update, user),
@@ -276,9 +163,6 @@ api.wrap = function wrapUser (user, main = true) {
releaseMounts: _.partial(importedOps.releaseMounts, user),
releaseBoth: _.partial(importedOps.releaseBoth, user),
buy: _.partial(importedOps.buy, user),
buyHealthPotion: _.partial(importedOps.buyHealthPotion, user),
buyArmoire: _.partial(importedOps.buyArmoire, user),
buyGear: _.partial(importedOps.buyGear, user),
buyQuest: _.partial(importedOps.buyQuest, user),
buyMysterySet: _.partial(importedOps.buyMysterySet, user),
hourglassPurchase: _.partial(importedOps.hourglassPurchase, user),
@@ -291,12 +175,11 @@ api.wrap = function wrapUser (user, main = true) {
allocate: _.partial(importedOps.allocate, user),
readCard: _.partial(importedOps.readCard, user),
openMysteryItem: _.partial(importedOps.openMysteryItem, user),
score: _.partial(importedOps.scoreTask, user),
markPmsRead: _.partial(importedOps.markPmsRead, user),
score: _.partial(importedOps.score, user),
};
}
user.fns = {
getItem: _.partial(importedFns.getItem, user),
handleTwoHanded: _.partial(importedFns.handleTwoHanded, user),
predictableRandom: _.partial(importedFns.predictableRandom, user),
crit: _.partial(importedFns.crit, user),
@@ -306,14 +189,34 @@ api.wrap = function wrapUser (user, main = true) {
randomDrop: _.partial(importedFns.randomDrop, user),
autoAllocate: _.partial(importedFns.autoAllocate, user),
updateStats: _.partial(importedFns.updateStats, user),
statsComputed: _.partial(statsComputed, user),
cron: _.partial(importedFns.cron, user),
preenUserHistory: _.partial(importedFns.preenUserHistory, user),
ultimateGear: _.partial(importedFns.ultimateGear, user),
nullify: _.partial(importedFns.nullify, user),
};
Object.defineProperty(user, '_statsComputed', {
get () {
return statsComputed(user);
},
get: function() {
var computed;
computed = _.reduce(['per', 'con', 'str', 'int'], (function(_this) {
return function(m, stat) {
m[stat] = _.reduce($w('stats stats.buffs items.gear.equipped.weapon items.gear.equipped.armor items.gear.equipped.head items.gear.equipped.shield'), function(m2, path) {
var item, val;
val = user.fns.dotGet(path);
return m2 + (~path.indexOf('items.gear') ? (item = content.gear.flat[val], (+(item != null ? item[stat] : void 0) || 0) * ((item != null ? item.klass : void 0) === user.stats["class"] || (item != null ? item.specialClass : void 0) === user.stats["class"] ? 1.5 : 1)) : +val[stat] || 0);
}, 0);
m[stat] += Math.floor(api.capByLevel(user.stats.lvl) / 2);
return m;
};
})(this), {});
computed.maxMP = computed.int * 2 + 30;
return computed;
}
});
return Object.defineProperty(user, 'tasks', {
get: function() {
var tasks;
tasks = user.habits.concat(user.dailys).concat(user.todos).concat(user.rewards);
return _.object(_.pluck(tasks, "id"), tasks);
}
});
};
+12 -7
View File
@@ -1,14 +1,19 @@
import _ from 'lodash';
/*
Are there tags applied?
*/
// TODO move to client
module.exports = function appliedTags (userTags, taskTags = []) {
let arr = userTags.filter(tag => {
return taskTags.indexOf(tag.id) !== -1;
}).map(tag => {
return tag.name;
module.exports = function(userTags, taskTags) {
var arr;
arr = [];
_.each(userTags, function(t) {
if (t == null) {
return;
}
if (taskTags != null ? taskTags[t.id] : void 0) {
return arr.push(t.name);
}
});
return arr.join(', ');
};
+7
View File
@@ -0,0 +1,7 @@
import _ from 'lodash';
module.exports = function(items) {
return _.reduce(items, (function(m, v) {
return m + (v ? 1 : 0);
}), 0);
};
+7 -3
View File
@@ -1,5 +1,9 @@
import _ from 'lodash';
// TODO remove completely, only used in client
module.exports = _.get;
module.exports = function(obj, path) {
return _.reduce(path.split('.'), ((function(_this) {
return function(curr, next) {
return curr != null ? curr[next] : void 0;
};
})(this)), obj);
};
+12 -3
View File
@@ -1,5 +1,14 @@
import _ from 'lodash';
// TODO remove completely, only used in client
module.exports = _.set;
module.exports = function(obj, path, val) {
var arr;
arr = path.split('.');
return _.reduce(arr, (function(_this) {
return function(curr, next, index) {
if ((arr.length - 1) === index) {
curr[next] = val;
}
return curr[next] != null ? curr[next] : curr[next] = {};
};
})(this), obj);
};
+9
View File
@@ -0,0 +1,9 @@
/*
Encode the download link for .ics iCal file
*/
module.exports = function(uid, apiToken) {
var loc, ref;
loc = (typeof window !== "undefined" && window !== null ? window.location.host : void 0) || (typeof process !== "undefined" && process !== null ? (ref = process.env) != null ? ref.BASE_URL : void 0 : void 0) || '';
return encodeURIComponent("http://" + loc + "/v1/users/" + uid + "/calendar.ics?apiToken=" + apiToken);
};
-42
View File
@@ -1,42 +0,0 @@
import extendableBuiltin from './extendableBuiltin';
// Base class for custom application errors
// It extends Error and capture the stack trace
export class CustomError extends extendableBuiltin(Error) {
constructor () {
super();
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.constructor);
}
}
}
// We specify an httpCode for all errors so that they can be used in the API too
export class NotAuthorized extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 401;
this.message = customMessage || 'Not authorized.';
}
}
export class BadRequest extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 400;
this.message = customMessage || 'Bad request.';
}
}
export class NotFound extends CustomError {
constructor (customMessage) {
super();
this.name = this.constructor.name;
this.httpCode = 404;
this.message = customMessage || 'Not found.';
}
}
-11
View File
@@ -1,11 +0,0 @@
// Babel 6 doesn't support extending native class (Error, Array, ...)
// This function makes it possible to extend native classes with the same results as Babel 5
module.exports = function extendableBuiltin (klass) {
function ExtendableBuiltin () {
klass.apply(this, arguments);
}
ExtendableBuiltin.prototype = Object.create(klass.prototype);
Object.setPrototypeOf(ExtendableBuiltin, klass);
return ExtendableBuiltin;
};
+9
View File
@@ -0,0 +1,9 @@
import moment from 'moment';
/*
Friendly timestamp
*/
module.exports = function(timestamp) {
return moment(timestamp).format('MM/DD h:mm:ss a');
};
+2 -4
View File
@@ -1,9 +1,7 @@
// TODO move to client
module.exports = function gold (num) {
module.exports = function(num) {
if (num) {
return Math.floor(num);
} else {
return '0';
return "0";
}
};
+46
View File
@@ -0,0 +1,46 @@
import uuid from './uuid';
import taskDefaults from './taskDefaults';
import refPush from './refPush';
import splitWhitespace from './splitWhitespace';
import planGemLimits from './planGemLimits';
import preenTodos from './preenTodos';
import dotSet from './dotSet';
import dotGet from './dotGet';
import preenHistory from './preenHistory';
import countExists from './countExists';
import updateStore from './updateStore';
import appliedTags from './appliedTags';
import encodeiCalLink from './encodeiCalLink';
import friendlyTimestamp from './friendlyTimestamp';
import gold from './gold';
import newChatMessages from './newChatMessages';
import noTags from './noTags';
import percent from './percent';
import removeWhitespace from './removeWhitespace';
import silver from './silver';
import taskClasses from './taskClasses';
module.exports = {
uuid,
taskDefaults,
refPush,
splitWhitespace,
planGemLimits,
preenTodos,
dotSet,
dotGet,
preenHistory,
countExists,
updateStore,
appliedTags,
encodeiCalLink,
friendlyTimestamp,
gold,
newChatMessages,
noTags,
percent,
removeWhitespace,
silver,
taskClasses,
};
+10
View File
@@ -0,0 +1,10 @@
/*
Does user have new chat messages?
*/
module.exports = function(messages, lastMessageSeen) {
if (!((messages != null ? messages.length : void 0) > 0)) {
return false;
}
return (messages != null ? messages[0] : void 0) && (messages[0].id !== lastMessageSeen);
};
+2 -4
View File
@@ -4,10 +4,8 @@ import _ from 'lodash';
are any tags active?
*/
// TODO move to client
module.exports = function noTags (tags) {
return _.isEmpty(tags) || _.isEmpty(_.filter(tags, (t) => {
module.exports = function(tags) {
return _.isEmpty(tags) || _.isEmpty(_.filter(tags, function(t) {
return t;
}));
};
+4 -6
View File
@@ -1,12 +1,10 @@
// TODO move to client
module.exports = function percent (x, y, dir) {
let roundFn;
module.exports = function(x, y, dir) {
var roundFn;
switch (dir) {
case 'up':
case "up":
roundFn = Math.ceil;
break;
case 'down':
case "down":
roundFn = Math.floor;
break;
default:
-13
View File
@@ -1,13 +0,0 @@
// An utility to pick deep properties from an object.
// Works like _.pick but supports nested props (ie pickDeep(obj, ['deep.property']))
import _ from 'lodash';
module.exports = function pickDeep (obj, properties) {
if (!_.isArray(properties)) throw new Error('"properties" must be an array');
let result = {};
_.each(properties, (prop) => _.set(result, prop, _.get(obj, prop)));
return result;
};
+43
View File
@@ -0,0 +1,43 @@
import moment from 'moment';
import _ from 'lodash';
/*
Preen history for users with > 7 history entries
This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array
of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week
of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite]
*/
module.exports = function(history) {
var newHistory, preen, thisMonth;
history = _.filter(history, function(h) {
return !!h;
});
newHistory = [];
preen = function(amount, groupBy) {
var groups;
groups = _.chain(history).groupBy(function(h) {
return moment(h.date).format(groupBy);
}).sortBy(function(h, k) {
return k;
}).value();
groups = groups.slice(-amount);
groups.pop();
return _.each(groups, function(group) {
newHistory.push({
date: moment(group[0].date).toDate(),
value: _.reduce(group, (function(m, obj) {
return m + obj.value;
}), 0) / group.length
});
return true;
});
};
preen(50, "YYYY");
preen(moment().format('MM'), "YYYYMM");
thisMonth = moment().format('YYYYMM');
newHistory = newHistory.concat(_.filter(history, function(h) {
return moment(h.date).format('YYYYMM') === thisMonth;
}));
return newHistory;
};
+7 -5
View File
@@ -1,12 +1,14 @@
import moment from 'moment';
import _ from 'lodash';
// TODO used only in v2
/*
Preen 3-day past-completed To-Dos from Angular & mobile app
*/
module.exports = function preenTodos (tasks) {
return _.filter(tasks, (t) => {
return !t.completed || t.challenge && t.challenge.id || moment(t.dateCompleted).isAfter(moment().subtract({
days: 3,
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
}));
});
};
+5 -6
View File
@@ -7,14 +7,13 @@ import uuid from './uuid';
no problem. To maintain sorting, we use these helper functions:
*/
module.exports = function refPush (reflist, item) {
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();
}
reflist[item.id] = item;
return reflist[item.id];
return reflist[item.id] = item;
};
+10
View File
@@ -0,0 +1,10 @@
/*
Remove whitespace #FIXME are we using this anywwhere? Should we be?
*/
module.exports = function(str) {
if (!str) {
return '';
}
return str.replace(/\s/g, '');
};
+3 -6
View File
@@ -2,13 +2,10 @@
Silver amount from their money
*/
// TODO move to client
module.exports = function silver (num) {
module.exports = function(num) {
if (num) {
let centCount = Math.floor((num - Math.floor(num)) * 100);
return `0${centCount}`.slice(-2);
return ("0" + Math.floor((num - Math.floor(num)) * 100)).slice(-2);
} else {
return '00';
return "00";
}
};
+1 -2
View File
@@ -1,4 +1,3 @@
module.exports = function splitWhitespace (s) {
module.exports = function(s) {
return s.split(' ');
};
-28
View File
@@ -1,28 +0,0 @@
import _ from 'lodash';
import content from '../content/index';
import * as statHelpers from '../statHelpers';
module.exports = function statsComputed (user) {
let paths = ['stats', 'stats.buffs', 'items.gear.equipped.weapon', 'items.gear.equipped.armor',
'items.gear.equipped.head', 'items.gear.equipped.shield'];
let computed = _.reduce(['per', 'con', 'str', 'int'], (m, stat) => {
m[stat] = _.reduce(paths, (m2, path) => {
let val = _.get(user, path);
let item = content.gear.flat[val];
if (!item) item = {};
if (!item[stat]) {
item[stat] = 0;
} else {
item[stat] = Number(item[stat]);
}
let thisMultiplier = item.klass === user.stats.class || item.specialClass === user.stats.class ? 1.5 : 1;
let thisReturn = path.indexOf('items.gear') !== -1 ? item[stat] * thisMultiplier : Number(val[stat]);
return m2 + thisReturn || 0;
}, 0);
m[stat] += Math.floor(statHelpers.capByLevel(user.stats.lvl) / 2);
return m;
}, {});
computed.maxMP = computed.int * 2 + 30;
return computed;
};
+34 -31
View File
@@ -1,45 +1,51 @@
import {
shouldDo,
shouldDo
} from '../cron';
/*
Task classes given everything about the class
*/
// TODO move to the client
module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCron = Number(new Date()), showCompleted = false, main = false) {
if (!task) {
return '';
module.exports = function(task, filters, dayStart, lastCron, showCompleted, main) {
var classes, completed, enabled, filter, priority, ref, repeat, type, value;
if (filters == null) {
filters = [];
}
let type = task.type;
let classes = task.type;
let completed = task.completed;
let value = task.value;
let priority = task.priority;
if (main && !task._editing) {
for (let filter in filters) {
let enabled = filters[filter];
if (!task.tags) task.tags = [];
if (enabled && task.tags.indexOf(filter) === -1) {
return 'hidden';
if (dayStart == null) {
dayStart = 0;
}
if (lastCron == null) {
lastCron = +(new Date);
}
if (showCompleted == null) {
showCompleted = false;
}
if (main == null) {
main = false;
}
if (!task) {
return;
}
type = task.type, completed = task.completed, value = task.value, repeat = task.repeat, priority = task.priority;
if (main) {
if (!task._editing) {
for (filter in filters) {
enabled = filters[filter];
if (enabled && !((ref = task.tags) != null ? ref[filter] : void 0)) {
return 'hidden';
}
}
}
}
classes = task.type;
classes = type;
if (task._editing) {
classes += ' beingEdited';
classes += " beingEdited";
}
if (type === 'todo' || type === 'daily') {
if (completed || (type === 'daily' && !shouldDo(Number(new Date()), task, { // eslint-disable-line no-extra-parens
dayStart,
if (completed || (type === 'daily' && !shouldDo(+(new Date), task, {
dayStart: dayStart
}))) {
classes += ' completed';
classes += " completed";
} else {
classes += ' uncompleted';
classes += " uncompleted";
}
} else if (type === 'habit') {
if (task.down && task.up) {
@@ -49,7 +55,6 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro
classes += ' habit-narrow';
}
}
if (priority === 0.1) {
classes += ' difficulty-trivial';
} else if (priority === 1) {
@@ -59,7 +64,6 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro
} else if (priority === 2) {
classes += ' difficulty-hard';
}
if (value < -20) {
classes += ' color-worst';
} else if (value < -10) {
@@ -75,6 +79,5 @@ module.exports = function taskClasses (task, filters = [], dayStart = 0, lastCro
} else {
classes += ' color-best';
}
return classes;
};
+40 -43
View File
@@ -1,74 +1,71 @@
import { v4 as uuid } from 'uuid';
import uuid from './uuid';
import _ from 'lodash';
import moment from 'moment';
// Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before
// sending up to the server for performance
/*
Even though Mongoose handles task defaults, we want to make sure defaults are set on the client-side before
sending up to the server for performance
*/
// TODO move to client code?
// TODO revisit
const tasksTypes = ['habit', 'daily', 'todo', 'reward'];
module.exports = function taskDefaults (task = {}) {
if (!task.type || tasksTypes.indexOf(task.type) === -1) {
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';
}
let defaultId = uuid();
let defaults = {
_id: defaultId,
text: task._id || defaultId,
defaults = {
id: uuid(),
text: task.id != null ? task.id : '',
notes: '',
tags: [],
value: task.type === 'reward' ? 10 : 0,
priority: 1,
challenge: {},
reminders: [],
attribute: 'str',
createdAt: new Date(), // TODO these are going to be overwritten by the server...
updatedAt: new Date(),
dateCreated: new Date()
};
_.defaults(task, defaults);
if (task.type === 'habit' || task.type === 'daily') {
_.defaults(task, {
history: [],
});
}
if (task.type === 'todo' || task.type === 'daily') {
_.defaults(task, {
completed: false,
collapseChecklist: false,
checklist: [],
});
}
if (task.type === 'habit') {
_.defaults(task, {
up: true,
down: true,
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,
su: true,
},
startDate: moment().startOf('day').toDate(),
s: true
}
}, {
startDate: new Date(),
everyX: 1,
frequency: 'weekly',
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;
};
+26 -21
View File
@@ -1,31 +1,36 @@
import _ from 'lodash';
import content from '../content/index';
// Return the list of gear items available for purchase
/*
Update the in-browser store with new gear. FIXME this was in user.fns, but it was causing strange issues there
*/
let sortOrder = _.reduce(content.gearTypes, (accumulator, val, key) => {
accumulator[val] = key;
return accumulator;
}, {});
var sortOrder = _.reduce(content.gearTypes, (function(m, v, k) {
m[v] = k;
return m;
}), {});
module.exports = function updateStore (user) {
let changes = [];
_.each(content.gearTypes, (type) => {
let found = _.find(content.gear.tree[type][user.stats.class], (item) => {
module.exports = function(user) {
var changes;
if (!user) {
return;
}
changes = [];
_.each(content.gearTypes, function(type) {
var found;
found = _.find(content.gear.tree[type][user.stats["class"]], function(item) {
return !user.items.gear.owned[item.key];
});
if (found) changes.push(found);
});
changes = changes.concat(_.filter(content.gear.flat, (val) => {
if (['special', 'mystery', 'armoire'].indexOf(val.klass) !== -1 && !user.items.gear.owned[val.key] && (val.canOwn ? val.canOwn(user) : false)) {
return true;
} else {
return false;
if (found) {
changes.push(found);
}
return true;
});
changes = changes.concat(_.filter(content.gear.flat, function(v) {
var ref;
return ((ref = v.klass) === 'special' || ref === 'mystery' || ref === 'armoire') && !user.items.gear.owned[v.key] && (typeof v.canOwn === "function" ? v.canOwn(user) : void 0);
}));
return _.sortBy(changes, (change) => sortOrder[change.type]);
return _.sortBy(changes, function(c) {
return sortOrder[c.type];
});
};
+1 -4
View File
@@ -1,4 +1 @@
import uuid from 'uuid';
// TODO remove this file completely
module.exports = uuid.v4;
module.exports = require('uuid').v4;
+11 -32
View File
@@ -1,41 +1,20 @@
import _ from 'lodash';
import i18n from '../i18n';
import {
BadRequest,
NotAuthorized,
} from '../libs/errors';
// TODO move to server code
module.exports = function addPushDevice (user, req = {}) {
let regId = _.get(req, 'body.regId');
if (!regId) throw new BadRequest(i18n.t('regIdRequired', req.language));
let type = _.get(req, 'body.type');
if (!type) throw new BadRequest(i18n.t('typeRequired', req.language));
module.exports = function(user, req, cb) {
var i, item, pd;
if (!user.pushDevices) {
user.pushDevices = [];
}
let pushDevices = user.pushDevices;
let item = {
regId,
type,
pd = user.pushDevices;
item = {
regId: req.body.regId,
type: req.body.type
};
let indexOfPushDevice = _.findIndex(pushDevices, {
regId: item.regId,
i = _.findIndex(pd, {
regId: item.regId
});
if (indexOfPushDevice !== -1) {
throw new NotAuthorized(i18n.t('pushDeviceAlreadyAdded', req.language));
if (i === -1) {
pd.push(item);
}
pushDevices.push(item);
return [
user.pushDevices,
i18n.t('pushDeviceAdded', req.language),
];
return typeof cb === "function" ? cb(null, user.pushDevices) : void 0;
};
+4 -9
View File
@@ -1,17 +1,12 @@
import uuid from '../libs/uuid';
import _ from 'lodash';
// TODO used only in client, move there?
module.exports = function addTag (user, req = {}) {
if (!user.tags) {
module.exports = function(user, req, cb) {
if (user.tags == null) {
user.tags = [];
}
user.tags.push({
name: req.body.name,
id: _.get(req, 'body.id') || uuid(),
id: req.body.id || uuid()
});
return user.tags;
return typeof cb === "function" ? cb(null, user.tags) : void 0;
};
+14 -10
View File
@@ -1,23 +1,27 @@
import taskDefaults from '../libs/taskDefaults';
import i18n from '../i18n';
// TODO move to client since it's only used there?
module.exports = function addTask (user, req = {body: {}}) {
let task = taskDefaults(req.body);
user.tasksOrder[`${task.type}s`].unshift(task._id);
user[`${task.type}s`].unshift(task);
module.exports = function(user, req, cb) {
var task;
task = 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;
};
+11 -23
View File
@@ -1,27 +1,15 @@
import refPush from '../libs/refPush';
import validator from 'validator';
import i18n from '../i18n';
import {
BadRequest,
} from '../libs/errors';
import _ from 'lodash';
module.exports = function addWebhook (user, req = {}) {
let wh = user.preferences.webhooks;
if (!validator.isURL(_.get(req, 'body.url'))) throw new BadRequest(i18n.t('invalidUrl', req.language));
if (!validator.isBoolean(_.get(req, 'body.enabled'))) throw new BadRequest(i18n.t('invalidEnabled', req.language));
user.markModified('preferences.webhooks');
if (req.v2 === true) {
return user.preferences.webhooks;
} else {
return [
refPush(wh, {
url: req.body.url,
enabled: req.body.enabled,
}),
];
module.exports = function(user, req, cb) {
var wh;
wh = user.preferences.webhooks;
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;
};
+5 -21
View File
@@ -1,31 +1,15 @@
import _ from 'lodash';
import {
ATTRIBUTES,
} from '../constants';
import {
BadRequest,
NotAuthorized,
} from '../libs/errors';
import i18n from '../i18n';
module.exports = function allocate (user, req = {}) {
let stat = _.get(req, 'query.stat', 'str');
if (ATTRIBUTES.indexOf(stat) === -1) {
throw new BadRequest(i18n.t('invalidAttribute', {attr: stat}, req.language));
}
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++;
}
} else {
throw new NotAuthorized(i18n.t('notEnoughAttrPoints', req.language));
}
return [
user.stats,
];
return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats'))) : void 0;
};
+5 -10
View File
@@ -1,15 +1,10 @@
import _ from 'lodash';
import autoAllocate from '../fns/autoAllocate';
module.exports = function allocateNow (user, req = {}) {
_.times(user.stats.points, () => autoAllocate(user));
module.exports = function(user, req, cb) {
_.times(user.stats.points, user.fns.autoAllocate);
user.stats.points = 0;
if (req.v2 === true) {
return _.pick(user, 'stats');
} else {
return [
user.stats,
];
if (typeof user.markModified === "function") {
user.markModified('stats');
}
return typeof cb === "function" ? cb(null, user.stats) : void 0;
};
+10 -18
View File
@@ -1,21 +1,13 @@
import validator from 'validator';
import i18n from '../i18n';
import {
BadRequest,
} from '../libs/errors';
module.exports = function blockUser (user, req = {}) {
if (!validator.isUUID(req.params.uuid)) throw new BadRequest(i18n.t('invalidUUID', req.language));
let i = user.inbox.blocks.indexOf(req.params.uuid);
if (i === -1) {
user.inbox.blocks.push(req.params.uuid);
} else {
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);
}
user.markModified('inbox.blocks');
return [
user.inbox.blocks,
];
if (typeof user.markModified === "function") {
user.markModified('inbox.blocks');
}
return typeof cb === "function" ? cb(null, user.inbox.blocks) : void 0;
};
+114 -19
View File
@@ -1,24 +1,119 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import {
BadRequest,
} from '../libs/errors';
import buyHealthPotion from './buyHealthPotion';
import buyArmoire from './buyArmoire';
import buyGear from './buyGear';
import count from '../count';
import splitWhitespace from '../libs/splitWhitespace';
module.exports = function buy (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let buyRes;
if (key === 'potion') {
buyRes = buyHealthPotion(user, req, analytics);
} else if (key === 'armoire') {
buyRes = buyArmoire(user, req, analytics);
} else {
buyRes = buyGear(user, req, analytics);
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;
}
return buyRes;
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: '<span class="shop_' + drop.key + ' pull-left"></span>',
dropText: drop.text(req.language)
}, req.language);
if (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: '<span class="Pet_Food_' + drop.key + ' pull-left"></span>',
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, splitWhitespace('items achievements stats flags'));
if (armoireResp) {
buyResp["armoire"] = armoireResp;
}
return typeof cb === "function" ? cb({
code: 200,
message: message
}, buyResp) : void 0;
};
-115
View File
@@ -1,115 +0,0 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import count from '../count';
import splitWhitespace from '../libs/splitWhitespace';
import {
NotAuthorized,
} from '../libs/errors';
import predictableRandom from '../fns/predictableRandom';
import randomVal from '../fns/randomVal';
module.exports = function buyArmoire (user, req = {}, analytics) {
let item = content.armoire;
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
let armoireResp;
let armoireResult;
let eligibleEquipment;
let drop;
let message;
armoireResult = predictableRandom(user, user.stats.gp);
eligibleEquipment = _.filter(content.gear.flat, (eligible) => {
return eligible.klass === 'armoire' && !user.items.gear.owned[eligible.key];
});
if (!_.isEmpty(eligibleEquipment) && (armoireResult < 0.6 || !user.flags.armoireOpened)) {
eligibleEquipment.sort();
drop = randomVal(user, eligibleEquipment);
if (user.items.gear.owned[drop.key]) {
throw new NotAuthorized(i18n.t('equipmentAlradyOwned', req.language));
}
user.items.gear.owned[drop.key] = true;
user.flags.armoireOpened = true;
message = i18n.t('armoireEquipment', {
image: `<span class="shop_${drop.key} pull-left"></span>`,
dropText: drop.text(req.language),
}, req.language);
if (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 < 0.8) || armoireResult < 0.5) { // eslint-disable-line no-extra-parens
drop = randomVal(user, _.where(content.food, {
canDrop: true,
}));
user.items.food[drop.key] = user.items.food[drop.key] || 0;
user.items.food[drop.key] += 1;
message = i18n.t('armoireFood', {
image: `<span class="Pet_Food_${drop.key} pull-left"></span>`,
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 {
let armoireExp = Math.floor(predictableRandom(user, user.stats.exp) * 40 + 10);
user.stats.exp += armoireExp;
message = i18n.t('armoireExp', req.language);
armoireResp = {
type: 'experience',
value: armoireExp,
};
}
user.stats.gp -= item.value;
if (!message) {
message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
}
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: 'Armoire',
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
let resData = _.pick(user, splitWhitespace('items flags'));
if (armoireResp) resData.armoire = armoireResp;
if (req.v2 === true) {
return resData;
} else {
return [
resData,
message,
];
}
};
-70
View File
@@ -1,70 +0,0 @@
import content from '../content/index';
import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors';
import handleTwoHanded from '../fns/handleTwoHanded';
import ultimateGear from '../fns/ultimateGear';
module.exports = function buyGear (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let item = content.gear.flat[key];
if (!item) throw new NotFound(i18n.t('itemNotFound', {key}, req.language));
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
let message;
if (user.items.gear.owned[item.key]) {
throw new NotAuthorized(i18n.t('equipmentAlreadyOwned', req.language));
}
if (user.preferences.autoEquip) {
user.items.gear.equipped[item.type] = item.key;
message = handleTwoHanded(user, item, undefined, req);
}
user.items.gear.owned[item.key] = true;
if (item.last) ultimateGear(user);
user.stats.gp -= item.value;
if (!message) {
message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
}
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: key,
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
if (req.v2 === true) {
return _.pick(user, splitWhitespace('items achievements stats flags'));
} else {
return [
_.pick(user, splitWhitespace('items achievements stats flags')),
message,
];
}
};
-48
View File
@@ -1,48 +0,0 @@
import content from '../content/index';
import i18n from '../i18n';
import {
NotAuthorized,
} from '../libs/errors';
module.exports = function buyHealthPotion (user, req = {}, analytics) {
let item = content.potion;
if (user.stats.gp < item.value) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
}
if (item.canOwn && !item.canOwn(user)) {
throw new NotAuthorized(i18n.t('cannotBuyItem', req.language));
}
user.stats.hp += 15;
if (user.stats.hp > 50) {
user.stats.hp = 50;
}
user.stats.gp -= item.value;
let message = i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language);
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: 'Potion',
acquireMethod: 'Gold',
goldCost: item.value,
category: 'behavior',
});
}
if (req.v2 === true) {
return user.stats;
} else {
return [
user.stats,
message,
];
}
};
+33 -45
View File
@@ -2,54 +2,42 @@ import i18n from '../i18n';
import content from '../content/index';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import pickDeep from '../libs/pickDeep';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors';
module.exports = function buyMysterySet (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
module.exports = function(user, req, cb, analytics) {
var mysterySet, ref;
if (!(user.purchased.plan.consecutive.trinkets > 0)) {
throw new NotAuthorized(i18n.t('notEnoughHourglasses', req.language));
return typeof cb === "function" ? cb({
code: 401,
message: i18n.t('notEnoughHourglasses', req.language)
}) : void 0;
}
let ref = content.timeTravelerStore(user.items.gear.owned);
let mysterySet = ref ? ref[key] : undefined;
if (!mysterySet) {
throw new NotFound(i18n.t('mysterySetNotFound', req.language));
}
if (typeof window !== 'undefined' && window.confirm) { // TODO move to client
if (!window.confirm(i18n.t('hourglassBuyEquipSetConfirm'))) return;
}
_.each(mysterySet.items, item => {
user.items.gear.owned[item.key] = true;
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: item.key,
itemType: 'Subscriber Gear',
acquireMethod: 'Hourglass',
category: 'behavior',
});
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;
}
});
user.purchased.plan.consecutive.trinkets--;
if (req.v2 === true) {
return pickDeep(user, splitWhitespace('items purchased.plan.consecutive'));
} else {
return [
{ items: user.items, purchasedPlanConsecutive: user.purchased.plan.consecutive },
i18n.t('hourglassPurchaseSet', req.language),
];
}
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, splitWhitespace('items purchased.plan.consecutive'))) : void 0;
};
+39 -40
View File
@@ -1,50 +1,49 @@
import i18n from '../i18n';
import content from '../content/index';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors';
import _ from 'lodash';
// buy a quest with gold
module.exports = function buyQuest (user, req = {}, analytics) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let item = content.quests[key];
if (!item) throw new NotFound(i18n.t('questNotFound', {key}, req.language));
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)) {
throw new NotAuthorized(i18n.t('questNotGoldPurchasable', {key}, req.language));
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) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
return typeof cb === "function" ? cb({
code: 401,
message: i18n.t('messageNotEnoughGold', req.language)
}) : void 0;
}
user.items.quests[item.key] = user.items.quests[item.key] || 0;
user.items.quests[item.key]++;
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;
if (analytics) {
analytics.track('acquire item', {
uuid: user._id,
itemKey: item.key,
itemType: 'Market',
goldCost: item.goldValue,
acquireMethod: 'Gold',
category: 'behavior',
});
}
if (req.v2 === true) {
return user.items.quests;
} else {
return [
user.items.quests,
i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language),
];
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;
};
+21 -25
View File
@@ -2,34 +2,30 @@ import i18n from '../i18n';
import content from '../content/index';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import {
BadRequest,
NotAuthorized,
NotFound,
} from '../libs/errors';
module.exports = function buySpecialSpell (user, req = {}) {
let key = _.get(req, 'params.key');
if (!key) throw new BadRequest(i18n.t('missingKeyParam', req.language));
let item = content.special[key];
if (!item) throw new NotFound(i18n.t('spellNotFound', {spellId: key}, req.language));
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) {
throw new NotAuthorized(i18n.t('messageNotEnoughGold', req.language));
return typeof cb === "function" ? cb({
code: 401,
message: i18n.t('messageNotEnoughGold', req.language)
}) : void 0;
}
user.stats.gp -= item.value;
user.items.special[key]++;
if (req.v2 === true) {
return _.pick(user, splitWhitespace('items stats'));
} else {
return [
_.pick(user, splitWhitespace('items stats')),
i18n.t('messageBought', {
itemText: item.text(req.language),
}, req.language),
];
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, splitWhitespace('items stats'))) : void 0;
};
+44 -63
View File
@@ -2,77 +2,58 @@ import i18n from '../i18n';
import _ from 'lodash';
import splitWhitespace from '../libs/splitWhitespace';
import { capByLevel } from '../statHelpers';
import {
NotAuthorized,
} from '../libs/errors';
module.exports = function changeClass (user, req = {}, analytics) {
let klass = _.get(req, 'query.class');
// user.flags.classSelected is set to false after the user paid the 3 gems
if (user.stats.lvl < 10) {
throw new NotAuthorized(i18n.t('lvl10ChangeClass', req.language));
} else if (!user.flags.classSelected && (klass === 'warrior' || klass === 'rogue' || klass === 'wizard' || klass === 'healer')) {
user.stats.class = klass;
user.flags.classSelected = true;
_.each(['weapon', 'armor', 'shield', 'head'], (type) => {
let foundKey = false;
_.findLast(user.items.gear.owned, (val, key) => {
if (key.indexOf(`${type}_${klass}`) !== -1 && val === true) {
foundKey = key;
return true;
}
});
if (!foundKey) {
if (type === 'weapon') {
foundKey = `weapon_${klass}_0`;
} else if (type === 'shield' && klass === 'rogue') {
foundKey = 'shield_rogue_0';
} else {
foundKey = `${type}_base_0`;
}
}
user.items.gear.equipped[type] = foundKey;
if (type === 'weapon' || (type === 'shield' && klass === 'rogue')) { // eslint-disable-line no-extra-parens
user.items.gear.owned[`${type}_${klass}_0`] = true;
}
});
if (analytics) {
analytics.track('change class', {
uuid: user._id,
class: klass,
acquireMethod: 'Gems',
gemCost: 3,
category: 'behavior',
});
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 < 0.75) throw new NotAuthorized(i18n.t('notEnoughGems', req.language));
user.balance -= 0.75;
if (!(user.balance >= .75)) {
return typeof cb === "function" ? cb({
code: 401,
message: i18n.t('notEnoughGems', req.language)
}) : void 0;
}
user.balance -= .75;
}
user.stats.str = 0;
user.stats.con = 0;
user.stats.per = 0;
user.stats.int = 0;
user.stats.points = capByLevel(user.stats.lvl);
_.merge(user.stats, {
str: 0,
con: 0,
per: 0,
int: 0,
points: capByLevel(user.stats.lvl)
});
user.flags.classSelected = false;
}
if (req.v2 === true) {
return _.pick(user, splitWhitespace('stats flags items preferences'));
} else {
return [
_.pick(user, splitWhitespace('stats flags items preferences')),
];
}
return typeof cb === "function" ? cb(null, _.pick(user, splitWhitespace('stats flags items preferences'))) : void 0;
};
+8 -6
View File
@@ -1,10 +1,12 @@
import _ from 'lodash';
// TODO move to client since it's only used there?
// TODO rename file to clearCompletedTodos
module.exports = function clearCompletedTodos (todos) {
_.remove(todos, todo => {
return todo.completed && (!todo.challenge || !todo.challenge.id || todo.challenge.broken);
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;
};
+5 -5
View File
@@ -1,7 +1,7 @@
module.exports = function clearPMs (user) {
module.exports = function(user, req, cb) {
user.inbox.messages = {};
user.markModified('inbox.messages');
return [
user.inbox.messages,
];
if (typeof user.markModified === "function") {
user.markModified('inbox.messages');
}
return typeof cb === "function" ? cb(null, user.inbox.messages) : void 0;
};
+6 -8
View File
@@ -1,9 +1,7 @@
import _ from 'lodash';
module.exports = function deletePM (user, req = {}) {
delete user.inbox.messages[_.get(req, 'params.id')];
user.markModified(`inbox.messages.${req.params.id}`);
return [
user.inbox.messages,
];
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;
};

Some files were not shown because too many files have changed in this diff Show More