diff --git a/public/js/controllers/groupsCtrl.js b/public/js/controllers/groupsCtrl.js index ca062e95e8..8d723ce055 100644 --- a/public/js/controllers/groupsCtrl.js +++ b/public/js/controllers/groupsCtrl.js @@ -3,6 +3,24 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', '$http', 'API_URL', '$q', 'User', 'Members', '$state', function($scope, $rootScope, Shared, Groups, $http, API_URL, $q, User, Members, $state) { + $scope.isMemberOfPendingQuest = function(userid, group){ + if (!group.quest || !group.quest.members) return false; + if (group.quest.active) return false; // quest is started, not pending + return userid in group.quest.members && group.quest.members[userid] != false; + } + + $scope.isMemberOfRunningQuest = function(userid, group){ + if (!group.quest || !group.quest.members) return false; + if (!group.quest.active) return false; // quest is pending, not started + return group.quest.members[userid]; + } + + $scope.isMemberOfGroup = function(userid, group){ + if (!group.members) return false; + var memberIds = _.map(group.members, function(x){return x._id}); + return ~(memberIds.indexOf(userid)); + } + $scope.isMember = function(user, group){ return ~(group.members.indexOf(user._id)); } @@ -362,11 +380,17 @@ habitrpg.controller("GroupsCtrl", ['$scope', '$rootScope', 'Shared', 'Groups', ' User.set({'invitations.party':{}}); } + $scope.questCancel = function(){ + if (!confirm(window.env.t('sureCancel'))) return; + $rootScope.party.$questCancel(); + } + $scope.questAbort = function(){ if (!confirm(window.env.t('sureAbort'))) return; if (!confirm(window.env.t('doubleSureAbort'))) return; $rootScope.party.$questAbort(); } + } ]) diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 6fd86bdd90..98398b329a 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -464,7 +464,15 @@ api.removeMember = function(req, res, next){ } if(_.contains(group.members, uuid)){ - Group.update({_id:group._id},{$pull:{members:uuid},$inc:{memberCount:-1}}, function(err, saved){ + var update = {$pull:{members:uuid}}; + if(group.quest && group.quest.members){ + // remove member from quest + update['$unset'] = {}; + update['$unset']['quest.members.' + uuid] = ""; + // TODO: run cleanQuestProgress and return scroll to member if member was quest owner + } + update['$inc'] = {memberCount: -1}; + Group.update({_id:group._id},update, function(err, saved){ if (err) return next(err); // Sending an empty 204 because Group.update doesn't return the group @@ -530,7 +538,6 @@ questStart = function(req, res, next) { quest = shared.content.quests[key], collected = quest.collect ? _.transform(quest.collect, function(m,v,k){m[k]=0}) : {}; - // TODO will this handle appropriately when people leave/join party between quest invite? _.each(group.members, function(m){ var updates = {$set:{},$inc:{'_v':1}}; if (m == group.quest.leader) @@ -611,12 +618,19 @@ api.questReject = function(req, res, next) { api.questCancel = function(req, res, next){ // Cancel a quest BEFORE it has begun (i.e., in the invitation stage) + // Quest scroll has not yet left quest owner's inventory so no need to return it. + // Do not wipe quest progress for members because they'll want it to be applied to the next quest that's started. var group = res.locals.group; async.parallel([ function(cb){ - group.quest = {key:null,progress:{},leader:null}; - group.markModified('quest'); - group.save(cb); + if (! group.quest.active) { + // Do not cancel active quests because this function does + // not do the clean-up required for that. + // TODO: return an informative error when quest is active + group.quest = {key:null,progress:{},leader:null}; + group.markModified('quest'); + group.save(cb); + } } ], function(err){ if (err) return next(err); diff --git a/views/options/social/boss.jade b/views/options/social/boss.jade index 837deff14d..833014dbce 100644 --- a/views/options/social/boss.jade +++ b/views/options/social/boss.jade @@ -9,25 +9,27 @@ mixin boss(tavern, mobile) table.table.table-striped tr(ng-repeat='member in group.members') td - span(ng-if=':: group.quest.leader && group.quest.leader==member._id') + span(ng-if=':: group.quest.leader && group.quest.leader==member._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)') |*  + // XXX does this trigger badly when ex-leader reenters group? {{::member.profile.name}} td {{group.quest.members[member._id] === true ? env.t('accepted') : group.quest.members[member._id] === false ? env.t('rejected') : env.t('pending')}} - //span(ng-if=':: group.quest.leader && group.members.indexOf(group.quest.leader) > -1') - |*  - =env.t('questOwner') - br - span(ng-if=':: !group.quest.leader') no quest leader - br - span(ng-if=':: group.members.indexOf(group.quest.leader) == -1') not in group - // span(ng-if=':: !group.quest.leader || group.members.indexOf(group.quest.leader) == -1')=env.t('questOwnerMIA') + + span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)') + |*  + =env.t('questOwner') + span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && ! isMemberOfPendingQuest(group.quest.leader,group)') + =env.t('questOwnerNotInPendingQuest') + span(ng-if=':: ! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group)') + =env.t('questOwnerNotInPendingQuestParty') hr .npc_ian.pull-left p=env.t('questStart') - button.btn.btn-sm.btn-warning(ng-if=':: !group.quest.leader || group.members.indexOf(group.quest.leader) == -1 || group.quest.leader==user._id', ng-click='party.$questAccept({"force":true})')=env.t('begin') - //button.btn.btn-sm.btn-warning(ng-if=':: !group.quest.leader || group.members.indexOf(group.quest.leader) == -1 || group.quest.leader==user._id', ng-click='party.$questCancel()')=env.t('cancel') - //-TODO Cancel button - //-TODO Both force-start & cancel should only be available to quest-initiator + + // only the quest owner sees the begin button: + button.btn.btn-sm.btn-warning(ng-if=':: group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)', ng-click='party.$questAccept({"force":true})')=env.t('begin') + // only the quest owner sees the cancel button UNLESS the quest owner is no longer in the quest/party and then everyone sees it: + button.btn.btn-sm.btn-danger(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)) || (! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfPendingQuest(group.quest.leader,group))', ng-click='questCancel()')=env.t('cancel') div(ng-if='group.quest.active==true') div(ng-if='::Content.quests[group.quest.key].boss',ng-init='boss=Content.quests[group.quest.key].boss;progress=group.quest.progress') @@ -69,13 +71,17 @@ mixin boss(tavern, mobile) table.table.table-striped tr(ng-repeat='(k,v) in group.members', ng-show='group.quest.members[v._id]') td - span(ng-if=':: group.quest.leader==v._id') + span(ng-if=':: group.quest.leader && group.quest.leader==v._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)') |*  {{::v.profile.name}} - // span(ng-if=':: group.quest.leader && group.members.indexOf(group.quest.leader) > -1') - // |*  - // =env.t('questOwner') - // span(ng-if=':: !group.quest.leader || group.members.indexOf(group.quest.leader) == -1')=env.t('questOwnerMIA') + span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)') + |*  + =env.t('questOwner') + span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && ! isMemberOfRunningQuest(group.quest.leader,group)') + =env.t('questOwnerNotInRunningQuest') + span(ng-if=':: ! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group)') + =env.t('questOwnerNotInRunningQuestParty') + quest-rewards(key='{{::group.quest.key}}') div(ng-if='::Content.quests[group.quest.key].boss') @@ -93,6 +99,6 @@ mixin boss(tavern, mobile) br p=env.t('bossColl2') - unless tavern - button.btn.btn-xs.btn-danger(ng-if='::!group.quest.leader || group.members.indexOf(group.quest.leader) == -1 || group.quest.leader==user._id', ng-click='questAbort()')=env.t('abort') + // only the quest owner sees the abort button UNLESS the quest owner is no longer in the quest/party and then everyone sees it: + button.btn.btn-sm.btn-warning(ng-if=':: (group.quest.leader && group.quest.leader==user._id && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)) || ! group.quest.leader || ! isMemberOfGroup(group.quest.leader,group) || ! isMemberOfRunningQuest(group.quest.leader,group)', ng-click='questAbort()')=env.t('abort')