diff --git a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js index 3793e789dd..9453757530 100644 --- a/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js +++ b/test/api/v3/integration/user/POST-user_class_cast_spellId.test.js @@ -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\''); diff --git a/test/common/libs/achievements.test.js b/test/common/libs/achievements.test.js index 824d5280b4..390f3bf7bf 100644 --- a/test/common/libs/achievements.test.js +++ b/test/common/libs/achievements.test.js @@ -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`]; diff --git a/website/assets/sprites/spritesmith/achievements/achievement-congrats2x.png b/website/assets/sprites/spritesmith/achievements/achievement-congrats2x.png new file mode 100644 index 0000000000..6e58e3cedf Binary files /dev/null and b/website/assets/sprites/spritesmith/achievements/achievement-congrats2x.png differ diff --git a/website/assets/sprites/spritesmith/achievements/achievement-getwell2x.png b/website/assets/sprites/spritesmith/achievements/achievement-getwell2x.png new file mode 100644 index 0000000000..45f1790b76 Binary files /dev/null and b/website/assets/sprites/spritesmith/achievements/achievement-getwell2x.png differ diff --git a/website/assets/sprites/spritesmith/misc/inventory_special_congrats.png b/website/assets/sprites/spritesmith/misc/inventory_special_congrats.png new file mode 100644 index 0000000000..2bf35d30b0 Binary files /dev/null and b/website/assets/sprites/spritesmith/misc/inventory_special_congrats.png differ diff --git a/website/assets/sprites/spritesmith/misc/inventory_special_getwell.png b/website/assets/sprites/spritesmith/misc/inventory_special_getwell.png new file mode 100644 index 0000000000..4314a218aa Binary files /dev/null and b/website/assets/sprites/spritesmith/misc/inventory_special_getwell.png differ diff --git a/website/common/locales/en/generic.json b/website/common/locales/en/generic.json index dc57ecb55e..6e10623c93 100644 --- a/website/common/locales/en/generic.json +++ b/website/common/locales/en/generic.json @@ -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", diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index 067a4a0ea9..56ea6e1608 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -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`, diff --git a/website/common/script/content/index.js b/website/common/script/content/index.js index 7665ff2375..c8882cd878 100644 --- a/website/common/script/content/index.js +++ b/website/common/script/content/index.js @@ -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; diff --git a/website/common/script/content/spells.js b/website/common/script/content/spells.js index 9d73f49a3f..b83c1ba222 100644 --- a/website/common/script/content/spells.js +++ b/website/common/script/content/spells.js @@ -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; }, }, diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index 1d49449a3a..b587dc4411 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -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}); }); diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index 3ac8207eb7..444ec8b9fa 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -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 ------------------- diff --git a/website/views/options/inventory/drops.jade b/website/views/options/inventory/drops.jade index a0716bf0e2..655bc3ef44 100644 --- a/website/views/options/inventory/drops.jade +++ b/website/views/options/inventory/drops.jade @@ -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 diff --git a/website/views/options/inventory/market.jade b/website/views/options/inventory/market.jade new file mode 100644 index 0000000000..32cde11fb3 --- /dev/null +++ b/website/views/options/inventory/market.jade @@ -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 \ No newline at end of file