mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-08-05 20:12:19 +00:00
commit
b3a031bee5
33 changed files with 1015 additions and 1019 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -5,3 +5,4 @@ node_modules
|
|||
*.swp
|
||||
.idea*
|
||||
config.json
|
||||
npm-debug.log
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -19,3 +19,6 @@
|
|||
[submodule "public/vendor/BrowserQuest"]
|
||||
path = public/vendor/BrowserQuest
|
||||
url = https://github.com/mozilla/BrowserQuest.git
|
||||
[submodule "public/vendor/bootstrap-tour"]
|
||||
path = public/vendor/bootstrap-tour
|
||||
url = git://github.com/sorich87/bootstrap-tour.git
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@
|
|||
##Contact
|
||||
###[Bugs](https://github.com/lefnire/habitrpg/issues)
|
||||
###[New Features](https://trello.com/board/habitrpg/50e5d3684fe3a7266b0036d6)
|
||||
###[Email](mailto:tylerrenelle@gmail.com)
|
||||
|
||||
##License
|
||||
Code is licensed under GNU GPL v3. Content is licensed under CC-BY-SA 3.0.
|
||||
|
|
|
|||
39
migrations/20130307_exp_overflow.js
Normal file
39
migrations/20130307_exp_overflow.js
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_normalize_algo_values.js
|
||||
|
||||
/**
|
||||
* Make sure people aren't overflowing their exp with the new system
|
||||
*/
|
||||
db.users.find().forEach(function(user){
|
||||
function oldTnl(level) {
|
||||
return (Math.pow(level,2)*10)+(level*10)+80
|
||||
}
|
||||
|
||||
function newTnl(level) {
|
||||
var value = 0;
|
||||
if (level >= 100) {
|
||||
value = 0
|
||||
} else {
|
||||
value = Math.round(((Math.pow(level,2)*0.25)+(10 * level) + 139.75)/10)*10; // round to nearest 10
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
var newTnl = newTnl(user.stats.lvl);
|
||||
if (user.stats.exp > newTnl) {
|
||||
var percent = user.stats.exp / oldTnl(user.stats.lvl);
|
||||
percent = (percent>1) ? 1 : percent;
|
||||
user.stats.exp = newTnl * percent;
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set: {'stats.exp': user.stats.exp}},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
47
migrations/20130307_normalize_algo_values.js
Normal file
47
migrations/20130307_normalize_algo_values.js
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_normalize_algo_values.js
|
||||
|
||||
/**
|
||||
* Users were experiencing a lot of extreme Exp multiplication (https://github.com/lefnire/habitrpg/issues/594).
|
||||
* This sets things straight, and in preparation for another algorithm overhaul
|
||||
*/
|
||||
db.users.find().forEach(function(user){
|
||||
if (user.stats.exp >= 3580) {
|
||||
user.stats.exp = 0;
|
||||
}
|
||||
|
||||
if (user.stats.lvl > 100) {
|
||||
user.stats.lvl = 100;
|
||||
}
|
||||
|
||||
_.each(user.tasks, function(task, key){
|
||||
// remove corrupt tasks
|
||||
if (!task) {
|
||||
delete user.tasks[key];
|
||||
return;
|
||||
}
|
||||
|
||||
// Fix busted values
|
||||
if (task.value > 21.27) {
|
||||
task.value = 21.27;
|
||||
}
|
||||
else if (task.value < -47.27) {
|
||||
task.value = -47.27;
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set:
|
||||
{
|
||||
'stats.lvl': user.stats.lvl,
|
||||
'stats.exp': user.stats.exp,
|
||||
'tasks' : user.tasks
|
||||
}
|
||||
},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
28
migrations/20130307_remove_duff_histories.js
Normal file
28
migrations/20130307_remove_duff_histories.js
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* Remove duff histories for dailies
|
||||
*/
|
||||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/20130307_remove_duff_histories.js
|
||||
db.users.find().forEach(function(user){
|
||||
|
||||
|
||||
_.each(user.tasks, function(task, key){
|
||||
if (task.type === "daily") {
|
||||
// remove busted history entries
|
||||
task.history = _.filter(task.history, function(h){return !!h.value})
|
||||
}
|
||||
});
|
||||
|
||||
try {
|
||||
db.users.update(
|
||||
{_id:user._id},
|
||||
{$set:
|
||||
{
|
||||
'tasks' : user.tasks
|
||||
}
|
||||
},
|
||||
{multi:true}
|
||||
);
|
||||
} catch(e) {
|
||||
print(e);
|
||||
}
|
||||
})
|
||||
11
migrations/find_unique_user.js
Normal file
11
migrations/find_unique_user.js
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
// mongo habitrpg ./node_modules/underscore/underscore.js ./migrations/find_unique_user.js
|
||||
|
||||
/**
|
||||
* There are some rare instances of lost user accounts, due to a corrupt user auth variable (see https://github.com/lefnire/habitrpg/wiki/User-ID)
|
||||
* Past in the text of a unique habit here to find the user, then you can restore their UUID
|
||||
*/
|
||||
|
||||
db.users.find().forEach(function(user){
|
||||
var found = _.findWhere(user.tasks, {text: "Replace Me"})
|
||||
if (found) printjson({id:user._id, auth:user.auth});
|
||||
})
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"version": "0.0.0-150",
|
||||
"main": "./server.js",
|
||||
"dependencies": {
|
||||
"derby": "git://github.com/lefnire/derby#habitrpg",
|
||||
"derby": "git://github.com/Unroll-Me/derby#master",
|
||||
"racer": "git://github.com/lefnire/racer#habitrpg",
|
||||
"racer-db-mongo": "git://github.com/lefnire/racer-db-mongo#habitrpg",
|
||||
"derby-ui-boot": "git://github.com/codeparty/derby-ui-boot#master",
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@
|
|||
<div class='jumbotron masthead'>
|
||||
<div class='container'>
|
||||
<h1><img src="/img/logo/habitrpg_pixel.png" alt="HabitRPG"/></h1>
|
||||
<p>Habit tracking which treats your goals like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.</p>
|
||||
<p>A habit building program which treats your life like a Role Playing Game. Level up as you succeed, lose HP as you fail, earn money to buy weapons and armor.</p>
|
||||
<a href="/?play=1" class='btn btn-primary btn-small'>Play</a>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -82,29 +82,29 @@
|
|||
<div class="page-header">
|
||||
<h1>Habits</h1>
|
||||
</div>
|
||||
<p class="lead">Habits are goals that you constantly track. For some habits, it only makes sense to gain points (eg, "1h Productive Work"). For others, it only makes sense to lose points (like "Eat Junk Food"). For the rest, both gain and loss apply (eg, for "Take The Stairs", stairs is a gain, elevator is a loss).</p>
|
||||
<p class="lead">Habits are situational goals that you constantly track. You can either try to break bad habits or reinforce good ones. You may encounter some habits multiple times a day (like "Floss After Eating"), and some you may not encounter often at all (like "Replace the Toilet Paper When it Runs Out"). For some habits, it only makes sense to gain points (like "Do One Hour of Productive Work"). For others, it only makes sense to lose points (like "Eat Junk Food"). For the rest, both gain and loss apply (like for "Take The Stairs", taking the stairs would be a gain while taking the elevator would be a loss).</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<h1>Dailies</h1>
|
||||
</div>
|
||||
<p class="lead">Dailies are goals that you want to complete once a day. At the end of each day, non-completed Dailies dock you points. If you are doing well, they turn green and are less valuable (experience, gold) and less damaging (HP). This means you can ease up on them for a bit. But if you are doing poorly, they turn red. The worse you do, the more valuable (exp, gold) and more damaging (HP) these goals become. This encourages you to focus on your shortcomings, the reds.</p>
|
||||
<p class="lead">Dailies are goals that you want to complete once a day, building them into your routine (like "Workout for 30 Minutes"). Unlike Habits, Dailies may only be checked off once a day. Also unlike habits, at the end of each day non-completed Dailies will cause damage, making you lose Hit Points. If you are doing well and consistantly check off a Daily day after day, it will turn green and earn less gold and experience, though it will also cause you to lose fewer Hit Points if you skip it. This means you can ease up on it for a bit. Conversely, if you are doing poorly and fail to check something off every day, it will turn red. The worse you do, the more experience and gold that Daily is worth and the more hit points it takes away if left uncompleted. This encourages you to focus on your shortcomings, the reds. Oh, and don't worry. You can make a Daily active only for certain days of the week, so weekday tasks won't pester you on the weekends. Or you can make a Daily active only on Mondays, thus creating a weekly task.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<h1>Todos</h1>
|
||||
</div>
|
||||
<p class="lead">Todos are one-off goals which need to be completed eventually. Non-completed Todos won’t hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.</p>
|
||||
<p class="lead">Todos are one-time goals which need to be completed eventually (like "Wash the car" or "Buy milk"). Non-completed Todos won’t hurt you, but they will become more valuable over time. This will encourage you to wrap up stale Todos.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<div class="page-header">
|
||||
<h1>Rewards</h1>
|
||||
</div>
|
||||
<p class='lead'>As you complete goals, you earn gold to buy rewards. Buy them liberally - rewards are integral in forming good habits. But only buy if you have enough gold - you lose HP otherwise.</p>
|
||||
<p class='lead'>After you’ve played for a while, you unlock the <strong>Item Store</strong> under the rewards column. You can now buy weapons, armor, potions, etc. Armor decreases HP loss (by an increasing amount wich each upgrade). Weapons increase experience gain. Potions recover 15 HP.</p>
|
||||
<p class='lead'>As you complete goals, you earn gold to buy rewards that you create (like "An Hour of Video Games"). Create plenty of rewards and buy them liberally! Rewarding good performance is the best way to reinforce good habits. If you really need a reward but don't have enough gold, you can still make a purchase. But be careful because it will cost you Hit Points!</p>
|
||||
<p class='lead'>After you’ve reached level 2, you unlock the <strong>Item Store</strong> under the rewards column. You can now buy weapons, armor, and potions. Armor keeps you protected by reducing how much damage you take from failed tasks. Weapons increase how much experience you gain for completing a task. Potions recover 15 HP, for when you've piled just a little too much onto your schedule.</p>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
|
|
|
|||
2
public/vendor/bootstrap
vendored
2
public/vendor/bootstrap
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 8c7f9c66a7d12f47f50618ef420868fe836d0c33
|
||||
Subproject commit eb24718add4dd36fe92fdbdb79e6ff4ce5919300
|
||||
1
public/vendor/bootstrap-tour
vendored
Submodule
1
public/vendor/bootstrap-tour
vendored
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit 4f94fa056c88c6099dea135b644d8f95d38ac9e1
|
||||
271
public/vendor/bootstrap-tour.js
vendored
271
public/vendor/bootstrap-tour.js
vendored
|
|
@ -1,271 +0,0 @@
|
|||
|
||||
/* ============================================================
|
||||
# bootstrap-tour.js v0.1
|
||||
# http://pushly.github.com/bootstrap-tour/
|
||||
# ==============================================================
|
||||
# Copyright 2012 Push.ly
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
|
||||
(function($, window) {
|
||||
var Tour, document;
|
||||
document = window.document;
|
||||
Tour = (function() {
|
||||
|
||||
function Tour(options) {
|
||||
var _this = this;
|
||||
this._options = $.extend({
|
||||
name: 'tour',
|
||||
labels: {
|
||||
end: 'End tour',
|
||||
next: 'Next »',
|
||||
prev: '« Prev'
|
||||
},
|
||||
keyboard: true,
|
||||
afterSetState: function(key, value) {},
|
||||
afterGetState: function(key, value) {},
|
||||
onShow: function(tour) {},
|
||||
onHide: function(tour) {},
|
||||
onShown: function(tour) {}
|
||||
}, options);
|
||||
this._steps = [];
|
||||
this.setCurrentStep();
|
||||
this._onresize(function() {
|
||||
if (!_this.ended) return _this.showStep(_this._current);
|
||||
});
|
||||
}
|
||||
|
||||
Tour.prototype.setState = function(key, value) {
|
||||
$.cookie("" + this._options.name + "_" + key, value, {
|
||||
expires: 36500,
|
||||
path: '/'
|
||||
});
|
||||
return this._options.afterSetState(key, value);
|
||||
};
|
||||
|
||||
Tour.prototype.getState = function(key) {
|
||||
var value;
|
||||
value = $.cookie("" + this._options.name + "_" + key);
|
||||
this._options.afterGetState(key, value);
|
||||
return value;
|
||||
};
|
||||
|
||||
Tour.prototype.addStep = function(step) {
|
||||
return this._steps.push(step);
|
||||
};
|
||||
|
||||
Tour.prototype.getStep = function(i) {
|
||||
if (this._steps[i] != null) {
|
||||
return $.extend({
|
||||
path: "",
|
||||
placement: "right",
|
||||
title: "",
|
||||
content: "",
|
||||
next: i === this._steps.length - 1 ? -1 : i + 1,
|
||||
prev: i - 1,
|
||||
animation: true,
|
||||
onShow: this._options.onShow,
|
||||
onHide: this._options.onHide,
|
||||
onShown: this._options.onShown
|
||||
}, this._steps[i]);
|
||||
}
|
||||
};
|
||||
|
||||
Tour.prototype.start = function(force) {
|
||||
var _this = this;
|
||||
if (force == null) force = false;
|
||||
if (this.ended() && !force) return;
|
||||
$(document).off("click.bootstrap-tour", ".popover .next").on("click.bootstrap-tour", ".popover .next", function(e) {
|
||||
e.preventDefault();
|
||||
return _this.next();
|
||||
});
|
||||
$(document).off("click.bootstrap-tour", ".popover .prev").on("click.bootstrap-tour", ".popover .prev", function(e) {
|
||||
e.preventDefault();
|
||||
return _this.prev();
|
||||
});
|
||||
$(document).off("click.bootstrap-tour", ".popover .end").on("click.bootstrap-tour", ".popover .end", function(e) {
|
||||
e.preventDefault();
|
||||
return _this.end();
|
||||
});
|
||||
this._setupKeyboardNavigation();
|
||||
return this.showStep(this._current);
|
||||
};
|
||||
|
||||
Tour.prototype.next = function() {
|
||||
this.hideStep(this._current);
|
||||
return this.showNextStep();
|
||||
};
|
||||
|
||||
Tour.prototype.prev = function() {
|
||||
this.hideStep(this._current);
|
||||
return this.showPrevStep();
|
||||
};
|
||||
|
||||
Tour.prototype.end = function() {
|
||||
this.hideStep(this._current);
|
||||
$(document).off(".bootstrap-tour");
|
||||
return this.setState("end", "yes");
|
||||
};
|
||||
|
||||
Tour.prototype.ended = function() {
|
||||
return !!this.getState("end");
|
||||
};
|
||||
|
||||
Tour.prototype.restart = function() {
|
||||
this.setState("current_step", null);
|
||||
this.setState("end", null);
|
||||
this.setCurrentStep(0);
|
||||
return this.start();
|
||||
};
|
||||
|
||||
Tour.prototype.hideStep = function(i) {
|
||||
var step;
|
||||
step = this.getStep(i);
|
||||
if (step.onHide != null) step.onHide(this);
|
||||
return $(step.element).popover("hide");
|
||||
};
|
||||
|
||||
Tour.prototype.showStep = function(i) {
|
||||
var step;
|
||||
step = this.getStep(i);
|
||||
if (!step) return;
|
||||
this.setCurrentStep(i);
|
||||
if (step.path !== "" && document.location.pathname !== step.path && document.location.pathname.replace(/^.*[\\\/]/, '') !== step.path) {
|
||||
document.location.href = step.path;
|
||||
return;
|
||||
}
|
||||
if (step.onShow != null) step.onShow(this);
|
||||
if (!((step.element != null) && $(step.element).length !== 0 && $(step.element).is(":visible"))) {
|
||||
this.showNextStep();
|
||||
return;
|
||||
}
|
||||
this._showPopover(step, i);
|
||||
if (step.onShown != null) return step.onShown(this);
|
||||
};
|
||||
|
||||
Tour.prototype.setCurrentStep = function(value) {
|
||||
if (value != null) {
|
||||
this._current = value;
|
||||
return this.setState("current_step", value);
|
||||
} else {
|
||||
this._current = this.getState("current_step");
|
||||
if (this._current === null || this._current === "null") {
|
||||
return this._current = 0;
|
||||
} else {
|
||||
return this._current = parseInt(this._current);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Tour.prototype.showNextStep = function() {
|
||||
var step;
|
||||
step = this.getStep(this._current);
|
||||
return this.showStep(step.next);
|
||||
};
|
||||
|
||||
Tour.prototype.showPrevStep = function() {
|
||||
var step;
|
||||
step = this.getStep(this._current);
|
||||
return this.showStep(step.prev);
|
||||
};
|
||||
|
||||
Tour.prototype._showPopover = function(step, i) {
|
||||
var content, nav, options, tip,
|
||||
_this = this;
|
||||
content = "" + step.content + "<br /><p>";
|
||||
options = $.extend({}, this._options);
|
||||
if (step.options) $.extend(options, step.options);
|
||||
if (step.reflex) {
|
||||
$(step.element).css("cursor", "pointer");
|
||||
$(step.element).on("click", function(e) {
|
||||
$(step.element).css("cursor", "auto");
|
||||
return _this.next();
|
||||
});
|
||||
}
|
||||
nav = [];
|
||||
if (step.prev >= 0) {
|
||||
nav.push("<a href='#" + step.prev + "' class='prev'>" + options.labels.prev + "</a>");
|
||||
}
|
||||
if (step.next >= 0) {
|
||||
nav.push("<a href='#" + step.next + "' class='next'>" + options.labels.next + "</a>");
|
||||
}
|
||||
content += nav.join(" | ");
|
||||
content += "<a href='#' class='pull-right end'>" + options.labels.end + "</a>";
|
||||
$(step.element).popover({
|
||||
placement: step.placement,
|
||||
trigger: "manual",
|
||||
title: step.title,
|
||||
content: content,
|
||||
html: true,
|
||||
animation: step.animation
|
||||
}).popover("show");
|
||||
tip = $(step.element).data("popover").tip();
|
||||
this._reposition(tip);
|
||||
return this._scrollIntoView(tip);
|
||||
};
|
||||
|
||||
Tour.prototype._reposition = function(tip) {
|
||||
var offsetBottom, offsetRight, tipOffset;
|
||||
tipOffset = tip.offset();
|
||||
offsetBottom = $(document).outerHeight() - tipOffset.top - $(tip).outerHeight();
|
||||
if (offsetBottom < 0) tipOffset.top = tipOffset.top + offsetBottom;
|
||||
offsetRight = $(document).outerWidth() - tipOffset.left - $(tip).outerWidth();
|
||||
if (offsetRight < 0) tipOffset.left = tipOffset.left + offsetRight;
|
||||
if (tipOffset.top < 0) tipOffset.top = 0;
|
||||
if (tipOffset.left < 0) tipOffset.left = 0;
|
||||
return tip.offset(tipOffset);
|
||||
};
|
||||
|
||||
Tour.prototype._scrollIntoView = function(tip) {
|
||||
var tipRect;
|
||||
tipRect = tip.get(0).getBoundingClientRect();
|
||||
if (!(tipRect.top > 0 && tipRect.bottom < $(window).height() && tipRect.left > 0 && tipRect.right < $(window).width())) {
|
||||
return tip.get(0).scrollIntoView(true);
|
||||
}
|
||||
};
|
||||
|
||||
Tour.prototype._onresize = function(cb, timeout) {
|
||||
return $(window).resize(function() {
|
||||
clearTimeout(timeout);
|
||||
return timeout = setTimeout(cb, 100);
|
||||
});
|
||||
};
|
||||
|
||||
Tour.prototype._setupKeyboardNavigation = function() {
|
||||
var _this = this;
|
||||
if (this._options.keyboard) {
|
||||
return $(document).on("keyup.bootstrap-tour", function(e) {
|
||||
if (!e.which) return;
|
||||
switch (e.which) {
|
||||
case 39:
|
||||
e.preventDefault();
|
||||
if (_this._current < _this._steps.length - 1) return _this.next();
|
||||
break;
|
||||
case 37:
|
||||
e.preventDefault();
|
||||
if (_this._current > 0) return _this.prev();
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
return Tour;
|
||||
|
||||
})();
|
||||
return window.Tour = Tour;
|
||||
})(jQuery, window);
|
||||
|
||||
}).call(this);
|
||||
|
|
@ -22,7 +22,7 @@ process.env.SMTP_SERVICE = conf.get("SMTP_SERVICE");
|
|||
process.env.STRIPE_API_KEY = conf.get("STRIPE_API_KEY");
|
||||
process.env.STRIPE_PUB_KEY = conf.get("STRIPE_PUB_KEY");
|
||||
|
||||
var agent;
|
||||
/*var agent;
|
||||
if (process.env.NODE_ENV === 'development') {
|
||||
// Follow these instructions for profiling / debugging leaks
|
||||
// * https://developers.google.com/chrome-developer-tools/docs/heap-profiling
|
||||
|
|
@ -31,7 +31,7 @@ if (process.env.NODE_ENV === 'development') {
|
|||
console.log("To debug memory leaks:" +
|
||||
"\n\t(1) Run `kill -SIGUSR2 " + process.pid + "`" +
|
||||
"\n\t(2) open http://c4milo.github.com/node-webkit-agent/21.0.1180.57/inspector.html?host=localhost:1337&page=0");
|
||||
}
|
||||
}*/
|
||||
|
||||
process.on('uncaughtException', function (error) {
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,33 @@
|
|||
XP = 15
|
||||
HP = 2
|
||||
|
||||
MODIFIER = .02
|
||||
priorityValue = (priority='!') ->
|
||||
switch priority
|
||||
when '!' then 1
|
||||
when '!!' then 1.5
|
||||
when '!!!' then 2
|
||||
else 1
|
||||
|
||||
module.exports.tnl = (level) ->
|
||||
return (Math.pow(level,2)*10)+(level*10)+80
|
||||
if level >= 100
|
||||
value = 0
|
||||
else
|
||||
value = Math.round(((Math.pow(level,2)*0.25)+(10 * level) + 139.75)/10)*10 # round to nearest 10
|
||||
return value
|
||||
|
||||
###
|
||||
Calculates Exp modificaiton based on level and weapon strength
|
||||
{value} task.value for exp gain
|
||||
{weaponStrength) weapon strength
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
module.exports.expModifier = (value, weaponStrength, level) ->
|
||||
levelModifier = (level-1) * MODIFIER
|
||||
weaponModifier = weaponStrength / 100
|
||||
strength = 1 + weaponModifier + levelModifier
|
||||
return value * strength
|
||||
module.exports.expModifier = (value, weaponStr, level, priority='!') ->
|
||||
str = (level-1) / 2 # ultimately get this from user
|
||||
totalStr = (str + weaponStr) / 100
|
||||
strMod = 1 + totalStr
|
||||
exp = value * XP * strMod * priorityValue(priority)
|
||||
return Math.round(exp)
|
||||
|
||||
###
|
||||
Calculates HP modification based on level and armor defence
|
||||
|
|
@ -22,18 +35,21 @@ module.exports.expModifier = (value, weaponStrength, level) ->
|
|||
{armorDefense} defense from armor
|
||||
{helmDefense} defense from helm
|
||||
{level} current user level
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
module.exports.hpModifier = (value, armorDefense, helmDefense, shieldDefense, level) ->
|
||||
levelModifier = (level-1) * MODIFIER
|
||||
armorModifier = (armorDefense + helmDefense + shieldDefense) / 100
|
||||
defense = 1 - levelModifier + armorModifier
|
||||
return value * defense
|
||||
module.exports.hpModifier = (value, armorDef, helmDef, shieldDef, level, priority='!') ->
|
||||
def = (level-1) / 2 # ultimately get this from user?
|
||||
totalDef = (def + armorDef + helmDef + shieldDef) / 100 #ultimate get this from user
|
||||
defMod = 1 - totalDef
|
||||
hp = value * HP * defMod * priorityValue(priority)
|
||||
return Math.round(hp * 10)/10 # round to 1dp
|
||||
|
||||
###
|
||||
Future use
|
||||
{priority} user-defined priority multiplier
|
||||
###
|
||||
module.exports.gpModifier = (value, modifier) ->
|
||||
return value * modifier
|
||||
module.exports.gpModifier = (value, modifier, priority='!') ->
|
||||
return value * modifier * priorityValue(priority)
|
||||
|
||||
###
|
||||
Calculates the next task.value based on direction
|
||||
|
|
@ -42,13 +58,10 @@ module.exports.gpModifier = (value, modifier) ->
|
|||
{direction} up or down
|
||||
###
|
||||
module.exports.taskDeltaFormula = (currentValue, direction) ->
|
||||
if direction is 'up'
|
||||
delta = Math.max(Math.pow(0.95,currentValue),0.25)
|
||||
else
|
||||
delta = -Math.min(Math.pow(0.95,currentValue),5)
|
||||
#sign = if (direction is 'up') then 1 else -1
|
||||
#delta = Math.pow(0.95,currentValue) * sign
|
||||
#if delta < -5 then delta = -5
|
||||
#console.log("CurrentValue: " + currentValue + " delta: " + delta)
|
||||
#delta = if (currentValue < 0) then (( -0.1 * currentValue + 1 ) * sign) else (( Math.pow(0.9,currentValue) ) * sign)
|
||||
return delta
|
||||
if currentValue < -47.27 then currentValue = -47.27
|
||||
else if currentValue > 21.27 then currentValue = 21.27
|
||||
delta = Math.pow(0.9747,currentValue)
|
||||
return delta if direction is 'up'
|
||||
return -delta
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -3,39 +3,21 @@ moment = require 'moment'
|
|||
#algos = require './algos'
|
||||
|
||||
|
||||
module.exports.restoreRefs = restoreRefs = (model) ->
|
||||
restoreRefs = module.exports.restoreRefs = (model) ->
|
||||
# tnl function
|
||||
model.fn '_tnl', '_user.stats.lvl', (lvl) ->
|
||||
# see https://github.com/lefnire/habitrpg/issues/4
|
||||
# also update in scoring.coffee. TODO create a function accessible in both locations
|
||||
#TODO find a method of calling algos.tnl()
|
||||
10*Math.pow(lvl,2)+(lvl*10)+80
|
||||
if lvl==100
|
||||
0
|
||||
else
|
||||
Math.round(((Math.pow(lvl,2)*0.25)+(10 * lvl) + 139.75)/10)*10
|
||||
|
||||
#refLists
|
||||
_.each ['habit', 'daily', 'todo', 'reward'], (type) ->
|
||||
model.refList "_#{type}List", "_user.tasks", "_user.#{type}Ids"
|
||||
|
||||
module.exports.resetDom = (model) ->
|
||||
window.DERBY.app.dom.clear()
|
||||
restoreRefs(model)
|
||||
window.DERBY.app.view.render(model)
|
||||
reconstructPage model
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
reconstructPage model
|
||||
setupGrowlNotifications(model) unless model.get('_view.mobileDevice')
|
||||
|
||||
reconstructPage = (model) ->
|
||||
loadJavaScripts(model)
|
||||
setupSortable(model)
|
||||
setupTooltips(model)
|
||||
setupTour(model)
|
||||
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
|
||||
.on 'changeDate', (ev) ->
|
||||
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
|
||||
#FIXME also, it saves as a day behind??
|
||||
model.at(ev.target).set 'date', moment(ev.date).add('d',1).format('MM/DD/YYYY')
|
||||
|
||||
###
|
||||
Loads JavaScript files from (1) public/js/* and (2) external sources
|
||||
We use this file (instead of <Scripts:> or <Tail:> inside .html) so we can utilize require() to concatinate for
|
||||
|
|
@ -60,7 +42,7 @@ loadJavaScripts = (model) ->
|
|||
|
||||
|
||||
require '../../public/vendor/jquery-cookie/jquery.cookie'
|
||||
require '../../public/vendor/bootstrap-tour' #https://raw.github.com/pushly/bootstrap-tour/master/bootstrap-tour.js
|
||||
require '../../public/vendor/bootstrap-tour/bootstrap-tour'
|
||||
require '../../public/vendor/bootstrap-datepicker/js/bootstrap-datepicker'
|
||||
require '../../public/vendor/bootstrap-growl/jquery.bootstrap-growl.min'
|
||||
|
||||
|
|
@ -107,6 +89,11 @@ setupTooltips = (model) ->
|
|||
$('[rel=tooltip]').tooltip()
|
||||
$('[rel=popover]').popover()
|
||||
|
||||
$('.priority-multiplier-help').popover
|
||||
title: "How difficult is this task?"
|
||||
trigger: "hover"
|
||||
content: "This multiplies its point value. Use sparingly, rely instead on our organic value-adjustment algorithms. But some tasks are grossly more valuable (Write Thesis vs Floss Teeth). Click for more info."
|
||||
|
||||
setupTour = (model) ->
|
||||
tourSteps = [
|
||||
{
|
||||
|
|
@ -154,12 +141,8 @@ setupTour = (model) ->
|
|||
$('.main-avatar').popover('destroy') #remove previous popovers
|
||||
tour = new Tour()
|
||||
_.each tourSteps, (step) ->
|
||||
tour.addStep
|
||||
html: true
|
||||
element: step.element
|
||||
title: step.title
|
||||
content: step.content
|
||||
placement: step.placement
|
||||
tour.addStep _.defaults step, {html:true}
|
||||
tour._current = 0 if isNaN(tour._current) #bootstrap-tour bug
|
||||
tour.start()
|
||||
|
||||
###
|
||||
|
|
@ -191,13 +174,14 @@ setupGrowlNotifications = (model) ->
|
|||
else if num > 0
|
||||
statsNotification "<i class='icon-heart'></i> + #{rounded} HP", 'hp' # gained hp from potion/level?
|
||||
|
||||
user.on 'set', 'stats.exp', (captures, args, isLocal, silent) ->
|
||||
num = captures - args
|
||||
rounded = Math.abs(num.toFixed(1))
|
||||
if num < 0 and not silent
|
||||
statsNotification "<i class='icon-star'></i> - #{rounded} XP", 'xp'
|
||||
else if num > 0
|
||||
statsNotification "<i class='icon-star'></i> + #{rounded} XP", 'xp'
|
||||
user.on 'set', 'stats.exp', (captures, args, isLocal, silent=false) ->
|
||||
# unless silent
|
||||
num = captures - args
|
||||
rounded = Math.abs(num.toFixed(1))
|
||||
if num < 0 and num > -50 # TODO fix hackey negative notification supress
|
||||
statsNotification "<i class='icon-star'></i> - #{rounded} XP", 'xp'
|
||||
else if num > 0
|
||||
statsNotification "<i class='icon-star'></i> + #{rounded} XP", 'xp'
|
||||
|
||||
user.on 'set', 'stats.gp', (captures, args) ->
|
||||
num = captures - args
|
||||
|
|
@ -215,6 +199,26 @@ setupGrowlNotifications = (model) ->
|
|||
user.on 'set', 'stats.lvl', (captures, args) ->
|
||||
if captures > args
|
||||
if captures is 1 and args is 0
|
||||
statsNotification '<i class="icon-death"></i> You died!', 'death'
|
||||
statsNotification '<i class="icon-death"></i> You died! Game over.', 'death'
|
||||
else
|
||||
statsNotification '<i class="icon-chevron-up"></i> Level Up!', 'lvl'
|
||||
|
||||
|
||||
module.exports.resetDom = (model) ->
|
||||
window.DERBY.app.dom.clear()
|
||||
window.DERBY.app.view.render(model)
|
||||
|
||||
module.exports.app = (appExports, model, app) ->
|
||||
loadJavaScripts(model)
|
||||
setupGrowlNotifications(model) unless model.get('_view.mobileDevice')
|
||||
|
||||
app.on 'render', (ctx) ->
|
||||
#restoreRefs(model)
|
||||
setupSortable(model)
|
||||
setupTooltips(model)
|
||||
setupTour(model)
|
||||
$('.datepicker').datepicker({autoclose:true, todayBtn:true})
|
||||
.on 'changeDate', (ev) ->
|
||||
#for some reason selecting a date doesn't fire a change event on the field, meaning our changes aren't saved
|
||||
#FIXME also, it saves as a day behind??
|
||||
model.at(ev.target).set 'date', moment(ev.date).add('d',1).format('MM/DD/YYYY')
|
||||
|
|
@ -63,6 +63,9 @@ module.exports.app = (appExports, model) ->
|
|||
appExports.closeOnliesNotification = (e, el) ->
|
||||
user.set('flags.onliesNotification', 'hide')
|
||||
|
||||
appExports.closePriorityNotification = (e, el) ->
|
||||
user.set('flags.priorityNotification', 'hide')
|
||||
|
||||
appExports.customizeGender = (e, el) ->
|
||||
user.set 'preferences.gender', $(el).attr('data-value')
|
||||
|
||||
|
|
@ -79,7 +82,7 @@ module.exports.app = (appExports, model) ->
|
|||
batch = new BatchUpdate(model)
|
||||
batch.startTransaction()
|
||||
$('#restore-form input').each ->
|
||||
batch.set $(this).attr('data-for'), parseInt($(this).val())
|
||||
batch.set $(this).attr('data-for'), parseInt($(this).val() || 1)
|
||||
batch.commit()
|
||||
|
||||
user.on 'set', 'flags.customizationsNotification', (captures, args) ->
|
||||
|
|
@ -172,7 +175,7 @@ module.exports.updateUser = (model) ->
|
|||
union = _.union obj[type + 'Ids'], taskIds
|
||||
|
||||
# 2. remove empty (grey) tasks
|
||||
preened = _.filter(union, (val) -> _.contains(taskIds, val))
|
||||
preened = _.filter union, (val) -> _.contains(taskIds, val) and val?
|
||||
|
||||
# There were indeed issues found, set the new list
|
||||
batch.set("#{type}Ids", preened) # if _.difference(preened, userObj[path]).length != 0
|
||||
|
|
|
|||
|
|
@ -8,8 +8,13 @@ module.exports.app = (appExports, model) ->
|
|||
user.set 'lastCron', yesterday
|
||||
window.location.reload()
|
||||
|
||||
appExports.emulateTenDays = ->
|
||||
yesterday = +moment().subtract('days', 10).toDate()
|
||||
user.set 'lastCron', yesterday
|
||||
window.location.reload()
|
||||
|
||||
appExports.cheat = ->
|
||||
user.incr 'stats.exp', 20
|
||||
user.incr 'stats.exp', model.get '_tnl'
|
||||
user.incr 'stats.gp', 1000
|
||||
|
||||
appExports.reset = ->
|
||||
|
|
|
|||
|
|
@ -19,6 +19,9 @@ module.exports.viewHelpers = (view) ->
|
|||
|
||||
view.fn "floor", (num) ->
|
||||
Math.floor num
|
||||
|
||||
view.fn "ceil", (num) ->
|
||||
Math.ceil num
|
||||
|
||||
view.fn "lt", (a, b) ->
|
||||
a < b
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
derby = require 'derby'
|
||||
{get, view, ready} = derby.createApp module
|
||||
app = derby.createApp module
|
||||
{get, view, ready} = app
|
||||
derby.use require('derby-ui-boot'), {styles: ['bootstrap', 'responsive']}
|
||||
derby.use require '../../ui'
|
||||
derby.use require 'derby-auth/components'
|
||||
|
|
@ -59,5 +60,5 @@ ready (model) ->
|
|||
profile.app(exports, model)
|
||||
require('../server/private').app(exports, model)
|
||||
require('./debug').app(exports, model) if model.get('_view.nodeEnv') != 'production'
|
||||
browser.app(exports, model)
|
||||
browser.app(exports, model, app)
|
||||
|
||||
|
|
|
|||
|
|
@ -165,22 +165,22 @@ module.exports.app = (appExports, model) ->
|
|||
model.set '_view.activeTabPets', true
|
||||
model.set '_view.activeTabRewards', false
|
||||
|
||||
user.on 'set', 'flags.itemsEnabled', (captures, args) ->
|
||||
return unless captures == true
|
||||
model.on 'set', '_user.flags.itemsEnabled', (captures, args) ->
|
||||
return unless captures is true
|
||||
html = """
|
||||
<div class='item-store-popover'>
|
||||
<img src='/vendor/BrowserQuest/client/img/1/chest.png' />
|
||||
Congratulations, you have unlocked the Item Store! You can now buy weapons, armor, potions, etc. Read each item's comment for more information.
|
||||
<a href='#' onClick="$('ul.items').popover('hide');return false;">[Close]</a>
|
||||
<a href='#' onClick="$('div.rewards').popover('hide');return false;">[Close]</a>
|
||||
</div>
|
||||
"""
|
||||
$('ul.items').popover
|
||||
$('div.rewards').popover({
|
||||
title: "Item Store Unlocked"
|
||||
placement: 'left'
|
||||
trigger: 'manual'
|
||||
html: true
|
||||
content: html
|
||||
$('ul.items').popover 'show'
|
||||
}).popover 'show'
|
||||
|
||||
user.on 'set', 'flags.petsEnabled', (captures, args) ->
|
||||
return unless captures == true
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
taskPath = "tasks.#{taskId}"
|
||||
taskObj = obj.tasks[taskId]
|
||||
{type, value} = taskObj
|
||||
priority = taskObj.priority or '!'
|
||||
|
||||
# If they're trying to purhcase a too-expensive reward, confirm they want to take a hit for it
|
||||
if taskObj.value > obj.stats.gp and taskObj.type is 'reward'
|
||||
|
|
@ -51,24 +52,19 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
addPoints = ->
|
||||
level = user.get('stats.lvl')
|
||||
weaponStrength = items.items.weapon[user.get('items.weapon')].strength
|
||||
modified = algos.expModifier(delta,weaponStrength,level)
|
||||
exp += modified*10
|
||||
gp += delta
|
||||
exp += algos.expModifier(delta,weaponStrength,level, priority) / 2 # / 2 hack for now bcause people leveling too fast
|
||||
gp += algos.gpModifier(delta, 1, priority)
|
||||
|
||||
subtractPoints = ->
|
||||
level = user.get('stats.lvl')
|
||||
armorDefense = items.items.armor[user.get('items.armor')].defense
|
||||
helmDefense = items.items.head[user.get('items.head')].defense
|
||||
shieldDefense = items.items.shield[user.get('items.shield')].defense
|
||||
modified = algos.hpModifier(delta,armorDefense,helmDefense,shieldDefense,level)
|
||||
hp += modified
|
||||
hp += algos.hpModifier(delta,armorDefense,helmDefense,shieldDefense,level, priority)
|
||||
|
||||
switch type
|
||||
when 'habit'
|
||||
# Don't adjust values for habits that don't have both + and -
|
||||
#adjustvalue = if (taskObj.up==false or taskObj.down==false) then false else true
|
||||
adjustvalue = true;
|
||||
calculateDelta(adjustvalue)
|
||||
calculateDelta()
|
||||
# Add habit value to habit-history (if different)
|
||||
if (delta > 0) then addPoints() else subtractPoints()
|
||||
taskObj.history ?= []
|
||||
|
|
@ -78,20 +74,20 @@ score = (model, taskId, direction, times, batch, cron) ->
|
|||
batch.set "#{taskPath}.history", taskObj.history
|
||||
|
||||
when 'daily'
|
||||
#calculateDelta()
|
||||
if cron? # cron
|
||||
calculateDelta()
|
||||
subtractPoints()
|
||||
else
|
||||
calculateDelta(false)
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
if delta != 0
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
when 'todo'
|
||||
if cron? #cron
|
||||
calculateDelta()
|
||||
#don't touch stats on cron
|
||||
else
|
||||
calculateDelta(false)
|
||||
calculateDelta()
|
||||
addPoints() # obviously for delta>0, but also a trick to undo accidental checkboxes
|
||||
|
||||
when 'reward'
|
||||
|
|
@ -140,18 +136,30 @@ updateStats = (model, newStats, batch) ->
|
|||
obj.stats.hp = newStats.hp
|
||||
|
||||
if newStats.exp?
|
||||
# level up & carry-over exp
|
||||
tnl = model.get '_tnl'
|
||||
silent = false
|
||||
if newStats.exp >= tnl
|
||||
silent = true
|
||||
user.set('stats.exp', newStats.exp)
|
||||
newStats.exp -= tnl
|
||||
obj.stats.lvl++
|
||||
obj.stats.hp = 50
|
||||
#silent = false
|
||||
# if we're at level 100, turn xp to gold
|
||||
if obj.stats.lvl >= 100
|
||||
newStats.gp += newStats.exp / 15
|
||||
newStats.exp = 0
|
||||
obj.stats.lvl = 100
|
||||
else
|
||||
# level up & carry-over exp
|
||||
if newStats.exp >= tnl
|
||||
#silent = true # push through the negative xp silently
|
||||
user.set('stats.exp', newStats.exp) # push normal + notification
|
||||
while newStats.exp >= tnl and obj.stats.lvl < 100 # keep levelling up
|
||||
newStats.exp -= tnl
|
||||
obj.stats.lvl++
|
||||
tnl = algos.tnl(obj.stats.lvl)
|
||||
if obj.stats.lvl== 100
|
||||
newStats.exp = 0
|
||||
obj.stats.hp = 50
|
||||
|
||||
obj.stats.exp = newStats.exp
|
||||
user.pass(silent:true).set('stats.exp', obj.stats.exp) if silent
|
||||
#if silent
|
||||
#console.log("pushing silent :" + obj.stats.exp)
|
||||
#user.pass(true).set('stats.exp', obj.stats.exp)
|
||||
|
||||
# Set flags when they unlock features
|
||||
if !obj.flags.customizationsNotification and (obj.stats.exp > 10 or obj.stats.lvl > 1)
|
||||
|
|
@ -206,19 +214,25 @@ cron = (model) ->
|
|||
if repeat[helpers.dayMapping[thatDay.day()]]==true
|
||||
daysFailed++
|
||||
score model, id, 'down', daysFailed, batch, true
|
||||
|
||||
if type == 'daily'
|
||||
if completed #set OHV for completed dailies
|
||||
newValue = taskObj.value + algos.taskDeltaFormula(taskObj.value,'up')
|
||||
batch.set "tasks.#{taskObj.id}.value", newValue
|
||||
|
||||
taskObj.history ?= []
|
||||
taskObj.history.push { date: +new Date, value: value }
|
||||
taskObj.history.push { date: +new Date, value: taskObj.value }
|
||||
batch.set "tasks.#{taskObj.id}.history", taskObj.history
|
||||
batch.set "tasks.#{taskObj.id}.completed", false
|
||||
else
|
||||
value = obj.tasks[taskObj.id].value #get updated value
|
||||
absVal = if (completed) then Math.abs(value) else value
|
||||
todoTally += absVal
|
||||
else if type is 'habit' #reset 'onlies' value to 0
|
||||
else if type is 'habit' # slowly reset 'onlies' value to 0
|
||||
if taskObj.up==false or taskObj.down==false
|
||||
batch.set "tasks.#{taskObj.id}.value", 0
|
||||
if Math.abs(taskObj.value) < 0.1
|
||||
batch.set "tasks.#{taskObj.id}.value", 0
|
||||
else
|
||||
batch.set "tasks.#{taskObj.id}.value", taskObj.value / 2
|
||||
|
||||
# Finished tallying
|
||||
obj.history ?= {}; obj.history.todos ?= []; obj.history.exp ?= []
|
||||
|
|
|
|||
|
|
@ -14,14 +14,20 @@ module.exports.view = (view) ->
|
|||
else
|
||||
classes += " uncompleted"
|
||||
|
||||
switch
|
||||
when value<-8 then classes += ' color-worst'
|
||||
when value>=-8 and value<-5 then classes += ' color-worse'
|
||||
when value>=-5 and value<-1 then classes += ' color-bad'
|
||||
when value>=-1 and value<1 then classes += ' color-neutral'
|
||||
when value>=1 and value<5 then classes += ' color-good'
|
||||
when value>=5 and value<10 then classes += ' color-better'
|
||||
when value>=10 then classes += ' color-best'
|
||||
if value < -20
|
||||
classes += ' color-worst'
|
||||
else if value < -10
|
||||
classes += ' color-worse'
|
||||
else if value < -1
|
||||
classes += ' color-bad'
|
||||
else if value < 1
|
||||
classes += ' color-neutral'
|
||||
else if value < 5
|
||||
classes += ' color-good'
|
||||
else if value < 10
|
||||
classes += ' color-better'
|
||||
else
|
||||
classes += ' color-best'
|
||||
return classes
|
||||
|
||||
module.exports.app = (appExports, model) ->
|
||||
|
|
@ -135,9 +141,9 @@ module.exports.app = (appExports, model) ->
|
|||
data = google.visualization.arrayToDataTable matrix
|
||||
|
||||
options = {
|
||||
title: 'History'
|
||||
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
|
||||
backgroundColor: 'whiteSmoke'
|
||||
title: 'History'
|
||||
#TODO use current background color: $(el).css('background-color), but convert to hex (see http://goo.gl/ql5pR)
|
||||
backgroundColor: 'whiteSmoke'
|
||||
}
|
||||
|
||||
chart = new google.visualization.LineChart(document.getElementById( chartSelector ))
|
||||
|
|
@ -173,7 +179,15 @@ module.exports.app = (appExports, model) ->
|
|||
task = model.at $(el).parents('li')[0]
|
||||
scoring.score(model, task.get('id'), direction)
|
||||
|
||||
appExports.tasksToggleAdvanced = (e, el) ->
|
||||
$(el).next('.advanced').toggle()
|
||||
|
||||
appExports.tasksSaveAndClose = ->
|
||||
# When they update their notes, re-establish tooltip & popover
|
||||
$('[rel=tooltip]').tooltip()
|
||||
$('[rel=popover]').popover()
|
||||
|
||||
appExports.tasksSetPriority = (e, el) ->
|
||||
dataId = $(el).parent('[data-id]').attr('data-id')
|
||||
#"_user.tasks.#{dataId}"
|
||||
model.at(e.target).set 'priority', $(el).attr('data-priority')
|
||||
|
|
@ -3,6 +3,7 @@ router = new express.Router()
|
|||
|
||||
scoring = require '../app/scoring'
|
||||
_ = require 'underscore'
|
||||
{ tnl } = require '../app/algos'
|
||||
validator = require 'derby-auth/node_modules/validator'
|
||||
check = validator.check
|
||||
sanitize = validator.sanitize
|
||||
|
|
@ -41,6 +42,9 @@ auth = (req, res, next) ->
|
|||
router.get '/user', auth, (req, res) ->
|
||||
user = req.userObj
|
||||
|
||||
user.stats.toNextLevel = tnl user.stats.lvl
|
||||
user.stats.maxHealth = 50
|
||||
|
||||
delete user.apiToken
|
||||
|
||||
res.json user
|
||||
|
|
@ -56,7 +60,7 @@ validateTask = (req, res, next) ->
|
|||
newTask = { type, text, notes, value, up, down, completed } = req.body
|
||||
|
||||
# If we're updating, get the task from the user
|
||||
if req.method is 'PUT'
|
||||
if req.method is 'PUT' or req.method is 'DELETE'
|
||||
task = req.userObj?.tasks[req.params.id]
|
||||
return res.json 400, err: "No task found." if !task || _.isEmpty(task)
|
||||
# Strip for now
|
||||
|
|
@ -86,6 +90,15 @@ router.put '/user/task/:id', auth, validateTask, (req, res) ->
|
|||
|
||||
res.json 200, req.task
|
||||
|
||||
router.delete '/user/task/:id', auth, validateTask, (req, res) ->
|
||||
taskIds = req.user.get "#{req.task.type}Ids"
|
||||
|
||||
req.user.del "tasks.#{req.task.id}"
|
||||
# Remove one id from array of typeIds
|
||||
req.user.remove "#{req.task.type}Ids", taskIds.indexOf(req.task.id), 1
|
||||
|
||||
res.send 204
|
||||
|
||||
router.post '/user/task', auth, validateTask, (req, res) ->
|
||||
task = req.task
|
||||
type = task.type
|
||||
|
|
@ -152,4 +165,3 @@ router.post '/user/tasks/:taskId/:direction', auth, scoreTask
|
|||
module.exports = router
|
||||
module.exports.auth = auth
|
||||
module.exports.scoreTask = scoreTask # export so deprecated can call it
|
||||
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ auth.store(store, habitrpgStore.customAccessControl)
|
|||
|
||||
mongo_store = new MongoStore {url: process.env.NODE_DB_URI}, ->
|
||||
expressApp
|
||||
.use(middleware.allowCrossDomain)
|
||||
.use(express.favicon("#{publicPath}/favicon.ico"))
|
||||
# Gzip static files and serve from memory
|
||||
.use(gzippo.staticGzip(publicPath, maxAge: ONE_YEAR))
|
||||
|
|
|
|||
|
|
@ -12,3 +12,15 @@ module.exports.view = (req, res, next) ->
|
|||
_view.nodeEnv = process.env.NODE_ENV
|
||||
model.set '_view', _view
|
||||
next()
|
||||
|
||||
#CORS middleware
|
||||
module.exports.allowCrossDomain = (req, res, next) ->
|
||||
res.header "Access-Control-Allow-Origin", (req.headers.origin || "*")
|
||||
res.header "Access-Control-Allow-Methods", "OPTIONS,GET,POST,PUT,HEAD,DELETE"
|
||||
res.header "Access-Control-Allow-Headers", "Content-Type,X-Requested-With,x-api-user,x-api-key"
|
||||
|
||||
# wtf is this for?
|
||||
if req.method is 'OPTIONS'
|
||||
res.send(200);
|
||||
else
|
||||
next()
|
||||
|
|
@ -70,7 +70,7 @@ userAccess = (store) ->
|
|||
###
|
||||
REST = (store) ->
|
||||
store.query.expose "users", "withIdAndToken", (uid, token) ->
|
||||
@byId(uid)
|
||||
@where("id").equals(uid)
|
||||
.where('apiToken').equals(token)
|
||||
.findOne()
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ conf.argv().env().file({file: __dirname + '../config.json'}).defaults
|
|||
process.env.BASE_URL = conf.get("BASE_URL")
|
||||
process.env.FACEBOOK_KEY = conf.get("FACEBOOK_KEY")
|
||||
process.env.FACEBOOK_SECRET = conf.get("FACEBOOK_SECRET")
|
||||
process.env.NODE_DB_URI = 'mongodb://localhost/habirpg'
|
||||
process.env.NODE_DB_URI = 'mongodb://localhost/habitrpg'
|
||||
|
||||
## monkey-patch expect.js for better diffs on mocha
|
||||
## see: https://github.com/LearnBoost/expect.js/pull/34
|
||||
|
|
@ -100,6 +100,8 @@ describe 'API', ->
|
|||
expect(res.body.id).not.to.be.empty()
|
||||
self = _.clone(currentUser)
|
||||
delete self.apiToken
|
||||
self.stats.toNextLevel = 150
|
||||
self.stats.maxHealth = 50
|
||||
|
||||
expect(res.body).to.eql self
|
||||
done()
|
||||
|
|
@ -243,3 +245,29 @@ describe 'API', ->
|
|||
# Ensure that the two sets are equal
|
||||
expect(_.difference(_.pluck(res.body,'id'), _.pluck(tasks,'id')).length).to.equal 0
|
||||
done()
|
||||
|
||||
it 'DELETE /api/v1/user/task/:id', (done) ->
|
||||
tid = currentUser.habitIds[2]
|
||||
request.del("#{baseURL}/user/task/#{tid}")
|
||||
.set('Accept', 'application/json')
|
||||
.set('X-API-User', currentUser.id)
|
||||
.set('X-API-Key', currentUser.apiToken)
|
||||
.end (res) ->
|
||||
expect(res.body.err).to.be undefined
|
||||
expect(res.statusCode).to.be 204
|
||||
query = model.query('users').withIdAndToken(currentUser.id, currentUser.apiToken)
|
||||
query.fetch (err, user) ->
|
||||
expect(user.get('habitIds').indexOf(tid)).to.be -1
|
||||
expect(user.get("tasks.#{tid}")).to.be undefined
|
||||
done()
|
||||
|
||||
it 'DELETE /api/v1/user/task/:id (no task found)', (done) ->
|
||||
tid = "adsfasdfjunkshouldntbeatask"
|
||||
request.del("#{baseURL}/user/task/#{tid}")
|
||||
.set('Accept', 'application/json')
|
||||
.set('X-API-User', currentUser.id)
|
||||
.set('X-API-Key', currentUser.apiToken)
|
||||
.end (res) ->
|
||||
expect(res.statusCode).to.be 400
|
||||
expect(res.body.err).to.be 'No task found.'
|
||||
done()
|
||||
|
|
|
|||
|
|
@ -22,5 +22,11 @@
|
|||
chime in with your recommendations on this mechanic.
|
||||
</p>
|
||||
</div>
|
||||
{/}
|
||||
|
||||
{#if equal(_user.flags.priorityNotification, 'show')}
|
||||
<div class='alert alert-success'>
|
||||
<a x-bind="click:closePriorityNotification" class=pull-right>[x]</a>
|
||||
<p>New Feature: Priority Multiplier! You can now multiply "more important" tasks on a 1x, 1.5x, or 2x scale. <a target="_blank" href="https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17">See details</a>.</p>
|
||||
</div>
|
||||
{/}
|
||||
|
|
@ -19,7 +19,8 @@
|
|||
{else}
|
||||
<div class='pull-right'>
|
||||
<button class='btn' x-bind="click:emulateNextDay">Emulate Next Day</button>
|
||||
<button class='btn' x-bind="click:cheat">Add GP & Exp</button>
|
||||
<button class='btn' x-bind="click:emulateTenDays">Emulate 10 Days</button>
|
||||
<button class='btn' x-bind="click:cheat">Insta Level</button>
|
||||
<button class='btn' x-bind='click:reset'>Reset Level</button>
|
||||
</div>
|
||||
{/}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@
|
|||
<div class="progress-bars">
|
||||
<div class="progress progress-danger" rel=tooltip data-placement=bottom title="Health">
|
||||
<div class="bar" style="width: {percent(_user.stats.hp, 50)}%;"></div>
|
||||
<span class="progress-text"><i class=icon-heart></i> {round(_user.stats.hp)} / 50</span>
|
||||
<span class="progress-text"><i class=icon-heart></i> {ceil(_user.stats.hp)} / 50</span>
|
||||
</div>
|
||||
|
||||
<div class="progress progress-warning" rel=tooltip data-placement=bottom title="Experience">
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@
|
|||
</div>
|
||||
<app:newTask type="reward" inputValue="{_newReward}" placeHolder="New Reward" />
|
||||
|
||||
{#if _user.flags.petsEnabled}
|
||||
{#if equal(_user.flags.petsEnabled,true)}
|
||||
<div class='tabbable tabs-below'>
|
||||
|
||||
<div class="tab-content">
|
||||
|
|
@ -128,43 +128,43 @@
|
|||
<input class="btn" type="submit" value="Add" />
|
||||
</form>
|
||||
|
||||
<task:>
|
||||
<li data-id={{:task.id}} class="task {taskClasses(:task.type, :task.completed, :task.value, :task.repeat)}">
|
||||
<pre>
|
||||
<div class="task-meta-controls">
|
||||
<task:>
|
||||
<li data-id={{:task.id}} class="task {taskClasses(:task.type, :task.completed, :task.value, :task.repeat)}">
|
||||
<pre>
|
||||
<div class="task-meta-controls">
|
||||
|
||||
<div class="hover-show">
|
||||
<a x-bind=click:toggleTaskEdit data-hide-id="{{:task.id}}-chart" data-toggle-id="{{:task.id}}-edit" rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
|
||||
<a x-bind=click:del rel=tooltip title="Delete"><i class="icon-trash"></i></a>
|
||||
{#if :task.history}
|
||||
<a x-bind=click:toggleChart data-toggle-id="{{:task.id}}-chart" data-hide-id="{{:task.id}}-edit" data-history-path="_user.tasks.{{:task.id}}.history" rel="tooltip" title="Progress">
|
||||
<i class="icon-signal"></i>
|
||||
</a>
|
||||
{/}
|
||||
</div>
|
||||
<div class="hover-show">
|
||||
<a x-bind=click:toggleTaskEdit data-hide-id="{{:task.id}}-chart" data-toggle-id="{{:task.id}}-edit" rel=tooltip title="Edit"><i class="icon-pencil"></i></a>
|
||||
<a x-bind=click:del rel=tooltip title="Delete"><i class="icon-trash"></i></a>
|
||||
{#if :task.history}
|
||||
<a x-bind=click:toggleChart data-toggle-id="{{:task.id}}-chart" data-hide-id="{{:task.id}}-edit" data-history-path="_user.tasks.{{:task.id}}.history" rel="tooltip" title="Progress">
|
||||
<i class="icon-signal"></i>
|
||||
</a>
|
||||
{/}
|
||||
</div>
|
||||
|
||||
{#if :task.notes}
|
||||
<span rel="popover" data-trigger="hover" data-placement="left" data-content="{:task.notes}" data-original-title="{:task.text}" class='task-notes'><i class="icon-comment"></i></span>
|
||||
{/}
|
||||
</div>
|
||||
{#if :task.notes}
|
||||
<span rel="popover" data-trigger="hover" data-placement="left" data-content="{:task.notes}" data-original-title="{:task.text}" class='task-notes'><i class="icon-comment"></i></span>
|
||||
{/}
|
||||
</div>
|
||||
|
||||
<div class="task-controls">
|
||||
<!-- Habits -->
|
||||
{#if equal(:task.type, 'habit')}
|
||||
{#if :task.up}<a data-direction=up x-bind=click:score><img src="/img/add.png" /></a>{/}
|
||||
{#if :task.down}<a data-direction=down x-bind=click:score><img src="/img/remove.png" /></a>{/}
|
||||
<!-- Rewards -->
|
||||
{else if equal(:task.type, 'reward')}
|
||||
<a x-bind=click:score class="buy-link" data-direction=down>{:task.value}<img src="/img/coin_single_gold.png"/></a>
|
||||
<!-- Daily & Todos -->
|
||||
{else}
|
||||
<input type=checkbox checked="{:task.completed}"/>
|
||||
{/}
|
||||
</div>
|
||||
<div class="task-text">{:task.text}</div>
|
||||
<div class="task-controls">
|
||||
<!-- Habits -->
|
||||
{#if equal(:task.type, 'habit')}
|
||||
{#if :task.up}<a data-direction=up x-bind=click:score><img src="/img/add.png" /></a>{/}
|
||||
{#if :task.down}<a data-direction=down x-bind=click:score><img src="/img/remove.png" /></a>{/}
|
||||
<!-- Rewards -->
|
||||
{else if equal(:task.type, 'reward')}
|
||||
<a x-bind=click:score class="buy-link" data-direction=down>{:task.value}<img src="/img/coin_single_gold.png"/></a>
|
||||
<!-- Daily & Todos -->
|
||||
{else}
|
||||
<input type=checkbox checked="{:task.completed}"/>
|
||||
{/}
|
||||
</div>
|
||||
<div class="task-text">{:task.text}</div>
|
||||
|
||||
<app:taskMeta />
|
||||
</pre>
|
||||
<app:taskMeta />
|
||||
</pre>
|
||||
</li>
|
||||
|
||||
<taskMeta:>
|
||||
|
|
@ -176,37 +176,59 @@
|
|||
<label>Notes</label><textarea rows=3>{:task.notes}</textarea>
|
||||
</div>
|
||||
{#if equal(:task.type, 'habit')}
|
||||
<div class="control-group">
|
||||
<label class="checkbox inline"><input type=checkbox checked={:task.up}>Up</label>
|
||||
<label class="checkbox inline"><input type=checkbox checked={:task.down}>Down</label>
|
||||
</div>
|
||||
<hr/>
|
||||
<label>Direction</label>
|
||||
<div class="control-group">
|
||||
<label class="checkbox inline"><input type=checkbox checked={:task.up}>Up</label>
|
||||
<label class="checkbox inline"><input type=checkbox checked={:task.down}>Down</label>
|
||||
</div>
|
||||
{else if equal(:task.type, 'daily')}
|
||||
<label>Repeat</label>
|
||||
<div class="control-group btn-group repeat-days">
|
||||
<!-- note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding -->
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.su}active{/}" data-day='su' x-bind=click:toggleDay>Su</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.m}active{/}" data-day='m' x-bind=click:toggleDay>M</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.t}active{/}" data-day='t' x-bind=click:toggleDay>T</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.w}active{/}" data-day='w' x-bind=click:toggleDay>W</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.th}active{/}" data-day='th' x-bind=click:toggleDay>Th</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.f}active{/}" data-day='f' x-bind=click:toggleDay>F</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.s}active{/}" data-day='s' x-bind=click:toggleDay>S</button>
|
||||
</div>
|
||||
<hr/>
|
||||
<label>Repeat</label>
|
||||
<div class="control-group btn-group repeat-days">
|
||||
<!-- note, does not use data-toggle="buttons-checkbox" - it would interfere with our own click binding -->
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.su}active{/}" data-day='su' x-bind=click:toggleDay>Su</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.m}active{/}" data-day='m' x-bind=click:toggleDay>M</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.t}active{/}" data-day='t' x-bind=click:toggleDay>T</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.w}active{/}" data-day='w' x-bind=click:toggleDay>W</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.th}active{/}" data-day='th' x-bind=click:toggleDay>Th</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.f}active{/}" data-day='f' x-bind=click:toggleDay>F</button>
|
||||
<button type="button" class="btn btn-info {#if :task.repeat.s}active{/}" data-day='s' x-bind=click:toggleDay>S</button>
|
||||
</div>
|
||||
{else if equal(:task.type, 'reward')}
|
||||
<div class=control-group>
|
||||
<label>Price
|
||||
<hr/>
|
||||
<div class=control-group>
|
||||
<label>Price</label>
|
||||
<div class="input-append">
|
||||
<input class="span5" size="16" type="number" min="0" value={:task.value}><span class="add-on">Gold</span>
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
{else if equal(:task.type, 'todo')}
|
||||
<hr/>
|
||||
<div class=control-group>
|
||||
<label>Due Date</label>
|
||||
<input type="text" value="{:task.date}" data-date-format="mm/dd/yyyy" class="datepicker" />
|
||||
<div><small>Enter as date, eg 02/19/2013 or 02-19-2013</small></div>
|
||||
</div>
|
||||
{/}
|
||||
|
||||
<hr/>
|
||||
{#unless equal(:task.type, 'reward')}
|
||||
<div>
|
||||
<a x-bind="click:tasksToggleAdvanced">Advanced</a>
|
||||
<div class='advanced hide'>
|
||||
|
||||
<label>Difficulty <a class='priority-multiplier-help' href="https://trello.com/card/priority-multiplier/50e5d3684fe3a7266b0036d6/17" target="_blank"><i class='icon-question-sign'></i></a></label>
|
||||
<div class="control-group btn-group priority-multiplier" data-id="{{:task.id}}">
|
||||
<button type="button" class="btn btn-info {#if equal(:task.priority,'!')}active{/}{#unless :task.priority}active{/}" data-priority='!' x-bind=click:tasksSetPriority>Easy</button>
|
||||
<button type="button" class="btn btn-info {#if equal(:task.priority,'!!')}active{/}" data-priority='!!' x-bind=click:tasksSetPriority>Medium</button>
|
||||
<button type="button" class="btn btn-info {#if equal(:task.priority,'!!!')}active{/}" data-priority='!!!' x-bind=click:tasksSetPriority>Hard</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<br/>
|
||||
{/}
|
||||
<button type=submit class="btn" x-bind="click:tasksSaveAndClose">Save & Close</button>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -13,308 +13,307 @@
|
|||
<p>
|
||||
<strong>PLEASE READ THIS PRIVACY POLICY CAREFULLY.</strong>
|
||||
<br>
|
||||
By accessing
|
||||
or otherwise using habitrpg.com or any sub domains thereto ("the Sites"),
|
||||
or using a habitrpg.com or HabitRPG application on a mobile device ("the Applications"),
|
||||
you agree to be bound contractually by this Privacy Policy. Individually
|
||||
or collectively, the Applications and the Sites may be referred to as
|
||||
By accessing or otherwise using habitrpg.com or any sub domains thereto ("the Sites"),
|
||||
or using a habitrpg.com or HabitRPG application on a mobile device ("the Applications"),
|
||||
you agree to be bound contractually by this Privacy Policy. Individually
|
||||
or collectively, the Applications and the Sites may be referred to as
|
||||
the "Services."
|
||||
</p>
|
||||
<p>
|
||||
To review material modifications and their effective dates scroll
|
||||
To review material modifications and their effective dates scroll
|
||||
to the bottom of the page.
|
||||
</p>
|
||||
<p>
|
||||
<strong> 1. Privacy Statement; Collection of Personal
|
||||
<strong> 1. Privacy Statement; Collection of Personal
|
||||
Information. </strong>
|
||||
<br>
|
||||
1.1 OCDevel owns and operates this business. All
|
||||
1.1 OCDevel owns and operates this business. All
|
||||
references to "we", "us", shall be construed to mean OCDevel.
|
||||
</p>
|
||||
<p>
|
||||
1.2 We understand that visitors to this website are concerned
|
||||
about the privacy of information. The following describes our privacy
|
||||
policy regarding information, including personal information, that we
|
||||
1.2 We understand that visitors to this website are concerned
|
||||
about the privacy of information. The following describes our privacy
|
||||
policy regarding information, including personal information, that we
|
||||
collect through this website.
|
||||
</p>
|
||||
<p>
|
||||
<strong>2. Modification of Privacy Policy.</strong>
|
||||
<br>
|
||||
We reserve the right to modify this Privacy Policy at any time,
|
||||
and without prior notice, by posting an amended Privacy Policy that is
|
||||
always accessible by clicking on the "Privacy Policy" link on this
|
||||
site's home page. Your continued use of this site indicates your
|
||||
acceptance of the amended Privacy Policy. You should check the Privacy
|
||||
Policy through this link periodically for modifications by clicking on
|
||||
the link provided near the top of the Privacy Policy for a listing of
|
||||
material modifications and their effective dates. Regarding personal
|
||||
information, if any modifications are materially less restrictive on our
|
||||
use or disclosure of the personal information previously disclosed by
|
||||
you, we will obtain your consent before implementing such revisions with
|
||||
We reserve the right to modify this Privacy Policy at any time,
|
||||
and without prior notice, by posting an amended Privacy Policy that is
|
||||
always accessible by clicking on the "Privacy Policy" link on this
|
||||
site's home page. Your continued use of this site indicates your
|
||||
acceptance of the amended Privacy Policy. You should check the Privacy
|
||||
Policy through this link periodically for modifications by clicking on
|
||||
the link provided near the top of the Privacy Policy for a listing of
|
||||
material modifications and their effective dates. Regarding personal
|
||||
information, if any modifications are materially less restrictive on our
|
||||
use or disclosure of the personal information previously disclosed by
|
||||
you, we will obtain your consent before implementing such revisions with
|
||||
respect to such information.
|
||||
</p>
|
||||
<p>
|
||||
<strong>3. Collection of Anonymous, Passive Information.</strong>
|
||||
<br>
|
||||
We reserve the right to monitor your use of the services. As you
|
||||
navigate through the services, certain anonymous information may be
|
||||
passively collected (that is, gathered without your actively providing
|
||||
the information) using various technologies, such as cookies, Internet
|
||||
tags or web beacons, and navigational data collection (log files, server
|
||||
logs, clickstream). The following is a listing and a brief explanation
|
||||
of passive information collection methodologies which we may use from
|
||||
We reserve the right to monitor your use of the services. As you
|
||||
navigate through the services, certain anonymous information may be
|
||||
passively collected (that is, gathered without your actively providing
|
||||
the information) using various technologies, such as cookies, Internet
|
||||
tags or web beacons, and navigational data collection (log files, server
|
||||
logs, clickstream). The following is a listing and a brief explanation
|
||||
of passive information collection methodologies which we may use from
|
||||
time to time to better understand how the Services are being used.
|
||||
</p>
|
||||
<p>
|
||||
3.1 A "cookie" is a text file that this site sends to your
|
||||
browser in the form of a text file The information generated by the
|
||||
cookie about your use of this site (including your IP address) will be
|
||||
transmitted to and stored. Most browsers automatically accept cookies,
|
||||
but they usually can be modified to decline cookies if you prefer;
|
||||
however, certain features of this site might not work without cookies.
|
||||
3.1 A "cookie" is a text file that this site sends to your
|
||||
browser in the form of a text file The information generated by the
|
||||
cookie about your use of this site (including your IP address) will be
|
||||
transmitted to and stored. Most browsers automatically accept cookies,
|
||||
but they usually can be modified to decline cookies if you prefer;
|
||||
however, certain features of this site might not work without cookies.
|
||||
</p>
|
||||
<p>
|
||||
3.2 "Session" cookies are temporary bits of information that are
|
||||
used to improve navigation, block visitors from providing information
|
||||
where inappropriate (the Services "remembers" previous entries of age or
|
||||
country of origin that were outside the specified parameters and blocks
|
||||
subsequent changes), and collect aggregate statistical information on
|
||||
the Services. They are erased once you exit your Web browser or
|
||||
3.2 "Session" cookies are temporary bits of information that are
|
||||
used to improve navigation, block visitors from providing information
|
||||
where inappropriate (the Services "remembers" previous entries of age or
|
||||
country of origin that were outside the specified parameters and blocks
|
||||
subsequent changes), and collect aggregate statistical information on
|
||||
the Services. They are erased once you exit your Web browser or
|
||||
otherwise turn off your computer.
|
||||
</p>
|
||||
<p>
|
||||
3.3 "Persistent" cookies are more permanent bits of information
|
||||
that are placed on the hard drive of your computer and stay there unless
|
||||
you delete the cookie. Persistent cookies store information on your
|
||||
computer for a number of purposes, such as retrieving certain
|
||||
information you have previously provided, helping to determine what
|
||||
areas of the Services you may find most valuable, and customizing the
|
||||
Services based on your preferences on an ongoing basis. Persistent
|
||||
cookies placed by this site in your computer do not hold personal
|
||||
3.3 "Persistent" cookies are more permanent bits of information
|
||||
that are placed on the hard drive of your computer and stay there unless
|
||||
you delete the cookie. Persistent cookies store information on your
|
||||
computer for a number of purposes, such as retrieving certain
|
||||
information you have previously provided, helping to determine what
|
||||
areas of the Services you may find most valuable, and customizing the
|
||||
Services based on your preferences on an ongoing basis. Persistent
|
||||
cookies placed by this site in your computer do not hold personal
|
||||
information.
|
||||
</p>
|
||||
<p>
|
||||
3.4 You can set your browser to accept all cookies, to reject all
|
||||
cookies, or to notify you whenever a cookie is offered so that you can
|
||||
decide each time whether to accept it. To learn more about cookies and
|
||||
how to specify your preferences, please search for "cookie" in the
|
||||
3.4 You can set your browser to accept all cookies, to reject all
|
||||
cookies, or to notify you whenever a cookie is offered so that you can
|
||||
decide each time whether to accept it. To learn more about cookies and
|
||||
how to specify your preferences, please search for "cookie" in the
|
||||
"Help" portion of your browser.
|
||||
</p>
|
||||
<p>
|
||||
3.5 An Internet Protocol (IP) address is a number assigned to
|
||||
your computer by your Internet service provider so you can access the
|
||||
Internet and is generally considered to be non-personally identifiable
|
||||
information, because in most cases an IP address is dynamic (changing
|
||||
each time you connect to the Internet), rather than static (unique to a
|
||||
particular user's computer). The IP address can be used to diagnose
|
||||
problems with a server, report aggregate information, determine the
|
||||
fastest route for your computer to use in connecting to a site, and
|
||||
3.5 An Internet Protocol (IP) address is a number assigned to
|
||||
your computer by your Internet service provider so you can access the
|
||||
Internet and is generally considered to be non-personally identifiable
|
||||
information, because in most cases an IP address is dynamic (changing
|
||||
each time you connect to the Internet), rather than static (unique to a
|
||||
particular user's computer). The IP address can be used to diagnose
|
||||
problems with a server, report aggregate information, determine the
|
||||
fastest route for your computer to use in connecting to a site, and
|
||||
administer and improve the Services.
|
||||
</p>
|
||||
<p>
|
||||
3.6 "Internet tags" (also known as Web Beacons, single-pixel
|
||||
GIFs, clear GIFs, invisible GIFs, and 1-by-1 GIFs) are smaller than
|
||||
cookies and tell the Web site server information such as the IP address
|
||||
and browser type related to the visitor's computer. Tags may be placed
|
||||
both on online advertisements that bring people to the Services and on
|
||||
different pages of the Services. Such tags indicate how many times a
|
||||
3.6 "Internet tags" (also known as Web Beacons, single-pixel
|
||||
GIFs, clear GIFs, invisible GIFs, and 1-by-1 GIFs) are smaller than
|
||||
cookies and tell the Web site server information such as the IP address
|
||||
and browser type related to the visitor's computer. Tags may be placed
|
||||
both on online advertisements that bring people to the Services and on
|
||||
different pages of the Services. Such tags indicate how many times a
|
||||
page is opened and which information is consulted.
|
||||
</p>
|
||||
<p>
|
||||
3.7 "Navigational data" (log files, server logs, and clickstream
|
||||
data) are used for system management, to improve the content of the
|
||||
Services, market research purposes, and to communicate information to
|
||||
3.7 "Navigational data" (log files, server logs, and clickstream
|
||||
data) are used for system management, to improve the content of the
|
||||
Services, market research purposes, and to communicate information to
|
||||
visitors.
|
||||
</p>
|
||||
<p>
|
||||
<strong>4. Use and Sharing of Anonymous, Passive Information.</strong>
|
||||
<br>
|
||||
The Services may make full use of passively collected anonymous
|
||||
information, including without limitation the right to use such
|
||||
information to provide better service to Service users, customize the
|
||||
Services based on your preferences, compile and analyze statistics and
|
||||
trends, and otherwise administer and improve the Services for your use. We
|
||||
reserve the right to share this anonymous, passive information in
|
||||
The Services may make full use of passively collected anonymous
|
||||
information, including without limitation the right to use such
|
||||
information to provide better service to Service users, customize the
|
||||
Services based on your preferences, compile and analyze statistics and
|
||||
trends, and otherwise administer and improve the Services for your use. We
|
||||
reserve the right to share this anonymous, passive information in
|
||||
aggregated form.
|
||||
</p>
|
||||
<p>
|
||||
<strong>5. 3rd Party Behavioral Ads; Google's AdSense Network.</strong>
|
||||
<br>
|
||||
5.1 We reserve the right to use anonymous, passive information
|
||||
about your visits to this and other websites (not including your name,
|
||||
address, email address or telephone number) for purposes of serving our
|
||||
ads and third party ads that are targeted to your interests ("3rd Party
|
||||
Behavioral Ads"). We reserve the right to share anonymous, passive
|
||||
information collected on the services with third parties for purposes of
|
||||
serving 3rd Party Behavioral Ads. These 3rd Party Behavioral Ads do not
|
||||
identify you personally. Instead, they associate your behavioral data on
|
||||
visited sites with your browser, so that the ads your computer sees on
|
||||
this site are more likely to be relevant to your interests. 3rd Party
|
||||
Behavioral Ads require that that you be served with a cookie containing
|
||||
a tracking code. You may refuse the use of cookies by selecting the
|
||||
appropriate settings on your browser; however, please note that if you
|
||||
5.1 We reserve the right to use anonymous, passive information
|
||||
about your visits to this and other websites (not including your name,
|
||||
address, email address or telephone number) for purposes of serving our
|
||||
ads and third party ads that are targeted to your interests ("3rd Party
|
||||
Behavioral Ads"). We reserve the right to share anonymous, passive
|
||||
information collected on the services with third parties for purposes of
|
||||
serving 3rd Party Behavioral Ads. These 3rd Party Behavioral Ads do not
|
||||
identify you personally. Instead, they associate your behavioral data on
|
||||
visited sites with your browser, so that the ads your computer sees on
|
||||
this site are more likely to be relevant to your interests. 3rd Party
|
||||
Behavioral Ads require that that you be served with a cookie containing
|
||||
a tracking code. You may refuse the use of cookies by selecting the
|
||||
appropriate settings on your browser; however, please note that if you
|
||||
do this you may not be able to use the full functionality of this site.
|
||||
</p>
|
||||
<p>
|
||||
5.2 We reserve the right to participate in Google's AdSense
|
||||
network for purposes of serving 3rd Party Behavioral Ads. Google uses
|
||||
DoubleClick's DART cookie for serving 3rd Party Behavioral Ads over the
|
||||
AdSense network. You may opt out of the use of the DART cookie. For
|
||||
5.2 We reserve the right to participate in Google's AdSense
|
||||
network for purposes of serving 3rd Party Behavioral Ads. Google uses
|
||||
DoubleClick's DART cookie for serving 3rd Party Behavioral Ads over the
|
||||
AdSense network. You may opt out of the use of the DART cookie. For
|
||||
information regarding how to opt out, go to
|
||||
http://www.google.com/privacy_ads.html.
|
||||
</p>
|
||||
<p>
|
||||
<strong>6. Use of 3rd Party Analytics.</strong>
|
||||
<br>
|
||||
We reserve the right to use analytics services provided by
|
||||
third parties. These services use 3rd party cookies to collect
|
||||
anonymous, passive information about your use of this site (see
|
||||
explanation of cookies in Collection of Anonymous, Passive Information
|
||||
above). We use this information for the purpose of evaluating your use
|
||||
of the Services, compiling reports on activity, and providing other
|
||||
services. These web analytics services may also transfer this
|
||||
information to third parties where required to do so by law, or where
|
||||
We reserve the right to use analytics services provided by
|
||||
third parties. These services use 3rd party cookies to collect
|
||||
anonymous, passive information about your use of this site (see
|
||||
explanation of cookies in Collection of Anonymous, Passive Information
|
||||
above). We use this information for the purpose of evaluating your use
|
||||
of the Services, compiling reports on activity, and providing other
|
||||
services. These web analytics services may also transfer this
|
||||
information to third parties where required to do so by law, or where
|
||||
such third parties process the information on the service's behalf.
|
||||
</p>
|
||||
<p>
|
||||
<strong>7. Collection of Personal Information; Categories.</strong>
|
||||
<br>
|
||||
We will ask you for personal information when you sign up for any
|
||||
specific benefit or purpose that requires registration. Personal
|
||||
information that we collect may vary with the each registration, and it
|
||||
may include one or more of the following categories: name, physical
|
||||
address, an email address, phone number, and credit card information
|
||||
including credit card number, expiration date, and billing address,
|
||||
emergency contact information, current medications, allergies, medical
|
||||
We will ask you for personal information when you sign up for any
|
||||
specific benefit or purpose that requires registration. Personal
|
||||
information that we collect may vary with the each registration, and it
|
||||
may include one or more of the following categories: name, physical
|
||||
address, an email address, phone number, and credit card information
|
||||
including credit card number, expiration date, and billing address,
|
||||
emergency contact information, current medications, allergies, medical
|
||||
insurance information.
|
||||
</p>
|
||||
<p>
|
||||
<strong> 8. Use And Sharing of Personal Information: General
|
||||
<strong> 8. Use And Sharing of Personal Information: General
|
||||
Policy And Exceptions. </strong>
|
||||
<br>
|
||||
Our general policy is that we will use your personal information,
|
||||
including combining your personal information with passive information
|
||||
collected from this site, only for: the performance of the services or
|
||||
transaction for which it was given, our private, internal reporting for
|
||||
this site, and security assessments for this site, and we will not
|
||||
share, sell, or rent your personal information to others. The only
|
||||
exceptions to this general policy: (i) are described in the subsections
|
||||
Our general policy is that we will use your personal information,
|
||||
including combining your personal information with passive information
|
||||
collected from this site, only for: the performance of the services or
|
||||
transaction for which it was given, our private, internal reporting for
|
||||
this site, and security assessments for this site, and we will not
|
||||
share, sell, or rent your personal information to others. The only
|
||||
exceptions to this general policy: (i) are described in the subsections
|
||||
below, and (ii) if you explicitly approve through our site.
|
||||
</p>
|
||||
<p>
|
||||
8.1 Affiliates And Service Providers. We reserve the right to
|
||||
provide such information to our affiliates or subsidiaries, or trusted
|
||||
service providers for the purpose of hosting our servers or processing
|
||||
or archiving personal information for us. We require that these parties
|
||||
agree to privacy and security safeguards for this information that are
|
||||
8.1 Affiliates And Service Providers. We reserve the right to
|
||||
provide such information to our affiliates or subsidiaries, or trusted
|
||||
service providers for the purpose of hosting our servers or processing
|
||||
or archiving personal information for us. We require that these parties
|
||||
agree to privacy and security safeguards for this information that are
|
||||
consistent with this Privacy Policy.
|
||||
</p>
|
||||
<p>
|
||||
8.2 Acquisition; Bankruptcy. In the event that we are acquired by
|
||||
or merged with a third party entity, we reserve the right to transfer
|
||||
such information as part of such merger, acquisition, sale, or other
|
||||
change of control. In the unlikely event of our bankruptcy, insolvency,
|
||||
reorganization, receivership, or assignment for the benefit of
|
||||
creditors, or the application of laws or equitable principles affecting
|
||||
creditors' rights generally, we reserve the right to transfer such
|
||||
8.2 Acquisition; Bankruptcy. In the event that we are acquired by
|
||||
or merged with a third party entity, we reserve the right to transfer
|
||||
such information as part of such merger, acquisition, sale, or other
|
||||
change of control. In the unlikely event of our bankruptcy, insolvency,
|
||||
reorganization, receivership, or assignment for the benefit of
|
||||
creditors, or the application of laws or equitable principles affecting
|
||||
creditors' rights generally, we reserve the right to transfer such
|
||||
information to protect our rights or as required by law.
|
||||
</p>
|
||||
<p>
|
||||
8.3 Enforcement; Legal Process. We reserve the right to transfer
|
||||
such information if we have a good faith belief that access, use,
|
||||
preservation or disclosure of such information is reasonably necessary
|
||||
(i) to satisfy any applicable law, regulation, legal process or
|
||||
enforceable governmental request, or (ii) to investigate or enforce
|
||||
8.3 Enforcement; Legal Process. We reserve the right to transfer
|
||||
such information if we have a good faith belief that access, use,
|
||||
preservation or disclosure of such information is reasonably necessary
|
||||
(i) to satisfy any applicable law, regulation, legal process or
|
||||
enforceable governmental request, or (ii) to investigate or enforce
|
||||
violations of our rights or the security of this site.
|
||||
</p>
|
||||
<p>
|
||||
8.4 Miscellaneous. We reserve the right to share personal
|
||||
information with the following additional parties: online organizers
|
||||
using our tools and resellers of our products and services from whose
|
||||
site the sale originated (even though the sale originates at site of the
|
||||
reseller, registration and collection of personal information occurs at
|
||||
8.4 Miscellaneous. We reserve the right to share personal
|
||||
information with the following additional parties: online organizers
|
||||
using our tools and resellers of our products and services from whose
|
||||
site the sale originated (even though the sale originates at site of the
|
||||
reseller, registration and collection of personal information occurs at
|
||||
this site).
|
||||
</p>
|
||||
<p>
|
||||
<strong> 9. Onward Transfer of Personal Information Outside Your
|
||||
<strong> 9. Onward Transfer of Personal Information Outside Your
|
||||
Country of Residence. </strong>
|
||||
<br>
|
||||
Any personal information which we may collect on this site will
|
||||
be stored and processed in our servers located only in the United
|
||||
States. By using this site, if you reside outside the United States, you
|
||||
consent to the transfer of personal information outside your country of
|
||||
Any personal information which we may collect on this site will
|
||||
be stored and processed in our servers located only in the United
|
||||
States. By using this site, if you reside outside the United States, you
|
||||
consent to the transfer of personal information outside your country of
|
||||
residence to the United States.
|
||||
</p>
|
||||
<p>
|
||||
<strong>10. Security of Personal Information.</strong>
|
||||
<br>
|
||||
We follow reasonable and appropriate industry standards to
|
||||
protect your personal information and data. Unfortunately, no data
|
||||
transmission over the Internet or method of data storage can be
|
||||
guaranteed 100% secure. Therefore, while we strive to protect your
|
||||
personal information by following generally accepted industry standards,
|
||||
we cannot ensure or warrant the absolute security of any information you
|
||||
We follow reasonable and appropriate industry standards to
|
||||
protect your personal information and data. Unfortunately, no data
|
||||
transmission over the Internet or method of data storage can be
|
||||
guaranteed 100% secure. Therefore, while we strive to protect your
|
||||
personal information by following generally accepted industry standards,
|
||||
we cannot ensure or warrant the absolute security of any information you
|
||||
transmit to us or archive at this site.
|
||||
</p>
|
||||
<p>
|
||||
<strong>11. Changing And Updating Personal Information.</strong>
|
||||
<br>
|
||||
Upon request, we will permit you to request or make changes or
|
||||
updates to your personal information for legitimate purposes. We request
|
||||
identification prior to approving such requests. We reserve the right to
|
||||
decline any requests that are unreasonably repetitive or systematic,
|
||||
require unreasonable time or effort of our technical or administrative
|
||||
personnel, or undermine the privacy rights of others. We reserve the
|
||||
right to permit you to access your personal information in any account
|
||||
you establish with this site for purposes of making your own changes or
|
||||
updates, and in such case, instructions for making such changes or
|
||||
Upon request, we will permit you to request or make changes or
|
||||
updates to your personal information for legitimate purposes. We request
|
||||
identification prior to approving such requests. We reserve the right to
|
||||
decline any requests that are unreasonably repetitive or systematic,
|
||||
require unreasonable time or effort of our technical or administrative
|
||||
personnel, or undermine the privacy rights of others. We reserve the
|
||||
right to permit you to access your personal information in any account
|
||||
you establish with this site for purposes of making your own changes or
|
||||
updates, and in such case, instructions for making such changes or
|
||||
updates will be provided where necessary.
|
||||
</p>
|
||||
<p>
|
||||
<strong>12. Email From This Site; Opt-Out Rights.</strong>
|
||||
<br>
|
||||
If you supply us with your e-mail address you may receive
|
||||
periodic messages from us with information specific to the Services and
|
||||
required for the normal functioning of the Services as well as for new
|
||||
products or services or upcoming events. If you prefer not to receive
|
||||
periodic email messages, you may opt-out by following the instructions
|
||||
If you supply us with your e-mail address you may receive
|
||||
periodic messages from us with information specific to the Services and
|
||||
required for the normal functioning of the Services as well as for new
|
||||
products or services or upcoming events. If you prefer not to receive
|
||||
periodic email messages, you may opt-out by following the instructions
|
||||
on the email.
|
||||
</p>
|
||||
<p>
|
||||
<strong>13. Children's Online Policy.</strong>
|
||||
<br>
|
||||
We are committed to preserving online privacy for all of its
|
||||
website visitors, including children. This site is a general audience
|
||||
site. Consistent with the Children's Online Privacy Protection Act
|
||||
(COPPA), we will not knowingly collect any information from, or sell to,
|
||||
children under the age of 13. If you are a parent or guardian who has
|
||||
discovered that your child under the age of 13 has submitted his or her
|
||||
personally identifiable information without your permission or consent,
|
||||
we will remove the information from our active list, at your request. To
|
||||
request the removal of your child's information, please send contact our
|
||||
site as provided below under “Contact Us”, and be sure to include in
|
||||
We are committed to preserving online privacy for all of its
|
||||
website visitors, including children. This site is a general audience
|
||||
site. Consistent with the Children's Online Privacy Protection Act
|
||||
(COPPA), we will not knowingly collect any information from, or sell to,
|
||||
children under the age of 13. If you are a parent or guardian who has
|
||||
discovered that your child under the age of 13 has submitted his or her
|
||||
personally identifiable information without your permission or consent,
|
||||
we will remove the information from our active list, at your request. To
|
||||
request the removal of your child's information, please send contact our
|
||||
site as provided below under “Contact Us”, and be sure to include in
|
||||
your message the same login information that your child submitted.
|
||||
</p>
|
||||
<p>
|
||||
<strong> 14. Email And Other Messages Through This Site; ECPA
|
||||
<strong> 14. Email And Other Messages Through This Site; ECPA
|
||||
Notice. </strong>
|
||||
<br>
|
||||
This site treats email messages and other electronic messages
|
||||
that are sent through this site and not viewable by others as
|
||||
confidential and private, except as required by law, including without
|
||||
limitation, the Electronic Communications Privacy Act of 1986, 18 U.S.C.
|
||||
Sections 2701-2711 (the "ECPA"). The ECPA permits this site's limited
|
||||
ability to intercept and/or disclose electronic messages, for example
|
||||
(i) as necessary to operate our system or to protect our rights or
|
||||
property, (ii) upon legal demand (court orders, warrants, subpoenas), or
|
||||
(iii) where we receive information inadvertently which appears to
|
||||
pertain to the commission of a crime. This site is not considered a
|
||||
This site treats email messages and other electronic messages
|
||||
that are sent through this site and not viewable by others as
|
||||
confidential and private, except as required by law, including without
|
||||
limitation, the Electronic Communications Privacy Act of 1986, 18 U.S.C.
|
||||
Sections 2701-2711 (the "ECPA"). The ECPA permits this site's limited
|
||||
ability to intercept and/or disclose electronic messages, for example
|
||||
(i) as necessary to operate our system or to protect our rights or
|
||||
property, (ii) upon legal demand (court orders, warrants, subpoenas), or
|
||||
(iii) where we receive information inadvertently which appears to
|
||||
pertain to the commission of a crime. This site is not considered a
|
||||
"secure communications medium" under the ECPA.
|
||||
</p>
|
||||
<p>
|
||||
<strong>15. Contact Us.</strong>
|
||||
<br>
|
||||
If you have any questions regarding this Privacy Policy, please
|
||||
If you have any questions regarding this Privacy Policy, please
|
||||
contact the owner and operator of this website business:
|
||||
</p>
|
||||
<address>
|
||||
|
|
|
|||
770
views/terms.html
770
views/terms.html
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue