classes: refactor score function. Merge all separate functions into the

score function (I'll do something for unit tests, they were just too
scattered). Factor in the new passive-stats (STR, PER, INT,
CON). They're not formalized, just starters. @SabreCat :)
This commit is contained in:
Tyler Renelle 2013-12-13 15:55:47 -07:00
parent 15826f3722
commit 2f8b4c552b
2 changed files with 233 additions and 347 deletions

View file

@ -10040,7 +10040,7 @@ var process=require("__browserify_process");(function() {
content = require('./content.coffee');
XP = 15;
XP = 7.5;
HP = 2;
@ -10556,7 +10556,8 @@ var process=require("__browserify_process");(function() {
},
update: function(req, cb) {
_.each(req.body, function(v, k) {
return user.fns.dotSet(k, v);
user.fns.dotSet(k, v);
return true;
});
return typeof cb === "function" ? cb(null, req) : void 0;
},
@ -10782,7 +10783,8 @@ var process=require("__browserify_process");(function() {
}
if (fullSet) {
_.each(path.split(","), function(p) {
return user.fns.dotSet("purchased." + p, true);
user.fns.dotSet("purchased." + p, true);
return true;
});
} else {
if (user.fns.dotGet("purchased." + path) === true) {
@ -10816,8 +10818,9 @@ var process=require("__browserify_process");(function() {
});
user.items.gear.equipped[type] = foundKey ? foundKey : type === "weapon" ? "weapon_" + klass + "_0" : type === "shield" && klass === "rogue" ? "shield_rogue_0" : "" + type + "_base_0";
if (type === "weapon" || (type === "shield" && klass === "rogue")) {
return user.items.gear.owned["" + type + "_" + klass + "_0"] = true;
user.items.gear.owned["" + type + "_" + klass + "_0"] = true;
}
return true;
});
} else {
_.merge(user.stats, {
@ -10843,14 +10846,26 @@ var process=require("__browserify_process");(function() {
return typeof cb === "function" ? cb(null, req) : void 0;
},
score: function(req, cb) {
var addPoints, calculateDelta, cron, delta, direction, exp, gp, hp, id, lvl, num, priority, streak, subtractPoints, task, times, type, value, _ref, _ref1, _ref2, _ref3, _ref4, _ref5;
var addPoints, calculateDelta, delta, direction, id, num, options, stats, subtractPoints, task, _ref;
_ref = req.params, id = _ref.id, direction = _ref.direction;
task = user.tasks[id];
options = req.query || {};
_.defaults(options, {
times: 1,
cron: false
});
user._tmp = {};
_ref1 = [+user.stats.gp, +user.stats.hp, +user.stats.exp, ~~user.stats.lvl], gp = _ref1[0], hp = _ref1[1], exp = _ref1[2], lvl = _ref1[3];
_ref2 = [task.type, +task.value, ~~task.streak, +task.priority || 1], type = _ref2[0], value = _ref2[1], streak = _ref2[2], priority = _ref2[3];
_ref5 = [((_ref3 = req.query) != null ? _ref3.times : void 0) || 1, ((_ref4 = req.query) != null ? _ref4.cron : void 0) || false], times = _ref5[0], cron = _ref5[1];
if (task.value > user.stats.gp && task.type === 'reward') {
stats = {
gp: +user.stats.gp,
hp: +user.stats.hp,
exp: +user.stats.exp
};
task.value = +task.value;
task.streak = ~~task.streak;
if (task.priority == null) {
task.priority = 1;
}
if (task.value > stats.gp && task.type === 'reward') {
return cb('Not enough Gold');
}
delta = 0;
@ -10858,33 +10873,33 @@ var process=require("__browserify_process");(function() {
if (adjustvalue == null) {
adjustvalue = true;
}
return _.times(times, function() {
var nextDelta;
nextDelta = user.fns.taskDeltaFormula(value, direction);
return _.times(options.times, function() {
var currVal, nextDelta;
currVal = task.value < -47.27 ? -47.27 : task.value > 21.27 ? 21.27 : task.value;
nextDelta = Math.pow(0.9747, currVal) * (direction === 'down' ? -1 : 1);
if (adjustvalue) {
value += nextDelta;
task.value += nextDelta;
if (direction === 'up' && task.type !== 'reward' && !(task.type === 'habit' && !task.down)) {
task.value += user._statsComputed.str * .25;
}
}
return delta += nextDelta;
});
};
addPoints = function() {
var weaponStr;
weaponStr = user.fns.getItem('weapon').str;
exp += user.fns.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2;
if (streak) {
return gp += user.fns.gpModifier(delta, 1, priority, streak);
} else {
return gp += user.fns.gpModifier(delta, 1, priority);
}
var afterStreak, gpMod, intMod, streakBonus;
intMod = 1 + (user._statsComputed.int / 100);
stats.exp += Math.round(delta * XP * intMod * task.priority);
gpMod = delta * task.priority;
gpMod *= user._statsComputed.per * .3;
return stats.gp += task.streak ? (streakBonus = task.streak / 100 + 1, afterStreak = gpMod * streakBonus, gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0, afterStreak) : gpMod;
};
subtractPoints = function() {
var armorDef, headDef, shieldDef;
armorDef = user.fns.getItem('armor').con;
headDef = user.fns.getItem('head').con;
shieldDef = user.fns.getItem('shield').con;
return hp += user.fns.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority);
var conMod;
conMod = 1 - (user._statsComputed.con / 100);
return stats.hp += Math.round(task.value * HP * conMod * task.priority);
};
switch (type) {
switch (task.type) {
case 'habit':
calculateDelta();
if (delta > 0) {
@ -10892,15 +10907,25 @@ var process=require("__browserify_process");(function() {
} else {
subtractPoints();
}
if (task.value !== value) {
(task.history != null ? task.history : task.history = []).push({
if (task.history == null) {
task.history = [];
}
if (moment(task.history[task.history.length - 1].date).isSame(new Date, 'day')) {
task.history[task.history.length - 1].value = task.value;
} else {
task.history.push({
date: +(new Date),
value: value
value: task.value
});
}
if (typeof user.markModified === "function") {
user.markModified("habits." + (_.findIndex(user.habits, {
id: task.id
})) + ".history");
}
break;
case 'daily':
if (cron) {
if (options.cron) {
calculateDelta();
subtractPoints();
task.streak = 0;
@ -10908,21 +10933,20 @@ var process=require("__browserify_process");(function() {
calculateDelta();
addPoints();
if (direction === 'up') {
streak = streak ? streak + 1 : 1;
if ((streak % 21) === 0) {
task.streak = task.streak ? task.streak + 1 : 1;
if ((task.streak % 21) === 0) {
user.achievements.streak = user.achievements.streak ? user.achievements.streak + 1 : 1;
}
} else {
if ((streak % 21) === 0) {
if ((task.streak % 21) === 0) {
user.achievements.streak = user.achievements.streak ? user.achievements.streak - 1 : 0;
}
streak = streak ? streak - 1 : 0;
task.streak = task.streak ? task.streak - 1 : 0;
}
task.streak = streak;
}
break;
case 'todo':
if (cron) {
if (options.cron) {
calculateDelta();
} else {
calculateDelta();
@ -10931,25 +10955,21 @@ var process=require("__browserify_process");(function() {
break;
case 'reward':
calculateDelta(false);
gp -= Math.abs(task.value);
stats.gp -= Math.abs(task.value);
num = parseFloat(task.value).toFixed(2);
if (gp < 0) {
hp += gp;
gp = 0;
if (stats.gp < 0) {
stats.hp += stats.gp;
stats.gp = 0;
}
}
task.value = value;
updateStats(user, {
hp: hp,
exp: exp,
gp: gp
});
user.fns.updateStats(stats);
if (typeof window === 'undefined') {
if (direction === 'up') {
randomDrop(user, {
user.fns.randomDrop({
task: task,
delta: delta,
priority: priority,
streak: streak
priority: task.priority,
streak: task.streak
});
}
}
@ -10977,8 +10997,9 @@ var process=require("__browserify_process");(function() {
return !user.items.gear.owned[item.key];
});
if (found) {
return changes.push(found);
changes.push(found);
}
return true;
});
_.defaults(changes, _.transform(_.where(content.gear.flat, {
klass: 'special'
@ -11086,152 +11107,57 @@ var process=require("__browserify_process");(function() {
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;
updateStats: function(stats) {
var tnl;
if (stats.hp <= 0) {
return user.stats.hp = 0;
}
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.hp = stats.hp;
user.stats.gp = stats.gp >= 0 ? stats.gp : 0;
tnl = api.tnl(user.stats.lvl);
if (user.stats.lvl >= 100) {
stats.gp += stats.exp / 15;
stats.exp = 0;
user.stats.lvl = 100;
} else {
if (stats.exp >= tnl) {
user.stats.exp = stats.exp;
while (stats.exp >= tnl && user.stats.lvl < 100) {
stats.exp -= tnl;
user.stats.lvl++;
tnl = api.tnl(user.stats.lvl);
}
}
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 (user.stats.lvl === 100) {
stats.exp = 0;
}
user.stats.hp = 50;
}
}
if (newStats.gp != null) {
if ((typeof gp === "undefined" || gp === null) || gp < 0) {
gp = 0.0;
}
return user.stats.gp = newStats.gp;
user.stats.exp = stats.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) {
return user.flags.classSelected;
}
},
/*
@ -11339,6 +11265,12 @@ var process=require("__browserify_process");(function() {
value: expTally
});
user.fns.preenUserHistory();
if (typeof user.markModified === "function") {
user.markModified('history');
}
if (typeof user.markModified === "function") {
user.markModified('dailys');
}
return user;
},
preenUserHistory: function(minHistLen) {
@ -11373,6 +11305,7 @@ var process=require("__browserify_process");(function() {
val = user.fns.dotGet(path);
return m2 + (~path.indexOf('items.gear') ? (+((_ref = content.gear.flat[val]) != null ? _ref[stat] : void 0) || 0) * (~(val != null ? val.indexOf(user.stats["class"]) : void 0) ? 1.5 : 1) : +val[stat] || 0);
}, 0);
m[stat] += (user.stats.lvl - 1) / 2;
return m;
}, {});
}

View file

@ -2,7 +2,7 @@ moment = require('moment')
_ = require('lodash')
content = require('./content.coffee')
XP = 15
XP = 7.5 # originally 15, users were complaining that they gained exp too fast
HP = 2
api = module.exports = {}
@ -342,7 +342,7 @@ api.wrap = (user) ->
update: (req, cb) ->
_.each req.body, (v,k) ->
user.fns.dotSet(k,v)
user.fns.dotSet(k,v);true
cb? null, req
sortTask: (req, cb) ->
@ -478,7 +478,7 @@ api.wrap = (user) ->
return cb?({code:401, err: "Not enough gems"}) if user.balance < cost
if fullSet
_.each path.split(","), (p) ->
user.fns.dotSet "purchased." + p, true
user.fns.dotSet("purchased." + p, true);true
else
if user.fns.dotGet("purchased." + path) is true
user.preferences[path.split(".")[0]] = path.split(".")[1]
@ -512,6 +512,7 @@ api.wrap = (user) ->
# Grant them their new class's gear
user.items.gear.owned["#{type}_#{klass}_0"] = true if type is "weapon" or (type is "shield" and klass is "rogue")
true
else
# Null ?class value means "reset class"
_.merge user.stats, {str: 0, def: 0, per: 0, int: 0}
@ -527,58 +528,83 @@ api.wrap = (user) ->
user.stats.mp++ if stat is 'int' #increase their MP along with their max MP
cb? null, req
score: (req, cb) ->
{id, direction} = req.params # up or down
task = user.tasks[id]
options = req.query or {}; _.defaults(options, {times:1, cron:false})
# This is for setting one-time temporary flags, such as streakBonus or itemDropped. Useful for notifying
# the API consumer, then cleared afterwards
user._tmp = {}
[gp, hp, exp, lvl] = [+user.stats.gp, +user.stats.hp, +user.stats.exp, ~~user.stats.lvl]
[type, value, streak, priority] = [task.type, +task.value, ~~task.streak, +task.priority or 1]
[times, cron] = [req.query?.times or 1, req.query?.cron or false]
# TODO do we need this fail-safe casting anymore? Are we safe now we're off Derby?
stats = {gp: +user.stats.gp, hp: +user.stats.hp, exp: +user.stats.exp}
task.value = +task.value; task.streak = ~~task.streak; task.priority ?= 1
# If they're trying to purhcase a too-expensive reward, don't allow them to do that.
if task.value > user.stats.gp and task.type is 'reward'
if task.value > stats.gp and task.type is 'reward'
return cb('Not enough Gold');
delta = 0
calculateDelta = (adjustvalue = true) ->
calculateDelta = (adjustvalue=true) ->
# If multiple days have passed, multiply times days missed
_.times times, ->
# 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 = user.fns.taskDeltaFormula(value, direction)
value += nextDelta if adjustvalue
_.times options.times, ->
# Each iteration calculate the nextDelta, which is then accumulated in the total delta.
# Calculates the next task.value based on direction
# Uses a capped inverse log y=.95^x, y>= -5
# Min/max on task redness
currVal =
if task.value < -47.27 then -47.27
else if task.value > 21.27 then 21.27
else task.value
nextDelta = Math.pow(0.9747, currVal) * (if direction is 'down' then -1 else 1)
if adjustvalue
task.value += nextDelta
# Factor in STR. Only for up-scoring, ignore up-onlies and rewards
if direction is 'up' and task.type != 'reward' and !(task.type is 'habit' and !task.down)
task.value += user._statsComputed.str * .25
delta += nextDelta
addPoints = ->
weaponStr = user.fns.getItem('weapon').str
exp += user.fns.expModifier(delta, weaponStr, user.stats.lvl, priority) / 2 # /2 hack for now, people leveling too fast
if streak
gp += user.fns.gpModifier(delta, 1, priority, streak)
else
gp += user.fns.gpModifier(delta, 1, priority)
# Exp Modifier
intMod = 1 + (user._statsComputed.int / 100)
stats.exp += Math.round(delta * XP * intMod * task.priority)
# GP modifier
gpMod = delta * task.priority
gpMod *= (user._statsComputed.per *.3) # Factor in PER
stats.gp +=
if task.streak
streakBonus = task.streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc
afterStreak = gpMod * streakBonus
user._tmp.streakBonus = afterStreak - gpMod if (gpMod > 0) # keep this on-hand for later, so we can notify streak-bonus
afterStreak
else gpMod
# HP modifier
subtractPoints = ->
armorDef = user.fns.getItem('armor').con
headDef = user.fns.getItem('head').con
shieldDef = user.fns.getItem('shield').con
hp += user.fns.hpModifier(delta, armorDef, headDef, shieldDef, user.stats.lvl, priority)
conMod = 1 - (user._statsComputed.con / 100)
stats.hp += Math.round(task.value * HP * conMod * task.priority)
# round to 1dp
switch type
switch task.type
when 'habit'
calculateDelta()
# Add habit value to habit-history (if different)
if (delta > 0) then addPoints() else subtractPoints()
if task.value != value
(task.history ?= []).push { date: +new Date, value: value }
# History
task.history ?= []
if moment(task.history[task.history.length-1].date).isSame(new Date, 'day')
task.history[task.history.length-1].value = task.value
else
task.history.push {date: +new Date, value: task.value}
user.markModified? "habits.#{_.findIndex(user.habits, {id:task.id})}.history"
when 'daily'
if cron
if options.cron
calculateDelta()
subtractPoints()
task.streak = 0
@ -586,22 +612,18 @@ api.wrap = (user) ->
calculateDelta()
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
if direction is 'up'
streak = if streak then streak + 1 else 1
task.streak = if task.streak then task.streak + 1 else 1
# Give a streak achievement when the streak is a multiple of 21
if (streak % 21) is 0
if (task.streak % 21) is 0
user.achievements.streak = if user.achievements.streak then user.achievements.streak + 1 else 1
else
# Remove a streak achievement if streak was a multiple of 21 and the daily was undone
if (streak % 21) is 0
if (task.streak % 21) is 0
user.achievements.streak = if user.achievements.streak then user.achievements.streak - 1 else 0
streak = if streak then streak - 1 else 0
task.streak = streak
task.streak = if task.streak then task.streak - 1 else 0
when 'todo'
if cron
if options.cron
calculateDelta()
#don't touch stats on cron
else
@ -612,20 +634,19 @@ api.wrap = (user) ->
# Don't adjust values for rewards
calculateDelta(false)
# purchase item
gp -= Math.abs(task.value)
stats.gp -= Math.abs(task.value)
num = parseFloat(task.value).toFixed(2)
# if too expensive, reduce health & zero gp
if gp < 0
hp += gp
if stats.gp < 0
# hp - gp difference
gp = 0
stats.hp += stats.gp
stats.gp = 0
task.value = value
updateStats user, { hp, exp, gp }
user.fns.updateStats stats
# Drop system (don't run on the client, as it would only be discarded since ops are sent to the API, not the results)
if typeof window is 'undefined'
randomDrop(user, {delta, priority, streak}) if direction is 'up'
user.fns.randomDrop({task, delta, priority:task.priority, streak:task.streak}) if direction is 'up'
cb? null, req
return delta
@ -635,6 +656,7 @@ api.wrap = (user) ->
# ----------------------------------------------------------------------
user.fns =
getItem: (type) ->
item = content.gear.flat[user.items.gear.equipped[type]]
return content.gear.flat["#{type}_base_0"] unless item
@ -646,6 +668,7 @@ api.wrap = (user) ->
found = _.find content.gear.tree[type][user.stats.class], (item) ->
!user.items.gear.owned[item.key]
changes.push(found) if found
true
# Add special items (contrib gear, backer gear, etc)
_.defaults changes, _.transform _.where(content.gear.flat, {klass:'special'}), (m,v) ->
m.push v if v.canOwn?(user) && !user.items.gear.owned[v.key]
@ -756,125 +779,51 @@ api.wrap = (user) ->
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
updateStats: (stats) ->
# Game Over
return user.stats.hp=0 if stats.hp <= 0
if newStats.hp?
# Game Over
if newStats.hp <= 0
user.stats.hp = 0
return
else
user.stats.hp = newStats.hp
user.stats.hp = stats.hp
user.stats.gp = if stats.gp >= 0 then stats.gp else 0
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
tnl = api.tnl(user.stats.lvl)
# if we're at level 100, turn xp to gold
if user.stats.lvl >= 100
stats.gp += stats.exp / 15
stats.exp = 0
user.stats.lvl = 100
else
# level up & carry-over exp
if stats.exp >= tnl
#silent = true # push through the negative xp silently
user.stats.exp = stats.exp # push normal + notification
while stats.exp >= tnl and user.stats.lvl < 100 # keep levelling up
stats.exp -= tnl
user.stats.lvl++
tnl = api.tnl(user.stats.lvl)
if user.stats.lvl == 100
stats.exp = 0
user.stats.hp = 50
user.stats.exp = stats.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
###
------------------------------------------------------
@ -963,6 +912,8 @@ api.wrap = (user) ->
expTally += api.tnl(lvl)
(user.history.exp ?= []).push { date: now, value: expTally }
user.fns.preenUserHistory()
user.markModified? 'history'
user.markModified? 'dailys' # covers dailys.*.history
user
# Registered users with some history
@ -974,9 +925,9 @@ api.wrap = (user) ->
_.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')
#user.markModified? 'history'
#user.markModified? 'habits'
#user.markModified? 'dailys'
# ----------------------------------------------------------------------
# Virtual Attributes
@ -994,7 +945,9 @@ api.wrap = (user) ->
(+content.gear.flat[val]?[stat] or 0) * (if ~val?.indexOf(user.stats.class) then 1.5 else 1)
else
+val[stat] or 0
, 0); m
, 0)
m[stat] += (user.stats.lvl - 1) / 2
m
, {})
Object.defineProperty user, 'tasks',
get: ->