mirror of
https://github.com/sudoxnym/habitica.git
synced 2026-07-14 07:52:15 +00:00
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:
commit
e6a29f80fa
7 changed files with 115 additions and 21 deletions
|
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}])
|
||||
}])
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
])
|
||||
|
||||
|
|
|
|||
|
|
@ -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'}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)')
|
||||
|*
|
||||
{{::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')}}
|
||||
|*
|
||||
=env.t('questOwner')
|
||||
|
||||
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.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)')
|
||||
|*
|
||||
{{::v.profile.name}}
|
||||
|*
|
||||
=env.t('questOwner')
|
||||
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')
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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()')
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue