From 7df10d51b00b3c5e3c4a06c03641310db9b806bc Mon Sep 17 00:00:00 2001 From: Sabe Jones Date: Fri, 21 Apr 2017 15:38:24 -0500 Subject: [PATCH 1/9] PRs 2017/04/07 - 2017/04/14 (#8691) * Issue 8432: Pre-test for initial fix * Add failing test, seeking help/advice * Fixes achievements injection. Now trying with Guide * Fixes tests * Remoed logging * Mock ENV function * Removes test focus * Remove Beat Master unlock for Rebirth * Special message when complete all check-in prizes * Updated comment * fix(test): unpend party cap test * remove wrong subscriptions from gift modal * edit the new issue template to emphasise that the Report a Bug guild should be used first (#8659) * fix(translation): resolve merge conflict * fix(news): merge conflict * Added email invite limit (#8664) * Added email invite limit * change error message for sending too many invitations to instruct them to email us * fix test error message to use variable in locales string * add comment to warn about keeping INVITES_LIMIT low If INVITES_LIMIT is allowed to be greater than MAX_EMAIL_INVITES_BY_USER then the inviter can send more than MAX_EMAIL_INVITES_BY_USER invitations at once. * Fix for automatic allocation not persisting #8641 (#8661) * Fixed text of check-in prize when it is a set of backgrounds (#8599) * Fixed text of check-in prize when it is a set of backgrounds * Use existing i18n string for BGs name * Added user.preferences.language as second parameter * fix Shiny Seeds info about achievement (not given to caster) (#8679) Ref: https://habitica.slack.com/archives/C02RK7DKF/p1492032261365388 * Release mergeback v3.86.0 (#8685) * 3.85.0 * New User Tasks for Mobile (#8682) * feat(mobile): different default tasks * fix(linting): missing space * fix(user): correct client type logic * test(integration): tasks by platform * fix(test): remove only * test(user): deeper checks on tasks * refactor(test): whitespace for readability * feat(subs): Jackalope Pets (#8684) * chore(sprites): compile * chore(i18n): update locales * 3.86.0 --- .github/ISSUE_TEMPLATE.md | 12 +-- .../spec/controllers/userCtrlSpec.js | 69 ++++++++++++ website/client-old/js/controllers/userCtrl.js | 4 +- .../js/services/notificationServices.js | 1 + .../common/locales/en/loginIncentives.json | 3 +- website/common/locales/zh/gear.json | 2 +- .../common/script/content/loginIncentives.js | 5 +- website/common/script/fns/updateStats.js | 2 +- website/server/libs/cron.js | 7 +- website/views/options/profile/stats.jade | 102 +++++++++--------- .../login-incentives-reward-unlocked.jade | 2 + website/views/shared/modals/members.jade | 2 +- 12 files changed, 146 insertions(+), 65 deletions(-) create mode 100644 test/client-old/spec/controllers/userCtrlSpec.js diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 4322c53807..8c75e4f542 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,21 +1,19 @@ -[//]: # (Before logging this issue, look through common problems at https://github.com/HabitRPG/habitrpg/issues If you find your issue there, read at least the first post to see if there is a workaround for you) +[//]: # (Before logging this issue, please post to the Report a Bug guild from the Habitica website's Help menu. Most bugs can be handled quickly there. If a GitHub issue is needed, you will be advised of that by a moderator or staff member -- a player with a dark blue or purple name. It is recommended that you don't create a new issue unless advised to.) -[//]: # (Github is primarily used for reporting bugs. If you have a feature request, use "Help > Request a Feature" so that the feature request can be vetted by the larger Habitica community) +[//]: # (Bugs in the mobile apps can also be reported there.) -[//]: # (To report a bug in one of the mobile apps, please report it in the correct repository. Android: https://github.com/HabitRPG/habitrpg-android, iOS: https://github.com/HabitRPG/habitrpg-ios) +[//]: # (If you have a feature request, use "Help > Request a Feature", not GitHub or the Report a Bug guild.) [//]: # (For more guidelines see https://github.com/HabitRPG/habitrpg/issues/2760) [//]: # (Fill out relevant information - UUID is found in Settings -> API) -General Info +### General Info * UUID: * Browser: * OS: ### Description -[//]: # (Describe bug in detail here. Include pictures if helpful.) - - +[//]: # (Describe bug in detail here. Include screenshots if helpful.) #### Console Errors [//]: # (Include any JavaScript console errors here.) diff --git a/test/client-old/spec/controllers/userCtrlSpec.js b/test/client-old/spec/controllers/userCtrlSpec.js new file mode 100644 index 0000000000..2ac2dbe8e1 --- /dev/null +++ b/test/client-old/spec/controllers/userCtrlSpec.js @@ -0,0 +1,69 @@ +'use strict'; + +describe('User Controller', function() { + var $rootScope, $window, User, shared, scope, ctrl, content; + + beforeEach(function() { + module(function ($provide) { + var user = specHelper.newUser(); + User = {user: user} + $provide.value('Guide', sandbox.stub()); + $provide.value('User', User); + $provide.value('Achievement', sandbox.stub()); + $provide.value('Social', sandbox.stub()); + $provide.value('Shared', { + achievements: { + getAchievementsForProfile: sandbox.stub() + }, + shops: { + getBackgroundShopSets: sandbox.stub() + } + }); + $provide.value('Content', { + loginIncentives: sandbox.stub() + }) + }); + + inject(function($rootScope, $controller, User, Content) { + scope = $rootScope.$new(); + content = Content; + $controller('RootCtrl', { $scope: scope, User: User}); + ctrl = $controller('UserCtrl', { $scope: scope, User: User, $window: $window}); + }); + }); + + describe('getProgressDisplay', function() { + + beforeEach(() => { + sandbox.stub(window.env, 't'); + window.env.t.onFirstCall().returns('Progress until next'); + }); + + it('should return initial progress', function() { + scope.profile.loginIncentives = 0; + content.loginIncentives = [{ + nextRewardAt: 1, + reward: true + }]; + var actual = scope.getProgressDisplay(); + expect(actual.trim()).to.eql('Progress until next 0/1'); + }); + + it('should return progress between next reward and current reward', function() { + scope.profile.loginIncentives = 1; + content.loginIncentives = [{ + nextRewardAt: 1, + reward: true + }, { + prevRewardAt: 0, + nextRewardAt: 2, + reward: true + }, { + prevRewardAt: 1, + nextRewardAt: 3 + }]; + var actual = scope.getProgressDisplay(); + expect(actual.trim()).to.eql('Progress until next 0/1'); + }); + }); +}); diff --git a/website/client-old/js/controllers/userCtrl.js b/website/client-old/js/controllers/userCtrl.js index f233f366cd..a109db178b 100644 --- a/website/client-old/js/controllers/userCtrl.js +++ b/website/client-old/js/controllers/userCtrl.js @@ -1,6 +1,6 @@ "use strict"; -habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', 'Content', 'Stats', 'Social', 'Costume', +habitrpg.controller('UserCtrl', ['$rootScope', '$scope', '$location', 'User', '$http', '$state', 'Guide', 'Shared', 'Content', 'Stats', 'Social', 'Costume', function($rootScope, $scope, $location, User, $http, $state, Guide, Shared, Content, Stats, Social, Costume) { $scope.profile = User.user; @@ -102,6 +102,8 @@ habitrpg.controller("UserCtrl", ['$rootScope', '$scope', '$location', 'User', '$ if (!currentLoginDay) return env.t('moreIncentivesComingSoon'); var nextRewardAt = currentLoginDay.nextRewardAt; if (!nextRewardAt) return env.t('moreIncentivesComingSoon'); + // if we are on a reward day, let's show progress relative to this + if (currentLoginDay.reward) currentLoginDay.prevRewardKey = $scope.profile.loginIncentives; if (!currentLoginDay.prevRewardKey) currentLoginDay.prevRewardKey = 0; return env.t('checkinProgressTitle') + ' ' + ($scope.profile.loginIncentives - currentLoginDay.prevRewardKey) + '/' + (nextRewardAt - currentLoginDay.prevRewardKey); }; diff --git a/website/client-old/js/services/notificationServices.js b/website/client-old/js/services/notificationServices.js index edb08cd4cc..7e380e36fa 100644 --- a/website/client-old/js/services/notificationServices.js +++ b/website/client-old/js/services/notificationServices.js @@ -147,6 +147,7 @@ angular.module("habitrpg").factory("Notification", modalScope.data = rewardData; var nextRewardKey = Shared.content.loginIncentives[user.loginIncentives].nextRewardAt; modalScope.nextReward = Shared.content.loginIncentives[nextRewardKey]; + modalScope.MAX_INCENTIVES = Shared.constants.MAX_INCENTIVES; modalScope.user = user; // modalScope.loadWidgets = Social.loadWidgets; modalScope.loadWidgets = loadWidgets; diff --git a/website/common/locales/en/loginIncentives.json b/website/common/locales/en/loginIncentives.json index a434f4cc77..06b1774f6f 100644 --- a/website/common/locales/en/loginIncentives.json +++ b/website/common/locales/en/loginIncentives.json @@ -12,5 +12,6 @@ "totalCheckinsTitle": "Total Check-Ins", "checkinProgressTitle": "Progress until next", "incentiveBackgroundsUnlockedWithCheckins": "Locked Plain Backgrounds will unlock with Daily Check-Ins.", - "moreIncentivesComingSoon": "New check-in incentives will be added soon!" + "moreIncentivesComingSoon": "New check-in incentives will be added soon!", + "checkinReceivedAllRewardsMessage" : "You have received all the Check-In prizes currently available! More will be added in the future." } diff --git a/website/common/locales/zh/gear.json b/website/common/locales/zh/gear.json index f6dce54dc9..75e309adc1 100644 --- a/website/common/locales/zh/gear.json +++ b/website/common/locales/zh/gear.json @@ -1189,4 +1189,4 @@ "eyewearMystery301703Notes": "完美适配美妙的化妆舞会,或者静悄悄地穿过那些特别讲究穿着的人群。没有属性加成。3017年三月捐赠者礼品。", "eyewearArmoirePlagueDoctorMaskText": "瘟疫医生面具", "eyewearArmoirePlagueDoctorMaskNotes": "防治延迟瘟疫的医生袋的地道面具。没有属性加成。魔法衣橱: 瘟疫医生系列 (3件的第2件)。" -} \ No newline at end of file +} diff --git a/website/common/script/content/loginIncentives.js b/website/common/script/content/loginIncentives.js index 10bd714174..39783702eb 100644 --- a/website/common/script/content/loginIncentives.js +++ b/website/common/script/content/loginIncentives.js @@ -15,6 +15,7 @@ module.exports = function getLoginIncentives (api) { 2: { rewardKey: ['background_purple'], reward: [api.backgrounds.incentiveBackgrounds], + rewardName: 'incentiveBackgrounds', // i18n string assignReward: function assignReward (user) { user.purchased.background.blue = true; user.purchased.background.green = true; @@ -225,8 +226,10 @@ module.exports = function getLoginIncentives (api) { }, }, }; + // When the final check-in prize is added here, change checkinReceivedAllRewardsMessage in website/common/locales/en/loginIncentives.json + // to say "You have received the final Check-In prize!". Confirm the message with Lemoness first. - // Add refence link to next reward and add filler days so we have a map to refernce the next reward from any day + // Add reference link to next reward and add filler days so we have a map to reference the next reward from any day // We could also, use a list, but then we would be cloning each of the rewards. // Create a new array if we want the loginIncentives to be immutable in the future let nextRewardKey; diff --git a/website/common/script/fns/updateStats.js b/website/common/script/fns/updateStats.js index 3a8bc5b131..9fa1ff1231 100644 --- a/website/common/script/fns/updateStats.js +++ b/website/common/script/fns/updateStats.js @@ -94,7 +94,7 @@ module.exports = function updateStats (user, stats, req = {}, analytics) { }; } }); - if (!user.flags.rebirthEnabled && (user.stats.lvl >= 50 || user.achievements.beastMaster)) { + if (!user.flags.rebirthEnabled && user.stats.lvl >= 50) { user.addNotification('REBIRTH_ENABLED'); user.flags.rebirthEnabled = true; } diff --git a/website/server/libs/cron.js b/website/server/libs/cron.js index 36f3d3f2e1..ec7f5915d7 100644 --- a/website/server/libs/cron.js +++ b/website/server/libs/cron.js @@ -157,7 +157,7 @@ function awardLoginIncentives (user) { notificationData.rewardText = i18n.t('potion', {potionType: notificationData.rewardText}, user.preferences.language); } } else if (loginIncentive.rewardKey[0] === 'background_blue') { - notificationData.rewardText = i18n.t('incentiveBackgrounds'); + notificationData.rewardText = i18n.t('incentiveBackgrounds', user.preferences.language); } if (loginIncentive.reward.length > 0 && count < loginIncentive.reward.length - 1) notificationData.rewardText += ', '; @@ -165,6 +165,11 @@ function awardLoginIncentives (user) { count += 1; } + // Overwrite notificationData.rewardText if rewardName was explicitly declared + if (loginIncentive.rewardName) { + notificationData.rewardText = i18n.t(loginIncentive.rewardName, user.preferences.language); + } + notificationData.rewardKey = loginIncentive.rewardKey; notificationData.message = i18n.t('unlockedCheckInReward', user.preferences.language); } diff --git a/website/views/options/profile/stats.jade b/website/views/options/profile/stats.jade index 144f617caa..b1e06a9a2d 100644 --- a/website/views/options/profile/stats.jade +++ b/website/views/options/profile/stats.jade @@ -1,51 +1,51 @@ -script(id='partials/options.profile.stats.html', type='text/ng-template') - .container-fluid - div(class='row') - .border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') - include ../../shared/profiles/stats_col1 - div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') - button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') - hr(ng-if='user.preferences.disableClasses') - include ../../shared/profiles/stats_col2 - .col-md-4.border-left.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses') - h3=env.t('characterBuild') - h4 - =env.t('class') + ': ' - span {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[user.stats.class] }}  - a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass') - small.cost 3 - table.table.table-striped - tr - td - p(ng-if='::user.stats.lvl >= 100')!=env.t('noMoreAllocate') - p(ng-if='user.stats.points || user.stats.lvl < 100') - strong.inline - |{{user.stats.points}}  - strong.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))=env.t('unallocated') - td - tr - td(colspan=2) - fieldset.auto-allocate - .checkbox - label - input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation?true: false})', ng-click='set({"preferences.allocationMode":"taskbased"})') - span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))=env.t('autoAllocation') - form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em') - .radio - label - input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})') - span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))=env.t('evenAllocation') - .radio - label - input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})') - span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))=env.t('classAllocation') - .radio - label - input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})') - span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation') - div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)') - a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) - span.glyphicon.glyphicon-download - |  - =env.t('distributePoints') - +statAllocation() +script(id='partials/options.profile.stats.html', type='text/ng-template') + .container-fluid + div(class='row') + .border-right(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') + include ../../shared/profiles/stats_col1 + div(ng-class='user.flags.classSelected && !user.preferences.disableClasses ? "col-md-4" : "col-md-6"') + button.btn.btn-default(ng-if='user.preferences.disableClasses', ng-click='User.changeClass({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('enableClassPop'))= env.t('enableClass') + hr(ng-if='user.preferences.disableClasses') + include ../../shared/profiles/stats_col2 + .col-md-4.border-left.allocate-stats(ng-if='user.flags.classSelected && !user.preferences.disableClasses') + h3=env.t('characterBuild') + h4 + =env.t('class') + ': ' + span {{ {warrior:env.t("warrior"), wizard:env.t("mage"), rogue:env.t("rogue"), healer:env.t("healer")}[user.stats.class] }}  + a.btn.btn-danger.btn-xs(ng-click='changeClass(null)')=env.t('changeClass') + small.cost 3 + table.table.table-striped + tr + td + p(ng-if='::user.stats.lvl >= 100')!=env.t('noMoreAllocate') + p(ng-if='user.stats.points || user.stats.lvl < 100') + strong.inline + |{{user.stats.points}}  + strong.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('levelPopover'))=env.t('unallocated') + td + tr + td(colspan=2) + fieldset.auto-allocate + .checkbox + label + input(type='checkbox', ng-model='user.preferences.automaticAllocation', ng-change='set({"preferences.automaticAllocation": user.preferences.automaticAllocation, "preferences.allocationMode":"taskbased"})') + span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('autoAllocationPop'))=env.t('autoAllocation') + form(ng-show='user.preferences.automaticAllocation',style='margin-left:1em') + .radio + label + input(type='radio', name='allocationMode', value='flat', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "flat"})') + span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('evenAllocationPop'))=env.t('evenAllocation') + .radio + label + input(type='radio', name='allocationMode', value='classbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "classbased"})') + span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('classAllocationPop'))=env.t('classAllocation') + .radio + label + input(type='radio', name='allocationMode', value='taskbased', ng-model='user.preferences.allocationMode', ng-change='set({"preferences.allocationMode": "taskbased"})') + span.hint(popover-trigger='mouseenter', popover-placement='right', popover=env.t('taskAllocationPop'))=env.t('taskAllocation') + div(ng-show='user.preferences.automaticAllocation && !(user.preferences.allocationMode === "taskbased") && (user.stats.points > 0)') + a.btn.btn-primary.btn-xs(ng-click='User.allocateNow({})', popover-trigger='mouseenter', popover-placement='right', popover=env.t('distributePointsPop')) + span.glyphicon.glyphicon-download + |  + =env.t('distributePoints') + +statAllocation() diff --git a/website/views/shared/modals/login-incentives-reward-unlocked.jade b/website/views/shared/modals/login-incentives-reward-unlocked.jade index 91986aa96d..a1a7ac1462 100644 --- a/website/views/shared/modals/login-incentives-reward-unlocked.jade +++ b/website/views/shared/modals/login-incentives-reward-unlocked.jade @@ -18,6 +18,8 @@ script(id='modals/login-incentives-reward-unlocked.html', type='text/ng-template p {{env.t('earnedRewardForDevotion', {reward: data.rewardText})}} .col-md-12.text-center(ng-show="data.nextRewardAt") h4 {{env.t('nextRewardUnlocksIn', {numberOfCheckinsLeft: data.nextRewardAt - user.loginIncentives})}} + .col-md-10.col-md-offset-1.text-center + h4(ng-if="user.loginIncentives === MAX_INCENTIVES")=env.t('checkinReceivedAllRewardsMessage') .modal-footer .row a.btn.btn-primary.col-md-6.col-md-offset-3(ng-click='$close()')=env.t('awesome') diff --git a/website/views/shared/modals/members.jade b/website/views/shared/modals/members.jade index 1e761661af..2ce0915626 100644 --- a/website/views/shared/modals/members.jade +++ b/website/views/shared/modals/members.jade @@ -87,7 +87,7 @@ script(type='text/ng-template', id='modals/send-gift.html') .panel-heading=env.t('subscription') .panel-body .form-group - .radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"', ng-if="block.type !== 'group'") + .radio(ng-repeat='block in Content.subscriptionBlocks | toArray | omit:"discount==true" | orderBy:"months"', ng-if="block.target !== 'group' && block.canSubscribe === true") label input(type="radio", name="subRadio", ng-value="block.key", ng-model='gift.subscription.key') =env.t('sendGiftSubscription', {price: '{{::block.price}}', months: '{{::block.months}}'}) From f3712c06418e895cc23f8cba8ac496882f91defe Mon Sep 17 00:00:00 2001 From: SabreCat Date: Fri, 21 Apr 2017 20:40:03 +0000 Subject: [PATCH 2/9] 3.87.1 --- npm-shrinkwrap.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 215897498c..86fb8d4dd0 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -1,6 +1,6 @@ { "name": "habitica", - "version": "3.87.0", + "version": "3.87.1", "dependencies": { "@gulp-sourcemaps/map-sources": { "version": "1.0.0", diff --git a/package.json b/package.json index 9ab7feb770..2942666f3d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "habitica", "description": "A habit tracker app which treats your goals like a Role Playing Game.", - "version": "3.87.0", + "version": "3.87.1", "main": "./website/server/index.js", "dependencies": { "@slack/client": "^3.8.1", From 984e7f80052d74d7dc2fb6f9083c803a58aafd43 Mon Sep 17 00:00:00 2001 From: SabreCat Date: Tue, 25 Apr 2017 22:11:20 +0000 Subject: [PATCH 3/9] feat(content): Subscriber Items 2017-04 --- migrations/mystery_items.js | 2 +- .../mystery_201704/back_mystery_201704.png | Bin 0 -> 926 bytes .../broad_armor_mystery_201704.png | Bin 0 -> 557 bytes .../mystery_201704/shop_armor_mystery_201704.png | Bin 0 -> 3174 bytes .../mystery_201704/shop_back_mystery_201704.png | Bin 0 -> 3455 bytes .../mystery_201704/slim_armor_mystery_201704.png | Bin 0 -> 538 bytes .../promo/promo_mystery_201704.png | Bin 0 -> 1396 bytes website/common/locales/en/gear.json | 8 ++++++-- website/common/locales/en/subscriber.json | 1 + .../common/script/content/gear/sets/mystery.js | 12 ++++++++++++ website/common/script/content/mystery-sets.js | 4 ++++ 11 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 website/assets/sprites/spritesmith/gear/events/mystery_201704/back_mystery_201704.png create mode 100644 website/assets/sprites/spritesmith/gear/events/mystery_201704/broad_armor_mystery_201704.png create mode 100644 website/assets/sprites/spritesmith/gear/events/mystery_201704/shop_armor_mystery_201704.png create mode 100644 website/assets/sprites/spritesmith/gear/events/mystery_201704/shop_back_mystery_201704.png create mode 100644 website/assets/sprites/spritesmith/gear/events/mystery_201704/slim_armor_mystery_201704.png create mode 100644 website/assets/sprites/spritesmith_large/promo/promo_mystery_201704.png diff --git a/migrations/mystery_items.js b/migrations/mystery_items.js index 5fa5573ee0..bce80d8ef4 100644 --- a/migrations/mystery_items.js +++ b/migrations/mystery_items.js @@ -2,7 +2,7 @@ var _id = ''; var update = { $addToSet: { 'purchased.plan.mysteryItems':{ - $each:['head_mystery_201703','armor_mystery_201703'] + $each:['back_mystery_201704','armor_mystery_201704'] } } }; diff --git a/website/assets/sprites/spritesmith/gear/events/mystery_201704/back_mystery_201704.png b/website/assets/sprites/spritesmith/gear/events/mystery_201704/back_mystery_201704.png new file mode 100644 index 0000000000000000000000000000000000000000..f30ce209e65e82809e0055f474e788f1abd77817 GIT binary patch literal 926 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901|%(3I5Gh#wj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI+MN*iTt^0!%GXn#&wx^3@NX4zUvwbs!9Yq?pFH`F9 zT@|WvL;j+7OVbXg038YKB)!{8UDM~-ez{VuujJIG!S_0&T|t5B@J*87G@AE>`zn z_xzqZ?XTj${7iFO_22oun%HAs_M+iRd0Dyi@%_CKw+@y;By6F6`XtzVwb@=Evuoc4S=P5bU)Ia97whM^4+tQkSe zd6oX{iZ8R<=5;^bCVX)PbLpD-vYPSSM`HuOo;=`RD!2srdIV=fG3fcWC$LKJ<0F)uYP3YS#G+KU#VgK3$;wHTPxDm(s!y zB}a3kdvo8ncDTu|Sghx@@ng}uZkuO6ucfV@J@12%=<%5^?qvGxxz*Pw2=Z8tR_u*U(pCQ#ZZ^%C6K%F# z z+M;CgYmbZW1*wa3UVq;n=J=La1#`^A#BRy6VO uwe@zFzSZ8W30bq@jyo_sV2Sod&>zMfF$O-Gj+?)L#5`U7T-G@yGywoJg{Ln7 literal 0 HcmV?d00001 diff --git a/website/assets/sprites/spritesmith/gear/events/mystery_201704/broad_armor_mystery_201704.png b/website/assets/sprites/spritesmith/gear/events/mystery_201704/broad_armor_mystery_201704.png new file mode 100644 index 0000000000000000000000000000000000000000..3a35fe36fec7ef0c93058d6015ab750366fef8ef GIT binary patch literal 557 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901|%(3I5Gh#wj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI+MN&k^I?5xogn@x^m#2$kNX4zUH|)I+2Z*#kbeSS$ z&MM0yE3#yA7ay-ukY>{h1-&|!(z3M{KN#<@lopj020L~Kty1asSg61!s#6kh#aCDK zby-n+i^-NP%l4fw+3_U)^iF%fMVUYgae{whwmSS$-AA?ZUT@2Now%b;dha!fKNoo~ z$xYjF!+!UquM-zDuC%^%z39xBGj0oBr(d!Tn$ACcNnM1d$6hn}^;eYLUSuq4ev#4j zG0%D1+q=J)=RDu|s5M(8^b*(SMyy#4M zeSdz;luyCuOoiM0+=XQ`UcLxWZ>%T^y!PEse`#NuUCjBjz9pV}=gleIo3?jfKG=wh zBCo{0-MY0b^HkNBYn|QIs!AcR3?1LQ0`+s;y%KmK`OXieEkj!%X?+^ ziIv@bYE}*>L;#<)G8K=yR?b6Mw<&;$V03+iJ4 literal 0 HcmV?d00001 diff --git a/website/assets/sprites/spritesmith/gear/events/mystery_201704/shop_armor_mystery_201704.png b/website/assets/sprites/spritesmith/gear/events/mystery_201704/shop_armor_mystery_201704.png new file mode 100644 index 0000000000000000000000000000000000000000..d7b4e66553634715780c2ec5b98eb876f6e8264b GIT binary patch literal 3174 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 literal 0 HcmV?d00001 diff --git a/website/assets/sprites/spritesmith/gear/events/mystery_201704/slim_armor_mystery_201704.png b/website/assets/sprites/spritesmith/gear/events/mystery_201704/slim_armor_mystery_201704.png new file mode 100644 index 0000000000000000000000000000000000000000..681f818bc172023ab24f655569eb877417c39308 GIT binary patch literal 538 zcmeAS@N?(olHy`uVBq!ia0vp^Q6S901|%(3I5Gh#wj^(N7l!{JxM1({$v_d#0*}aI z1_o|n5N2eUHAey{$X?><>&pI+MN&jZ;)lKp&;Z8eo-U3d6}R5r*za{XK;-yG7p<*+ zS}z_vNSN-YWu?;5v2@o9g?)_{9h*xJ;*Sq<97K>HAt^cs7vu}RkLb->VpPNU@*H}(^ZC|xp$=1H` zyUN$f<4dkM&($=};V&_{c=3*qRbG_st@o|&mbW|~tC+Z5*|8$bZEyOsAT?Rv5`}9Q z7y2;?|CEfpZ=>NgS99;Ld)0<_^u7k!Nn1UQI| z>+7r)`(53IWiRBt`f*lpUFmkux%>X|>|HkT{-VX5eb3Tf?Z_;>WN}C9MAg%MTBSda z)$fvc@p;+AfAf}H3GTLBDSKSes)O%H@+%G@5%J%H2NzeeuP6Wj1p!G! zK~#9!?VD{(m1P*mpW``tfaFajFae!>;OyWEkB!dE1(K%KPRwAn#n#$diK|BIlVRDS zjr!Db)$C<9*G3=OX^vbbf~bw?#Oh?N!&n=n9JQuk#35b}p1xhzrMsS<9e5Zt{=ZMx zZtv%L?wj5B&->*%h5-No0000000000000000001B-f=9p26?^SgdOkc>FHTieOrdH zhCejaVF$Z1-XcXr^FO8Xcsx#VN=gdFYcD*ZzUPk(pHVz9F+s7*<)XOY(vuXoj_h|} zL_-~5uDBLT7ki(6j>_wU?^B$eovrFZ)*p(bQ5+0eYeYV495E6Lp%IK|{Gp)^mFUHb zG$p(HE>L-OWw}FJT}WMcii;`ES+j-}!LPYV7uWu}qPAg^*UNPV2PxkF)fVn>zUXof zhhKD@qxP2-ZQvEl`t=TsXs83&x?z#J$S*9U@=JfmDDDpbMDeT7?^8D>W=u^@QCxhz zPObBM-Y3bqQfy@2c<(U9WxmZ6=VzvKUj_zj(FjH~)Um|XVWx|d(Xr#|N+4qAvTsTwicj2K3mTBoF>gwIUY8kTh;k%(Hy>2zAQ zU#>J{UkqO!Ml{p`=B^HR2Ww$wrCbU8X{nJ%kF?VN@@+5Hh!w~%MYWp?C@x(gzkHk8 zp3)T~>a#_wxqfbLF2y_F2_Co9gDpPU61Uh@@rd`Q~Ysr zo4P5u;VTOWjZpamD~Hu@^$q=_mP&qNY?`nS?@oIcrz`21Yd0u{3baw=QnU z_fR(ra%y`T_%hUObXjUd>SD5gE5Ea;)UwU8ZMzE#16p5@O$v1Y+trb5dYD8Wd-l24 zQQUUmVTwQa`V1?jQ%@C(`1oSC?!b166A4~j6ir^{2i#c;q7jT}r~~RC z>f+h^DSr8$7u08EE{b!Tp3we!m^Wgv{xHRdYOC-Y7N`TOj@con(y^-@Y8{z@X_ivu z_xlr8x;SyHQE$YagO=?y`)-TDDQ%OZdNyfnv=Sp4>Hu?Jb<77+9r9?j?m<5G4241t zyPw5~hC0CiS{-I8NOsE4