mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-16 06:07:21 +01:00
New cards — Congratulations, Get Well (#8655)
* Add card and achievement sprite for Congrats card
* Add data regarding Congrats card
* Add Get Well card
* Add Get Well images
* Add schema
* Remove `if (!target.flags) target.flags = {};` code from cards
* Remove white backgrounds for congrats sprites
* add inital tests for cards
* Fix card tests
* Fix invalid urls in tests
* Update POST-user_class_cast_spellId.test.js
* Update POST-user_class_cast_spellId.test.js
* Update POST-user_class_cast_spellId.test.js
* Update congrats card sprite
* Fix card logic
* Fix user schema
* Change achievement values for new cards to Number
* Resize congrats and getwell cards
This will make them be sized properly
* Separate Market from Drops
* Extract cards to new section
* fix(sprites): revert spritesheet changes
* Add flags if target does not have them
This commit is contained in:
@@ -221,6 +221,27 @@ describe('POST /user/class/cast/:spellId', () => {
|
||||
expect(syncedGroupTask.value).to.equal(0);
|
||||
});
|
||||
|
||||
it('increases both user\'s achievement values', async () => {
|
||||
let party = await createAndPopulateGroup({
|
||||
members: 1,
|
||||
});
|
||||
let leader = party.groupLeader;
|
||||
let recipient = party.members[0];
|
||||
await leader.update({'stats.gp': 10});
|
||||
await leader.post(`/user/class/cast/birthday?targetId=${recipient._id}`);
|
||||
await leader.sync();
|
||||
await recipient.sync();
|
||||
expect(leader.achievements.birthday).to.equal(1);
|
||||
expect(recipient.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
it('only increases user\'s achievement one if target == caster', async () => {
|
||||
await user.update({'stats.gp': 10});
|
||||
await user.post(`/user/class/cast/birthday?targetId=${user._id}`);
|
||||
await user.sync();
|
||||
expect(user.achievements.birthday).to.equal(1);
|
||||
});
|
||||
|
||||
// TODO find a way to have sinon working in integration tests
|
||||
// it doesn't work when tests are running separately from server
|
||||
it('passes correct target to spell when targetType === \'task\'');
|
||||
|
||||
@@ -174,7 +174,7 @@ describe('achievements', () => {
|
||||
});
|
||||
|
||||
it('card achievements exist with counts', () => {
|
||||
let cardTypes = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
||||
let cardTypes = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday', 'congrats', 'getwell'];
|
||||
cardTypes.forEach((card) => {
|
||||
let cardAchiev = seasonalAchievs[`${card}Cards`];
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 849 B |
Binary file not shown.
|
After Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 271 B |
Binary file not shown.
|
After Width: | Height: | Size: 232 B |
@@ -154,6 +154,7 @@
|
||||
"achievementBewilder": "Savior of Mistiflying",
|
||||
"achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
|
||||
"checkOutProgress": "Check out my progress in Habitica!",
|
||||
"cards": "Cards",
|
||||
"cardReceived": "Received a card!",
|
||||
"cardReceivedFrom": "<%= cardType %> from <%= userName %>",
|
||||
"greetingCard": "Greeting Card",
|
||||
@@ -180,6 +181,25 @@
|
||||
"birthday0": "Happy birthday to you!",
|
||||
"birthdayCardAchievementTitle": "Birthday Bonanza",
|
||||
"birthdayCardAchievementText": "Many happy returns! Sent or received <%= count %> birthday cards.",
|
||||
"congratsCard": "Congratulations Card",
|
||||
"congratsCardExplanation": "You both recieve the Congratulatory Companion achievement!",
|
||||
"congratsCardNotes": "Send a Congratulations card to a party member.",
|
||||
"congrats0": "Congratulations on your success!",
|
||||
"congrats1": "I'm so proud of you!",
|
||||
"congrats2": "Well done!",
|
||||
"congrats3": "A round of applause for you!",
|
||||
"congrats4": "Bask in your well-deserved success!",
|
||||
"congratsCardAchievementTitle": "Congratulatory Companion",
|
||||
"congratsCardAchievementText": "It's great to celebrate your friends' achievements! Sent or received <%= count %> congratulations cards.",
|
||||
"getwellCard": "Get Well Card",
|
||||
"getwellCardExplanation": "You both recieve the Caring Confidant achievement!",
|
||||
"getwellCardNotes": "Send a Get Well card to a party member.",
|
||||
"getwell0": "Hope you feel better soon!",
|
||||
"getwell1": "Take care! <3",
|
||||
"getwell2": "You're in my thoughts!",
|
||||
"getwell3": "Sorry you're not feeling your best!",
|
||||
"getwellCardAchievementTitle": "Caring Confidant",
|
||||
"getwellCardAchievementText": "Well-wishes are always appreciated. Sent or received <%= count %> get well cards.",
|
||||
"streakAchievement": "You earned a streak achievement!",
|
||||
"firstStreakAchievement": "21-Day Streak",
|
||||
"streakAchievementCount": "<%= streaks %> 21-Day Streaks",
|
||||
|
||||
@@ -181,7 +181,7 @@ let ultimateGearAchievs = ['healer', 'rogue', 'warrior', 'mage'].reduce((achievs
|
||||
}, {});
|
||||
Object.assign(achievementsData, ultimateGearAchievs);
|
||||
|
||||
let cardAchievs = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'].reduce((achievs, type) => {
|
||||
let cardAchievs = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday', 'congrats', 'getwell'].reduce((achievs, type) => {
|
||||
achievs[`${type}Cards`] = {
|
||||
icon: `achievement-${type}`,
|
||||
titleKey: `${type}CardAchievementTitle`,
|
||||
|
||||
@@ -144,6 +144,16 @@ api.cardTypes = {
|
||||
messageOptions: 1,
|
||||
yearRound: true,
|
||||
},
|
||||
congrats: {
|
||||
key: 'congrats',
|
||||
messageOptions: 5,
|
||||
yearRound: true,
|
||||
},
|
||||
getwell: {
|
||||
key: 'getwell',
|
||||
messageOptions: 4,
|
||||
yearRound: true,
|
||||
},
|
||||
};
|
||||
|
||||
api.special = api.spells.special;
|
||||
|
||||
@@ -511,6 +511,62 @@ spells.special = {
|
||||
if (!target.flags) target.flags = {};
|
||||
target.flags.cardReceived = true;
|
||||
|
||||
user.stats.gp -= 10;
|
||||
},
|
||||
},
|
||||
congrats: {
|
||||
text: t('congratsCard'),
|
||||
mana: 0,
|
||||
value: 10,
|
||||
immediateUse: true,
|
||||
silent: true,
|
||||
target: 'user',
|
||||
notes: t('congratsCardNotes'),
|
||||
cast (user, target) {
|
||||
if (user === target) {
|
||||
if (!user.achievements.congrats) user.achievements.congrats = 0;
|
||||
user.achievements.congrats++;
|
||||
} else {
|
||||
each([user, target], (u) => {
|
||||
if (!u.achievements.congrats) u.achievements.congrats = 0;
|
||||
u.achievements.congrats++;
|
||||
});
|
||||
}
|
||||
|
||||
if (!target.items.special.congratsReceived) target.items.special.congratsReceived = [];
|
||||
target.items.special.congratsReceived.push(user.profile.name);
|
||||
|
||||
if (!target.flags) target.flags = {};
|
||||
target.flags.cardReceived = true;
|
||||
|
||||
user.stats.gp -= 10;
|
||||
},
|
||||
},
|
||||
getwell: {
|
||||
text: t('getwellCard'),
|
||||
mana: 0,
|
||||
value: 10,
|
||||
immediateUse: true,
|
||||
silent: true,
|
||||
target: 'user',
|
||||
notes: t('getwellCardNotes'),
|
||||
cast (user, target) {
|
||||
if (user === target) {
|
||||
if (!user.achievements.getwell) user.achievements.getwell = 0;
|
||||
user.achievements.getwell++;
|
||||
} else {
|
||||
each([user, target], (u) => {
|
||||
if (!u.achievements.getwell) u.achievements.getwell = 0;
|
||||
u.achievements.getwell++;
|
||||
});
|
||||
}
|
||||
|
||||
if (!target.items.special.getwellReceived) target.items.special.getwellReceived = [];
|
||||
target.items.special.getwellReceived.push(user.profile.name);
|
||||
|
||||
if (!target.flags) target.flags = {};
|
||||
target.flags.cardReceived = true;
|
||||
|
||||
user.stats.gp -= 10;
|
||||
},
|
||||
},
|
||||
|
||||
@@ -240,7 +240,7 @@ function _getSeasonalAchievements (user, language) {
|
||||
|
||||
_addPlural(result, user, {path: 'costumeContests', language});
|
||||
|
||||
let cardAchievements = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday'];
|
||||
let cardAchievements = ['greeting', 'thankyou', 'nye', 'valentine', 'birthday', 'congrats', 'getwell'];
|
||||
cardAchievements.forEach(path => {
|
||||
_addSimpleWithCount(result, user, {path, key: `${path}Cards`, language});
|
||||
});
|
||||
|
||||
@@ -111,6 +111,8 @@ let schema = new Schema({
|
||||
birthday: Number,
|
||||
partyUp: Boolean,
|
||||
partyOn: Boolean,
|
||||
congrats: Number,
|
||||
getwell: Number,
|
||||
royallyLoyal: Boolean,
|
||||
joinedGuild: Boolean,
|
||||
},
|
||||
@@ -288,6 +290,10 @@ let schema = new Schema({
|
||||
thankyouReceived: Array,
|
||||
birthday: {type: Number, default: 0},
|
||||
birthdayReceived: Array,
|
||||
congrats: {type: Number, default: 0},
|
||||
congratsReceived: Array,
|
||||
getwell: {type: Number, default: 0},
|
||||
getwellReceived: Array,
|
||||
},
|
||||
|
||||
// -------------- Animals -------------------
|
||||
|
||||
@@ -79,93 +79,4 @@
|
||||
ng-click='openCardsModal(type.key, type.messageOptions)')
|
||||
.badge.badge-info.stack-count {{user.items.special[received].length}}
|
||||
|
||||
.col-md-6.border-left
|
||||
h2=env.t('market')
|
||||
.npc_alex_container
|
||||
.pull-left-sm.col-centered(class="#{env.worldDmg.market ? 'npc_alex_broken' : 'npc_alex'}")
|
||||
.popover.static-popover.fade.right.in.pull-left-sm
|
||||
.arrow.hidden-xs
|
||||
h3.popover-title
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/523661924')=env.t('alexander')
|
||||
.popover-content
|
||||
p=env.t('welcomeMarket')
|
||||
hr(ng-show='selectedEgg || selectedPotion || selectedFood')
|
||||
div(ng-show='selectedEgg || selectedPotion || selectedFood')
|
||||
.pull-left.customize-option(class='Pet_Egg_{{selectedEgg.key}}' ng-show='selectedEgg')
|
||||
p(ng-show='selectedEgg')
|
||||
!=env.t('displayEggForGold', {itemType: "{{selectedEgg.text()}}"})
|
||||
.pull-left.customize-option(class='Pet_HatchingPotion_{{selectedPotion.key}}' ng-show='selectedPotion')
|
||||
p(ng-show='selectedPotion')
|
||||
!=env.t('displayPotionForGold', {itemType: "{{selectedPotion.text()}}"})
|
||||
.pull-left.customize-option(class='Pet_Food_{{selectedFood.key}}' ng-show='selectedFood')
|
||||
p(ng-show='selectedFood')
|
||||
!=env.t('displayItemForGold', {itemType: "{{selectedFood.text()}}"})
|
||||
.clearfix
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {itemType: "{{selectedEgg.text()}}", gold: "{{selectedEgg.value}}"})
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {itemType: "{{selectedPotion.text()}}", gold: "{{selectedPotion.value}}"})
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text()}}", gold: "{{selectedFood.value}}"})
|
||||
|
||||
menu.inventory-list(type='list')
|
||||
li.customize-menu(ng-repeat='category in marketShopCategories')
|
||||
menu.pets-menu(label='{{category.text}}', ng-if='category.items.length > 0')
|
||||
p.muted(ng-bind-html='category.notes')
|
||||
|
||||
div(ng-repeat='item in category.items')
|
||||
button.customize-option(class='{{item.class}}',
|
||||
popover='{{item.notes}}', popover-append-to-body='true',
|
||||
popover-title!='{{item.text}}',
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
ng-click='purchase(item.purchaseType, item)')
|
||||
p {{item.value}}
|
||||
span.Pet_Currency_Gem1x.inline-gems(ng-if='item.currency === "gems"')
|
||||
span(class='shop_gold', ng-if='item.currency === "gold"')
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label=env.t('special'))
|
||||
div
|
||||
button.customize-option(class='inventory_special_fortify',
|
||||
popover=env.t('fortifyPop'),
|
||||
popover-title=env.t('fortifyName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("reroll")')
|
||||
p
|
||||
| 4
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-show='user.flags.rebirthEnabled')
|
||||
button.customize-option(class='rebirth_orb',
|
||||
popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("rebirth")')
|
||||
p(ng-show='user.stats.lvl < 100')
|
||||
| 6
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-show='petCount >= 90 || mountCount >= 90')
|
||||
button.customize-option(popover=env.t('petKeyPop'), popover-title=env.t('petKeyName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("pet-key", {size:"lg", controller:"InventoryCtrl"})', class='pet_key')
|
||||
p(ng-show='petCount < 90 || mountCount < 90 || !user.achievements.triadBingo')
|
||||
| 4
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-if='user.purchased.plan.customerId', ng-class='::{transparent:(Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought) < 1}')
|
||||
button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='User.purchase({params:{type:"gems",key:"gem"}})')
|
||||
span.Pet_Currency_Gem.inline-gems
|
||||
.badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}
|
||||
p
|
||||
| 20
|
||||
span.shop_gold
|
||||
div(ng-repeat='type in Content.cardTypes', ng-show='type.yearRound')
|
||||
button.customize-option(class='inventory_special_{{::type.key}}',
|
||||
popover='{{::Content.spells.special[type.key].notes()}}',
|
||||
popover-title='{{::Content.spells.special[type.key].text()}}',
|
||||
popover-trigger='mouseenter', popover-placement='right',
|
||||
popover-append-to-body='true',
|
||||
ng-click='castStart(Content.spells.special[type.key])')
|
||||
p
|
||||
| {{Content.spells.special[type.key].value}}
|
||||
span(class='shop_gold')
|
||||
include market.jade
|
||||
|
||||
93
website/views/options/inventory/market.jade
Normal file
93
website/views/options/inventory/market.jade
Normal file
@@ -0,0 +1,93 @@
|
||||
.col-md-6.border-left
|
||||
h2=env.t('market')
|
||||
.npc_alex_container
|
||||
.pull-left-sm.col-centered(class="#{env.worldDmg.market ? 'npc_alex_broken' : 'npc_alex'}")
|
||||
.popover.static-popover.fade.right.in.pull-left-sm
|
||||
.arrow.hidden-xs
|
||||
h3.popover-title
|
||||
a(target='_blank', href='http://www.kickstarter.com/profile/523661924')=env.t('alexander')
|
||||
.popover-content
|
||||
p=env.t('welcomeMarket')
|
||||
hr(ng-show='selectedEgg || selectedPotion || selectedFood')
|
||||
div(ng-show='selectedEgg || selectedPotion || selectedFood')
|
||||
.pull-left.customize-option(class='Pet_Egg_{{selectedEgg.key}}' ng-show='selectedEgg')
|
||||
p(ng-show='selectedEgg')
|
||||
!=env.t('displayEggForGold', {itemType: "{{selectedEgg.text()}}"})
|
||||
.pull-left.customize-option(class='Pet_HatchingPotion_{{selectedPotion.key}}' ng-show='selectedPotion')
|
||||
p(ng-show='selectedPotion')
|
||||
!=env.t('displayPotionForGold', {itemType: "{{selectedPotion.text()}}"})
|
||||
.pull-left.customize-option(class='Pet_Food_{{selectedFood.key}}' ng-show='selectedFood')
|
||||
p(ng-show='selectedFood')
|
||||
!=env.t('displayItemForGold', {itemType: "{{selectedFood.text()}}"})
|
||||
.clearfix
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedEgg', ng-click='sellInventory()')=env.t('sellForGold', {itemType: "{{selectedEgg.text()}}", gold: "{{selectedEgg.value}}"})
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedPotion', ng-click='sellInventory()')=env.t('sellForGold', {itemType: "{{selectedPotion.text()}}", gold: "{{selectedPotion.value}}"})
|
||||
button.btn.btn-primary.btn-block(ng-show='selectedFood', ng-click='sellInventory()')=env.t('sellForGold', {item: "{{selectedFood.text()}}", gold: "{{selectedFood.value}}"})
|
||||
|
||||
menu.inventory-list(type='list')
|
||||
li.customize-menu(ng-repeat='category in marketShopCategories')
|
||||
menu.pets-menu(label='{{category.text}}', ng-if='category.items.length > 0')
|
||||
p.muted(ng-bind-html='category.notes')
|
||||
|
||||
div(ng-repeat='item in category.items')
|
||||
button.customize-option(class='{{item.class}}',
|
||||
popover='{{item.notes}}', popover-append-to-body='true',
|
||||
popover-title!='{{item.text}}',
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
ng-click='purchase(item.purchaseType, item)')
|
||||
p {{item.value}}
|
||||
span.Pet_Currency_Gem1x.inline-gems(ng-if='item.currency === "gems"')
|
||||
span(class='shop_gold', ng-if='item.currency === "gold"')
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label=env.t('cards'))
|
||||
div(ng-repeat='type in Content.cardTypes', ng-show='type.yearRound')
|
||||
button.customize-option(class='inventory_special_{{::type.key}}',
|
||||
popover='{{::Content.spells.special[type.key].notes()}}',
|
||||
popover-title='{{::Content.spells.special[type.key].text()}}',
|
||||
popover-trigger='mouseenter', popover-placement='right',
|
||||
popover-append-to-body='true',
|
||||
ng-click='castStart(Content.spells.special[type.key])')
|
||||
p
|
||||
| {{Content.spells.special[type.key].value}}
|
||||
span(class='shop_gold')
|
||||
|
||||
li.customize-menu
|
||||
menu.pets-menu(label=env.t('special'))
|
||||
div
|
||||
button.customize-option(class='inventory_special_fortify',
|
||||
popover=env.t('fortifyPop'),
|
||||
popover-title=env.t('fortifyName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("reroll")')
|
||||
p
|
||||
| 4
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-show='user.flags.rebirthEnabled')
|
||||
button.customize-option(class='rebirth_orb',
|
||||
popover=env.t('rebirthPop'), popover-title=env.t('rebirthName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("rebirth")')
|
||||
p(ng-show='user.stats.lvl < 100')
|
||||
| 6
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-show='petCount >= 90 || mountCount >= 90')
|
||||
button.customize-option(popover=env.t('petKeyPop'), popover-title=env.t('petKeyName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='openModal("pet-key", {size:"lg", controller:"InventoryCtrl"})', class='pet_key')
|
||||
p(ng-show='petCount < 90 || mountCount < 90 || !user.achievements.triadBingo')
|
||||
| 4
|
||||
span.Pet_Currency_Gem1x.inline-gems
|
||||
div(ng-if='user.purchased.plan.customerId', ng-class='::{transparent:(Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought) < 1}')
|
||||
button.customize-option(popover=env.t('subGemPop'), popover-title=env.t('subGemName'),
|
||||
popover-trigger='mouseenter', popover-placement='top',
|
||||
popover-append-to-body='true',
|
||||
ng-click='User.purchase({params:{type:"gems",key:"gem"}})')
|
||||
span.Pet_Currency_Gem.inline-gems
|
||||
.badge.badge-success.stack-count {{Shared.planGemLimits.convCap + User.user.purchased.plan.consecutive.gemCapExtra - User.user.purchased.plan.gemsBought}}
|
||||
p
|
||||
| 20
|
||||
span.shop_gold
|
||||
Reference in New Issue
Block a user