diff --git a/public/js/controllers/rootCtrl.js b/public/js/controllers/rootCtrl.js index 869b47cf2c..e37a4a9662 100644 --- a/public/js/controllers/rootCtrl.js +++ b/public/js/controllers/rootCtrl.js @@ -191,9 +191,9 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$ .success(function(){ var msg = "You cast " + 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; + case 'task': msg += ' on ' + target.text + '.';break; + case 'user': msg += ' on ' + target.profile.name + '.';break; + case 'party': msg += ' for the Party.';break; } Notification.text(msg); }); diff --git a/public/js/filters/filters.js b/public/js/filters/filters.js index 9d2255d9e2..f2ba025772 100644 --- a/public/js/filters/filters.js +++ b/public/js/filters/filters.js @@ -8,4 +8,9 @@ angular.module('habitrpg') return function (gp) { return Math.floor((gp - Math.floor(gp))*100); } + }) + .filter('htmlDecode',function(){ + return function(html){ + return $('
').html(html).text(); + } }) \ No newline at end of file diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 53d0a6f040..cfdaa2d179 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -539,9 +539,11 @@ api.questAccept = function(req, res) { // If ?key=xxx is provided, we're starting a new quest and inviting the party. Otherwise, we're a party member accepting the invitation if (key) { - if (!shared.content.quests[key]) return res.json(404,{err:'Quest ' + key + ' not found'}); + var quest = shared.content.quests[key]; + if (!quest) return res.json(404,{err:'Quest ' + key + ' not found'}); + if (quest.lvl && user.stats.lvl < quest.lvl) return res.json(400, {err: "You must be level "+quest.lvl+" to being this quest."}); if (group.quest.key) return res.json(400, {err: 'Party already on a quest (and only have one quest at a time)'}); - if (!user.items.quests[key]) return res.json(401, {err: "You don't own that quest scroll"}); + if (!user.items.quests[key]) return res.json(400, {err: "You don't own that quest scroll"}); group.quest.key = key; group.quest.members = {}; // Invite everyone. true means "accepted", false="rejected", undefined="pending". Once we click "start quest" diff --git a/src/models/group.js b/src/models/group.js index 4d1e8b3ed4..fa901dad8f 100644 --- a/src/models/group.js +++ b/src/models/group.js @@ -148,6 +148,7 @@ GroupSchema.methods.finishQuest = function(quest, cb) { case 'eggs': case 'food': case 'hatchingPotions': + case 'quests': updates['$inc']['items.'+item.type+'.'+dropK] = _.where(quest.drop.items,{type:item.type,key:item.key}).length; break; case 'pets': diff --git a/test/api.mocha.js b/test/api.mocha.js index 6c66b612e5..9b69fd461e 100644 --- a/test/api.mocha.js +++ b/test/api.mocha.js @@ -288,7 +288,8 @@ describe('API', function () { request.post(baseURL+'/user/batch-update') .send([ {op:'score',params:{direction:'up',id:user.dailys[0].id}}, - {op:'score',params:{direction:'up',id:user.dailys[1].id}} + {op:'score',params:{direction:'up',id:user.dailys[1].id}}, + {op:'update',body:{'stats.lvl':50}} ]) .end(function(res){ user = res.body; @@ -344,21 +345,21 @@ describe('API', function () { // Start the quest async.waterfall([ function (cb) { - request.post(baseURL + "/groups/" + group._id + "/questAccept?key=gryphon") + request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3") .end(function (res) { - expectCode(res, 401); - User.findByIdAndUpdate(_id, {$set: {'items.quests.gryphon':1}}, cb); + expectCode(res, 400); + User.findByIdAndUpdate(_id, {$set: {'items.quests.vice3':1}}, cb); }); }, function (_user,cb) { - request.post(baseURL + "/groups/" + group._id + "/questAccept?key=gryphon") + request.post(baseURL + "/groups/" + group._id + "/questAccept?key=vice3") .end(function (res) { expectCode(res, 200); Group.findById(group._id, cb); }); }, function (_group,cb) { - expect(_group.quest.key).to.be('gryphon'); + expect(_group.quest.key).to.be('vice3'); expect(_group.quest.active).to.be(false); request.post(baseURL + "/groups/" + group._id + "/questAccept") @@ -435,7 +436,7 @@ describe('API', function () { request.post(baseURL+'/user/batch-update') .end(function(){ request.get(baseURL+'/groups/party').end(function(res){ - expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.gryphon.boss.hp); + expect(res.body.quest.progress.hp).to.be.below(shared.content.quests.vice3.boss.hp); var _party = res.body.members; expect(_.find(_party,{_id:party[0]._id}).stats.hp).to.be.below(50); expect(_.find(_party,{_id:party[1]._id}).stats.hp).to.be.below(50); @@ -445,7 +446,7 @@ describe('API', function () { // Kill the boss function(cb){ - expect(user.items.eggs['Gryphon']).to.not.be.ok(); + expect(user.items.gear.owned.weapon_special_2).to.not.be.ok(); Group.findByIdAndUpdate(group._id,{$set:{'quest.progress.hp':0}},cb); }, function(_group,cb){ @@ -465,12 +466,14 @@ describe('API', function () { }, function(_group,cb){ expect(_.isEmpty(_group.quest)).to.be(true); - expect(user.items.eggs['Gryphon']).to.be(2); - expect(_.find(_group.members,{_id:party[0]._id}).items.eggs['Gryphon']).to.be(2); - expect(_.find(_group.members,{_id:party[1]._id}).items.eggs['Gryphon']).to.be(2); - expect(_.find(_group.members,{_id:party[2]._id}).items.eggs['Gryphon']).to.not.be.ok(); - expect(user.stats.exp).to.be.above(shared.content.quests.gryphon.drop.exp); - expect(user.stats.gp).to.be.above(shared.content.quests.gryphon.drop.gp); + expect(user.items.gear.owned.weapon_special_2).to.be(true); + expect(user.items.eggs.Dragon).to.be(2); + expect(user.items.hatchingPotions.Shade).to.be(2); + expect(_.find(_group.members,{_id:party[0]._id}).items.gear.owned.weapon_special_2).to.be(true); + expect(_.find(_group.members,{_id:party[1]._id}).items.gear.owned.weapon_special_2).to.be(true); + expect(_.find(_group.members,{_id:party[2]._id}).items.gear.owned.weapon_special_2).to.not.be.ok(); + expect(user.stats.exp).to.be.above(shared.content.quests.vice3.drop.exp); + expect(user.stats.gp).to.be.above(shared.content.quests.vice3.drop.gp); cb(); } ],done); diff --git a/views/options/inventory/inventory.jade b/views/options/inventory/inventory.jade index 66bc97c7f1..641b48e7b1 100644 --- a/views/options/inventory/inventory.jade +++ b/views/options/inventory/inventory.jade @@ -47,7 +47,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') p.muted(ng-show='questCount < 1')=env.t('noScrolls') p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + '' div(ng-repeat='(quest_key,points) in ownedItems(user.items.quests)', ng-init='quest = Content.quests[quest_key]') - button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') + button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes | htmlDecode}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='showQuest(quest_key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') .badge.badge-info.stack-count {{points}} li.customize-menu @@ -109,8 +109,8 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') li.customize-menu menu.pets-menu(label=env.t('quests')) p.muted!=env.t('scrollsText1') + ' ' + env.t('scrollsText2') + '' - div(ng-repeat='quest in Content.quests') - button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buyQuest(quest.key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') + div(ng-repeat='quest in Content.quests', ng-if='quest.canBuy') + button.customize-option(popover="{{quest.previous && !user.achievements.quests[quest.previous] ? env.t('scrollsPre') : quest.notes | htmlDecode}}", popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='buyQuest(quest.key)', class='inventory_quest_scroll', ng-class='{locked: quest.previous && !user.achievements.quests[quest.previous]}') p | {{quest.value}} span.Pet_Currency_Gem1x.inline-gems @@ -127,7 +127,7 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html') p | 4 span.Pet_Currency_Gem1x.inline-gems - div + //-div button.customize-option(popover='{{Content.spells.special.snowball.notes}}', popover-title='{{Content.spells.special.snowball.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("special", Content.spells.special.snowball)', class='inventory_special_snowball') p | {{Content.spells.special.snowball.value}} diff --git a/views/options/social/group.jade b/views/options/social/group.jade index aea47a6331..caa9d3469e 100644 --- a/views/options/social/group.jade +++ b/views/options/social/group.jade @@ -47,11 +47,12 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title=env.t('guild table.table.table-striped tr(ng-repeat='(k,v) in group.quest.progress.collect', class='quest_collected_{{v >= Content.quests[group.quest.key].collect[k].count}}') td - div(class='quest_{{group.quest.key}}_{{k}}') {{Content.quests[group.quest.key].collect[k].text}} + div.pull-left(class='quest_{{group.quest.key}}_{{k}}') + | {{Content.quests[group.quest.key].collect[k].text}} td {{v}} / {{Content.quests[group.quest.key].collect[k].count}} - p {{Content.quests[group.quest.key].notes}} + div(ng-bind-html='Content.quests[group.quest.key].notes') hr h5=env.t('participants') table.table.table-striped diff --git a/views/shared/modals/new-stuff.jade b/views/shared/modals/new-stuff.jade index e849afd403..0c51815132 100644 --- a/views/shared/modals/new-stuff.jade +++ b/views/shared/modals/new-stuff.jade @@ -26,34 +26,40 @@ script(type='text/ng-template', id='modals/newStuff.html') h3.popover-title a(target='_blank', href='https://twitter.com/Mihakuu') Bailey .popover-content + h5 Vice + p You awaken after the Winter Wonderland festivities and birthday celebrations with a smile. It's been a snowy, cheerful couple months, and the NPCs have finally returned to their normal attire. But today something is very wrong. Shadowy whisps cover the ground of Habitica, the sky has darkened. At the tavern you hear @DanielTheBard struming dark tales on his lute, and @Baconsaur peering into a mug, grumbling about her mounts swallowed in the shadows. They speak of the same thing: Vice, a dark an terrible foe. This new boss arc is a 3-part quest that requires level 30 to begin. Bring your strongest party members, and don't miss your dailies - there's a powerful weapon at the end! + p + small.muted by @baconsaur & @DanielTheBard + p + small.muted 02/01/2014 - table.table.table-striped - tr - td - h5 Happy Birthday, HabitRPG! - p The fair land of Habitica is two years old on January 31st! The NPCs are celebrating in style, and it looks like some of the staff is, too! Won't you join in? - tr - td - h5 Absurd Party Robtes - p As part of the festivities, Absurd Party Robes are available free of charge in the Item Store! Swath yourself in those silly garbs and don your matching hats to celebrate this momentous day. - tr - td - h5 Delicious Cake - p What would a birthday be without birthday cake in a myriad of flavors? Of course, pets are very picky, but luckily Lemoness and her team of bakers have plenty of slices to go around. Mmm, delicious! - tr - td - h5 Last Day of Winter Wonderland Event - p Also, just a reminder - January 31st is the final day of the Winter Wonderland event, so it's your last day to get the Limited Edition Winter Hair Colors, the Winter Outfits, the snowballs, and the Trapper Santa and Find the Cub quest scrolls. Remember that mid-progress Trapper Santa and Find the Cub quests will not abort, nor will you lose your scrolls - they will simply be removed from Alexander's marketplace. We hope that you've had a wonderful winter! - tr - td - h5 Birthday Bash Badge - p Finally, to commemorate the fun, all party participants receive a birthday badge! Polish it frequently and wear it fondly. + hr + h5 01/30/2014 + table.table.table-striped + tr + td + h5 Happy Birthday, HabitRPG! + p The fair land of Habitica is two years old on January 31st! The NPCs are celebrating in style, and it looks like some of the staff is, too! Won't you join in? + tr + td + h5 Absurd Party Robtes + p As part of the festivities, Absurd Party Robes are available free of charge in the Item Store! Swath yourself in those silly garbs and don your matching hats to celebrate this momentous day. + tr + td + h5 Delicious Cake + p What would a birthday be without birthday cake in a myriad of flavors? Of course, pets are very picky, but luckily Lemoness and her team of bakers have plenty of slices to go around. Mmm, delicious! + tr + td + h5 Last Day of Winter Wonderland Event + p Also, just a reminder - January 31st is the final day of the Winter Wonderland event, so it's your last day to get the Limited Edition Winter Hair Colors, the Winter Outfits, the snowballs, and the Trapper Santa and Find the Cub quest scrolls. Remember that mid-progress Trapper Santa and Find the Cub quests will not abort, nor will you lose your scrolls - they will simply be removed from Alexander's marketplace. We hope that you've had a wonderful winter! + tr + td + h5 Birthday Bash Badge + p Finally, to commemorate the fun, all party participants receive a birthday badge! Polish it frequently and wear it fondly. - p Thanks so much for being a part of the HabitRPG community. We love you guys, and we can't wait to have you at our sides in the upcoming year! Stay productive, Habiteers, and have an awesome day. + p Thanks so much for being a part of the HabitRPG community. We love you guys, and we can't wait to have you at our sides in the upcoming year! Stay productive, Habiteers, and have an awesome day. - p.muted By @lemoness - - small.muted 01/30/2014 + p.muted By @lemoness hr h5 01/28/2014 diff --git a/views/shared/modals/quests.jade b/views/shared/modals/quests.jade index f09a176117..8c26933369 100644 --- a/views/shared/modals/quests.jade +++ b/views/shared/modals/quests.jade @@ -28,7 +28,7 @@ script(type='text/ng-template', id='modals/showQuest.html') h3 {{selectedQuest.text}} .modal-body .pull-right(class='quest_{{selectedQuest.key}}') - p {{selectedQuest.notes}} + div(ng-bind-html='selectedQuest.notes') quest-rewards(key='{{selectedQuest.key}}') hr div(style='clear:left;clear:right') @@ -44,7 +44,7 @@ script(type='text/ng-template', id='modals/buyQuest.html') h3 {{selectedQuest.text}} .modal-body .pull-right(class='quest_{{selectedQuest.key}}') - p {{selectedQuest.notes}} + div(ng-bind-html='selectedQuest.notes') quest-rewards(key='{{selectedQuest.key}}') .modal-footer button.btn.btn-large.cancel(ng-click='closeQuest()')=env.t('neverMind') @@ -56,7 +56,7 @@ script(type='text/ng-template', id='modals/questInvitation.html') h3=env.t('questInvitation') | {{Content.quests[party.quest.key].text}} .modal-body - p {{Content.quests[party.quest.key].notes}} + div(ng-bind-html='Content.quests[party.quest.key].notes') quest-rewards(key='{{party.quest.key}}') .modal-footer button.btn.btn-default.btn-small.btn-cancel(ng-click='questHold = true')=env.t('askLater')