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:
Travis
2016-12-28 01:38:52 -06:00
committed by Keith Holliday
parent 7d99873960
commit bf5ad2db1f
9 changed files with 123 additions and 49 deletions

View File

@@ -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 () => {

View File

@@ -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) {

View File

@@ -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];
}
}

View File

@@ -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);
});;
});
});
}

View File

@@ -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.",

View File

@@ -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,

View File

@@ -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);

View File

@@ -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()}}

View File

@@ -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')