start moving scoring functions from module.exports to user.fns. Will want to clean this up & optimize
This commit is contained in:
Vendored
+345
-363
@@ -10031,7 +10031,7 @@ var global=self;/**
|
||||
|
||||
},{"lodash":3}],6:[function(require,module,exports){
|
||||
var process=require("__browserify_process");(function() {
|
||||
var HP, XP, api, content, dayMapping, moment, preenHistory, randomDrop, randomVal, sanitizeOptions, updateStats, _,
|
||||
var HP, XP, api, content, dayMapping, moment, preenHistory, randomVal, sanitizeOptions, _,
|
||||
__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; };
|
||||
|
||||
moment = require('moment');
|
||||
@@ -10171,64 +10171,6 @@ var process=require("__browserify_process");(function() {
|
||||
return result;
|
||||
};
|
||||
|
||||
randomDrop = function(user, modifiers) {
|
||||
var a, acceptableDrops, alpha, chanceMultiplier, delta, drop, max, priority, rarity, reachedDropLimit, streak, _base, _base1, _base2, _base3, _name, _name1, _name2, _ref;
|
||||
delta = modifiers.delta, priority = modifiers.priority, streak = modifiers.streak;
|
||||
if (streak == null) {
|
||||
streak = 0;
|
||||
}
|
||||
if ((_base = user.items).lastDrop == null) {
|
||||
_base.lastDrop = {
|
||||
date: +moment().subtract('d', 1),
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
reachedDropLimit = (api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= 5);
|
||||
if (reachedDropLimit) {
|
||||
return;
|
||||
}
|
||||
chanceMultiplier = Math.abs(delta);
|
||||
chanceMultiplier *= priority;
|
||||
chanceMultiplier += streak;
|
||||
max = 0.75;
|
||||
a = 0.1;
|
||||
alpha = a * max * chanceMultiplier / (a * chanceMultiplier + max);
|
||||
if (((_ref = user.flags) != null ? _ref.dropsEnabled : void 0) && Math.random() < alpha) {
|
||||
rarity = Math.random();
|
||||
if (rarity > .6) {
|
||||
drop = randomVal(_.omit(content.food, 'Saddle'));
|
||||
if ((_base1 = user.items.food)[_name = drop.name] == null) {
|
||||
_base1[_name] = 0;
|
||||
}
|
||||
user.items.food[drop.name] += 1;
|
||||
drop.type = 'Food';
|
||||
drop.dialog = "You've found a " + drop.text + " Food! " + drop.notes;
|
||||
} else if (rarity > .3) {
|
||||
drop = randomVal(content.eggs);
|
||||
if ((_base2 = user.items.eggs)[_name1 = drop.name] == null) {
|
||||
_base2[_name1] = 0;
|
||||
}
|
||||
user.items.eggs[drop.name]++;
|
||||
drop.type = 'Egg';
|
||||
drop.dialog = "You've found a " + drop.text + " Egg! " + drop.notes;
|
||||
} else {
|
||||
acceptableDrops = rarity < .03 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
|
||||
drop = randomVal(_.pick(content.hatchingPotions, (function(v, k) {
|
||||
return __indexOf.call(acceptableDrops, k) >= 0;
|
||||
})));
|
||||
if ((_base3 = user.items.hatchingPotions)[_name2 = drop.name] == null) {
|
||||
_base3[_name2] = 0;
|
||||
}
|
||||
user.items.hatchingPotions[drop.name]++;
|
||||
drop.type = 'HatchingPotion';
|
||||
drop.dialog = "You've found a " + drop.text + " Hatching Potion! " + drop.notes;
|
||||
}
|
||||
user._tmp.drop = drop;
|
||||
user.items.lastDrop.date = +(new Date);
|
||||
return user.items.lastDrop.count++;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------------------------------------
|
||||
Scoring
|
||||
@@ -10236,282 +10178,12 @@ var process=require("__browserify_process");(function() {
|
||||
*/
|
||||
|
||||
|
||||
api.tnl = function(level) {
|
||||
var value;
|
||||
if (level >= 100) {
|
||||
value = 0;
|
||||
api.tnl = function(lvl) {
|
||||
if (lvl >= 100) {
|
||||
return 0;
|
||||
} else {
|
||||
value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10;
|
||||
return Math.round(((Math.pow(lvl, 2) * 0.25) + (10 * lvl) + 139.75) / 10) * 10;
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
/*
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
|
||||
api.expModifier = function(value, weaponStr, level, priority) {
|
||||
var exp, str, strMod, totalStr;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
str = (level - 1) / 2;
|
||||
totalStr = (str + weaponStr) / 100;
|
||||
strMod = 1 + totalStr;
|
||||
exp = value * XP * strMod * priority;
|
||||
return Math.round(exp);
|
||||
};
|
||||
|
||||
/*
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
|
||||
api.hpModifier = function(value, armorDef, helmDef, shieldDef, level, priority) {
|
||||
var def, defMod, hp, totalDef;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
def = (level - 1) / 2;
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100;
|
||||
defMod = 1 - totalDef;
|
||||
hp = value * HP * defMod * priority;
|
||||
return Math.round(hp * 10) / 10;
|
||||
};
|
||||
|
||||
/*
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
|
||||
api.gpModifier = function(value, modifier, priority, streak, user) {
|
||||
var afterStreak, streakBonus, val;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
val = value * modifier * priority;
|
||||
if (streak && user) {
|
||||
streakBonus = streak / 100 + 1;
|
||||
afterStreak = val * streakBonus;
|
||||
if (val > 0) {
|
||||
user._tmp.streakBonus = afterStreak - val;
|
||||
}
|
||||
return afterStreak;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
*/
|
||||
|
||||
|
||||
api.taskDeltaFormula = function(currentValue, direction) {
|
||||
var delta;
|
||||
if (currentValue < -47.27) {
|
||||
currentValue = -47.27;
|
||||
} else if (currentValue > 21.27) {
|
||||
currentValue = 21.27;
|
||||
}
|
||||
delta = Math.pow(0.9747, currentValue);
|
||||
if (direction === 'up') {
|
||||
return delta;
|
||||
}
|
||||
return -delta;
|
||||
};
|
||||
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
|
||||
updateStats = function(user, newStats) {
|
||||
var gp, tnl;
|
||||
if (user.stats.hp <= 0) {
|
||||
return;
|
||||
}
|
||||
if (newStats.hp != null) {
|
||||
if (newStats.hp <= 0) {
|
||||
user.stats.hp = 0;
|
||||
return;
|
||||
} else {
|
||||
user.stats.hp = newStats.hp;
|
||||
}
|
||||
}
|
||||
if (newStats.exp != null) {
|
||||
tnl = api.tnl(user.stats.lvl);
|
||||
if (user.stats.lvl >= 100) {
|
||||
newStats.gp += newStats.exp / 15;
|
||||
newStats.exp = 0;
|
||||
user.stats.lvl = 100;
|
||||
} else {
|
||||
if (newStats.exp >= tnl) {
|
||||
user.stats.exp = newStats.exp;
|
||||
while (newStats.exp >= tnl && user.stats.lvl < 100) {
|
||||
newStats.exp -= tnl;
|
||||
user.stats.lvl++;
|
||||
tnl = api.tnl(user.stats.lvl);
|
||||
}
|
||||
if (user.stats.lvl === 100) {
|
||||
newStats.exp = 0;
|
||||
}
|
||||
user.stats.hp = 50;
|
||||
}
|
||||
}
|
||||
user.stats.exp = newStats.exp;
|
||||
if (user.flags == null) {
|
||||
user.flags = {};
|
||||
}
|
||||
if (!user.flags.customizationsNotification && (user.stats.exp > 10 || user.stats.lvl > 1)) {
|
||||
user.flags.customizationsNotification = true;
|
||||
}
|
||||
if (!user.flags.itemsEnabled && user.stats.lvl >= 2) {
|
||||
user.flags.itemsEnabled = true;
|
||||
}
|
||||
if (!user.flags.partyEnabled && user.stats.lvl >= 3) {
|
||||
user.flags.partyEnabled = true;
|
||||
}
|
||||
if (!user.flags.dropsEnabled && user.stats.lvl >= 4) {
|
||||
user.flags.dropsEnabled = true;
|
||||
user.items.eggs["Wolf"] = 1;
|
||||
}
|
||||
if (!user.flags.classSelected && user.stats.lvl >= 5) {
|
||||
user.flags.classSelected;
|
||||
}
|
||||
}
|
||||
if (newStats.gp != null) {
|
||||
if ((typeof gp === "undefined" || gp === null) || gp < 0) {
|
||||
gp = 0.0;
|
||||
}
|
||||
return user.stats.gp = newStats.gp;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------------------------------------
|
||||
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}
|
||||
*/
|
||||
|
||||
|
||||
api.cron = function(user, options) {
|
||||
var daysMissed, expTally, lvl, now, todoTally, _base, _base1;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
now = [+options.now || +(new Date)][0];
|
||||
daysMissed = api.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;
|
||||
}
|
||||
if (user.preferences.sleep === true) {
|
||||
return;
|
||||
}
|
||||
todoTally = 0;
|
||||
user.todos.concat(user.dailys).forEach(function(task) {
|
||||
var absVal, completed, id, repeat, scheduleMisses, type;
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
if (user.stats.buffs.stealth && user.stats.buffs.stealth--) {
|
||||
return;
|
||||
}
|
||||
id = task.id, type = task.type, completed = task.completed, repeat = task.repeat;
|
||||
if (!completed) {
|
||||
scheduleMisses = daysMissed;
|
||||
if ((type === 'daily') && repeat) {
|
||||
scheduleMisses = 0;
|
||||
_.times(daysMissed, function(n) {
|
||||
var thatDay;
|
||||
thatDay = moment(now).subtract('days', n + 1);
|
||||
if (api.shouldDo(thatDay, repeat, user.preferences)) {
|
||||
return scheduleMisses++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (scheduleMisses > 0) {
|
||||
user.ops.score({
|
||||
params: {
|
||||
id: task.id,
|
||||
direction: 'down'
|
||||
},
|
||||
query: {
|
||||
times: scheduleMisses,
|
||||
cron: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case 'daily':
|
||||
(task.history != null ? task.history : task.history = []).push({
|
||||
date: +(new Date),
|
||||
value: task.value
|
||||
});
|
||||
return task.completed = false;
|
||||
case 'todo':
|
||||
absVal = completed ? Math.abs(task.value) : task.value;
|
||||
return todoTally += absVal;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
((_base = (user.history != null ? user.history : user.history = {})).todos != null ? (_base = (user.history != null ? user.history : user.history = {})).todos : _base.todos = []).push({
|
||||
date: now,
|
||||
value: todoTally
|
||||
});
|
||||
expTally = user.stats.exp;
|
||||
lvl = 0;
|
||||
while (lvl < (user.stats.lvl - 1)) {
|
||||
lvl++;
|
||||
expTally += api.tnl(lvl);
|
||||
}
|
||||
((_base1 = user.history).exp != null ? (_base1 = user.history).exp : _base1.exp = []).push({
|
||||
date: now,
|
||||
value: expTally
|
||||
});
|
||||
api.preenUserHistory(user);
|
||||
return user;
|
||||
};
|
||||
|
||||
/*
|
||||
@@ -10556,29 +10228,6 @@ var process=require("__browserify_process");(function() {
|
||||
return newHistory;
|
||||
};
|
||||
|
||||
api.preenUserHistory = 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);
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
------------------------------------------------------
|
||||
Content
|
||||
@@ -10882,10 +10531,9 @@ var process=require("__browserify_process");(function() {
|
||||
user on the server is a Mongoose model, so we can use prototype - but to do it on the client, we'd probably have to
|
||||
move to $resource for user
|
||||
* Move to $resource!
|
||||
* Migrate the remaining parts: Tags, feeding pets, etc
|
||||
* Migrate the remaining parts: Tags, party invitiations
|
||||
|
||||
BUGS
|
||||
* User registration doesn't refresh, and you have to click "Play" again (and refresh *yet again* after getting in)
|
||||
* Daily repeats don't have the days showing up (translations issue?)
|
||||
*/
|
||||
|
||||
@@ -11212,7 +10860,7 @@ var process=require("__browserify_process");(function() {
|
||||
}
|
||||
return _.times(times, function() {
|
||||
var nextDelta;
|
||||
nextDelta = api.taskDeltaFormula(value, direction);
|
||||
nextDelta = user.fns.taskDeltaFormula(value, direction);
|
||||
if (adjustvalue) {
|
||||
value += nextDelta;
|
||||
}
|
||||
@@ -11222,11 +10870,11 @@ var process=require("__browserify_process");(function() {
|
||||
addPoints = function() {
|
||||
var weaponStr;
|
||||
weaponStr = user.fns.getItem('weapon').str;
|
||||
exp += api.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2;
|
||||
exp += user.fns.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2;
|
||||
if (streak) {
|
||||
return gp += api.gpModifier(delta, 1, priority, streak, user);
|
||||
return gp += user.fns.gpModifier(delta, 1, priority, streak);
|
||||
} else {
|
||||
return gp += api.gpModifier(delta, 1, priority);
|
||||
return gp += user.fns.gpModifier(delta, 1, priority);
|
||||
}
|
||||
};
|
||||
subtractPoints = function() {
|
||||
@@ -11234,7 +10882,7 @@ var process=require("__browserify_process");(function() {
|
||||
armorDef = user.fns.getItem('armor').con;
|
||||
headDef = user.fns.getItem('head').con;
|
||||
shieldDef = user.fns.getItem('shield').con;
|
||||
return hp += api.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority);
|
||||
return hp += user.fns.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority);
|
||||
};
|
||||
switch (type) {
|
||||
case 'habit':
|
||||
@@ -11380,6 +11028,340 @@ var process=require("__browserify_process");(function() {
|
||||
return _.reduce(path.split('.'), (function(curr, next) {
|
||||
return curr != null ? curr[next] : void 0;
|
||||
}), user);
|
||||
},
|
||||
randomDrop: function(modifiers) {
|
||||
var a, acceptableDrops, alpha, chanceMultiplier, delta, drop, max, priority, rarity, reachedDropLimit, streak, _base, _base1, _base2, _base3, _name, _name1, _name2, _ref;
|
||||
delta = modifiers.delta, priority = modifiers.priority, streak = modifiers.streak;
|
||||
if (streak == null) {
|
||||
streak = 0;
|
||||
}
|
||||
if ((_base = user.items).lastDrop == null) {
|
||||
_base.lastDrop = {
|
||||
date: +moment().subtract('d', 1),
|
||||
count: 0
|
||||
};
|
||||
}
|
||||
reachedDropLimit = (api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= 5);
|
||||
if (reachedDropLimit) {
|
||||
return;
|
||||
}
|
||||
chanceMultiplier = Math.abs(delta);
|
||||
chanceMultiplier *= priority;
|
||||
chanceMultiplier += streak;
|
||||
max = 0.75;
|
||||
a = 0.1;
|
||||
alpha = a * max * chanceMultiplier / (a * chanceMultiplier + max);
|
||||
if (((_ref = user.flags) != null ? _ref.dropsEnabled : void 0) && Math.random() < alpha) {
|
||||
rarity = Math.random();
|
||||
if (rarity > .6) {
|
||||
drop = randomVal(_.omit(content.food, 'Saddle'));
|
||||
if ((_base1 = user.items.food)[_name = drop.name] == null) {
|
||||
_base1[_name] = 0;
|
||||
}
|
||||
user.items.food[drop.name] += 1;
|
||||
drop.type = 'Food';
|
||||
drop.dialog = "You've found a " + drop.text + " Food! " + drop.notes;
|
||||
} else if (rarity > .3) {
|
||||
drop = randomVal(content.eggs);
|
||||
if ((_base2 = user.items.eggs)[_name1 = drop.name] == null) {
|
||||
_base2[_name1] = 0;
|
||||
}
|
||||
user.items.eggs[drop.name]++;
|
||||
drop.type = 'Egg';
|
||||
drop.dialog = "You've found a " + drop.text + " Egg! " + drop.notes;
|
||||
} else {
|
||||
acceptableDrops = rarity < .03 ? ['Golden'] : rarity < .09 ? ['Zombie', 'CottonCandyPink', 'CottonCandyBlue'] : rarity < .18 ? ['Red', 'Shade', 'Skeleton'] : ['Base', 'White', 'Desert'];
|
||||
drop = randomVal(_.pick(content.hatchingPotions, (function(v, k) {
|
||||
return __indexOf.call(acceptableDrops, k) >= 0;
|
||||
})));
|
||||
if ((_base3 = user.items.hatchingPotions)[_name2 = drop.name] == null) {
|
||||
_base3[_name2] = 0;
|
||||
}
|
||||
user.items.hatchingPotions[drop.name]++;
|
||||
drop.type = 'HatchingPotion';
|
||||
drop.dialog = "You've found a " + drop.text + " Hatching Potion! " + drop.notes;
|
||||
}
|
||||
user._tmp.drop = drop;
|
||||
user.items.lastDrop.date = +(new Date);
|
||||
return user.items.lastDrop.count++;
|
||||
}
|
||||
},
|
||||
/*
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
expModifier: function(value, weaponStr, level, priority) {
|
||||
var exp, str, strMod, totalStr;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
str = (level - 1) / 2;
|
||||
totalStr = (str + weaponStr) / 100;
|
||||
strMod = 1 + totalStr;
|
||||
exp = value * XP * strMod * priority;
|
||||
return Math.round(exp);
|
||||
},
|
||||
/*
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
hpModifier: function(value, armorDef, helmDef, shieldDef, level, priority) {
|
||||
var def, defMod, hp, totalDef;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
def = (level - 1) / 2;
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100;
|
||||
defMod = 1 - totalDef;
|
||||
hp = value * HP * defMod * priority;
|
||||
return Math.round(hp * 10) / 10;
|
||||
},
|
||||
/*
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
*/
|
||||
|
||||
gpModifier: function(value, modifier, priority, streak) {
|
||||
var afterStreak, streakBonus, val;
|
||||
if (priority == null) {
|
||||
priority = 1;
|
||||
}
|
||||
val = value * modifier * priority;
|
||||
if (streak && user) {
|
||||
streakBonus = streak / 100 + 1;
|
||||
afterStreak = val * streakBonus;
|
||||
if (val > 0) {
|
||||
user._tmp.streakBonus = afterStreak - val;
|
||||
}
|
||||
return afterStreak;
|
||||
} else {
|
||||
return val;
|
||||
}
|
||||
},
|
||||
/*
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
*/
|
||||
|
||||
taskDeltaFormula: function(currentValue, direction) {
|
||||
var delta;
|
||||
if (currentValue < -47.27) {
|
||||
currentValue = -47.27;
|
||||
} else if (currentValue > 21.27) {
|
||||
currentValue = 21.27;
|
||||
}
|
||||
delta = Math.pow(0.9747, currentValue);
|
||||
if (direction === 'up') {
|
||||
return delta;
|
||||
}
|
||||
return -delta;
|
||||
},
|
||||
/*
|
||||
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
|
||||
*/
|
||||
|
||||
updateStats: function(newStats) {
|
||||
var gp, tnl;
|
||||
if (user.stats.hp <= 0) {
|
||||
return;
|
||||
}
|
||||
if (newStats.hp != null) {
|
||||
if (newStats.hp <= 0) {
|
||||
user.stats.hp = 0;
|
||||
return;
|
||||
} else {
|
||||
user.stats.hp = newStats.hp;
|
||||
}
|
||||
}
|
||||
if (newStats.exp != null) {
|
||||
tnl = api.tnl(user.stats.lvl);
|
||||
if (user.stats.lvl >= 100) {
|
||||
newStats.gp += newStats.exp / 15;
|
||||
newStats.exp = 0;
|
||||
user.stats.lvl = 100;
|
||||
} else {
|
||||
if (newStats.exp >= tnl) {
|
||||
user.stats.exp = newStats.exp;
|
||||
while (newStats.exp >= tnl && user.stats.lvl < 100) {
|
||||
newStats.exp -= tnl;
|
||||
user.stats.lvl++;
|
||||
tnl = api.tnl(user.stats.lvl);
|
||||
}
|
||||
if (user.stats.lvl === 100) {
|
||||
newStats.exp = 0;
|
||||
}
|
||||
user.stats.hp = 50;
|
||||
}
|
||||
}
|
||||
user.stats.exp = newStats.exp;
|
||||
if (user.flags == null) {
|
||||
user.flags = {};
|
||||
}
|
||||
if (!user.flags.customizationsNotification && (user.stats.exp > 10 || user.stats.lvl > 1)) {
|
||||
user.flags.customizationsNotification = true;
|
||||
}
|
||||
if (!user.flags.itemsEnabled && user.stats.lvl >= 2) {
|
||||
user.flags.itemsEnabled = true;
|
||||
}
|
||||
if (!user.flags.partyEnabled && user.stats.lvl >= 3) {
|
||||
user.flags.partyEnabled = true;
|
||||
}
|
||||
if (!user.flags.dropsEnabled && user.stats.lvl >= 4) {
|
||||
user.flags.dropsEnabled = true;
|
||||
user.items.eggs["Wolf"] = 1;
|
||||
}
|
||||
if (!user.flags.classSelected && user.stats.lvl >= 5) {
|
||||
user.flags.classSelected;
|
||||
}
|
||||
}
|
||||
if (newStats.gp != null) {
|
||||
if ((typeof gp === "undefined" || gp === null) || gp < 0) {
|
||||
gp = 0.0;
|
||||
}
|
||||
return user.stats.gp = newStats.gp;
|
||||
}
|
||||
},
|
||||
/*
|
||||
------------------------------------------------------
|
||||
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 daysMissed, expTally, lvl, now, todoTally, _base, _base1;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
now = [+options.now || +(new Date)][0];
|
||||
daysMissed = api.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;
|
||||
}
|
||||
if (user.preferences.sleep === true) {
|
||||
return;
|
||||
}
|
||||
todoTally = 0;
|
||||
user.todos.concat(user.dailys).forEach(function(task) {
|
||||
var absVal, completed, id, repeat, scheduleMisses, type;
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
if (user.stats.buffs.stealth && user.stats.buffs.stealth--) {
|
||||
return;
|
||||
}
|
||||
id = task.id, type = task.type, completed = task.completed, repeat = task.repeat;
|
||||
if (!completed) {
|
||||
scheduleMisses = daysMissed;
|
||||
if ((type === 'daily') && repeat) {
|
||||
scheduleMisses = 0;
|
||||
_.times(daysMissed, function(n) {
|
||||
var thatDay;
|
||||
thatDay = moment(now).subtract('days', n + 1);
|
||||
if (api.shouldDo(thatDay, repeat, user.preferences)) {
|
||||
return scheduleMisses++;
|
||||
}
|
||||
});
|
||||
}
|
||||
if (scheduleMisses > 0) {
|
||||
user.ops.score({
|
||||
params: {
|
||||
id: task.id,
|
||||
direction: 'down'
|
||||
},
|
||||
query: {
|
||||
times: scheduleMisses,
|
||||
cron: true
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
case 'daily':
|
||||
(task.history != null ? task.history : task.history = []).push({
|
||||
date: +(new Date),
|
||||
value: task.value
|
||||
});
|
||||
return task.completed = false;
|
||||
case 'todo':
|
||||
absVal = completed ? Math.abs(task.value) : task.value;
|
||||
return todoTally += absVal;
|
||||
}
|
||||
});
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
((_base = (user.history != null ? user.history : user.history = {})).todos != null ? (_base = (user.history != null ? user.history : user.history = {})).todos : _base.todos = []).push({
|
||||
date: now,
|
||||
value: todoTally
|
||||
});
|
||||
expTally = user.stats.exp;
|
||||
lvl = 0;
|
||||
while (lvl < (user.stats.lvl - 1)) {
|
||||
lvl++;
|
||||
expTally += api.tnl(lvl);
|
||||
}
|
||||
((_base1 = user.history).exp != null ? (_base1 = user.history).exp : _base1.exp = []).push({
|
||||
date: now,
|
||||
value: expTally
|
||||
});
|
||||
user.fns.preenUserHistory();
|
||||
return user;
|
||||
},
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
Object.defineProperty(user, '_statsComputed', {
|
||||
|
||||
+323
-306
@@ -75,78 +75,7 @@ randomVal = (obj) ->
|
||||
result = val if Math.random() < (1 / ++count)
|
||||
result
|
||||
|
||||
randomDrop = (user, modifiers) ->
|
||||
{delta, priority, streak} = modifiers
|
||||
streak ?= 0
|
||||
# limit drops to 2 / day
|
||||
user.items.lastDrop ?=
|
||||
date: +moment().subtract('d', 1) # trick - set it to yesterday on first run, that way they can get drops today
|
||||
count: 0
|
||||
|
||||
reachedDropLimit = (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 5)
|
||||
return if reachedDropLimit
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= priority # multiply chance by reddness
|
||||
chanceMultiplier += streak # streak bonus
|
||||
|
||||
# Temporary solution to lower the maximum drop chance to 75 percent. More thorough
|
||||
# overhaul of drop changes is needed. See HabitRPG/habitrpg#1922 for details.
|
||||
# Old drop chance:
|
||||
# if user.flags?.dropsEnabled and Math.random() < (.05 * chanceMultiplier)
|
||||
max = 0.75 # Max probability of drop
|
||||
a = 0.1 # rate of increase
|
||||
alpha = a*max*chanceMultiplier/(a*chanceMultiplier+max) # current probability of drop
|
||||
|
||||
if user.flags?.dropsEnabled and Math.random() < alpha
|
||||
# current breakdown - 1% (adjustable) chance on drop
|
||||
# If they got a drop: 50% chance of egg, 50% Hatching Potion. If hatchingPotion, broken down further even further
|
||||
rarity = Math.random()
|
||||
|
||||
# Food: 40% chance
|
||||
if rarity > .6
|
||||
drop = randomVal _.omit(content.food, 'Saddle')
|
||||
user.items.food[drop.name] ?= 0
|
||||
user.items.food[drop.name]+= 1
|
||||
drop.type = 'Food'
|
||||
drop.dialog = "You've found a #{drop.text} Food! #{drop.notes}"
|
||||
|
||||
# Eggs: 30% chance
|
||||
else if rarity > .3
|
||||
drop = randomVal content.eggs
|
||||
user.items.eggs[drop.name] ?= 0
|
||||
user.items.eggs[drop.name]++
|
||||
drop.type = 'Egg'
|
||||
drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}"
|
||||
|
||||
# Hatching Potion, 30% chance - break down by rarity.
|
||||
else
|
||||
acceptableDrops =
|
||||
# Very Rare: 10% (of 30%)
|
||||
if rarity < .03 then ['Golden']
|
||||
# Rare: 20% (of 30%)
|
||||
else if rarity < .09 then ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']
|
||||
# Uncommon: 30% (of 30%)
|
||||
else if rarity < .18 then ['Red', 'Shade', 'Skeleton']
|
||||
# Common: 40% (of 30%)
|
||||
else ['Base', 'White', 'Desert']
|
||||
|
||||
# No Rarity (@see https://github.com/HabitRPG/habitrpg/issues/1048, we may want to remove rareness when we add mounts)
|
||||
#drop = helpers.randomVal hatchingPotions
|
||||
drop = randomVal _.pick(content.hatchingPotions, ((v,k) -> k in acceptableDrops))
|
||||
|
||||
user.items.hatchingPotions[drop.name] ?= 0
|
||||
user.items.hatchingPotions[drop.name]++
|
||||
drop.type = 'HatchingPotion'
|
||||
drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}"
|
||||
|
||||
# if they've dropped something, we want the consuming client to know so they can notify the user. See how the Derby
|
||||
# app handles it for example. Would this be better handled as an emit() ?
|
||||
user._tmp.drop = drop
|
||||
|
||||
user.items.lastDrop.date = +new Date
|
||||
user.items.lastDrop.count++
|
||||
|
||||
###
|
||||
------------------------------------------------------
|
||||
@@ -154,222 +83,12 @@ randomDrop = (user, modifiers) ->
|
||||
------------------------------------------------------
|
||||
###
|
||||
|
||||
api.tnl = (level) ->
|
||||
if level >= 100
|
||||
value = 0
|
||||
else
|
||||
value = Math.round(((Math.pow(level, 2) * 0.25) + (10 * level) + 139.75) / 10) * 10
|
||||
# round to nearest 10
|
||||
return value
|
||||
|
||||
###
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
api.expModifier = (value, weaponStr, level, priority=1) ->
|
||||
str = (level - 1) / 2
|
||||
# ultimately get this from user
|
||||
totalStr = (str + weaponStr) / 100
|
||||
strMod = 1 + totalStr
|
||||
exp = value * XP * strMod * priority
|
||||
return Math.round(exp)
|
||||
|
||||
###
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
api.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority=1) ->
|
||||
def = (level - 1) / 2
|
||||
# ultimately get this from user?
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100
|
||||
#ultimate get this from user
|
||||
defMod = 1 - totalDef
|
||||
hp = value * HP * defMod * priority
|
||||
return Math.round(hp * 10) / 10
|
||||
# round to 1dp
|
||||
|
||||
###
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
api.gpModifier = (value, modifier, priority=1, streak, user) ->
|
||||
val = value * modifier * priority
|
||||
if streak and user
|
||||
streakBonus = streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc
|
||||
afterStreak = val * streakBonus
|
||||
user._tmp.streakBonus = afterStreak - val if (val > 0) # keep this on-hand for later, so we can notify streak-bonus
|
||||
return afterStreak
|
||||
else
|
||||
return val
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
###
|
||||
api.taskDeltaFormula = (currentValue, direction) ->
|
||||
if currentValue < -47.27 then currentValue = -47.27
|
||||
else if currentValue > 21.27 then currentValue = 21.27
|
||||
delta = Math.pow(0.9747, currentValue)
|
||||
return delta if direction is 'up'
|
||||
return -delta
|
||||
|
||||
###
|
||||
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
|
||||
###
|
||||
updateStats = (user, newStats) ->
|
||||
# if user is dead, dont do anything
|
||||
return if user.stats.hp <= 0
|
||||
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
user.stats.hp = 0
|
||||
return
|
||||
else
|
||||
user.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
tnl = api.tnl(user.stats.lvl)
|
||||
#silent = false
|
||||
# if we're at level 100, turn xp to gold
|
||||
if user.stats.lvl >= 100
|
||||
newStats.gp += newStats.exp / 15
|
||||
newStats.exp = 0
|
||||
user.stats.lvl = 100
|
||||
else
|
||||
# level up & carry-over exp
|
||||
if newStats.exp >= tnl
|
||||
#silent = true # push through the negative xp silently
|
||||
user.stats.exp = newStats.exp # push normal + notification
|
||||
while newStats.exp >= tnl and user.stats.lvl < 100 # keep levelling up
|
||||
newStats.exp -= tnl
|
||||
user.stats.lvl++
|
||||
tnl = api.tnl(user.stats.lvl)
|
||||
if user.stats.lvl == 100
|
||||
newStats.exp = 0
|
||||
user.stats.hp = 50
|
||||
|
||||
user.stats.exp = newStats.exp
|
||||
|
||||
# Set flags when they unlock features
|
||||
user.flags ?= {}
|
||||
if !user.flags.customizationsNotification and (user.stats.exp > 10 or user.stats.lvl > 1)
|
||||
user.flags.customizationsNotification = true
|
||||
if !user.flags.itemsEnabled and user.stats.lvl >= 2
|
||||
user.flags.itemsEnabled = true
|
||||
if !user.flags.partyEnabled and user.stats.lvl >= 3
|
||||
user.flags.partyEnabled = true
|
||||
if !user.flags.dropsEnabled and user.stats.lvl >= 4
|
||||
user.flags.dropsEnabled = true
|
||||
user.items.eggs["Wolf"] = 1
|
||||
if !user.flags.classSelected and user.stats.lvl >= 5
|
||||
user.flags.classSelected
|
||||
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp < 0)
|
||||
user.stats.gp = newStats.gp
|
||||
api.tnl = (lvl) ->
|
||||
if lvl >= 100 then 0
|
||||
else Math.round(((Math.pow(lvl, 2) * 0.25) + (10 * lvl) + 139.75) / 10) * 10
|
||||
# round to nearest 10?
|
||||
|
||||
|
||||
###
|
||||
------------------------------------------------------
|
||||
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}
|
||||
###
|
||||
api.cron = (user, options={}) ->
|
||||
[now] = [+options.now || +new Date]
|
||||
|
||||
# They went to a different timezone
|
||||
# FIXME:
|
||||
# (1) This exit-early code isn't taking timezone into consideration!!
|
||||
# (2) Won't switching timezones be handled automatically client-side anyway? (aka, can we just remove this code?)
|
||||
# (3) And if not, is this the correct way to handle switching timezones
|
||||
# if moment(user.lastCron).isAfter(now)
|
||||
# user.lastCron = now
|
||||
# return
|
||||
|
||||
daysMissed = api.daysSince user.lastCron, _.defaults({now}, user.preferences)
|
||||
return unless daysMissed > 0
|
||||
|
||||
user.auth.timestamps.loggedin = new Date()
|
||||
|
||||
user.lastCron = now
|
||||
|
||||
# Reset the lastDrop count to zero
|
||||
if user.items.lastDrop.count > 0
|
||||
user.items.lastDrop.count = 0
|
||||
|
||||
# User is resting at the inn. Used to be we un-checked each daily without performing calculation (see commits before fb29e35)
|
||||
# but to prevent abusing the inn (http://goo.gl/GDb9x) we now do *not* calculate dailies, and simply set lastCron to today
|
||||
return if user.preferences.sleep is true
|
||||
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
user.todos.concat(user.dailys).forEach (task) ->
|
||||
return unless task
|
||||
|
||||
return if user.stats.buffs.stealth && user.stats.buffs.stealth-- # User "evades" a certain number of tasks
|
||||
|
||||
{id, type, completed, repeat} = task
|
||||
# Deduct experience for missed Daily tasks, but not for Todos (just increase todo's value)
|
||||
unless completed
|
||||
scheduleMisses = daysMissed
|
||||
# for dailys which have repeat dates, need to calculate how many they've missed according to their own schedule
|
||||
if (type is 'daily') and repeat
|
||||
scheduleMisses = 0
|
||||
_.times daysMissed, (n) ->
|
||||
thatDay = moment(now).subtract('days', n + 1)
|
||||
scheduleMisses++ if api.shouldDo(thatDay, repeat, user.preferences)
|
||||
if scheduleMisses > 0
|
||||
user.ops.score({params:{id:task.id, direction:'down'}, query:{times:scheduleMisses, cron:true}})
|
||||
|
||||
switch type
|
||||
when 'daily'
|
||||
(task.history ?= []).push({ date: +new Date, value: task.value })
|
||||
task.completed = false
|
||||
when 'todo'
|
||||
#get updated value
|
||||
absVal = if (completed) then Math.abs(task.value) else task.value
|
||||
todoTally += absVal
|
||||
|
||||
user.habits.forEach (task) -> # slowly reset 'onlies' value to 0
|
||||
if task.up is false or task.down is false
|
||||
if Math.abs(task.value) < 0.1
|
||||
task.value = 0
|
||||
else
|
||||
task.value = task.value / 2
|
||||
|
||||
|
||||
# Finished tallying
|
||||
((user.history ?= {}).todos ?= []).push { date: now, value: todoTally }
|
||||
# tally experience
|
||||
expTally = user.stats.exp
|
||||
lvl = 0 #iterator
|
||||
while lvl < (user.stats.lvl - 1)
|
||||
lvl++
|
||||
expTally += api.tnl(lvl)
|
||||
(user.history.exp ?= []).push { date: now, value: expTally }
|
||||
api.preenUserHistory(user)
|
||||
user
|
||||
|
||||
###
|
||||
Preen history for users with > 7 history entries
|
||||
@@ -407,20 +126,6 @@ preenHistory = (history) ->
|
||||
|
||||
newHistory
|
||||
|
||||
# Registered users with some history
|
||||
api.preenUserHistory = (user, minHistLen = 7) ->
|
||||
_.each user.habits.concat(user.dailys), (task) ->
|
||||
task.history = preenHistory(task.history) if task.history?.length > minHistLen
|
||||
true
|
||||
|
||||
_.defaults user.history, {todos:[], exp: []}
|
||||
user.history.exp = preenHistory(user.history.exp) if user.history.exp.length > minHistLen
|
||||
user.history.todos = preenHistory(user.history.todos) if user.history.todos.length > minHistLen
|
||||
#user.markModified('history')
|
||||
#user.markModified('habits')
|
||||
#user.markModified('dailys')
|
||||
|
||||
|
||||
###
|
||||
------------------------------------------------------
|
||||
Content
|
||||
@@ -612,16 +317,19 @@ TODO
|
||||
user on the server is a Mongoose model, so we can use prototype - but to do it on the client, we'd probably have to
|
||||
move to $resource for user
|
||||
* Move to $resource!
|
||||
* Migrate the remaining parts: Tags, feeding pets, etc
|
||||
* Migrate the remaining parts: Tags, party invitiations
|
||||
|
||||
BUGS
|
||||
* User registration doesn't refresh, and you have to click "Play" again (and refresh *yet again* after getting in)
|
||||
* Daily repeats don't have the days showing up (translations issue?)
|
||||
###
|
||||
api.wrap = (user) ->
|
||||
return if user._wrapped
|
||||
user._wrapped = true
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# user.ops shared client/server operations
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
user.ops =
|
||||
|
||||
sleep: (req, cb) ->
|
||||
@@ -843,23 +551,23 @@ api.wrap = (user) ->
|
||||
# Each iteration calculate the delta (nextDelta), which is then accumulated in delta
|
||||
# (aka, the total delta). This weirdness won't be necessary when calculating mathematically
|
||||
# rather than iteratively
|
||||
nextDelta = api.taskDeltaFormula(value, direction)
|
||||
nextDelta = user.fns.taskDeltaFormula(value, direction)
|
||||
value += nextDelta if adjustvalue
|
||||
delta += nextDelta
|
||||
|
||||
addPoints = ->
|
||||
weaponStr = user.fns.getItem('weapon').str
|
||||
exp += api.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2 # /2 hack for now, people leveling too fast
|
||||
exp += user.fns.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2 # /2 hack for now, people leveling too fast
|
||||
if streak
|
||||
gp += api.gpModifier(delta, 1, priority, streak, user)
|
||||
gp += user.fns.gpModifier(delta, 1, priority, streak)
|
||||
else
|
||||
gp += api.gpModifier(delta, 1, priority)
|
||||
gp += user.fns.gpModifier(delta, 1, priority)
|
||||
|
||||
subtractPoints = ->
|
||||
armorDef = user.fns.getItem('armor').con
|
||||
headDef = user.fns.getItem('head').con
|
||||
shieldDef = user.fns.getItem('shield').con
|
||||
hp += api.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority)
|
||||
hp += user.fns.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority)
|
||||
|
||||
switch type
|
||||
when 'habit'
|
||||
@@ -922,6 +630,10 @@ api.wrap = (user) ->
|
||||
cb? null, req
|
||||
return delta
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# user.fns helpers
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
user.fns =
|
||||
getItem: (type) ->
|
||||
item = content.gear.flat[user.items.gear.equipped[type]]
|
||||
@@ -965,6 +677,311 @@ api.wrap = (user) ->
|
||||
dotGet: (path) ->
|
||||
_.reduce path.split('.'), ((curr, next) => curr?[next]), user
|
||||
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Scoring
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
randomDrop: (modifiers) ->
|
||||
{delta, priority, streak} = modifiers
|
||||
streak ?= 0
|
||||
# limit drops to 2 / day
|
||||
user.items.lastDrop ?=
|
||||
date: +moment().subtract('d', 1) # trick - set it to yesterday on first run, that way they can get drops today
|
||||
count: 0
|
||||
|
||||
reachedDropLimit = (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 5)
|
||||
return if reachedDropLimit
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= priority # multiply chance by reddness
|
||||
chanceMultiplier += streak # streak bonus
|
||||
|
||||
# Temporary solution to lower the maximum drop chance to 75 percent. More thorough
|
||||
# overhaul of drop changes is needed. See HabitRPG/habitrpg#1922 for details.
|
||||
# Old drop chance:
|
||||
# if user.flags?.dropsEnabled and Math.random() < (.05 * chanceMultiplier)
|
||||
max = 0.75 # Max probability of drop
|
||||
a = 0.1 # rate of increase
|
||||
alpha = a*max*chanceMultiplier/(a*chanceMultiplier+max) # current probability of drop
|
||||
|
||||
if user.flags?.dropsEnabled and Math.random() < alpha
|
||||
# current breakdown - 1% (adjustable) chance on drop
|
||||
# If they got a drop: 50% chance of egg, 50% Hatching Potion. If hatchingPotion, broken down further even further
|
||||
rarity = Math.random()
|
||||
|
||||
# Food: 40% chance
|
||||
if rarity > .6
|
||||
drop = randomVal _.omit(content.food, 'Saddle')
|
||||
user.items.food[drop.name] ?= 0
|
||||
user.items.food[drop.name]+= 1
|
||||
drop.type = 'Food'
|
||||
drop.dialog = "You've found a #{drop.text} Food! #{drop.notes}"
|
||||
|
||||
# Eggs: 30% chance
|
||||
else if rarity > .3
|
||||
drop = randomVal content.eggs
|
||||
user.items.eggs[drop.name] ?= 0
|
||||
user.items.eggs[drop.name]++
|
||||
drop.type = 'Egg'
|
||||
drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}"
|
||||
|
||||
# Hatching Potion, 30% chance - break down by rarity.
|
||||
else
|
||||
acceptableDrops =
|
||||
# Very Rare: 10% (of 30%)
|
||||
if rarity < .03 then ['Golden']
|
||||
# Rare: 20% (of 30%)
|
||||
else if rarity < .09 then ['Zombie', 'CottonCandyPink', 'CottonCandyBlue']
|
||||
# Uncommon: 30% (of 30%)
|
||||
else if rarity < .18 then ['Red', 'Shade', 'Skeleton']
|
||||
# Common: 40% (of 30%)
|
||||
else ['Base', 'White', 'Desert']
|
||||
|
||||
# No Rarity (@see https://github.com/HabitRPG/habitrpg/issues/1048, we may want to remove rareness when we add mounts)
|
||||
#drop = helpers.randomVal hatchingPotions
|
||||
drop = randomVal _.pick(content.hatchingPotions, ((v,k) -> k in acceptableDrops))
|
||||
|
||||
user.items.hatchingPotions[drop.name] ?= 0
|
||||
user.items.hatchingPotions[drop.name]++
|
||||
drop.type = 'HatchingPotion'
|
||||
drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}"
|
||||
|
||||
# if they've dropped something, we want the consuming client to know so they can notify the user. See how the Derby
|
||||
# app handles it for example. Would this be better handled as an emit() ?
|
||||
user._tmp.drop = drop
|
||||
|
||||
user.items.lastDrop.date = +new Date
|
||||
user.items.lastDrop.count++
|
||||
|
||||
###
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
expModifier: (value, weaponStr, level, priority=1) ->
|
||||
str = (level - 1) / 2
|
||||
# ultimately get this from user
|
||||
totalStr = (str + weaponStr) / 100
|
||||
strMod = 1 + totalStr
|
||||
exp = value * XP * strMod * priority
|
||||
return Math.round(exp)
|
||||
|
||||
###
|
||||
Calculates HP modification based on level and armor defence
|
||||
{value} task.value for hp loss
|
||||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
hpModifier: (value, armorDef, helmDef, shieldDef, level, priority=1) ->
|
||||
def = (level - 1) / 2
|
||||
# ultimately get this from user?
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100
|
||||
#ultimate get this from user
|
||||
defMod = 1 - totalDef
|
||||
hp = value * HP * defMod * priority
|
||||
return Math.round(hp * 10) / 10
|
||||
# round to 1dp
|
||||
|
||||
###
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
gpModifier: (value, modifier, priority=1, streak) ->
|
||||
val = value * modifier * priority
|
||||
if streak and user
|
||||
streakBonus = streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc
|
||||
afterStreak = val * streakBonus
|
||||
user._tmp.streakBonus = afterStreak - val if (val > 0) # keep this on-hand for later, so we can notify streak-bonus
|
||||
return afterStreak
|
||||
else
|
||||
return val
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
Uses a capped inverse log y=.95^x, y>= -5
|
||||
{currentValue} the current value of the task
|
||||
{direction} up or down
|
||||
###
|
||||
taskDeltaFormula: (currentValue, direction) ->
|
||||
if currentValue < -47.27 then currentValue = -47.27
|
||||
else if currentValue > 21.27 then currentValue = 21.27
|
||||
delta = Math.pow(0.9747, currentValue)
|
||||
return delta if direction is 'up'
|
||||
return -delta
|
||||
|
||||
###
|
||||
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
|
||||
###
|
||||
updateStats: (newStats) ->
|
||||
# if user is dead, dont do anything
|
||||
return if user.stats.hp <= 0
|
||||
|
||||
if newStats.hp?
|
||||
# Game Over
|
||||
if newStats.hp <= 0
|
||||
user.stats.hp = 0
|
||||
return
|
||||
else
|
||||
user.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
tnl = api.tnl(user.stats.lvl)
|
||||
#silent = false
|
||||
# if we're at level 100, turn xp to gold
|
||||
if user.stats.lvl >= 100
|
||||
newStats.gp += newStats.exp / 15
|
||||
newStats.exp = 0
|
||||
user.stats.lvl = 100
|
||||
else
|
||||
# level up & carry-over exp
|
||||
if newStats.exp >= tnl
|
||||
#silent = true # push through the negative xp silently
|
||||
user.stats.exp = newStats.exp # push normal + notification
|
||||
while newStats.exp >= tnl and user.stats.lvl < 100 # keep levelling up
|
||||
newStats.exp -= tnl
|
||||
user.stats.lvl++
|
||||
tnl = api.tnl(user.stats.lvl)
|
||||
if user.stats.lvl == 100
|
||||
newStats.exp = 0
|
||||
user.stats.hp = 50
|
||||
|
||||
user.stats.exp = newStats.exp
|
||||
|
||||
# Set flags when they unlock features
|
||||
user.flags ?= {}
|
||||
if !user.flags.customizationsNotification and (user.stats.exp > 10 or user.stats.lvl > 1)
|
||||
user.flags.customizationsNotification = true
|
||||
if !user.flags.itemsEnabled and user.stats.lvl >= 2
|
||||
user.flags.itemsEnabled = true
|
||||
if !user.flags.partyEnabled and user.stats.lvl >= 3
|
||||
user.flags.partyEnabled = true
|
||||
if !user.flags.dropsEnabled and user.stats.lvl >= 4
|
||||
user.flags.dropsEnabled = true
|
||||
user.items.eggs["Wolf"] = 1
|
||||
if !user.flags.classSelected and user.stats.lvl >= 5
|
||||
user.flags.classSelected
|
||||
|
||||
if newStats.gp?
|
||||
#FIXME what was I doing here? I can't remember, gp isn't defined
|
||||
gp = 0.0 if (!gp? or gp < 0)
|
||||
user.stats.gp = newStats.gp
|
||||
|
||||
|
||||
###
|
||||
------------------------------------------------------
|
||||
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: (options={}) ->
|
||||
[now] = [+options.now || +new Date]
|
||||
|
||||
# They went to a different timezone
|
||||
# FIXME:
|
||||
# (1) This exit-early code isn't taking timezone into consideration!!
|
||||
# (2) Won't switching timezones be handled automatically client-side anyway? (aka, can we just remove this code?)
|
||||
# (3) And if not, is this the correct way to handle switching timezones
|
||||
# if moment(user.lastCron).isAfter(now)
|
||||
# user.lastCron = now
|
||||
# return
|
||||
|
||||
daysMissed = api.daysSince user.lastCron, _.defaults({now}, user.preferences)
|
||||
return unless daysMissed > 0
|
||||
|
||||
user.auth.timestamps.loggedin = new Date()
|
||||
|
||||
user.lastCron = now
|
||||
|
||||
# Reset the lastDrop count to zero
|
||||
if user.items.lastDrop.count > 0
|
||||
user.items.lastDrop.count = 0
|
||||
|
||||
# User is resting at the inn. Used to be we un-checked each daily without performing calculation (see commits before fb29e35)
|
||||
# but to prevent abusing the inn (http://goo.gl/GDb9x) we now do *not* calculate dailies, and simply set lastCron to today
|
||||
return if user.preferences.sleep is true
|
||||
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
user.todos.concat(user.dailys).forEach (task) ->
|
||||
return unless task
|
||||
|
||||
return if user.stats.buffs.stealth && user.stats.buffs.stealth-- # User "evades" a certain number of tasks
|
||||
|
||||
{id, type, completed, repeat} = task
|
||||
# Deduct experience for missed Daily tasks, but not for Todos (just increase todo's value)
|
||||
unless completed
|
||||
scheduleMisses = daysMissed
|
||||
# for dailys which have repeat dates, need to calculate how many they've missed according to their own schedule
|
||||
if (type is 'daily') and repeat
|
||||
scheduleMisses = 0
|
||||
_.times daysMissed, (n) ->
|
||||
thatDay = moment(now).subtract('days', n + 1)
|
||||
scheduleMisses++ if api.shouldDo(thatDay, repeat, user.preferences)
|
||||
if scheduleMisses > 0
|
||||
user.ops.score({params:{id:task.id, direction:'down'}, query:{times:scheduleMisses, cron:true}})
|
||||
|
||||
switch type
|
||||
when 'daily'
|
||||
(task.history ?= []).push({ date: +new Date, value: task.value })
|
||||
task.completed = false
|
||||
when 'todo'
|
||||
#get updated value
|
||||
absVal = if (completed) then Math.abs(task.value) else task.value
|
||||
todoTally += absVal
|
||||
|
||||
user.habits.forEach (task) -> # slowly reset 'onlies' value to 0
|
||||
if task.up is false or task.down is false
|
||||
if Math.abs(task.value) < 0.1
|
||||
task.value = 0
|
||||
else
|
||||
task.value = task.value / 2
|
||||
|
||||
|
||||
# Finished tallying
|
||||
((user.history ?= {}).todos ?= []).push { date: now, value: todoTally }
|
||||
# tally experience
|
||||
expTally = user.stats.exp
|
||||
lvl = 0 #iterator
|
||||
while lvl < (user.stats.lvl - 1)
|
||||
lvl++
|
||||
expTally += api.tnl(lvl)
|
||||
(user.history.exp ?= []).push { date: now, value: expTally }
|
||||
user.fns.preenUserHistory()
|
||||
user
|
||||
|
||||
# Registered users with some history
|
||||
preenUserHistory: (minHistLen = 7) ->
|
||||
_.each user.habits.concat(user.dailys), (task) ->
|
||||
task.history = preenHistory(task.history) if task.history?.length > minHistLen
|
||||
true
|
||||
|
||||
_.defaults user.history, {todos:[], exp: []}
|
||||
user.history.exp = preenHistory(user.history.exp) if user.history.exp.length > minHistLen
|
||||
user.history.todos = preenHistory(user.history.todos) if user.history.todos.length > minHistLen
|
||||
#user.markModified('history')
|
||||
#user.markModified('habits')
|
||||
#user.markModified('dailys')
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Virtual Attributes
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
# Aggregate all intrinsic stats, buffs, weapon, & armor into computed stats
|
||||
Object.defineProperty user, '_statsComputed',
|
||||
get: ->
|
||||
|
||||
Reference in New Issue
Block a user