Merge pull request #1 from HabitRPG/develop

updating my fork?
This commit is contained in:
crystalphoenix
2013-12-22 13:53:49 -08:00
96 changed files with 2403 additions and 1305 deletions
+1 -1
View File
@@ -18,7 +18,7 @@ module.exports = function(grunt) {
// `sprite` has `name`, `image` (full path), `x`, `y`
// `width`, `height`, `total_width`, `total_height`
// EXAMPLE: Prefix all sprite names with 'sprite-'
if (sprite.name.match(/hair|skin|beard|mustach|shirt/) || sprite.name=='head_base_0') {
if (sprite.name.match(/hair|skin|beard|mustach|shirt/) || sprite.name=='head_0') {
sprite.name = 'customize-option.' + sprite.name;
sprite.x = sprite.x + 25;
sprite.y = sprite.y + 15;
+2 -2
View File
@@ -1,6 +1,6 @@
[class*='Pet_'] {
/*[class*='Pet_'] {
margin-left: 1.25em;
}
}*/
.Gems {
display:inline-block;
+949 -523
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
File diff suppressed because one or more lines are too long
+129 -79
View File
@@ -9167,12 +9167,14 @@ var global=self;/**
},{}],5:[function(require,module,exports){
(function() {
var api, crit, gear, repeat, _;
var api, gear, moment, repeat, _;
_ = require('lodash');
api = module.exports;
moment = require('moment');
/*
---------------------------------------------------------------
Gear (Weapons, Armor, Head, Shield)
@@ -9999,43 +10001,29 @@ var global=self;/**
*/
crit = function(user, stat, chance) {
if (stat == null) {
stat = 'per';
}
if (chance == null) {
chance = .03;
}
if (user.fns.predictableRandom() <= chance) {
return 1.5 + (.05 * user._statsComputed[stat]);
} else {
return 1;
}
};
api.spells = {
wizard: {
fireball: {
text: 'Burst of Flames',
mana: 10,
lvl: 6,
lvl: 11,
target: 'task',
notes: 'With a crack, flames burst from your staff, scorching a task. You deal much higher damage to the task and gain additional experience.',
notes: 'With a crack, flames burst from your staff, scorching a task. You deal high damage to the task, and gain additional experience (more experience for greens).',
cast: function(user, target) {
target.value += user._statsComputed.int * .02 * crit(user, 'per');
return user.stats.exp += Math.abs(target.value);
target.value += user._statsComputed.int * .0075 * user.fns.crit('per');
return user.stats.exp += (target.value < 0 ? 1 : target.value + 1) * 2.5;
}
},
mpheal: {
text: 'Ethereal Surge',
mana: 30,
lvl: 7,
lvl: 12,
target: 'party',
notes: "A flow of magical energy rushes from your hands and recharges your party. Your party recovers MP.",
cast: function(user, target) {
return _.each(target, function(member) {
var bonus;
bonus = Math.ceil(user._statsComputed.int * .2 + 5);
bonus = Math.ceil(user._statsComputed.int * .1);
if (bonus > 25) {
bonus = 25;
}
@@ -10046,7 +10034,7 @@ var global=self;/**
earth: {
text: 'Earthquake',
mana: 35,
lvl: 8,
lvl: 13,
target: 'party',
notes: "The ground below your party's tasks cracks and shakes with extreme intensity, slowing them down and opening them up to more attacks. Your party gains a buff to experience.",
cast: function(user, target) {
@@ -10055,14 +10043,14 @@ var global=self;/**
if ((_base = member.stats.buffs).int == null) {
_base.int = 0;
}
return member.stats.buffs.int = user._statsComputed.int * .2;
return member.stats.buffs.int += Math.ceil(user._statsComputed.int * .05);
});
}
},
frost: {
text: 'Chilling Frost',
mana: 40,
lvl: 9,
lvl: 14,
target: 'self',
notes: "Ice erupts from every surface, swallowing your tasks and freezing them in place. Your dailies' streaks won't reset at the end of the day.",
cast: function(user, target) {
@@ -10074,17 +10062,17 @@ var global=self;/**
smash: {
text: 'Brutal Smash',
mana: 10,
lvl: 6,
lvl: 11,
target: 'task',
notes: "You savagely hit a single task with all of your might, beating it into submission. The task's redness decreases.",
cast: function(user, target) {
return target.value += user._statsComputed.str * .03;
return target.value += user._statsComputed.str * .01 * user.fns.crit('per');
}
},
defensiveStance: {
text: 'Defensive Stance',
mana: 25,
lvl: 7,
lvl: 12,
target: 'self',
notes: "You take a moment to relax your body and enter a defensive stance to ready yourself for the tasks' next onslaught. Reduces damage from dailies at the end of the day.",
cast: function(user, target) {
@@ -10092,13 +10080,13 @@ var global=self;/**
if ((_base = user.stats.buffs).con == null) {
_base.con = 0;
}
return user.stats.buffs.con += user._statsComputed.con * .3;
return user.stats.buffs.con += Math.ceil(user._statsComputed.con * .05);
}
},
valorousPresence: {
text: 'Valorous Presence',
mana: 20,
lvl: 8,
lvl: 13,
target: 'party',
notes: "Your presence emboldens the party. Their newfound courage gives them a boost of strength. Party members gain a buff to their STR.",
cast: function(user, target) {
@@ -10107,14 +10095,14 @@ var global=self;/**
if ((_base = member.stats.buffs).str == null) {
_base.str = 0;
}
return member.stats.buffs.str += user._statsComputed.str * .2;
return member.stats.buffs.str += Math.ceil(user._statsComputed.str * .05);
});
}
},
intimidate: {
text: 'Intimidating Gaze',
mana: 15,
lvl: 9,
lvl: 14,
target: 'party',
notes: "Your gaze strikes fear into the hearts of your party's enemies. The party gains a moderate boost to defense.",
cast: function(user, target) {
@@ -10123,7 +10111,7 @@ var global=self;/**
if ((_base = member.stats.buffs).con == null) {
_base.con = 0;
}
return member.stats.buffs.con = user._statsComputed.con * .2;
return member.stats.buffs.con += Math.ceil(user._statsComputed.con * .03);
});
}
}
@@ -10132,46 +10120,32 @@ var global=self;/**
pickPocket: {
text: 'Pickpocket',
mana: 10,
lvl: 6,
lvl: 11,
target: 'task',
notes: "Your nimble fingers run through the task's pockets and 'find' some treasures for yourself. You gain an increased gold bonus on the task and a higher chance of an item drop.",
notes: "Your nimble fingers run through the task's pockets and find some treasures for yourself. You gain an increased gold bonus on the task, higher yet the 'fatter' (greener) your task.",
cast: function(user, target) {
return user.stats.gp += ((target.value < 0 ? 0 : target.value) + 1) + user._statsComputed.per * .3;
return user.stats.gp += (target.value < 0 ? 1 : target.value + 1) + user._statsComputed.per * .075;
}
},
backStab: {
text: 'Backstab',
mana: 15,
lvl: 7,
lvl: 12,
target: 'task',
notes: "Without a sound, you sweep behind a task and stab it in the back. You deal higher damage to the task, with a higher chance of a critical hit.",
cast: function(user, target) {
var bonus, _crit;
_crit = crit(user, 'per', .5);
_crit = user.fns.crit('per', .3);
target.value += _crit * .03;
bonus = ((target.value < 0 ? 0 : target.value) + 1) * _crit;
bonus = (target.value < 0 ? 1 : target.value + 1) * _crit;
user.stats.exp += bonus;
return user.stats.gp += bonus;
}
},
stealth: {
text: 'Stealth',
mana: 20,
lvl: 8,
target: 'self',
notes: "You duck into the shadows, pulling up your hood. Many dailies won't find you this night; fewer yet the higher your Perception.",
cast: function(user, target) {
var _base;
if ((_base = user.stats.buffs).stealth == null) {
_base.stealth = 0;
}
return user.stats.buffs.stealth = Math.ceil(user._statsComputed.per * .075);
}
},
toolsOfTrade: {
text: 'Tools of the Trade',
mana: 25,
lvl: 9,
lvl: 13,
target: 'party',
notes: "You share your thievery tools with the party to aid them in 'acquiring' more gold. The party's gold bonus for tasks is buffed for a day.",
cast: function(user, target) {
@@ -10180,20 +10154,34 @@ var global=self;/**
if ((_base = member.stats.buffs).per == null) {
_base.per = 0;
}
return member.stats.buffs.per += user._statsComputed.per * .2;
return member.stats.buffs.per += Math.ceil(user._statsComputed.per * .03);
});
}
},
stealth: {
text: 'Stealth',
mana: 45,
lvl: 14,
target: 'self',
notes: "You duck into the shadows, pulling up your hood. Many dailies won't find you this night; fewer yet the higher your Perception.",
cast: function(user, target) {
var _base;
if ((_base = user.stats.buffs).stealth == null) {
_base.stealth = 0;
}
return user.stats.buffs.stealth = Math.ceil(user._statsComputed.per * .03);
}
}
},
healer: {
heal: {
text: 'Healing Light',
mana: 15,
lvl: 6,
lvl: 11,
target: 'self',
notes: 'Light covers your body, healing your wounds. You gain a boost to your health.',
cast: function(user, target) {
user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 10) * .5;
user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .075;
if (user.stats.hp > 50) {
return user.stats.hp = 50;
}
@@ -10202,7 +10190,7 @@ var global=self;/**
brightness: {
text: 'Searing Brightness',
mana: 15,
lvl: 7,
lvl: 12,
target: 'self',
notes: "You cast a burst of light that blinds all of your tasks. The redness of your tasks is reduced.",
cast: function(user, target) {
@@ -10210,14 +10198,14 @@ var global=self;/**
if (target.type === 'reward') {
return;
}
return target.value += user._statsComputed.int * .02;
return target.value += user._statsComputed.int * .006;
});
}
},
protectAura: {
text: 'Protective Aura',
mana: 30,
lvl: 8,
lvl: 13,
target: 'party',
notes: "A magical aura surrounds your party members, protecting them from damage. Your party members gain a buff to their defense.",
cast: function(user, target) {
@@ -10226,25 +10214,54 @@ var global=self;/**
if ((_base = member.stats.buffs).con == null) {
_base.con = 0;
}
return member.stats.buffs.con = user._statsComputed.con * .4;
return member.stats.buffs.con += Math.ceil(user._statsComputed.con * .15);
});
}
},
heallAll: {
text: 'Blessing',
mana: 25,
lvl: 9,
lvl: 14,
target: 'party',
notes: "Soothing light envelops your party and heals them of their injuries. Your party members gain a boost to their health.",
cast: function(user, target) {
return _.each(target, function(member) {
member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 10) * .3;
member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .04;
if (member.stats.hp > 50) {
return member.stats.hp = 50;
}
});
}
}
},
special: {
snowball: {
text: 'Snowball',
mana: 0,
value: 1,
target: 'user',
notes: "Throw a snowball at a party member, what could possibly go wrong? Lasts until member's new day.",
cast: function(user, target) {
var _base;
target.stats.buffs.snowball = true;
if ((_base = target.achievements).snowball == null) {
_base.snowball = 0;
}
target.achievements.snowball++;
return user.items.special.snowball--;
}
},
salt: {
text: 'Salt',
mana: 0,
value: 5,
target: 'self',
notes: 'Someone has snowballed you. Ha ha, very funny. Now get this snow off me!',
cast: function(user, target) {
user.stats.buffs.snowball = false;
return user.stats.gp -= 5;
}
}
}
};
@@ -10260,6 +10277,8 @@ var global=self;/**
});
});
api.special = api.spells.special;
/*
---------------------------------------------------------------
Drops
@@ -10528,7 +10547,7 @@ var global=self;/**
}).call(this);
},{"lodash":3}],6:[function(require,module,exports){
},{"lodash":3,"moment":4}],6:[function(require,module,exports){
var process=require("__browserify_process");(function() {
var api, content, dayMapping, moment, preenHistory, 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; };
@@ -10654,6 +10673,21 @@ var process=require("__browserify_process");(function() {
}
};
/*
A hyperbola function that creates diminishing returns, so you can't go to infinite (eg, with Exp gain).
{max} The asymptote
{bonus} All the numbers combined for your point bonus (eg, task.value * user.stats.int * critChance, etc)
{halfway} (optional) the point at which the graph starts bending
*/
api.diminishingReturns = function(bonus, max, halfway) {
if (halfway == null) {
halfway = bonus / 2;
}
return max * (bonus / (bonus + halfway));
};
/*
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
@@ -11309,10 +11343,10 @@ var process=require("__browserify_process");(function() {
purchase: function(req, cb) {
var item, key, type, _ref;
_ref = req.params, type = _ref.type, key = _ref.key;
if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food') {
if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'special') {
return cb({
code: 404,
message: ":type must be in [hatchingPotions,eggs,food]"
message: ":type must be in [hatchingPotions,eggs,food,special]"
}, req);
}
item = content[type][key];
@@ -11567,28 +11601,28 @@ var process=require("__browserify_process");(function() {
if (adjustvalue) {
task.value += nextDelta;
if (direction === 'up' && task.type !== 'reward' && !(task.type === 'habit' && !task.down)) {
task.value += nextDelta * user._statsComputed.str * .005;
task.value += nextDelta * user._statsComputed.str * .004;
}
}
return delta += nextDelta;
});
};
addPoints = function() {
var afterStreak, crit, gpMod, intMod, streakBonus;
crit = user.fns.predictableRandom() <= .03 ? 1.5 + (.05 * user._statsComputed.str) : 1;
intMod = 1 + (user._statsComputed.int * .075);
stats.exp += Math.round(delta * intMod * task.priority * crit);
gpMod = delta * task.priority * crit;
gpMod *= 1 + user._statsComputed.per * .03;
return stats.gp += task.streak ? (streakBonus = task.streak / 100 + 1, afterStreak = gpMod * streakBonus, gpMod > 0 ? user._tmp.streakBonus = afterStreak - gpMod : void 0, afterStreak) : gpMod;
var afterStreak, gpMod, intBonus, perBonus, streakBonus, _crit;
_crit = user.fns.crit();
intBonus = 1 + (user._statsComputed.int * .025);
stats.exp += Math.round(delta * intBonus * task.priority * _crit * 6);
perBonus = 1 + user._statsComputed.per * .02;
gpMod = delta * task.priority * _crit * perBonus;
return gpMod *= 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 conMod, hpMod;
conMod = 1 - (user._statsComputed.con / 100);
if (conMod < .1) {
conMod = 0.1;
var conBonus, hpMod;
conBonus = 1 - (user._statsComputed.con / 250);
if (conBonus < .1) {
conBonus = 0.1;
}
hpMod = delta * conMod * task.priority;
hpMod = delta * conBonus * task.priority * 2;
return stats.hp += Math.round(hpMod * 10) / 10;
};
switch (task.type) {
@@ -11643,6 +11677,9 @@ var process=require("__browserify_process");(function() {
} else {
calculateDelta();
addPoints();
if (!(user.stats.mp >= user._statsComputed.maxMP)) {
user.stats.mp++;
}
}
break;
case 'reward':
@@ -11712,6 +11749,19 @@ var process=require("__browserify_process");(function() {
x = Math.sin(seed++) * 10000;
return x - Math.floor(x);
},
crit: function(stat, chance) {
if (stat == null) {
stat = 'str';
}
if (chance == null) {
chance = .03;
}
if (user.fns.predictableRandom() <= chance) {
return 1.5 + (.02 * user._statsComputed[stat]);
} else {
return 1;
}
},
/*
Get a random property from an object
http://stackoverflow.com/questions/2532218/pick-random-property-from-a-javascript-object
@@ -11883,7 +11933,7 @@ var process=require("__browserify_process");(function() {
user.flags.dropsEnabled = true;
user.items.eggs["Wolf"] = 1;
}
if (!user.flags.classSelected && user.stats.lvl >= 5) {
if (!user.flags.classSelected && user.stats.lvl >= 10) {
return user.flags.classSelected;
}
},
+949 -523
View File
File diff suppressed because it is too large Load Diff
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 286 KiB

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

+69 -46
View File
@@ -1,5 +1,6 @@
_ = require 'lodash'
api = module.exports
moment = require 'moment'
###
---------------------------------------------------------------
@@ -130,6 +131,7 @@ gear =
0: text: "Shade Helm", notes:'Blood and ash, lava and obsidian give this helm its imagery and power. Increases INT by 20.', int: 20, value:150, canOwn: ((u)-> +u.backer?.tier >= 45)
1: text: "Crystal Helm", notes:'The favored crown of those who lead by example. Increases all attributes by 6.', con: 6, str: 6, per: 6, int: 6, value:170, canOwn: ((u)-> +u.contributor?.level >= 3)
2: text: "Nameless Helm", notes:'A testament to those who gave of themselves while asking nothing in return. Increases INT and STR by 25 each.', int: 25, str: 25, value:200, canOwn: ((u)-> +u.backer?.tier >= 300)
#candycane: text: "Candy Cane Hat", notes: 'A hat adorned in candy, a wintery treat!', value:10, canOwn: ((u)-> moment(u.auth.timestamps?.created).isBefore(new Date '01/10/2014'))
shield:
base:
@@ -207,50 +209,46 @@ api.potion = type: 'potion', text: "Health Potion", notes: "Recover 15 Health (I
Note, user.stats.mp is docked after automatically (it's appended to functions automatically down below in an _.each)
###
crit = (user, stat='per', chance=.03) ->
if user.fns.predictableRandom() <= chance then 1.5 + (.05*user._statsComputed[stat])
else 1
api.spells =
wizard:
fireball:
text: 'Burst of Flames'
mana: 10
lvl: 6
lvl: 11
target: 'task'
notes: 'With a crack, flames burst from your staff, scorching a task. You deal much higher damage to the task and gain additional experience.'
notes: 'With a crack, flames burst from your staff, scorching a task. You deal high damage to the task, and gain additional experience (more experience for greens).'
cast: (user, target) ->
target.value += user._statsComputed.int * .02 * crit(user, 'per')
user.stats.exp += Math.abs(target.value)
target.value += user._statsComputed.int * .0075 * user.fns.crit('per')
user.stats.exp += (if target.value < 0 then 1 else target.value+1) * 2.5
mpheal:
text: 'Ethereal Surge'
mana: 30
lvl: 7
lvl: 12
target: 'party'
notes: "A flow of magical energy rushes from your hands and recharges your party. Your party recovers MP.",
cast: (user, target)->
_.each target, (member) ->
bonus = Math.ceil(user._statsComputed.int * .2 + 5)
bonus = Math.ceil(user._statsComputed.int * .1)
bonus = 25 if bonus > 25 #prevent ability to replenish own mp infinitely
member.stats.mp += bonus
earth:
text: 'Earthquake'
mana: 35
lvl: 8
lvl: 13
target: 'party'
notes: "The ground below your party's tasks cracks and shakes with extreme intensity, slowing them down and opening them up to more attacks. Your party gains a buff to experience.",
cast: (user, target) ->
_.each target, (member) ->
member.stats.buffs.int ?= 0
member.stats.buffs.int = user._statsComputed.int * .2
member.stats.buffs.int += Math.ceil(user._statsComputed.int * .05)
frost:
text: 'Chilling Frost'
mana: 40
lvl: 9
lvl: 14
target: 'self'
notes: "Ice erupts from every surface, swallowing your tasks and freezing them in place. Your dailies' streaks won't reset at the end of the day."
cast: (user, target) ->
@@ -260,125 +258,148 @@ api.spells =
smash:
text: 'Brutal Smash'
mana: 10
lvl: 6
lvl: 11
target: 'task'
notes: "You savagely hit a single task with all of your might, beating it into submission. The task's redness decreases."
cast: (user, target) ->
target.value += user._statsComputed.str * .03
target.value += user._statsComputed.str * .01 * user.fns.crit('per')
defensiveStance:
text: 'Defensive Stance'
mana: 25
lvl: 7
lvl: 12
target: 'self'
notes: "You take a moment to relax your body and enter a defensive stance to ready yourself for the tasks' next onslaught. Reduces damage from dailies at the end of the day."
cast: (user, target) ->
user.stats.buffs.con ?= 0
user.stats.buffs.con += user._statsComputed.con * .3
user.stats.buffs.con += Math.ceil(user._statsComputed.con * .05)
valorousPresence:
text: 'Valorous Presence'
mana: 20
lvl: 8
lvl: 13
target: 'party'
notes: "Your presence emboldens the party. Their newfound courage gives them a boost of strength. Party members gain a buff to their STR."
cast: (user, target) ->
_.each target, (member) ->
member.stats.buffs.str ?= 0
member.stats.buffs.str += user._statsComputed.str * .2
member.stats.buffs.str += Math.ceil(user._statsComputed.str * .05)
intimidate:
text: 'Intimidating Gaze'
mana: 15
lvl: 9
lvl: 14
target: 'party'
notes: "Your gaze strikes fear into the hearts of your party's enemies. The party gains a moderate boost to defense."
cast: (user, target) ->
_.each target, (member) ->
member.stats.buffs.con ?= 0
member.stats.buffs.con = user._statsComputed.con * .2
member.stats.buffs.con += Math.ceil(user._statsComputed.con * .03)
rogue:
pickPocket:
text: 'Pickpocket'
mana: 10
lvl: 6
lvl: 11
target: 'task'
notes: "Your nimble fingers run through the task's pockets and 'find' some treasures for yourself. You gain an increased gold bonus on the task and a higher chance of an item drop."
notes: "Your nimble fingers run through the task's pockets and find some treasures for yourself. You gain an increased gold bonus on the task, higher yet the 'fatter' (greener) your task."
cast: (user, target) ->
user.stats.gp += ((if target.value < 0 then 0 else target.value) + 1) + user._statsComputed.per * .3
user.stats.gp += (if target.value < 0 then 1 else target.value+1) + user._statsComputed.per * .075
backStab:
text: 'Backstab'
mana: 15
lvl: 7
lvl: 12
target: 'task'
notes: "Without a sound, you sweep behind a task and stab it in the back. You deal higher damage to the task, with a higher chance of a critical hit."
cast: (user, target) ->
_crit = crit(user, 'per', .5)
_crit = user.fns.crit('per', .3)
target.value += _crit * .03
bonus = ((if target.value < 0 then 0 else target.value) + 1) * _crit
bonus = (if target.value < 0 then 1 else target.value+1) * _crit
user.stats.exp += bonus
user.stats.gp += bonus
stealth:
text: 'Stealth'
mana: 20
lvl: 8
target: 'self'
notes: "You duck into the shadows, pulling up your hood. Many dailies won't find you this night; fewer yet the higher your Perception."
cast: (user, target) ->
user.stats.buffs.stealth ?= 0
user.stats.buffs.stealth = Math.ceil(user._statsComputed.per * .075)
toolsOfTrade:
text: 'Tools of the Trade'
mana: 25
lvl: 9
lvl: 13
target: 'party'
notes: "You share your thievery tools with the party to aid them in 'acquiring' more gold. The party's gold bonus for tasks is buffed for a day."
cast: (user, target) ->
## lasts 24 hours ##
_.each target, (member) ->
member.stats.buffs.per ?= 0
member.stats.buffs.per += user._statsComputed.per * .2
member.stats.buffs.per += Math.ceil(user._statsComputed.per * .03)
stealth:
text: 'Stealth'
mana: 45
lvl: 14
target: 'self'
notes: "You duck into the shadows, pulling up your hood. Many dailies won't find you this night; fewer yet the higher your Perception."
cast: (user, target) ->
user.stats.buffs.stealth ?= 0
user.stats.buffs.stealth = Math.ceil(user._statsComputed.per * .03)
healer:
heal:
text: 'Healing Light'
mana: 15
lvl: 6
lvl: 11
target: 'self'
notes: 'Light covers your body, healing your wounds. You gain a boost to your health.'
cast: (user, target) ->
user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 10) *.5
user.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .075
user.stats.hp = 50 if user.stats.hp > 50
brightness:
text: 'Searing Brightness'
mana: 15
lvl: 7
lvl: 12
target: 'self'
notes: "You cast a burst of light that blinds all of your tasks. The redness of your tasks is reduced."
cast: (user, target) ->
_.each user.tasks, (target) ->
return if target.type is 'reward'
target.value += user._statsComputed.int * .02
target.value += user._statsComputed.int * .006
protectAura:
text: 'Protective Aura'
mana: 30
lvl: 8
lvl: 13
target: 'party'
notes: "A magical aura surrounds your party members, protecting them from damage. Your party members gain a buff to their defense."
cast: (user, target) ->
## lasts 24 hours ##
_.each target, (member) ->
member.stats.buffs.con ?= 0
member.stats.buffs.con = user._statsComputed.con * .4
member.stats.buffs.con += Math.ceil(user._statsComputed.con * .15)
heallAll:
text: 'Blessing'
mana: 25
lvl: 9
lvl: 14
target: 'party'
notes: "Soothing light envelops your party and heals them of their injuries. Your party members gain a boost to their health."
cast: (user, target) ->
_.each target, (member) ->
member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 10) * .3
member.stats.hp += (user._statsComputed.con + user._statsComputed.int + 5) * .04
member.stats.hp = 50 if member.stats.hp > 50
special:
snowball:
text: 'Snowball'
mana: 0
value: 1
target: 'user'
notes: "Throw a snowball at a party member, what could possibly go wrong? Lasts until member's new day."
cast: (user, target) ->
target.stats.buffs.snowball = true
target.achievements.snowball ?= 0
target.achievements.snowball++
user.items.special.snowball--
salt:
text: 'Salt'
mana: 0
value: 5
target: 'self'
notes: 'Someone has snowballed you. Ha ha, very funny. Now get this snow off me!'
cast: (user, target) ->
user.stats.buffs.snowball = false
user.stats.gp -= 5
# Intercept all spells to reduce user.stats.mp after casting the spell
_.each api.spells, (spellClass) ->
_.each spellClass, (spell, k) ->
@@ -389,6 +410,8 @@ _.each api.spells, (spellClass) ->
_cast(user,target)
user.stats.mp -= spell.mana
api.special = api.spells.special
###
---------------------------------------------------------------
Drops
+29 -15
View File
@@ -65,6 +65,16 @@ api.tnl = (lvl) ->
else Math.round(((Math.pow(lvl, 2) * 0.25) + (10 * lvl) + 139.75) / 10) * 10
# round to nearest 10?
###
A hyperbola function that creates diminishing returns, so you can't go to infinite (eg, with Exp gain).
{max} The asymptote
{bonus} All the numbers combined for your point bonus (eg, task.value * user.stats.int * critChance, etc)
{halfway} (optional) the point at which the graph starts bending
###
api.diminishingReturns = (bonus, max, halfway=bonus/2) ->
max*(bonus/(bonus+halfway))
###
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
@@ -85,7 +95,7 @@ preenHistory = (history) ->
newHistory.push
date: moment(group[0].date).toDate()
#date: moment(group[0].date).format('MM/DD/YYYY') # Use this one when testing
value: _.reduce(group, ((m, obj) -> m + api.value), 0) / group.length # average
value: _.reduce(group, ((m, obj) -> m + obj.value), 0) / group.length # average
true
# Keep the last:
@@ -418,7 +428,7 @@ api.wrap = (user) ->
task = user.tasks[req.params?.id]
return cb({code:404,message:'Task not found'}) unless task
i = user[task.type + "s"].indexOf(task)
user[task.type + "s"].splice i, 1 if ~i
user[task.type + "s"].splice(i, 1) if ~i
cb null, req
addTask: (req, cb) ->
@@ -499,7 +509,7 @@ api.wrap = (user) ->
# buy is for gear, purchase is for gem-purchaseables (i know, I know...)
purchase: (req, cb) ->
{type,key} = req.params
return cb({code:404,message:":type must be in [hatchingPotions,eggs,food]"},req) unless type in ['eggs','hatchingPotions', 'food']
return cb({code:404,message:":type must be in [hatchingPotions,eggs,food,special]"},req) unless type in ['eggs','hatchingPotions', 'food', 'special']
item = content[type][key]
return cb({code:404,message:":key not found for Content.#{type}"},req) unless item
user.items[type][key] = 0 unless user.items[type][key]
@@ -676,26 +686,25 @@ api.wrap = (user) ->
# (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)
# TODO STR Improves the amount by which Dailies and +/- Habits decrease in threat when scored, by .25% per point.
task.value += nextDelta * user._statsComputed.str * .005
task.value += nextDelta * user._statsComputed.str * .004
delta += nextDelta
addPoints = ->
# ===== CRITICAL HITS =====
crit =
if user.fns.predictableRandom() <= .03 then 1.5 + (.05*user._statsComputed.str)
else 1
_crit = user.fns.crit()
# Exp Modifier
# ===== Intelligence =====
# TODO Increases Experience gain by .2% per point.
intMod = 1 + (user._statsComputed.int * .075)
stats.exp += Math.round(delta * intMod * task.priority * crit)
intBonus = 1 + (user._statsComputed.int * .025)
stats.exp += Math.round (delta * intBonus * task.priority * _crit * 6)
# GP modifier
gpMod = delta * task.priority * crit
# ===== PERCEPTION =====
# TODO Increases Gold gained from tasks by .3% per point.
gpMod *= (1 + user._statsComputed.per *.03)
perBonus = (1 + user._statsComputed.per *.02)
gpMod = (delta * task.priority * _crit * perBonus)
gpMod *=
stats.gp +=
if task.streak
streakBonus = task.streak / 100 + 1 # eg, 1-day streak is 1.1, 2-day is 1.2, etc
@@ -708,9 +717,9 @@ api.wrap = (user) ->
subtractPoints = ->
# ===== CONSTITUTION =====
# TODO Decreases HP loss from bad habits / missed dailies by 0.5% per point.
conMod = 1 - (user._statsComputed.con / 100)
conMod = 0.1 if conMod < .1
hpMod = delta * conMod * task.priority
conBonus = 1 - (user._statsComputed.con / 250)
conBonus = 0.1 if conBonus < .1
hpMod = delta * conBonus * task.priority * 2 # constant 2 multiplier for better results
stats.hp += Math.round(hpMod * 10) / 10 # round to 1dp
switch task.type
@@ -753,6 +762,7 @@ api.wrap = (user) ->
else
calculateDelta()
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
user.stats.mp++ unless user.stats.mp >= user._statsComputed.maxMP
when 'reward'
# Don't adjust values for rewards
@@ -807,6 +817,10 @@ api.wrap = (user) ->
x = Math.sin(seed++) * 10000
x - Math.floor(x)
crit: (stat='str', chance=.03) ->
if user.fns.predictableRandom() <= chance then 1.5 + (.02*user._statsComputed[stat])
else 1
###
Get a random property from an object
http://stackoverflow.com/questions/2532218/pick-random-property-from-a-javascript-object
@@ -970,7 +984,7 @@ api.wrap = (user) ->
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
if !user.flags.classSelected and user.stats.lvl >= 10
user.flags.classSelected
###
+128 -115
View File
@@ -2,20 +2,51 @@ _ = require 'lodash'
expect = require 'expect.js'
sinon = require 'sinon'
moment = require 'moment'
# Custom modules
algos = require '../script/algos.coffee'
helpers = require '../script/helpers.coffee'
items = require '../script/items.coffee'
shared = require '../script/index.coffee'
### Helper Functions ####
newUser = (addTasks=true)->
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
user =
auth:
timestamps: {}
stats: {str:1, con:1, per:1, int:1, mp: 32, class: 'warrior', buffs: buffs}
items:
lastDrop:
count: 0
hatchingPotions: {}
eggs: {}
food: {}
gear:
equipped: {}
costume: {}
preferences: {}
dailys: []
todos: []
rewards: []
flags: {}
shared.wrap(user)
user.ops.reset(null, ->)
if addTasks
_.each ['habit', 'todo', 'daily'], (task)->
user.ops.addTask {body: {type: task, id: shared.uuid()}}
user
rewrapUser = (user)->
user._wrapped = false
shared.wrap(user)
user
expectStrings = (obj, paths) ->
_.each paths, (path) -> expect(obj[path]).to.be.ok()
# options.daysAgo: days ago when the last cron was executed
beforeAfter = (options={}) ->
user = helpers.newUser()
[before, after] = [_.cloneDeep(user), _.cloneDeep(user)]
user = newUser()
[before, after] = [user, _.cloneDeep(user)]
# avoid closure on the original user
rewrapUser(after)
before.preferences.dayStart = after.preferences.dayStart = options.dayStart if options.dayStart
before.preferences.timezoneOffset = after.preferences.timezoneOffset = (options.timezoneOffset or moment().zone())
if options.limitOne
@@ -44,7 +75,9 @@ expectGainedPoints = (before, after, taskType) ->
expect(after["#{taskType}s"][0].history).to.have.length(1) if taskType is 'habit'
# daily & todo histories handled on cron
expectNoChange = (before,after) -> expect(before).to.eql after
expectNoChange = (before,after) ->
_.each ['stats', 'items', 'gear', 'dailys', 'todos', 'rewards', 'flags', 'preferences'], (attr)->
expect(after[attr]).to.eql before[attr]
expectDayResetNoDamage = (b,a) ->
[before,after] = [_.cloneDeep(b); _.cloneDeep(a)]
@@ -61,17 +94,17 @@ expectDayResetNoDamage = (b,a) ->
_.each ['dailys','todos','history','lastCron'], (path) ->
_.each [before,after], (obj) -> delete obj[path]
delete after._tmp
expect(after).to.eql before
expectNoChange(before, after)
cycle = (array)->
n = -1
->
(seed=0)->
n++
return array[n % array.length]
repeatWithoutLastWeekday = ()->
repeat = {su:1,m:1,t:1,w:1,th:1,f:1,s:1}
if helpers.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday
if shared.startOfWeek(moment().zone(0)).isoWeekday() == 1 # Monday
repeat.su = false
else
repeat.s = false
@@ -81,143 +114,130 @@ repeatWithoutLastWeekday = ()->
describe 'User', ->
it 'sets correct user defaults', ->
user = helpers.newUser()
expect(user.stats).to.eql { gp: 0, exp: 0, lvl: 1, hp: 50 }
expect(user.items.weapon).to.eql 0
expect(user.items.armor).to.eql 0
expect(user.items.head).to.eql 0
expect(user.items.shield).to.eql 0
expect(user.items.lastDrop.count).to.eql 0
expect(user.preferences).to.eql { gender: 'm', skin: 'white', hair: 'blond', armorSet: 'v1', dayStart:0, showHelm: true }
expect(user.balance).to.eql 0
expect(user.lastCron).to.be.greaterThan 0
expect(user.flags).to.eql {partyEnabled: false, itemsEnabled: false, ads: 'show'}
expectStrings(user, ['apiToken'])
expectStrings(user.habits[0], ['text','id'])
expectStrings(user.dailys[0], ['text','id'])
expectStrings(user.todos[0], ['text','id'])
expectStrings(user.rewards[0], ['text','id'])
expectStrings(user.tags[0], ['name','id'])
expectStrings(user.tags[1], ['name','id'])
expectStrings(user.tags[2], ['name','id'])
user = newUser()
base_gear = { armor: 'armor_base_0', weapon: 'weapon_base_0', head: 'head_base_0', shield: 'shield_base_0' }
buffs = {per:0, int:0, con:0, str:0, stealth: 0, streaks: false}
expect(user.stats).to.eql { str: 1, con: 1, per: 1, int: 1, hp: 50, mp: 32, lvl: 1, exp: 0, gp: 0, class: 'warrior', buffs: buffs }
expect(user.items.gear).to.eql { equipped: base_gear, costume: base_gear, owned: {weapon_warrior_0: true} }
expect(user.preferences).to.eql { costume: false }
it 'revives correctly', ->
user = helpers.newUser()
user = newUser()
user.stats = { gp: 10, exp: 100, lvl: 2, hp: 1 }
user.weapon = 1
algos.revive user
expect(user.stats).to.eql { gp: 0, exp: 0, lvl: 1, hp: 50 }
expect(user.items.weapon).to.eql 0
expect(user.items.armor).to.eql 0
expect(user.items.head).to.eql 0
expect(user.items.shield).to.eql 0
user.ops.revive()
expect(user.stats.gp).to.eql 0
expect(user.stats.exp).to.eql 0
expect(user.stats.lvl).to.eql 1
expect(user.stats.hp).to.eql 50
expect(user.items.gear.owned).to.eql { weapon_warrior_0: false }
describe 'store', ->
it 'recovers hp buying potions', ->
user = helpers.newUser()
user = newUser()
user.stats.hp = 30
user.stats.gp = 50
expect(items.buyItem user, 'potion').to.be true
user.ops.buy {params: {key: 'potion'}}
expect(user.stats.hp).to.eql 45
expect(user.stats.gp).to.eql 25
expect(items.buyItem user, 'potion').to.be true
user.ops.buy {params: {key: 'potion'}}
expect(user.stats.hp).to.eql 50 # don't exceed max hp
expect(user.stats.gp).to.eql 0
it 'buys equipment', ->
user = helpers.newUser()
user = newUser()
user.stats.gp = 31
expect(items.buyItem user, 'armor').to.be true
expect(user.items.armor).to.eql 1
user.ops.buy {params: {key: 'armor_warrior_1'}}
expect(user.items.gear.owned).to.eql { weapon_warrior_0: true, armor_warrior_1: true }
expect(user.items.gear.equipped).to.eql { armor: 'armor_warrior_1', weapon: 'weapon_base_0', head: 'head_base_0', shield: 'shield_base_0' }
expect(user.stats.gp).to.eql 1
it 'do not buy equipment without enough money', ->
user = helpers.newUser()
user = newUser()
user.stats.gp = 1
expect(items.buyItem user, 'armor').to.be false
expect(user.items.armor).to.eql 0
user.ops.buy {params: {key: 'armor_warrior_1'}}
expect(user.items.gear.equipped).to.eql { armor: 'armor_base_0', weapon: 'weapon_base_0', head: 'head_base_0', shield: 'shield_base_0' }
expect(user.stats.gp).to.eql 1
describe 'drop system', ->
user = null
beforeEach ->
user = helpers.newUser()
user = newUser()
user.flags.dropsEnabled = true
# too many Math.random calls to stub, let's return the last element
sinon.stub(helpers, 'randomVal', (obj)->
# too many predictableRandom calls to stub, let's return the last element
sinon.stub(user.fns, 'randomVal', (obj)->
result = undefined
for key, val of obj
result = val
result
)
@task_id = shared.uuid()
user.ops.addTask({body: {type: 'daily', id: @task_id}})
it 'gets a golden potion', ->
sinon.stub(Math, 'random').returns 0
algos.score(user, user.dailys[0], 'up')
sinon.stub(user.fns, 'predictableRandom').returns 0
user.ops.score {params: { id: @task_id, direction: 'up'}}
expect(user.items.eggs).to.eql {}
expect(user.items.hatchingPotions).to.eql {'Golden': 1}
expect(user.items.food).to.eql {}
it 'gets a bear cub egg', ->
sinon.stub(Math, 'random', cycle [0, 0.55])
algos.score(user, user.dailys[0], 'up')
sinon.stub(user.fns, 'predictableRandom', cycle [0, 0, 0.55])
user.ops.score {params: { id: @task_id, direction: 'up'}}
expect(user.items.eggs).to.eql {'BearCub': 1}
expect(user.items.hatchingPotions).to.eql {}
expect(user.items.food).to.eql {}
it 'gets honey', ->
sinon.stub(Math, 'random', cycle [0, 0.9])
algos.score(user, user.dailys[0], 'up')
sinon.stub(user.fns, 'predictableRandom', cycle [0, 0, 0.9])
user.ops.score {params: { id: @task_id, direction: 'up'}}
expect(user.items.eggs).to.eql {}
expect(user.items.hatchingPotions).to.eql {}
expect(user.items.food).to.eql {'Honey': 1}
it 'does not get a drop', ->
sinon.stub(Math, 'random').returns 0.5
algos.score(user, user.dailys[0], 'up')
sinon.stub(user.fns, 'predictableRandom').returns 0.5
user.ops.score {params: { id: @task_id, direction: 'up'}}
expect(user.items.eggs).to.eql {}
expect(user.items.hatchingPotions).to.eql {}
expect(user.items.food).to.eql {}
afterEach ->
Math.random.restore()
helpers.randomVal.restore()
user.fns.randomVal.restore()
user.fns.predictableRandom.restore()
describe 'Simple Scoring', ->
beforeEach ->
{@before, @after} = beforeAfter()
it 'Habits : Up', ->
{before,after} = beforeAfter()
algos.score(after, after.habits[0], 'down', {times:5})
expectLostPoints(before,after,'habit')
@after.ops.score {params: {id: @after.habits[0].id, direction: 'down'}, query: {times: 5}}
expectLostPoints(@before, @after,'habit')
it 'Habits : Down', ->
{before,after} = beforeAfter()
algos.score(after, after.habits[0], 'up', {times:5})
expectGainedPoints(before,after,'habit')
@after.ops.score {params: {id: @after.habits[0].id, direction: 'up'}, query: {times: 5}}
expectGainedPoints(@before, @after,'habit')
it 'Dailys : Up', ->
{before,after} = beforeAfter()
algos.score(after, after.dailys[0], 'up')
expectGainedPoints(before,after,'daily')
@after.ops.score {params: {id: @after.dailys[0].id, direction: 'up'}}
expectGainedPoints(@before, @after,'daily')
it 'Todos : Up', ->
{before,after} = beforeAfter()
algos.score(after, after.todos[0], 'up')
expectGainedPoints(before,after,'todo')
@after.ops.score {params: {id: @after.todos[0].id, direction: 'up'}}
expectGainedPoints(@before, @after,'todo')
describe 'Cron', ->
it 'computes shouldCron', ->
user = helpers.newUser()
user = newUser()
paths = {};algos.cron user, {paths}
expect(paths.lastCron).to.not.be.ok # it setup the cron property now
paths = {};user.fns.cron {paths}
expect(user.lastCron).to.not.be.ok # it setup the cron property now
user.lastCron = +moment().subtract('days',1)
paths = {};algos.cron user, {paths}
expect(paths.lastCron).to.be true
paths = {};user.fns.cron {paths}
expect(user.lastCron).to.be.greaterThan 0
# user.lastCron = +moment().add('days',1)
# paths = {};algos.cron user, {paths}
@@ -226,7 +246,7 @@ describe 'Cron', ->
it 'only dailies & todos are effected', ->
{before,after} = beforeAfter({daysAgo:1})
before.dailys = before.todos = after.dailys = after.todos = []
algos.cron(after)
after.fns.cron()
expect(after.lastCron).to.not.be before.lastCron # make sure cron was run
expect(before.stats).to.eql after.stats
beforeTasks = before.habits.concat(before.dailys).concat(before.todos).concat(before.rewards)
@@ -288,7 +308,7 @@ describe 'Cron', ->
]
after.history = {exp: _.cloneDeep(history), todos: _.cloneDeep(history)}
after.habits[0].history = _.cloneDeep(history)
algos.cron(after)
after.fns.cron()
# remove history entries created by cron
after.history.exp.pop()
@@ -301,7 +321,7 @@ describe 'Cron', ->
it '1 day missed', ->
{before,after} = beforeAfter({daysAgo:1})
before.dailys = after.dailys = []
algos.cron(after)
after.fns.cron()
# todos don't effect stats
expect(after.stats.hp).to.be 50
@@ -324,14 +344,14 @@ describe 'Cron', ->
runCron = (options) ->
_.each [480, 240, 0, -120], (timezoneOffset) -> # test different timezones
now = helpers.startOfWeek({timezoneOffset}).add('hours', options.currentHour||0)
now = shared.startOfWeek({timezoneOffset}).add('hours', options.currentHour||0)
{before,after} = beforeAfter({now, timezoneOffset, daysAgo:1, dayStart:options.dayStart||0, limitOne:'daily'})
before.dailys[0].repeat = after.dailys[0].repeat = options.repeat if options.repeat
before.dailys[0].streak = after.dailys[0].streak = 10
before.dailys[0].completed = after.dailys[0].completed = true if options.checked
if options.shouldDo
expect(helpers.shouldDo(now, options.repeat, {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
algos.cron(after,{now})
expect(shared.shouldDo(now, options.repeat, {timezoneOffset, dayStart:options.dayStart, now})).to.be.ok()
after.fns.cron {now}
switch options.expect
when 'losePoints' then expectLostPoints(before,after,'daily')
when 'noChange' then expectNoChange(before,after)
@@ -395,52 +415,45 @@ describe 'Cron', ->
it 'calculates day differences with dayStart properly', ->
dayStart = 4
yesterday = helpers.startOfDay {now: moment().subtract('d', 1), dayStart}
now = helpers.startOfDay {dayStart: dayStart-1}
expect(helpers.daysSince(yesterday, {now, dayStart})).to.eql 0
yesterday = shared.startOfDay {now: moment().subtract('d', 1), dayStart}
now = shared.startOfDay {dayStart: dayStart-1}
expect(shared.daysSince(yesterday, {now, dayStart})).to.eql 0
now = moment().startOf('day').add('h', dayStart).add('m', 1)
expect(helpers.daysSince(yesterday, {now, dayStart})).to.eql 1
expect(shared.daysSince(yesterday, {now, dayStart})).to.eql 1
describe 'Helper', ->
it 'calculates gold coins', ->
expect(helpers.gold(10)).to.eql 10
expect(helpers.gold(1.957)).to.eql 1
expect(helpers.gold()).to.eql 0
expect(shared.gold(10)).to.eql 10
expect(shared.gold(1.957)).to.eql 1
expect(shared.gold()).to.eql 0
it 'calculates silver coins', ->
expect(helpers.silver(10)).to.eql 0
expect(helpers.silver(1.957)).to.eql 95
expect(helpers.silver(0.01)).to.eql "01"
expect(helpers.silver()).to.eql "00"
expect(shared.silver(10)).to.eql 0
expect(shared.silver(1.957)).to.eql 95
expect(shared.silver(0.01)).to.eql "01"
expect(shared.silver()).to.eql "00"
it 'calculates experience to next level', ->
expect(algos.tnl 1).to.eql 150
expect(algos.tnl 2).to.eql 160
expect(algos.tnl 10).to.eql 260
expect(algos.tnl 99).to.eql 3580
expect(algos.tnl 100).to.eql 0
it 'calculates priority values', ->
expect(algos.priorityValue()).to.eql 1
expect(algos.priorityValue '!').to.eql 1
expect(algos.priorityValue '!!').to.eql 1.5
expect(algos.priorityValue '!!!').to.eql 2
expect(algos.priorityValue '!!!!').to.eql 1
expect(shared.tnl 1).to.eql 150
expect(shared.tnl 2).to.eql 160
expect(shared.tnl 10).to.eql 260
expect(shared.tnl 99).to.eql 3580
expect(shared.tnl 100).to.eql 0
it 'calculates the start of the day', ->
expect(helpers.startOfDay({now: new Date(2013, 0, 1, 0)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
expect(helpers.startOfDay({now: new Date(2013, 0, 1, 5)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
expect(helpers.startOfDay({now: new Date(2013, 0, 1, 23, 59, 59)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
expect(shared.startOfDay({now: new Date(2013, 0, 1, 0)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
expect(shared.startOfDay({now: new Date(2013, 0, 1, 5)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
expect(shared.startOfDay({now: new Date(2013, 0, 1, 23, 59, 59)}).format('YYYY-MM-DD HH:mm')).to.eql '2013-01-01 00:00'
it 'counts pets', ->
pets = {}
expect(helpers.countPets(null, pets)).to.eql 0
expect(helpers.countPets(1, pets)).to.eql 1
expect(shared.countPets(null, pets)).to.eql 0
expect(shared.countPets(1, pets)).to.eql 1
pets = { "Dragon-Red": 1, "Wolf-Base": 2 }
expect(helpers.countPets(null, pets)).to.eql 2
expect(helpers.countPets(2, pets)).to.eql 2
expect(shared.countPets(null, pets)).to.eql 2
expect(shared.countPets(2, pets)).to.eql 2
pets = { "Wolf-Base": 2, "Wolf-Veteran": 1, "Wolf-Cerberus": 1, "Dragon-Hydra": 1}
expect(helpers.countPets(null, pets)).to.eql 1
expect(helpers.countPets(_.size(pets), pets)).to.eql 1
expect(shared.countPets(null, pets)).to.eql 1
expect(shared.countPets(_.size(pets), pets)).to.eql 1
+146
View File
@@ -0,0 +1,146 @@
###
1) clone the repo
2) npm install
3) coffee ./tests/math_samples.coffee
Current results at https://gist.github.com/lefnire/8049676
###
shared = require '../script/index.coffee'
_ = require 'lodash'
id = shared.uuid()
user =
stats: {class: 'warrior', buffs: {per:0,int:0,con:0,str:0}}
items:
eggs: {}
hatchingPotions: {}
food: {}
gear:
equipped:
weapon: 'weapon_warrior_4'
armor: 'armor_warrior_4'
shield: 'shield_warrior_4'
head: 'head_warrior_4'
habits: [
shared.taskDefaults({id, value: 0})
]
dailys: []
todos: []
rewards: []
shared.wrap(user)
s = user.stats
task = user.tasks[id]
party = [user]
console.log "\n\n================================================"
console.log "New Simulation"
console.log "================================================\n\n"
clearUser = (lvl=1) ->
_.merge user.stats, {exp:0, gp:0, hp:50, lvl:lvl, str:lvl, con:lvl, per:lvl, int:lvl, mp: 100}
_.merge s.buffs, {str:0,con:0,int:0,per:0}
_.each [1,50,99], (lvl) ->
console.log "[LEVEL #{lvl}] (#{lvl} points in every attr)\n\n"
_.each {red:-5,yellow:0,green:5}, (taskVal, color) ->
console.log "[task.value = #{taskVal} (#{color})]"
console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus"
_.each ['up','down'], (direction) ->
clearUser(lvl)
b4 = {hp:s.hp, taskVal}
task.value = taskVal
delta = user.ops.score params:{id, direction}
console.log "#{if direction is 'up' then '' else ''}\t\t#{s.exp}/#{shared.tnl(s.lvl)}\t\t#{(b4.hp-s.hp).toFixed(1)}\t#{s.gp.toFixed(1)}\t#{delta.toFixed(1)}\t\t#{(task.value-b4.taskVal-delta).toFixed(1)}"
str = '- [Wizard]'
task.value = taskVal;clearUser(lvl)
b4 = {taskVal}
shared.content.spells.wizard.fireball.cast(user,task)
str += "\tfireball(task.valΔ:#{(task.value-taskVal).toFixed(1)} exp:#{s.exp.toFixed(1)})"
task.value = taskVal;clearUser(lvl)
_party = [user, {stats:{mp:0}}]
shared.content.spells.wizard.mpheal.cast(user,_party)
str += "\t| mpheal(mp:#{_party[1].stats.mp})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.wizard.earth.cast(user,party)
str += "\t\t\t\t| earth(buffs.int:#{s.buffs.int})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.wizard.frost.cast(user,{})
str += "\t\t\t| frost(N/A)"
console.log str
str = '- [Warrior]'
task.value = taskVal;clearUser(lvl)
shared.content.spells.warrior.smash.cast(user,task)
b4 = {taskVal}
str += "\tsmash(task.valΔ:#{(task.value-taskVal).toFixed(1)})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.warrior.defensiveStance.cast(user,{})
str += "\t\t| defensiveStance(buffs.con:#{s.buffs.con})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.warrior.valorousPresence.cast(user,party)
str += "\t\t\t| valorousPresence(buffs.str:#{s.buffs.str})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.warrior.intimidate.cast(user,party)
str += "\t\t| intimidate(buffs.con:#{s.buffs.con})"
console.log str
str = '- [Rogue]'
task.value = taskVal;clearUser(lvl)
shared.content.spells.rogue.pickPocket.cast(user,task)
str += "\tpickPocket(gp:#{s.gp.toFixed(1)})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.rogue.backStab.cast(user,task)
b4 = {taskVal}
str += "\t\t| backStab(task.valΔ:#{(task.value-b4.taskVal).toFixed(1)} exp:#{s.exp.toFixed(1)} gp:#{s.gp.toFixed(1)})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.rogue.toolsOfTrade.cast(user,party)
str += "\t| toolsOfTrade(buffs.per:#{s.buffs.per})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.rogue.stealth.cast(user,{})
str += "\t\t| stealth(avoiding #{user.stats.buffs.stealth} tasks)"
console.log str
str = '- [Healer]'
task.value = taskVal;clearUser(lvl)
s.hp=0
shared.content.spells.healer.heal.cast(user,{})
str += "\theal(hp:#{s.hp.toFixed(1)})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.healer.brightness.cast(user,{})
b4 = {taskVal}
str += "\t\t\t| brightness(task.valΔ:#{(task.value-b4.taskVal).toFixed(1)})"
task.value = taskVal;clearUser(lvl)
shared.content.spells.healer.protectAura.cast(user,party)
str += "\t\t\t| protectAura(buffs.con:#{s.buffs.con})"
task.value = taskVal;clearUser(lvl)
s.hp=0
shared.content.spells.healer.heallAll.cast(user,party)
str += "\t\t| heallAll(hp:#{s.hp.toFixed(1)})"
console.log str
console.log '\n'
console.log '------------------------------------------------------------'