v3 adapt v2: port quests

This commit is contained in:
Matteo Pagliazzi
2016-04-07 16:11:17 +02:00
parent 2d3fbe9f13
commit 5a259e7a25
2 changed files with 174 additions and 123 deletions

View File

@@ -860,6 +860,11 @@ api.removeMember = function(req, res, next){
// ------------------------------------ // ------------------------------------
// Quests // Quests
// ------------------------------------ // ------------------------------------
function canStartQuestAutomatically (group) {
// If all members are either true (accepted) or false (rejected) return true
// If any member is null/undefined (undecided) return false
return _.every(group.quest.members, _.isBoolean);
}
function questStart(req, res, next) { function questStart(req, res, next) {
var group = res.locals.group; var group = res.locals.group;
@@ -975,70 +980,99 @@ api.questAccept = function(req, res, next) {
if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."}); if (quest.lvl && user.stats.lvl < quest.lvl) return res.status(400).json({err: "You must be level "+quest.lvl+" to begin this quest."});
if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'}); if (group.quest.key) return res.status(400).json({err: 'Your party is already on a quest. Try again when the current quest has ended.'});
if (!user.items.quests[key]) return res.status(400).json({err: "You don't own that quest scroll"}); if (!user.items.quests[key]) return res.status(400).json({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"
// or everyone has either accepted/rejected, then we store quest key in user object.
_.each(group.members, function(m){
if (m == user._id) {
var analyticsData = {
category: 'behavior',
owner: true,
response: 'accept',
gaLabel: 'accept',
questName: key,
uuid: user._id,
};
analytics.track('quest',analyticsData);
group.quest.members[m] = true;
group.quest.leader = user._id;
} else {
User.update({_id:m},{$set: {'party.quest.RSVPNeeded': true, 'party.quest.key': group.quest.key}}).exec();
group.quest.members[m] = undefined;
}
});
User.find({ User.find({
_id: { 'party._id': group._id,
$in: _.without(group.members, user._id) _id: {$ne: user._id},
}).select('auth.facebook auth.local preferences.emailNotifications profile.name pushDevices')
.exec().then(members => {
group.markModified('quest');
group.quest.key = questKey;
group.quest.leader = user._id;
group.quest.members = {};
group.quest.members[user._id] = true;
user.party.quest.RSVPNeeded = false;
user.party.quest.key = questKey;
return User.update({
'party._id': group._id,
_id: {$ne: user._id},
}, {
$set: {
'party.quest.RSVPNeeded': true,
'party.quest.key': questKey,
},
}, {multi: true}).exec();
}).then(() => {
_.each(members, (member) => {
group.quest.members[member._id] = null;
});
if (canStartQuestAutomatically(group)) {
group.startQuest(user).then(() => {
return Q.all([group.save(), user.save()])
})
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
} else {
Q.all([group.save(), user.save()])
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
} }
}, {auth: 1, preferences: 1, profile: 1, pushDevices: 1}, function(err, members){ }).catch(next);
if(err) return next(err);
var inviterVars = utils.getUserInfo(user, ['name', 'email']);
var membersToEmail = members.filter(function(member){
return member.preferences.emailNotifications.invitedQuest !== false;
});
utils.txnEmail(membersToEmail, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [
{name: 'QUEST_NAME', content: quest.text()},
{name: 'INVITER', content: inviterVars.name},
{name: 'PARTY_URL', content: '/#/options/groups/party'}
]);
_.each(members, function(groupMember){
pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() }));
});
questStart(req,res,next);
});
// Party member accepting the invitation // Party member accepting the invitation
} else { } else {
if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'}); group.markModified('quest');
var analyticsData = {
category: 'behavior',
owner: false,
response: 'accept',
gaLabel: 'accept',
questName: group.quest.key,
uuid: user._id,
};
analytics.track('quest',analyticsData);
group.quest.members[user._id] = true; group.quest.members[user._id] = true;
User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false}}).exec(); user.party.quest.RSVPNeeded = false;
questStart(req,res,next);
if (canStartQuestAutomatically(group)) {
group.startQuest(user).then(() => {
return Q.all([group.save(), user.save()])
})
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
} else {
Q.all([group.save(), user.save()])
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
}
} }
} }
@@ -1046,84 +1080,102 @@ api.questReject = function(req, res, next) {
var group = res.locals.group; var group = res.locals.group;
var user = res.locals.user; var user = res.locals.user;
if (!group.quest.key) return res.status(400).json({err:'No quest invitation has been sent out yet.'});
var analyticsData = {
category: 'behavior',
owner: false,
response: 'reject',
gaLabel: 'reject',
questName: group.quest.key,
uuid: user._id,
};
analytics.track('quest',analyticsData);
group.quest.members[user._id] = false; group.quest.members[user._id] = false;
User.update({_id:user._id}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec(); group.markModified('quest.members');
questStart(req,res,next);
user.party.quest = Group.cleanQuestProgress();
user.markModified('party.quest');
if (canStartQuestAutomatically(group)) {
group.startQuest(user).then(() => {
return Q.all([group.save(), user.save()])
})
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
} else {
Q.all([group.save(), user.save()])
.then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
})
.catch(next);
}
} }
api.questCancel = function(req, res, next){ api.questCancel = function(req, res, next){
var group = res.locals.group;
group.quest = Group.cleanGroupQuest();
group.markModified('quest');
Q.all([
group.save(),
User.update(
{'party._id': groupId},
{$set: {'party.quest': Group.cleanQuestProgress()}},
{multi: true}
),
]).then(results => {
results[0].getTransformedData({
cb (err, groupTransformed) {
if (err) return next(err);
res.json(groupTransformed);
},
populateMembers: group.type === 'party' ? partyFields : nameFields,
});
}).catch(next);
// Cancel a quest BEFORE it has begun (i.e., in the invitation stage) // 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. // 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. // 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);
_.each(group.members, function(m){
User.update({_id:m}, {$set: {'party.quest.RSVPNeeded': false, 'party.quest.key': null}}).exec();
});
}
}
], function(err){
if (err) return next(err);
res.json(group);
group = null;
})
} }
api.questAbort = function(req, res, next){ api.questAbort = function(req, res, next){
// Abort a quest AFTER it has begun (see questCancel for BEFORE)
var group = res.locals.group; var group = res.locals.group;
async.parallel([
function(cb){ let memberUpdates = User.update({
User.update( 'party._id': group._id,
{_id:{$in: _.keys(group.quest.members)}}, }, {
{ $set: {'party.quest': Group.cleanQuestProgress()},
$set: {'party.quest':Group.cleanQuestProgress()}, $inc: {_v: 1}, // TODO update middleware
$inc: {_v:1} }, {multi: true}).exec();
let questLeaderUpdate = User.update({
_id: group.quest.leader,
}, {
$inc: {
[`items.quests.${group.quest.key}`]: 1, // give back the quest to the quest leader
}, },
{multi:true}, }).exec();
cb);
}, group.quest = Group.cleanGroupQuest();
// Refund party leader quest scroll
function(cb){
if (group.quest.active) {
var update = {$inc:{}};
update['$inc']['items.quests.' + group.quest.key] = 1;
User.update({_id:group.quest.leader}, update).exec();
}
group.quest = {key:null,progress:{},leader:null};
group.markModified('quest'); group.markModified('quest');
group.save(cb);
}, function(cb){ Q.all([group.save(), memberUpdates, questLeaderUpdate])
populateQuery(group.type, Group.findById(group._id)).exec(cb); .then(results => {
} results[0].getTransformedData({
], function(err, results){ cb (err, groupTransformed) {
if (err) return next(err); if (err) return next(err);
res.json(groupTransformed);
var groupClone = clone(group); },
populateMembers: group.type === 'party' ? partyFields : nameFields,
groupClone.members = results[2].members; });
res.json(groupClone);
group = null;
}) })
.catch(next);
} }
api.questLeave = function(req, res, next) { api.questLeave = function(req, res, next) {
@@ -1143,7 +1195,7 @@ api.questLeave = function(req, res, next) {
return res.status(403).json({ err: 'Quest leader cannot leave quest' }); return res.status(403).json({ err: 'Quest leader cannot leave quest' });
} }
delete group.quest.members[user._id]; group.quest.members[user._id] = false;
group.markModified('quest.members'); group.markModified('quest.members');
user.party.quest = Group.cleanQuestProgress(); user.party.quest = Group.cleanQuestProgress();
@@ -1160,7 +1212,6 @@ api.questLeave = function(req, res, next) {
}); });
} }
// TODO port to api v3? in tojson?
function _purgeFlagInfoFromChat(group, user) { function _purgeFlagInfoFromChat(group, user) {
group.chat = _.filter(group.chat, function(message) { return !message.flagCount || message.flagCount < 2; }); group.chat = _.filter(group.chat, function(message) { return !message.flagCount || message.flagCount < 2; });
_.each(group.chat, function (message) { _.each(group.chat, function (message) {

View File

@@ -335,7 +335,7 @@ api.cancelQuest = {
group.quest = Group.cleanGroupQuest(); group.quest = Group.cleanGroupQuest();
group.markModified('quest'); group.markModified('quest');
let [savedGroup] = await Promise.all([ let [savedGroup] = await Q.all([
group.save(), group.save(),
User.update( User.update(
{'party._id': groupId}, {'party._id': groupId},