mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
v3 adapt v2: port quests
This commit is contained in:
@@ -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')
|
||||||
}, {auth: 1, preferences: 1, profile: 1, pushDevices: 1}, function(err, members){
|
.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);
|
if (err) return next(err);
|
||||||
|
res.json(groupTransformed);
|
||||||
var inviterVars = utils.getUserInfo(user, ['name', 'email']);
|
},
|
||||||
|
populateMembers: group.type === 'party' ? partyFields : nameFields,
|
||||||
var membersToEmail = members.filter(function(member){
|
|
||||||
return member.preferences.emailNotifications.invitedQuest !== false;
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
|
||||||
utils.txnEmail(membersToEmail, ('invite-' + (quest.boss ? 'boss' : 'collection') + '-quest'), [
|
} else {
|
||||||
{name: 'QUEST_NAME', content: quest.text()},
|
Q.all([group.save(), user.save()])
|
||||||
{name: 'INVITER', content: inviterVars.name},
|
.then(results => {
|
||||||
{name: 'PARTY_URL', content: '/#/options/groups/party'}
|
results[0].getTransformedData({
|
||||||
]);
|
cb (err, groupTransformed) {
|
||||||
|
if (err) return next(err);
|
||||||
_.each(members, function(groupMember){
|
res.json(groupTransformed);
|
||||||
pushNotify.sendNotify(groupMember, shared.i18n.t('questInvitationTitle'), shared.i18n.t('questInvitationInfo', { quest: quest.text() }));
|
},
|
||||||
});
|
populateMembers: group.type === 'party' ? partyFields : nameFields,
|
||||||
|
|
||||||
questStart(req,res,next);
|
|
||||||
});
|
});
|
||||||
|
})
|
||||||
|
.catch(next);
|
||||||
|
}
|
||||||
|
}).catch(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}
|
$inc: {_v: 1}, // TODO update middleware
|
||||||
|
}, {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) {
|
||||||
|
|||||||
@@ -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},
|
||||||
|
|||||||
Reference in New Issue
Block a user