@@ -1,9 +1,4 @@
|
||||
{
|
||||
"presets": ["es2015"],
|
||||
"plugins": [
|
||||
["transform-async-to-module-method", {
|
||||
"module": "bluebird",
|
||||
"method": "coroutine"
|
||||
}]
|
||||
]
|
||||
"plugins": ["syntax-async-functions","transform-regenerator"]
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
{
|
||||
"directory": "website/client/bower_components"
|
||||
"directory": "website/public/bower_components"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,8 +2,5 @@
|
||||
"extends": [
|
||||
"habitrpg/server",
|
||||
"habitrpg/babel"
|
||||
],
|
||||
"globals": {
|
||||
"Promise": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2,7 +2,7 @@ node_modules/**
|
||||
.bower-cache/**
|
||||
.bower-tmp/**
|
||||
.bower-registry/**
|
||||
website/client/**
|
||||
website/public/**
|
||||
website/views/**
|
||||
website/build/**
|
||||
.git/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 @@
|
||||
web: node ./website/transpiled-babel/index.js
|
||||
web: node ./website/transpiled-babel/server.js
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"ignore": [
|
||||
"**/.*",
|
||||
"node_modules",
|
||||
"website/client/bower_components",
|
||||
"public/bower_components",
|
||||
"test",
|
||||
"tests"
|
||||
],
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
require('babel-polyfill');
|
||||
|
||||
var shared = require('./script/index');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 153 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 123 KiB |
@@ -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;
|
||||
|
||||
|
Before Width: | Height: | Size: 255 KiB After Width: | Height: | Size: 255 KiB |
@@ -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 |
|
Before Width: | Height: | Size: 3.7 KiB |
@@ -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';
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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!"
|
||||
}
|
||||
|
||||
@@ -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,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!",
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
"etherealLion": "Ethereal Lion",
|
||||
"veteranWolf": "Veteran Wolf",
|
||||
"veteranTiger": "Veteran Tiger",
|
||||
"veteranLion": "Veteran Lion",
|
||||
"cerberusPup": "Cerberus Pup",
|
||||
"hydra": "Hydra",
|
||||
"mantisShrimp": "Mantis Shrimp",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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'),
|
||||
|
||||
@@ -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,4 +1,3 @@
|
||||
// TODO what can be moved to /website/server?
|
||||
/*
|
||||
------------------------------------------------------
|
||||
Cron and time / day functions
|
||||
|
||||
@@ -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)]++;
|
||||
})()]++;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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++;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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)];
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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(', ');
|
||||
};
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
module.exports = function(items) {
|
||||
return _.reduce(items, (function(m, v) {
|
||||
return m + (v ? 1 : 0);
|
||||
}), 0);
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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.';
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -0,0 +1,9 @@
|
||||
import moment from 'moment';
|
||||
|
||||
/*
|
||||
Friendly timestamp
|
||||
*/
|
||||
|
||||
module.exports = function(timestamp) {
|
||||
return moment(timestamp).format('MM/DD h:mm:ss a');
|
||||
};
|
||||
@@ -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";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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;
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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
|
||||
}));
|
||||
});
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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, '');
|
||||
};
|
||||
@@ -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,4 +1,3 @@
|
||||
|
||||
module.exports = function splitWhitespace (s) {
|
||||
module.exports = function(s) {
|
||||
return s.split(' ');
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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 +1 @@
|
||||
import uuid from 'uuid';
|
||||
|
||||
// TODO remove this file completely
|
||||
module.exports = uuid.v4;
|
||||
module.exports = require('uuid').v4;
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -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,
|
||||
];
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||