mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
feat: Use party's quest key in cron
This commit is contained in:
@@ -5,8 +5,6 @@ import { quests as questScrolls } from '../../../../../common/script/content';
|
||||
import * as email from '../../../../../website/server/libs/api-v3/email';
|
||||
|
||||
describe('Group Model', () => {
|
||||
context('Instance Methods', () => {
|
||||
describe('#startQuest', () => {
|
||||
let party, questLeader, participatingMember, nonParticipatingMember, undecidedMember;
|
||||
|
||||
beforeEach(async () => {
|
||||
@@ -20,6 +18,7 @@ describe('Group Model', () => {
|
||||
|
||||
questLeader = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Quest Leader' },
|
||||
items: {
|
||||
quests: {
|
||||
whale: 1,
|
||||
@@ -31,12 +30,15 @@ describe('Group Model', () => {
|
||||
|
||||
participatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Participating Member' },
|
||||
});
|
||||
nonParticipatingMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Non-Participating Member' },
|
||||
});
|
||||
undecidedMember = new User({
|
||||
party: { _id: party._id },
|
||||
profile: { name: 'Undecided Member' },
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
@@ -48,6 +50,8 @@ describe('Group Model', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
context('Instance Methods', () => {
|
||||
describe('#startQuest', () => {
|
||||
context('Failure Conditions', () => {
|
||||
it('throws an error if group is not a party', async () => {
|
||||
let guild = new Group({
|
||||
@@ -74,11 +78,12 @@ describe('Group Model', () => {
|
||||
party.quest.key = 'whale';
|
||||
party.quest.active = false;
|
||||
party.quest.leader = questLeader._id;
|
||||
party.quest.members = { };
|
||||
party.quest.members[questLeader._id] = true;
|
||||
party.quest.members[participatingMember._id] = true;
|
||||
party.quest.members[nonParticipatingMember._id] = false;
|
||||
party.quest.members[undecidedMember._id] = null;
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
});
|
||||
|
||||
it('activates quest', () => {
|
||||
@@ -296,5 +301,344 @@ describe('Group Model', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processQuestProgress', () => {
|
||||
let progress;
|
||||
|
||||
beforeEach(async () => {
|
||||
progress = {
|
||||
up: 5,
|
||||
down: -5,
|
||||
};
|
||||
|
||||
party.quest.active = true;
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
|
||||
await party.save();
|
||||
|
||||
sandbox.stub(Group, 'processBossQuest').returns(Promise.resolve());
|
||||
sandbox.stub(Group, 'processCollectionQuest').returns(Promise.resolve());
|
||||
});
|
||||
|
||||
it('returns early if user is not in a party', async () => {
|
||||
let userWithoutParty = new User();
|
||||
|
||||
await userWithoutParty.save();
|
||||
|
||||
await Group.processQuestProgress(userWithoutParty, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('returns early if party is not on quest', async () => {
|
||||
party.quest.active = false;
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('returns early if user is not on quest', async () => {
|
||||
await Group.processQuestProgress(nonParticipatingMember, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('returns early if user has made no progress', async () => {
|
||||
await Group.processQuestProgress(participatingMember, null);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('returns early if quest does not exist', async () => {
|
||||
party.quest.key = 'foobar';
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('calls processBossQuest if quest is a boss quest', async () => {
|
||||
party.quest.key = 'whale';
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.be.calledOnce;
|
||||
expect(Group.processCollectionQuest).to.not.be.called;
|
||||
});
|
||||
|
||||
it('calls processCollectionQuest if quest is a boss quest', async () => {
|
||||
party.quest.key = 'evilsanta2';
|
||||
await party.save();
|
||||
|
||||
await Group.processQuestProgress(participatingMember, progress);
|
||||
|
||||
expect(Group.processBossQuest).to.not.be.called;
|
||||
expect(Group.processCollectionQuest).to.be.calledOnce;
|
||||
});
|
||||
});
|
||||
|
||||
describe('processBossQuest', () => {
|
||||
let progress, quest;
|
||||
|
||||
beforeEach(async () => {
|
||||
progress = {
|
||||
up: 5,
|
||||
down: -5,
|
||||
};
|
||||
quest = questScrolls.whale;
|
||||
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
party.quest.key = 'whale';
|
||||
|
||||
await party.save();
|
||||
await party.startQuest(questLeader);
|
||||
|
||||
sandbox.stub(party, 'sendChat');
|
||||
});
|
||||
|
||||
it('applies user\'s progress to quest boss hp', async () => {
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.quest.progress.hp).to.eql(495);
|
||||
});
|
||||
|
||||
it('sends a chat message about progress', async () => {
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledOnce;
|
||||
expect(party.sendChat).to.be.calledWith('`Participating Member attacks Wailing Whale for 5.0 damage.` `Wailing Whale attacks party for 7.5 damage.`');
|
||||
});
|
||||
|
||||
it('applies damage only to participating members of party', async () => {
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
let [
|
||||
updatedLeader,
|
||||
updatedParticipatingMember,
|
||||
updatedNonParticipatingMember,
|
||||
updatedUndecidedMember,
|
||||
] = await Promise.all([
|
||||
User.findById(questLeader._id),
|
||||
User.findById(participatingMember._id),
|
||||
User.findById(nonParticipatingMember._id),
|
||||
User.findById(undecidedMember._id),
|
||||
]);
|
||||
|
||||
expect(updatedLeader.stats.hp).to.eql(42.5);
|
||||
expect(updatedParticipatingMember.stats.hp).to.eql(42.5);
|
||||
expect(updatedNonParticipatingMember.stats.hp).to.eql(50);
|
||||
expect(updatedUndecidedMember.stats.hp).to.eql(50);
|
||||
});
|
||||
|
||||
it('sends message about victory', async () => {
|
||||
progress.up = 999;
|
||||
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledTwice;
|
||||
expect(party.sendChat).to.be.calledWith('`You defeated Wailing Whale! Questing party members receive the rewards of victory.`');
|
||||
});
|
||||
|
||||
it('calls finishQuest when boss has <= 0 hp', async () => {
|
||||
progress.up = 999;
|
||||
sandbox.spy(party, 'finishQuest');
|
||||
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.finishQuest).to.be.calledOnce;
|
||||
expect(party.finishQuest).to.be.calledWith(quest);
|
||||
});
|
||||
|
||||
context('rage quests', () => {
|
||||
beforeEach(async () => {
|
||||
party.quest.active = false;
|
||||
party.quest.key = 'trex_undead';
|
||||
quest = questScrolls[party.quest.key];
|
||||
|
||||
await party.save();
|
||||
|
||||
await party.startQuest(questLeader);
|
||||
});
|
||||
|
||||
it('applies down progress to boss rage', async () => {
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.quest.progress.rage).to.eql(10);
|
||||
});
|
||||
|
||||
it('activates rage when progress.down triggers rage bar', async () => {
|
||||
progress.down = -999;
|
||||
|
||||
party.quest.progress.hp = 300;
|
||||
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledWith(quest.boss.rage.effect('en'));
|
||||
expect(party.quest.progress.hp).to.eql(383.5);
|
||||
expect(party.quest.progress.rage).to.eql(0);
|
||||
});
|
||||
|
||||
it('rage sets boss hp to max hp if raging would have caused hp to be higher than the max', async () => {
|
||||
progress.down = -999;
|
||||
|
||||
party.quest.progress.hp = 490;
|
||||
|
||||
await Group.processBossQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.quest.progress.hp).to.eql(500);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('processCollectionQuest', () => {
|
||||
let progress, quest;
|
||||
|
||||
beforeEach(async () => {
|
||||
progress = {
|
||||
collect: {
|
||||
soapBars: 5,
|
||||
},
|
||||
};
|
||||
quest = questScrolls.atom1;
|
||||
|
||||
party.quest.members = {
|
||||
[questLeader._id]: true,
|
||||
[participatingMember._id]: true,
|
||||
[nonParticipatingMember._id]: false,
|
||||
[undecidedMember._id]: null,
|
||||
};
|
||||
party.quest.key = 'atom1';
|
||||
|
||||
await party.save();
|
||||
await party.startQuest(questLeader);
|
||||
|
||||
sandbox.stub(party, 'sendChat');
|
||||
});
|
||||
|
||||
it('applies user\'s progress to found quest items', async () => {
|
||||
await Group.processCollectionQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.quest.progress.collect.soapBars).to.eq(5)
|
||||
});
|
||||
|
||||
it('sends a chat message about progress', async () => {
|
||||
await Group.processCollectionQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledOnce;
|
||||
expect(party.sendChat).to.be.calledWith('`Participating Member found 5 Bars of Soap.`');
|
||||
});
|
||||
|
||||
it('sends a chat message if no progress is made', async () => {
|
||||
delete progress.collect.soapBars;
|
||||
|
||||
await Group.processCollectionQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledOnce;
|
||||
expect(party.sendChat).to.be.calledWith('`Participating Member found nothing.`');
|
||||
});
|
||||
|
||||
it('sends message about victory', async () => {
|
||||
progress.collect.soapBars = 500;
|
||||
|
||||
await Group.processCollectionQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.sendChat).to.be.calledTwice;
|
||||
expect(party.sendChat).to.be.calledWith('`All items found! Party has received their rewards.`');
|
||||
});
|
||||
|
||||
it('calls finishQuest when all items are found', async () => {
|
||||
progress.collect.soapBars = 999;
|
||||
sandbox.spy(party, 'finishQuest');
|
||||
|
||||
await Group.processCollectionQuest({
|
||||
user: participatingMember,
|
||||
progress,
|
||||
quest,
|
||||
group: party,
|
||||
});
|
||||
|
||||
expect(party.finishQuest).to.be.calledOnce;
|
||||
expect(party.finishQuest).to.be.calledWith(quest);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -158,13 +158,7 @@ async function cronAsync (req, res) {
|
||||
});
|
||||
await Bluebird.all(toSave);
|
||||
|
||||
let quest = common.content.quests[user.party.quest.key];
|
||||
|
||||
if (quest) {
|
||||
// If user is on a quest, roll for boss & player, or handle collections
|
||||
let questType = quest.boss ? 'boss' : 'collect';
|
||||
await Group[`${questType}Quest`](user, progress);
|
||||
}
|
||||
await Group.processQuestProgress(user, progress);
|
||||
|
||||
// Set _cronSignature, lastCron and auth.timestamps.loggedin to signal end of cron
|
||||
await User.update({
|
||||
|
||||
@@ -491,10 +491,13 @@ function _isOnQuest (user, progress, group) {
|
||||
return group && progress && group.quest && group.quest.active && group.quest.members[user._id] === true;
|
||||
}
|
||||
|
||||
schema.statics.collectQuest = async function collectQuest (user, progress) {
|
||||
let group = await this.getGroup({user, groupId: 'party'});
|
||||
if (!_isOnQuest(user, progress, group)) return;
|
||||
let quest = shared.content.quests[group.quest.key];
|
||||
async function processCollectionQuest (options) {
|
||||
let {
|
||||
user,
|
||||
progress,
|
||||
quest,
|
||||
group,
|
||||
} = options;
|
||||
|
||||
_.each(progress.collect, (v, k) => {
|
||||
group.quest.progress.collect[k] += v;
|
||||
@@ -505,7 +508,7 @@ schema.statics.collectQuest = async function collectQuest (user, progress) {
|
||||
return m;
|
||||
}, []);
|
||||
|
||||
foundText = foundText ? foundText.join(', ') : 'nothing';
|
||||
foundText = foundText.length > 0 ? foundText.join(', ') : 'nothing';
|
||||
group.sendChat(`\`${user.profile.name} found ${foundText}.\``);
|
||||
group.markModified('quest.progress.collect');
|
||||
|
||||
@@ -518,14 +521,15 @@ schema.statics.collectQuest = async function collectQuest (user, progress) {
|
||||
group.sendChat('`All items found! Party has received their rewards.`');
|
||||
|
||||
return await group.save();
|
||||
};
|
||||
}
|
||||
|
||||
schema.statics.bossQuest = async function bossQuest (user, progress) {
|
||||
let group = await this.getGroup({user, groupId: 'party'});
|
||||
if (!_isOnQuest(user, progress, group)) return;
|
||||
|
||||
let quest = shared.content.quests[group.quest.key];
|
||||
if (!progress || !quest) return; // TODO why is this ever happening, progress should be defined at this point, log?
|
||||
async function processBossQuest (options) {
|
||||
let {
|
||||
user,
|
||||
progress,
|
||||
quest,
|
||||
group,
|
||||
} = options;
|
||||
|
||||
let down = progress.down * quest.boss.str; // multiply by boss strength
|
||||
|
||||
@@ -570,6 +574,28 @@ schema.statics.bossQuest = async function bossQuest (user, progress) {
|
||||
}
|
||||
|
||||
return await group.save();
|
||||
}
|
||||
|
||||
schema.statics.processBossQuest = processBossQuest;
|
||||
schema.statics.processCollectionQuest = processCollectionQuest;
|
||||
|
||||
schema.statics.processQuestProgress = async function processQuestProgress (user, progress) {
|
||||
let group = await this.getGroup({user, groupId: 'party'});
|
||||
|
||||
if (!_isOnQuest(user, progress, group)) return;
|
||||
|
||||
let quest = shared.content.quests[group.quest.key];
|
||||
|
||||
if (!quest) return; // TODO should this throw an error instead?
|
||||
|
||||
let questType = quest.boss ? 'Boss' : 'Collection';
|
||||
|
||||
await this[`process${questType}Quest`]({
|
||||
user,
|
||||
progress,
|
||||
quest,
|
||||
group,
|
||||
});
|
||||
};
|
||||
|
||||
// to set a boss: `db.groups.update({_id:TAVERN_ID},{$set:{quest:{key:'dilatory',active:true,progress:{hp:1000,rage:1500}}}})`
|
||||
|
||||
Reference in New Issue
Block a user