diff --git a/migrations/20131204_classes.js b/migrations/20131204_classes.js new file mode 100644 index 0000000000..8dddd0a826 --- /dev/null +++ b/migrations/20131204_classes.js @@ -0,0 +1,26 @@ +/** + * Probably the biggest migration of all! This adds the following features: + * - Customization Redo: https://trello.com/c/YKXmHNjY/306-customization-redo + * - Armory: https://trello.com/c/83M5RqQB/299-armory + * - Classes + */ + +db.users.find().forEach(function(user){ + + user.stats.class = 'warrior'; + + // grant backer/contrib gear, 300, rather than using js logic + + // customizations redo: https://trello.com/c/YKXmHNjY/306-customization-redo + + // migrate current owned items + + // gender => size + user.preferences.size = (user.preferences.gender == 'f') ? 'slim' : 'broad'; + delete user.preferences.gender; + + // Delete armorSet + delete user.preferences.armorSet; + + db.users.update({_id:user._id}, user); +}); \ No newline at end of file diff --git a/public/css/header.styl b/public/css/header.styl index a12f1b1408..f5013ec182 100644 --- a/public/css/header.styl +++ b/public/css/header.styl @@ -150,6 +150,8 @@ background: darken($worse, 38%) .experience .bar background: darken($neutral, 30%) + .mana .bar + background: darken($best, 30%) .meter-text position: absolute diff --git a/public/css/index.styl b/public/css/index.styl index 2d1e906f69..3d30dfca9a 100644 --- a/public/css/index.styl +++ b/public/css/index.styl @@ -179,3 +179,12 @@ a .modal-indented-list margin-left: 10px; padding-left: 10px; + +// Spells + +html.applying-action, html.applying-action * + cursor: copy !important +.cast-target:hover + border: 5px solid green !important +.selected-class + background-color: green \ No newline at end of file diff --git a/public/css/npcs.styl b/public/css/npcs.styl index 2c2370808c..f7127f5f72 100644 --- a/public/css/npcs.styl +++ b/public/css/npcs.styl @@ -1,36 +1,27 @@ -.NPC-Alex, .NPC-Bailey, .NPC-Bailey-Head, .NPC-Daniel, .NPC-Justin, .NPC-Justin-Head, .NPC-Matt {background: url("/bower_components/habitrpg-shared/img/npcs/NPC-SpriteSheet.png") no-repeat} +.npc_alex_container + margin-bottom: 20px -.NPC-Alex {background-position: 0 0; width: 162px; height: 138px;} -.NPC-Alex-container{margin-bottom: 20px;} -.NPC-Daniel {background-position: -222px 0; width: 135px; height: 123px} -.NPC-Justin {background-position: -357px 0; width: 84px; height: 119px} -.NPC-Justin-Head {background-position: -396px 0; width: 36px; height: 96px} -.NPC-Matt {background-position: -441px 0; width: 100%; padding-left: 208px; padding-bottom: 20px;} +.npc_matt + margin-bottom: 20px -// Bailey -.NPC-Bailey - background-position: -162px -42px - width: 60px - height: 72px - float:left -.NPC-Bailey-Head - background-position: -162px -42px - width: 60px - height: 30px - position: absolute - top: 117px // 147 (header-height) - 30 (bailey height) - right: 50px - cursor: pointer - - -.static-popover - z-index 0 - display block - position:relative +.npc_bailey + float:left +.npc_bailey_head + height: 30px !important + position: absolute + top: 117px // 147 (header-height) - 30 (bailey height) + right: 50px + cursor: pointer // Tour (Justin) -.NPC-Justin.float-left +.npc_justin.float-left float: left margin-right: 5px margin-bottom: 5px + +.static-popover + z-index 0 + display block + position:relative + .popover-navigation {clear:both;} \ No newline at end of file diff --git a/public/css/tasks.styl b/public/css/tasks.styl index 680fd1ce65..1cd7709df9 100644 --- a/public/css/tasks.styl +++ b/public/css/tasks.styl @@ -402,4 +402,4 @@ form color: #333 &:hover border-top: 0 - margin-top: 2px + margin-top: 2px \ No newline at end of file diff --git a/public/js/controllers/inventoryCtrl.js b/public/js/controllers/inventoryCtrl.js index 236dfe6719..3e94a7f001 100644 --- a/public/js/controllers/inventoryCtrl.js +++ b/public/js/controllers/inventoryCtrl.js @@ -2,6 +2,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', function($rootScope, $scope, User, API_URL, $http, Notification) { var user = User.user; + var Items = window.habitrpgShared.items; // convenience vars since these are accessed frequently @@ -17,11 +18,22 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', $scope.$watch('user.items.hatchingPotions', function(pots){ $scope.potCount = countStacks(pots); }, true); $scope.$watch('user.items.food', function(food){ $scope.foodCount = countStacks(food); }, true); + $scope.$watch('user.items.gear', function(gear){ + $scope.gear = { + base: _.where(Items.items.gear.flat, {klass: 'base'}) + }; + _.each(gear.owned, function(bool,key){ + var item = Items.items.gear.flat[key]; + if (!$scope.gear[item.klass]) $scope.gear[item.klass] = []; + $scope.gear[item.klass].push(item); + }) + }, true) + $scope.chooseEgg = function(egg){ if ($scope.selectedEgg && $scope.selectedEgg.name == egg) { return $scope.selectedEgg = null; // clicked same egg, unselect } - var eggData = _.findWhere(window.habitrpgShared.items.items.eggs, {name:egg}); + var eggData = _.findWhere(Items.items.eggs, {name:egg}); if (!$scope.selectedPotion) { $scope.selectedEgg = eggData; } else { @@ -34,7 +46,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', return $scope.selectedPotion = null; // clicked same egg, unselect } // we really didn't think through the way these things are stored and getting passed around... - var potionData = _.findWhere(window.habitrpgShared.items.items.hatchingPotions, {name:potion}); + var potionData = _.findWhere(Items.items.hatchingPotions, {name:potion}); if (!$scope.selectedEgg) { $scope.selectedPotion = potionData; } else { @@ -171,5 +183,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', User.set('items.currentMount', (user.items.currentMount == mount) ? '' : mount); } + $scope.equip = function(user, item, costume) { + var equipTo = costume ? 'costume' : 'equipped'; + if (item.type == 'shield') { + var weapon = Items.items.gear.flat[user.items.gear[equipTo].weapon]; + if (weapon && weapon.twoHanded) return Notification.text(weapon.text + ' is two-handed'); + } + var setVars = {}; + setVars['items.gear.' + equipTo + '.' + item.type] = item.key; + if (item.twoHanded) + setVars['items.gear.' + equipTo + '.shield'] = 'warrior_shield_0'; + User.setMultiple(setVars); + } + } ]); \ No newline at end of file diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 55ada3cb67..17525266ab 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -3,8 +3,8 @@ /* Make user and settings available for everyone through root scope. */ -habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', - function($scope, $rootScope, $location, User, $http, $state, $stateParams) { +habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', + function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups) { $rootScope.modals = {}; $rootScope.modals.achievements = {}; $rootScope.User = User; @@ -137,5 +137,51 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ chart = new google.visualization.LineChart($("." + id + "-chart")[0]); chart.draw(data, options); }; + + + + /* + ------------------------ + Spells + ------------------------ + */ + $scope.castStart = function(spell) { + if (User.user.stats.mp < spell.mana) return Notification.text("Not enough mana."); + $rootScope.applyingAction = true; + $scope.spell = spell; + if (spell.target == 'self') { + var tasks = User.user.habits.concat(User.user.dailys).concat(User.user.todos); + User.user.tasks = _.object(_.pluck(tasks,'id'), tasks); + $scope.castEnd(null, 'self'); + } else if (spell.target == 'party') { + var party = Groups.party(); + party = (_.isArray(party) ? party : []).concat(User.user); + $scope.castEnd(party, 'party'); + } + } + + $scope.castEnd = function(target, type, $event){ + if ($scope.spell.target != type) return Notification.text("Invalid target"); + $scope.spell.cast(User.user, target); + $http.post('/api/v1/user/cast/' + $scope.spell.name, {target:target, type:type}).success(function(){ + var msg = "You cast " + $scope.spell.text; + switch (type) { + case 'task': msg += ' on ' + target.text;break; + case 'user': msg += ' on ' + target.profile.name;break; + case 'party': msg += ' on the Party';break; + } + Notification.text(msg); + $rootScope.applyingAction = false; + $scope.spell = null; + //User.sync(); // FIXME push a lot of the server code to also in client, so we can run updates in browser without requiring sync + }) + $event && $event.stopPropagation(); + } + +// $rootScope.castCancel = function(){ +// debugger +// $rootScope.applyingAction = false; +// $scope.spell = null; +// } } ]); diff --git a/public/js/controllers/settingsCtrl.js b/public/js/controllers/settingsCtrl.js index 890110e4c4..6d10e0d758 100644 --- a/public/js/controllers/settingsCtrl.js +++ b/public/js/controllers/settingsCtrl.js @@ -74,24 +74,24 @@ habitrpg.controller('SettingsCtrl', $rootScope.$watch('modals.restore', function(value){ if(value === true){ $scope.restoreValues.stats = angular.copy(User.user.stats); - $scope.restoreValues.items = angular.copy(User.user.items); +// $scope.restoreValues.items = angular.copy(User.user.items); $scope.restoreValues.achievements = {streak: User.user.achievements.streak || 0}; } }) $scope.restore = function(){ var stats = $scope.restoreValues.stats, - items = $scope.restoreValues.items, +// items = $scope.restoreValues.items, achievements = $scope.restoreValues.achievements; User.setMultiple({ "stats.hp": stats.hp, "stats.exp": stats.exp, "stats.gp": stats.gp, "stats.lvl": stats.lvl, - "items.weapon": items.weapon, - "items.armor": items.armor, - "items.head": items.head, - "items.shield": items.shield, +// "items.weapon": items.weapon, +// "items.armor": items.armor, +// "items.head": items.head, +// "items.shield": items.shield, "achievements.streak": achievements.streak }); $rootScope.modals.restore = false; diff --git a/public/js/controllers/tasksCtrl.js b/public/js/controllers/tasksCtrl.js index ec3ad92e90..72fb1dcb59 100644 --- a/public/js/controllers/tasksCtrl.js +++ b/public/js/controllers/tasksCtrl.js @@ -104,24 +104,14 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', ' ------------------------ */ - var updateStore = function(){ - var sorted, updated; - updated = window.habitrpgShared.items.updateStore(User.user); - /* Figure out whether we wanna put this in habitrpg-shared - */ + $scope.itemStore = window.habitrpgShared.items.updateStore(User.user); - sorted = [updated.weapon, updated.armor, updated.head, updated.shield, updated.potion]; - $scope.itemStore = sorted; - } - - updateStore(); - - $scope.buy = function(type) { - var hasEnough = window.habitrpgShared.items.buyItem(User.user, type); + $scope.buy = function(item) { + var hasEnough = window.habitrpgShared.items.buyItem(User.user, item); if (hasEnough) { - User.log({op: "buy",type: type}); + User.log({op: "buy", key: item.key}); Notification.text("Item purchased."); - updateStore(); + $scope.itemStore = window.habitrpgShared.items.updateStore(User.user); } else { Notification.text("Not enough Gold!"); } @@ -132,6 +122,13 @@ habitrpg.controller("TasksCtrl", ['$scope', '$rootScope', '$location', 'User', ' User.log({op: 'clear-completed'}); } + + /* + ------------------------ + Ads + ------------------------ + */ + /** * See conversation on http://productforums.google.com/forum/#!topic/adsense/WYkC_VzKwbA, * Adsense is very sensitive. It must be called once-and-only-once for every , else things break. diff --git a/public/js/controllers/userCtrl.js b/public/js/controllers/userCtrl.js index 70a950c9cd..e0c52fdba2 100644 --- a/public/js/controllers/userCtrl.js +++ b/public/js/controllers/userCtrl.js @@ -11,6 +11,59 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ if(value === true) $scope.editingProfile = angular.copy(User.user.profile); }); + $scope.allocate = function(stat){ + var setObj = {} + setObj['stats.' + stat] = User.user.stats[stat] + 1; + setObj['stats.points'] = User.user.stats.points - 1; + User.setMultiple(setObj); + } + + $scope.rerollClass = function(){ + if (!confirm("Are you sure you want to re-roll? This will reset your character's class and allocated points (you'll get them all back to re-allocate)")) + return; + User.setMultiple({ + 'flags.classSelected': false, + //'stats.points': this is handled on the server + 'stats.str': 0, + 'stats.def': 0, + 'stats.per': 0, + 'stats.int': 0 + }) + } + $scope.rerollSubmit = function(){ + var klass = $scope.selectedClass; + var setVars = { + "stats.class": klass, + "flags.classSelected": true + }; + + // Clear their gear and equip their new class's gear (can still equip old gear from inventory) + // If they've rolled this class before, restore their progress + _.each(['weapon', 'armor','shield','head'], function(type){ + var foundKey = false; + _.findLast(User.user.items.gear.owned, function(v,k){ + if (~k.indexOf(type + '_' + klass)) { + foundKey = k; + return true; + } + }); + setVars['items.gear.equipped.' + type] = + foundKey ? foundKey : // restore progress from when they last rolled this class + (type == 'weapon') ? 'weapon_' + klass + '_0' : // weapon_0 is significant, don't reset to base_0 + (type == 'shield' && klass == 'rogue') ? 'shield_rogue_0' : // rogues start with an off-hand weapon + type + '_base_0'; // naked for the rest! + + // Grant them their new class's gear + if (type == 'weapon' || (type == 'shield' && klass == 'rogue')) + setVars['items.gear.owned.' + type + '_' + klass + '_0'] = true; + }); + + User.setMultiple(setVars); + $scope.selectedClass = undefined; + + //FIXME run updateStore (we need to access a different scope) + } + $scope.save = function(){ var values = {}; _.each($scope.editingProfile, function(value, key){ diff --git a/public/js/services/guideServices.js b/public/js/services/guideServices.js index 490b3de84d..cdb3fc034c 100644 --- a/public/js/services/guideServices.js +++ b/public/js/services/guideServices.js @@ -74,7 +74,7 @@ angular.module('guideServices', []). } ]; _.each(tourSteps, function(step){ - step.content = "
" + step.content + "
"; // add Justin NPC img + step.content = "
" + step.content + "
"; // add Justin NPC img }); $('.main-herobox').popover('destroy'); var tour = new Tour({ @@ -97,7 +97,7 @@ angular.module('guideServices', []). if (!placement) placement = 'bottom'; $(selector).popover('destroy'); var button = ""; - html = "
" + html + '
' + button + '
'; + html = "
" + html + '
' + button + '
'; $(selector).popover({ title: title, placement: placement, diff --git a/public/js/services/userServices.js b/public/js/services/userServices.js index c945cb3cdd..26308ee955 100644 --- a/public/js/services/userServices.js +++ b/public/js/services/userServices.js @@ -23,6 +23,7 @@ angular.module('userServices', []). //first we populate user with schema _.extend(user, $window.habitrpgShared.helpers.newUser()); user.apiToken = user._id = ''; // we use id / apitoken to determine if registered + $window.habitrpgShared.algos.defineComputed(user); //than we try to load localStorage if (localStorage.getItem(STORAGE_USER_ID)) { diff --git a/src/controllers/user.js b/src/controllers/user.js index 9738273967..fcae5a6596 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -203,13 +203,12 @@ api.clearCompleted = function(req, res, next) { ------------------------------------------------------------------------ */ api.buy = function(req, res, next) { - var hasEnough, type, user; - user = res.locals.user; - type = req.params.type; - if (type !== 'weapon' && type !== 'armor' && type !== 'head' && type !== 'shield' && type !== 'potion') { - return res.json(400, {err: ":type must be in one of: 'weapon', 'armor', 'head', 'shield', 'potion'"}); + var user = res.locals.user; + var key = req.params.key; + if (key !== 'potion' && !items.items.gear.flat[key]) { + return res.json(400, {err: ":item must be a supported key, see https://github.com/HabitRPG/habitrpg-shared/blob/master/script/items.coffee"}); } - hasEnough = items.buyItem(user, type); + var hasEnough = items.buyItem(user, items.items.gear.flat[key]); if (hasEnough) { return user.save(function(err, saved) { if (err) return res.json(500, {err: err}); @@ -484,6 +483,70 @@ api.deleteTag = function(req, res){ } } +/* + ------------------------------------------------------------------------ + Spells + ------------------------------------------------------------------------ + */ +api.cast = function(req, res) { + var user = res.locals.user; + var type = req.body.type, target = req.body.target; + var spell = items.items.spells[user.stats.class][req.params.spell]; + + var done = function(){ + var err = arguments[0]; + var saved = _.size(arguments == 3) ? arguments[2] : arguments[1]; + if (err) return res.json(500, {err:err}); + res.json(saved); + } + + switch (type) { + case 'task': + spell.cast(user, user.tasks[target.id]); + user.save(done); + break; + + case 'self': + spell.cast(user); + user.save(done); + break; + + case 'party': + async.waterfall([ + function(cb){ + Group.findOne({type: 'party', members: {'$in': [user._id]}}).populate('members').exec(cb); + }, + function(group, cb) { + if (!group) group = {members:[user]}; + spell.cast(user, group.members); + var series = _.transform(group.members, function(m,v,k){ + m[k] = function(cb2){v.save(cb2);} + }); + async.series(series, cb); + }, + function(whatever, cb){ + user.save(cb); + } + ], done); + break; + + case 'user': + async.waterfall([ + function(cb) { + User.findById(target._id, cb); + }, + function(member, cb) { + spell.cast(user, member); + member.save(cb); // not parallel because target could be user, which causes race condition when saving + }, + function(saved, num, cb) { + user.save(cb); + } + ], done); + break; + } +} + /* ------------------------------------------------------------------------ Batch Update @@ -502,6 +565,7 @@ api.batchUpdate = function(req, res, next) { req.params.id = action.data && action.data.id; req.params.direction = action.dir; req.params.type = action.type; + req.params.key = action.key; req.body = action.data; res.send = res.json = function(code, data) { if (_.isNumber(code) && code >= 400) { diff --git a/src/models/user.js b/src/models/user.js index a3d76cfd68..10e548d11e 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -94,7 +94,8 @@ var UserSchema = new Schema({ rewrite: {type: Boolean, 'default': true}, partyEnabled: Boolean, // FIXME do we need this? rest: {type: Boolean, 'default': false}, // fixme - change to preferences.resting once we're off derby - contributor: Boolean + contributor: Boolean, + classSelected: {type: Boolean, 'default': false} }, history: { exp: Array, // [{date: Date, value: Number}], // big peformance issues if these are defined @@ -107,10 +108,26 @@ var UserSchema = new Schema({ party: Schema.Types.Mixed }, items: { - armor: Number, - weapon: Number, - head: Number, - shield: Number, + gear: { + owned: _.transform(items.items.gear.flat, function(m,v,k){ + m[v.key] = {type: Boolean}; + if (v.key.match(/[weapon|armor|head|shield]_warrior_0/)) + m[v.key]['default'] = true; + }), + + equipped: { + weapon: {type: String, 'default': 'weapon_warrior_0'}, + armor: {type: String, 'default': 'armor_base_0'}, + head: {type: String, 'default': 'head_base_0'}, + shield: {type: String, 'default': 'shield_base_0'} + }, + costume: { + weapon: {type: String, 'default': 'weapon_base_0'}, + armor: {type: String, 'default': 'armor_base_0'}, + head: {type: String, 'default': 'head_base_0'}, + shield: {type: String, 'default': 'shield_base_0'} + }, + }, // -------------- Animals ------------------- // Complex bit here. The result looks like: @@ -185,16 +202,20 @@ var UserSchema = new Schema({ preferences: { armorSet: String, dayStart: {type:Number, 'default': 0}, - gender: {type:String, 'default': 'm'}, - hair: {type:String, 'default':'blond'}, + size: {type:String, enum: ['broad','slim'], 'default': 'broad'}, + hair: { + color: {type: String, 'default': 'blond'}, + base: {type: Number, 'default': 0}, + bangs: {type: Number, 'default': 0}, + beard: {type: Number, 'default': 0}, + mustach: {type: Number, 'default': 0}, + }, hideHeader: {type:Boolean, 'default':false}, - showHelm: {type:Boolean, 'default':true}, - showWeapon: {type:Boolean, 'default':true}, - showShield: {type:Boolean, 'default':true}, - showArmor: {type:Boolean, 'default':true}, skin: {type:String, 'default':'white'}, timezoneOffset: Number, - language: String + language: String, + automaticAllocation: Boolean, + useCostume: Boolean }, profile: { blurb: String, @@ -202,10 +223,25 @@ var UserSchema = new Schema({ name: String, }, stats: { - hp: Number, - exp: Number, - gp: Number, - lvl: Number + hp: {type: Number, 'default': 50}, + mp: {type: Number, 'default': 10}, + exp: {type: Number, 'default': 0}, + gp: {type: Number, 'default': 0}, + lvl: {type: Number, 'default': 1}, + + // Class System + 'class': {type: String, enum: ['warrior','rogue','wizard','healer'], 'default': 'warrior'}, + points: {type: Number, 'default': 0}, + str: {type: Number, 'default': 0}, + con: {type: Number, 'default': 0}, + int: {type: Number, 'default': 0}, + per: {type: Number, 'default': 0}, + buffs: { + str: Number, + def: Number, + per: Number, + stealth: Number + } }, tags: [ { @@ -268,10 +304,15 @@ UserSchema.pre('save', function(next) { 'Anonymous'; } + // FIXME handle this on level-up instead, and come up with how we're going to handle retroactively + // Actually, can this be used as an attr default? (schema {type: ..., 'default': function(){}}) + this.stats.points = this.stats.lvl - (this.stats.con + this.stats.str + this.stats.per + this.stats.int); + var petCount = helpers.countPets(_.reduce(this.items.pets,function(m,v){ //HOTFIX - Remove when solution is found, the first argument passed to reduce is a function if(_.isFunction(v)) return m; return m+(v?1:0)},0), this.items.pets); + this.achievements.beastMaster = petCount >= 90; //our own version incrementer diff --git a/src/routes/api.js b/src/routes/api.js index 6fed614a2c..f63b8b6fe5 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -49,7 +49,7 @@ if (nconf.get('NODE_ENV') == 'development') { } /* Items*/ -router.post('/user/buy/:type', auth.auth, cron, user.buy); +router.post('/user/buy/:key', auth.auth, cron, user.buy); /* User*/ router.get('/user', auth.auth, cron, user.getUser); @@ -61,6 +61,7 @@ router.post('/user/buy-gems', auth.auth, user.buyGems); router.post('/user/buy-gems/paypal-ipn', user.buyGemsPaypalIPN); router.post('/user/unlock', auth.auth, cron, user.unlock); router.post('/user/reset', auth.auth, user.reset); +router.post('/user/cast/:spell', auth.auth, user.cast); router['delete']('/user', auth.auth, user['delete']); /* Tags */ diff --git a/views/index.jade b/views/index.jade index 733bdca2ae..637586e75b 100644 --- a/views/index.jade +++ b/views/index.jade @@ -1,10 +1,11 @@ doctype 5 -html +//html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keypress="{27:'castCancel()'}") +html(ng-app="habitrpg", ng-controller="RootCtrl", ng-class='{"applying-action":applyingAction}', ui-keypress="{27:'castCancel()'}") head title HabitRPG | Your Life The Role Playing Game // ?v=1 needed to force refresh - link(rel='shortcut icon' href='#{env.getBuildUrl("favicon.ico")}?v=2') + link(rel='shortcut icon', href='#{env.getBuildUrl("favicon.ico")}?v=2') script(type='text/javascript'). window.env = !{JSON.stringify(env)}; @@ -21,7 +22,7 @@ html meta(name='viewport', content='width=device-width') meta(name='apple-mobile-web-app-capable', content='yes') - body(ng-app="habitrpg", ng-controller="RootCtrl", ng-cloak) + body(ng-cloak) div(ng-controller='GroupsCtrl') include ./shared/modals/index include ./shared/header/header diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index b6841f2c64..c1fee38b2c 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -5,6 +5,20 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') p.well Click an egg to see usable potions highlighted in green and then click one of the highlighted potions to hatch your pet. If no potions are highlighted, click that egg again to deselect it, and instead click a potion first to have the usable eggs highlighted. You can also sell unwanted drops to Alexander the Merchant. menu.inventory-list(type='list') + h4 Gear + li.customize-menu + menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]') + div(ng-repeat='item in gear[klass]') + button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='equip(user,item)', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.equipped[item.type] == item.key}') + label.checkbox.inline + input(type="checkbox", ng-model="user.preferences.costume") + | Use Costume  + i.icon-question-sign(popover="Show something different on your avatar than the gear you have equipped for battle", popover-trigger='mouseenter', popover-placement='right') + li.customize-menu(ng-if='user.preferences.costume') + menu.pets-menu(label='{{label}}', ng-repeat='(klass,label) in {base:"Base", warrior:"Warrior", wizard:"Wizard", rogue:"Rogue", special:"Special"}', ng-show='gear[klass]') + div(ng-repeat='item in gear[klass]') + button.customize-option(popover='{{item.notes}}', popover-title='{{item.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='equip(user,item, true)', class='shop_{{item.key}}', ng-class='{selectableInventory: user.items.gear.costume[item.type] == item.key}') + li.customize-menu menu.pets-menu(label='Eggs ({{eggCount}})') p(ng-show='eggCount < 1') You don't have any eggs. @@ -33,10 +47,10 @@ script(type='text/ng-template', id='partials/options.inventory.inventory.html') .span6 h2 Market .row-fluid - table.NPC-Alex-container + table.npc_alex_container tr td - .NPC-Alex.pull-left + .npc_alex.pull-left td .popover.static-popover.fade.right.in .arrow diff --git a/views/options/inventory/stable.jade b/views/options/inventory/stable.jade index 765412a16c..c5656b844c 100644 --- a/views/options/inventory/stable.jade +++ b/views/options/inventory/stable.jade @@ -12,15 +12,19 @@ script(type='text/ng-template', id='partials/options.inventory.stable.html') script(type='text/ng-template', id='partials/options.inventory.stable.mounts.html') .stable - .NPC-Matt - .popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;') - .arrow - h3.popover-title - a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch - .popover-content - p. - Shall I bring you your steed, {{user.profile.name}}? Click a mount to saddle up. - h4 {{mountCount}} / {{totalPets}} Mounts Tamed + table(style='width:100%') + tr + td(style='width:144px') + .npc_matt + td + .popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;') + .arrow + h3.popover-title + a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch + .popover-content + p. + Shall I bring you your steed, {{user.profile.name}}? Click a mount to saddle up. + h4 {{mountCount}} / {{totalPets}} Mounts Tamed menu.pets(type='list') li.customize-menu(ng-repeat='egg in Items.eggs') menu @@ -28,7 +32,7 @@ script(type='text/ng-template', id='partials/options.inventory.stable.mounts.htm button(class="pet-button Mount_Head_{{mount}}", ng-show='user.items.mounts[mount]', ng-class='{active: user.items.currentMount == mount}', ng-click='chooseMount(egg.name, potion.name)') //div(class='Mount_Head_{{mount}}') button(class="pet-button pet-not-owned", ng-hide='user.items.mounts[mount]') - img(src='/bower_components/habitrpg-shared/img/PixelPaw.png') + .PixelPaw h4 Rare Mounts menu @@ -40,15 +44,19 @@ script(type='text/ng-template', id='partials/options.inventory.stable.mounts.htm script(type='text/ng-template', id='partials/options.inventory.stable.pets.html') .stable - .NPC-Matt - .popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;') - .arrow - h3.popover-title - a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch - .popover-content - p. - Welcome to the Stable! I'm Matt, the beast master. Choose a pet here to venture at your side. Feed them and they'll grow into powerful steeds. Have a look-see at all the pets you can collect. - h4 {{petCount}} / {{totalPets}} Pets Found + table(style='width:100%') + tr + td(style='width:144px') + .npc_matt + td + .popover.static-popover.fade.right.in(style='max-width: 550px; margin-left: 10px;') + .arrow + h3.popover-title + a(target='_blank', href='http://www.kickstarter.com/profile/mattboch') Matt Boch + .popover-content + p. + Welcome to the Stable! I'm Matt, the beast master. Choose a pet here to venture at your side. Feed them and they'll grow into powerful steeds. Have a look-see at all the pets you can collect. + h4 {{petCount}} / {{totalPets}} Pets Found menu.pets(type='list') li.customize-menu(ng-repeat='egg in Items.eggs') @@ -58,7 +66,7 @@ script(type='text/ng-template', id='partials/options.inventory.stable.pets.html' .progress(ng-class='{"progress-success": user.items.pets[pet]<50}') .bar(style="width: {{user.items.pets[pet]/.5}}%;") button(class="pet-button pet-not-owned", ng-if='!user.items.pets[pet]') - img(src='/bower_components/habitrpg-shared/img/PixelPaw.png') + .PixelPaw h4 Rare Pets menu @@ -69,7 +77,7 @@ script(type='text/ng-template', id='partials/options.inventory.stable.pets.html' button(ng-if='user.items.pets["Turkey-Base"]', class="pet-button Pet-Turkey-Base", ng-class='{active: user.items.currentPet == "Turkey-Base"}', ng-click='choosePet("Turkey", "Base")', popover='Turkey', popover-trigger='mouseenter', popover-placement='bottom') a(target='_blank', href='http://habitrpg.wikia.com/wiki/Contributing_to_HabitRPG') button(ng-if='!user.items.pets["Dragon-Hydra"]', class="pet-button pet-not-owned", popover-trigger='mouseenter', popover-placement='right', popover="Click the gold paw to learn more about how you can obtain this rare pet through contributing to HabitRPG!", popover-title='How to Get this Pet!') - img(src='/bower_components/habitrpg-shared/img/PixelPaw-Gold.png') + .PixelPaw-Gold .well.food-tray p(ng-show='foodCount < 1') You don't have any food yet. diff --git a/views/options/profile.jade b/views/options/profile.jade index b3ae3da80e..b0d32c24a0 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -6,66 +6,83 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') li.customize-menu menu(label='Head') menu - button.m_head_0.customize-option(type='button', ng-click='set("preferences.gender","m")') - button.f_head_0.customize-option(type='button', ng-click='set("preferences.gender","f")') - label.checkbox - input(type='checkbox', ng-model='user.preferences.showHelm', ng-change='set("preferences.showHelm", user.preferences.showHelm?true: false)') - | Show Helm - menu(type='list') - li.customize-menu - menu(label='Clothing') - button.f_armor_0_v1.customize-option(ng-show='user.preferences.gender=="f"', type='button', ng-click='set("preferences.armorSet","v1")') - button.f_armor_0_v2.customize-option(ng-show='user.preferences.gender=="f"', type='button', ng-click='set("preferences.armorSet","v2")') - label.checkbox - input(type='checkbox', ng-model='user.preferences.showArmor', ng-change='set("preferences.showArmor", user.preferences.showArmor?true: false)') - | Show Armor - menu(type='list') - li.customize-menu - menu(label='Other') - label.checkbox - input(type='checkbox', ng-model='user.preferences.showWeapon', ng-change='set("preferences.showWeapon", user.preferences.showWeapon?true: false)') - | Show Weapon - label.checkbox - input(type='checkbox', ng-model='user.preferences.showShield', ng-change='set("preferences.showShield", user.preferences.showShield?true: false)') - | Show Shield - + button.broad_armor_base_0.customize-option(type='button', ng-click='set("preferences.size","broad")') + button.slim_armor_base_0.customize-option(type='button', ng-click='set("preferences.size","slim")') .span4 - // hair - li.customize-menu - menu(label='Hair') - button(class='{{user.preferences.gender}}_hair_blond customize-option', type='button', ng-click='set("preferences.hair","blond")') - button(class='{{user.preferences.gender}}_hair_black customize-option', type='button', ng-click='set("preferences.hair","black")') - button(class='{{user.preferences.gender}}_hair_brown customize-option', type='button', ng-click='set("preferences.hair","brown")') - button(class='{{user.preferences.gender}}_hair_white customize-option', type='button', ng-click='set("preferences.hair","white")') - button(class='{{user.preferences.gender}}_hair_red customize-option', type='button', ng-click='set("preferences.hair","red")') + h3 Hair + menu(type='list') + // Color + li.customize-menu + menu(label='Color') + button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#c8c8c8;', ng-click='set("preferences.hair.color", "white")') + button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#903a00;', ng-click='set("preferences.hair.color", "brown")') + button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#cfb853;', ng-click='set("preferences.hair.color", "blond")') + button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#ec720f;', ng-click='set("preferences.hair.color", "red")') + button(type='button', class='customize-option', style='width: 40px; height: 40px; background-color:#2e2e2e;', ng-click='set("preferences.hair.color", "black")') + + // Bangs + li.customize-menu + menu(label='Bangs') + button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.bangs",0)') + button(class='hair_bangs_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",1)') + button(class='hair_bangs_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",2)') + button(class='hair_bangs_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.bangs",3)') + + // Beard + li.customize-menu + menu(label='Beard') + button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.beard",0)') + button(class='hair_beard_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",1)') + button(class='hair_beard_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",2)') + button(class='hair_beard_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.beard",3)') + + // Mustache + li.customize-menu + menu(label='Mustache') + button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.mustache",0)') + button(class='hair_mustache_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.mustache",1)') + button(class='hair_mustache_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.mustache",2)') + + // Base + li.customize-menu + menu(label='Base') + button(class='head_base_0 customize-option', type='button', ng-click='set("preferences.hair.base",0)') + button(class='hair_base_1_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",1)') + button(class='hair_base_2_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",2)') + button(class='hair_base_3_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",3)') + button(class='hair_base_4_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",4)') + button(class='hair_base_5_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",5)') + button(class='hair_base_6_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",6)') + button(class='hair_base_7_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",7)') + button(class='hair_base_8_{{user.preferences.hair.color}} customize-option', type='button', ng-click='set("preferences.hair.base",8)') .span4 // skin li.customize-menu menu(label='Basic Skins') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_asian', ng-click='set("preferences.skin","asian")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_white', ng-click='set("preferences.skin","white")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ea8349', ng-click='set("preferences.skin","ea8349")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_c06534', ng-click='set("preferences.skin","c06534")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_98461a', ng-click='set("preferences.skin","98461a")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_black', ng-click='set("preferences.skin","black")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_dead', ng-click='set("preferences.skin","dead")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_orc', ng-click='set("preferences.skin","orc")') + button.customize-option(type='button', class='skin_asian', ng-click='set("preferences.skin","asian")') + button.customize-option(type='button', class='skin_white', ng-click='set("preferences.skin","white")') + button.customize-option(type='button', class='skin_ea8349', ng-click='set("preferences.skin","ea8349")') + button.customize-option(type='button', class='skin_c06534', ng-click='set("preferences.skin","c06534")') + button.customize-option(type='button', class='skin_98461a', ng-click='set("preferences.skin","98461a")') + button.customize-option(type='button', class='skin_black', ng-click='set("preferences.skin","black")') + button.customize-option(type='button', class='skin_dead', ng-click='set("preferences.skin","dead")') + button.customize-option(type='button', class='skin_orc', ng-click='set("preferences.skin","orc")') // Rainbow Skin h5. Rainbow Skins - 2/skin //menu(label='Rainbow Skins (2G / skin)') menu - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_eb052b', ng-class='{locked: !user.purchased.skin.eb052b}', ng-click='unlock("skin.eb052b")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f69922', ng-class='{locked: !user.purchased.skin.f69922}', ng-click='unlock("skin.f69922")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_f5d70f', ng-class='{locked: !user.purchased.skin.f5d70f}', ng-click='unlock("skin.f5d70f")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_0ff591', ng-class='{locked: !user.purchased.skin.0ff591}', ng-click='unlock("skin.0ff591")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_2b43f6', ng-class='{locked: !user.purchased.skin.2b43f6}', ng-click='unlock("skin.2b43f6")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")') + button.customize-option(type='button', class='skin_eb052b', ng-class='{locked: !user.purchased.skin.eb052b}', ng-click='unlock("skin.eb052b")') + button.customize-option(type='button', class='skin_f69922', ng-class='{locked: !user.purchased.skin.f69922}', ng-click='unlock("skin.f69922")') + button.customize-option(type='button', class='skin_f5d70f', ng-class='{locked: !user.purchased.skin.f5d70f}', ng-click='unlock("skin.f5d70f")') + button.customize-option(type='button', class='skin_0ff591', ng-class='{locked: !user.purchased.skin.0ff591}', ng-click='unlock("skin.0ff591")') + button.customize-option(type='button', class='skin_2b43f6', ng-class='{locked: !user.purchased.skin.2b43f6}', ng-click='unlock("skin.2b43f6")') + button.customize-option(type='button', class='skin_d7a9f7', ng-class='{locked: !user.purchased.skin.d7a9f7}', ng-click='unlock("skin.d7a9f7")') + button.customize-option(type='button', class='skin_800ed0', ng-class='{locked: !user.purchased.skin.800ed0}', ng-click='unlock("skin.800ed0")') + button.customize-option(type='button', class='skin_rainbow', ng-class='{locked: !user.purchased.skin.rainbow}', ng-click='unlock("skin.rainbow")') button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.eb052b && user.purchased.skin.f69922 && user.purchased.skin.f5d70f && user.purchased.skin.0ff591 && user.purchased.skin.2b43f6 && user.purchased.skin.d7a9f7 && user.purchased.skin.800ed0 && user.purchased.skin.rainbow', ng-click='unlock(["skin.eb052b", "skin.f69922", "skin.f5d70f", "skin.0ff591", "skin.2b43f6", "skin.d7a9f7", "skin.800ed0", "skin.rainbow"])') Unlock Set - 5 // Special Events @@ -73,18 +90,53 @@ script(id='partials/options.profile.avatar.html', type='text/ng-template') div(ng-if='user.purchased.skin.monster || user.purchased.skin.pumpkin || user.purchased.skin.skeleton || user.purchased.skin.zombie || user.purchased.skin.ghost || user.purchased.skin.shadow') h5 Spooky Skins menu - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_monster', ng-if='user.purchased.skin.monster', ng-click='unlock("skin.monster")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_pumpkin', ng-if='user.purchased.skin.pumpkin', ng-click='unlock("skin.pumpkin")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_skeleton', ng-if='user.purchased.skin.skeleton', ng-click='unlock("skin.skeleton")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_zombie', ng-if='user.purchased.skin.zombie', ng-click='unlock("skin.zombie")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ghost', ng-if='user.purchased.skin.ghost', ng-click='unlock("skin.ghost")') - button.customize-option(type='button', class='{{user.preferences.gender}}_skin_shadow', ng-if='user.purchased.skin.shadow', ng-click='unlock("skin.shadow")') + button.customize-option(type='button', class='skin_monster', ng-if='user.purchased.skin.monster', ng-click='unlock("skin.monster")') + button.customize-option(type='button', class='skin_pumpkin', ng-if='user.purchased.skin.pumpkin', ng-click='unlock("skin.pumpkin")') + button.customize-option(type='button', class='skin_skeleton', ng-if='user.purchased.skin.skeleton', ng-click='unlock("skin.skeleton")') + button.customize-option(type='button', class='skin_zombie', ng-if='user.purchased.skin.zombie', ng-click='unlock("skin.zombie")') + button.customize-option(type='button', class='skin_ghost', ng-if='user.purchased.skin.ghost', ng-click='unlock("skin.ghost")') + button.customize-option(type='button', class='skin_shadow', ng-if='user.purchased.skin.shadow', ng-click='unlock("skin.shadow")') script(id='partials/options.profile.stats.html', type='text/ng-template') .row-fluid - .span6.border-right + .border-right(ng-class='user.stats.lvl < 5 ? "span6" : "span4"') include ../shared/profiles/stats - .span6.border-right + .span4.border-right(ng-show='user.stats.lvl >= 5') + h4 + | {{user.stats.class}}  + a.btn.btn-danger.btn-mini(ng-click='rerollClass()') Re-roll + h6 Points: {{user.stats.points}} + fieldset + label.checkbox + input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set("preferences.automaticAllocation", user.preferences.automaticAllocation?true: false)') + | Automatic Allocation + i.icon-question-sign(popover-trigger='mouseenter', popover-placement='bottom', popover="When 'automatic' is checked, your points will be allocated to the stat representing your task focus (see Task > Edit). When unchecked, you'll have one point to allocate each level. The system makes a suggestion, but you can ignore the suggestion.") + table.table.table-striped + tr + td + i.icon-question-sign(popover-title='Strength', popover='Strength increases damage you inflict to tasks, reducing their redness', popover-trigger='mouseenter') + |  STR: {{user.stats.str}} + td + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("str")') + + tr + td + i.icon-question-sign(popover-title='Defense', popover='Defense decreases damage inflicted by tasks, and increases the effectiveness of healing and buff spells', popover-trigger='mouseenter') + |  DEF: {{user.stats.def}} + td + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("con")') + + tr + td + i.icon-question-sign(popover-title='Perception', popover='Perception increases the likelihood of drops, Gold bonuses, and critical hit chances', popover-trigger='mouseenter') + |  PER: {{user.stats.per}} + td + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("per")') + + tr + td + i.icon-question-sign(popover-title='Intelligence', popover='Intelligence increases your overall mana, allowing you to cast more spells', popover-trigger='mouseenter') + |  INT: {{user.stats.int}} + td + a.btn.btn-primary(ng-show='user.stats.points', ng-click='allocate("int")') + + .border-right(ng-class='user.stats.lvl < 5 ? "span6" : "span4"') include ../shared/profiles/achievements script(id='partials/options.profile.profile.html', type='text/ng-template') diff --git a/views/options/social/group.jade b/views/options/social/group.jade index 9371e3567d..17ef4cbde3 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -1,7 +1,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank', popover='Gems which your guild leader can use for challenge prizes.', popover-placement='left', popover-html='true') // span.task-action-btn.tile.flush.neutral - .Gems + .Pet_Currency_Gem2x.Gems | {{group.balance * 4 | number:0 }} Guild Gems .row-fluid .span4 diff --git a/views/options/social/tavern.jade b/views/options/social/tavern.jade index be289c8ff4..c12fdb1aef 100644 --- a/views/options/social/tavern.jade +++ b/views/options/social/tavern.jade @@ -4,7 +4,7 @@ table tr td - .NPC-Daniel + .npc_daniel td .popover.static-popover.fade.right.in .arrow @@ -62,23 +62,23 @@ When your first submission is deployed, you will receive the HabitRPG Contributor's badge. Your name in Tavern chat will proudly display that you are a contributor. As a bounty for your work, you will also receive 2 Gems. hr p. - When your second submission is deployed, the Crystal Armor will be available for purchase in the Rewards shop after the Golden Armor. As a bounty for your continued work, you will also receive 2 Gems. + When your second submission is deployed, the Crystal Armor will be available for purchase in the Rewards shop after the Golden Armor. As a bounty for your continued work, you will also receive 2 Gems. tr td a.label.label-contributor-3(ng-click='toggleUserTier($event)') Elite (3-4) div(style='display:none;') p. - When your third submission is deployed, the Crystal Helmet will be available for purchase in the Rewards shop after the Golden Helmet. As a bounty for your great work, you will also receive 2 Gems. + When your third submission is deployed, the Crystal Helmet will be available for purchase in the Rewards shop after the Golden Helmet. As a bounty for your great work, you will also receive 2 Gems. hr p. - When your fourth submission is deployed, the Crystal Sword will be available for purchase in the Rewards shop after the Golden Sword. You will once again receive a bounty of 2 Gems. + When your fourth submission is deployed, the Crystal Sword will be available for purchase in the Rewards shop after the Golden Sword. You will once again receive a bounty of 2 Gems. tr td a.label.label-contributor-5(ng-click='toggleUserTier($event)') Champion (5-6) div(style='display:none;') p. - When your fifth submission is deployed, the Crystal Shield will be available for purchase in the Reward shop after the Golden Shield. You will also receive 2 Gems. + When your fifth submission is deployed, the Crystal Shield will be available for purchase in the Reward shop after the Golden Shield. You will also receive 2 Gems. hr p.
When your sixth submission is deployed, you will receive a Hydra Pet. You will also receive 2 Gems. diff --git a/views/shared/gems.jade b/views/shared/gems.jade index 0ef01f96c4..fc41724843 100644 --- a/views/shared/gems.jade +++ b/views/shared/gems.jade @@ -1,5 +1,5 @@ a.pull-right.gem-wallet(ng-click='modals.buyGems = true', popover-trigger='mouseenter', popover-title='Gems', popover="Used for buying special items and services (eggs, hatching potions, Fortify, etc.). You'll need to unlock those features before being able to use Gems.", popover-placement='bottom') span.task-action-btn.tile.flush.bright.add-gems-btn + span.task-action-btn.tile.flush.neutral - .Gems + .Pet_Currency_Gem2x.Gems | {{user.balance * 4 | number:0}} Gems \ No newline at end of file diff --git a/views/shared/header/avatar.jade b/views/shared/header/avatar.jade index 8655764885..dce75fa564 100644 --- a/views/shared/header/avatar.jade +++ b/views/shared/header/avatar.jade @@ -3,28 +3,40 @@ Removing it will remove the user's name, but will allow more room for mounts & helms. IMO this is the lesser of two evils, revisit //-figure.herobox(ng-click='clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: profile.id==user.id, hasPet: profile.items.currentPet}', data-level='{{profile.stats.lvl}}', data-uid='{{profile.id}}', rel='popover', data-placement='bottom', data-trigger='hover', data-html='true', data-content="
Level: {{profile.stats.lvl}}
GP: {{profile.stats.gp | number:0}}
{{count(profile.items.pets)}} / 90 Pets Found
") -figure.herobox(ng-click='clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: user._id==profile._id && !(user.items.currentMount && user.items.currentPet), hasPet: profile.items.currentPet && profile.items.currentMount, isLeader: party.leader==profile._id}') +figure.herobox(ng-click='spell ? castEnd(profile, "user", $event) : clickMember(profile._id)', data-name='{{profile.profile.name}}', ng-class='{isUser: user._id==profile._id && !(user.items.currentMount && user.items.currentPet), hasPet: profile.items.currentPet && profile.items.currentMount, "cast-target": applyingAction, isLeader: party.leader==profile._id}') .character-sprites // Mount Body span(ng-if='profile.items.currentMount', class='Mount_Body_{{profile.items.currentMount}}') // Avatar - span(class='{{profile.preferences.gender}}_skin_{{profile.preferences.skin}}') - span(class='{{profile.preferences.gender}}_hair_{{profile.preferences.hair}}') - span(class='{{equipped("armor", 0, profile.preferences, profile.backer, profile.contributor)}}') - span(class='{{equipped("armor", profile.items.armor, profile.preferences, profile.backer, profile.contributor)}}', ng-show='profile.preferences.showArmor') - span(class='{{profile.preferences.gender}}_head_0', ng-hide='profile.preferences.showHelm') - span(class='{{equipped("head", profile.items.head, profile.preferences, profile.backer, profile.contributor)}}', ng-show='profile.preferences.showHelm') - span(class='{{equipped("shield",profile.items.shield,profile.preferences, profile.backer, profile.contributor)}}', ng-show='profile.preferences.showShield') - span(class='{{equipped("weapon",profile.items.weapon,profile.preferences, profile.backer, profile.contributor)}}', ng-show='profile.preferences.showWeapon') + span(class='skin_{{profile.preferences.skin}}') + + span(class='{{profile.preferences.size}}_{{profile.items.gear.equipped.armor}}', ng-if='!profile.preferences.costume') + span(class='{{profile.preferences.size}}_{{profile.items.gear.costume.armor}}', ng-if='profile.preferences.costume') + + span(class='head_base_0') + span(class='hair_base_{{profile.preferences.hair.base}}_{{profile.preferences.hair.color}}') + span(class='hair_bangs_{{profile.preferences.hair.bangs}}_{{profile.preferences.hair.color}}') + + span(class='hair_beard_{{profile.preferences.hair.beard}}_{{profile.preferences.hair.color}}') + span(class='hair_mustache_{{profile.preferences.hair.mustache}}_{{profile.preferences.hair.color}}') + + span(class='{{profile.items.gear.equipped.head}}', ng-if='!profile.preferences.costume') + span(class='{{profile.items.gear.costume.head}}', ng-if='profile.preferences.costume') + + + span(class='{{profile.items.gear.equipped.shield}}', ng-if='!profile.preferences.costume') + span(class='{{profile.items.gear.costume.shield}}', ng-if='profile.preferences.costume') + + span(class='{{profile.items.gear.equipped.weapon}}', ng-if='!profile.preferences.costume') + span(class='{{profile.items.gear.costume.weapon}}', ng-if='profile.preferences.costume') + // Mount Head span(ng-if='profile.items.currentMount', class='Mount_Head_{{profile.items.currentMount}}') // Resting span(ng-class='{zzz:profile.flags.rest}') - - // Pet // FIXME handle @minimal, this might have to be a directive span.current-pet(class='Pet-{{profile.items.currentPet}}', ng-show='profile.items.currentPet && !minimal') .avatar-level(ng-class='userLevelStyle(profile,"label")') diff --git a/views/shared/header/header.jade b/views/shared/header/header.jade index 5f7819e656..ee8fde65d9 100644 --- a/views/shared/header/header.jade +++ b/views/shared/header/header.jade @@ -1,5 +1,4 @@ -//.header-wrap(ng-controller='HeaderCtrl', data-spy="affix", data-offset-top="148") -.header-wrap(ng-controller='HeaderCtrl') +.header-wrap(ng-controller='HeaderCtrl', data-spy="affix", data-offset-top="148") a.label.undo-button(x-bind='click:undo', ng-show='_undo') Undo div(ng-if='!user.preferences.hideHeader') include menu @@ -25,10 +24,16 @@ span(ng-show='user.history.exp') a(ng-click='toggleChart("exp")', tooltip='Progress') i.icon-signal + .meter.mana(title='Mana', ng-if='user.flags.classSelected') + .bar(style='width: {{percent(user.stats.mp, user.stats.int+10)}}%;') + span.meter-text + i.icon-fire + | {{user.stats.mp || 0 | number:0}} / {{user.stats.int+10}} + // party span(ng-controller='PartyCtrl') .herobox-wrap(ng-repeat='profile in partyMinusSelf') include avatar - .NPC-Bailey-Head(ng-show='user.flags.newStuff', tooltip='Psst', tooltip-placement='top', ng-click='modals.newStuff=true') + .npc_bailey.npc_bailey_head(ng-show='user.flags.newStuff', tooltip='Psst', tooltip-placement='top', ng-click='modals.newStuff=true') diff --git a/views/shared/modals/achievements.jade b/views/shared/modals/achievements.jade index fd05296c54..c599d910e1 100644 --- a/views/shared/modals/achievements.jade +++ b/views/shared/modals/achievements.jade @@ -35,7 +35,7 @@ div(modal='user.flags.contributor') .modal-header h3 Contributor Achievement! .modal-body - .NPC-Justin.float-left + .npc_justin.float-left p. {{user.profile.name}}, you awesome person! You're now a level {{user.contributor.level}} contributor for helping HabitRPG. See what prizes you've earned for your contribution! diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index e041f2d0ae..61688ef33c 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -7,7 +7,7 @@ div(modal='modals.buyGems') table tr td - .NPC-Justin + .npc_justin td .popover.static-popover.fade.right.in.wide-popover .arrow diff --git a/views/shared/modals/classes.jade b/views/shared/modals/classes.jade new file mode 100644 index 0000000000..ae5c5c06a3 --- /dev/null +++ b/views/shared/modals/classes.jade @@ -0,0 +1,69 @@ +.modal(ng-if='!user.flags.classSelected && user.stats.lvl >= 5', data-backdrop=true, ng-controller='UserCtrl') + .modal-header + h3 Class System Unlocked! + .modal-body + .well Select your class. Upon level-up, you will now be able to allocate points to various stats. Alternatively, you can choose which stat your tasks represent (physical, mental, social, or "other") and drive your class automatically. + .well(ng-show='selectedClass=="warrior"') Warriors deal moderate damage to tasks and have moderate defense against tasks. + .well(ng-show='selectedClass=="wizard"') Wizards deal high damage to task, and can cast debuff spells on multiple tasks. + .well(ng-show='selectedClass=="rogue"') Rogues finds more drops, Gold, and have a high chance of dealing "critical hits," which grant large GP & Exp bonuses. Rogues can also "go stealth" to avoid and entire day of dailies. + .well(ng-show='selectedClass=="healer"') Healers have high defense against damage, and can heal themselves and other players in the party, as well as buff players. + .row-fluid + .span3(ng-click='selectedClass = "warrior"') + h5 Warrior + figure.herobox(ng-class='{"selected-class": selectedClass=="warrior"}') + .character-sprites + span(class='skin_{{user.preferences.skin}}') + span(class='{{user.preferences.size}}_armor_warrior_5') + span(class='head_base_0') + span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}') + span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}') + span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}') + span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}') + span(class='head_warrior_5') + span(class='shield_warrior_5') + span(class='weapon_warrior_6') + .span3(ng-click='selectedClass = "wizard"') + h5 Wizard + figure.herobox(ng-class='{"selected-class": selectedClass=="wizard"}') + .character-sprites + span(class='skin_{{user.preferences.skin}}') + span(class='{{user.preferences.size}}_armor_wizard_5') + span(class='head_base_0') + span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}') + span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}') + span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}') + span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}') + span(class='head_wizard_5') + span(class='shield_wizard_5') + span(class='weapon_wizard_6') + .span3(ng-click='selectedClass = "rogue"') + h5 Rogue + figure.herobox(ng-class='{"selected-class": selectedClass=="rogue"}') + .character-sprites + span(class='skin_{{user.preferences.skin}}') + span(class='{{user.preferences.size}}_armor_rogue_5') + span(class='head_base_0') + span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}') + span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}') + span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}') + span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}') + span(class='head_rogue_5') + span(class='shield_rogue_5') + span(class='weapon_rogue_6') + .span3(ng-click='selectedClass = "healer"') + h5 Healer + figure.herobox(ng-class='{"selected-class": selectedClass=="healer"}') + .character-sprites + span(class='skin_{{user.preferences.skin}}') + span(class='{{user.preferences.size}}_armor_healer_5') + span(class='head_base_0') + span(class='hair_base_{{user.preferences.hair.base}}_{{user.preferences.hair.color}}') + span(class='hair_bangs_{{user.preferences.hair.bangs}}_{{user.preferences.hair.color}}') + span(class='hair_beard_{{user.preferences.hair.beard}}_{{user.preferences.hair.color}}') + span(class='hair_mustache_{{user.preferences.hair.mustache}}_{{user.preferences.hair.color}}') + span(class='head_healer_5') + span(class='shield_healer_5') + span(class='weapon_healer_6') + + .modal-footer + button.btn.btn-default.cancel(ng-click='rerollSubmit()') Select \ No newline at end of file diff --git a/views/shared/modals/death.jade b/views/shared/modals/death.jade index 3384ccbfe9..7069de2135 100644 --- a/views/shared/modals/death.jade +++ b/views/shared/modals/death.jade @@ -3,7 +3,7 @@ div(modal='user.stats.hp <= 0', options='{backdrop:true, keyboard:false, backdro .row-fluid .span4 figure.notification-character - img(src='/bower_components/habitrpg-shared/img/sprites/dead.png') + .GrimReaper .span8 h2 You Died! .row-fluid diff --git a/views/shared/modals/index.jade b/views/shared/modals/index.jade index 6d15f517b7..339424984e 100644 --- a/views/shared/modals/index.jade +++ b/views/shared/modals/index.jade @@ -6,4 +6,5 @@ include ./new-stuff include ./buy-gems include ./members include ./settings -include ./pets \ No newline at end of file +include ./pets +include ./classes \ No newline at end of file diff --git a/views/shared/modals/new-stuff.jade b/views/shared/modals/new-stuff.jade index e14f0c9545..b13bb61235 100644 --- a/views/shared/modals/new-stuff.jade +++ b/views/shared/modals/new-stuff.jade @@ -5,7 +5,7 @@ div(modal='modals.newStuff') table tr td - .NPC-Bailey + .npc_bailey td .popover.static-popover.fade.right.in.wide-popover .arrow diff --git a/views/shared/modals/settings.jade b/views/shared/modals/settings.jade index 639304c55f..ea227ded35 100644 --- a/views/shared/modals/settings.jade +++ b/views/shared/modals/settings.jade @@ -31,19 +31,21 @@ div(ng-controller='SettingsCtrl') .option-group.option-medium input.option-content(type='number', data-for='stats.lvl', ng-model='restoreValues.stats.lvl') span.input-suffix Level - h3 Equipment - .option-group.option-medium - input.option-content(type='number', data-for='items.weapon', ng-model='restoreValues.items.weapon') - span.input-suffix Weapon - .option-group.option-medium - input.option-content(type='number', data-for='items.armor', ng-model='restoreValues.items.armor') - span.input-suffix Armor - .option-group.option-medium - input.option-content(type='number', data-for='items.head', ng-model='restoreValues.items.head') - span.input-suffix Helm - .option-group.option-medium - input.option-content(type='number', data-for='items.shield', ng-model='restoreValues.items.shield') - span.input-suffix Shield + //- + //- Commenting out since it doesn't make sense to restore equipment with Armory anymore. They can just restore GP & buy + h3 Equipment + .option-group.option-medium + input.option-content(type='number', data-for='items.weapon', ng-model='restoreValues.items.weapon') + span.input-suffix Weapon + .option-group.option-medium + input.option-content(type='number', data-for='items.armor', ng-model='restoreValues.items.armor') + span.input-suffix Armor + .option-group.option-medium + input.option-content(type='number', data-for='items.head', ng-model='restoreValues.items.head') + span.input-suffix Helm + .option-group.option-medium + input.option-content(type='number', data-for='items.shield', ng-model='restoreValues.items.shield') + span.input-suffix Shield h3 Achievements .option-group.option-medium input.option-content(type='number', data-for='achievements.streak', ng-model='restoreValues.achievements.streak') diff --git a/views/shared/tasks/lists.jade b/views/shared/tasks/lists.jade index 268106ee83..4598d278df 100644 --- a/views/shared/tasks/lists.jade +++ b/views/shared/tasks/lists.jade @@ -45,8 +45,8 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") include ./task // Static Rewards - ul.items(bo-if='main && list.type=="reward" && user.flags.itemsEnabled') - li.task.reward-item(ng-hide='item.hide', ng-repeat='item in itemStore') + ul.items.rewards(bo-if='main && list.type=="reward" && user.flags.itemsEnabled') + li.task.reward-item(ng-repeat='item in itemStore') // right-hand side control buttons .task-meta-controls span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{item.notes}}', popover-title='{{item.text}}') @@ -55,13 +55,29 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template") .task-controls a.task-action-btn.btn-reroll(bo-if='item.type=="reroll"', ng-click='modals.reroll = true') i.icon-repeat - a.money.btn-buy.item-btn(bo-if='item.type!="reroll"', ng-click='buy(item.type)') + a.money.btn-buy.item-btn(bo-if='item.type!="reroll"', ng-click='buy(item)') span.reward-cost {{item.value}} span.shop_gold // main content - span(bo-class='{"shop_{{item.classes}} shop-sprite item-img": true}') + span(bo-class='{"shop_{{item.key}} shop-sprite item-img": true}') p.task-text {{item.text}} + // Spells + ul.items(ng-if='main && list.type=="reward" && user.stats.class') + li.task.reward-item(ng-repeat='(k,spell) in Items.spells[user.stats.class]', ng-show='user.stats.lvl >= spell.lvl') + .task-meta-controls + span.task-notes(popover-trigger='mouseenter', popover-placement='left', popover='{{spell.notes}}', popover-title='{{spell.text}}') + i.icon-comment + //left-hand size commands + .task-controls + a.money.btn-buy.item-btn(ng-click='castStart(spell)') + span.reward-cost + strong {{spell.mana}} + |  MP + // main content + span(ng-class='{"shop_{{spell.key}} shop-sprite item-img": true}') + p.task-text {{spell.text}} + br // Ads diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade index a1a6600f92..55c87fbaaa 100644 --- a/views/shared/tasks/task.jade +++ b/views/shared/tasks/task.jade @@ -1,4 +1,4 @@ -li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', data-id='{{task.id}}') +li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{taskClasses(task, user.filters, user.preferences.dayStart, user.lastCron, list.showCompleted, main)}}', ng-click='spell && castEnd(task, "task", $event)', ng-class='{"cast-target":spell}') // right-hand side control buttons .task-meta-controls @@ -161,6 +161,14 @@ li(bindonce='list', ng-repeat='task in obj[list.type+"s"]', class='task {{taskCl span(ng-if='task.type=="daily"') legend.option-title Restore Streak input.option-content(type='number', ng-model='task.streak') + + legend.option-title Attributes + .task-controls.tile-group + button.task-action-btn.tile(type='button') Physical + button.task-action-btn.tile(type='button') Mental + button.task-action-btn.tile(type='button') Social + button.task-action-btn.tile(type='button') Other + button.task-action-btn.tile.spacious(type='submit') Save & Close div(class='{{obj._id}}{{task.id}}-chart', ng-show='charts[obj._id+task.id]')