";
+ }
+ });
+ $('.allocate-stats').popover('destroy');
+ var tour = new Tour({
// onEnd: function(){
// User.set({'flags.showTour': false});
// }
- });
- tourSteps.forEach(function(step) {
- tour.addStep(_.defaults(step, {html: true}));
- });
- tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load
- //tour.start(true);
- };
+ });
+ tourSteps.forEach(function(step) {
+ tour.addStep(_.defaults(step, {html: true}));
+ });
+ tour.restart(); // Tour doesn't quite mesh with our handling of flags.showTour, just restart it on page load
+ //tour.start(true);
+ };
- return {
- initTour: initTour,
- classesTour: classesTour
- };
-
- }
-]);
+ return {
+ initTour: initTour,
+ classesTour: classesTour
+ };
+}]);
diff --git a/public/js/services/memberServices.js b/public/js/services/memberServices.js
index bb3a4c5ac5..53a1c1e998 100644
--- a/public/js/services/memberServices.js
+++ b/public/js/services/memberServices.js
@@ -4,85 +4,84 @@
* Services that persists and retrieves user from localStorage.
*/
-angular.module('memberServices', ['ngResource', 'sharedServices']).
- factory('Members', ['$rootScope', 'Shared', 'ApiUrlService', '$resource',
- function($rootScope, Shared, ApiUrlService, $resource) {
- var members = {};
- var Member = $resource(ApiUrlService.get() + '/api/v2/members/:uid', {uid:'@_id'});
- var memberServices = {
+angular.module('habitrpg').factory('Members',
+['$rootScope', 'Shared', 'ApiUrl', '$resource',
+function($rootScope, Shared, ApiUrl, $resource) {
+ var members = {};
+ var Member = $resource(ApiUrl.get() + '/api/v2/members/:uid', {uid:'@_id'});
+ var memberServices = {
- Member: Member,
+ Member: Member,
- members: members,
+ members: members,
- /**
- * Allows us to lazy-load party / group / public members throughout the application.
- * @param obj - either a group or an individual member. If it's a group, we lazy-load all of its members.
- */
- populate: function(obj){
+ /**
+ * Allows us to lazy-load party / group / public members throughout the application.
+ * @param obj - either a group or an individual member. If it's a group, we lazy-load all of its members.
+ */
+ populate: function(obj){
- function populateGroup(group){
- _.each(group.members, function(member){
- // meaning `populate('members')` wasn't run on the server, so we're getting the "in-database" form of
- // the members array, which is just a list of IDs - not the populated objects
- if (_.isString(member)) return;
+ function populateGroup(group){
+ _.each(group.members, function(member){
+ // meaning `populate('members')` wasn't run on the server, so we're getting the "in-database" form of
+ // the members array, which is just a list of IDs - not the populated objects
+ if (_.isString(member)) return;
- // lazy-load
- members[member._id] = member;
- })
- }
+ // lazy-load
+ members[member._id] = member;
+ })
+ }
- // Array of groups
- if (_.isArray(obj)) {
- if (obj[0] && obj[0].members) {
- _.each(obj, function(group){
- populateGroup(group);
- })
- }
-
- // Individual Group
- } else if (obj.members)
- populateGroup(obj);
-
- // individual Member
- if (obj._id) {
- members[obj._id] = obj;
- }
- },
-
- selectedMember: undefined,
-
- /**
- * Once users are populated, we fetch them throughout the application (eg, modals). This
- * either gets them or fetches if not available
- * @param uid
- */
- selectMember: function(uid, cb) {
- var self = this;
- // Fetch from cache if we can. For guild members, only their uname will have been fetched on initial load,
- // check if they have full fields (eg, check profile.items and an item inside
- // because sometimes profile.items exists but it's empty like when user is fetched for party
- // and then for guild)
- // and if not, fetch them
- if (members[uid] && members[uid].items && members[uid].items.weapon) {
- Shared.wrap(members[uid],false);
- self.selectedMember = members[uid];
- cb();
- } else {
- Member.get({uid: uid}, function(member){
- self.populate(member); // lazy load for later
- Shared.wrap(member,false);
- self.selectedMember = members[member._id];
- cb();
- });
- }
- }
+ // Array of groups
+ if (_.isArray(obj)) {
+ if (obj[0] && obj[0].members) {
+ _.each(obj, function(group){
+ populateGroup(group);
+ })
}
- $rootScope.$on('userUpdated', function(event, user){
- memberServices.populate(user);
- })
+ // Individual Group
+ } else if (obj.members)
+ populateGroup(obj);
- return memberServices;
+ // individual Member
+ if (obj._id) {
+ members[obj._id] = obj;
}
-]);
+ },
+
+ selectedMember: undefined,
+
+ /**
+ * Once users are populated, we fetch them throughout the application (eg, modals). This
+ * either gets them or fetches if not available
+ * @param uid
+ */
+ selectMember: function(uid, cb) {
+ var self = this;
+ // Fetch from cache if we can. For guild members, only their uname will have been fetched on initial load,
+ // check if they have full fields (eg, check profile.items and an item inside
+ // because sometimes profile.items exists but it's empty like when user is fetched for party
+ // and then for guild)
+ // and if not, fetch them
+ if (members[uid] && members[uid].items && members[uid].items.weapon) {
+ Shared.wrap(members[uid],false);
+ self.selectedMember = members[uid];
+ cb();
+ } else {
+ Member.get({uid: uid}, function(member){
+ self.populate(member); // lazy load for later
+ Shared.wrap(member,false);
+ self.selectedMember = members[member._id];
+ cb();
+ });
+ }
+ }
+ }
+
+ $rootScope.$on('userUpdated', function(event, user){
+ memberServices.populate(user);
+ })
+
+ return memberServices;
+}]);
diff --git a/public/js/services/notificationServices.js b/public/js/services/notificationServices.js
index da2cd51afa..a0dde36fbb 100644
--- a/public/js/services/notificationServices.js
+++ b/public/js/services/notificationServices.js
@@ -1,85 +1,84 @@
/**
Set up "+1 Exp", "Level Up", etc notifications
*/
-angular.module("notificationServices", [])
- .factory("Notification", [function() {
- var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60};
- function notify(html, type, icon) {
- var notice = $.pnotify({
- type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
- text: html,
- opacity: 1,
- addclass: 'alert-' + type,
- delay: 7000,
- hide: (type == 'error') ? false : true,
- mouse_reset: false,
- width: "250px",
- stack: stack_topright,
- icon: icon || false
- }).click(function() { notice.pnotify_remove() });
- };
+angular.module("habitrpg").factory("Notification",
+[function() {
+ var stack_topright = {"dir1": "down", "dir2": "left", "spacing1": 15, "spacing2": 15, "firstpos1": 60};
+ function notify(html, type, icon) {
+ var notice = $.pnotify({
+ type: type || 'warning', //('info', 'text', 'warning', 'success', 'gp', 'xp', 'hp', 'lvl', 'death', 'mp', 'crit')
+ text: html,
+ opacity: 1,
+ addclass: 'alert-' + type,
+ delay: 7000,
+ hide: (type == 'error') ? false : true,
+ mouse_reset: false,
+ width: "250px",
+ stack: stack_topright,
+ icon: icon || false
+ }).click(function() { notice.pnotify_remove() });
+ };
- /**
- Show "+ 5 {gold_coin} 3 {silver_coin}"
- */
- function coins(money) {
- var absolute, gold, silver;
- absolute = Math.abs(money);
- gold = Math.floor(absolute);
- silver = Math.floor((absolute - gold) * 100);
- if (gold && silver > 0) {
- return "" + gold + " " + silver + " ";
- } else if (gold > 0) {
- return "" + gold + " ";
- } else if (silver > 0) {
- return "" + silver + " ";
- }
- };
-
- var sign = function(number){
- return number?number<0?'-':'+':'+';
+ /**
+ Show "+ 5 {gold_coin} 3 {silver_coin}"
+ */
+ function coins(money) {
+ var absolute, gold, silver;
+ absolute = Math.abs(money);
+ gold = Math.floor(absolute);
+ silver = Math.floor((absolute - gold) * 100);
+ if (gold && silver > 0) {
+ return "" + gold + " " + silver + " ";
+ } else if (gold > 0) {
+ return "" + gold + " ";
+ } else if (silver > 0) {
+ return "" + silver + " ";
}
+ };
- var round = function(number){
- return Math.abs(number.toFixed(1));
- }
-
- return {
- coins: coins,
- hp: function(val) {
- // don't show notifications if user dead
- notify(sign(val) + " " + round(val) + " " + window.env.t('hp'), 'hp', 'glyphicon glyphicon-heart');
- },
- exp: function(val) {
- if (val < -50) return; // don't show when they level up (resetting their exp)
- notify(sign(val) + " " + round(val) + " " + window.env.t('xp'), 'xp', 'glyphicon glyphicon-star');
- },
- gp: function(val, bonus) {
- notify(sign(val) + " " + coins(val - bonus), 'gp');
- },
- text: function(val){
- if (val) {
- notify(val, 'info');
- }
- },
- lvl: function(){
- notify(window.env.t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up');
- },
- error: function(error){
- notify(error, "danger", 'glyphicon glyphicon-exclamation-sign');
- },
- mp: function(val) {
- notify(sign(val) + " " + round(val) + " " + window.env.t('mp'), 'mp', 'glyphicon glyphicon-fire');
- },
- crit: function(val) {
- notify(window.env.t('critBonus') + Math.round(val) + "%", 'crit', 'glyphicon glyphicon-certificate');
- },
- streak: function(val) {
- notify(window.env.t('streakName') + ': ' + val, 'streak', 'glyphicon glyphicon-repeat');
- },
- drop: function(val) {
- notify(val, 'drop', 'glyphicon glyphicon-gift');
- }
- };
+ var sign = function(number){
+ return number?number<0?'-':'+':'+';
}
-]);
+
+ var round = function(number){
+ return Math.abs(number.toFixed(1));
+ }
+
+ return {
+ coins: coins,
+ hp: function(val) {
+ // don't show notifications if user dead
+ notify(sign(val) + " " + round(val) + " " + window.env.t('hp'), 'hp', 'glyphicon glyphicon-heart');
+ },
+ exp: function(val) {
+ if (val < -50) return; // don't show when they level up (resetting their exp)
+ notify(sign(val) + " " + round(val) + " " + window.env.t('xp'), 'xp', 'glyphicon glyphicon-star');
+ },
+ gp: function(val, bonus) {
+ notify(sign(val) + " " + coins(val - bonus), 'gp');
+ },
+ text: function(val){
+ if (val) {
+ notify(val, 'info');
+ }
+ },
+ lvl: function(){
+ notify(window.env.t('levelUp'), 'lvl', 'glyphicon glyphicon-chevron-up');
+ },
+ error: function(error){
+ notify(error, "danger", 'glyphicon glyphicon-exclamation-sign');
+ },
+ mp: function(val) {
+ notify(sign(val) + " " + round(val) + " " + window.env.t('mp'), 'mp', 'glyphicon glyphicon-fire');
+ },
+ crit: function(val) {
+ notify(window.env.t('critBonus') + Math.round(val) + "%", 'crit', 'glyphicon glyphicon-certificate');
+ },
+ streak: function(val) {
+ notify(window.env.t('streakName') + ': ' + val, 'streak', 'glyphicon glyphicon-repeat');
+ },
+ drop: function(val) {
+ notify(val, 'drop', 'glyphicon glyphicon-gift');
+ }
+ };
+}]);
diff --git a/public/js/services/paymentServices.js b/public/js/services/paymentServices.js
index e0a059c07c..bc677b9155 100644
--- a/public/js/services/paymentServices.js
+++ b/public/js/services/paymentServices.js
@@ -1,6 +1,6 @@
'use strict';
-angular.module('paymentServices',[]).factory('Payments',
+angular.module('habitrpg').factory('Payments',
['$rootScope', 'User', '$http', 'Content',
function($rootScope, User, $http, Content) {
var Payments = {};
diff --git a/public/js/services/sharedServices.js b/public/js/services/sharedServices.js
index 31ef0fc120..5694d56fb3 100644
--- a/public/js/services/sharedServices.js
+++ b/public/js/services/sharedServices.js
@@ -4,12 +4,10 @@
* Services that expose habitrpg-shared
*/
-angular.module('sharedServices', []).
- factory('Shared', [function () {
- return window.habitrpgShared;
- }
-]).
- factory('Content', ['Shared', function (Shared) {
- return Shared.content;
- }
-]);
+angular.module('habitrpg')
+.factory('Shared', [function () {
+ return window.habitrpgShared;
+}])
+.factory('Content', ['Shared', function (Shared) {
+ return Shared.content;
+}]);
diff --git a/public/js/static.js b/public/js/static.js
index 860dbf5587..3fdd1e6f2d 100644
--- a/public/js/static.js
+++ b/public/js/static.js
@@ -1,13 +1,12 @@
"use strict";
-window.habitrpgStatic = angular.module('habitrpgStatic', ['notificationServices', 'userServices', 'chieffancypants.loadingBar', 'authCtrl', 'ui.bootstrap'])
+window.habitrpg = angular.module('habitrpg', ['chieffancypants.loadingBar', 'ui.bootstrap'])
.constant("API_URL", "")
.constant("STORAGE_USER_ID", 'habitrpg-user')
.constant("STORAGE_SETTINGS_ID", 'habit-mobile-settings')
.constant("MOBILE_APP", false)
.controller("RootCtrl", ['$scope', '$location', '$modal', '$http', function($scope, $location, $modal, $http){
- // must be #?memberId=xx, see https://github.com/angular/angular.js/issues/7239
var memberId = $location.search()['memberId'];
if (memberId) {
$http.get('/api/v2/members/'+memberId).success(function(data, status, headers, config){
diff --git a/public/manifest.json b/public/manifest.json
index dd4e9abe2a..15deca809c 100644
--- a/public/manifest.json
+++ b/public/manifest.json
@@ -37,8 +37,8 @@
"js/env.js",
"js/app.js",
+ "bower_components/habitrpg-shared/script/config.js",
"js/services/sharedServices.js",
- "js/services/authServices.js",
"js/services/notificationServices.js",
"bower_components/habitrpg-shared/script/userServices.js",
"bower_components/habitrpg-shared/script/directives.js",
diff --git a/src/controllers/groups.js b/src/controllers/groups.js
index 3891c70618..4c7b37c919 100644
--- a/src/controllers/groups.js
+++ b/src/controllers/groups.js
@@ -211,8 +211,23 @@ api.attachGroup = function(req, res, next) {
}
api.getChat = function(req, res, next) {
- return res.json(res.locals.group.chat);
-}
+ // TODO: This code is duplicated from api.get - pull it out into a function to remove duplication.
+ var user = res.locals.user;
+ var gid = req.params.gid;
+ var q = (gid == 'party')
+ ? Group.findOne({type: 'party', members: {$in:[user._id]}})
+ : Group.findOne({$or:[
+ {_id:gid, privacy:'public'},
+ {_id:gid, privacy:'private', members: {$in:[user._id]}}
+ ]});
+ populateQuery(gid, q);
+ q.exec(function(err, group){
+ if (err) return next(err);
+ if (!group && gid!=='party') return res.json(404,{err: "Group not found or you don't have access."});
+ res.json(res.locals.group.chat);
+ gid = null;
+ });
+};
/**
* TODO make this it's own ngResource so we don't have to send down group data with each chat post
@@ -645,10 +660,12 @@ questStart = function(req, res, next) {
})
group.quest.active = true;
- if (quest.boss)
+ if (quest.boss) {
group.quest.progress.hp = quest.boss.hp;
- else
+ if (quest.boss.rage) group.quest.progress.rage = 0;
+ } else {
group.quest.progress.collect = collected;
+ }
group.quest.members = questMembers;
group.markModified('quest'); // members & progress.collect are both Mixed types
parallel.push(function(cb2){group.save(cb2)});
diff --git a/src/middleware.js b/src/middleware.js
index 56e72bda7e..189275443b 100644
--- a/src/middleware.js
+++ b/src/middleware.js
@@ -79,7 +79,7 @@ module.exports.errorHandler = function(err, req, res, next) {
error: "Uncaught error",
stack: (err.stack || err.message || err),
body: req.body, headers: req.header,
- auth: (req.headers['x-api-user'] + ' | ' + req.headers['x-api-key']),
+ auth: req.headers['x-api-user'],
originalUrl: req.originalUrl
});
var message = err.message ? err.message : err;
diff --git a/src/models/group.js b/src/models/group.js
index 5b5243b71f..516267e469 100644
--- a/src/models/group.js
+++ b/src/models/group.js
@@ -280,6 +280,7 @@ GroupSchema.statics.tavernBoss = function(user,progress) {
tavern.quest.extra.worldDmg.recent = scene;
tavern.markModified('quest.extra.worldDmg');
tavern.quest.progress.rage = 0;
+ tavern.quest.progress.hp += (quest.boss.rage.healing * tavern.quest.progress.hp);
}
}
tavern.save(cb);
@@ -300,8 +301,17 @@ GroupSchema.statics.bossQuest = function(user, progress, cb) {
var down = progress.down * quest.boss.str; // multiply by boss strength
group.quest.progress.hp -= progress.up;
- group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage, " + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`");
+ group.sendChat("`" + user.profile.name + " attacks " + quest.boss.name('en') + " for " + (progress.up.toFixed(1)) + " damage, " + quest.boss.name('en') + " attacks party for " + Math.abs(down).toFixed(1) + " damage.`"); //TODO Create a party preferred language option so emits like this can be localized
+ // If boss has Rage, increment Rage as well
+ if (quest.boss.rage) {
+ group.quest.progress.rage += Math.abs(down);
+ if (group.quest.progress.rage >= quest.boss.rage.value) {
+ group.sendChat(quest.boss.rage.effect('en'));
+ group.quest.progress.rage = 0;
+ if (quest.boss.rage.healing) group.quest.progress.hp += (group.quest.progress.hp * quest.boss.rage.healing); //TODO To make Rage effects more expandable, let's turn these into functions in quest.boss.rage
+ }
+ }
// Everyone takes damage
var series = [
function(cb2){
diff --git a/src/models/user.js b/src/models/user.js
index 132f5f76cf..48eb31ecbc 100644
--- a/src/models/user.js
+++ b/src/models/user.js
@@ -250,6 +250,7 @@ var UserSchema = new Schema({
party: {
// id // FIXME can we use a populated doc instead of fetching party separate from user?
order: {type:String, 'default':'level'},
+ orderAscending: {type:String, 'default':'ascending'},
quest: {
key: String,
progress: {
diff --git a/src/routes/apiv2.coffee b/src/routes/apiv2.coffee
index 6c93e3ba42..39d4523a75 100644
--- a/src/routes/apiv2.coffee
+++ b/src/routes/apiv2.coffee
@@ -607,7 +607,7 @@ module.exports = (swagger, v2) ->
path: '/members/{uuid}'
description: "Get a member."
parameters: [path('uuid','Member ID','string')]
- middleware: [auth.auth, i18n.getUserLanguage]
+ middleware: [i18n.getUserLanguage] # removed auth.auth, so anon users can view shared avatars
action: members.getMember
"/members/{uuid}/message":
spec:
diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade
index cfdebc7513..f5e760f30e 100644
--- a/views/options/inventory/inventory.jade
+++ b/views/options/inventory/inventory.jade
@@ -183,6 +183,11 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
p
| {{::Content.eggs.#{egg}.value}}
span.Pet_Currency_Gem1x.inline-gems
+ div(ng-show='(user.achievements.quests.trex + user.achievements.quests.trex_undead) > 1')
+ button.customize-option(popover='{{::Content.eggs.TRex.notes()}}', popover-title!=env.t("egg", {eggType: "{{Content.eggs.TRex.text()}}"}), popover-trigger='mouseenter', popover-placement='top', ng-click='purchase("eggs", Content.eggs.TRex)', class='Pet_Egg_TRex')
+ p
+ | {{::Content.eggs.TRex.value}}
+ span.Pet_Currency_Gem1x.inline-gems
li.customize-menu
menu.pets-menu(label=env.t('hatchingPotions'))
diff --git a/views/options/profile.jade b/views/options/profile.jade
index 59c48b0d6e..19a2ba48d1 100644
--- a/views/options/profile.jade
+++ b/views/options/profile.jade
@@ -213,6 +213,12 @@ script(id='partials/options.profile.profile.html', type='text/ng-template')
button.btn.btn-default(ng-click='_editing.profile = true', ng-show='!_editing.profile')= env.t('edit')
h4=env.t('displayName')
span(ng-show='profile.profile.name') {{profile.profile.name}}
+ p
+ small.muted=env.t('displayNameDescription1')
+ |
+ a(href='/#/options/settings/settings')=env.t('displayNameDescription2')
+ |
+ =env.t('displayNameDescription3')
span.muted(ng-hide='profile.profile.name') -
=env.t('none')
| -
diff --git a/views/options/settings.jade b/views/options/settings.jade
index 3fb026d635..e0b150c850 100644
--- a/views/options/settings.jade
+++ b/views/options/settings.jade
@@ -75,7 +75,7 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
h5.hint(popover=env.t('clockInfo'), popover-trigger='mouseenter')=env.t('customDayStart')
.form-group
.input-group
- input.form-control(type='number', min='0', max='23', ng-model='user.preferences.dayStart', ng-change='saveDayStart()')
+ input.form-control(type='number', min='0', max='23', ng-model='user.preferences.dayStart', ng-blur='saveDayStart()')
span.input-group-addon= ':00 (' + env.t('24HrClock') + ')'
small
=env.t('subWarning1')
@@ -88,10 +88,26 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
.panel-heading
span Registration
.panel-body
- p(ng-if='user.auth.facebook.id') Registered with Facebook
+ p(ng-if='user.auth.facebook.id')=env.t('registeredWithFb')
div(ng-if='user.auth.local.username')
- p Username: {{user.auth.local.username}}
- p Email: {{user.auth.local.email}}
+ p=env.t('username')
+ |: {{user.auth.local.username}}
+ p
+ small.muted
+ =env.t('loginNameDescription1')
+ |
+ a(href='/#/options/profile/profile')=env.t('loginNameDescription2')
+ |
+ =env.t('loginNameDescription3')
+ p=env.t('email')
+ |: {{::user.auth.local.email}}
+ p
+ small.muted
+ =env.t('emailChange1')
+ |
+ a(href='mailto:admin@habitrpg.com')=env.t('emailChange2')
+ |
+ =env.t('emailChange3')
hr
h5=env.t('changeUsername')
form(ng-submit='changeUsername(changeUser)', ng-show='user.auth.local')
diff --git a/views/options/social/group.jade b/views/options/social/group.jade
index 18ce8afe06..5d50ec3596 100644
--- a/views/options/social/group.jade
+++ b/views/options/social/group.jade
@@ -58,13 +58,20 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
button.pull-right.btn.btn-primary(ng-click="openModal('invite-friends', {controller:'GroupsCtrl'})", ng-if='::group.type=="party"') Invite Friends
.panel-body.modal-fixed-height
div.form-group(ng-if='::group.type=="party"')
- =env.t('partyList')
+ p=env.t('partyList')
select.form-control#partyOrder(
ng-model='user.party.order',
ng-controller='ChatCtrl',
ng-options='k as v for (k , v) in partyOrderChoices',
ng-change='set({"party.order": user.party.order})'
)
+ |
+ select.form-control#partyOrderAscending(
+ ng-model='user.party.orderAscending',
+ ng-controller='ChatCtrl',
+ ng-options='k as v for (k , v) in partyOrderAscendingChoices',
+ ng-change='set({"party.orderAscending": user.party.orderAscending})'
+ )
table.table.table-striped(bindonce='group')
tr(ng-repeat='member in group.members track by member._id')
td.media
diff --git a/views/shared/new-stuff.jade b/views/shared/new-stuff.jade
index 4ab302be4d..81c719a57b 100644
--- a/views/shared/new-stuff.jade
+++ b/views/shared/new-stuff.jade
@@ -1,25 +1,43 @@
-h5 WORLD BOSS BATTLE BEGINS!
+h5 TYRANNOSAUR PET QUEST, SPREAD THE WORD CHALLENGE, AND FIRST STRESS STRIKE!
tr
td
- h5 World Boss: The Abominable Stressbeast!
- .quest_stressbeast.pull-right
- p A new World Boss has appeared in the Tavern! All of the completed Dailies and To-Dos of Habiticans damage the World Boss. Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC.
- p A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied. Read on for the details!
+ h5 Tyrannosaur Pet Quest
+ p In the Market there are now two new pet quests: King of the Dinosaurs and The Dinosaur Unearthed! They both give out the same rewards, including pet Tyrannosaur eggs. The difference is that "King of the Dinosaurs" is a normal pet quest, like all the others, whereas "The Dinosaur Unearthed" has less HP - but also a Rage bar (a la World Bosses) that allows it to heal if you skip too many of your Dailies. Both bosses still attack your party based on how many Dailies are incomplete. Users will be able to buy Tyrannosaur eggs after defeating either boss twice or both bosses once.
+ p Have fun!
+ p.small.muted by Baconsaur, Urse, Lemoness, and SabreCat
tr
td
- h5 Winter Plot-Line: The Abominable Stressbeast Attacks!
- p The first thing we hear are the footsteps, slower and more thundering than the stampede. One by one, Habiticans look outside their doors, and words fail us.
- p We've all seen Stressbeasts before, of course - tiny vicious creatures that attack during difficult times. But this? This towers taller than the buildings, with paws that could crush a dragon with ease. Frost swings from its stinking fur, and as it roars, the icy blast rips the roofs off our houses. A monster of this magnitude has never been mentioned outside of distant legend.
- p "Beware, Habiticans!" SabreCat cries. "Barricade yourselves indoors - this is the Abominable Stressbeast itself!"
- p "That thing must be made of centuries of stress!" Kiwibot says, locking the Tavern door tightly and shuttering the windows.
- p "The Stoïkalm Steppes," Lemoness says, face grim. "All this time, we thought they were placid and untroubled, but they must have been secretly hiding their stress somewhere. Over generations, it grew into this, and now it's broken free and attacked them - and us!"
- p There's only one way to drive away a Stressbeast, Abominable or otherwise, and that's to attack it with completed Dailies and To-Dos! Let's all band together and fight off this fearsome foe - but be sure not to slack on your tasks, or our undone Dailies may enrage it so much that it lashes out...
+ h5 Spread the Word Challenge Reminder
+ p In case you missed it, we're running our second Spread the Word Challenge! The rules are simple: make a post some time between December 31st 2014 and January 31st 2015 on some form of blog or social media that tells people about HabitRPG. The top post will be awarded 100 GEMS, and the next nineteen top posts will be awarded 80 GEMS each. Learn more and join in here!
+ tr
+ td
+ h5 World Boss: First Stress Strike!
+ p The World Boss in the Tavern has used its first Stress Strike!
+ p Despite our best efforts, we've let some Dailies get away from us, and their dark-red color has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the Stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least it's distracted for the moment.
+ p Complete Dailies and To-Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied.
p.small.muted by Lemoness, Kiwibot, and SabreCat
hr
a(href='/static/old-news', target='_blank') Read older news
mixin oldNews
+ h5 1/8/2015
+ tr
+ td
+ h5 World Boss: The Abominable Stressbeast!
+ .quest_stressbeast.pull-right
+ p A new World Boss has appeared in the Tavern! All of the completed Dailies and To-Dos of Habiticans damage the World Boss. Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC.
+ p A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied. Read on for the details!
+ tr
+ td
+ h5 Winter Plot-Line: The Abominable Stressbeast Attacks!
+ p The first thing we hear are the footsteps, slower and more thundering than the stampede. One by one, Habiticans look outside their doors, and words fail us.
+ p We've all seen Stressbeasts before, of course - tiny vicious creatures that attack during difficult times. But this? This towers taller than the buildings, with paws that could crush a dragon with ease. Frost swings from its stinking fur, and as it roars, the icy blast rips the roofs off our houses. A monster of this magnitude has never been mentioned outside of distant legend.
+ p "Beware, Habiticans!" SabreCat cries. "Barricade yourselves indoors - this is the Abominable Stressbeast itself!"
+ p "That thing must be made of centuries of stress!" Kiwibot says, locking the Tavern door tightly and shuttering the windows.
+ p "The Stoïkalm Steppes," Lemoness says, face grim. "All this time, we thought they were placid and untroubled, but they must have been secretly hiding their stress somewhere. Over generations, it grew into this, and now it's broken free and attacked them - and us!"
+ p There's only one way to drive away a Stressbeast, Abominable or otherwise, and that's to attack it with completed Dailies and To-Dos! Let's all band together and fight off this fearsome foe - but be sure not to slack on your tasks, or our undone Dailies may enrage it so much that it lashes out...
+ p.small.muted by Lemoness, Kiwibot, and SabreCat
h5 1/5/2015
tr
td
diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade
index 3bd2edaad7..e3d387dbd5 100644
--- a/views/shared/tasks/task.jade
+++ b/views/shared/tasks/task.jade
@@ -63,8 +63,8 @@ li(bindonce='list', bo-id='"task-"+task.id', ng-repeat='task in obj[list.type+"s
// Habits
span(ng-if='::task.type=="habit"')
// score() is overridden in challengesCtrl to do nothing
- a.task-action-btn(ng-if='task.up', ng-click='score(task,"up")') +
- a.task-action-btn(ng-if='task.down', ng-click='score(task,"down")') -
+ a.task-action-btn(ng-if='task.up', ng-click='applyingAction || score(task,"up")') +
+ a.task-action-btn(ng-if='task.down', ng-click='applyingAction || score(task,"down")') -
// Rewards
span(ng-show='task.type=="reward"')
diff --git a/views/static/layout.jade b/views/static/layout.jade
index 3c8bd38bd7..8744b06518 100644
--- a/views/static/layout.jade
+++ b/views/static/layout.jade
@@ -1,7 +1,7 @@
//-Trick needed to pass 'env' to ./layout
block vars
doctype html
-html(ng-app='habitrpgStatic')
+html(ng-app='habitrpg')
head
block extraHead
diff --git a/views/static/new-stuff.jade b/views/static/new-stuff.jade
index 9cbf77b87f..6757b48f68 100644
--- a/views/static/new-stuff.jade
+++ b/views/static/new-stuff.jade
@@ -1,6 +1,6 @@
block vars
doctype html
-html(ng-app='habitrpgStatic')
+html(ng-app='habitrpg')
head
block title