diff --git a/migrations/20170425_missing_incentives.js b/migrations/20170425_missing_incentives.js new file mode 100644 index 0000000000..be8c224719 --- /dev/null +++ b/migrations/20170425_missing_incentives.js @@ -0,0 +1,207 @@ +var migrationName = '20170425_missing_incentives'; +var authorName = 'Sabe'; // in case script author needs to know when their ... +var authorUuid = '7f14ed62-5408-4e1b-be83-ada62d504931'; //... own data is done + +/* + * Award missing Royal Purple Hatching Potion to users with 55+ check-ins + * Reduce users with impossible check-in counts to a reasonable number + */ + +import monk from 'monk'; +import common from '../website/common'; + +var connectionString = 'mongodb://localhost:27017/habitrpg?auto_reconnect=true'; // FOR TEST DATABASE +var dbUsers = monk(connectionString).get('users', { castIds: false }); + +function processUsers(lastId) { + // specify a query to limit the affected users (empty for all users): + var query = { + 'loginIncentives': {$gt:99}, + 'migration': {$ne: migrationName}, + }; + + if (lastId) { + query._id = { + $gt: lastId + } + } + + dbUsers.find(query, { + sort: {_id: 1}, + limit: 250, + fields: [] // specify fields we are interested in to limit retrieved data (empty if we're not reading data): + }) + .then(updateUsers) + .catch(function (err) { + console.log(err); + return exiting(1, 'ERROR! ' + err); + }); +} + +var progressCount = 1000; +var count = 0; + +function updateUsers (users) { + if (!users || users.length === 0) { + console.warn('All appropriate users found and modified.'); + displayData(); + return; + } + + var userPromises = users.map(updateUser); + var lastUser = users[users.length - 1]; + + return Promise.all(userPromises) + .then(function () { + processUsers(lastUser._id); + }); +} + +function updateUser (user) { + count++; + var language = user.preferences.language || 'en'; + var set = {'migration': migrationName}; + var inc = { + 'items.eggs.BearCub': 0, + 'items.eggs.Cactus': 0, + 'items.eggs.Dragon': 0, + 'items.eggs.FlyingPig': 0, + 'items.eggs.Fox': 0, + 'items.eggs.LionCub': 0, + 'items.eggs.PandaCub': 0, + 'items.eggs.TigerCub': 0, + 'items.eggs.Wolf': 0, + 'items.food.Chocolate': 0, + 'items.food.CottonCandyBlue': 0, + 'items.food.CottonCandyPink': 0, + 'items.food.Fish': 0, + 'items.food.Honey': 0, + 'items.food.Meat': 0, + 'items.food.Milk': 0, + 'items.food.Potatoe': 0, + 'items.food.RottenMeat': 0, + 'items.food.Strawberry': 0, + 'items.hatchingPotions.Base': 0, + 'items.hatchingPotions.CottonCandyBlue': 0, + 'items.hatchingPotions.CottonCandyPink': 0, + 'items.hatchingPotions.Desert': 0, + 'items.hatchingPotions.Golden': 0, + 'items.hatchingPotions.Red': 0, + 'items.hatchingPotions.RoyalPurple': 0, + 'items.hatchingPotions.Shade': 0, + 'items.hatchingPotions.Skeleton': 0, + 'items.hatchingPotions.White': 0, + 'items.hatchingPotions.Zombie': 0, + }; + var nextReward; + + if (user.loginIncentives >= 105) { + inc['items.hatchingPotions.RoyalPurple'] += 1; + nextReward = 110; + } + if (user.loginIncentives >= 110) { + inc['items.eggs.BearCub'] += 1; + inc['items.eggs.Cactus'] += 1; + inc['items.eggs.Dragon'] += 1; + inc['items.eggs.FlyingPig'] += 1; + inc['items.eggs.Fox'] += 1; + inc['items.eggs.LionCub'] += 1; + inc['items.eggs.PandaCub'] += 1; + inc['items.eggs.TigerCub'] += 1; + inc['items.eggs.Wolf'] += 1; + nextReward = 115; + } + if (user.loginIncentives >= 115) { + inc['items.hatchingPotions.RoyalPurple'] += 1; + nextReward = 120; + } + if (user.loginIncentives >= 120) { + inc['items.hatchingPotions.Base'] += 1; + inc['items.hatchingPotions.CottonCandyBlue'] += 1; + inc['items.hatchingPotions.CottonCandyPink'] += 1; + inc['items.hatchingPotions.Desert'] += 1; + inc['items.hatchingPotions.Golden'] += 1; + inc['items.hatchingPotions.Red'] += 1; + inc['items.hatchingPotions.Shade'] += 1; + inc['items.hatchingPotions.Skeleton'] += 1; + inc['items.hatchingPotions.White'] += 1; + inc['items.hatchingPotions.Zombie'] += 1; + nextReward = 125; + } + if (user.loginIncentives >= 125) { + inc['items.hatchingPotions.RoyalPurple'] += 1; + nextReward = 130; + } + if (user.loginIncentives >= 130) { + inc['items.food.Chocolate'] += 3; + inc['items.food.CottonCandyBlue'] += 3; + inc['items.food.CottonCandyPink'] += 3; + inc['items.food.Fish'] += 3; + inc['items.food.Honey'] += 3; + inc['items.food.Meat'] += 3; + inc['items.food.Milk'] += 3; + inc['items.food.Potatoe'] += 3; + inc['items.food.RottenMeat'] += 3; + inc['items.food.Strawberry'] += 3; + } + if (user.loginIncentives >= 135) { + inc['items.hatchingPotions.RoyalPurple'] += 1; + nextReward = 140; + } + if (user.loginIncentives >= 140) { + set['items.gear.owned.weapon_special_skeletonKey'] = true; + set['items.gear.owned.shield_special_lootBag'] = true; + nextReward = 145; + } + if (user.loginIncentives >= 145) { + inc['items.hatchingPotions.RoyalPurple'] += 1; + nextReward = 150; + } + if (user.loginIncentives >= 150) { + set['items.gear.owned.head_special_clandestineCowl'] = true; + set['items.gear.owned.armor_special_sneakthiefRobes'] = true; + nextReward = 155; + } + if (user.loginIncentives > 155) { + set.loginIncentives = 155; + nextReward = 160; + } + + var push = { + 'notifications': { + 'type': 'LOGIN_INCENTIVE', + 'data': { + 'nextRewardAt': nextReward, + 'rewardKey': [ + 'shop_armoire', + ], + 'rewardText': common.i18n.t('checkInRewards', language), + 'reward': [], + 'message': common.i18n.t('backloggedCheckInRewards', language), + }, + 'id': common.uuid(), + } + }; + + dbUsers.update({_id: user._id}, {$set:set, $push:push, $inc:inc}); + + if (count % progressCount == 0) console.warn(count + ' ' + user._id); + if (user._id == authorUuid) console.warn(authorName + ' processed'); +} + +function displayData() { + console.warn('\n' + count + ' users processed\n'); + return exiting(0); +} + +function exiting(code, msg) { + code = code || 0; // 0 = success + if (code && !msg) { msg = 'ERROR!'; } + if (msg) { + if (code) { console.error(msg); } + else { console.log( msg); } + } + process.exit(code); +} + +module.exports = processUsers; diff --git a/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_dandySuit.png b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_dandySuit.png new file mode 100644 index 0000000000..ab35fe5ebf Binary files /dev/null and b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_dandySuit.png differ diff --git a/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_nomadsCuirass.png b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_nomadsCuirass.png new file mode 100644 index 0000000000..fe2148737a Binary files /dev/null and b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_nomadsCuirass.png differ diff --git a/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_samuraiArmor.png b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_samuraiArmor.png new file mode 100644 index 0000000000..459d77fe49 Binary files /dev/null and b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_samuraiArmor.png differ diff --git a/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_sneakthiefRobes.png b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_sneakthiefRobes.png new file mode 100644 index 0000000000..765f9978b1 Binary files /dev/null and b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_sneakthiefRobes.png differ diff --git a/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_snowSovereignRobes.png b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_snowSovereignRobes.png new file mode 100644 index 0000000000..dd5f32c55f Binary files /dev/null and b/website/assets/sprites/spritesmith/gear/armor/broad_armor_special_snowSovereignRobes.png differ diff --git a/website/common/locales/en/gear.json b/website/common/locales/en/gear.json index a691af44c4..37a7e07945 100644 --- a/website/common/locales/en/gear.json +++ b/website/common/locales/en/gear.json @@ -97,6 +97,14 @@ "weaponSpecialPageBannerNotes": "Wave your banner high to inspire confidence! Increases Strength by <%= str %>.", "weaponSpecialRoguishRainbowMessageText": "Roguish Rainbow Message", "weaponSpecialRoguishRainbowMessageNotes": "This sparkly envelope contains messages of encouragement from Habiticans, and a touch of magic to help speed your deliveries! Increases Perception by <%= per %>.", + "weaponSpecialSkeletonKeyText": "Skeleton Key", + "weaponSpecialSkeletonKeyNotes": "All the best Sneakthieves carry a key that can open any lock! Increases Constitution by <%= con %>.", + "weaponSpecialNomadsScimitarText": "Nomad's Scimitar", + "weaponSpecialNomadsScimitarNotes": "The curved blade of this Scimitar is perfect for attacking Tasks from the back of a mount! Increases Intelligence by <%= int %>.", + "weaponSpecialFencingFoilText": "Fencing Foil", + "weaponSpecialFencingFoilNotes": "Should anyone dare to impugn your honor, you'll be ready with this fine foil! Increases Strength by <%= str %>.", + "weaponSpecialTachiText": "Tachi", + "weaponSpecialTachiNotes": "This light and curved sword will shred your tasks to ribbons! Increases Strength by <%= str %>.", "weaponSpecialYetiText": "Yeti-Tamer Spear", "weaponSpecialYetiNotes": "This spear allows its user to command any yeti. Increases Strength by <%= str %>. Limited Edition 2013-2014 Winter Gear.", @@ -356,6 +364,16 @@ "armorSpecialPageArmorNotes": "Carry everything you need in your perfect pack! Increases Constitution by <%= con %>.", "armorSpecialRoguishRainbowMessengerRobesText": "Roguish Rainbow Messenger Robes", "armorSpecialRoguishRainbowMessengerRobesNotes": "These vividly striped robes will allow you to fly through gale-force winds smoothly and safely. Increases Strength by <%= str %>.", + "armorSpecialSneakthiefRobesText": "Sneakthief Robes", + "armorSpecialSneakthiefRobesNotes": "These robes will help hide you in the dead of night, but will also allow freedom of movement as you silently sneak about! Increases Intelligence by <%= int %>.", + "armorSpecialSnowSovereignRobesText": "Snow Sovereign Robes", + "armorSpecialSnowSovereignRobesNotes": "These robes are elegant enough for court, yet warm enough for the coldest winter day. Increases Perception by <%= per %>.", + "armorSpecialNomadsCuirassText": "Nomad's Cuirass", + "armorSpecialNomadsCuirassNotes": "This armor features a strong chest-plate to protect your heart! Increases Constitution by <%= con %>.", + "armorSpecialDandySuitText": "Dandy Suit", + "armorSpecialDandySuitNotes": "You're undeniably dressed for success! Increases Perception by <%= per %>.", + "armorSpecialSamuraiArmorText": "Samurai Armor", + "armorSpecialSamuraiArmorNotes": "This strong, scaled armor is held together by elegant silk cords. Increases Perception by <%= per %>.", "armorSpecialYetiText": "Yeti-Tamer Robe", "armorSpecialYetiNotes": "Fuzzy and fierce. Increases Constitution by <%= con %>. Limited Edition 2013-2014 Winter Gear.", @@ -682,6 +700,16 @@ "headSpecialPageHelmNotes": "Chainmail: for the stylish AND the practical. Increases Perception by <%= per %>.", "headSpecialRoguishRainbowMessengerHoodText": "Roguish Rainbow Messenger Hood", "headSpecialRoguishRainbowMessengerHoodNotes": "This bright hood emits a colorful glow that will protect you from unpleasant weather! Increases Constitution by <%= con %>.", + "headSpecialClandestineCowlText": "Clandestine Cowl", + "headSpecialClandestineCowlNotes": "Take care to conceal your face as you rob your Tasks of gold and loot! Increases Perception by <%= per %>.", + "headSpecialSnowSovereignCrownText": "Snow Sovereign Crown", + "headSpecialSnowSovereignCrownNotes": "The jewels in this crown sparkle like new-fallen snowflakes. Increases Constitution by <%= con %>.", + "headSpecialSpikedHelmText": "Spiked Helm", + "headSpecialSpikedHelmNotes": "You'll be well protected from stray Dailies and bad Habits with this functional (and neat-looking!) helm. Increases Strength by <%= str %>.", + "headSpecialDandyHatText": "Dandy Hat", + "headSpecialDandyHatNotes": "What a merry chapeau! You'll look quite fine enjoying a stroll in it. Increases Constitution by <%= con %>.", + "headSpecialKabutoText": "Kabuto", + "headSpecialKabutoNotes": "This helm is functional and beautiful! Your enemies will become distracted admiring it. Increases Intelligence by <%= int %>.", "headSpecialNyeText": "Absurd Party Hat", "headSpecialNyeNotes": "You've received an Absurd Party Hat! Wear it with pride while ringing in the New Year! Confers no benefit.", @@ -994,6 +1022,12 @@ "shieldSpecialDiamondStaveNotes": "This valuable stave has mystical powers. Increases Intelligence by <%= int %>.", "shieldSpecialRoguishRainbowMessageText": "Roguish Rainbow Message", "shieldSpecialRoguishRainbowMessageNotes": "This sparkly envelope contains messages of encouragement from Habiticans, and a touch of magic to help speed your deliveries! Increases Intelligence by <%= int %>.", + "shieldSpecialLootBagText": "Loot Bag", + "shieldSpecialLootBagNotes": "This bag is ideal for storing all the goodies you've stealthily removed from unsuspecting Tasks! Increases Strength by <%= str %>.", + "shieldSpecialWintryMirrorText": "Wintry Mirror", + "shieldSpecialWintryMirrorNotes": "How else to best admire your wintry look? Increases Intelligence by <%= int %>.", + "shieldSpecialWakizashiText": "Wakizashi", + "shieldSpecialWakizashiNotes": "This short sword is perfect for close-quarters battles with your Dailies! Increases Constitution by <%= con %>.", "shieldSpecialYetiText": "Yeti-Tamer Shield", "shieldSpecialYetiNotes": "This shield reflects light from the snow. Increases Constitution by <%= con %>. Limited Edition 2013-2014 Winter Gear.", @@ -1156,6 +1190,8 @@ "backSpecialWonderconBlackNotes": "Spun of shadows and whispers. Confers no benefit. Special Edition Convention Item.", "backSpecialTakeThisText": "Take This Wings", "backSpecialTakeThisNotes": "These wings were earned by participating in a sponsored Challenge made by Take This. Congratulations! Increases all attributes by <%= attrs %>.", + "backSpecialSnowdriftVeilText": "Snowdrift Veil", + "backSpecialSnowdriftVeilNotes": "This translucent veil makes it appear you are surrounded by an elegant flurry of snow! Confers no benefit.", "body": "Body Accessory", "bodyBase0Text": "No Body Accessory", diff --git a/website/common/locales/en/loginIncentives.json b/website/common/locales/en/loginIncentives.json index 82059156ee..c8f5248ba9 100644 --- a/website/common/locales/en/loginIncentives.json +++ b/website/common/locales/en/loginIncentives.json @@ -1,6 +1,6 @@ { "unlockedReward": "You have received <%= reward %>", - "earnedRewardForDevotion": "You have earned a <%= reward %> for being committed to improving your life.", + "earnedRewardForDevotion": "You have earned <%= reward %> for being committed to improving your life.", "nextRewardUnlocksIn": "Check-ins until your next prize: <%= numberOfCheckinsLeft %>", "awesome": "Awesome!", "totalCount": "<%= count %> total count", @@ -20,5 +20,10 @@ "threeOfEachFood": "three of each standard Pet Food", "fourOfEachFood": "four of each standard Pet Food", "twoSaddles": "two Saddles", - "threeSaddles": "three Saddles" + "threeSaddles": "three Saddles", + "incentiveAchievement": "the Royally Loyal achievement", + "royallyLoyal": "Royally Loyal", + "royallyLoyalText": "This user has checked in over 500 times, and has earned every Check-In Prize!", + "checkInRewards": "Check-In Rewards", + "backloggedCheckInRewards": "You received Check-In Prizes! Visit your Inventory and Equipment to see what's new." } diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index 3acb083174..67f234c53a 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -99,7 +99,7 @@ let basicAchievs = { }, royallyLoyal: { icon: 'achievement-royally-loyal', - titleKey: 'royallyLoyalName', + titleKey: 'royallyLoyal', textKey: 'royallyLoyalText', }, }; diff --git a/website/common/script/content/loginIncentives.js b/website/common/script/content/loginIncentives.js index a168830fe3..f70447a28d 100644 --- a/website/common/script/content/loginIncentives.js +++ b/website/common/script/content/loginIncentives.js @@ -337,7 +337,7 @@ module.exports = function getLoginIncentives (api) { }, }, 140: { - rewardKey: ['weapon_special_skeletonKey', 'shield_special_lootBag'], + rewardKey: ['shop_weapon_special_skeletonKey', 'shop_shield_special_lootBag'], reward: [api.gear.flat.weapon_special_skeletonKey, api.gear.flat.shield_special_lootBag], assignReward: function assignReward (user) { user.items.gear.owned.weapon_special_skeletonKey = true; // eslint-disable-line camelcase @@ -353,7 +353,7 @@ module.exports = function getLoginIncentives (api) { }, }, 150: { - rewardKey: ['head_special_clandestineCowl', 'broad_armor_special_sneakthiefRobes'], + rewardKey: ['shop_head_special_clandestineCowl', 'shop_armor_special_sneakthiefRobes'], reward: [api.gear.flat.head_special_clandestineCowl, api.gear.flat.armor_special_sneakthiefRobes], assignReward: function assignReward (user) { user.items.gear.owned.head_special_clandestineCowl = true; // eslint-disable-line camelcase @@ -369,7 +369,7 @@ module.exports = function getLoginIncentives (api) { }, }, 170: { - rewardKey: ['head_special_snowSovereignCrown', 'broad_armor_special_snowSovereignRobes'], + rewardKey: ['shop_head_special_snowSovereignCrown', 'shop_armor_special_snowSovereignRobes'], reward: [api.gear.flat.head_special_snowSovereignCrown, api.gear.flat.armor_special_snowSovereignRobes], assignReward: function assignReward (user) { user.items.gear.owned.head_special_snowSovereignCrown = true; // eslint-disable-line camelcase @@ -385,11 +385,11 @@ module.exports = function getLoginIncentives (api) { }, }, 190: { - rewardKey: ['shield_special_wintryMirror', 'back_special_snowdriftVeil'], + rewardKey: ['shop_shield_special_wintryMirror', 'shop_back_special_snowdriftVeil'], reward: [api.gear.flat.shield_special_wintryMirror, api.gear.flat.back_special_snowdriftVeil], assignReward: function assignReward (user) { - user.items.gear.owned.head_special_wintryMirror = true; // eslint-disable-line camelcase - user.items.gear.owned.armor_special_snowdriftVeil = true; // eslint-disable-line camelcase + user.items.gear.owned.shield_special_wintryMirror = true; // eslint-disable-line camelcase + user.items.gear.owned.back_special_snowdriftVeil = true; // eslint-disable-line camelcase }, }, 200: { @@ -409,7 +409,7 @@ module.exports = function getLoginIncentives (api) { }, }, 240: { - rewardKey: ['weapon_special_nomadsScimitar', 'broad_armor_special_nomadsCuirass'], + rewardKey: ['shop_weapon_special_nomadsScimitar', 'shop_armor_special_nomadsCuirass'], reward: [api.gear.flat.weapon_special_nomadsScimitar, api.gear.flat.armor_special_nomadsCuirass], assignReward: function assignReward (user) { user.items.gear.owned.weapon_special_nomadsScimitar = true; // eslint-disable-line camelcase @@ -417,7 +417,7 @@ module.exports = function getLoginIncentives (api) { }, }, 260: { - rewardKey: ['head_special_spikedHelm'], + rewardKey: ['shop_head_special_spikedHelm'], reward: [api.gear.flat.head_special_spikedHelm], assignReward: function assignReward (user) { user.items.gear.owned.head_special_spikedHelm = true; // eslint-disable-line camelcase @@ -476,14 +476,14 @@ module.exports = function getLoginIncentives (api) { }, }, 320: { - rewardKey: ['head_special_dandyHat'], + rewardKey: ['shop_head_special_dandyHat'], reward: [api.gear.flat.head_special_dandyHat], assignReward: function assignReward (user) { user.items.gear.owned.head_special_dandyHat = true; // eslint-disable-line camelcase }, }, 340: { - rewardKey: ['weapon_special_fencingFoil', 'broad_armor_special_dandySuit'], + rewardKey: ['shop_weapon_special_fencingFoil', 'shop_armor_special_dandySuit'], reward: [api.gear.flat.weapon_special_fencingFoil, api.gear.flat.armor_special_dandySuit], assignReward: function assignReward (user) { user.items.gear.owned.weapon_special_fencingFoil = true; // eslint-disable-line camelcase @@ -561,7 +561,7 @@ module.exports = function getLoginIncentives (api) { }, }, 450: { - rewardKey: ['weapon_special_tachi', 'broad_armor_special_samuraiArmor'], + rewardKey: ['shop_weapon_special_tachi', 'shop_armor_special_samuraiArmor'], reward: [api.gear.flat.weapon_special_tachi, api.gear.flat.armor_special_samuraiArmor], assignReward: function assignReward (user) { user.items.gear.owned.weapon_special_tachi = true; // eslint-disable-line camelcase @@ -569,7 +569,7 @@ module.exports = function getLoginIncentives (api) { }, }, 475: { - rewardKey: ['head_special_kabuto', 'shield_special_wakizashi'], + rewardKey: ['shop_head_special_kabuto', 'shop_shield_special_wakizashi'], reward: [api.gear.flat.head_special_kabuto, api.gear.flat.shield_special_wakizashi], assignReward: function assignReward (user) { user.items.gear.owned.head_special_kabuto = true; // eslint-disable-line camelcase @@ -579,13 +579,12 @@ module.exports = function getLoginIncentives (api) { 500: { rewardKey: ['achievement-royally-loyal2x'], reward: [api.achievements.royallyLoyal], + rewardName: 'incentiveAchievement', assignReward: function assignReward (user) { user.achievements.royallyLoyal = true; // eslint-disable-line camelcase }, }, }; - // 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 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. diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index b410824fdf..318beca18d 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -179,6 +179,7 @@ function _getBasicAchievements (user, language) { _addSimple(result, user, {path: 'partyUp', language}); _addSimple(result, user, {path: 'partyOn', language}); + _addSimple(result, user, {path: 'royallyLoyal', language}); _addSimpleWithMasterCount(result, user, {path: 'beastMaster', language}); _addSimpleWithMasterCount(result, user, {path: 'mountMaster', language});