mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 06:07:21 +01:00
split user.fns
This commit is contained in:
56
common/script/fns/autoAllocate.js
Normal file
56
common/script/fns/autoAllocate.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import splitWhitespace from '../libs/splitWhitespace';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Updates user stats with new stats. Handles death, leveling up, etc
|
||||||
|
{stats} new stats
|
||||||
|
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
|
||||||
|
*/
|
||||||
|
|
||||||
|
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 "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";
|
||||||
|
}
|
||||||
|
})()]++;
|
||||||
|
};
|
||||||
15
common/script/fns/crit.js
Normal file
15
common/script/fns/crit.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
279
common/script/fns/cron.js
Normal file
279
common/script/fns/cron.js
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
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;
|
||||||
|
if (options == null) {
|
||||||
|
options = {};
|
||||||
|
}
|
||||||
|
now = +options.now || +(new Date);
|
||||||
|
daysMissed = daysSince(user.lastCron, _.defaults({
|
||||||
|
now: now
|
||||||
|
}, user.preferences));
|
||||||
|
if (!(daysMissed > 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
user.auth.timestamps.loggedin = new Date();
|
||||||
|
user.lastCron = now;
|
||||||
|
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;
|
||||||
|
};
|
||||||
5
common/script/fns/dotGet.js
Normal file
5
common/script/fns/dotGet.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import dotGet from '../libs/dotGet';
|
||||||
|
|
||||||
|
module.exports = function(user, path) {
|
||||||
|
return dotGet(user, path);
|
||||||
|
};
|
||||||
12
common/script/fns/dotSet.js
Normal file
12
common/script/fns/dotSet.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(user, path, val) {
|
||||||
|
return dotSet(user, path, val);
|
||||||
|
};
|
||||||
11
common/script/fns/getItem.js
Normal file
11
common/script/fns/getItem.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import content from '../content/index';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
|
module.exports = function(user, type) {
|
||||||
|
var item;
|
||||||
|
item = content.gear.flat[user.items.gear.equipped[type]];
|
||||||
|
if (!item) {
|
||||||
|
return content.gear.flat[type + "_base_0"];
|
||||||
|
}
|
||||||
|
return item;
|
||||||
|
};
|
||||||
24
common/script/fns/handleTwoHanded.js
Normal file
24
common/script/fns/handleTwoHanded.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import content from '../content/index';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
|
||||||
|
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];
|
||||||
|
|
||||||
|
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";
|
||||||
|
message = i18n.t('messageTwoHandedEquip', {
|
||||||
|
twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language),
|
||||||
|
}, req.language);
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
};
|
||||||
31
common/script/fns/index.js
Normal file
31
common/script/fns/index.js
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import getItem from './getItem';
|
||||||
|
import handleTwoHanded from './handleTwoHanded';
|
||||||
|
import predictableRandom from './predictableRandom';
|
||||||
|
import crit from './crit';
|
||||||
|
import randomVal from './randomVal';
|
||||||
|
import dotSet from './dotSet';
|
||||||
|
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,
|
||||||
|
randomVal,
|
||||||
|
dotSet,
|
||||||
|
dotGet,
|
||||||
|
randomDrop,
|
||||||
|
autoAllocate,
|
||||||
|
updateStats,
|
||||||
|
cron,
|
||||||
|
preenUserHistory,
|
||||||
|
ultimateGear,
|
||||||
|
nullify,
|
||||||
|
};
|
||||||
5
common/script/fns/nullify.js
Normal file
5
common/script/fns/nullify.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module.exports = function(user) {
|
||||||
|
user.ops = null;
|
||||||
|
user.fns = null;
|
||||||
|
return user = null;
|
||||||
|
};
|
||||||
20
common/script/fns/predictableRandom.js
Normal file
20
common/script/fns/predictableRandom.js
Normal file
@@ -0,0 +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
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(user, seed) {
|
||||||
|
var x;
|
||||||
|
if (!seed || seed === Math.PI) {
|
||||||
|
seed = _.reduce(user.stats, (function(m, v) {
|
||||||
|
if (_.isNumber(v)) {
|
||||||
|
return m + v;
|
||||||
|
} else {
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
}), 0);
|
||||||
|
}
|
||||||
|
x = Math.sin(seed++) * 10000;
|
||||||
|
return x - Math.floor(x);
|
||||||
|
};
|
||||||
25
common/script/fns/preenUserHistory.js
Normal file
25
common/script/fns/preenUserHistory.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import preenHistory from '../libs/preenHistory';
|
||||||
|
|
||||||
|
module.exports = function(user, minHistLen) {
|
||||||
|
if (minHistLen == null) {
|
||||||
|
minHistLen = 7;
|
||||||
|
}
|
||||||
|
_.each(user.habits.concat(user.dailys), function(task) {
|
||||||
|
var ref;
|
||||||
|
if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) {
|
||||||
|
task.history = preenHistory(task.history);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
_.defaults(user.history, {
|
||||||
|
todos: [],
|
||||||
|
exp: []
|
||||||
|
});
|
||||||
|
if (user.history.exp.length > minHistLen) {
|
||||||
|
user.history.exp = preenHistory(user.history.exp);
|
||||||
|
}
|
||||||
|
if (user.history.todos.length > minHistLen) {
|
||||||
|
return user.history.todos = preenHistory(user.history.todos);
|
||||||
|
}
|
||||||
|
};
|
||||||
75
common/script/fns/randomDrop.js
Normal file
75
common/script/fns/randomDrop.js
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import content from '../content/index';
|
||||||
|
import i18n from '../i18n';
|
||||||
|
import { daysSince } from '../cron';
|
||||||
|
import { diminishingReturns } from '../statHelpers';
|
||||||
|
|
||||||
|
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 + .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);
|
||||||
|
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
|
||||||
|
});
|
||||||
|
user.party.quest.progress.collect[dropK]++;
|
||||||
|
if (typeof user.markModified === "function") {
|
||||||
|
user.markModified('party.quest.progress');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 (((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 = 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;
|
||||||
|
drop.type = 'Food';
|
||||||
|
drop.dialog = i18n.t('messageDropFood', {
|
||||||
|
dropArticle: drop.article,
|
||||||
|
dropText: drop.text(req.language),
|
||||||
|
dropNotes: drop.notes(req.language)
|
||||||
|
}, req.language);
|
||||||
|
} else if (rarity > .3) {
|
||||||
|
drop = 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)
|
||||||
|
}, req.language);
|
||||||
|
} else {
|
||||||
|
acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
|
||||||
|
drop = user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) {
|
||||||
|
return acceptableDrops.indexOf(k) >= 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)
|
||||||
|
}, req.language);
|
||||||
|
}
|
||||||
|
user._tmp.drop = drop;
|
||||||
|
user.items.lastDrop.date = +(new Date);
|
||||||
|
return user.items.lastDrop.count++;
|
||||||
|
}
|
||||||
|
};
|
||||||
14
common/script/fns/randomVal.js
Normal file
14
common/script/fns/randomVal.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get a random property from an object
|
||||||
|
returns random property (the value)
|
||||||
|
*/
|
||||||
|
|
||||||
|
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)];
|
||||||
|
};
|
||||||
33
common/script/fns/ultimateGear.js
Normal file
33
common/script/fns/ultimateGear.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import content from '../content/index';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
109
common/script/fns/updateStats.js
Normal file
109
common/script/fns/updateStats.js
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import {
|
||||||
|
MAX_HEALTH,
|
||||||
|
MAX_STAT_POINTS
|
||||||
|
} from '../constants';
|
||||||
|
import { toNextLevel } from '../statHelpers';
|
||||||
|
module.exports = function (user, stats, req, analytics) {
|
||||||
|
let allocatedStatPoints;
|
||||||
|
let totalStatPoints;
|
||||||
|
let experienceToNextLevel;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
if (stats.exp >= experienceToNextLevel) {
|
||||||
|
user.stats.exp = stats.exp;
|
||||||
|
|
||||||
|
while (stats.exp >= experienceToNextLevel) {
|
||||||
|
stats.exp -= experienceToNextLevel;
|
||||||
|
user.stats.lvl++;
|
||||||
|
|
||||||
|
experienceToNextLevel = toNextLevel(user.stats.lvl);
|
||||||
|
user.stats.hp = MAX_HEALTH;
|
||||||
|
allocatedStatPoints = user.stats.str + user.stats.int + user.stats.con + user.stats.per;
|
||||||
|
totalStatPoints = allocatedStatPoints + user.stats.points;
|
||||||
|
|
||||||
|
if (totalStatPoints >= MAX_STAT_POINTS) {
|
||||||
|
continue; // eslint-disable-line no-continue
|
||||||
|
}
|
||||||
|
if (user.preferences.automaticAllocation) {
|
||||||
|
user.fns.autoAllocate();
|
||||||
|
} else {
|
||||||
|
user.stats.points = user.stats.lvl - allocatedStatPoints;
|
||||||
|
totalStatPoints = user.stats.points + allocatedStatPoints;
|
||||||
|
|
||||||
|
if (totalStatPoints > MAX_STAT_POINTS) {
|
||||||
|
user.stats.points = MAX_STAT_POINTS - allocatedStatPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.stats.points < 0) {
|
||||||
|
user.stats.points = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
if (!user.flags.itemsEnabled && (user.stats.exp > 10 || user.stats.lvl > 1)) {
|
||||||
|
user.flags.itemsEnabled = true;
|
||||||
|
}
|
||||||
|
if (!user.flags.dropsEnabled && user.stats.lvl >= 3) {
|
||||||
|
user.flags.dropsEnabled = true;
|
||||||
|
if (user.items.eggs["Wolf"] > 0) {
|
||||||
|
user.items.eggs["Wolf"]++;
|
||||||
|
} else {
|
||||||
|
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
|
||||||
|
}, 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.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
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
|
||||||
|
return user.flags.rebirthEnabled = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -32,73 +32,12 @@ api.tnl = statHelpers.toNextLevel;
|
|||||||
api.diminishingReturns = statHelpers.diminishingReturns;
|
api.diminishingReturns = statHelpers.diminishingReturns;
|
||||||
|
|
||||||
$w = api.$w = importedLibs.splitWhitespace;
|
$w = api.$w = importedLibs.splitWhitespace;
|
||||||
|
api.dotSet = importedLibs.dotSet;
|
||||||
api.dotSet = function(obj, path, val) {
|
api.dotGet = importedLibs.dotGet;
|
||||||
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);
|
|
||||||
};
|
|
||||||
|
|
||||||
api.dotGet = function(obj, path) {
|
|
||||||
return _.reduce(path.split('.'), ((function(_this) {
|
|
||||||
return function(curr, next) {
|
|
||||||
return curr != null ? curr[next] : void 0;
|
|
||||||
};
|
|
||||||
})(this)), obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
api.refPush = importedLibs.refPush;
|
api.refPush = importedLibs.refPush;
|
||||||
|
|
||||||
api.planGemLimits = importedLibs.planGemLimits;
|
api.planGemLimits = importedLibs.planGemLimits;
|
||||||
|
|
||||||
/*
|
preenHistory = importedLibs.preenHistory;
|
||||||
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]
|
|
||||||
*/
|
|
||||||
|
|
||||||
preenHistory = 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Preen 3-day past-completed To-Dos from Angular & mobile app
|
Preen 3-day past-completed To-Dos from Angular & mobile app
|
||||||
@@ -414,6 +353,7 @@ TODO
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import importedOps from './ops';
|
import importedOps from './ops';
|
||||||
|
import importedFns from './fns';
|
||||||
|
|
||||||
api.wrap = function(user, main) {
|
api.wrap = function(user, main) {
|
||||||
if (main == null) {
|
if (main == null) {
|
||||||
@@ -431,7 +371,7 @@ api.wrap = function(user, main) {
|
|||||||
reset: _.partial(importedOps.reset, user),
|
reset: _.partial(importedOps.reset, user),
|
||||||
reroll: _.partial(importedOps.reroll, user),
|
reroll: _.partial(importedOps.reroll, user),
|
||||||
rebirth: _.partial(importedOps.rebirth, user),
|
rebirth: _.partial(importedOps.rebirth, user),
|
||||||
allocateNow: _.partial(importedOps.reroll, user),
|
allocateNow: _.partial(importedOps.allocateNow, user),
|
||||||
clearCompleted: _.partial(importedOps.clearCompleted, user),
|
clearCompleted: _.partial(importedOps.clearCompleted, user),
|
||||||
sortTask: _.partial(importedOps.sortTask, user),
|
sortTask: _.partial(importedOps.sortTask, user),
|
||||||
updateTask: _.partial(importedOps.updateTask, user),
|
updateTask: _.partial(importedOps.updateTask, user),
|
||||||
@@ -473,645 +413,20 @@ api.wrap = function(user, main) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
user.fns = {
|
user.fns = {
|
||||||
getItem: function(type) {
|
getItem: _.partial(importedFns.getItem, user),
|
||||||
var item;
|
handleTwoHanded: _.partial(importedFns.handleTwoHanded, user),
|
||||||
item = content.gear.flat[user.items.gear.equipped[type]];
|
predictableRandom: _.partial(importedFns.predictableRandom, user),
|
||||||
if (!item) {
|
crit: _.partial(importedFns.crit, user),
|
||||||
return content.gear.flat[type + "_base_0"];
|
randomVal: _.partial(importedFns.randomVal, user),
|
||||||
}
|
dotSet: _.partial(importedFns.dotSet, user),
|
||||||
return item;
|
dotGet: _.partial(importedFns.dotGet, user),
|
||||||
},
|
randomDrop: _.partial(importedFns.randomDrop, user),
|
||||||
handleTwoHanded: function(item, type, req) {
|
autoAllocate: _.partial(importedFns.autoAllocate, user),
|
||||||
var message, currentWeapon, currentShield;
|
updateStats: _.partial(importedFns.updateStats, user),
|
||||||
if (type == null) {
|
cron: _.partial(importedFns.cron, user),
|
||||||
type = 'equipped';
|
preenUserHistory: _.partial(importedFns.preenUserHistory, user),
|
||||||
}
|
ultimateGear: _.partial(importedFns.ultimateGear, user),
|
||||||
currentShield = content.gear.flat[user.items.gear[type].shield];
|
nullify: _.partial(importedFns.nullify, user),
|
||||||
currentWeapon = content.gear.flat[user.items.gear[type].weapon];
|
|
||||||
|
|
||||||
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";
|
|
||||||
message = i18n.t('messageTwoHandedEquip', {
|
|
||||||
twoHandedText: item.text(req.language), offHandedText: currentShield.text(req.language),
|
|
||||||
}, req.language);
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
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
|
|
||||||
*/
|
|
||||||
predictableRandom: function(seed) {
|
|
||||||
var x;
|
|
||||||
if (!seed || seed === Math.PI) {
|
|
||||||
seed = _.reduce(user.stats, (function(m, v) {
|
|
||||||
if (_.isNumber(v)) {
|
|
||||||
return m + v;
|
|
||||||
} else {
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
}), 0);
|
|
||||||
}
|
|
||||||
x = Math.sin(seed++) * 10000;
|
|
||||||
return x - Math.floor(x);
|
|
||||||
},
|
|
||||||
crit: function(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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
Get a random property from an object
|
|
||||||
returns random property (the value)
|
|
||||||
*/
|
|
||||||
randomVal: function(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)];
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
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.
|
|
||||||
*/
|
|
||||||
dotSet: function(path, val) {
|
|
||||||
return api.dotSet(user, path, val);
|
|
||||||
},
|
|
||||||
dotGet: function(path) {
|
|
||||||
return api.dotGet(user, path);
|
|
||||||
},
|
|
||||||
randomDrop: function(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 + .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 = api.diminishingReturns(chance, 0.75);
|
|
||||||
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
|
|
||||||
});
|
|
||||||
user.party.quest.progress.collect[dropK]++;
|
|
||||||
if (typeof user.markModified === "function") {
|
|
||||||
user.markModified('party.quest.progress');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 (((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 = 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;
|
|
||||||
drop.type = 'Food';
|
|
||||||
drop.dialog = i18n.t('messageDropFood', {
|
|
||||||
dropArticle: drop.article,
|
|
||||||
dropText: drop.text(req.language),
|
|
||||||
dropNotes: drop.notes(req.language)
|
|
||||||
}, req.language);
|
|
||||||
} else if (rarity > .3) {
|
|
||||||
drop = 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)
|
|
||||||
}, req.language);
|
|
||||||
} else {
|
|
||||||
acceptableDrops = rarity < .02 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
|
|
||||||
drop = user.fns.randomVal(_.pick(content.hatchingPotions, (function(v, k) {
|
|
||||||
return indexOf.call(acceptableDrops, k) >= 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)
|
|
||||||
}, req.language);
|
|
||||||
}
|
|
||||||
user._tmp.drop = drop;
|
|
||||||
user.items.lastDrop.date = +(new Date);
|
|
||||||
return user.items.lastDrop.count++;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
Updates user stats with new stats. Handles death, leveling up, etc
|
|
||||||
{stats} new stats
|
|
||||||
{update} if aggregated changes, pass in userObj as update. otherwise commits will be made immediately
|
|
||||||
*/
|
|
||||||
autoAllocate: function() {
|
|
||||||
return user.stats[(function() {
|
|
||||||
var diff, ideal, lvlDiv7, preference, stats, suggested;
|
|
||||||
switch (user.preferences.allocationMode) {
|
|
||||||
case "flat":
|
|
||||||
stats = _.pick(user.stats, $w('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 "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";
|
|
||||||
}
|
|
||||||
})()]++;
|
|
||||||
},
|
|
||||||
updateStats (stats, req, analytics) {
|
|
||||||
let allocatedStatPoints;
|
|
||||||
let totalStatPoints;
|
|
||||||
let experienceToNextLevel;
|
|
||||||
|
|
||||||
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 = api.tnl(user.stats.lvl);
|
|
||||||
|
|
||||||
if (stats.exp >= experienceToNextLevel) {
|
|
||||||
user.stats.exp = stats.exp;
|
|
||||||
|
|
||||||
while (stats.exp >= experienceToNextLevel) {
|
|
||||||
stats.exp -= experienceToNextLevel;
|
|
||||||
user.stats.lvl++;
|
|
||||||
|
|
||||||
experienceToNextLevel = api.tnl(user.stats.lvl);
|
|
||||||
user.stats.hp = MAX_HEALTH;
|
|
||||||
allocatedStatPoints = user.stats.str + user.stats.int + user.stats.con + user.stats.per;
|
|
||||||
totalStatPoints = allocatedStatPoints + user.stats.points;
|
|
||||||
|
|
||||||
if (totalStatPoints >= MAX_STAT_POINTS) {
|
|
||||||
continue; // eslint-disable-line no-continue
|
|
||||||
}
|
|
||||||
if (user.preferences.automaticAllocation) {
|
|
||||||
user.fns.autoAllocate();
|
|
||||||
} else {
|
|
||||||
user.stats.points = user.stats.lvl - allocatedStatPoints;
|
|
||||||
totalStatPoints = user.stats.points + allocatedStatPoints;
|
|
||||||
|
|
||||||
if (totalStatPoints > MAX_STAT_POINTS) {
|
|
||||||
user.stats.points = MAX_STAT_POINTS - allocatedStatPoints;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.stats.points < 0) {
|
|
||||||
user.stats.points = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
if (!user.flags.itemsEnabled && (user.stats.exp > 10 || user.stats.lvl > 1)) {
|
|
||||||
user.flags.itemsEnabled = true;
|
|
||||||
}
|
|
||||||
if (!user.flags.dropsEnabled && user.stats.lvl >= 3) {
|
|
||||||
user.flags.dropsEnabled = true;
|
|
||||||
if (user.items.eggs["Wolf"] > 0) {
|
|
||||||
user.items.eggs["Wolf"]++;
|
|
||||||
} else {
|
|
||||||
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
|
|
||||||
}, 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.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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) {
|
|
||||||
return user.flags.rebirthEnabled = true;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/*
|
|
||||||
------------------------------------------------------
|
|
||||||
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}
|
|
||||||
*/
|
|
||||||
cron: function(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;
|
|
||||||
if (options == null) {
|
|
||||||
options = {};
|
|
||||||
}
|
|
||||||
now = +options.now || +(new Date);
|
|
||||||
daysMissed = daysSince(user.lastCron, _.defaults({
|
|
||||||
now: now
|
|
||||||
}, user.preferences));
|
|
||||||
if (!(daysMissed > 0)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
user.auth.timestamps.loggedin = new Date();
|
|
||||||
user.lastCron = now;
|
|
||||||
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 += api.tnl(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(api.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;
|
|
||||||
},
|
|
||||||
preenUserHistory: function(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);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
ultimateGear: function() {
|
|
||||||
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) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
nullify: function() {
|
|
||||||
user.ops = null;
|
|
||||||
user.fns = null;
|
|
||||||
return user = null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
Object.defineProperty(user, '_statsComputed', {
|
Object.defineProperty(user, '_statsComputed', {
|
||||||
get: function() {
|
get: function() {
|
||||||
|
|||||||
9
common/script/libs/dotGet.js
Normal file
9
common/script/libs/dotGet.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
module.exports = function(obj, path) {
|
||||||
|
return _.reduce(path.split('.'), ((function(_this) {
|
||||||
|
return function(curr, next) {
|
||||||
|
return curr != null ? curr[next] : void 0;
|
||||||
|
};
|
||||||
|
})(this)), obj);
|
||||||
|
};
|
||||||
14
common/script/libs/dotSet.js
Normal file
14
common/script/libs/dotSet.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
@@ -4,6 +4,9 @@ import refPush from './refPush';
|
|||||||
import splitWhitespace from './splitWhitespace';
|
import splitWhitespace from './splitWhitespace';
|
||||||
import planGemLimits from './planGemLimits';
|
import planGemLimits from './planGemLimits';
|
||||||
import preenTodos from './preenTodos';
|
import preenTodos from './preenTodos';
|
||||||
|
import dotSet from './dotSet';
|
||||||
|
import dotGet from './dotGet';
|
||||||
|
import preenHistory from './preenHistory';
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
uuid,
|
uuid,
|
||||||
@@ -12,4 +15,7 @@ module.exports = {
|
|||||||
splitWhitespace,
|
splitWhitespace,
|
||||||
planGemLimits,
|
planGemLimits,
|
||||||
preenTodos,
|
preenTodos,
|
||||||
|
dotSet,
|
||||||
|
dotGet,
|
||||||
|
preenHistory,
|
||||||
};
|
};
|
||||||
|
|||||||
43
common/script/libs/preenHistory.js
Normal file
43
common/script/libs/preenHistory.js
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
|
/*
|
||||||
|
Preen history for users with > 7 history entries
|
||||||
|
This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array
|
||||||
|
of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week
|
||||||
|
of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite]
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = function(history) {
|
||||||
|
var newHistory, preen, thisMonth;
|
||||||
|
history = _.filter(history, function(h) {
|
||||||
|
return !!h;
|
||||||
|
});
|
||||||
|
newHistory = [];
|
||||||
|
preen = function(amount, groupBy) {
|
||||||
|
var groups;
|
||||||
|
groups = _.chain(history).groupBy(function(h) {
|
||||||
|
return moment(h.date).format(groupBy);
|
||||||
|
}).sortBy(function(h, k) {
|
||||||
|
return k;
|
||||||
|
}).value();
|
||||||
|
groups = groups.slice(-amount);
|
||||||
|
groups.pop();
|
||||||
|
return _.each(groups, function(group) {
|
||||||
|
newHistory.push({
|
||||||
|
date: moment(group[0].date).toDate(),
|
||||||
|
value: _.reduce(group, (function(m, obj) {
|
||||||
|
return m + obj.value;
|
||||||
|
}), 0) / group.length
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
preen(50, "YYYY");
|
||||||
|
preen(moment().format('MM'), "YYYYMM");
|
||||||
|
thisMonth = moment().format('YYYYMM');
|
||||||
|
newHistory = newHistory.concat(_.filter(history, function(h) {
|
||||||
|
return moment(h.date).format('YYYYMM') === thisMonth;
|
||||||
|
}));
|
||||||
|
return newHistory;
|
||||||
|
};
|
||||||
@@ -12,6 +12,9 @@ const COMMON_FILES = [
|
|||||||
// @TODO remove these negations as the files are converted over.
|
// @TODO remove these negations as the files are converted over.
|
||||||
'!./common/script/index.js',
|
'!./common/script/index.js',
|
||||||
'!./common/script/content/index.js',
|
'!./common/script/content/index.js',
|
||||||
|
'!./common/script/ops/**/*.js',
|
||||||
|
'!./common/script/fns/**/*.js',
|
||||||
|
'!./common/script/libs/**/*.js',
|
||||||
'!./common/script/public/**/*.js',
|
'!./common/script/public/**/*.js',
|
||||||
];
|
];
|
||||||
const TEST_FILES = [
|
const TEST_FILES = [
|
||||||
|
|||||||
Reference in New Issue
Block a user