diff --git a/public/css/customizer.styl b/public/css/customizer.styl index 95e35a6540..e61402f24b 100644 --- a/public/css/customizer.styl +++ b/public/css/customizer.styl @@ -52,83 +52,6 @@ menu } } -// narrower spriting for customize modal -.customize-menu - .m_hair_blond,.m_hair_black,.m_hair_brown,.m_hair_white, - .f_hair_blond,.f_hair_black,.f_hair_brown,.f_hair_white, - .m_skin_dead,.m_skin_orc,.m_skin_asian,.m_skin_black,.m_skin_white,.m_skin_ea8349,.m_skin_c06534,.m_skin_98461a, - .f_skin_dead,.f_skin_orc,.f_skin_asian,.f_skin_black,.f_skin_white,.f_skin_ea8349,.f_skin_c06534,.f_skin_98461a, - .m_head_0,.f_head_0, - .f_armor_0_v1,.f_armor_0_v2 - width: 60px - height: 60px - - // head - .m_head_0 - background-position: -1557px -9px; - - // hair - .m_hair_blond - background-position: -1647px -4px; - .m_hair_black - background-position: -1737px -4px; - .m_hair_brown - background-position: -1827px -4px; - .m_hair_white - background-position: -1917px -4px; - - // skin - .m_skin_dead - background-position: -2547px -20px; - .m_skin_orc - background-position: -2637px -20px; - .m_skin_asian - background-position: -2727px -20px; - .m_skin_black - background-position: -2817px -20px; - .m_skin_white - background-position: -2907px -20px; - .m_skin_ea8349 - background-position: -2997px -20px; - .m_skin_c06534 - background-position: -3087px -20px; - .m_skin_98461a - background-position: -3177px -20px; - - // head - .f_head_0 - background-position: -1917px -9px; - - // hair - .f_hair_white - background-position: -2009px -8px; - .f_hair_brown - background-position: -2099px -8px; - .f_hair_black - background-position: -2189px -8px; - .f_hair_blond - background-position: -2279px -8px; - - // skin - .f_skin_dead - background-position: -2997px -20px; - .f_skin_orc - background-position: -3087px -20px; - .f_skin_asian - background-position: -3177px -20px; - .f_skin_black - background-position: -3267px -20px; - .f_skin_white - background-position: -3357px -20px; - .f_skin_ea8349 - background-position: -3447px -20px; - .f_skin_c06534 - background-position: -3537px -20px; - .f_skin_98461a - background-position: -3627px -20px; - - // starting armor - .f_armor_0_v2 - background-position: -2819px -38px; - .f_armor_0_v1 - background-position: -2909px -38px; +.well.limited-edition + padding: 5px + margin: 0px \ No newline at end of file diff --git a/public/css/index.styl b/public/css/index.styl index 729328f19d..444f3e49f4 100644 --- a/public/css/index.styl +++ b/public/css/index.styl @@ -170,6 +170,6 @@ a .btn margin-right: 5px -.buy-gems-list +.modal-indented-list margin-left: 10px; padding-left: 10px; \ No newline at end of file diff --git a/public/css/npcs.styl b/public/css/npcs.styl index 084a5c19d4..5fc3bfc879 100644 --- a/public/css/npcs.styl +++ b/public/css/npcs.styl @@ -1,4 +1,4 @@ -.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, .NPC-Bailey, .NPC-Bailey-Head, .NPC-Daniel, .NPC-Justin, .NPC-Justin-Head, .NPC-Matt {background: url("/bower_components/habitrpg-shared/img/npcs/halloween/NPC-SpriteSheet.png") no-repeat} .NPC-Alex {background-position: 0 0; width: 162px; height: 138px;} .NPC-Alex-container{margin-bottom: 20px;} diff --git a/public/js/controllers/userCtrl.js b/public/js/controllers/userCtrl.js index 80a0d1413c..511183e808 100644 --- a/public/js/controllers/userCtrl.js +++ b/public/js/controllers/userCtrl.js @@ -1,35 +1,39 @@ "use strict"; -habitrpg.controller("UserCtrl", ['$scope', '$location', 'User', - function($scope, $location, User) { - $scope.profile = User.user; - $scope.hideUserAvatar = function() { - $(".userAvatar").hide(); - }; - $scope.toggleHelm = function(val){ - User.log({op:'set', data:{'preferences.showHelm':val}}); - } +habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', + function($rootScope, $scope, $location, User, $http) { + $scope.profile = User.user; + $scope.hideUserAvatar = function() { + $(".userAvatar").hide(); + }; + $scope.toggleHelm = function(val){ + User.log({op:'set', data:{'preferences.showHelm':val}}); + } - $scope.$watch('_editing.profile', function(value){ - if(value === true) $scope.editingProfile = angular.copy(User.user.profile); - }); - - $scope.save = function(){ - var values = {}; - _.each($scope.editingProfile, function(value, key){ - // Using toString because we need to compare two arrays (websites) - if($scope.editingProfile[key].toString() !== $scope.profile.profile[key].toString()) values['profile.' + key] = value; + $scope.$watch('_editing.profile', function(value){ + if(value === true) $scope.editingProfile = angular.copy(User.user.profile); }); - User.setMultiple(values); - $scope._editing.profile = false; - } - $scope.addWebsite = function(){ - if (!$scope.editingProfile.websites) $scope.editingProfile.websites = []; - $scope.editingProfile.websites.push($scope._newWebsite); - $scope._newWebsite = ''; + $scope.save = function(){ + var values = {}; + _.each($scope.editingProfile, function(value, key){ + // Using toString because we need to compare two arrays (websites) + if($scope.editingProfile[key].toString() !== $scope.profile.profile[key].toString()) values['profile.' + key] = value; + }); + User.setMultiple(values); + $scope._editing.profile = false; + } + + $scope.addWebsite = function(){ + if (!$scope.editingProfile.websites) $scope.editingProfile.websites = []; + $scope.editingProfile.websites.push($scope._newWebsite); + $scope._newWebsite = ''; + } + $scope.removeWebsite = function($index){ + $scope.editingProfile.websites.splice($index,1); + } + + $scope.unlock = User.unlock; + } - $scope.removeWebsite = function($index){ - $scope.editingProfile.websites.splice($index,1); - } -}]); +]); diff --git a/public/js/services/userServices.js b/public/js/services/userServices.js index 3ed760ace5..dae616f6f0 100644 --- a/public/js/services/userServices.js +++ b/public/js/services/userServices.js @@ -164,6 +164,38 @@ angular.module('userServices', []). userServices.log(log); }, + /** + * For gem-unlockable preferences, (a) if owned, select preference (b) else, purchase + * @param path: User.preferences <-> User.purchased maps like User.preferences.skin=abc <-> User.purchased.skin.abc. + * Pass in this paramater as "skin.abc". Alternatively, pass as an array ["skin.abc", "skin.xyz"] to unlock sets + */ + unlock: function(path){ + var self = userServices; //this; // why isn't this working? + + if (_.isArray(path)) { + if (confirm("Purchase for 5 Gems?") !== true) return; + if (user.balance < 1.25) return $rootScope.modals.buyGems = true; + path = path.join(','); + } else { + if (window.habitrpgShared.helpers.dotGet('purchased.' + path, user)) { + var pref = path.split('.')[0], + val = path.split('.')[1]; + return window.habitrpgShared.helpers.dotSet('preferences.' + pref, val, user); + } else { + if (confirm("Purchase for 2 Gems?") !== true) return; + if (user.balance < 0.5) return $rootScope.modals.buyGems = true; + } + } + + $http.post(API_URL + '/api/v1/user/unlock?path=' + path) + .success(function(data, status, headers, config){ + self.log({}); // sync new unlocked & preferences + }).error(function(data, status, headers, config){ + alert(status + ': ' + data); + //FIXME use method used elsewhere for handling this error, this is temp while developing + }) + }, + save: save, settings: settings diff --git a/src/controllers/user.js b/src/controllers/user.js index 377a56d1ca..c98fdd845d 100644 --- a/src/controllers/user.js +++ b/src/controllers/user.js @@ -564,6 +564,49 @@ api['delete'] = function(req, res) { }) } +/* + ------------------------------------------------------------------------ + Unlock Preferences + ------------------------------------------------------------------------ + */ + +api.unlock = function(req, res) { + var user = res.locals.user; + var path = req.query.path; + var fullSet = ~path.indexOf(','); + + // 5G per set, 2G per individual + cost = fullSet ? 1.25 : 0.5; + + if (user.balance < cost) + return res.json(401, {err: 'Not enough gems'}); + + if (fullSet) { + var paths = path.split(','); + _.each(paths, function(p){ + helpers.dotSet('purchased.' + p, true, user); + }); + } else { + if (helpers.dotGet('purchased.' + path, user) === true) + return res.json(401, {err: 'User already purchased that'}); + helpers.dotSet('purchased.' + path, true, user); + } + + user.balance -= cost; + user.__v++; + user.markModified('purchased'); + user.save(function(err, saved){ + if (err) res.json(500, {err:err}); + res.send(200); + }) +} + +/* + ------------------------------------------------------------------------ + Buy Gems + ------------------------------------------------------------------------ + */ + /* Setup Stripe response when posting payment diff --git a/src/models/user.js b/src/models/user.js index 52aae66993..d5f4725170 100644 --- a/src/models/user.js +++ b/src/models/user.js @@ -69,6 +69,12 @@ var UserSchema = new Schema({ rewardIds: Array, filters: {type: Schema.Types.Mixed, 'default': {}}, + purchased: { + ads: {type: Boolean, 'default': false}, + skin: {type: Schema.Types.Mixed, 'default': {}}, // eg, {skeleton: true, pumpkin: true, eb052b: true} + hair: {type: Schema.Types.Mixed, 'default': {}} + }, + flags: { customizationsNotification: {type: Boolean, 'default': false}, showTour: {type: Boolean, 'default': true}, diff --git a/src/routes/api.js b/src/routes/api.js index 86f995239c..1eb4ade222 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -48,6 +48,7 @@ router.post('/user/batch-update', auth.auth, cron, user.batchUpdate); router.post('/user/reroll', auth.auth, cron, user.reroll); 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['delete']('/user', auth.auth, user['delete']); diff --git a/views/options/profile.jade b/views/options/profile.jade index 0f50d9df16..03a9f623c6 100644 --- a/views/options/profile.jade +++ b/views/options/profile.jade @@ -6,11 +6,14 @@ // gender 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='toggleHelm(user.preferences.showHelm)') | Show Helm + hr + // hair li.customize-menu menu(label='Hair') @@ -18,17 +21,47 @@ 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")') + hr + // skin li.customize-menu - menu(label='Skin') - button.customize-option(class='{{user.preferences.gender}}_skin_asian', type='button', ng-click='set("preferences.skin","asian")') - button.customize-option(class='{{user.preferences.gender}}_skin_white', type='button', ng-click='set("preferences.skin","white")') - button.customize-option(class='{{user.preferences.gender}}_skin_ea8349', type='button', ng-click='set("preferences.skin","ea8349")') - button.customize-option(class='{{user.preferences.gender}}_skin_c06534', type='button', ng-click='set("preferences.skin","c06534")') - button.customize-option(class='{{user.preferences.gender}}_skin_98461a', type='button', ng-click='set("preferences.skin","98461a")') - button.customize-option(class='{{user.preferences.gender}}_skin_black', type='button', ng-click='set("preferences.skin","black")') - button.customize-option(class='{{user.preferences.gender}}_skin_dead', type='button', ng-click='set("preferences.skin","dead")') - button.customize-option(class='{{user.preferences.gender}}_skin_orc', type='button', ng-click='set("preferences.skin","orc")') + 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")') + + // Rainbow Skin + menu(label='Rainbow Skins (2G / skin)') + 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.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 (5G) + + // Special Events + div.well.limited-edition + .label.label-info.pull-right(popover='Available for purchase until November 10th (but permanently in your options if purchased).', popover-title='Limited Edition', popover-placement='right', popover-trigger='mouseenter') + | Limited Edition  + i.icon.icon-question-sign + menu(label='Spooky Skins (2G / skin)') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_monster', ng-class='{locked: !user.purchased.skin.monster}', ng-click='unlock("skin.monster")') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_pumpkin', ng-class='{locked: !user.purchased.skin.pumpkin}', ng-click='unlock("skin.pumpkin")') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_skeleton', ng-class='{locked: !user.purchased.skin.skeleton}', ng-click='unlock("skin.skeleton")') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_zombie', ng-class='{locked: !user.purchased.skin.zombie}', ng-click='unlock("skin.zombie")') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_ghost', ng-class='{locked: !user.purchased.skin.ghost}', ng-click='unlock("skin.ghost")') + button.customize-option(type='button', class='{{user.preferences.gender}}_skin_shadow', ng-class='{locked: !user.purchased.skin.shadow}', ng-click='unlock("skin.shadow")') + button.btn.btn-small.btn-primary(ng-hide='user.purchased.skin.monster && user.purchased.skin.pumpkin && user.purchased.skin.skeleton && user.purchased.skin.zombie && user.purchased.skin.ghost && user.purchased.skin.shadow', ng-click='unlock(["skin.monster", "skin.pumpkin", "skin.skeleton", "skin.zombie", "skin.ghost", "skin.shadow"])') Unlock Set (5G) + + menu(ng-show='user.preferences.gender=="f"', type='list') li.customize-menu menu(label='Clothing') diff --git a/views/shared/modals/buy-gems.jade b/views/shared/modals/buy-gems.jade index 77d44d26ab..bcb6908943 100644 --- a/views/shared/modals/buy-gems.jade +++ b/views/shared/modals/buy-gems.jade @@ -15,7 +15,7 @@ div(modal='modals.buyGems') a(target='_blank', href='https://twitter.com/bowenNstuff') Justin .popover-content p $5 USD will: - ul.buy-gems-list + ul.modal-indented-list li Add 20 gems to your account, which are used to buy special items. li Disable ads. li Donate to the developers (as an open source project, we can us all the help we can get). diff --git a/views/shared/modals/new-stuff.jade b/views/shared/modals/new-stuff.jade index efcd9bfc47..ecbf986797 100644 --- a/views/shared/modals/new-stuff.jade +++ b/views/shared/modals/new-stuff.jade @@ -1,49 +1,3 @@ -div(modal='user.flags.rewrite !== false') - .modal-header - h3 Mega Ultra Update! - .modal-body - table - tr - td - .NPC-Bailey - td - .popover.static-popover.fade.right.in.wide-popover - .arrow - h3.popover-title - a(target='_blank', href='https://twitter.com/Mihakuu') Bailey - .popover-content - p. - Hello my Habiteers! I have some amazing news to share with you, it's huge! - Has Habit ever crashed for you? (Joke). Well we re-wrote the website from the ground up - to conquor those critical bugs once and for all (more from Tyler in a bit). If you haven't seen me for a while (due to a bug in the old site), be sure to catch up with me on the right side of the screen for any missed news. Importantly: - .alert.alert-success(style='margin-bottom:5px'). - Android & iOS Apps are out! - p. - They're open source, so help us make them awesome. As for the rewrite: not all features are yet ported, but don't worry - you're still getting drops and streak-bonuses in the background, even if you can't see them yet. - We'll be working hard to bring in all the missing features. And if you're not already, be sure to follow our updates on Tumblr (there are some fun member highlights recently). One more thing: if you are a Veteran of the old site, I have granted you a Veteran Wolf! Check your inventory :) - - table(style='clear:both;') - tr - td - .popover.static-popover.fade.left.in - .arrow - h3.popover-title - a(target='_blank', href='https://twitter.com/lefnire') Tyler - .popover-content - p. - JavaScript developers! To me! We must finish vanquishing the old site, as not all features have been ported. - We rewrote Habit on AngularJS + Express. - We desparately need your help porting the rest of the features, and polishing off the bugs. Read this guide to getting started. - Thanks everyone for all your support and patience! - - td - img.pull-right(src='/bower_components/habitrpg-shared/img/unprocessed/efbd21c4-82a1-11e2-8190-fbc609b5c58b.png', style='height:72px') - - .modal-footer - button.btn.btn-default.cancel(ng-click='user.flags.rewrite = false') Read Later - button.btn.btn-warning.cancel(ng-click='set("flags.rewrite", false)') Dismiss - - div(modal='modals.newStuff') .modal-header h3 New Stuff! @@ -58,24 +12,46 @@ div(modal='modals.newStuff') h3.popover-title a(target='_blank', href='https://twitter.com/Mihakuu') Bailey .popover-content - strong 10/19/2013 + strong 10/22/2013 p - ul - li. - New custom skin colors are now available! Go check them out in the Profile section. Also, the new mobile update, 0.0.10, is now available to download! It includes the new skin tones and the ability to hide or show your helm, among other things. - li. - You can now sell un-wanted drops to Alex the Merchant. Trade those troves of eggs for gold! + ul.modal-indented-list + li TRICK OR TREAT! It's Habit Halloween! Some of the NPCs have decorated for the occasion. Can you spot us? + li Two gem-purchasable skin tones are now available! The Rainbow Skin Set is here to stay, but in honor of Halloween, we also have the LIMITED EDITION SPOOKY SKIN SET. You will only be able to purchase the Spooky Skin Set until November 10th, so if you want a monstrous avatar, now's the time to act! + li Do note, skins won't work on mobile until the app is updated. We'll update Android ASAP, iPhone usually takes ~1wk to approve. hr h3 Archive p + h4 10/19/2013 + ul + li. + New custom skin colors are now available! Go check them out in the Profile section. Also, the new mobile update, 0.0.10, is now available to download! It includes the new skin tones and the ability to hide or show your helm, among other things. + li. + You can now sell un-wanted drops to Alex the Merchant. Trade those troves of eggs for gold! h4 09/01/2013 ul li. We re-wrote the website from the ground up And in case you missed it, Android & iOS Apps are out! Both apps and the website are open source, and we desparately need your help porting the rest of the features, and polishing off the bugs. Read this guide to getting started. - Each of your pull requests shall grant you a special Contributor Gear piece. + We're working on a system of Contributor Gear to reward the awesome people who help out, so stay tuned! + + h4 The Rewrite! (Mid August) + ul + li. + Hello my Habiteers! I have some amazing news to share with you, it's huge! + Has Habit ever crashed for you? (Joke). Well we re-wrote the website from the ground up + to conquor those critical bugs once and for all (more from Tyler in a bit). If you haven't seen me for a while (due to a bug in the old site), be sure to catch up with me on the right side of the screen for any missed news. Importantly: + .alert.alert-success(style='margin-bottom:5px'). + Android & iOS Apps are out! + li. + They're open source, so help us make them awesome. As for the rewrite: not all features are yet ported, but don't worry - you're still getting drops and streak-bonuses in the background, even if you can't see them yet. + We'll be working hard to bring in all the missing features. And if you're not already, be sure to follow our updates on Tumblr (there are some fun member highlights recently). One more thing: if you are a Veteran of the old site, I have granted you a Veteran Wolf! Check your inventory :) + li. + JavaScript developers! To me! We must finish vanquishing the old site, as not all features have been ported. + We rewrote Habit on AngularJS + Express. + We desparately need your help porting the rest of the features, and polishing off the bugs. Read this guide to getting started. + Thanks everyone for all your support and patience! h4 8/20/2013 ul