Merge pull request #4002 from Alys/questAbortFix

add Cancel button for quests, allow party to cancel/abort if quest owner missing, add Debug tools for quest testing
This commit is contained in:
Alice Harris 2014-09-12 14:01:09 +10:00
commit e6a29f80fa
7 changed files with 115 additions and 21 deletions

View file

@ -54,6 +54,11 @@
* Debug functions. Note that the server route for gems is only available if process.env.DEBUG=true
*/
if (_.contains(['development','test'],window.env.NODE_ENV)) {
$scope.setHealthLow = function(){
User.set({
'stats.hp': 1
});
}
$scope.addMissedDay = function(){
if (!confirm("Are you sure you want to reset the day?")) return;
var dayBefore = moment(User.user.lastCron).subtract('days', 1).toDate();
@ -65,11 +70,16 @@
User.log({});
})
}
$scope.addGold = function(){
User.set({
'stats.gp': User.user.stats.gp + 500,
});
}
$scope.addLevelsAndGold = function(){
User.set({
'stats.exp': User.user.stats.exp + 10000,
'stats.gp': User.user.stats.gp + 10000,
'stats.mp': User.user.stats.mp + 10000
'stats.gp': User.user.stats.gp + 10000,
'stats.mp': User.user.stats.mp + 10000
});
}
$scope.addOneLevel = function(){
@ -77,6 +87,10 @@
'stats.exp': User.user.stats.exp + (Math.round(((Math.pow(User.user.stats.lvl, 2) * 0.25) + (10 * User.user.stats.lvl) + 139.75) / 10) * 10)
});
}
$scope.addBossQuestProgressUp = function(){
User.set({
'party.quest.progress.up': User.user.party.quest.progress.up + 1000
});
}
}
}])
}])

View file

@ -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();
}
}
])

View file

@ -19,6 +19,7 @@ angular.module('groupServices', ['ngResource']).
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'},
questCancel: {method: "POST", url: API_URL + '/api/v2/groups/:gid/questCancel'},
questAbort: {method: "POST", url: API_URL + '/api/v2/groups/:gid/questAbort'}
});

View file

@ -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)
@ -571,13 +578,13 @@ api.questAccept = function(req, res, next) {
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 (!group) return res.json(400, {err: "Must be in a party to start quests."});
// 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) {
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 (quest.lvl && user.stats.lvl < quest.lvl) return res.json(400, {err: "You must be level "+quest.lvl+" to begin 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(400, {err: "You don't own that quest scroll"});
group.quest.key = key;
@ -609,8 +616,30 @@ api.questReject = function(req, res, next) {
questStart(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){
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);
res.json(group);
})
}
api.questAbort = function(req, res, next){
// Abort a quest AFTER it has begun (see questCancel for BEFORE)
var group = res.locals.group;
async.parallel([
function(cb){
@ -639,3 +668,4 @@ api.questAbort = function(req, res, next){
res.json(group);
})
}

View file

@ -465,10 +465,18 @@ module.exports = (swagger, v2) ->
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
action: groups.questReject
"/groups/{gid}/questCancel":
spec:
method: 'POST'
description: 'Cancel quest before it starts (in invitation stage)'
parameters: [path('gid','Group to cancel quest in','string')]
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
action: groups.questCancel
"/groups/{gid}/questAbort":
spec:
method: 'POST'
description: 'Abort quest'
description: 'Abort quest after it has started (all progress will be lost)'
parameters: [path('gid','Group to abort quest in','string')]
middleware: [auth.auth, i18n.getUserLanguage, groups.attachGroup]
action: groups.questAbort

View file

@ -9,18 +9,26 @@ mixin boss(tavern, mobile)
table.table.table-striped
tr(ng-repeat='member in group.members')
td
span(ng-if=':: 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)')
|*&nbsp;
{{::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')}}
|*&nbsp;
=env.t('questOwner')
span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && isMemberOfPendingQuest(group.quest.leader,group)')
|*&nbsp;
=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.quest.leader==user._id', ng-click='party.$questAccept({"force":true})')=env.t('begin')
//-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')
@ -62,11 +70,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)')
|*&nbsp;
{{::v.profile.name}}
|*&nbsp;
=env.t('questOwner')
span(ng-if=':: group.quest.leader && isMemberOfGroup(group.quest.leader,group) && isMemberOfRunningQuest(group.quest.leader,group)')
|*&nbsp;
=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')
@ -84,6 +98,6 @@ mixin boss(tavern, mobile)
br
p=env.t('bossColl2')
unless tavern
button.btn.btn-xs.btn-danger(ng-if='::!group.quest.leader || 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')

View file

@ -74,10 +74,13 @@ footer.footer(ng-controller='FooterCtrl')
else if (env.NODE_ENV==='development' || env.NODE_ENV==='test') && !env.isStaticPage
h4 Debug
.btn-group-vertical
a.btn.btn-default(ng-click='setHealthLow()') Health = 1
a.btn.btn-default(ng-click='addMissedDay()') +1 Missed Day
a.btn.btn-default(ng-click='addTenGems()') +10 Gems
a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP
a.btn.btn-default(ng-click='addGold()') +GP
a.btn.btn-default(ng-click='addLevelsAndGold()') +Exp +GP +MP
a.btn.btn-default(ng-click='addOneLevel()') +1 Level
a.btn.btn-default(ng-click='addBossQuestProgressUp()') +1000 Boss Quest Progress Up
div(ng-init='deferredScripts()')