Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
benmanley 2014-02-18 15:55:33 +00:00
commit 977aeec27f
37 changed files with 458 additions and 215 deletions

View file

@ -14,5 +14,4 @@ HabitRPG uses [Trello](https://trello.com/b/EpoYEYod/habitrpg) to track feature
# Contributing Code
* See [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG)
* See [Tips](http://habitrpg.wikia.com/wiki/Tips)
* See [Contributing to HabitRPG](http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG#Coders_.28Web_.26_Mobile.29)

View file

@ -4,6 +4,8 @@ module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
pkg: grunt.file.readJSON('package.json'),
git_changelog: {
minimal: {
options: {
@ -56,7 +58,10 @@ module.exports = function(grunt) {
build: {
files: [
{expand: true, cwd: 'public/', src: 'favicon.ico', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith-*.png', dest: 'build/'}
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/dist/spritesmith.png', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/backer-only/*.gif', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/habitrpg-shared/img/sprites/npc_ian.gif', dest: 'build/'},
{expand: true, cwd: 'public/', src: 'bower_components/bootstrap/dist/fonts/*', dest: 'build/'}
]
}
},
@ -69,16 +74,18 @@ module.exports = function(grunt) {
},
src: [
'build/*.js', 'build/*.css', 'build/favicon.ico',
'build/bower_components/bootstrap/docs/assets/css/*.css',
'build/bower_components/habitrpg-shared/dist/*.css'
'build/bower_components/habitrpg-shared/dist/*.png',
'build/bower_components/habitrpg-shared/img/sprites/backer-only/*.gif',
'build/bower_components/habitrpg-shared/img/sprites/npc_ian.gif',
'build/bower_components/bootstrap/dist/fonts/*'
],
dest: 'make-sure-i-do-not-exist'
dest: 'build/*.css'
}
},
nodemon: {
nodemon: {
dev: {
ignoredFiles: ['public/*', 'Gruntfile.js', 'CHANGELOG.md', 'views/*', 'build/*', '.idea*', '.git*', '*.log']
script: '<%= pkg.main %>'
}
},
@ -106,27 +113,35 @@ module.exports = function(grunt) {
var files = grunt.file.readJSON('./public/manifest.json');
var uglify = {};
var cssmin = {};
_.each(files, function(val, key){
var js = uglify['build/' + key + '.js'] = [];
_.each(files[key]['js'], function(val){
js.push('public/' + val);
});
var css = cssmin['build/' + key + '.css'] = [];
_.each(files[key]['css'], function(val){
if(val == 'app.css' || val == 'static.css'){
cssmin['build/' + val] = ['build/' + val]
}else{
cssmin['build/' + val] = ['public/' + val]
}
var path = (val == 'app.css' || val == 'static.css') ? 'build/' : 'public/';
css.push(path + val)
});
});
grunt.config.set('uglify.build.files', uglify);
grunt.config.set('uglify.build.options', {compress: false})
grunt.config.set('uglify.build.options', {compress: false});
grunt.config.set('cssmin.build.files', cssmin);
// Rewrite urls to relative path
grunt.config.set('cssmin.build.options', {'target': 'public/css/whatever-css.css'});
});
// Register tasks.
grunt.registerTask('build:prod', ['loadManifestFiles', 'clean:build', 'uglify', 'stylus', 'cssmin', 'copy:build', 'hashres']);
grunt.registerTask('build:dev', ['loadManifestFiles', 'clean:build', 'stylus', 'cssmin', 'copy:build']);
grunt.registerTask('build:dev', ['stylus']);
grunt.registerTask('run:dev', [ 'build:dev', 'concurrent' ]);

View file

@ -17,9 +17,9 @@
"jquery": "~2.1.0",
"jquery.cookie": "~1.4.0",
"jquery-ui": "~1.10.3",
"angular": "~1.2.11",
"angular-sanitize": "~1.2.11",
"angular-resource": "~1.2.11",
"angular": "~1.2.12",
"angular-sanitize": "~1.2.12",
"angular-resource": "~1.2.12",
"angular-ui-utils": "~0.1.0",
"angular-ui-select2": "https://github.com/angular-ui/ui-select2.git#4469fd7a32eba2e28d5e3dd319b49e1ad090b167",
"angular-bootstrap": "~0.10.0",
@ -36,13 +36,12 @@
"JavaScriptButtons": "git://github.com/paypal/JavaScriptButtons.git#master",
"Angular-At-Directive": "git://github.com/snicker/Angular-At-Directive#master",
"js-emoji": "git://github.com/snicker/js-emoji#master",
"gemoji": "git://github.com/github/gemoji",
"sticky": "*",
"swagger-ui": "https://github.com/wordnik/swagger-ui.git#105c516f5f055b140e935e9cfe0c36f841921dff",
"ngInfiniteScroll": "~1.0.0",
"jquery-colorbox": "~1.4.36"
},
"devDependencies": {
"angular-mocks": "~1.2.11"
"angular-mocks": "~1.2.12"
}
}

View file

@ -0,0 +1,23 @@
// Give contrib.level 7+ free subscription for life
db.users.update(
{
'contributor.level':{$gte:7},
'purchased.plan.customerId':null
},
{
$set: {
'purchased.plan':{
planId: 'basic',
customerId: 'habitrpg',
dateCreated: new Date,
dateUpdated: new Date,
gemsBought: 0
}
}
},
{multi:true}
)

View file

@ -33,6 +33,10 @@ alert-sm-mixin()
.alert
alert-sm-mixin()
.notification-icon
transform: scale(0.8)
// variables
gpColor = #FBEFC0
@ -95,35 +99,6 @@ borderDarken = 20%
border-color: darken(dropColor,borderDarken)
color: dropText
// alert icons
.icon-gold
background: url("/bower_components/habitrpg-shared/img/coin_single_gold.png") no-repeat
background-position: center center
background-size: 18px
width: 14px
height: 14px
display: inline-block
vertical-align: middle
.icon-silver
background: url("/bower_components/habitrpg-shared/img/coin_single_silver.png") no-repeat
background-position: center center
background-size: 18px
width: 14px
height: 14px
display: inline-block
vertical-align: middle
.icon-death
background: url("/bower_components/habitrpg-shared/img/sprites/dead.png") no-repeat
background-position: center center
background-size: 14px
width: 14px
height: 14px
display: inline-block
vertical-align: middle
.undo-button
z-index: 3000
position:absolute

View file

@ -118,6 +118,10 @@ html, body
float: left
padding: 15px
&.herobox
padding-right: 0px
padding-left: 0px
.death-modal // a .modal-body
margin: 10px
@ -163,3 +167,19 @@ a.label
background-color: #428bca
.badge-success
background-color: #47a447
.white, .white a
color: #fff !important
.collapse-toolbar
position:absolute
right:0px
top:0px
width:30px
height:25px
background-color:#222
z-index:999
.glyphicon
color:white
padding-left:7px
padding-top:5px

View file

@ -4,15 +4,15 @@
.npc_matt
margin-bottom: 20px
.toolbar-bailey-container
width:55px
position:relative
.npc_bailey
float:left
.npc_bailey_head
//height: 30px !important
height: 50px !important
position: absolute
//top: 117px // 147 (header-height) - 30 (bailey height)
top: 97px
right: 50px
top: 4px
height:45px !important
cursor: pointer
// Tour (Justin)

View file

@ -114,8 +114,12 @@ window.habitrpg = angular.module('habitrpg',
.state('options.social.guilds.detail', {
url: '/:gid',
templateUrl: 'partials/options.social.guilds.detail.html',
controller: ['$scope', 'Groups', '$stateParams', function($scope, Groups, $stateParams){
$scope.group = Groups.Group.get({gid:$stateParams.gid});
controller: ['$scope', 'Groups', '$stateParams',
function($scope, Groups, $stateParams){
Groups.Group.get({gid:$stateParams.gid}, function(group){
$scope.group = group;
Groups.seenMessage(group._id);
});
}]
})

View file

@ -319,6 +319,9 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '
$scope.text = 'Party';
$scope.group = $rootScope.party = Groups.party();
$scope.newGroup = new Groups.Group({type:'party'});
Groups.seenMessage($scope.group._id);
$scope.create = function(group){
group.$save(function(newGroup){
$scope.group = newGroup;

View file

@ -94,7 +94,6 @@ habitrpg.controller('NotificationCtrl',
$rootScope.$watch('user.items.' + watched, function(before, after){
if (after == before) return;
if (+after < +before) {
Notification.death();
//don't want to day "lost a head"
if (watched === 'head') watched = 'helm';
Notification.text('Lost GP, 1 LVL, ' + watched);
@ -116,12 +115,6 @@ habitrpg.controller('NotificationCtrl',
}
});
// Math updates modal
$rootScope.$watch('!user.flags.mathUpdates', function(after, before){
if (after == before || after != true) return;
$rootScope.openModal('mathUpdates');
});
// Completed quest modal
$rootScope.$watch('user.party.quest.completed', function(after, before){
if (after == before || after != true) return;

View file

@ -26,6 +26,7 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
$rootScope.Shared = Shared;
$rootScope.Content = Content;
$rootScope.env = window.env;
$rootScope.Math = Math;
// Angular UI Router
$rootScope.$state = $state;
@ -198,6 +199,11 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
*/
$scope.castStart = function(spell) {
if (User.user.stats.mp < spell.mana) return Notification.text(window.env.t('notEnoughMana'));
// Temporary for valentine's day, remove after event
if (spell.key == 'valentine' && User.user.stats.gp < spell.value)
return Notification.text('Not enough gold.');
$rootScope.applyingAction = true;
$scope.spell = spell;
if (spell.target == 'self') {

View file

@ -5,8 +5,8 @@
*/
angular.module('groupServices', ['ngResource']).
factory('Groups', ['API_URL', '$resource', '$q',
function(API_URL, $resource, $q) {
factory('Groups', ['API_URL', '$resource', '$q', '$http', 'User',
function(API_URL, $resource, $q, $http, User) {
var Group = $resource(API_URL + '/api/v2/groups/:gid',
{gid:'@_id', messageId: '@_messageId'},
{
@ -44,6 +44,12 @@ angular.module('groupServices', ['ngResource']).
return tavern;
},
// On enter, set chat message to "seen"
seenMessage: function(gid){
$http.post('/api/v2/groups/'+gid+'/chat/seen');
if (User.user.newMessages) User.user.newMessages[gid] = false;
},
Group: Group
}
}

View file

@ -25,11 +25,11 @@ angular.module("notificationServices", [])
gold = Math.floor(absolute);
silver = Math.floor((absolute - gold) * 100);
if (gold && silver > 0) {
return "" + gold + " <span class='icon-gold'></span> " + silver + " <span class='icon-silver'></span>";
return "" + gold + " <span class='notification-icon shop_gold'></span> " + silver + " <span class='notification-icon shop_silver'></span>";
} else if (gold > 0) {
return "" + gold + " <span class='icon-gold'></span>";
return "" + gold + " <span class='notification-icon shop_gold'></span>";
} else if (silver > 0) {
return "" + silver + " <span class='icon-silver'></span>";
return "" + silver + " <span class='notification-icon shop_silver'></span>";
}
};
@ -60,9 +60,6 @@ angular.module("notificationServices", [])
lvl: function(){
growl('<span class="glyphicon glyphicon-chevron-up"></span>&nbsp;' + window.env.t('levelUp'), 'lvl');
},
death: function(){
growl("<span class='glyphicon glyphicon-death'></span>&nbsp;" + window.env.t('respawn'), "death");
},
error: function(error){
growl("<span class='glyphicon glyphicon-exclamation-sign'></span>&nbsp; " + error, "danger");
},

View file

@ -1,7 +1,7 @@
{
"app": {
"js": [
"bower_components/jquery/jquery.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/jquery.cookie/jquery.cookie.js",
"bower_components/bootstrap-growl/jquery.bootstrap-growl.js",
"bower_components/bootstrap-tour/build/js/bootstrap-tour.js",
@ -72,7 +72,7 @@
},
"static": {
"js": [
"bower_components/jquery/jquery.js",
"bower_components/jquery/dist/jquery.min.js",
"bower_components/habitrpg-shared/dist/habitrpg-shared.js",
"bower_components/angular/angular.js",
"bower_components/angular-ui/build/angular-ui.js",

View file

@ -249,6 +249,14 @@ api.deleteChatMessage = function(req, res){
});
}
api.seenMessage = function(req,res,next){
// Skip the auth step, we want this to be fast. If !found with uuid/token, then it just doesn't save
var update = {$set:{}};
update['$set']['newMessages.'+req.params.gid+'.value'] = false;
User.update({_id:req.headers['x-api-user'], apiToken:req.headers['x-api-key']},update).exec();
res.send(200);
}
api.likeChatMessage = function(req, res, next) {
var user = res.locals.user;
var group = res.locals.group;

View file

@ -89,10 +89,25 @@ api.score = function(req, res, next) {
delta: delta,
_tmp: user._tmp
}, saved.toJSON().stats));
});
// if it's a challenge task, sync the score
user.syncScoreToChallenge(task, delta);
// If it's a challenge task, sync the score. Do it in the background, we've already sent down a response
// and the user doesn't care what happens back there
if (!task.challenge || !task.challenge.id || task.challenge.broken) return;
if (task.type == 'reward') return; // we don't want to update the reward GP cost
Challenge.findById(task.challenge.id, 'habits dailys todos rewards', function(err, chal){
if (err) return next(err);
if (!chal) {
task.challenge.broken = 'CHALLENGE_DELETED';
return user.save();
}
var t = chal.tasks[task.id];
if (!t) return chal.syncToUser(user); // this task was removed from the challenge, notify user
t.value += delta;
if (t.type == 'habit' || t.type == 'daily')
t.history.push({value: t.value, date: +new Date});
chal.save();
});
});
};
/**
@ -409,7 +424,7 @@ api.cast = function(req, res, next) {
case 'user':
async.waterfall([
function(cb){
Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements').exec(cb);
Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members', 'profile.name stats achievements items.special').exec(cb);
},
function(group, cb) {
// Solo player? let's just create a faux group for simpler code

View file

@ -122,22 +122,21 @@ var getManifestFiles = function(page){
if(!files) throw new Error("Page not found!");
var css = '';
_.each(files.css, function(file){
css += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
var code = '';
if(nconf.get('NODE_ENV') === 'production'){
return css + '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(page + '.css') + '">';
code += '<script type="text/javascript" src="' + getBuildUrl(page + '.js') + '"></script>';
}else{
var results = css;
_.each(files.js, function(file){
results += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
_.each(files.css, function(file){
code += '<link rel="stylesheet" type="text/css" href="' + getBuildUrl(file) + '">';
});
_.each(files.js, function(file){
code += '<script type="text/javascript" src="' + getBuildUrl(file) + '"></script>';
});
return results;
}
return code;
}
// Translations

View file

@ -106,6 +106,17 @@ GroupSchema.methods.sendChat = function(message, user){
}
group.chat.unshift(message);
group.chat.splice(200);
// Kick off chat notifications in the background.
var lastSeenUpdate = {$set:{}, $inc:{_v:1}};
lastSeenUpdate['$set']['newMessages.'+group._id] = {name:group.name,value:true};
if (group._id == 'habitrpg') {
// TODO For Tavern, only notify them if their name was mentioned
// var profileNames = [] // get usernames from regex of @xyz. how to handle space-delimited profile names?
// User.update({'profile.name':{$in:profileNames}},lastSeenUpdate,{multi:true}).exec();
} else {
mongoose.model('User').update({_id:{$in:group.members, $ne: user ? user._id : ''}},lastSeenUpdate,{multi:true}).exec();
}
}
var cleanQuestProgress = function(merge){
@ -164,13 +175,13 @@ GroupSchema.methods.finishQuest = function(quest, cb) {
}
// FIXME this is a temporary measure, we need to remove quests from users when they traverse parties
function isOnQuest(user,group){
return group && user.party.quest.key && user.party.quest.key == group.quest.key;
function isOnQuest(user,progress,group){
return group && progress && user.party.quest.key && user.party.quest.key == group.quest.key;
}
GroupSchema.statics.collectQuest = function(user, progress, cb) {
this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){
if (!isOnQuest(user,group)) return cb(null);
if (!isOnQuest(user,progress,group)) return cb(null);
var quest = shared.content.quests[group.quest.key];
_.each(progress.collect,function(v,k){
@ -204,7 +215,7 @@ GroupSchema.statics.collectQuest = function(user, progress, cb) {
GroupSchema.statics.bossQuest = function(user, progress, cb) {
this.findOne({type: 'party', members: {'$in': [user._id]}},function(err, group){
if (!isOnQuest(user,group)) return cb(null);
if (!isOnQuest(user,progress,group)) return cb(null);
var quest = shared.content.quests[group.quest.key];
if (!progress || !quest) return cb(null); // FIXME why is this ever happening, progress should be defined at this point
var down = progress.down * quest.boss.str; // multiply by boss strength

View file

@ -42,7 +42,8 @@ var UserSchema = new Schema({
rebirths: Number,
rebirthLevel: Number,
perfect: Number,
habitBirthday: Boolean
habitBirthday: Boolean,
valentine: Number
},
auth: {
facebook: Schema.Types.Mixed,
@ -136,7 +137,9 @@ var UserSchema = new Schema({
},
special:{
snowball: {type: Number, 'default': 0}
snowball: {type: Number, 'default': 0},
valentine: Number,
valentineReceived: Array // array of strings, by sender name
},
// -------------- Animals -------------------
@ -207,9 +210,11 @@ var UserSchema = new Schema({
lastCron: {type: Date, 'default': Date.now},
// {GROUP_ID: Boolean}, represents whether they have unseen chat messages
newMessages: {type: Schema.Types.Mixed, 'default': {}},
party: {
// id // FIXME can we use a populated doc instead of fetching party separate from user?
lastMessageSeen: String,
order: {type:String, 'default':'level'},
quest: {
key: String,
@ -245,7 +250,8 @@ var UserSchema = new Schema({
disableClasses: {type: Boolean, 'default': false},
newTaskEdit: {type: Boolean, 'default': false},
tagsCollapsed: {type: Boolean, 'default': false},
advancedCollapsed: {type: Boolean, 'default': false}
advancedCollapsed: {type: Boolean, 'default': false},
toolbarCollapsed: {type:Boolean, 'default':false}
},
profile: {
blurb: String,
@ -367,22 +373,6 @@ UserSchema.pre('save', function(next) {
next();
});
UserSchema.methods.syncScoreToChallenge = function(task, delta){
if (!task.challenge || !task.challenge.id || task.challenge.broken) return;
if (task.type == 'reward') return; // we don't want to update the reward GP cost
var self = this;
Challenge.findById(task.challenge.id, function(err, chal){
if (err) throw err;
var t = chal.tasks[task.id];
if (!t) return chal.syncToUser(self); // this task was removed from the challenge, notify user
t.value += delta;
if (t.type == 'habit' || t.type == 'daily') {
t.history.push({value: t.value, date: +new Date});
}
chal.save();
});
}
UserSchema.methods.unlink = function(options, cb) {
var cid = options.cid, keep = options.keep, tid = options.tid;
var self = this;

View file

@ -488,6 +488,17 @@ module.exports = (swagger, v2) ->
middleware: [auth.auth, groups.attachGroup]
action: groups.postChat
# placing before route below, so that if !=='seen' it goes to next()
"/groups/{gid}/chat/seen":
spec:
method: 'POST'
description: "Flag chat messages for a particular group as seen"
parameters: [
path 'gid','Group id','string'
]
middleware: []
action: groups.seenMessage
"/groups/{gid}/chat/{messageId}":
spec:
method: 'DELETE'

View file

@ -116,7 +116,9 @@ if (cluster.isMaster && (isDev || isProd)) {
app.use(app.router);
var maxAge = isProd ? 31536000000 : 0;
// Cache emojis without copying them to build, they are too many
app.use(express['static'](path.join(__dirname, "/../build"), { maxAge: maxAge }));
app.use('/bower_components/habitrpg-shared/img/emoji/unicode', express['static'](path.join(__dirname, "/../public/bower_components/habitrpg-shared/img/emoji/unicode"), { maxAge: maxAge }));
app.use(express['static'](path.join(__dirname, "/../public")));
// Custom Directives

View file

@ -253,6 +253,33 @@ describe('API', function () {
});
});
it('Challenge deleted, breaks task link', function (done) {
var itThis = this;
request.del(baseURL + "/challenges/" + challenge._id).end(function (res) {
User.findById(user._id, function(err,user){
var len = user.dailys.length- 1,
daily = user.dailys[user.dailys.length-1];
expect(daily.challenge.broken).to.be('CHALLENGE_DELETED');
// Now let's handle if challenge was deleted, but didn't get to update all the users (an error)
var unset = {$unset:{}};
unset['$unset']['dailys.' + len + '.challenge.broken'] = 1;
User.findByIdAndUpdate(user._id,unset,function(err,user){
expect(err).to.not.be.ok();
expect(user.dailys[len].challenge.broken).to.not.be.ok();
request.post(baseURL + "/user/tasks/" + daily.id + "/up").end(function (res) {
setTimeout(function(){
User.findById(user._id,function(err,user){
expect(user.dailys[len].challenge.broken).to.be('CHALLENGE_DELETED');
done()
})
}, 100); // we need to wait for challenge to update user, it's a background job for perf reasons
})
})
})
})
});
it('Admin creates a challenge', function (done) {
User.findByIdAndUpdate(_id, {$set:{'contributor.admin':true}}, function (err,_user) {
expect(err).to.not.be.ok();

View file

@ -20,19 +20,15 @@ html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":a
body(ng-cloak, ng-controller='GroupsCtrl')
include ./shared/header/menu
include ./shared/modals/index
include ./shared/header/header
include ./shared/tasks/lists
include ./main/index
include ./options/index
#notification-area(ng-controller='NotificationCtrl')
#wrap.container-fluid
//if they hide the header, we still need user-menu visible
div(ng-if='user.preferences.hideHeader')
include ./shared/header/menu
.row
.col-md-12.exp-chart(ng-show='charts.exp')
#main(ui-view)

View file

@ -21,7 +21,7 @@
form.form-inline(ng-show='_editing', ng-submit='createTag(_newTag)')
.input-group
input.form-control.input-sm(type='text', ng-model='_newTag', placeholder=env.t('newTag'))
span.input-group-addon(type='submit')
span.input-group-addon(ng-click='createTag(_newTag)')
=env.t('add')
// <li class="{#unless activeFilters(users[_userId].filters)}hidden{/}">
li

View file

@ -68,6 +68,12 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
button.customize-option(popover='{{Content.special.snowball.notes}}', popover-title='{{Content.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='castStart(Content.special.snowball)', class='inventory_special_snowball')
.badge.badge-info.stack-count {{user.items.special.snowball}}
li.customize-menu(ng-if='user.items.special.valentineReceived[0]')
menu.pets-menu(label=env.t('valentineCard'))
div
button.customize-option(popover="Valentine's Day Card from {{User.user.items.special.valentineReceived[0]}}", popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("valentine")', class='inventory_special_valentine')
.badge.badge-info.stack-count {{user.items.special.valentineReceived.length}}
li.customize-menu(ng-if='user.purchased.plan.customerId')
menu.pets-menu(label=env.t('subscriberItem'))
div

View file

@ -3,7 +3,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
.col-md-4
h3=env.t('bodyBody')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
h5=env.t('bodySize')
@ -27,7 +27,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
.col-md-4
h3=env.t('bodyHead')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
menu(type='list')
// For special events code, see commit dfa27b3
@ -86,7 +86,7 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template')
.col-md-4
h3=env.t('bodySkin')
small
| 2 <span class="Pet_Currency_Gem1x inline-gems"/> /
| 2 <span class="Pet_Currency_Gem1x inline-gems"></span> /
= ' ' + env.t('locked')
// skin
li.customize-menu

View file

@ -14,8 +14,8 @@
p
|&nbsp;
=env.t('noChallenges')
|&nbsp;
a.label.label-primary(ui-sref='options.social.challenges')
|&nbsp;
span.glyphicon.glyphicon-bullhorn
|&nbsp;
=env.t('challenges')

View file

@ -5,7 +5,7 @@
label.control-label(for='new-group-name')
| {{text}}&nbsp;
=env.t('name')
input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder='{{text}} Name', ng-model='newGroup.name')
input.form-control#new-group-name.input-medium.option-content(required, type='text', placeholder=('{{text}} ' + env.t('name')), ng-model='newGroup.name')
.form-group
label(for='new-group-description')=env.t('description')
textarea.form-control#new-group-description.option-content(cols='3', placeholder=env.t('description'), ng-model='newGroup.description')

View file

@ -12,7 +12,7 @@ script(type='text/ng-template', id='partials/options.social.party.html')
include ./group
div(ng-hide='group._id')
div(ng-show='user.invitations.party.id')
h2=env.t('invitedTo') + ' {{user.invitations.party.name}}'
h2=env.t('invitedTo', {name: '{{user.invitations.party.name}}'})
a.btn.btn-success(data-type='party', ng-click='join(user.invitations.party)')=env.t('accept')
a.btn.btn-danger(ng-click='reject()')=env.t('reject')
div(ng-hide='user.invitations.party.id')
@ -25,7 +25,7 @@ script(type='text/ng-template', id='partials/options.social.party.html')
script(type='text/ng-template', id='partials/options.social.guilds.public.html')
div(ng-repeat='invitation in user.invitations.guilds')
h3=env.t('invitedTo') + ' {{invitation.name}}'
h3=env.t('invitedTo', {name: '{{invitation.name}}'})
a.btn.btn-success(data-type='guild', ng-click='join(invitation)')=env.t('accept')
a.btn.btn-danger(ng-click='reject(invitation)')=env.t('reject')
// Public Groups

View file

@ -48,4 +48,5 @@ figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(
|&nbsp;
=env.t('lvl')
|&nbsp;{{profile.stats.lvl}}
|&nbsp;
span.glyphicon.glyphicon-plus-sign(ng-show='profile.achievements.rebirths', tooltip=env.t('reborn', {reLevel: "{{profile.achievements.rebirthLevel}}"}))

View file

@ -1,7 +1,5 @@
.header-wrap(ng-controller='HeaderCtrl')
a.label.label-default.undo-button(x-bind='click:undo', ng-show='_undo')=env.t('undo')
div(ng-if='!user.preferences.hideHeader')
include menu
header.site-header(ng-hide='user.preferences.hideHeader', role='banner', data-partysize='{{party.members.length>1 ? truarr(party.members.length) : 0}}')
// avatar
@ -38,6 +36,3 @@
span(ng-controller='PartyCtrl')
.herobox-wrap(ng-repeat='profile in partyMinusSelf')
include avatar
.npc_bailey.npc_bailey_head(ng-show='user.flags.newStuff', tooltip=env.t('psst'), tooltip-placement='top', ng-click='openModal("newStuff")')

View file

@ -1,41 +1,124 @@
.user-menu(ng-controller='AuthCtrl')
ul.nav.site-nav(ng-show='authenticated()')
li.flyout
h1.task-action-btn.tile.solid.user-reporter {{user.profile.name}}
ul.flyout-content.nav.stacked
li
a.task-action-btn.tile.solid
span(ng-show='$state.includes("options")', ui-sref='tasks')
span.glyphicon.glyphicon-ok
a.collapse-toolbar(popover=env.t('expandToolbar'),popover-placement='left',popover-trigger='mouseenter',ng-if='user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":false})')
span.glyphicon.glyphicon-chevron-down
nav.navbar.navbar-inverse(ng-controller='AuthCtrl',style='min-height:0px;margin-bottom:0px;border:0;border-radius:0px',ng-if='!user.preferences.toolbarCollapsed')
ul.nav.navbar-nav.pull-right
li
a(popover=env.t('collapseToolbar'),popover-placement='left',popover-trigger='mouseenter',,ng-if='!user.preferences.toolbarCollapsed',ng-click='set({"preferences.toolbarCollapsed":true})')
span.glyphicon.glyphicon-chevron-up.white
.navbar-header
button.navbar-toggle(type='button', ng-click='isNavbarCollapsed = !isNavbarCollapsed', ng-init='isNavbarCollapsed = true')
span.sr-only Toggle navigation
span.icon-bar
span.icon-bar
span.icon-bar
.collapse.navbar-collapse(collapse="isNavbarCollapsed")
ul.nav.navbar-nav
li
a(ui-sref='tasks',popover=env.t('tasks'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-check.white
li.dropdown
a.dropdown-toggle(popover='User',popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-user.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.profile.avatar')=env.t('avatar')
li
a(ui-sref='options.profile.stats')=env.t('stats')
li
a(ui-sref='options.profile.profile')=env.t('profile')
li
a(ng-click='logout()')
span.glyphicon.glyphicon-share-alt
| &nbsp;
=env.t('tasks')
span(ng-show='$state.includes("tasks")', ui-sref='options')
span.glyphicon.glyphicon-wrench
| &nbsp;
=env.t('options')
li
a.task-action-btn.tile.solid(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')
span.glyphicon.glyphicon-book
| &nbsp;
=env.t('FAQ')
li
a.task-action-btn.tile.solid(href="https://vimeo.com/57654086", target='_blank')
span.glyphicon.glyphicon-film
//| &nbsp; Tutorials is too long to stay in the menu if we add a space
=env.t('tutorials')
li
a.task-action-btn.tile.solid(ng-click='User.sync()')
span.glyphicon.glyphicon-refresh
| &nbsp;
=env.t('sync')
li
a.task-action-btn.tile.solid(ng-click='logout()')
span.glyphicon.glyphicon-share-alt
| &nbsp;
=env.t('logout')
// party invitation notification
a(ng-show='user.party.invitation', x-bind='click:gotoPartyChat')
span.glyphicon.glyphicon-user
// party chat notification
a(ng-show='newChatMessages(_party.chat,user.party.lastMessageSeen)', x-bind='click:gotoPartyChat')
span.glyphicon.glyphicon-comment(tooltip=env.t('partyNotification'))
=env.t('logout')
li.dropdown
a.dropdown-toggle(popover=env.t('social'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-heart.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.social.tavern')=env.t('tavern')
li
a(ui-sref='options.social.party')=env.t('party')
li
a(ui-sref='options.social.guilds')=env.t('guilds')
li
a(ui-sref='options.social.challenges')=env.t('challenges')
li
a(ui-sref='options.social.hall')=env.t('hall')
li.dropdown
a.dropdown-toggle(popover=env.t('inventory'), popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-gift.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.inventory.drops')=env.t('market')
li
a(ui-sref='options.inventory.pets')=env.t('pets')
li
a(ui-sref='options.inventory.mounts')=env.t('mounts')
li
a(ui-sref='options.inventory.equipment')=env.t('equipment')
li.dropdown
a.dropdown-toggle(popover=env.t('settings'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-wrench.white
span.caret
ul.dropdown-menu
li
a(ui-sref='options.settings.settings')=env.t('site')
li
a(ui-sref='options.settings.api')=env.t('API')
li
a(ui-sref='options.settings.export')=env.t('export')
li
a(ui-sref='options.settings.subscription')=env.t('subscription')
li.dropdown
a.dropdown-toggle(popover=env.t('help'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-question-sign.white
span.caret
ul.dropdown-menu
li
a(href="http://habitrpg.wikia.com/wiki/FAQ", target='_blank')
span.glyphicon.glyphicon-book
|&nbsp;
=env.t('FAQ')
li
a(href="https://vimeo.com/57654086", target='_blank')
span.glyphicon.glyphicon-film
|&nbsp;
=env.t('tutorials')
li
a(ng-click='User.sync()',popover=env.t('sync'),popover-placement='right',popover-trigger='mouseenter')
span.glyphicon.glyphicon-refresh.white
li
a.pull-right.gem-wallet(style='padding-top:8px;padding-bottom:0px;',ng-click='openModal("buyGems",{track:"Gems > Toolbar"})', popover-trigger='mouseenter', popover-title=env.t('gems'), popover=env.t('gemsWhatFor'), popover-placement='bottom',popover-append-to-body='true')
//span.task-action-btn.tile.flush.bright.add-gems-btn
span.task-action-btn.tile.flush.neutral
.Pet_Currency_Gem2x.Gems
| {{user.balance * 4 | number:0}}
li
button.btn.btn-primary.navbar-btn(ng-if='!user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover-title=env.t('subscriptions'),popover=env.t('subDescription'),popover-append-to-body='true')=env.t('subscribe')
button.btn.btn-default.navbar-btn(ng-if='user.purchased.plan.customerId',ui-sref='options.settings.subscription',popover-trigger='mouseenter',popover-placement='bottom',popover=env.t('manageSub'),popover-append-to-body='true')=env.t('subscribed')
li(ng-if='user.flags.newStuff')
div.toolbar-bailey-container
|&nbsp;
.npc_bailey.npc_bailey_head(popover=env.t('psst'), popover-trigger='mouseenter', popover-placement='right', ng-click='openModal("newStuff")')
// Invitations
li(ng-if='user.invitations.party.id')
a(ui-sref='options.social.party')
span.glyphicon.glyphicon-user.white(popover=env.t('invitedTo', {name: '{{user.invitations.party.name}}'}),popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')
li(ng-repeat='guild in user.invitations.guilds')
a(ui-sref='options.social.guilds')
span.glyphicon.glyphicon-user.white(popover=env.t('invitedTo', {name: '{{guild.name}}'}),popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')
// Chat notifs
li(ng-repeat='(k,v) in user.newMessages', ng-if='v.value')
a(ng-click='k==party._id ? $state.go("options.social.party") : $state.go("options.social.guilds.detail",{gid:k})')
span.glyphicon.glyphicon-comment.white(popover='New message in "{{v.name}}"',popover-placement='bottom',popover-trigger='mouseenter',popover-append-to-body='true')

View file

@ -8,4 +8,5 @@ include ./settings
include ./drops
include ./classes
include ./quests
include ./rebirth
include ./rebirth
include ./limited

View file

@ -0,0 +1,30 @@
//Valentine
script(id='modals/valentine.html', type='text/ng-template')
.modal-header
h4
.inventory_special_valentine.pull-right
=env.t('valentineCard')
.modal-body
.bg-info(style='padding:10px')
p To: {{user.profile.name}}, From: {{user.items.special.valentineReceived[0]}}
hr
div(ng-switch='Math.floor(Math.random()*3)')
ul.list-unstyled(ng-switch-when='0')
li "Roses are red
li My Dailies are blue
li I'm happy that I'm
li In a Party with you!"
ul.list-unstyled(ng-switch-when='1')
li "Roses are red
li Violets are nice
li Let's get together
li And fight against Vice!"
ul.list-unstyled(ng-switch-default)
li "Roses are red
li This poem style is old
li I hope that you like this
li Cuz it cost ten gold."
p
small For enduring such a saccharine poem, you both receive the "Adoring Friends" badge!
.modal-footer
button.btn.btn-default(ng-click='user.ops.readValentine({});$close()')=env.t('ok')

View file

@ -1,17 +1,3 @@
//-div(modal='!user.flags.mathUpdates')
script(type='text/ng-template', id='modals/mathUpdates.html')
.modal-header
h4 Math Updates
.modal-body
.npc_tyler.pull-left
p Hey all, Tyler here. Sorry for the rocky numbers (Exp, GP, etc) with the Class System roll-out! We're tweaking the math today, and things should be mostly finalized by tomorrow. <a target='_blank' href='https://trello.com/c/L1H6LiZ3/110-character-stats-attributes-the-class-system'>Follow our progress here</a>, and please do chime in!
br
p We've also changed classes to unlock at lvl 10 instead of 5, to prevent overwhelming our new users. And since users have expressed confusion, we'll be improving UI and clarifification. Stay tuned.
br
p iOS users, iTunes is on vacation until 12/28. Until we can push the updated app, you'll need to sync periodically to get the corrected numbers (app has old math, server saves the correct numbers). Android goes out ASAP. More info on <a target='_blank' href='https://trello.com/c/L1H6LiZ3/110-character-stats-attributes-the-class-system'>Trello</a>.
.modal-footer
button.btn.btn-default(ng-click='User.set({"flags.mathUpdates":true}); $close()') Dismiss
script(type='text/ng-template', id='modals/newStuff.html')
.modal-header
h4 New Stuff!
@ -26,37 +12,58 @@ script(type='text/ng-template', id='modals/newStuff.html')
h3.popover-title
a(target='_blank', href='https://twitter.com/Mihakuu') Bailey
.popover-content
table.table.table-striped
tr
td
h5 February Mystery Item
p
.pull-right.inventory_present
| We're excited to announce a new feature a s a big thank-you to the awesome people who <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> to HabitRPG! Every month, all subscribers will now receive a limited-edition mystery item! The mystery item will be a stats-free costume piece (like the Absurd Party Robes) that will <strong>only</strong> be available to the people who are subscribers each month. The February 2014 item will be revealed on the 23rd to everyone, but all people who are subscribers during the month of February will receive it. <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>Subscribe now</a>, get excited, and thank you so much for helping to support HabitRPG! We love you.
tr
td
h5 Critical Hammer Of Bug-Crushing
p
.pull-right.weapon_special_critical
| Some of you may have noticed that we periodically have some bugs that are nastier than the norm - the dreaded critical bugs. These monstrous apparitions have been snapping at the heels of many a player. For updates on what we're currently working on to improve site stability, read <a href='https://github.com/HabitRPG/habitrpg/issues/milestones' target='_blank'>this link</a> - and then jump in to help! Not only will programming assistance reward you with the usual contributor levels, but if you actually manage to fix a bug marked <a href='http://goo.gl/v4DnzB' target='_blank'>"critical,"</a> you will now receive the <em>Critical Hammer of Bug-Crushing</em> as your reward!
tr
td
h5 Rainbow Hair Colors
p
.pull-right.customize-option.hair_bangs_1_rainbow
| Want to spruce up your avatar? Rainbow hair colors are now available! Dye your luscious locks purple, green, or even rainbow-striped, and passersby will look at you with envy.
tr
td
h5 Stability Update
p We've stabilized the site a lot (we're still working out kinks, but we're way better now). Follow the <a href='https://github.com/HabitRPG/habitrpg/issues/milestones' target='_blank'>progress here</a>, but here are some workarounds for now:
ul
li Click slower. <a href='https://github.com/HabitRPG/habitrpg/issues/2301#issuecomment-34398206' target='_blank'>VersionError</a> is caused by clicking things off too fast (we're working on a fix).
li If you see an error, refresh before proceeding.
h5 Happy Valentine's Day!
p Help motivate all of the lovely people in your life by sending them a caring valentine. Valentines can be purchased for 10 gold from the Item Store. For spreading love and joy throughout the community, both the giver AND the receiver get a coveted "adoring friends" badge. Hooray!
p
small.muted By Lemoness, mariahm, crystalphoenix, aiseant, zoebeagle, cole, lefnire
small.muted By Lemoness and zoebeagle
hr
p
small.muted 02/13/2014
hr
h5 02/12/2014
table.table.table-striped
tr
td
h5 Chat & Invite Notifications
p Chat & group-invitation notifications are back! Miss them? They currently work for all chat updates in parties & guilds. Any devs willing to jump into @tagging in Tavern, <a href='http://goo.gl/uhcjkg' target='_blank'>see here</a>.
tr
td
h5 Toolbar
p In order to make room for these notifs, we added a toolbar above the header. You can collapse the toolbar (far-right icon), but take care as Bailey notifs are inside the toolbar!
hr
h5 02/07/2014
table.table.table-striped
tr
td
h5 February Mystery Item
p
.pull-right.inventory_present
| We're excited to announce a new feature a s a big thank-you to the awesome people who <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>subscribe</a> to HabitRPG! Every month, all subscribers will now receive a limited-edition mystery item! The mystery item will be a stats-free costume piece (like the Absurd Party Robes) that will <strong>only</strong> be available to the people who are subscribers each month. The February 2014 item will be revealed on the 23rd to everyone, but all people who are subscribers during the month of February will receive it. <a href='https://habitrpg.com/#/options/settings/subscription' target='_blank'>Subscribe now</a>, get excited, and thank you so much for helping to support HabitRPG! We love you.
tr
td
h5 Critical Hammer Of Bug-Crushing
p
.pull-right.weapon_special_critical
| Some of you may have noticed that we periodically have some bugs that are nastier than the norm - the dreaded critical bugs. These monstrous apparitions have been snapping at the heels of many a player. For updates on what we're currently working on to improve site stability, read <a href='https://github.com/HabitRPG/habitrpg/issues/milestones' target='_blank'>this link</a> - and then jump in to help! Not only will programming assistance reward you with the usual contributor levels, but if you actually manage to fix a bug marked <a href='http://goo.gl/v4DnzB' target='_blank'>"critical,"</a> you will now receive the <em>Critical Hammer of Bug-Crushing</em> as your reward!
tr
td
h5 Rainbow Hair Colors
p
.pull-right.customize-option.hair_bangs_1_rainbow
| Want to spruce up your avatar? Rainbow hair colors are now available! Dye your luscious locks purple, green, or even rainbow-striped, and passersby will look at you with envy.
tr
td
h5 Stability Update
p We've stabilized the site a lot (we're still working out kinks, but we're way better now). Follow the <a href='https://github.com/HabitRPG/habitrpg/issues/milestones' target='_blank'>progress here</a>, but here are some workarounds for now:
ul
li Click slower. <a href='https://github.com/HabitRPG/habitrpg/issues/2301#issuecomment-34398206' target='_blank'>VersionError</a> is caused by clicking things off too fast (we're working on a fix).
li If you see an error, refresh before proceeding.
p
small.muted By Lemoness, mariahm, crystalphoenix, aiseant, zoebeagle, cole, lefnire
hr
h5 02/01/2014
@ -68,6 +75,7 @@ script(type='text/ng-template', id='modals/newStuff.html')
p
small.muted by @baconsaur & @DanielTheBard
hr
h5 01/30/2014
table.table.table-striped
tr

View file

@ -133,3 +133,9 @@ div(ng-if='profile.achievements.habitBirthday')
=env.t('habitBirthdayText')
hr
div(ng-if='profile.achievements.valentine')
.achievement.achievement-valentine
h5=env.t('adoringFriends')
small
=env.t('adoringFriendsText', {cards: "{{profile.achievements.valentine}}"})
hr

View file

@ -58,8 +58,9 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
span(bo-class='{"shop_{{item.key}} shop-sprite item-img": true}')
p.task-text {{item.text}}
// Winter Event
ul.items.rewards(ng-if='main && list.type=="reward" && (user.items.special.snowball>0 || user.stats.buffs.snowball)')
// Events
ul.items.rewards(ng-if='main && list.type=="reward"')
li.task.reward-item(ng-if='user.items.special.snowball>0',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.snowball.notes}}')
.task-meta-controls
span.task-notes
@ -71,6 +72,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
span.shop_snowball(style='display:inline-block;vertical-align:top;')
// main content
p.task-text {{Content.spells.special.snowball.text}}
li.task.reward-item(ng-if='user.stats.buffs.snowball',popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.salt.notes}}')
.task-meta-controls
span.task-notes
@ -84,6 +86,18 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
span.shop_salt.shop-sprite.item-img
p.task-text {{Content.spells.special.salt.text}}
li.task.reward-item(popover-trigger='mouseenter', popover-placement='top', popover='{{Content.spells.special.valentine.notes}}')
.task-meta-controls
span.task-notes
span.glyphicon.glyphicon-comment
//left-hand size commands
.task-controls
a.money.btn-buy.item-btn(ng-click='castStart(Content.spells.special.valentine)')
span.reward-cost {{Content.spells.special.valentine.value}}
span.shop_gold
span.inventory_special_valentine.shop-sprite.item-img
p.task-text {{Content.spells.special.valentine.text}}
// Spells
ul.items(ng-if='main && list.type=="reward" && user.stats.class && !user.preferences.disableClasses')
li.task.reward-item(ng-repeat='(k,spell) in Content.spells[user.stats.class]', ng-if='user.stats.lvl >= spell.lvl',popover-trigger='mouseenter', popover-placement='top', popover='{{spell.notes}}')