mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 22:57:21 +01:00
Fixing Exponential Quest Reward Scrolls (#7800)
* adding quest owner specific rewards. closes #2715 * Updating model to prevent this from being a breaking change. * Removing duplicate translatable string and readding accidentally deleted portion * capitalizing according to pr. * fixing according to comments on pr * removing final mistakes * fixing whitespace * re-adding the onlyOwner field that got deleted when the index.js file was moved and fixed console errors. * moving cleaning of empty obejct for quest owner updates into quest owner updates method * Fixing so tests pass by updating variable name and removing unnecessary parameter definition. * adding a new test and refactoring client side code to use controller method.
This commit is contained in:
@@ -1259,14 +1259,34 @@ describe('Group Model', () => {
|
||||
expect(updatedParticipatingMember.items.hatchingPotions.Shade).to.eql(2);
|
||||
});
|
||||
|
||||
it('awards quests', async () => {
|
||||
it('awards quest scrolls to owner', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.quests.vice3).to.eql(1);
|
||||
});
|
||||
|
||||
it('awards non quest leader rewards to quest leader', async () => {
|
||||
let gearQuest = questScrolls.vice3;
|
||||
|
||||
await party.finishQuest(gearQuest);
|
||||
|
||||
let updatedLeader = await User.findById(questLeader._id);
|
||||
|
||||
expect(updatedLeader.items.gear.owned.weapon_special_2).to.eql(true);
|
||||
});
|
||||
|
||||
it('doesn\'t award quest owner rewards to all participants', async () => {
|
||||
let questAwardQuest = questScrolls.vice2;
|
||||
|
||||
await party.finishQuest(questAwardQuest);
|
||||
|
||||
let updatedParticipatingMember = await User.findById(participatingMember._id);
|
||||
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.eql(1);
|
||||
expect(updatedParticipatingMember.items.quests.vice3).to.not.exist;
|
||||
});
|
||||
|
||||
it('awards pets', async () => {
|
||||
|
||||
@@ -444,6 +444,10 @@ habitrpg.controller("InventoryCtrl",
|
||||
});
|
||||
};
|
||||
|
||||
$scope.getQuestOwnerRewards = function(quest) {
|
||||
return _.filter(quest.drop.items, 'onlyOwner');
|
||||
};
|
||||
|
||||
function findPet (fn) {
|
||||
var pets = Object.keys(user.items.pets);
|
||||
return pets.find(function (petKey) {
|
||||
|
||||
@@ -71,7 +71,8 @@ habitrpg.directive('questRewards', ['$rootScope', function($rootScope){
|
||||
restrict: 'AE',
|
||||
templateUrl: 'partials/options.social.party.quest-rewards.html',
|
||||
link: function(scope, element, attrs){
|
||||
scope.header = attrs.header || 'Rewards';
|
||||
scope.headerParticipant = attrs.headerParticipant || env.t('rewardsAllParticipants');
|
||||
scope.headerQuestOwner = attrs.headerQuestOwner || env.t('rewardsQuestOwner');
|
||||
scope.quest = $rootScope.Content.quests[attrs.key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,17 +83,26 @@ angular.module('habitrpg')
|
||||
}
|
||||
}
|
||||
text += '---\n\n';
|
||||
text += '**' + window.env.t('rewards') + ':**\n\n';
|
||||
if(quest.drop.items) {
|
||||
for (var item in quest.drop.items) {
|
||||
text += quest.drop.items[item].text() + '\n\n';
|
||||
}
|
||||
text += '**' + window.env.t('rewardsAllParticipants') + ':**\n\n';
|
||||
var participantRewards = _.reject(quest.drop.items, 'onlyOwner');
|
||||
if(participantRewards.length > 0) {
|
||||
_.each(participantRewards, function(item) {
|
||||
text += item.text() + '\n\n';
|
||||
});
|
||||
}
|
||||
if(quest.drop.exp)
|
||||
text += quest.drop.exp + ' ' + window.env.t('experience') + '\n\n';
|
||||
if(quest.drop.gp)
|
||||
text += quest.drop.gp + ' ' + window.env.t('gold') + '\n\n';
|
||||
|
||||
var ownerRewards = _.filter(quest.drop.items, 'onlyOwner');
|
||||
if(ownerRewards.length > 0) {
|
||||
text += '**' + window.env.t('rewardsQuestOwner') + ':**\n\n';
|
||||
_.each(ownerRewards, function(item){
|
||||
text += item.text() + '\n\n';
|
||||
});
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -140,7 +149,7 @@ angular.module('habitrpg')
|
||||
var quest = response.data.quest;
|
||||
if (!quest) quest = response.data.data;
|
||||
resolve(quest);
|
||||
});;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,12 @@
|
||||
"goldQuests": "Gold-Purchasable Quests",
|
||||
"questDetails": "Quest Details",
|
||||
"invitations": "Invitations",
|
||||
"completed": " Completed!",
|
||||
"completed": "Completed!",
|
||||
"rewardsAllParticipants": "Rewards for all Quest Participants",
|
||||
"rewardsQuestOwner": "Additional Rewards for Quest Owner",
|
||||
"questOwnerReceived": "The Quest Owner Has Also Received",
|
||||
"youWillReceive": "You Will Receive",
|
||||
"questOwnerWillReceive": "The Quest Owner Will Also Receive",
|
||||
"youReceived": "You've Received",
|
||||
"dropQuestCongrats": "Congratulations on earning this quest scroll! You can invite your party to begin the quest now, or come back to it any time in your Inventory > Quests.",
|
||||
"questSend": "Clicking \"Invite\" will send an invitation to your party members. When all members have accepted or denied, the quest begins. See status under Social > Party.",
|
||||
|
||||
@@ -867,7 +867,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: "vice2",
|
||||
text: t('questVice1DropVice2Quest')
|
||||
text: t('questVice1DropVice2Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 20,
|
||||
@@ -892,7 +893,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: 'vice3',
|
||||
text: t('questVice2DropVice3Quest')
|
||||
text: t('questVice2DropVice3Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 20,
|
||||
@@ -1116,7 +1118,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: "atom2",
|
||||
text: t('questAtom1Drop')
|
||||
text: t('questAtom1Drop'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 7,
|
||||
@@ -1140,7 +1143,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: "atom3",
|
||||
text: t('questAtom2Drop')
|
||||
text: t('questAtom2Drop'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 20,
|
||||
@@ -1293,7 +1297,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: "moonstone2",
|
||||
text: t('questMoonstone1DropMoonstone2Quest')
|
||||
text: t('questMoonstone1DropMoonstone2Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 50,
|
||||
@@ -1317,7 +1322,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: 'moonstone3',
|
||||
text: t('questMoonstone2DropMoonstone3Quest')
|
||||
text: t('questMoonstone2DropMoonstone3Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 500,
|
||||
@@ -1398,7 +1404,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: "goldenknight2",
|
||||
text: t('questGoldenknight1DropGoldenknight2Quest')
|
||||
text: t('questGoldenknight1DropGoldenknight2Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 15,
|
||||
@@ -1422,7 +1429,8 @@ api.quests = {
|
||||
{
|
||||
type: 'quests',
|
||||
key: 'goldenknight3',
|
||||
text: t('questGoldenknight2DropGoldenknight3Quest')
|
||||
text: t('questGoldenknight2DropGoldenknight3Quest'),
|
||||
onlyOwner: true
|
||||
}
|
||||
],
|
||||
gp: 75,
|
||||
|
||||
@@ -579,6 +579,39 @@ schema.statics.cleanGroupQuest = function cleanGroupQuest () {
|
||||
};
|
||||
};
|
||||
|
||||
function _getUserUpdateForQuestReward (itemToAward, allAwardedItems) {
|
||||
let updates = {
|
||||
$set: {},
|
||||
$inc: {},
|
||||
};
|
||||
let dropK = itemToAward.key;
|
||||
|
||||
switch (itemToAward.type) {
|
||||
case 'gear': {
|
||||
// TODO This means they can lose their new gear on death, is that what we want?
|
||||
updates.$set[`items.gear.owned.${dropK}`] = true;
|
||||
break;
|
||||
}
|
||||
case 'eggs':
|
||||
case 'food':
|
||||
case 'hatchingPotions':
|
||||
case 'quests': {
|
||||
updates.$inc[`items.${itemToAward.type}.${dropK}`] = _.where(allAwardedItems, {type: itemToAward.type, key: itemToAward.key}).length;
|
||||
break;
|
||||
}
|
||||
case 'pets': {
|
||||
updates.$set[`items.pets.${dropK}`] = 5;
|
||||
break;
|
||||
}
|
||||
case 'mounts': {
|
||||
updates.$set[`items.mounts.${dropK}`] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
updates = _.omit(updates, _.isEmpty);
|
||||
return updates;
|
||||
}
|
||||
|
||||
async function _updateUserWithRetries (userId, updates, numTry = 1) {
|
||||
return await User.update({_id: userId}, updates).exec()
|
||||
.then((raw) => {
|
||||
@@ -611,33 +644,18 @@ schema.methods.finishQuest = async function finishQuest (quest) {
|
||||
updates.$set['party.quest'] = _cleanQuestProgress({completed: questK}); // clear quest progress
|
||||
}
|
||||
|
||||
_.each(quest.drop.items, (item) => {
|
||||
let dropK = item.key;
|
||||
|
||||
switch (item.type) {
|
||||
case 'gear': {
|
||||
// TODO This means they can lose their new gear on death, is that what we want?
|
||||
updates.$set[`items.gear.owned.${dropK}`] = true;
|
||||
break;
|
||||
}
|
||||
case 'eggs':
|
||||
case 'food':
|
||||
case 'hatchingPotions':
|
||||
case 'quests': {
|
||||
updates.$inc[`items.${item.type}.${dropK}`] = _.where(quest.drop.items, {type: item.type, key: item.key}).length;
|
||||
break;
|
||||
}
|
||||
case 'pets': {
|
||||
updates.$set[`items.pets.${dropK}`] = 5;
|
||||
break;
|
||||
}
|
||||
case 'mounts': {
|
||||
updates.$set[`items.mounts.${dropK}`] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
_.each(_.reject(quest.drop.items, 'onlyOwner'), (item) => {
|
||||
_.merge(updates, _getUserUpdateForQuestReward(item, quest.drop.items));
|
||||
});
|
||||
|
||||
let questOwnerUpdates = {};
|
||||
let questLeader = this.quest.leader;
|
||||
|
||||
_.each(_.filter(quest.drop.items, 'onlyOwner'), (item) => {
|
||||
_.merge(questOwnerUpdates, _getUserUpdateForQuestReward(item, quest.drop.items));
|
||||
});
|
||||
_.merge(questOwnerUpdates, updates);
|
||||
|
||||
let participants = this._id === TAVERN_ID ? {} : this.getParticipatingQuestMembers();
|
||||
this.quest = {};
|
||||
this.markModified('quest');
|
||||
@@ -647,7 +665,11 @@ schema.methods.finishQuest = async function finishQuest (quest) {
|
||||
}
|
||||
|
||||
let promises = participants.map(userId => {
|
||||
return _updateUserWithRetries(userId, updates);
|
||||
if (userId === questLeader) {
|
||||
return _updateUserWithRetries(userId, questOwnerUpdates);
|
||||
} else {
|
||||
return _updateUserWithRetries(userId, updates);
|
||||
}
|
||||
});
|
||||
|
||||
return Bluebird.all(promises);
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
script(id='partials/options.social.party.quest-rewards.html', type='text/ng-template')
|
||||
hr
|
||||
h5 {{::header}}
|
||||
h5 {{::headerParticipant}}
|
||||
table.table.table-striped
|
||||
tr(ng-repeat='drop in quest.drop.items')
|
||||
tr(ng-repeat='drop in _.reject(quest.drop.items, \'onlyOwner\')')
|
||||
td {{::drop.text()}}
|
||||
tr(ng-if='::quest.drop.exp > 0')
|
||||
td {{::quest.drop.exp}}
|
||||
@@ -12,3 +12,8 @@ script(id='partials/options.social.party.quest-rewards.html', type='text/ng-temp
|
||||
=env.t('gold')
|
||||
tr(ng-if='::quest.drop.unlock()')
|
||||
td {{::quest.drop.unlock()}}
|
||||
div(ng-if='getQuestOwnerRewards(quest).length > 0')
|
||||
h5 {{::headerQuestOwner}}
|
||||
table.table.table-striped
|
||||
tr(ng-repeat='drop in getQuestOwnerRewards(quest)')
|
||||
td {{::drop.text()}}
|
||||
|
||||
@@ -20,7 +20,7 @@ mixin questInfo
|
||||
p(ng-repeat='(k,v) in ::selectedQuest.collect')
|
||||
| {{:: env.t('collectionItems', { number: selectedQuest.collect[k].count, items: selectedQuest.collect[k].text() })}}
|
||||
div(ng-bind-html='::selectedQuest.notes()')
|
||||
quest-rewards(key='{{::selectedQuest.key}}', header=env.t('rewards'))
|
||||
quest-rewards(key='{{::selectedQuest.key}}', header-participant=env.t('rewardsAllParticipants'), header-quest-owner=env.t('rewardsQuestOwner'))
|
||||
|
||||
script(type='text/ng-template', id='modals/questCompleted.html')
|
||||
.modal-header
|
||||
@@ -29,7 +29,7 @@ script(type='text/ng-template', id='modals/questCompleted.html')
|
||||
.modal-body
|
||||
.col-centered(ng-class='::Content.quests[user.party.quest.completed].completion() ? "pull-right-sm" : ""', class='quest_{{user.party.quest.completed}}')
|
||||
p(ng-bind-html='::Content.quests[user.party.quest.completed].completion()')
|
||||
quest-rewards(key='{{user.party.quest.completed}}', header=env.t('youReceived'))
|
||||
quest-rewards(key='{{user.party.quest.completed}}', header-participant=env.t('youReceived'), header-quest-owner=env.t('questOwnerReceived'))
|
||||
.modal-footer
|
||||
button.btn.btn-primary(ng-click='set({"party.quest.completed":""}); $close()')=env.t('ok')
|
||||
|
||||
@@ -84,7 +84,7 @@ script(type='text/ng-template', id='modals/questInvitation.html')
|
||||
strong=env.t('collect') + ': '
|
||||
| {{::Content.quests[user.party.quest.key].collect[k].count}} {{::Content.quests[user.party.quest.key].collect[k].text()}}
|
||||
div(ng-bind-html='::Content.quests[user.party.quest.key].notes()')
|
||||
quest-rewards(key='{{::user.party.quest.key}}', header=env.t('rewards'))
|
||||
quest-rewards(key='{{::user.party.quest.key}}', header-participant=env.t('rewardsAllParticipants'), header-quest-owner=env.t('rewardsQuestOwner'))
|
||||
.modal-footer
|
||||
button.btn.btn-default(ng-click='questHold = true; $close()')=env.t('askLater')
|
||||
button.btn.btn-default(ng-click='questReject(); $close()')=env.t('reject')
|
||||
|
||||
Reference in New Issue
Block a user