mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
Quests: initial Bosses WIP - invite, accept, reject, etc
This commit is contained in:
@@ -180,3 +180,12 @@ a
|
||||
.modal-indented-list
|
||||
margin-left: 10px;
|
||||
padding-left: 10px;
|
||||
|
||||
.inline-modal
|
||||
position: relative
|
||||
top: auto;
|
||||
left: auto
|
||||
right: auto
|
||||
margin: 0 auto 20px
|
||||
z-index: 1
|
||||
max-width: 100%
|
||||
@@ -267,11 +267,11 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Groups', '$http', 'A
|
||||
}
|
||||
])
|
||||
|
||||
.controller("PartyCtrl", ['$scope', 'Groups', 'User', '$state',
|
||||
function($scope, Groups, User, $state) {
|
||||
.controller("PartyCtrl", ['$rootScope','$scope', 'Groups', 'User', '$state',
|
||||
function($rootScope,$scope, Groups, User, $state) {
|
||||
$scope.type = 'party';
|
||||
$scope.text = 'Party';
|
||||
$scope.group = Groups.party();
|
||||
$scope.group = $rootScope.party = Groups.party();
|
||||
$scope.newGroup = new Groups.Group({type:'party', leader: User.user._id, members: [User.user._id]});
|
||||
$scope.create = function(group){
|
||||
group.$save(function(newGroup){
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL', '$http', 'Notification',
|
||||
function($rootScope, $scope, User, API_URL, $http, Notification) {
|
||||
habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User',
|
||||
function($rootScope, $scope, User) {
|
||||
|
||||
var user = User.user;
|
||||
var Content = $rootScope.Content;
|
||||
@@ -17,6 +17,7 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
|
||||
$scope.$watch('user.items.eggs', function(eggs){ $scope.eggCount = countStacks(eggs); }, true);
|
||||
$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.quests', function(quest){ $scope.questCount = countStacks(quest); }, true);
|
||||
|
||||
$scope.$watch('user.items.gear', function(gear){
|
||||
$scope.gear = {
|
||||
@@ -114,5 +115,18 @@ habitrpg.controller("InventoryCtrl", ['$rootScope', '$scope', 'User', 'API_URL',
|
||||
$scope.chooseMount = function(egg, potion) {
|
||||
User.user.ops.equip({params:{type: 'mount', key: egg + '-' + potion}});
|
||||
}
|
||||
|
||||
$scope.showQuest = function(quest) {
|
||||
$rootScope.selectedQuest = Content.quests[quest];
|
||||
$rootScope.modals.showQuest = true;
|
||||
}
|
||||
$scope.closeQuest = function(){
|
||||
$rootScope.selectedQuest = undefined;
|
||||
$rootScope.modals.showQuest = false;
|
||||
}
|
||||
$scope.questInit = function(){
|
||||
$rootScope.party.$questAccept({key:$scope.selectedQuest.name});
|
||||
$scope.closeQuest();
|
||||
}
|
||||
}
|
||||
]);
|
||||
@@ -16,7 +16,8 @@ angular.module('groupServices', ['ngResource']).
|
||||
join: {method: "POST", url: API_URL + '/api/v2/groups/:gid/join'},
|
||||
leave: {method: "POST", url: API_URL + '/api/v2/groups/:gid/leave'},
|
||||
invite: {method: "POST", url: API_URL + '/api/v2/groups/:gid/invite'},
|
||||
removeMember: {method: "POST", url: API_URL + '/api/v2/groups/:gid/removeMember'}
|
||||
questAccept: {method: "POST", url: API_URL + '/api/v2/groups/:gid/questAccept'},
|
||||
questReject: {method: "POST", url: API_URL + '/api/v2/groups/:gid/questReject'}
|
||||
});
|
||||
|
||||
// Defer loading everything until they're requested
|
||||
|
||||
@@ -200,6 +200,15 @@ api.attachGroup = function(req, res, next) {
|
||||
})
|
||||
}
|
||||
|
||||
api.attachGroupPopulated = function(req, res, next) {
|
||||
Group.findById(req.params.gid).populate('members').exec(function(err, group){
|
||||
if(err) return res.json(500, {err:err});
|
||||
if(!group) return res.json(404, {err: "Group not found"});
|
||||
res.locals.group = group;
|
||||
next();
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO make this it's own ngResource so we don't have to send down group data with each chat post
|
||||
*/
|
||||
@@ -398,5 +407,100 @@ api.removeMember = function(req, res, next){
|
||||
}else{
|
||||
return res.json(400, {err: "User not found among group's members!"});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ------------------------------------
|
||||
// Quests
|
||||
// ------------------------------------
|
||||
|
||||
questStart = function(req, res) {
|
||||
var group = res.locals.group;
|
||||
var user = res.locals.user;
|
||||
var force = req.query.force;
|
||||
|
||||
group.markModified('quest');
|
||||
|
||||
// Not ready yet, wait till everyone's accepted, rejected, or we force-start
|
||||
if (!force && _.findIndex(group.quest.members, function(m){
|
||||
return m === undefined;
|
||||
})) {
|
||||
return group.save(function(err,saved){
|
||||
if (err) return res.json(500,{err:err});
|
||||
res.json(saved);
|
||||
})
|
||||
}
|
||||
|
||||
var parallel = [];
|
||||
// TODO will this handle appropriately when people leave/join party between quest invite?
|
||||
_.each(group.members, function(m){
|
||||
if (m._id == user._id) m.items.quests[m.party.quest]--;
|
||||
if (group.quest.members[m._id] == true) {
|
||||
m.party.quest = group.quest.key;
|
||||
} else {
|
||||
m.party.quest = undefined;
|
||||
delete group.quest.members[m._id];
|
||||
}
|
||||
parallel.push(function(cb2){m.save(cb2);});
|
||||
})
|
||||
|
||||
group.quest.active = true;
|
||||
group.quest.hp = shared.content.quests[group.quest.key].hp;
|
||||
parallel.push(function(cb2){group.save(cb2);});
|
||||
|
||||
async.parallel(parallel,function(err, results){
|
||||
if (err) return res.json(500,{err:err});
|
||||
return res.json(group);
|
||||
});
|
||||
}
|
||||
|
||||
api.questAccept = function(req, res) {
|
||||
var group = res.locals.group;
|
||||
var user = res.locals.user;
|
||||
var key = req.query.key;
|
||||
|
||||
if (!group) return res.json(400, {err: "Must be in a party to start quests (this will change in the future)."});
|
||||
|
||||
// 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'});
|
||||
if (group.quest.key) return res.json(400, {err: 'Party already on a quest (and only have one quest at a time)'});
|
||||
group.quest.key = key;
|
||||
group.quest.members = {};
|
||||
// Invite everyone. true means "accepted", false="rejected", undefined="pending". Once we click "start quest"
|
||||
// or everyone has either accepted/rejected, then we store quest key in user object.
|
||||
_.each(group.members, function(m){
|
||||
if (m._id == user._id) {
|
||||
group.quest.members[m._id] = true;
|
||||
} else {
|
||||
group.quest.members[m._id] = undefined;
|
||||
}
|
||||
});
|
||||
|
||||
// Party member accepting the invitation
|
||||
} else {
|
||||
if (!group.quest.key) return res.json(400,{err:'No quest invitation has been sent out yet.'});
|
||||
group.quest.members[user._id] = true;
|
||||
}
|
||||
|
||||
questStart(req,res);
|
||||
}
|
||||
|
||||
api.questReject = function(req, res, next) {
|
||||
var group = res.locals.group;
|
||||
var user = res.locals.user;
|
||||
|
||||
if (!group.quest.key) return res.json(400,{err:'No quest invitation has been sent out yet.'});
|
||||
group.quest.members[user._id] = false;
|
||||
|
||||
group.save(function(err,saved){
|
||||
if (err) return res.json(500,{err:err});
|
||||
return res.json(200,saved);
|
||||
});
|
||||
|
||||
questStart(req,res);
|
||||
}
|
||||
|
||||
|
||||
//TODO
|
||||
function questEnd(){}
|
||||
function questAbort(){}
|
||||
|
||||
@@ -30,7 +30,19 @@ var GroupSchema = new Schema({
|
||||
balance: Number,
|
||||
logo: String,
|
||||
leaderMessage: String,
|
||||
challenges: [{type:'String', ref:'Challenge'}] // do we need this? could depend on back-ref instead (Challenge.find({group:GID}))
|
||||
challenges: [{type:'String', ref:'Challenge'}], // do we need this? could depend on back-ref instead (Challenge.find({group:GID}))
|
||||
quest: {
|
||||
key: String,
|
||||
hp: Number,
|
||||
active: {type:Boolean, 'default':false},
|
||||
|
||||
/*
|
||||
Shows boolean for each party-member who has accepted the quest. Eg {UUID: true, UUID: false}. Once all users click
|
||||
'Accept', the quest begins. If a false user waits too long, probably a good sign to prod them or boot them.
|
||||
TODO when booting user, remove from .joined and check again if we can now start the quest
|
||||
*/
|
||||
members: Schema.Types.Mixed
|
||||
}
|
||||
}, {
|
||||
strict: 'throw',
|
||||
minimize: false // So empty objects are returned
|
||||
|
||||
@@ -182,6 +182,12 @@ var UserSchema = new Schema({
|
||||
),
|
||||
currentMount: String,
|
||||
|
||||
// Quests: {
|
||||
// 'boss_0': 0, // 0 indicates "doesn't own"
|
||||
// 'collection_honey': 5 // Number indicates "stacking"
|
||||
// }
|
||||
quests: _.transform(shared.content.quests, function(m,v,k){ m[k] = Number; }),
|
||||
|
||||
lastDrop: {
|
||||
date: {type: Date, 'default': Date.now},
|
||||
count: {type: Number, 'default': 0}
|
||||
@@ -193,14 +199,14 @@ var UserSchema = new Schema({
|
||||
'default': Date.now
|
||||
},
|
||||
|
||||
// FIXME remove?
|
||||
party: {
|
||||
//party._id // FIXME make these populate docs?
|
||||
current: String, // party._id
|
||||
invitation: String, // party._id
|
||||
lastMessageSeen: String,
|
||||
leader: Boolean,
|
||||
order: {type:String, 'default':'level'}
|
||||
order: {type:String, 'default':'level'},
|
||||
quest: String
|
||||
},
|
||||
preferences: {
|
||||
armorSet: String,
|
||||
|
||||
@@ -98,6 +98,8 @@ router.post('/groups/:gid/join', auth.auth, groups.attachGroup, groups.join);
|
||||
router.post('/groups/:gid/leave', auth.auth, groups.attachGroup, groups.leave);
|
||||
router.post('/groups/:gid/invite', auth.auth, groups.attachGroup, groups.invite);
|
||||
router.post('/groups/:gid/removeMember', auth.auth, groups.attachGroup, groups.removeMember);
|
||||
router.post('/groups/:gid/questAccept', auth.auth, groups.attachGroupPopulated, groups.questAccept); // query={key} (optional. if provided, trigger new invite, if not, accept existing invite)
|
||||
router.post('/groups/:gid/questReject', auth.auth, groups.attachGroupPopulated, groups.questReject);
|
||||
|
||||
//GET /groups/:gid/chat
|
||||
router.post('/groups/:gid/chat', auth.auth, groups.attachGroup, groups.postChat);
|
||||
|
||||
@@ -41,7 +41,13 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||
div(ng-repeat='(pot,points) in ownedItems(user.items.hatchingPotions)')
|
||||
button.customize-option(popover='{{Content.hatchingPotions[pot].notes}}', popover-title='{{Content.hatchingPotions[pot].text}} Potion', popover-trigger='mouseenter', popover-placement='right', ng-click='choosePotion(pot)', class='Pet_HatchingPotion_{{pot}}', ng-class='{selectableInventory: selectedEgg && !user.items.pets[selectedEgg.name+"-"+pot]}')
|
||||
.badge.badge-info.stack-count {{points}}
|
||||
//-p {{pot}}
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Quest Scrolls ({{questCount}})')
|
||||
p(ng-show='questCount < 1') You don't have any quest scrolls.
|
||||
div(ng-repeat='(quest,points) in ownedItems(user.items.quests)')
|
||||
button.customize-option(popover='{{Content.quests[quest].notes}}', popover-title='{{Content.quests[quest].text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='showQuest(quest)', class='inventory_quest_scroll')
|
||||
.badge.badge-info.stack-count {{points}}
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Food ({{foodCount}})')
|
||||
@@ -49,7 +55,6 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||
div(ng-repeat='(food,points) in ownedItems(user.items.food)')
|
||||
button.customize-option(popover='{{Content.food[food].notes}}', popover-title='{{Content.food[food].text}}', popover-trigger='mouseenter', popover-placement='right', ng-click='chooseFood(food)', class='Pet_Food_{{food}}')
|
||||
.badge.badge-info.stack-count {{points}}
|
||||
//-p {{food}}
|
||||
|
||||
li.customize-menu(ng-if='user.items.special.snowball')
|
||||
menu.pets-menu(label='Special')
|
||||
@@ -105,6 +110,14 @@ script(type='text/ng-template', id='partials/options.inventory.drops.html')
|
||||
| {{food.value}}
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Quests')
|
||||
div(ng-repeat='quest in Content.quests')
|
||||
button.customize-option(popover='{{quest.notes}}', popover-title='{{quest.text}}', popover-trigger='mouseenter', popover-placement='left', ng-click='purchase("quests", quest)', class='inventory_quest_scroll')
|
||||
p
|
||||
| {{quest.value}}
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label='Special')
|
||||
div
|
||||
|
||||
@@ -6,8 +6,39 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
||||
.row-fluid
|
||||
.span4
|
||||
|
||||
|
||||
// ------ Bosses -------
|
||||
.modal.inline-modal(ng-if='group.type==="party" && group.quest.key && group.quest.active==false')
|
||||
.modal-header(bindonce='group')
|
||||
h3 Quest: {{Content.quests[group.quest.key].text}}
|
||||
.modal-body
|
||||
table.table.table-striped
|
||||
tr(ng-repeat='member in group.members')
|
||||
td {{member.profile.name}}
|
||||
td {{group.quest.members[member._id] == undefined ? 'Pending' : k ? 'Rejected' : 'Accepted'}}
|
||||
button.btn.btn-warning(ng-click='party.$questAccept({"force":true})') Force Start
|
||||
|
||||
.modal.inline-modal(ng-if='group.type=="party" && group.quest.key && group.quest.active==true')
|
||||
.modal-header(bindonce='group')
|
||||
h3 {{Content.quests[group.quest.key].text}}
|
||||
.modal-body
|
||||
div(class="quest_{{group.quest.key}}")
|
||||
//-
|
||||
.progress(style="height:10px")
|
||||
.bar(style='width: {{Shared.percent(group.quest.hp, Content.quests[group.quest.key].hp)}}%;')
|
||||
span.meter-text
|
||||
i.icon-heart
|
||||
| {{group.quest.hp | number:0}} / {{Content.quests[group.quest.key].hp}}
|
||||
.hero-stats
|
||||
.meter.health(title='Boss Health')
|
||||
.bar(style='width: {{Shared.percent(group.quest.hp, Content.quests[group.quest.key].hp)}}%;')
|
||||
span.meter-text
|
||||
i.icon-heart
|
||||
| {{group.quest.hp | number:0}} / {{Content.quests[group.quest.key].hp}}
|
||||
p {{Content.quests[group.quest.key].notes}}
|
||||
|
||||
// ------ Information -------
|
||||
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;')
|
||||
.modal.inline-modal
|
||||
.modal-header(bindonce='group')
|
||||
span(ng-if='group.leader == user.id')
|
||||
button.btn.btn-primary.pull-right(ng-click='save(group)', ng-show='group._editing') Save
|
||||
@@ -35,7 +66,7 @@ a.pull-right.gem-wallet(popover-trigger='mouseenter', popover-title='Guild Bank'
|
||||
include ./challenge-box
|
||||
|
||||
// ------ Members -------
|
||||
.modal(style='position: relative;top: auto;left: auto;right: auto;margin: 0 auto 20px;z-index: 1;max-width: 100%;')
|
||||
.modal.inline-modal
|
||||
.modal-header
|
||||
h3 Members
|
||||
.modal-body
|
||||
|
||||
@@ -6,5 +6,6 @@ include ./new-stuff
|
||||
include ./buy-gems
|
||||
include ./members
|
||||
include ./settings
|
||||
include ./pets
|
||||
include ./drops
|
||||
include ./classes
|
||||
include ./quests
|
||||
28
views/shared/modals/quests.jade
Normal file
28
views/shared/modals/quests.jade
Normal file
@@ -0,0 +1,28 @@
|
||||
div(modal='modals.showQuest', ng-controller='InventoryCtrl')
|
||||
.modal-header
|
||||
h3 {{selectedQuest.text}}
|
||||
.modal-body
|
||||
table
|
||||
tr
|
||||
td
|
||||
p {{selectedQuest.notes}}
|
||||
td
|
||||
div(class='quest_{{selectedQuest.name}}')
|
||||
hr
|
||||
div(style='clear:left;clear:right')
|
||||
.npc_ian.pull-left
|
||||
p Clicking "Invite" will send an invitation to your party members. When all members have accepted or denied, the quest begins. If they take to long, feel free to force-start the quest under Options > Social > Party
|
||||
.modal-footer
|
||||
button.btn.btn-default.btn-small.btn-cancel(ng-click='closeQuest()') Cancel
|
||||
button.btn.btn-default.btn-primary(ng-click='questInit()') Invite Party
|
||||
|
||||
div(modal='party.quest.key && !questHold && party.quest.members[user._id] == undefined')
|
||||
.modal-header
|
||||
h3 Quest Invitation
|
||||
.modal-body
|
||||
p You have been invited to {{Content.quests[party.quest.key].text}}!
|
||||
p (TODO list rewards)
|
||||
.modal-footer
|
||||
button.btn.btn-default.btn-small.btn-cancel(ng-click='questHold = true') Ask Later
|
||||
button.btn.btn-default.btn-small.btn-cancel(ng-click='party.$questReject()') Reject
|
||||
button.btn.btn-default.btn-primary(ng-click='party.$questAccept()') Accept
|
||||
Reference in New Issue
Block a user