Merge branch 'bosses' into develop
|
|
@ -2,6 +2,12 @@
|
|||
margin-left: 1.25em;
|
||||
}*/
|
||||
|
||||
.npc_ian {
|
||||
background: url("../img/sprites/npc_ian.gif") no-repeat;
|
||||
width: 78px;
|
||||
height: 135px;
|
||||
}
|
||||
|
||||
.Gems {
|
||||
display:inline-block;
|
||||
margin-right:5px;
|
||||
|
|
|
|||
1154
dist/customizer.css
vendored
2
dist/habitrpg-shared.css
vendored
210
dist/habitrpg-shared.js
vendored
|
|
@ -10010,8 +10010,13 @@ var global=self;/**
|
|||
target: 'task',
|
||||
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) {
|
||||
var bonus;
|
||||
target.value += user._statsComputed.int * .0075 * user.fns.crit('per');
|
||||
return user.stats.exp += (target.value < 0 ? 1 : target.value + 1) * 2.5;
|
||||
bonus = (target.value < 0 ? 1 : target.value + 1) * 2.5;
|
||||
user.stats.exp += bonus;
|
||||
if (user.party.quest.key) {
|
||||
return user.party.quest.tally.up += bonus;
|
||||
}
|
||||
}
|
||||
},
|
||||
mpheal: {
|
||||
|
|
@ -10066,7 +10071,10 @@ var global=self;/**
|
|||
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 * .01 * user.fns.crit('per');
|
||||
target.value += user._statsComputed.str * .01 * user.fns.crit('per');
|
||||
if (user.party.quest.key) {
|
||||
return user.party.quest.tally.up += Math.ceil(user._statsComputed.str * .2);
|
||||
}
|
||||
}
|
||||
},
|
||||
defensiveStance: {
|
||||
|
|
@ -10266,9 +10274,9 @@ var global=self;/**
|
|||
};
|
||||
|
||||
_.each(api.spells, function(spellClass) {
|
||||
return _.each(spellClass, function(spell, k) {
|
||||
return _.each(spellClass, function(spell, key) {
|
||||
var _cast;
|
||||
spell.name = k;
|
||||
spell.key = key;
|
||||
_cast = spell.cast;
|
||||
return spell.cast = function(user, target) {
|
||||
_cast(user, target);
|
||||
|
|
@ -10329,10 +10337,10 @@ var global=self;/**
|
|||
}
|
||||
};
|
||||
|
||||
_.each(api.eggs, function(egg, k) {
|
||||
_.each(api.eggs, function(egg, key) {
|
||||
return _.defaults(egg, {
|
||||
value: 3,
|
||||
name: k,
|
||||
key: key,
|
||||
notes: "Find a hatching potion to pour on this egg, and it will hatch into a " + egg.adjective + " " + egg.text + ".",
|
||||
mountText: egg.text
|
||||
});
|
||||
|
|
@ -10342,7 +10350,8 @@ var global=self;/**
|
|||
'Wolf-Veteran': true,
|
||||
'Wolf-Cerberus': true,
|
||||
'Dragon-Hydra': true,
|
||||
'Turkey-Base': true
|
||||
'Turkey-Base': true,
|
||||
'BearCub-Polar': true
|
||||
};
|
||||
|
||||
api.hatchingPotions = {
|
||||
|
|
@ -10388,9 +10397,9 @@ var global=self;/**
|
|||
}
|
||||
};
|
||||
|
||||
_.each(api.hatchingPotions, function(pot, k) {
|
||||
_.each(api.hatchingPotions, function(pot, key) {
|
||||
return _.defaults(pot, {
|
||||
name: k,
|
||||
key: key,
|
||||
value: 2,
|
||||
notes: "Pour this on an egg, and it will hatch as a " + pot.text + " pet."
|
||||
});
|
||||
|
|
@ -10444,14 +10453,65 @@ var global=self;/**
|
|||
}
|
||||
};
|
||||
|
||||
_.each(api.food, function(food, k) {
|
||||
_.each(api.food, function(food, key) {
|
||||
return _.defaults(food, {
|
||||
value: 1,
|
||||
name: k,
|
||||
key: key,
|
||||
notes: "Feed this to a pet and it may grow into a sturdy steed."
|
||||
});
|
||||
});
|
||||
|
||||
api.quests = {
|
||||
evilsanta: {
|
||||
text: "Trapper Santa",
|
||||
notes: "You hear bemoaned roars deep in the icefields. You follow the roars and growls - punctuated by another voice's cackling - to a clearing in the woods where you see a fully-grown polar bear. She's caged and shackled, roaring for life. Dancing atop the the cage is a malicious little imp wearing castaway Christmas costumes. Vanquish Trapper Santa, and save the beast!",
|
||||
completion: "Trapper Santa squeels in anger, and bounces off into the night. A grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the whisperer listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured. Help her find her baby!",
|
||||
value: 4,
|
||||
boss: {
|
||||
name: "Trapper Santa",
|
||||
hp: 300,
|
||||
str: 1
|
||||
},
|
||||
drop: {
|
||||
type: 'mounts',
|
||||
key: 'BearCub-Polar',
|
||||
text: "Polar Bear (Mount)",
|
||||
gp: 20,
|
||||
exp: 100
|
||||
}
|
||||
},
|
||||
evilsanta2: {
|
||||
text: "Find The Cub",
|
||||
notes: "Mama bear's cub had run off into the icefields when she was captured by the trapper. At the edge of the woods, she sniffs the air. You hear twig-snaps and snow crunch through the crystaline sound of the forest. Paw prints! You both start racing to follow the trail. Find all the prints and broken twigs, and retrieve her cub!",
|
||||
completion: "You've found the cub! Mama and baby bear couldn't be more grateful. As a token, they've decided to keep you company till the end of days.",
|
||||
value: 4,
|
||||
previous: 'evilsanta',
|
||||
collect: {
|
||||
tracks: {
|
||||
text: 'Tracks',
|
||||
count: 20
|
||||
},
|
||||
branches: {
|
||||
text: 'Broken Twigs',
|
||||
count: 10
|
||||
}
|
||||
},
|
||||
drop: {
|
||||
type: 'pets',
|
||||
key: 'BearCub-Polar',
|
||||
text: "Polar Bear (Pet)",
|
||||
gp: 20,
|
||||
exp: 100
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
_.each(api.quests, function(v, key) {
|
||||
return _.defaults(v, {
|
||||
key: key
|
||||
});
|
||||
});
|
||||
|
||||
repeat = {
|
||||
m: true,
|
||||
t: true,
|
||||
|
|
@ -10688,6 +10748,10 @@ var process=require("__browserify_process");(function() {
|
|||
return max * (bonus / (bonus + halfway));
|
||||
};
|
||||
|
||||
api.monod = function(bonus, rateOfIncrease, max) {
|
||||
return rateOfIncrease * max * bonus / (rateOfIncrease * bonus + max);
|
||||
};
|
||||
|
||||
/*
|
||||
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
|
||||
|
|
@ -11293,7 +11357,7 @@ var process=require("__browserify_process");(function() {
|
|||
message: ":pet not found in user.items.pets"
|
||||
});
|
||||
}
|
||||
if (!((_ref2 = user.items.food) != null ? _ref2[food.name] : void 0)) {
|
||||
if (!((_ref2 = user.items.food) != null ? _ref2[food.key] : void 0)) {
|
||||
return cb({
|
||||
code: 404,
|
||||
message: ":food not found in user.items.food"
|
||||
|
|
@ -11305,7 +11369,7 @@ var process=require("__browserify_process");(function() {
|
|||
message: "Can't feed this pet."
|
||||
});
|
||||
}
|
||||
if (user.items.mounts[pet] && (userPets[pet] >= 50 || food.name === 'Saddle')) {
|
||||
if (user.items.mounts[pet] && (userPets[pet] >= 50 || food.key === 'Saddle')) {
|
||||
return cb({
|
||||
code: 401,
|
||||
message: "You already have that mount"
|
||||
|
|
@ -11320,21 +11384,21 @@ var process=require("__browserify_process");(function() {
|
|||
}
|
||||
return message = "You have tamed " + egg + ", let's go for a ride!";
|
||||
};
|
||||
if (food.name === 'Saddle') {
|
||||
if (food.key === 'Saddle') {
|
||||
evolve();
|
||||
} else {
|
||||
if (food.target === potion) {
|
||||
userPets[pet] += 5;
|
||||
message = "" + egg + " really likes the " + food.name + "!";
|
||||
message = "" + egg + " really likes the " + food.key + "!";
|
||||
} else {
|
||||
userPets[pet] += 2;
|
||||
message = "" + egg + " eats the " + food.name + " but doesn't seem to enjoy it.";
|
||||
message = "" + egg + " eats the " + food.key + " but doesn't seem to enjoy it.";
|
||||
}
|
||||
if (userPets[pet] >= 50 && !user.items.mounts[pet]) {
|
||||
evolve();
|
||||
}
|
||||
}
|
||||
user.items.food[food.name]--;
|
||||
user.items.food[food.key]--;
|
||||
return cb({
|
||||
code: 200,
|
||||
message: message
|
||||
|
|
@ -11343,10 +11407,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' && type !== 'special') {
|
||||
if (type !== 'eggs' && type !== 'hatchingPotions' && type !== 'food' && type !== 'quests' && type !== 'special') {
|
||||
return cb({
|
||||
code: 404,
|
||||
message: ":type must be in [hatchingPotions,eggs,food,special]"
|
||||
message: ":type must be in [hatchingPotions,eggs,food,quests,special]"
|
||||
}, req);
|
||||
}
|
||||
item = content[type][key];
|
||||
|
|
@ -11590,19 +11654,21 @@ var process=require("__browserify_process");(function() {
|
|||
return cb('Not enough Gold');
|
||||
}
|
||||
delta = 0;
|
||||
calculateDelta = function(adjustvalue) {
|
||||
if (adjustvalue == null) {
|
||||
adjustvalue = true;
|
||||
}
|
||||
calculateDelta = function() {
|
||||
return _.times(options.times, function() {
|
||||
var currVal, nextDelta;
|
||||
var adjustAmt, currVal, nextDelta, _ref1;
|
||||
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) {
|
||||
task.value += nextDelta;
|
||||
if (task.type !== 'reward') {
|
||||
adjustAmt = nextDelta;
|
||||
if (direction === 'up' && task.type !== 'reward' && !(task.type === 'habit' && !task.down)) {
|
||||
task.value += nextDelta * user._statsComputed.str * .004;
|
||||
adjustAmt = nextDelta * (1 + user._statsComputed.str * .004);
|
||||
user.party.quest.progress.up = user.party.quest.progress.up || 0;
|
||||
if ((_ref1 = task.type) === 'daily' || _ref1 === 'todo') {
|
||||
user.party.quest.progress.up += adjustAmt;
|
||||
}
|
||||
}
|
||||
task.value += adjustAmt;
|
||||
}
|
||||
return delta += nextDelta;
|
||||
});
|
||||
|
|
@ -11683,7 +11749,7 @@ var process=require("__browserify_process");(function() {
|
|||
}
|
||||
break;
|
||||
case 'reward':
|
||||
calculateDelta(false);
|
||||
calculateDelta();
|
||||
stats.gp -= Math.abs(task.value);
|
||||
num = parseFloat(task.value).toFixed(2);
|
||||
if (stats.gp < 0) {
|
||||
|
|
@ -11805,45 +11871,44 @@ var process=require("__browserify_process");(function() {
|
|||
}), user);
|
||||
},
|
||||
randomDrop: function(modifiers) {
|
||||
var a, acceptableDrops, alpha, chanceMultiplier, delta, drop, max, priority, rarity, reachedDropLimit, streak, _base, _base1, _base2, _base3, _name, _name1, _name2, _ref, _ref1;
|
||||
delta = modifiers.delta;
|
||||
_ref = modifiers.task, priority = _ref.priority, streak = _ref.streak;
|
||||
if (streak == null) {
|
||||
streak = 0;
|
||||
var acceptableDrops, bonus, chance, drop, dropK, quest, rarity, task, _base, _base1, _base2, _name, _name1, _name2, _ref, _ref1;
|
||||
task = modifiers.task;
|
||||
bonus = Math.abs(task.value) * task.priority + (task.streak || 0) + (user._statsComputed.per * .5);
|
||||
bonus /= 100;
|
||||
chance = api.diminishingReturns(bonus, 1, 0.5);
|
||||
console.log("Drop Equation: Bonus(" + (bonus.toFixed(3)) + "), Modified Chance(" + (chance.toFixed(3)) + ")\n");
|
||||
quest = content.quests[(_ref = user.party.quest) != null ? _ref.key : void 0];
|
||||
if ((quest != null ? quest.collect : void 0) && user.fns.predictableRandom(user.stats.gp) < bonus) {
|
||||
dropK = user.fns.randomVal(quest.collect, {
|
||||
key: true
|
||||
});
|
||||
user.party.quest.progress.collect[dropK]++;
|
||||
if (typeof user.markModified === "function") {
|
||||
user.markModified('party.quest.progress');
|
||||
}
|
||||
console.log({
|
||||
progress: user.party.quest.progress
|
||||
});
|
||||
}
|
||||
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) {
|
||||
if ((api.daysSince(user.items.lastDrop.date, user.preferences) === 0) && (user.items.lastDrop.count >= 5)) {
|
||||
return;
|
||||
}
|
||||
chanceMultiplier = Math.abs(delta);
|
||||
chanceMultiplier *= priority;
|
||||
chanceMultiplier += streak;
|
||||
chanceMultiplier += user._statsComputed.per * .3;
|
||||
max = 0.75;
|
||||
a = 0.1;
|
||||
alpha = a * max * chanceMultiplier / (a * chanceMultiplier + max);
|
||||
if (((_ref1 = user.flags) != null ? _ref1.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < alpha) {
|
||||
if (((_ref1 = user.flags) != null ? _ref1.dropsEnabled : void 0) && user.fns.predictableRandom(user.stats.exp) < chance) {
|
||||
rarity = user.fns.predictableRandom(user.stats.gp);
|
||||
if (rarity > .6) {
|
||||
drop = user.fns.randomVal(_.omit(content.food, 'Saddle'));
|
||||
if ((_base1 = user.items.food)[_name = drop.name] == null) {
|
||||
_base1[_name] = 0;
|
||||
if ((_base = user.items.food)[_name = drop.key] == null) {
|
||||
_base[_name] = 0;
|
||||
}
|
||||
user.items.food[drop.name] += 1;
|
||||
user.items.food[drop.key] += 1;
|
||||
drop.type = 'Food';
|
||||
drop.dialog = "You've found a " + drop.text + " Food! " + drop.notes;
|
||||
} else if (rarity > .3) {
|
||||
drop = user.fns.randomVal(content.eggs);
|
||||
if ((_base2 = user.items.eggs)[_name1 = drop.name] == null) {
|
||||
_base2[_name1] = 0;
|
||||
if ((_base1 = user.items.eggs)[_name1 = drop.key] == null) {
|
||||
_base1[_name1] = 0;
|
||||
}
|
||||
user.items.eggs[drop.name]++;
|
||||
user.items.eggs[drop.key]++;
|
||||
drop.type = 'Egg';
|
||||
drop.dialog = "You've found a " + drop.text + " Egg! " + drop.notes;
|
||||
} else {
|
||||
|
|
@ -11851,10 +11916,10 @@ var process=require("__browserify_process");(function() {
|
|||
drop = user.fns.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;
|
||||
if ((_base2 = user.items.hatchingPotions)[_name2 = drop.key] == null) {
|
||||
_base2[_name2] = 0;
|
||||
}
|
||||
user.items.hatchingPotions[drop.name]++;
|
||||
user.items.hatchingPotions[drop.key]++;
|
||||
drop.type = 'HatchingPotion';
|
||||
drop.dialog = "You've found a " + drop.text + " Hatching Potion! " + drop.notes;
|
||||
}
|
||||
|
|
@ -11952,11 +12017,11 @@ var process=require("__browserify_process");(function() {
|
|||
*/
|
||||
|
||||
cron: function(options) {
|
||||
var daysMissed, expTally, lvl, now, todoTally, _base, _base1;
|
||||
var daysMissed, expTally, lvl, now, progress, todoTally, _base, _base1, _base2, _progress;
|
||||
if (options == null) {
|
||||
options = {};
|
||||
}
|
||||
now = [+options.now || +(new Date)][0];
|
||||
now = +options.now || +(new Date);
|
||||
daysMissed = api.daysSince(user.lastCron, _.defaults({
|
||||
now: now
|
||||
}, user.preferences));
|
||||
|
|
@ -11984,8 +12049,11 @@ var process=require("__browserify_process");(function() {
|
|||
return;
|
||||
}
|
||||
todoTally = 0;
|
||||
if ((_base = user.party.quest.progress).down == null) {
|
||||
_base.down = 0;
|
||||
}
|
||||
user.todos.concat(user.dailys).forEach(function(task) {
|
||||
var absVal, completed, id, repeat, scheduleMisses, type;
|
||||
var absVal, completed, delta, id, repeat, scheduleMisses, type;
|
||||
if (!task) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -12006,7 +12074,7 @@ var process=require("__browserify_process");(function() {
|
|||
});
|
||||
}
|
||||
if (scheduleMisses > 0) {
|
||||
user.ops.score({
|
||||
delta = user.ops.score({
|
||||
params: {
|
||||
id: task.id,
|
||||
direction: 'down'
|
||||
|
|
@ -12016,6 +12084,9 @@ var process=require("__browserify_process");(function() {
|
|||
cron: true
|
||||
}
|
||||
});
|
||||
if (type === 'daily') {
|
||||
user.party.quest.progress.down += delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
switch (type) {
|
||||
|
|
@ -12039,7 +12110,7 @@ var process=require("__browserify_process");(function() {
|
|||
}
|
||||
}
|
||||
});
|
||||
((_base = (user.history != null ? user.history : user.history = {})).todos != null ? (_base = (user.history != null ? user.history : user.history = {})).todos : _base.todos = []).push({
|
||||
((_base1 = (user.history != null ? user.history : user.history = {})).todos != null ? (_base1 = (user.history != null ? user.history : user.history = {})).todos : _base1.todos = []).push({
|
||||
date: now,
|
||||
value: todoTally
|
||||
});
|
||||
|
|
@ -12049,7 +12120,7 @@ var process=require("__browserify_process");(function() {
|
|||
lvl++;
|
||||
expTally += api.tnl(lvl);
|
||||
}
|
||||
((_base1 = user.history).exp != null ? (_base1 = user.history).exp : _base1.exp = []).push({
|
||||
((_base2 = user.history).exp != null ? (_base2 = user.history).exp : _base2.exp = []).push({
|
||||
date: now,
|
||||
value: expTally
|
||||
});
|
||||
|
|
@ -12068,7 +12139,16 @@ var process=require("__browserify_process");(function() {
|
|||
stealth: 0,
|
||||
streaks: false
|
||||
};
|
||||
return user;
|
||||
progress = user.party.quest.progress;
|
||||
_progress = _.cloneDeep(progress);
|
||||
_.merge(progress, {
|
||||
down: 0,
|
||||
up: 0
|
||||
});
|
||||
progress.collect = _.transform(progress.collect, (function(m, v, k) {
|
||||
return m[k] = 0;
|
||||
}));
|
||||
return _progress;
|
||||
},
|
||||
preenUserHistory: function(minHistLen) {
|
||||
if (minHistLen == null) {
|
||||
|
|
|
|||
1154
dist/spritesmith.css
vendored
BIN
dist/spritesmith.png
vendored
|
Before Width: | Height: | Size: 308 KiB After Width: | Height: | Size: 248 KiB |
|
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
img/sprites/npc_ian.gif
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
img/sprites/spritesmith/items/inventory_quest_scroll.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB |
BIN
img/sprites/spritesmith/pets/Pet-BearCub-Polar.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
img/sprites/spritesmith/quests/quest_evilsanta.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
|
|
@ -6,8 +6,8 @@
|
|||
"coffeeify": "0.4.0",
|
||||
"coffee-script": "1.6.2",
|
||||
"moment": "~2.4.0",
|
||||
"grunt-spritesmith": "~1.16.0",
|
||||
"lodash": "~2.4.1"
|
||||
"lodash": "~2.4.1",
|
||||
"grunt-spritesmith": "~1.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "*",
|
||||
|
|
|
|||
|
|
@ -220,7 +220,9 @@ api.spells =
|
|||
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 * .0075 * user.fns.crit('per')
|
||||
user.stats.exp += (if target.value < 0 then 1 else target.value+1) * 2.5
|
||||
bonus = (if target.value < 0 then 1 else target.value+1) * 2.5
|
||||
user.stats.exp += bonus
|
||||
user.party.quest.tally.up += bonus if user.party.quest.key
|
||||
|
||||
mpheal:
|
||||
text: 'Ethereal Surge'
|
||||
|
|
@ -263,6 +265,7 @@ api.spells =
|
|||
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 * .01 * user.fns.crit('per')
|
||||
user.party.quest.tally.up += Math.ceil(user._statsComputed.str * .2) if user.party.quest.key
|
||||
defensiveStance:
|
||||
text: 'Defensive Stance'
|
||||
mana: 25
|
||||
|
|
@ -314,6 +317,7 @@ api.spells =
|
|||
bonus = (if target.value < 0 then 1 else target.value+1) * _crit
|
||||
user.stats.exp += bonus
|
||||
user.stats.gp += bonus
|
||||
# user.party.quest.tally.up += bonus if user.party.quest.key # remove hurting bosses for rogues, seems OP for now
|
||||
toolsOfTrade:
|
||||
text: 'Tools of the Trade'
|
||||
mana: 25
|
||||
|
|
@ -402,8 +406,8 @@ api.spells =
|
|||
|
||||
# Intercept all spells to reduce user.stats.mp after casting the spell
|
||||
_.each api.spells, (spellClass) ->
|
||||
_.each spellClass, (spell, k) ->
|
||||
spell.name = k
|
||||
_.each spellClass, (spell, key) ->
|
||||
spell.key = key
|
||||
_cast = spell.cast
|
||||
spell.cast = (user, target) ->
|
||||
#return if spell.target and spell.target != (if target.type then 'task' else 'user')
|
||||
|
|
@ -429,11 +433,10 @@ api.eggs =
|
|||
Dragon: text: 'Dragon', adjective: 'mighty'
|
||||
Cactus: text: 'Cactus', adjective: 'prickly'
|
||||
BearCub: text: 'Bear Cub', mountText: 'Bear', adjective: 'cuddly'
|
||||
#{text: 'Polar Bear Cub', name: 'PolarBearCub', value: 3}
|
||||
_.each api.eggs, (egg,k) ->
|
||||
_.each api.eggs, (egg,key) ->
|
||||
_.defaults egg,
|
||||
value: 3
|
||||
name: k
|
||||
key: key
|
||||
notes: "Find a hatching potion to pour on this egg, and it will hatch into a #{egg.adjective} #{egg.text}."
|
||||
mountText: egg.text
|
||||
|
||||
|
|
@ -442,6 +445,7 @@ api.specialPets =
|
|||
'Wolf-Cerberus': true
|
||||
'Dragon-Hydra': true
|
||||
'Turkey-Base': true
|
||||
'BearCub-Polar': true
|
||||
|
||||
api.hatchingPotions =
|
||||
Base: value: 2, text: 'Base'
|
||||
|
|
@ -454,8 +458,8 @@ api.hatchingPotions =
|
|||
CottonCandyPink: value: 4, text: 'Cotton Candy Pink'
|
||||
CottonCandyBlue: value: 4, text: 'Cotton Candy Blue'
|
||||
Golden: value: 5, text: 'Golden'
|
||||
_.each api.hatchingPotions, (pot,k) ->
|
||||
_.defaults pot, {name: k, value: 2, notes: "Pour this on an egg, and it will hatch as a #{pot.text} pet."}
|
||||
_.each api.hatchingPotions, (pot,key) ->
|
||||
_.defaults pot, {key, value: 2, notes: "Pour this on an egg, and it will hatch as a #{pot.text} pet."}
|
||||
|
||||
api.food =
|
||||
Meat: text: 'Meat', target: 'Base'
|
||||
|
|
@ -473,8 +477,42 @@ api.food =
|
|||
#Watermelon: text: 'Watermelon', target: 'Golden'
|
||||
#SeaWeed: text: 'SeaWeed', target: 'Golden'
|
||||
Saddle: text: 'Saddle', value: 5, notes: 'Instantly raises your pet into a mount.'
|
||||
_.each api.food, (food,k) ->
|
||||
_.defaults food, {value: 1, name: k, notes: "Feed this to a pet and it may grow into a sturdy steed."}
|
||||
_.each api.food, (food,key) ->
|
||||
_.defaults food, {value: 1, key, notes: "Feed this to a pet and it may grow into a sturdy steed."}
|
||||
|
||||
api.quests =
|
||||
|
||||
evilsanta:
|
||||
text: "Trapper Santa" # title of the quest (eg, Deep into Vice's Layer)
|
||||
notes: "You hear bemoaned roars deep in the icefields. You follow the roars and growls - punctuated by another voice's cackling - to a clearing in the woods where you see a fully-grown polar bear. She's caged and shackled, roaring for life. Dancing atop the the cage is a malicious little imp wearing castaway Christmas costumes. Vanquish Trapper Santa, and save the beast!"
|
||||
completion: "Trapper Santa squeels in anger, and bounces off into the night. A grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the whisperer listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured. Help her find her baby!"
|
||||
value: 4 # Gem cost to buy, GP sell-back
|
||||
#mechanic: enum['perfectDailies', ...]
|
||||
boss:
|
||||
name: "Trapper Santa" # name of the boss himself (eg, Vice)
|
||||
hp: 300
|
||||
str: 1 # Multiplier of users' missed dailies
|
||||
drop:
|
||||
type: 'mounts'
|
||||
key: 'BearCub-Polar'
|
||||
text: "Polar Bear (Mount)"
|
||||
gp: 20
|
||||
exp: 100 # Exp bonus from defeating the boss
|
||||
|
||||
evilsanta2:
|
||||
text: "Find The Cub"
|
||||
notes: "Mama bear's cub had run off into the icefields when she was captured by the trapper. At the edge of the woods, she sniffs the air. You hear twig-snaps and snow crunch through the crystaline sound of the forest. Paw prints! You both start racing to follow the trail. Find all the prints and broken twigs, and retrieve her cub!"
|
||||
completion: "You've found the cub! Mama and baby bear couldn't be more grateful. As a token, they've decided to keep you company till the end of days."
|
||||
value: 4
|
||||
previous: 'evilsanta'
|
||||
collect:
|
||||
tracks: text: 'Tracks', count: 20
|
||||
branches: text: 'Broken Twigs', count: 10
|
||||
drop: type: 'pets', key: 'BearCub-Polar', text: "Polar Bear (Pet)", gp: 20, exp: 100
|
||||
|
||||
|
||||
_.each api.quests, (v,key) ->
|
||||
_.defaults v, {key}
|
||||
|
||||
repeat = {m:true,t:true,w:true,th:true,f:true,s:true,su:true}
|
||||
api.userDefaults =
|
||||
|
|
|
|||
|
|
@ -74,6 +74,8 @@ api.tnl = (lvl) ->
|
|||
api.diminishingReturns = (bonus, max, halfway=bonus/2) ->
|
||||
max*(bonus/(bonus+halfway))
|
||||
|
||||
api.monod = (bonus, rateOfIncrease, max) ->
|
||||
rateOfIncrease*max*bonus/(rateOfIncrease*bonus+max)
|
||||
|
||||
###
|
||||
Preen history for users with > 7 history entries
|
||||
|
|
@ -481,9 +483,9 @@ api.wrap = (user) ->
|
|||
userPets = user.items.pets
|
||||
|
||||
return cb({code:404, message:":pet not found in user.items.pets"}) unless userPets[pet]
|
||||
return cb({code:404, message:":food not found in user.items.food"}) unless user.items.food?[food.name]
|
||||
return cb({code:404, message:":food not found in user.items.food"}) unless user.items.food?[food.key]
|
||||
return cb({code:401, message:"Can't feed this pet."}) if content.specialPets[pet]
|
||||
return cb({code:401, message:"You already have that mount"}) if user.items.mounts[pet] and (userPets[pet] >= 50 or food.name is 'Saddle')
|
||||
return cb({code:401, message:"You already have that mount"}) if user.items.mounts[pet] and (userPets[pet] >= 50 or food.key is 'Saddle')
|
||||
|
||||
message = ''
|
||||
evolve = ->
|
||||
|
|
@ -492,24 +494,24 @@ api.wrap = (user) ->
|
|||
user.items.currentPet = "" if pet is user.items.currentPet
|
||||
message = "You have tamed #{egg}, let's go for a ride!"
|
||||
|
||||
if food.name is 'Saddle'
|
||||
if food.key is 'Saddle'
|
||||
evolve()
|
||||
else
|
||||
if food.target is potion
|
||||
userPets[pet] += 5
|
||||
message = "#{egg} really likes the #{food.name}!"
|
||||
message = "#{egg} really likes the #{food.key}!"
|
||||
else
|
||||
userPets[pet] += 2
|
||||
message = "#{egg} eats the #{food.name} but doesn't seem to enjoy it."
|
||||
message = "#{egg} eats the #{food.key} but doesn't seem to enjoy it."
|
||||
if userPets[pet] >= 50 and !user.items.mounts[pet]
|
||||
evolve()
|
||||
user.items.food[food.name]--
|
||||
user.items.food[food.key]--
|
||||
cb {code:200, message}, req
|
||||
|
||||
# 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,special]"},req) unless type in ['eggs','hatchingPotions', 'food', 'special']
|
||||
return cb({code:404,message:":type must be in [hatchingPotions,eggs,food,quests,special]"},req) unless type in ['eggs','hatchingPotions','food','quests','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]
|
||||
|
|
@ -667,7 +669,7 @@ api.wrap = (user) ->
|
|||
|
||||
delta = 0
|
||||
|
||||
calculateDelta = (adjustvalue=true) ->
|
||||
calculateDelta = ->
|
||||
# If multiple days have passed, multiply times days missed
|
||||
_.times options.times, ->
|
||||
# Each iteration calculate the nextDelta, which is then accumulated in the total delta.
|
||||
|
|
@ -680,13 +682,17 @@ api.wrap = (user) ->
|
|||
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
|
||||
unless task.type is 'reward'
|
||||
adjustAmt = nextDelta
|
||||
# ===== STRENGTH =====
|
||||
# (Only for up-scoring, ignore up-onlies and rewards)
|
||||
# Note, we create a new val (adjustAmt) to add to task.value, since delta will be used in Exp & GP calculations - we don't want STR to bonus that
|
||||
# TODO STR Improves the amount by which Dailies and +/- Habits decrease in threat when scored, by .25% per point.
|
||||
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 * .004
|
||||
adjustAmt = nextDelta * (1 + user._statsComputed.str * .004)
|
||||
user.party.quest.progress.up = user.party.quest.progress.up || 0;
|
||||
user.party.quest.progress.up += adjustAmt if task.type in ['daily','todo']
|
||||
task.value += adjustAmt
|
||||
delta += nextDelta
|
||||
|
||||
addPoints = ->
|
||||
|
|
@ -765,7 +771,7 @@ api.wrap = (user) ->
|
|||
|
||||
when 'reward'
|
||||
# Don't adjust values for rewards
|
||||
calculateDelta(false)
|
||||
calculateDelta()
|
||||
# purchase item
|
||||
stats.gp -= Math.abs(task.value)
|
||||
num = parseFloat(task.value).toFixed(2)
|
||||
|
|
@ -856,32 +862,28 @@ api.wrap = (user) ->
|
|||
# ----------------------------------------------------------------------
|
||||
|
||||
randomDrop: (modifiers) ->
|
||||
{delta} = modifiers
|
||||
{priority, streak} = modifiers.task
|
||||
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
|
||||
{task} = modifiers
|
||||
|
||||
reachedDropLimit = (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 5)
|
||||
return if reachedDropLimit
|
||||
# % chance of getting a drop
|
||||
bonus =
|
||||
Math.abs(task.value) * # + Task Redness
|
||||
task.priority + # * Task Priority
|
||||
(task.streak or 0) + # + Streak bonus
|
||||
(user._statsComputed.per * .5) # + Perception
|
||||
bonus /= 100 # /100 (as a percent)
|
||||
chance = api.diminishingReturns(bonus, 1, 0.5) # see HabitRPG/habitrpg#1922 for details
|
||||
console.log "Drop Equation: Bonus(#{bonus.toFixed(3)}), Modified Chance(#{chance.toFixed(3)})\n"
|
||||
|
||||
# % chance of getting a pet or meat
|
||||
chanceMultiplier = Math.abs(delta)
|
||||
chanceMultiplier *= priority # multiply chance by reddness
|
||||
chanceMultiplier += streak # streak bonus
|
||||
chanceMultiplier += user._statsComputed.per*.3
|
||||
quest = content.quests[user.party.quest?.key]
|
||||
if quest?.collect and user.fns.predictableRandom(user.stats.gp) < bonus # NOTE: < bonus, higher chance than drops
|
||||
dropK = user.fns.randomVal quest.collect, {key:true}
|
||||
user.party.quest.progress.collect[dropK]++
|
||||
user.markModified? 'party.quest.progress'
|
||||
console.log {progress:user.party.quest.progress}
|
||||
|
||||
# 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
|
||||
return if (api.daysSince(user.items.lastDrop.date, user.preferences) is 0) and (user.items.lastDrop.count >= 5)
|
||||
if user.flags?.dropsEnabled and user.fns.predictableRandom(user.stats.exp) < chance
|
||||
|
||||
if user.flags?.dropsEnabled and user.fns.predictableRandom(user.stats.exp) < 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 = user.fns.predictableRandom(user.stats.gp)
|
||||
|
|
@ -889,16 +891,16 @@ api.wrap = (user) ->
|
|||
# Food: 40% chance
|
||||
if rarity > .6
|
||||
drop = user.fns.randomVal _.omit(content.food, 'Saddle')
|
||||
user.items.food[drop.name] ?= 0
|
||||
user.items.food[drop.name]+= 1
|
||||
user.items.food[drop.key] ?= 0
|
||||
user.items.food[drop.key]+= 1
|
||||
drop.type = 'Food'
|
||||
drop.dialog = "You've found a #{drop.text} Food! #{drop.notes}"
|
||||
|
||||
# Eggs: 30% chance
|
||||
else if rarity > .3
|
||||
drop = user.fns.randomVal content.eggs
|
||||
user.items.eggs[drop.name] ?= 0
|
||||
user.items.eggs[drop.name]++
|
||||
user.items.eggs[drop.key] ?= 0
|
||||
user.items.eggs[drop.key]++
|
||||
drop.type = 'Egg'
|
||||
drop.dialog = "You've found a #{drop.text} Egg! #{drop.notes}"
|
||||
|
||||
|
|
@ -918,8 +920,8 @@ api.wrap = (user) ->
|
|||
#drop = helpers.randomVal hatchingPotions
|
||||
drop = user.fns.randomVal _.pick(content.hatchingPotions, ((v,k) -> k in acceptableDrops))
|
||||
|
||||
user.items.hatchingPotions[drop.name] ?= 0
|
||||
user.items.hatchingPotions[drop.name]++
|
||||
user.items.hatchingPotions[drop.key] ?= 0
|
||||
user.items.hatchingPotions[drop.key]++
|
||||
drop.type = 'HatchingPotion'
|
||||
drop.dialog = "You've found a #{drop.text} Hatching Potion! #{drop.notes}"
|
||||
|
||||
|
|
@ -1000,7 +1002,7 @@ api.wrap = (user) ->
|
|||
{user}
|
||||
###
|
||||
cron: (options={}) ->
|
||||
[now] = [+options.now || +new Date]
|
||||
now = +options.now || +new Date
|
||||
|
||||
# They went to a different timezone
|
||||
# FIXME:
|
||||
|
|
@ -1031,8 +1033,9 @@ api.wrap = (user) ->
|
|||
user.stats.buffs = {str:0,int:0,per:0,con:0,stealth:0,streaks:false}
|
||||
return
|
||||
|
||||
# Tally each task
|
||||
# Tally each task
|
||||
todoTally = 0
|
||||
user.party.quest.progress.down ?= 0
|
||||
user.todos.concat(user.dailys).forEach (task) ->
|
||||
return unless task
|
||||
|
||||
|
|
@ -1049,7 +1052,8 @@ api.wrap = (user) ->
|
|||
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}})
|
||||
delta = user.ops.score({params:{id:task.id, direction:'down'}, query:{times:scheduleMisses, cron:true}});
|
||||
user.party.quest.progress.down += delta if type is 'daily'
|
||||
|
||||
switch type
|
||||
when 'daily'
|
||||
|
|
@ -1081,7 +1085,12 @@ api.wrap = (user) ->
|
|||
user.markModified? 'history'
|
||||
user.markModified? 'dailys' # covers dailys.*.history
|
||||
user.stats.buffs = {str:0,int:0,per:0,con:0,stealth:0,streaks:false}
|
||||
user
|
||||
|
||||
# After all is said and done, progress up user's effect on quest, return those values & reset the user's
|
||||
progress = user.party.quest.progress; _progress = _.cloneDeep progress
|
||||
_.merge progress, {down:0,up:0}
|
||||
progress.collect = _.transform progress.collect, ((m,v,k)->m[k]=0)
|
||||
_progress
|
||||
|
||||
# Registered users with some history
|
||||
preenUserHistory: (minHistLen = 7) ->
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ _ = require 'lodash'
|
|||
id = shared.uuid()
|
||||
user =
|
||||
stats: {class: 'warrior', buffs: {per:0,int:0,con:0,str:0}}
|
||||
party: quest: tally: {up:0,down:0}
|
||||
items:
|
||||
eggs: {}
|
||||
hatchingPotions: {}
|
||||
|
|
@ -41,18 +42,21 @@ 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}
|
||||
_.merge user.party.quest.tally, {up:0,down:0}
|
||||
user.items.lastDrop = {count: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"
|
||||
console.log "direction\texpΔ\t\thpΔ\tgpΔ\ttask.valΔ\ttask.valΔ bonus\t\tboss-hit"
|
||||
_.each ['up','down'], (direction) ->
|
||||
clearUser(lvl)
|
||||
b4 = {hp:s.hp, taskVal}
|
||||
task.value = taskVal
|
||||
task.type = 'daily' if direction is 'up'
|
||||
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)}"
|
||||
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)}\t\t\t#{user.party.quest.tally.up.toFixed(1)}"
|
||||
|
||||
str = '- [Wizard]'
|
||||
|
||||
|
|
|
|||