Merge branch 'develop' into fixes-issue-5183
This commit is contained in:
@@ -187,3 +187,6 @@ a.label
|
||||
color: #fff !important
|
||||
.line-through
|
||||
text-decoration line-through
|
||||
|
||||
.markdown-preview markdown code
|
||||
white-space inherit
|
||||
@@ -110,6 +110,12 @@ menu.pets .customize-menu
|
||||
-o-filter: brightness(0%)
|
||||
-ms-filter: brightness(0%)*/
|
||||
|
||||
.PixelPaw
|
||||
margin-top: 36px // align paw with pets, at the bottom of the button
|
||||
.mount-not-owned
|
||||
width: 104px;
|
||||
height: 99px;
|
||||
|
||||
.PixelPaw
|
||||
margin-top: 36px // align paw with pets, at the bottom of the button
|
||||
|
||||
|
||||
@@ -41,8 +41,9 @@ a.hint:hover
|
||||
.popover
|
||||
hr
|
||||
margin: 10px 0
|
||||
ul
|
||||
padding-left: 10px
|
||||
|
||||
ul
|
||||
list-style-position: inside;
|
||||
|
||||
a#btn-leave
|
||||
margin-top -6px
|
||||
|
||||
@@ -142,6 +142,14 @@ for $stage in $stages
|
||||
padding: 0
|
||||
font-weight: 300
|
||||
|
||||
.task-column.preview
|
||||
padding: 0
|
||||
background: transparent
|
||||
border: 0;
|
||||
|
||||
.task:hover
|
||||
cursor: auto
|
||||
|
||||
// 50% width columns with scrollbars for tablets
|
||||
@media (min-width: 768px) and (max-width: 970px)
|
||||
.task-column
|
||||
@@ -180,6 +188,11 @@ for $stage in $stages
|
||||
.empty-task-notification
|
||||
height: 100%;
|
||||
|
||||
// message in Dailies column when Resting in Inn
|
||||
// ------------------------
|
||||
.dailiesRestingInInn
|
||||
clear: both
|
||||
|
||||
// an individual task entry
|
||||
// ------------------------
|
||||
.task
|
||||
|
||||
@@ -216,9 +216,9 @@ window.habitrpg = angular.module('habitrpg',
|
||||
url: "/export",
|
||||
templateUrl: "partials/options.settings.export.html"
|
||||
})
|
||||
.state('options.settings.coupon', {
|
||||
url: "/coupon",
|
||||
templateUrl: "partials/options.settings.coupon.html"
|
||||
.state('options.settings.promo', {
|
||||
url: "/promo",
|
||||
templateUrl: "partials/options.settings.promo.html"
|
||||
})
|
||||
.state('options.settings.subscription', {
|
||||
url: "/subscription",
|
||||
|
||||
@@ -15,11 +15,13 @@ angular.module('habitrpg')
|
||||
|
||||
var runAuth = function(id, token) {
|
||||
User.authenticate(id, token, function(err) {
|
||||
if(!err) $scope.registrationInProgress = false;
|
||||
$window.location.href = ('/' + window.location.hash);
|
||||
});
|
||||
};
|
||||
|
||||
function errorAlert(data, status, headers, config) {
|
||||
$scope.registrationInProgress = false;
|
||||
if (status === 0) {
|
||||
$window.alert(window.env.t('noReachServer'));
|
||||
} else if (!!data && !!data.err) {
|
||||
@@ -29,6 +31,8 @@ angular.module('habitrpg')
|
||||
}
|
||||
};
|
||||
|
||||
$scope.registrationInProgress = false;
|
||||
|
||||
$scope.register = function() {
|
||||
/*TODO highlight invalid inputs
|
||||
we have this as a workaround for https://github.com/HabitRPG/habitrpg-mobile/issues/64
|
||||
@@ -36,6 +40,8 @@ angular.module('habitrpg')
|
||||
var scope = angular.element(document.getElementById('registrationForm')).scope();
|
||||
if (scope.registrationForm.$invalid) return;
|
||||
|
||||
$scope.registrationInProgress = true;
|
||||
|
||||
var url = ApiUrl.get() + "/api/v2/register";
|
||||
if($rootScope.selectedLanguage) url = url + '?lang=' + $rootScope.selectedLanguage.code;
|
||||
$http.post(url, scope.registerVals).success(function(data, status, headers, config) {
|
||||
|
||||
@@ -28,7 +28,7 @@ function($scope, $rootScope, User, $http, Notification, ApiUrl) {
|
||||
// Google Analytics, only in production
|
||||
if (window.env.NODE_ENV === 'production') {
|
||||
// Get experiments API
|
||||
$.getScript('//www.google-analytics.com/cx/api.js?experiment=pi26hZ3rRFaEPNiKqXbhqA', function(){
|
||||
$.getScript('//www.google-analytics.com/cx/api.js?experiment=t-AFggRWQnuJ6Teck_x1-Q', function(){
|
||||
$rootScope.variant = cxApi.chooseVariation();
|
||||
$rootScope.$apply();
|
||||
|
||||
|
||||
@@ -232,6 +232,11 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
return user.indexOf(text) == 0;
|
||||
}
|
||||
|
||||
$scope.performCompletion = function(msg) {
|
||||
$scope.autoComplete(msg);
|
||||
$scope.query = null;
|
||||
}
|
||||
|
||||
$scope.addNewUser = function(user) {
|
||||
if($.inArray(user.user,$scope.usernames) == -1) {
|
||||
user.username = user.user;
|
||||
@@ -362,7 +367,25 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
$scope.copyToDo = function(message) {
|
||||
var taskNotes = env.t("messageWroteIn", {
|
||||
user: message.uuid == 'system'
|
||||
? 'system'
|
||||
: '[' + message.user + '](' + env.BASE_URL + '/static/front/#?memberId=' + message.uuid + ')',
|
||||
group: '[' + $scope.group.name + '](' + window.location.href + ')'
|
||||
});
|
||||
|
||||
var newScope = $scope.$new();
|
||||
newScope.text = message.text;
|
||||
newScope.notes = taskNotes;
|
||||
|
||||
$rootScope.openModal('copyChatToDo',{
|
||||
controller:'CopyMessageModalCtrl',
|
||||
scope: newScope
|
||||
});
|
||||
};
|
||||
|
||||
$scope.sync = function(group){
|
||||
group.$get();
|
||||
@@ -567,3 +590,20 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
.controller("CopyMessageModalCtrl", ['$scope', 'User', 'Notification',
|
||||
function($scope, User, Notification){
|
||||
$scope.saveTodo = function() {
|
||||
var newTask = {
|
||||
text: $scope.text,
|
||||
type: 'todo',
|
||||
notes: $scope.notes
|
||||
};
|
||||
|
||||
User.user.ops.addTask({body:newTask});
|
||||
Notification.text(window.env.t('messageAddedAsToDo'));
|
||||
|
||||
$scope.$close();
|
||||
}
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -103,26 +103,6 @@ habitrpg.controller("InventoryCtrl",
|
||||
}
|
||||
}
|
||||
|
||||
$scope.purchase = function(type, item){
|
||||
if (type == 'special') return User.user.ops.buySpecialSpell({params:{key:item.key}});
|
||||
|
||||
var gems = User.user.balance * 4;
|
||||
|
||||
var string = (type == 'weapon') ? window.env.t('weapon') : (type == 'armor') ? window.env.t('armor') : (type == 'head') ? window.env.t('headgear') : (type == 'shield') ? window.env.t('offhand') : (type == 'headAccessory') ? window.env.t('headAccessory') : (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : type; // this is ugly but temporary, once the purchase modal is done this will be removed
|
||||
if (type == 'weapon' || type == 'armor' || type == 'head' || type == 'shield' || type == 'headAccessory') {
|
||||
if (gems < ((item.specialClass == "wizard") && (item.type == "weapon")) + 1) return $rootScope.openModal('buyGems');
|
||||
var message = window.env.t('buyThis', {text: string, price: ((item.specialClass == "wizard") && (item.type == "weapon")) + 1, gems: gems})
|
||||
if($window.confirm(message))
|
||||
User.user.ops.purchase({params:{type:"gear",key:item.key}});
|
||||
} else {
|
||||
if(gems < item.value) return $rootScope.openModal('buyGems');
|
||||
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
|
||||
if($window.confirm(message))
|
||||
User.user.ops.purchase({params:{type:type,key:item.key}});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$scope.choosePet = function(egg, potion){
|
||||
var petDisplayName = env.t('petName', {
|
||||
potion: Content.hatchingPotions[potion] ? Content.hatchingPotions[potion].text() : potion,
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
/* Make user and settings available for everyone through root scope.
|
||||
*/
|
||||
|
||||
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce',
|
||||
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments,$sce) {
|
||||
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window',
|
||||
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window) {
|
||||
var user = User.user;
|
||||
|
||||
var initSticky = _.once(function(){
|
||||
@@ -201,7 +201,40 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
|
||||
chart.draw(data, options);
|
||||
};
|
||||
|
||||
$rootScope.getGearArray = function(set){
|
||||
var flatGearArray = _.toArray(Content.gear.flat);
|
||||
|
||||
var filteredArray = _.where(flatGearArray, {gearSet: set});
|
||||
|
||||
return filteredArray;
|
||||
}
|
||||
|
||||
$rootScope.purchase = function(type, item){
|
||||
if (type == 'special') return User.user.ops.buySpecialSpell({params:{key:item.key}});
|
||||
|
||||
var gems = User.user.balance * 4;
|
||||
|
||||
var string = (type == 'weapon') ? window.env.t('weapon') : (type == 'armor') ? window.env.t('armor') : (type == 'head') ? window.env.t('headgear') : (type == 'shield') ? window.env.t('offhand') : (type == 'headAccessory') ? window.env.t('headAccessory') : (type == 'hatchingPotions') ? window.env.t('hatchingPotion') : (type == 'eggs') ? window.env.t('eggSingular') : (type == 'quests') ? window.env.t('quest') : (item.key == 'Saddle') ? window.env.t('foodSaddleText').toLowerCase() : type; // FIXME this is ugly but temporary, once the purchase modal is done this will be removed
|
||||
var price = ((((item.specialClass == "wizard") && (item.type == "weapon")) || item.gearSet == "animal") + 1);
|
||||
if (type == 'weapon' || type == 'armor' || type == 'head' || type == 'shield' || type == 'headAccessory') {
|
||||
if (User.user.items.gear.owned[item.key]) {
|
||||
if (User.user.preferences.costume) return User.user.ops.equip({params:{type: 'costume', key: item.key}});
|
||||
else {
|
||||
return User.user.ops.equip({params:{type: 'equipped', key: item.key}})
|
||||
}
|
||||
}
|
||||
if (gems < price) return $rootScope.openModal('buyGems');
|
||||
var message = window.env.t('buyThis', {text: string, price: price, gems: gems})
|
||||
if($window.confirm(message))
|
||||
User.user.ops.purchase({params:{type:"gear",key:item.key}});
|
||||
} else {
|
||||
if(gems < item.value) return $rootScope.openModal('buyGems');
|
||||
var message = window.env.t('buyThis', {text: string, price: item.value, gems: gems})
|
||||
if($window.confirm(message))
|
||||
User.user.ops.purchase({params:{type:type,key:item.key}});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
------------------------
|
||||
|
||||
@@ -20,7 +20,7 @@ habitrpg.controller('SettingsCtrl',
|
||||
var mapPrefToEmailString = {
|
||||
'importantAnnouncements': 'inactivityEmails'
|
||||
};
|
||||
|
||||
|
||||
// If ?unsubFrom param is passed with valid email type,
|
||||
// automatically unsubscribe users from that email and
|
||||
// show an alert
|
||||
@@ -42,7 +42,7 @@ habitrpg.controller('SettingsCtrl',
|
||||
User.set({"preferences.stickyHeader":false});
|
||||
$rootScope.$on('userSynced', function(){
|
||||
window.location.reload();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,7 +144,7 @@ habitrpg.controller('SettingsCtrl',
|
||||
$http.post(ApiUrl.get() + '/api/v2/user/coupon/' + code).success(function(res,code){
|
||||
if (code!==200) return;
|
||||
User.sync();
|
||||
Notification.text('Coupon applied! Check your inventory');
|
||||
Notification.text(env.t('promoCodeApplied'));
|
||||
});
|
||||
}
|
||||
$scope.generateCodes = function(codes){
|
||||
|
||||
@@ -185,13 +185,14 @@ function($rootScope, User, $timeout, $state) {
|
||||
var showFinish = step.final || k == 'classes';
|
||||
var showCounter = k=='intro' && !step.final;
|
||||
|
||||
$rootScope.variant=2; // temporarily set finish & counter on until we can get experiment working
|
||||
|
||||
// Experiment wud1Ba5qT1m9qR3PP0-Mmg , remove this when experiment complete
|
||||
// 0=No Finish; Yes Counter 1=No Finish; No Counter 2=Yes Finish; Yes Counter 3=Yes Finish; No Counter
|
||||
showFinish = showFinish || $rootScope.variant==2 || $rootScope.variant==3;
|
||||
showCounter = showCounter && ($rootScope.variant==0 || $rootScope.variant==2);
|
||||
|
||||
// FIXME temporarily set finish & counter on until we can get experiment working
|
||||
showFinish=true;showCounter=true;
|
||||
|
||||
return '<div class="popover" role="tooltip">' +
|
||||
'<div class="arrow"></div>' +
|
||||
'<h3 class="popover-title"></h3>' +
|
||||
@@ -216,6 +217,7 @@ function($rootScope, User, $timeout, $state) {
|
||||
});
|
||||
|
||||
var goto = function(chapter, page, force) {
|
||||
//return; // TODO temporarily remove old tutorial system while experimenting with leslie's new gettup
|
||||
var curr = User.user.flags.tour[chapter];
|
||||
if (page != curr+1 && !force) return;
|
||||
var updates = {};updates['flags.tour.'+chapter] = page;
|
||||
|
||||
@@ -62,7 +62,7 @@ api.authWithUrl = function(req, res, next) {
|
||||
if (_.isEmpty(user)) return res.json(401, NO_USER_FOUND);
|
||||
res.locals.user = user;
|
||||
next();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
api.registerUser = function(req, res, next) {
|
||||
|
||||
@@ -136,8 +136,36 @@ api.get = function(req, res, next) {
|
||||
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(group);
|
||||
if(!group){
|
||||
if(gid !== 'party') return res.json(404,{err: "Group not found or you don't have access."});
|
||||
|
||||
// Don't send a 404 when querying for a party even if it doesn't exist
|
||||
// so that users with no party don't get a 404 on every access to the site
|
||||
return res.json(group);
|
||||
}
|
||||
//Since we have a limit on how many members are populate to the group, we want to make sure the user is always in the group
|
||||
var userInGroup = _.find(group.members, function(member){ return member._id == user._id; });
|
||||
//If the group is private or the group is a party, then the user must be a member of the group based on access restrictions above
|
||||
if (group.privacy === 'private' || gid === 'party') {
|
||||
//If the user is not in the group query, remove a user and add the current user
|
||||
if (!userInGroup) {
|
||||
group.members.splice(0,1);
|
||||
group.members.push(user);
|
||||
}
|
||||
res.json(group);
|
||||
} else if ( group.privacy === "public" ) { //The group is public, we must do an extra check to see if the user is already in the group query
|
||||
//We must see how to check if a user is a member of a public group, so we requery
|
||||
var q2 = Group.findOne({ _id: group._id, privacy:'public', members: {$in:[user._id]} });
|
||||
q2.exec(function(err, group2){
|
||||
if (err) return next(err);
|
||||
if (group2 && !userInGroup) {
|
||||
group.members.splice(0,1);
|
||||
group.members.push(user);
|
||||
}
|
||||
res.json(group);
|
||||
});
|
||||
}
|
||||
|
||||
gid = null;
|
||||
});
|
||||
};
|
||||
@@ -309,7 +337,8 @@ api.flagChatMessage = function(req, res, next){
|
||||
group.markModified('chat');
|
||||
group.save(function(err,_saved){
|
||||
if(err) return next(err);
|
||||
var addressesToSendTo = JSON.parse(nconf.get('FLAG_REPORT_EMAIL'));
|
||||
var addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL');
|
||||
addressesToSendTo = (typeof addressesToSendTo == 'string') ? JSON.parse(addressesToSendTo) : addressesToSendTo;
|
||||
|
||||
if(Array.isArray(addressesToSendTo)){
|
||||
addressesToSendTo = addressesToSendTo.map(function(email){
|
||||
@@ -326,17 +355,17 @@ api.flagChatMessage = function(req, res, next){
|
||||
{name: "REPORTER_USERNAME", content: user.profile.name},
|
||||
{name: "REPORTER_UUID", content: user._id},
|
||||
{name: "REPORTER_EMAIL", content: user.auth.local ? user.auth.local.email : ((user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) ? user.auth.facebook.emails[0].value : null)},
|
||||
{name: "REPORTER_MODAL_URL", content: "https://habitrpg.com/static/front/#?memberId=" + user._id},
|
||||
{name: "REPORTER_MODAL_URL", content: "/static/front/#?memberId=" + user._id},
|
||||
|
||||
{name: "AUTHOR_USERNAME", content: message.user},
|
||||
{name: "AUTHOR_UUID", content: message.uuid},
|
||||
{name: "AUTHOR_EMAIL", content: author.auth.local ? author.auth.local.email : ((author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) ? author.auth.facebook.emails[0].value : null)},
|
||||
{name: "AUTHOR_MODAL_URL", content: "https://habitrpg.com/static/front/#?memberId=" + message.uuid},
|
||||
{name: "AUTHOR_MODAL_URL", content: "/static/front/#?memberId=" + message.uuid},
|
||||
|
||||
{name: "GROUP_NAME", content: group.name},
|
||||
{name: "GROUP_TYPE", content: group.type},
|
||||
{name: "GROUP_ID", content: group._id},
|
||||
{name: "GROUP_URL", content: group._id == 'habitrpg' ? (nconf.get('BASE_URL') + '/#/options/groups/tavern') : (group.type === 'guild' ? (nconf.get('BASE_URL')+ '/#/options/groups/guilds/' + group._id) : 'party')},
|
||||
{name: "GROUP_URL", content: group._id == 'habitrpg' ? '/#/options/groups/tavern' : (group.type === 'guild' ? ('/#/options/groups/guilds/' + group._id) : 'party')},
|
||||
]);
|
||||
|
||||
return res.send(204);
|
||||
@@ -398,7 +427,8 @@ api.likeChatMessage = function(req, res, next) {
|
||||
|
||||
api.join = function(req, res, next) {
|
||||
var user = res.locals.user,
|
||||
group = res.locals.group;
|
||||
group = res.locals.group,
|
||||
isUserInvited = false;
|
||||
|
||||
if (group.type == 'party' && group._id == (user.invitations && user.invitations.party && user.invitations.party.id)) {
|
||||
User.update({_id:user.invitations.party.inviter}, {$inc:{'items.quests.basilist':1}}).exec(); // Reward inviter
|
||||
@@ -409,16 +439,25 @@ api.join = function(req, res, next) {
|
||||
group.quest.members[user._id] = undefined;
|
||||
group.markModified('quest.members');
|
||||
}
|
||||
}
|
||||
else if (group.type == 'guild' && user.invitations && user.invitations.guilds) {
|
||||
isUserInvited = true;
|
||||
} else if (group.type == 'guild' && user.invitations && user.invitations.guilds) {
|
||||
var i = _.findIndex(user.invitations.guilds, {id:group._id});
|
||||
if (~i) user.invitations.guilds.splice(i,1);
|
||||
user.save();
|
||||
if (~i){
|
||||
isUserInvited = true;
|
||||
user.invitations.guilds.splice(i,1);
|
||||
user.save();
|
||||
}else{
|
||||
isUserInvited = group.privacy === 'private' ? false : true;
|
||||
}
|
||||
}
|
||||
|
||||
if(!isUserInvited) return res.json(401, {err: "Can't join a group you're not invited to."});
|
||||
|
||||
if (!_.contains(group.members, user._id)){
|
||||
group.members.push(user._id);
|
||||
group.invites.splice(_.indexOf(group.invites, user._id), 1);
|
||||
if (group.invites.length > 0) {
|
||||
group.invites.splice(_.indexOf(group.invites, user._id), 1);
|
||||
}
|
||||
}
|
||||
|
||||
async.series([
|
||||
@@ -430,7 +469,6 @@ api.join = function(req, res, next) {
|
||||
}
|
||||
], function(err, results){
|
||||
if (err) return next(err);
|
||||
|
||||
// Return the group? Or not?
|
||||
res.json(results[1]);
|
||||
group = null;
|
||||
@@ -567,12 +605,12 @@ var inviteByUUIDs = function(uuids, group, req, res, next){
|
||||
if(group.type == 'guild'){
|
||||
emailVars.push(
|
||||
{name: 'GUILD_NAME', content: group.name},
|
||||
{name: 'GUILD_URL', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/public'}
|
||||
{name: 'GUILD_URL', content: '/#/options/groups/guilds/public'}
|
||||
);
|
||||
}else{
|
||||
emailVars.push(
|
||||
{name: 'PARTY_NAME', content: group.name},
|
||||
{name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'}
|
||||
{name: 'PARTY_URL', content: '/#/options/groups/party'}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -614,7 +652,7 @@ var inviteByEmails = function(invites, group, req, res, next){
|
||||
}
|
||||
|
||||
// yeah, it supports guild too but for backward compatibility we'll use partyInvite as query
|
||||
var link = nconf.get('BASE_URL')+'?partyInvite='+ utils.encrypt(JSON.stringify({id:group._id, inviter:res.locals.user._id, name:group.name}));
|
||||
var link = '?partyInvite='+ utils.encrypt(JSON.stringify({id:group._id, inviter:res.locals.user._id, name:group.name}));
|
||||
|
||||
var inviterVars = utils.getUserInfo(res.locals.user, ['name', 'email']);
|
||||
var variables = [
|
||||
@@ -679,8 +717,8 @@ api.removeMember = function(req, res, next){
|
||||
utils.txnEmail(removedUser, ('kicked-from-' + group.type), [
|
||||
{name: 'GROUP_NAME', content: group.name},
|
||||
{name: 'MESSAGE', content: message},
|
||||
{name: 'GUILDS_LINK', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/public'},
|
||||
{name: 'PARTY_WANTED_GUILD', content: nconf.get('BASE_URL') + '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'}
|
||||
{name: 'GUILDS_LINK', content: '/#/options/groups/guilds/public'},
|
||||
{name: 'PARTY_WANTED_GUILD', content: '/#/options/groups/guilds/f2db2a7f-13c5-454d-b3ee-ea1f5089e601'}
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -830,7 +868,7 @@ questStart = function(req, res, next) {
|
||||
});
|
||||
|
||||
utils.txnEmail(usersToEmail, 'quest-started', [
|
||||
{name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'}
|
||||
{name: 'PARTY_URL', content: '/#/options/groups/party'}
|
||||
]);
|
||||
|
||||
_.each(groupClone.members, function(user){
|
||||
@@ -890,7 +928,7 @@ api.questAccept = function(req, res, next) {
|
||||
{name: 'QUEST_NAME', content: quest.text()},
|
||||
{name: 'INVITER', content: inviterVars.name},
|
||||
{name: 'REPLY_TO_ADDRESS', content: inviterVars.email},
|
||||
{name: 'PARTY_URL', content: nconf.get('BASE_URL') + '/#/options/groups/party'}
|
||||
{name: 'PARTY_URL', content: '/#/options/groups/party'}
|
||||
]);
|
||||
|
||||
questStart(req,res,next);
|
||||
|
||||
@@ -72,7 +72,7 @@ api.sendPrivateMessage = function(req, res, next){
|
||||
if(fetchedMember.preferences.emailNotifications.newPM !== false){
|
||||
utils.txnEmail(fetchedMember, 'new-pm', [
|
||||
{name: 'SENDER', content: utils.getUserInfo(res.locals.user, ['name']).name},
|
||||
{name: 'PMS_INBOX_URL', content: nconf.get('BASE_URL') + '/#/options/groups/inbox'}
|
||||
{name: 'PMS_INBOX_URL', content: '/#/options/groups/inbox'}
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -198,7 +198,8 @@ module.exports.locals = function(req, res, next) {
|
||||
Content: shared.content,
|
||||
mods: require('./models/user').mods,
|
||||
tavern: tavern, // for world boss
|
||||
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {}
|
||||
worldDmg: (tavern && tavern.quest && tavern.quest.extra && tavern.quest.extra.worldDmg) || {},
|
||||
_: _
|
||||
});
|
||||
|
||||
// Put query-string party (& guild but use partyInvite for backward compatibility)
|
||||
|
||||
@@ -87,7 +87,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso
|
||||
},
|
||||
{
|
||||
name: 'RECIPIENT_UNSUB_URL',
|
||||
content: baseUrl + '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
|
||||
content: '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
|
||||
_id: mailingInfo._id,
|
||||
email: mailingInfo.email
|
||||
}))
|
||||
@@ -113,7 +113,7 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso
|
||||
},
|
||||
{
|
||||
name: 'RECIPIENT_UNSUB_URL',
|
||||
content: baseUrl + '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
|
||||
content: '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
|
||||
_id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
|
||||
email: singlePersonalVariables.rcpt
|
||||
}))
|
||||
|
||||
@@ -24,7 +24,7 @@ mixin mountList(source)
|
||||
div(popover-trigger='mouseenter', popover=env.t('mountName', {potion: potion.text(env.language.code), mount: egg.mountText(env.language.code)}), popover-placement='bottom')
|
||||
button(class="pet-button Mount_Head_#{mount}", ng-show='user.items.mounts["#{mount}"]', ng-class='{active: user.items.currentMount == "#{mount}"}', ng-click='chooseMount("#{egg.key}", "#{potion.key}")')
|
||||
//div(class='Mount_Head_{{mount}}')
|
||||
button(class="pet-button pet-not-owned", ng-hide='user.items.mounts["#{mount}"]')
|
||||
button(class="pet-button mount-not-owned", ng-hide='user.items.mounts["#{mount}"]')
|
||||
.PixelPaw
|
||||
-}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ mixin gemCost(cost)
|
||||
= ' ' + env.t('locked')
|
||||
block
|
||||
|
||||
-var gearGroup = function(grouping) { return env._(env.Content.gear.flat).where({gearSet:grouping}).pluck('key') }
|
||||
-var showPath = function(path, items, joiner) { return path+'["'+items.join('"] '+joiner+' '+path+'["')+'"]'; }
|
||||
-var unlockPath = function(path, items) { return 'unlock("'+path+'.'+items.join(','+path+'.')+'")'; }
|
||||
|
||||
@@ -45,6 +46,15 @@ mixin customizeProfile(mobile)
|
||||
each shirt in specialShirts
|
||||
button.customize-option(type='button', class='{{user.preferences.size}}_shirt_'+shirt, ng-class='{locked: !user.purchased.shirt.'+shirt+'}', ng-click='unlock("shirt.'+shirt+'")')
|
||||
|
||||
h3(class=mobile?'item item-divider':'')=env.t('accessories')
|
||||
|
||||
menu(type='list')
|
||||
li.customize-menu
|
||||
menu(label=env.t('animalEars'))
|
||||
span(ng-hide='#{showPath("user.items.gear.owned", gearGroup("animal"), "&&")}')
|
||||
+gemCost(2)
|
||||
button.btn.btn-xs(ng-click='#{unlockPath("items.gear.owned", gearGroup("animal"))}')!= env.t('unlockSet', {cost: 5}) + ' <span class="Pet_Currency_Gem1x inline-gems"/>'
|
||||
button.customize-option(ng-repeat='item in ::getGearArray("animal")' ng-class="{locked: user.items.gear.owned[item.key] == undefined}", popover='{{::item.notes()}}', popover-title='{{::item.text()}}', popover-trigger='mouseenter', popover-placement='right', popover-append-to-body='true', ng-click='purchase(item.type,item)', class='shop_{{::item.key}}')
|
||||
|
||||
.col-md-4
|
||||
h3(class=mobile?'item item-divider':'')=env.t('bodyHead')
|
||||
@@ -132,11 +142,12 @@ mixin customizeProfile(mobile)
|
||||
each color in ['ddc994','f5a76e','ea8349','c06534','98461a','915533','c3e1dc','6bd049']
|
||||
button.customize-option(type='button', class='skin_#{color}', ng-click='set({"preferences.skin":"#{color}"})')
|
||||
|
||||
// Rainbow Skin
|
||||
// Always-available premium skins
|
||||
+buyPref('skin', ['eb052b','f69922','f5d70f','0ff591','2b43f6','d7a9f7','800ed0','rainbow'], 'rainbowSkins')
|
||||
+buyPref('skin', ['pastelPink','pastelOrange','pastelYellow','pastelGreen','pastelBlue','pastelPurple','pastelRainbowChevron','pastelRainbowDiagonal'], 'pastelSkins', 'disabled')
|
||||
+buyPref('skin', ['bear','cactus','fox','lion','panda','pig','tiger','wolf'], 'animalSkins')
|
||||
|
||||
// Special Events
|
||||
// Seasonal event skins. Note that Spooky Skins are a legacy set and should always be disabled for purchase
|
||||
+buyPref('skin', ['pastelPink','pastelOrange','pastelYellow','pastelGreen','pastelBlue','pastelPurple','pastelRainbowChevron','pastelRainbowDiagonal'], 'pastelSkins', 'disabled')
|
||||
+buyPref('skin', ['monster','pumpkin','skeleton','zombie','ghost','shadow'], 'spookySkins', 'disabled')
|
||||
+buyPref('skin', ['candycorn','ogre','pumpkin2','reptile','shadow2','skeleton2','transparent','zombie2'], 'supernaturalSkins', 'disabled')
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@ script(id='partials/options.settings.html', type="text/ng-template")
|
||||
li(ng-class="{ active: $state.includes('options.settings.export') }")
|
||||
a(ui-sref='options.settings.export')
|
||||
=env.t('dataExport')
|
||||
li(ng-class="{ active: $state.includes('options.settings.coupon') }")
|
||||
a(ui-sref='options.settings.coupon')
|
||||
=env.t('coupon')
|
||||
li(ng-class="{ active: $state.includes('options.settings.promo') }")
|
||||
a(ui-sref='options.settings.promo')
|
||||
=env.t('promoCode')
|
||||
li(ng-class="{ active: $state.includes('options.settings.subscription') }")
|
||||
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
||||
li(ng-class="{ active: $state.includes('options.settings.notifications') }")
|
||||
@@ -69,7 +69,6 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||
// button.btn.btn-default(ng-click='showTour()', popover-placement='right', popover-trigger='mouseenter', popover=env.t('restartTour'))= env.t('showTour')
|
||||
button.btn.btn-default(ng-click='showBailey()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('showBaileyPop'))= env.t('showBailey')
|
||||
button.btn.btn-default(ng-click='openRestoreModal()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('fixValPop'))= env.t('fixVal')
|
||||
button.btn.btn-default(ng-click="openModal('invite-friends', {controller:'GroupsCtrl'})") Invite Friends
|
||||
button.btn.btn-default(ng-if='user.preferences.disableClasses==true', ng-click='user.ops.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass')
|
||||
button.btn.btn-default(ng-if='!user.preferences.disableClasses && user.flags.classSelected', ng-click='showClassesTour()', popover-trigger='mouseenter', popover-placement='right', popover=env.t('classTourPop'))= env.t('showClass')
|
||||
|
||||
@@ -155,14 +154,14 @@ script(type='text/ng-template', id='partials/options.settings.settings.html')
|
||||
a.btn.btn-danger(ng-click='openModal("reset", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('resetAccPop'))= env.t('resetAccount')
|
||||
a.btn.btn-danger(ng-click='openModal("delete", {controller:"SettingsCtrl"})', popover-trigger='mouseenter', popover=env.t('deleteAccPop'))= env.t('deleteAccount')
|
||||
|
||||
script(type='text/ng-template', id='partials/options.settings.coupon.html')
|
||||
script(type='text/ng-template', id='partials/options.settings.promo.html')
|
||||
.container-fluid
|
||||
.row
|
||||
.col-md-6
|
||||
h2= env.t('coupon')
|
||||
h2=env.t('promoCode')
|
||||
form.form-inline(role='form',ng-submit='enterCoupon(_couponCode)')
|
||||
input.form-control(type='text', ng-model='_couponCode', placeholder=env.t('couponPlaceholder'))
|
||||
button.btn.btn-primary(type='submit') Submit
|
||||
input.form-control(type='text', ng-model='_couponCode', placeholder=env.t('promoPlaceholder'))
|
||||
button.btn.btn-primary(type='submit')= env.t('submit')
|
||||
div
|
||||
small= env.t('couponText')
|
||||
div(ng-if='user.contributor.sudo')
|
||||
@@ -379,12 +378,12 @@ script(id='partials/options.settings.subscription.html',type='text/ng-template')
|
||||
|
||||
.form-inline
|
||||
.form-group
|
||||
input.form-control(type='text', ng-model='_subscription.coupon', placeholder='Promo Code')
|
||||
input.form-control(type='text', ng-model='_subscription.coupon', placeholder= env.t('couponPlaceholder'))
|
||||
.form-group
|
||||
button.btn.btn-small(type='button',ng-click='applyCoupon(_subscription.coupon)') Apply
|
||||
button.btn.btn-small(type='button',ng-click='applyCoupon(_subscription.coupon)')= env.t("apply")
|
||||
|
||||
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)') Resubscribe
|
||||
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key') Card
|
||||
h3(ng-if='(user.purchased.plan.customerId && user.purchased.plan.dateTerminated)')= env.t("resubscribe")
|
||||
a.btn.btn-primary(ng-click='Payments.showStripe({subscription:_subscription.key, coupon:_subscription.coupon})', ng-disabled='!_subscription.key')= env.t('card')
|
||||
a.btn.btn-warning(href='/paypal/subscribe?_id={{user._id}}&apiToken={{user.apiToken}}&sub={{_subscription.key}}{{_subscription.coupon ? "&coupon="+_subscription.coupon : ""}}', ng-disabled='!_subscription.key') PayPal
|
||||
div(ng-if='user.purchased.plan.customerId')
|
||||
.btn.btn-primary(ng-if='!user.purchased.plan.dateTerminated && user.purchased.plan.paymentMethod=="Stripe"', ng-click='Payments.showStripeEdit()')=env.t('subUpdateCard')
|
||||
|
||||
@@ -13,9 +13,9 @@ div.chat-form.guidelines-not-accepted(ng-if='!user.flags.communityGuidelinesAcce
|
||||
form.chat-form(ng-if='user.flags.communityGuidelinesAccepted' ng-submit='postChat(group,message.content)')
|
||||
div(ng-controller='AutocompleteCtrl')
|
||||
textarea.form-control(rows=4, ui-keydown='{"meta-enter":"postChat(group,message.content)"}', ui-keypress='{13:"postChat(group,message.content)"}', ng-model='message.content', updateinterval='250', flag='@', at-user, auto-complete placeholder="{{group._id == 'habitrpg' ? env.t('tavernCommunityGuidelinesPlaceholder') : ''}}")
|
||||
span.user-list(ng-show='!isAtListHidden')
|
||||
ul.list-at-user
|
||||
li(ng-repeat='msg in response | filter:filterUser | limitTo: 5', ng-click='autoComplete(msg)')
|
||||
span.user-list
|
||||
ul.list-at-user(ng-show="query")
|
||||
li(ng-repeat='msg in response | filter:filterUser | limitTo: 5', ng-click='performCompletion(msg)')
|
||||
span.username.label.label-default(ng-class=':: userLevelStyle(msg)') {{::msg.user}}
|
||||
.chat-controls
|
||||
.chat-formatting
|
||||
|
||||
@@ -31,6 +31,9 @@ mixin chatMessages(inbox)
|
||||
|
|
||||
a(ng-click="flagChatMessage(group._id, message)", ng-if=':: user.contributor.admin || (!message.sent && user.flags.communityGuidelinesAccepted && message.uuid != user.id && message.uuid != "system")')
|
||||
span.glyphicon.glyphicon-flag(tooltip="{{message.flags[user._id] ? env.t('abuseAlreadyReported') : env.t('abuseFlag')}}" ng-class='message.flags[user._id] ? "text-danger" : ""')
|
||||
|
|
||||
a(ng-click="copyToDo(message)")
|
||||
span.glyphicon.glyphicon-share(tooltip=env.t('copyMessageAsToDo'))
|
||||
span.float-label(ng-class='::contribText(message.contributor, message.backer).length > 30 ? "long-title" : ""')
|
||||
a.label.label-default.chat-message(ng-if=':: message.user', ng-class='::userLevelStyleFromLevel(message.contributor.level, message.backer.npc, style)', ng-click='clickMember(message.uuid, true)')
|
||||
span.glyphicon.glyphicon-arrow-right(ng-if='::message.sent')
|
||||
|
||||
@@ -23,6 +23,6 @@ form.col-md-12.form-horizontal(ng-submit='create(newGroup)')
|
||||
.checkbox
|
||||
label
|
||||
input(type='checkbox', ng-model='newGroup.leaderOnly.challenges')
|
||||
| Only group leader can create challenges
|
||||
=env.t('leaderOnlyChallenges')
|
||||
.form-group(ng-show='type=="party"')
|
||||
input.btn.btn-default.form-control(type='submit', value=env.t('create'))
|
||||
|
||||
@@ -42,7 +42,7 @@ a.pull-right.gem-wallet(ng-if='group.type!="party"', popover-trigger='mouseenter
|
||||
.checkbox
|
||||
label
|
||||
input(type='checkbox', ng-model='group.leaderOnly.challenges')
|
||||
| Only group leader can create challenges
|
||||
=env.t('leaderOnlyChallenges')
|
||||
|
||||
h4=env.t('assignLeader')
|
||||
select#group-leader-selection(ng-model='group._newLeader', ng-options='member.profile.name for member in group.members')
|
||||
|
||||
@@ -113,3 +113,33 @@ script(type='text/ng-template', id='partials/options.social.html')
|
||||
.tab-content
|
||||
.tab-pane.active
|
||||
div(ui-view)
|
||||
|
||||
script(type='text/ng-template', id='modals/copyChatToDo.html')
|
||||
.modal-header
|
||||
h4=env.t('copyMessageAsToDo')
|
||||
.modal-body
|
||||
.form-group
|
||||
input.form-control(type='text',ng-model='text', ng-model-options="{debounce: 1000}")
|
||||
.form-group
|
||||
textarea.form-control(rows='5',ng-model='notes', ng-model-options="{debounce: 1000}", focus-me)
|
||||
|
||||
hr
|
||||
|
||||
div.task-column.preview
|
||||
div(ng-init='popoverOpen = false', class='task todo uncompleted color-neutral', popover-trigger='mouseenter', data-popover-html="{{popoverOpen ? '' : notes | markdown}}", popover-placement="top")
|
||||
// right-hand side control buttons
|
||||
.task-meta-controls
|
||||
// Icons only available if you own the tasks (aka, hidden from challenge stats)
|
||||
span(ng-if='!obj._locked')
|
||||
// notes
|
||||
span.task-notes(ng-show='notes', ng-click='popoverOpen = !popoverOpen', popover-trigger='click', data-popover-html="{{notes | markdown}}", popover-placement="top")
|
||||
span.glyphicon.glyphicon-comment
|
||||
|
|
||||
|
||||
// main content
|
||||
div.task-text
|
||||
markdown(text='text',target='_blank')
|
||||
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click='$close()')=env.t('close')
|
||||
button.btn.btn-primary(ng-click='saveTodo()')=env.t('submit')
|
||||
@@ -14,14 +14,11 @@
|
||||
h3.popover-title
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/2014640723')=env.t('daniel')
|
||||
.popover-content
|
||||
=env.t('danielText')
|
||||
div
|
||||
button.btn.btn-lg.btn-success(ng-class='{active: user.preferences.sleep}', ng-click='User.user.ops.sleep({})')
|
||||
span(ng-show='user.preferences.sleep')=env.t('innCheckOut')
|
||||
span(ng-hide='user.preferences.sleep')=env.t('innCheckIn')
|
||||
=env.t('danielText2')
|
||||
.alert.alert-info(ng-show='user.preferences.sleep')
|
||||
=env.t('innText',{name:"{{user.profile.name}}"})
|
||||
| {{user.preferences.sleep ? env.t('innText',{name: user.profile.name}) : env.t('danielText')}}
|
||||
button.btn-block.btn.btn-lg.btn-success(ng-click='User.user.ops.sleep({})')
|
||||
| {{user.preferences.sleep ? env.t('innCheckOut') : env.t('innCheckIn')}}
|
||||
span(ng-if='!user.preferences.sleep')=env.t('danielText2')
|
||||
|
||||
|
||||
// Resources
|
||||
.panel.panel-default
|
||||
|
||||
@@ -243,7 +243,7 @@ nav.toolbar(ng-controller='AuthCtrl', ng-class='{active: isToolbarHidden}')
|
||||
li
|
||||
a(ui-sref='options.settings.export')=env.t('export')
|
||||
li
|
||||
a(ui-sref='options.settings.coupon') Coupon
|
||||
a(ui-sref='options.settings.promo')=env.t('promoCode')
|
||||
li
|
||||
a(ui-sref='options.settings.subscription')=env.t('subscription')
|
||||
li
|
||||
|
||||
@@ -11,3 +11,9 @@ mixin aLink(url, label)
|
||||
a(href="", ng-click="externalLink('#{url}')")= label
|
||||
else
|
||||
a(href='#{url}', target='_blank')= label
|
||||
|
||||
mixin previewMarkdown(text)
|
||||
.panel.panel-warning
|
||||
.panel-heading=env.t('msgPreviewHeading')
|
||||
.panel-body.markdown-preview
|
||||
markdown(text='#{text}')
|
||||
@@ -1,38 +1,75 @@
|
||||
h5 5/01/2015 - Skills Rebalancing
|
||||
hr
|
||||
tr
|
||||
td
|
||||
p After over a year's worth of complaints on Trello and countless bug reports on Github, and after almost five months of dedicated debating, coding, and testing, we have finally rolled out an overhaul of the skills and mana system. Thank you so much to everyone who contributed opinions, time, and effort! Here's what happened:
|
||||
tr
|
||||
td
|
||||
h5 HABITS DAMAGE BOSSES
|
||||
p Now it's even easier to damage a Boss without skills! It was definitely high time that HabitRPG counted Habits towards battles. We were not living up to our name.
|
||||
tr
|
||||
td
|
||||
h5 MANA DIRECTLY LINKED TO PROGRESS
|
||||
p Mana was pretty broken: you gained it overnight no matter what you'd done the day before, and it was only linked to To-Dos. This meant that using skills was pretty detached from how well you were doing in your real life, which defeated the purpose of the game!
|
||||
p Now you can gain Mana from Dailies and positive Habits as well as To-Dos! You still gain Mana overnight, but how much you gain depends on how many of your Dailies you did.
|
||||
p You can also lose Mana by hitting negative Habits. Yikes! Better not indulge...
|
||||
tr
|
||||
td
|
||||
h5 SKILLS STRENGTHENED AND WEAKENED
|
||||
p Some notoriously overpowered skills have been weakened, like Backstab (which often granted over 1000GP per cast), Burst of Flames (which allowed you to 1-hit KO most bosses regardless of how much or how little you'd done that day), and Valorous Presence (which caused the notorious "I checked off one Daily and gained 997 levels" bug reports).
|
||||
p Some painfully UNDERpowered skills have been strengthened, such as Tools of the Trade and Searing Brightness. We think you'll like these much better now!
|
||||
p For a full description of what changed, <a href='http://habitrpg.wikia.com/wiki/User_blog:LadyAlys/Skills_Rebalancing_and_Other_Changes' target='_blank'>check out this blog post</a>.
|
||||
tr
|
||||
td
|
||||
h5 GIVING FEEDBACK
|
||||
p We understand that these changes may be disruptive to some of you. We're sorry! We hope that with time, you will find them to be a drastic improvement. We will be waiting two weeks (so that everyone can get used to the new system), and then on May 15th will open up a new Trello card for comments and feedback.
|
||||
p For those of you who want to reallocate stats now that you have the new system, please post your User ID in this <a href='https://github.com/HabitRPG/habitrpg/issues/5082' target='_blank'>Github ticket</a>. (Your UUID is found under Settings > API; do NOT post your API Token.) That ticket is also being used to debate a possibility to make it so that reallocating stats does not cost gems. If that's a feature you would like (or would hate), definitely drop by that ticket and let us know!
|
||||
p.small.muted by Alys, Verabird, brandonjreid, ShilohT, betaveros, SabreCat, chimericdream, and everyone who commented with feedback on Trello and Github. You guys are great!
|
||||
h5 5/13/2015 - NEW ANIMAL SKINS AND ACCESSORIES, INN IMPROVEMENTS, COPY CHAT TO TO-DO, AND EXTRA INFO
|
||||
hr
|
||||
tr
|
||||
td
|
||||
.promo_pet_skins.pull-right
|
||||
h5 New Animal Skins and Accessories Available!
|
||||
p They say that owners resemble their pets, and now that's truer than ever for Habiticans! The Animal Skin Set and Animal Accessory Set are now available in the <a href='/#/options/profile/avatar' target='_blank'>Customization shop</a>. Time to let your avatar walk on the wild side.
|
||||
p.small.muted by Painter de Cluster and SabreCat
|
||||
tr
|
||||
td
|
||||
h5 Inn Improvements
|
||||
p Now even when you Rest in the Inn, your Dailies will refresh each day. <strong>Important note: your Dailies still do NOT hurt you while you're in the Inn.</strong> We wouldn't do that to you!
|
||||
br
|
||||
p It used to be that the Resting in the Inn froze all your Dailies so that they wouldn't refresh the next day - even for important Dailies like "take medicine" or "feed pets." This was very frustrating to a lot of users who relied on these features, even during periods of time when they needed to rest in the Inn for finals, illness, vacation, etc.
|
||||
br
|
||||
p Now if you're in a situation, for example finals period, where you don't have the time or energy to do most of your Dailies, you can still complete some of them every day while Resting in the Inn. It's much more flexible and convenient!
|
||||
br
|
||||
p If you're in a quest, you still won't deal damage/collect items until you check out of the Inn, and your Dailies will not damage your party members, but you can still be injured by a Boss if your Party mates skip their own Dailies. You also still do not gain Mana overnight until you check out of the Inn. If you have any questions about how Resting in the Inn works, be sure to ask in the <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>Newbies Guild!</a>
|
||||
p.small.muted by hairlessbear and Blade
|
||||
tr
|
||||
td
|
||||
h5 Copy Chat to To-Do
|
||||
p Now you can copy any chat message into your To-Do list! If your roommate posted in party chat and asked you to grab groceries, or your friend reminded you about the history quiz tomorrow, you can save it right into your To-Do list. Awesome!
|
||||
p.small.muted by Negue and Redphoenix
|
||||
tr
|
||||
td
|
||||
h5 Extra Info
|
||||
p We know that HabitRPG can be confusing, so now you easily double-check what each task type does by clicking the question mark next to its name.
|
||||
p.small.muted by Lemoness, lefnire, and SabreCat
|
||||
|
||||
hr
|
||||
a(href='/static/old-news', target='_blank') Read older news
|
||||
|
||||
mixin oldNews
|
||||
|
||||
|
||||
h5 5/06/2015 - MAY BACKGROUNDS REVEALED
|
||||
tr
|
||||
td
|
||||
.background_mountain_lake.pull-right
|
||||
h5 May Backgrounds Revealed
|
||||
p There are three new avatar backgrounds in the <a href='https://habitrpg.com/#/options/profile/backgrounds' target='_blank'>Background Shop</a>! Now your avatar can climb to the top of Pagodas, pose in front of a Marble Temple, or dip toes in a Mountain Lake!
|
||||
p.small.muted by (in order) Teto is Great, Painter de Cluster, and Hazel40
|
||||
|
||||
h5 5/01/2015 - Skills Rebalancing
|
||||
tr
|
||||
td
|
||||
p After over a year's worth of complaints on Trello and countless bug reports on Github, and after almost five months of dedicated debating, coding, and testing, we have finally rolled out an overhaul of the skills and mana system. Thank you so much to everyone who contributed opinions, time, and effort! Here's what happened:
|
||||
tr
|
||||
td
|
||||
h5 HABITS DAMAGE BOSSES
|
||||
p Now it's even easier to damage a Boss without skills! It was definitely high time that HabitRPG counted Habits towards battles. We were not living up to our name.
|
||||
tr
|
||||
td
|
||||
h5 MANA DIRECTLY LINKED TO PROGRESS
|
||||
p Mana was pretty broken: you gained it overnight no matter what you'd done the day before, and it was only linked to To-Dos. This meant that using skills was pretty detached from how well you were doing in your real life, which defeated the purpose of the game!
|
||||
p Now you can gain Mana from Dailies and positive Habits as well as To-Dos! You still gain Mana overnight, but how much you gain depends on how many of your Dailies you did.
|
||||
p You can also lose Mana by hitting negative Habits. Yikes! Better not indulge...
|
||||
tr
|
||||
td
|
||||
h5 SKILLS STRENGTHENED AND WEAKENED
|
||||
p Some notoriously overpowered skills have been weakened, like Backstab (which often granted over 1000GP per cast), Burst of Flames (which allowed you to 1-hit KO most bosses regardless of how much or how little you'd done that day), and Valorous Presence (which caused the notorious "I checked off one Daily and gained 997 levels" bug reports).
|
||||
p Some painfully UNDERpowered skills have been strengthened, such as Tools of the Trade and Searing Brightness. We think you'll like these much better now!
|
||||
p For a full description of what changed, <a href='http://habitrpg.wikia.com/wiki/User_blog:LadyAlys/Skills_Rebalancing_and_Other_Changes' target='_blank'>check out this blog post</a>.
|
||||
tr
|
||||
td
|
||||
h5 GIVING FEEDBACK
|
||||
p We understand that these changes may be disruptive to some of you. We're sorry! We hope that with time, you will find them to be a drastic improvement. We will be waiting two weeks (so that everyone can get used to the new system), and then on May 15th will open up a new Trello card for comments and feedback.
|
||||
p For those of you who want to reallocate stats now that you have the new system, please post your User ID in this <a href='https://github.com/HabitRPG/habitrpg/issues/5082' target='_blank'>Github ticket</a>. (Your UUID is found under Settings > API; do NOT post your API Token.) That ticket is also being used to debate a possibility to make it so that reallocating stats does not cost gems. If that's a feature you would like (or would hate), definitely drop by that ticket and let us know!
|
||||
p.small.muted by Alys, Verabird, brandonjreid, ShilohT, betaveros, SabreCat, chimericdream, and everyone who commented with feedback on Trello and Github. You all are great!
|
||||
|
||||
h5 4/30/2015 - LAST CHANCE FOR BUSY BEE AND SPRING FLING ITEMS, AND SOON-TO-APPEAR NEW EQUIPMENT!
|
||||
hr
|
||||
tr
|
||||
td
|
||||
.promo_shimmer_hair.pull-right
|
||||
|
||||
@@ -43,13 +43,13 @@ table.table.table-striped
|
||||
ul.list-unstyled(ng-init='g=Content.gear.flat;e=profile.items.gear.equipped')
|
||||
li(ng-show='profile.stats.lvl > 1')
|
||||
span.hint(popover-title=env.t('levelBonus'), popover-trigger='mouseenter', popover-placement='top', popover=env.t('levelBonusText'))=env.t('level')
|
||||
|: {{(Math.min(profile.stats.lvl - 1, 100)) / 2}}
|
||||
|: {{Math.ceil((Math.min(profile.stats.lvl - 1, 100)) / 2)}}
|
||||
li(ng-show='g[e.weapon].#{k} + g[e.armor].#{k} + g[e.head].#{k} + g[e.shield].#{k} > 0')
|
||||
span.hint(popover-title=env.t('equipment'), popover-trigger='mouseenter', popover-placement='top', popover=env.t('equipmentBonusText'))=env.t('equipment')
|
||||
|: {{g[e.weapon].#{k} + g[e.armor].#{k} + g[e.head].#{k} + g[e.shield].#{k} || 0}}
|
||||
li(ng-show='profile._statsComputed.#{k} - profile.stats.buffs.#{k} - ((Math.min(profile.stats.lvl - 1, 100)) / 2) - g[e.weapon].#{k} - g[e.armor].#{k} - g[e.head].#{k} - g[e.shield].#{k} - profile.stats.#{k} > 0')
|
||||
li(ng-show='profile._statsComputed.#{k} - profile.stats.buffs.#{k} - Math.ceil((Math.min(profile.stats.lvl - 1, 100)) / 2) - g[e.weapon].#{k} - g[e.armor].#{k} - g[e.head].#{k} - g[e.shield].#{k} - profile.stats.#{k} > 0')
|
||||
span.hint(popover-title=env.t('classBonus'), popover-trigger='mouseenter', popover-placement='top', popover=env.t('classBonusText'))=env.t('classEquipBonus')
|
||||
|: {{profile._statsComputed.#{k} - profile.stats.buffs.#{k} - ((Math.min(profile.stats.lvl - 1,100)) / 2) - g[e.weapon].#{k} - g[e.armor].#{k} - g[e.head].#{k} - g[e.shield].#{k} - profile.stats.#{k}}}
|
||||
|: {{profile._statsComputed.#{k} - profile.stats.buffs.#{k} - Math.ceil((Math.min(profile.stats.lvl - 1,100)) / 2) - g[e.weapon].#{k} - g[e.armor].#{k} - g[e.head].#{k} - g[e.shield].#{k} - profile.stats.#{k}}}
|
||||
li(ng-show='profile.stats.#{k} > 0')
|
||||
span.hint(popover-title=env.t('allocatedPoints'), popover-trigger='mouseenter', popover-placement='top', popover=env.t('allocatedPointsText'))=env.t('allocated')
|
||||
|: {{profile.stats.#{k} || 0}}
|
||||
|
||||
@@ -8,16 +8,52 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||
.task-column(class='{{list.type}}s')
|
||||
|
||||
// Todos export/graph options
|
||||
span.option-box.pull-right(ng-if='::main && list.type=="todo"')
|
||||
a.option-action(ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'))
|
||||
span.option-box.pull-right(ng-if='::main')
|
||||
a.option-action(ng-if='list.type=="todo"', ng-show='obj.history.todos', ng-click='toggleChart("todos")', tooltip=env.t('progress'), style='margin-right:5px;')
|
||||
span.glyphicon.glyphicon-signal
|
||||
//a.option-action(ng-href='/v1/users/{{user.id}}/calendar.ics?apiToken={{user.apiToken}}', tooltip='iCal')
|
||||
a.option-action(ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
||||
//-a.option-action(ng-if='list.type=="todo"', ng-click='notPorted()', tooltip='iCal', ng-show='false')
|
||||
span.glyphicon.glyphicon-calendar
|
||||
// <a href="https://www.google.com/calendar/render?cid={{encodeiCalLink(_user.id, _user.apiToken)}}" rel=tooltip title="Google Calendar"><i class=icon-calendar></i></a>
|
||||
a.option-action(ng-click='list.help=!list.help', tooltip='Click for help')
|
||||
span.glyphicon.glyphicon-question-sign
|
||||
|
||||
// Header
|
||||
h2.task-column_title {{list.header}}
|
||||
h2.task-column_title
|
||||
| {{list.header}}
|
||||
|
||||
div(ng-if='list.help', ng-switch='::list.type')
|
||||
div(ng-switch-when='habit')
|
||||
ul
|
||||
li Good Habits are things that you do often. They award gold and experience every time you click the <code>+</code>.
|
||||
li Bad Habits are things you want to avoid doing. They remove health every time you click the <code>-</code>.
|
||||
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Habits#Business' target='_blank'>sample Habits</a>
|
||||
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||
div(ng-switch-when='daily')
|
||||
ul
|
||||
li Dailies repeat <strong>every day</strong> that they are active. Click the pencil icon to change the days a Daily is active.
|
||||
li If you don’t complete active Dailies, they will hurt your avatar when your day rolls over.
|
||||
li Dailies turn <strong>redder</strong> when you miss them, and <strong>bluer</strong> when you complete them. The redder the Daily, the more it will reward you... and hurt you.
|
||||
li To change when your day rolls over, go to Settings > Site > Custom Day Start.
|
||||
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_Dailies#Business' target='_blank'>sample Dailies</a>!
|
||||
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||
|
||||
div(ng-switch-when='todo')
|
||||
ul
|
||||
li To-Dos start yellow, and get redder (more valuable) the longer it takes to complete them.
|
||||
li To-Dos never hurt you! They only award gold and experience.
|
||||
li Breaking a To-Do down into a small checklist will make it less scary, and will increase your points!
|
||||
li For inspiration, check out these <a href='http://habitrpg.wikia.com/wiki/Sample_To-Dos#Finances' target='_blank'>sample To-Dos</a>!
|
||||
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||
|
||||
div(ng-switch-when='reward')
|
||||
ul
|
||||
li The Equipment you buy for your avatar is stored in Inventory > Equipment.
|
||||
li Equipment affects your stats (Avatar > Stats).
|
||||
li There will be special equipment here during World Events.
|
||||
li Don't be afraid to set custom rewards! Check out some samples <a href='http://habitrpg.wikia.com/wiki/Sample_Custom_Rewards' target='_blank'>here</a>
|
||||
li More questions? Ask in <a href='https://habitrpg.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a' target='_blank'>the Newbies Guild!</a>
|
||||
|
||||
|
||||
// Todo Chart
|
||||
.todos-chart(ng-if='::list.type == "todo"', ng-show='charts.todos')
|
||||
@@ -34,6 +70,10 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
||||
span(ng-if='!list.bulk')=env.t('addmultiple')
|
||||
span(ng-if='list.bulk')=env.t('addsingle')
|
||||
|
||||
alert.alert-warning.dailiesRestingInInn(ng-if='::list.type == "daily" && user.preferences.sleep')
|
||||
i.glyphicon.glyphicon-warning-sign
|
||||
=env.t('dailiesRestingInInn')
|
||||
|
||||
mixin taskColumnTabs(position)
|
||||
// Habits Tabs
|
||||
div(ng-if='::main && list.type=="habit"', class='tabbable tabs-below')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
|
||||
li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s"] | conditionalOrderBy: list.view=="dated":"date"', class='task {{Shared.taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && (list.type != "reward") && castEnd(task, "task", $event)', ng-class='{"cast-target":spell && (list.type != "reward"), "locked-task":obj._locked === true}', popover-trigger='mouseenter', data-popover-html="{{task.popoverOpen ? '' : task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}', ng-show='shouldShow(task, list, user.preferences)')
|
||||
// right-hand side control buttons
|
||||
.task-meta-controls
|
||||
|
||||
@@ -53,7 +53,7 @@ li(bindonce='list', id='task-{{::task.id}}', ng-repeat='task in obj[list.type+"s
|
||||
span.glyphicon.glyphicon-signal
|
||||
|
|
||||
// notes
|
||||
span.task-notes(ng-show='task.notes && !task._editing')
|
||||
span.task-notes(ng-show='task.notes && !task._editing', ng-click='task.popoverOpen = !task.popoverOpen', popover-trigger='click', data-popover-html="{{task.notes | markdown}}", popover-placement="top", popover-append-to-body='{{::modal ? "false":"true"}}')
|
||||
span.glyphicon.glyphicon-comment
|
||||
|
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ block content
|
||||
.page-header
|
||||
h1=env.t('communityGuidelines')
|
||||
p.pagemeta
|
||||
|Last updated
|
||||
=env.t('lastUpdated')
|
||||
|
|
||||
=env.t('February')
|
||||
| 9, 2015
|
||||
h2=env.t('commGuideHeadingWelcome')
|
||||
|
||||
@@ -65,7 +65,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
|
||||
li
|
||||
button#header-play-button.btn.btn-primary.navbar-btn.navbar-right(ng-click='playButtonClick()')= env.t('playButtonFull')
|
||||
#intro
|
||||
h1= env.t('motivate')
|
||||
h1(ng-cloak) {{ variant==0 ? "#{env.t('motivate')}" : (variant==1 ? "#{env.t('motivate1')}" : "#{env.t('motivate2')}") }}
|
||||
img.center-block.img-responsive(src='https://s3.amazonaws.com/habitrpg-assets/front/images/intro.png')
|
||||
// insert intro images
|
||||
.introcall.bg-success
|
||||
@@ -170,7 +170,7 @@ html(ng-app='habitrpg', ng-controller='RootCtrl')
|
||||
= env.t('work')
|
||||
br
|
||||
small
|
||||
a(href='/business.html')= env.t('businessText')
|
||||
a(href='/static/plans')= env.t('businessText')
|
||||
.carousel-content.row
|
||||
.sampletasks.col-lg-4.col-md-6.col-sm-12.col-xs-12
|
||||
h4= env.t('sampleHabits')
|
||||
|
||||
@@ -69,4 +69,4 @@ script(id='modals/login.html', type='text/ng-template')
|
||||
a(href='/static/privacy' target='_blank')=env.t('privacy')
|
||||
| .
|
||||
.form-group
|
||||
input.btn.btn-primary(type='submit', ng-disabled='registrationForm.$invalid', value=env.t('register'))
|
||||
input.btn.btn-primary(type='submit', ng-disabled='registrationForm.$invalid || registrationInProgress', value=env.t('register'))
|
||||
|
||||
@@ -5,7 +5,7 @@ block vars
|
||||
- var menuItem = 'pressKit'
|
||||
|
||||
block title
|
||||
title Press Kit
|
||||
title= env.t('presskit')
|
||||
|
||||
block extraHead
|
||||
style.
|
||||
|
||||
Reference in New Issue
Block a user