324 lines
10 KiB
JavaScript
324 lines
10 KiB
JavaScript
// Generated by CoffeeScript 1.6.3
|
|
(function() {
|
|
var algos, batchTxn, helpers, indexedPath, items, taskInChallenge, _;
|
|
|
|
_ = require('lodash');
|
|
|
|
algos = require('habitrpg-shared/script/algos');
|
|
|
|
items = require('habitrpg-shared/script/items').items;
|
|
|
|
helpers = require('habitrpg-shared/script/helpers');
|
|
|
|
module.exports.batchTxn = batchTxn = function(model, cb, options) {
|
|
var batch, paths, ret, setOps, uObj, user;
|
|
if (options == null) {
|
|
options = {};
|
|
}
|
|
_.defaults(options, {
|
|
user: model.at("_user"),
|
|
cron: false,
|
|
done: function() {}
|
|
});
|
|
user = options.user;
|
|
uObj = helpers.hydrate(user.get());
|
|
batch = {
|
|
set: function(k, v) {
|
|
helpers.dotSet(k, v, uObj);
|
|
return paths[k] = true;
|
|
},
|
|
get: function(k) {
|
|
return helpers.dotGet(k, uObj);
|
|
}
|
|
};
|
|
paths = {};
|
|
model._dontPersist = true;
|
|
ret = cb(uObj, paths, batch);
|
|
_.each(paths, function(v, k) {
|
|
user.pass({
|
|
cron: options.cron
|
|
}).set(k, batch.get(k));
|
|
return true;
|
|
});
|
|
model._dontPersist = false;
|
|
if (!_.isEmpty(paths)) {
|
|
setOps = _.reduce(paths, (function(m, v, k) {
|
|
m[k] = batch.get(k);
|
|
return m;
|
|
}), {});
|
|
user.set("update__", setOps, options.done);
|
|
} else {
|
|
options.done();
|
|
}
|
|
return ret;
|
|
};
|
|
|
|
/*
|
|
We can't always use refLists, but we often still need to get a positional path by id: eg, users.1234.tasks.5678.value
|
|
For arrays (which use indexes, not id-paths), here's a helper function so we can run indexedPath('users',:user.id,'tasks',:task.id,'value)
|
|
*/
|
|
|
|
|
|
indexedPath = function() {
|
|
var _this = this;
|
|
return _.reduce(arguments, function(m, v) {
|
|
if (!m) {
|
|
return v;
|
|
}
|
|
if (_.isString(v)) {
|
|
return "" + m + "." + v;
|
|
}
|
|
return ("" + m + ".") + _.findIndex(_this.model.get(m), v);
|
|
}, '');
|
|
};
|
|
|
|
taskInChallenge = function(task) {
|
|
if (!(task != null ? task.challenge : void 0)) {
|
|
return void 0;
|
|
}
|
|
return this.model.at(indexedPath.call(this, "groups." + task.group.id + ".challenges", {
|
|
id: task.challenge
|
|
}, "" + task.type + "s", {
|
|
id: task.id
|
|
}));
|
|
};
|
|
|
|
/*
|
|
algos.score wrapper for habitrpg-helpers to work in Derby. We need to do model.set() instead of simply setting the
|
|
object properties, and it's very difficult to diff the two objects and find dot-separated paths to set. So we to first
|
|
clone our user object (if we don't do that, it screws with model.on() listeners, ping Tyler for an explaination),
|
|
perform the updates while tracking paths, then all the values at those paths
|
|
*/
|
|
|
|
|
|
module.exports.score = function(model, taskId, direction, allowUndo) {
|
|
var delta, drop;
|
|
if (allowUndo == null) {
|
|
allowUndo = false;
|
|
}
|
|
drop = void 0;
|
|
delta = batchTxn(model, function(uObj, paths) {
|
|
var chal, chalTask, chalUser, cu, previousUndo, tObj, tObjBefore, timeoutId, _ref, _ref1, _ref2, _ref3, _ref4;
|
|
tObj = uObj.tasks[taskId];
|
|
if (allowUndo) {
|
|
tObjBefore = _.cloneDeep(tObj);
|
|
if ((_ref = tObjBefore.type) === 'daily' || _ref === 'todo') {
|
|
tObjBefore.completed = !tObjBefore.completed;
|
|
}
|
|
previousUndo = model.get('_undo');
|
|
if (previousUndo != null ? previousUndo.timeoutId : void 0) {
|
|
clearTimeout(previousUndo.timeoutId);
|
|
}
|
|
timeoutId = setTimeout((function() {
|
|
return model.del('_undo');
|
|
}), 20000);
|
|
model.set('_undo', {
|
|
stats: _.cloneDeep(uObj.stats),
|
|
task: tObjBefore,
|
|
timeoutId: timeoutId
|
|
});
|
|
}
|
|
delta = algos.score(uObj, tObj, direction, {
|
|
paths: paths
|
|
});
|
|
if ((_ref1 = uObj._tmp) != null ? _ref1.streakBonus : void 0) {
|
|
model.set('_streakBonus', uObj._tmp.streakBonus);
|
|
}
|
|
drop = (_ref2 = uObj._tmp) != null ? _ref2.drop : void 0;
|
|
if ((chalTask = taskInChallenge.call({
|
|
model: model
|
|
}, tObj)) && (chalTask != null ? chalTask.get() : void 0)) {
|
|
model._dontPersist = false;
|
|
chalTask.incr("value", delta);
|
|
chal = model.at(indexedPath.call({
|
|
model: model
|
|
}, "groups." + tObj.group.id + ".challenges", {
|
|
id: tObj.challenge
|
|
}));
|
|
chalUser = function() {
|
|
return indexedPath.call({
|
|
model: model
|
|
}, chal.path(), 'users', {
|
|
id: uObj.id
|
|
});
|
|
};
|
|
cu = model.at(chalUser());
|
|
if (!(cu != null ? cu.get() : void 0)) {
|
|
chal.push("users", {
|
|
id: uObj.id,
|
|
name: helpers.username(uObj.auth, (_ref3 = uObj.profile) != null ? _ref3.name : void 0)
|
|
});
|
|
cu = model.at(chalUser());
|
|
} else {
|
|
cu.set('name', helpers.username(uObj.auth, (_ref4 = uObj.profile) != null ? _ref4.name : void 0));
|
|
}
|
|
cu.set("" + tObj.type + "s." + tObj.id, {
|
|
value: tObj.value,
|
|
history: tObj.history
|
|
});
|
|
return model._dontPersist = true;
|
|
}
|
|
}, {
|
|
done: function() {
|
|
if (drop && (typeof $ !== "undefined" && $ !== null)) {
|
|
model.set('_drop', drop);
|
|
return $('#item-dropped-modal').modal('show');
|
|
}
|
|
}
|
|
});
|
|
return delta;
|
|
};
|
|
|
|
/*
|
|
Cleanup task-corruption (null tasks, rogue/invisible tasks, etc)
|
|
Obviously none of this should be happening, but we'll stop-gap until we can find & fix
|
|
Gotta love refLists! see https://github.com/lefnire/habitrpg/issues/803 & https://github.com/lefnire/habitrpg/issues/6343
|
|
*/
|
|
|
|
|
|
module.exports.fixCorruptUser = function(model) {
|
|
var resetDom, tasks, user;
|
|
user = model.at('_user');
|
|
tasks = user.get('tasks');
|
|
_.each(tasks, function(task, key) {
|
|
if (!(((task != null ? task.id : void 0) != null) && ((task != null ? task.type : void 0) != null))) {
|
|
user.del("tasks." + key);
|
|
delete tasks[key];
|
|
}
|
|
return true;
|
|
});
|
|
resetDom = false;
|
|
batchTxn(model, function(uObj, paths, batch) {
|
|
var uniqInvites, uniqPets, _ref;
|
|
uniqPets = _.uniq(uObj.items.pets);
|
|
if (!_.isEqual(uniqPets, uObj.items.pets)) {
|
|
batch.set('items.pets', uniqPets);
|
|
}
|
|
if ((_ref = uObj.invitations) != null ? _ref.guilds : void 0) {
|
|
uniqInvites = _.uniq(uObj.invitations.guilds);
|
|
if (!_.isEqual(uniqInvites, uObj.invitations.guilds)) {
|
|
batch.set('invitations.guilds', uniqInvites);
|
|
}
|
|
}
|
|
['habit', 'daily', 'todo', 'reward'].forEach(function(type) {
|
|
var idList, preened, taskIds, union;
|
|
idList = uObj["" + type + "Ids"];
|
|
taskIds = _.pluck(_.where(tasks, {
|
|
type: type
|
|
}), 'id');
|
|
union = _.union(idList, taskIds);
|
|
preened = _.filter(union, function(id) {
|
|
return id && _.contains(taskIds, id);
|
|
});
|
|
if (!_.isEqual(idList, preened)) {
|
|
batch.set("" + type + "Ids", preened);
|
|
console.error(uObj.id + ("'s " + type + "s were corrupt."));
|
|
}
|
|
return true;
|
|
});
|
|
return resetDom = !_.isEmpty(paths);
|
|
});
|
|
if (resetDom) {
|
|
return require('./browser').resetDom(model);
|
|
}
|
|
};
|
|
|
|
module.exports.viewHelpers = function(view) {
|
|
view.fn("percent", function(x, y) {
|
|
if (x === 0) {
|
|
x = 1;
|
|
}
|
|
return Math.round(x / y * 100);
|
|
});
|
|
view.fn('indexOf', function(str1, str2) {
|
|
if (!(str1 && str2)) {
|
|
return false;
|
|
}
|
|
return str1.indexOf(str2) !== -1;
|
|
});
|
|
view.fn("round", Math.round);
|
|
view.fn("floor", Math.floor);
|
|
view.fn("ceil", Math.ceil);
|
|
view.fn("lt", function(a, b) {
|
|
return a < b;
|
|
});
|
|
view.fn('gt', function(a, b) {
|
|
return a > b;
|
|
});
|
|
view.fn("mod", function(a, b) {
|
|
return parseInt(a) % parseInt(b) === 0;
|
|
});
|
|
view.fn("notEqual", function(a, b) {
|
|
return a !== b;
|
|
});
|
|
view.fn("and", function() {
|
|
return _.reduce(arguments, function(cumm, curr) {
|
|
return cumm && curr;
|
|
});
|
|
});
|
|
view.fn("or", function() {
|
|
return _.reduce(arguments, function(cumm, curr) {
|
|
return cumm || curr;
|
|
});
|
|
});
|
|
view.fn("truarr", function(num) {
|
|
return num - 1;
|
|
});
|
|
view.fn('count', function(arr) {
|
|
return (arr != null ? arr.length : void 0) || 0;
|
|
});
|
|
view.fn('int', {
|
|
get: function(num) {
|
|
return num;
|
|
},
|
|
set: function(num) {
|
|
return [parseInt(num)];
|
|
}
|
|
});
|
|
view.fn('indexedPath', indexedPath);
|
|
view.fn("encodeiCalLink", helpers.encodeiCalLink);
|
|
view.fn("gems", function(balance) {
|
|
return balance * 4;
|
|
});
|
|
view.fn("username", helpers.username);
|
|
view.fn("tnl", algos.tnl);
|
|
view.fn('equipped', helpers.equipped);
|
|
view.fn("gold", helpers.gold);
|
|
view.fn("silver", helpers.silver);
|
|
view.fn('userStr', helpers.userStr);
|
|
view.fn('totalStr', helpers.totalStr);
|
|
view.fn('userDef', helpers.userDef);
|
|
view.fn('totalDef', helpers.totalDef);
|
|
view.fn('itemText', helpers.itemText);
|
|
view.fn('itemStat', helpers.itemStat);
|
|
view.fn('ownsPet', helpers.ownsPet);
|
|
view.fn('taskClasses', helpers.taskClasses);
|
|
view.fn('friendlyTimestamp', helpers.friendlyTimestamp);
|
|
view.fn('newChatMessages', helpers.newChatMessages);
|
|
view.fn('relativeDate', helpers.relativeDate);
|
|
view.fn('noTags', helpers.noTags);
|
|
view.fn('appliedTags', helpers.appliedTags);
|
|
view.fn('taskInChallenge', function(task) {
|
|
var _ref;
|
|
return (_ref = taskInChallenge.call(this, task)) != null ? _ref.get() : void 0;
|
|
});
|
|
view.fn('taskAttrFromChallenge', function(task, attr) {
|
|
var _ref;
|
|
return (_ref = taskInChallenge.call(this, task)) != null ? _ref.get(attr) : void 0;
|
|
});
|
|
view.fn('brokenChallengeLink', function(task) {
|
|
var _ref;
|
|
return (task != null ? task.challenge : void 0) && !((_ref = taskInChallenge.call(this, task)) != null ? _ref.get() : void 0);
|
|
});
|
|
return view.fn('challengeMemberScore', function(member, tType, tid) {
|
|
var _ref, _ref1;
|
|
return Math.round((_ref = member["" + tType + "s"]) != null ? (_ref1 = _ref[tid]) != null ? _ref1.value : void 0 : void 0);
|
|
});
|
|
};
|
|
|
|
}).call(this);
|
|
|
|
/*
|
|
//@ sourceMappingURL=misc.map
|
|
*/
|