diff --git a/migrations/archive/2019/20190927_kickstarter.js b/migrations/archive/2019/20190927_kickstarter.js new file mode 100644 index 0000000000..a62ed15350 --- /dev/null +++ b/migrations/archive/2019/20190927_kickstarter.js @@ -0,0 +1,67 @@ +/* eslint-disable no-console */ +const MIGRATION_NAME = '20190927_kickstarter'; +import { v4 as uuid } from 'uuid'; + +import { model as User } from '../../../website/server/models/user'; + +const progressCount = 1000; +let count = 0; + +async function updateUser (user) { + count++; + + const set = {}; + let push = {pinnedItems: {$each: []}}; + + set.migration = MIGRATION_NAME; + set['achievements.ks2019'] = true; + + // set['items.gear.owned.armor_special_ks2019'] = false; + // push.pinnedItems.$each.push({type: 'marketGear', path: 'gear.flat.armor_special_ks2019', _id: uuid()}); + set['items.gear.owned.head_special_ks2019'] = false; + push.pinnedItems.$each.push({type: 'marketGear', path: 'gear.flat.head_special_ks2019', _id: uuid()}); + // set['items.gear.owned.shield_special_ks2019'] = false; + // push.pinnedItems.$each.push({type: 'marketGear', path: 'gear.flat.shield_special_ks2019', _id: uuid()}); + // set['items.gear.owned.weapon_special_ks2019'] = false; + // push.pinnedItems.$each.push({type: 'marketGear', path: 'gear.flat.weapon_special_ks2019', _id: uuid()}); + set['items.gear.owned.eyewear_special_ks2019'] = false; + push.pinnedItems.$each.push({type: 'marketGear', path: 'gear.flat.eyewear_special_ks2019', _id: uuid()}); + // set['items.pets.Gryphon-Gryphatrice'] = 5; + // set['items.mounts.Gryphon-Gryphatrice'] = true; + + return await User.update({_id: user._id}, {$set: set, $push: push}).exec(); +} + +module.exports = async function processUsers () { + let query = { + migration: {$ne: MIGRATION_NAME}, + 'auth.local.lowerCaseUsername': {$in: []}, + }; + + const fields = { + _id: 1, + items: 1, + }; + + while (true) { // eslint-disable-line no-constant-condition + const users = await User // eslint-disable-line no-await-in-loop + .find(query) + .limit(250) + .sort({_id: 1}) + .select(fields) + .lean() + .exec(); + + if (users.length === 0) { + console.warn('All appropriate users found and modified.'); + console.warn(`\n${count} users processed\n`); + break; + } else { + query._id = { + $gt: users[users.length - 1], + }; + } + + await Promise.all(users.map(updateUser)); // eslint-disable-line no-await-in-loop + } +}; diff --git a/website/client/assets/css/sprites.css b/website/client/assets/css/sprites.css index 53fe4464fc..ded80afcc7 100755 --- a/website/client/assets/css/sprites.css +++ b/website/client/assets/css/sprites.css @@ -96,6 +96,52 @@ background: url("~assets/images/BackerOnly-Pet-CerberusPup.gif") no-repeat; } +.broad_armor_special_ks2019, .slim_armor_special_ks2019, .eyewear_special_ks2019, .head_special_ks2019, .shield_special_ks2019 { + width: 117px; + height: 120px; +} + +.broad_armor_special_ks2019, .slim_armor_special_ks2019 { + background: url("~assets/images/BackerOnly-Equip-MythicGryphonArmor.gif") no-repeat; +} + +.eyewear_special_ks2019 { + background: url("~assets/images/BackerOnly-Equip-MythicGryphonVisor.gif") no-repeat; +} + +.head_special_ks2019 { + background: url("~assets/images/BackerOnly-Equip-MythicGryphonHelm.gif") no-repeat; +} + +.shield_special_ks2019 { + background: url("~assets/images/BackerOnly-Equip-MythicGryphonShield.gif") no-repeat; +} + +.weapon_special_ks2019 { + background: url("~assets/images/BackerOnly-Equip-MythicGryphonGlaive.gif") no-repeat; + width: 120px; + height: 120px; +} + +.Pet-Gryphon-Gryphatrice { + background: url("~assets/images/BackerOnly-Pet-Gryphatrice.gif") no-repeat; + width: 81px; + height: 99px; +} + +.Mount_Head_Gryphon-Gryphatrice, .Mount_Body_Gryphon-Gryphatrice { + width: 135px; + height: 135px; +} + +.Mount_Head_Gryphon-Gryphatrice { + background: url("~assets/images/BackerOnly-Mount-Head-Gryphatrice.gif") no-repeat; +} + +.Mount_Body_Gryphon-Gryphatrice { + background: url("~assets/images/BackerOnly-Mount-Body-Gryphatrice.gif") no-repeat; +} + /* FIXME figure out how to handle customize menu!! .customize-menu .f_head_0 { width: 60px; diff --git a/website/client/assets/images/BackerOnly-Equip-MythicGryphonArmor.gif b/website/client/assets/images/BackerOnly-Equip-MythicGryphonArmor.gif new file mode 100644 index 0000000000..4b647759cf Binary files /dev/null and b/website/client/assets/images/BackerOnly-Equip-MythicGryphonArmor.gif differ diff --git a/website/client/assets/images/BackerOnly-Equip-MythicGryphonGlaive.gif b/website/client/assets/images/BackerOnly-Equip-MythicGryphonGlaive.gif new file mode 100644 index 0000000000..3cd89ebcf4 Binary files /dev/null and b/website/client/assets/images/BackerOnly-Equip-MythicGryphonGlaive.gif differ diff --git a/website/client/assets/images/BackerOnly-Equip-MythicGryphonHelm.gif b/website/client/assets/images/BackerOnly-Equip-MythicGryphonHelm.gif new file mode 100644 index 0000000000..d1dbc4d362 Binary files /dev/null and b/website/client/assets/images/BackerOnly-Equip-MythicGryphonHelm.gif differ diff --git a/website/client/assets/images/BackerOnly-Equip-MythicGryphonShield.gif b/website/client/assets/images/BackerOnly-Equip-MythicGryphonShield.gif new file mode 100644 index 0000000000..781e8a09c1 Binary files /dev/null and b/website/client/assets/images/BackerOnly-Equip-MythicGryphonShield.gif differ diff --git a/website/client/assets/images/BackerOnly-Equip-MythicGryphonVisor.gif b/website/client/assets/images/BackerOnly-Equip-MythicGryphonVisor.gif new file mode 100644 index 0000000000..3eddc99d1f Binary files /dev/null and b/website/client/assets/images/BackerOnly-Equip-MythicGryphonVisor.gif differ diff --git a/website/client/assets/images/BackerOnly-Mount-Body-Gryphatrice.gif b/website/client/assets/images/BackerOnly-Mount-Body-Gryphatrice.gif new file mode 100644 index 0000000000..f913c2695a Binary files /dev/null and b/website/client/assets/images/BackerOnly-Mount-Body-Gryphatrice.gif differ diff --git a/website/client/assets/images/BackerOnly-Mount-Head-Gryphatrice.gif b/website/client/assets/images/BackerOnly-Mount-Head-Gryphatrice.gif new file mode 100644 index 0000000000..94d363e2cc Binary files /dev/null and b/website/client/assets/images/BackerOnly-Mount-Head-Gryphatrice.gif differ diff --git a/website/client/assets/images/BackerOnly-Pet-Gryphatrice.gif b/website/client/assets/images/BackerOnly-Pet-Gryphatrice.gif new file mode 100644 index 0000000000..202636425f Binary files /dev/null and b/website/client/assets/images/BackerOnly-Pet-Gryphatrice.gif differ diff --git a/website/common/locales/en/achievements.json b/website/common/locales/en/achievements.json index db8b78ea2b..7de4a1baf9 100644 --- a/website/common/locales/en/achievements.json +++ b/website/common/locales/en/achievements.json @@ -24,5 +24,7 @@ "achievementDustDevilModalText": "You collected all the Desert Pets!", "achievementAridAuthority": "Arid Authority", "achievementAridAuthorityText": "Has tamed all Desert Mounts.", - "achievementAridAuthorityModalText": "You tamed all the Desert Mounts!" + "achievementAridAuthorityModalText": "You tamed all the Desert Mounts!", + "achievementKickstarter2019": "Pin Kickstarter Backer", + "achievementKickstarter2019Text": "Backed the 2019 Pin Kickstarter Project" } diff --git a/website/common/locales/en/gear.json b/website/common/locales/en/gear.json index fde6b2cded..f1487ae0a5 100644 --- a/website/common/locales/en/gear.json +++ b/website/common/locales/en/gear.json @@ -122,6 +122,8 @@ "weaponSpecialTachiNotes": "This light and curved sword will shred your tasks to ribbons! Increases Strength by <%= str %>.", "weaponSpecialAetherCrystalsText": "Aether Crystals", "weaponSpecialAetherCrystalsNotes": "These bracers and crystals once belonged to the Lost Masterclasser herself. Increases all Stats by <%= attrs %>.", + "weaponSpecialKS2019Text": "Mythic Gryphon Glaive", + "weaponSpecialKS2019Notes": "Curved as a gryphon's beak and talons, this ornate polearm reminds you to power through when the task ahead feels daunting. 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.", @@ -539,6 +541,8 @@ "armorSpecialTurkeyArmorBaseNotes": "Keep your drumsticks warm and cozy in this feathery armor! Confers no benefit.", "armorSpecialTurkeyArmorGildedText": "Gilded Turkey Armor", "armorSpecialTurkeyArmorGildedNotes": "Strut your stuff in this seasonally shiny armor! Confers no benefit.", + "armorSpecialKS2019Text": "Mythic Gryphon Armor", + "armorSpecialKS2019Notes": "Glowing from within like a gryphon's noble heart, this resplendent armor encourages you to take pride in your accomplishments. Increases Constitution by <%= con %>.", "armorSpecialYetiText": "Yeti-Tamer Robe", "armorSpecialYetiNotes": "Fuzzy and fierce. Increases Constitution by <%= con %>. Limited Edition 2013-2014 Winter Gear.", @@ -1067,6 +1071,8 @@ "headSpecialTurkeyHelmGildedNotes": "Gobble gobble! Bling bling! Confers no benefit.", "headSpecialPiDayText": "Pi Hat", "headSpecialPiDayNotes": "Try to balance this slice of delicious pie on your head while walking in a circle. Or throw it at a red Daily! Or you could just eat it. Your choice! Confers no benefit.", + "headSpecialKS2019Text": "Mythic Gryphon Helm", + "headSpecialKS2019Notes": "Adorned with a gryphon's likeness and plumage, this glorious helmet symbolizes the way your skills and bearing stand as an example to others. 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.", @@ -1563,6 +1569,8 @@ "shieldSpecialWakizashiNotes": "This short sword is perfect for close-quarters battles with your Dailies! Increases Constitution by <%= con %>.", "shieldSpecialPiDayText": "Pi Shield", "shieldSpecialPiDayNotes": "We dare you to calculate the ratio of this shield's circumference to its deliciousness! Confers no benefit.", + "shieldSpecialKS2019Text": "Mythic Gryphon Shield", + "shieldSpecialKS2019Notes": "Sparkling like the shell of a gryphon egg, this magnificent shield shows you how to stand ready to help when your own burdens are light. Increases Perception by <%= per %>.", "shieldSpecialYetiText": "Yeti-Tamer Shield", "shieldSpecialYetiNotes": "This shield reflects light from the snow. Increases Constitution by <%= con %>. Limited Edition 2013-2014 Winter Gear.", @@ -2063,6 +2071,8 @@ "eyewearSpecialAetherMaskText": "Aether Mask", "eyewearSpecialAetherMaskNotes": "This mask has a mysterious history. Increases Intelligence by <%= int %>.", + "eyewearSpecialKS2019Text": "Mythic Gryphon Visor", + "eyewearSpecialKS2019Notes": "Bold as a gryphon's... hmm, gryphons don't have visors. It reminds you to... oh, who are we kidding, it just looks cool! Confers no benefit.", "eyewearSpecialSummerRogueText": "Roguish Eyepatch", "eyewearSpecialSummerRogueNotes": "It doesn't take a scallywag to see how stylish this is! Confers no benefit. Limited Edition 2014 Summer Gear.", diff --git a/website/common/locales/en/pets.json b/website/common/locales/en/pets.json index c629c7bd53..40dbe23b58 100644 --- a/website/common/locales/en/pets.json +++ b/website/common/locales/en/pets.json @@ -33,6 +33,7 @@ "hopefulHippogriffMount": "Hopeful Hippogriff", "royalPurpleJackalope": "Royal Purple Jackalope", "invisibleAether": "Invisible Aether", + "gryphatrice": "Gryphatrice", "rarePetPop1": "Click the gold paw to learn more about how you can obtain this rare pet through contributing to Habitica!", "rarePetPop2": "How to Get this Pet!", "potion": "<%= potionType %> Potion", diff --git a/website/common/script/content/achievements.js b/website/common/script/content/achievements.js index 154bf78011..c39939383d 100644 --- a/website/common/script/content/achievements.js +++ b/website/common/script/content/achievements.js @@ -193,6 +193,11 @@ let specialAchievs = { pluralTitleKey: 'helped', pluralTextKey: 'surveysMultiple', }, + kickstarter2019: { + icon: 'achievement-kickstarter2019', + titleKey: 'achievementKickstarter2019', + textKey: 'achievementKickstarter2019Text', + }, }; Object.assign(achievementsData, specialAchievs); diff --git a/website/common/script/content/gear/sets/special/index.js b/website/common/script/content/gear/sets/special/index.js index a53d822fb0..36917da31a 100644 --- a/website/common/script/content/gear/sets/special/index.js +++ b/website/common/script/content/gear/sets/special/index.js @@ -1257,6 +1257,13 @@ let armor = { value: 90, con: 15, }, + ks2019: { + text: t('armorSpecialKS2019Text'), + notes: t('armorSpecialKS2019Notes', { con: 20 }), + value: 0, + con: 20, + canOwn: ownsItem('armor_special_ks2019'), + }, }; let back = { @@ -1602,6 +1609,12 @@ let eyewear = { notes: t('eyewearSpecialFall2019HealerNotes'), value: 20, }, + ks2019: { + text: t('eyewearSpecialKS2019Text'), + notes: t('eyewearSpecialKS2019Notes'), + value: 0, + canOwn: ownsItem('eyewear_special_ks2019'), + }, }; let head = { @@ -2865,6 +2878,13 @@ let head = { value: 60, int: 7, }, + ks2019: { + text: t('headSpecialKS2019Text'), + notes: t('headSpecialKS2019Notes', { int: 20 }), + value: 0, + int: 20, + canOwn: ownsItem('head_special_ks2019'), + }, }; let headAccessory = { @@ -4079,6 +4099,13 @@ let shield = { value: 70, con: 9, }, + ks2019: { + text: t('shieldSpecialKS2019Text'), + notes: t('shieldSpecialKS2019Notes', { per: 20 }), + value: 0, + per: 20, + canOwn: ownsItem('shield_special_ks2019'), + }, }; let weapon = { @@ -5332,6 +5359,13 @@ let weapon = { value: 90, int: 9, }, + ks2019: { + text: t('weaponSpecialKS2019Text'), + notes: t('weaponSpecialKS2019Notes', { str: 20 }), + value: 0, + str: 20, + canOwn: ownsItem('weapon_special_ks2019'), + }, }; let specialSet = { diff --git a/website/common/script/content/stable.js b/website/common/script/content/stable.js index bee75eea94..4ee406ca80 100644 --- a/website/common/script/content/stable.js +++ b/website/common/script/content/stable.js @@ -103,6 +103,7 @@ let specialPets = { 'Hippogriff-Hopeful': 'hopefulHippogriffPet', 'Fox-Veteran': 'veteranFox', 'JackOLantern-Glow': 'glowJackolantern', + 'Gryphon-Gryphatrice': 'gryphatrice', }; let specialMounts = { @@ -121,6 +122,7 @@ let specialMounts = { 'Aether-Invisible': 'invisibleAether', 'JackOLantern-Ghost': 'ghostJackolantern', 'Hippogriff-Hopeful': 'hopefulHippogriffMount', + 'Gryphon-Gryphatrice': 'gryphatrice', }; each(specialPets, (translationString, key) => { diff --git a/website/common/script/libs/achievements.js b/website/common/script/libs/achievements.js index fe386361e6..64ac15b06c 100644 --- a/website/common/script/libs/achievements.js +++ b/website/common/script/libs/achievements.js @@ -300,6 +300,10 @@ function _getSpecialAchievements (user, language) { _addSimple(result, user, {path: 'originalUser', language}); } + if (user.achievements.kickstarter2019) { + _addSimple(result, user, {path: 'kickstarter2019', language}); + } + return result; } diff --git a/website/raw_sprites/spritesmith/achievements/achievement-kickstarter20192x.png b/website/raw_sprites/spritesmith/achievements/achievement-kickstarter20192x.png new file mode 100644 index 0000000000..2e4af4aca6 Binary files /dev/null and b/website/raw_sprites/spritesmith/achievements/achievement-kickstarter20192x.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_armor_special_ks2019.png b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_armor_special_ks2019.png new file mode 100644 index 0000000000..35edc824b3 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_armor_special_ks2019.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_eyewear_special_ks2019.png b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_eyewear_special_ks2019.png new file mode 100644 index 0000000000..c30d7de3ae Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_eyewear_special_ks2019.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_head_special_ks2019.png b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_head_special_ks2019.png new file mode 100644 index 0000000000..19f67bb729 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_head_special_ks2019.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_shield_special_ks2019.png b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_shield_special_ks2019.png new file mode 100644 index 0000000000..15070e03c4 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_shield_special_ks2019.png differ diff --git a/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_weapon_special_ks2019.png b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_weapon_special_ks2019.png new file mode 100644 index 0000000000..d33b4730e0 Binary files /dev/null and b/website/raw_sprites/spritesmith/gear/events/kickstarter/shop_weapon_special_ks2019.png differ diff --git a/website/raw_sprites/spritesmith/stable/mounts/icon/Mount_Icon_Gryphon-Gryphatrice.png b/website/raw_sprites/spritesmith/stable/mounts/icon/Mount_Icon_Gryphon-Gryphatrice.png new file mode 100644 index 0000000000..3f0e1510ea Binary files /dev/null and b/website/raw_sprites/spritesmith/stable/mounts/icon/Mount_Icon_Gryphon-Gryphatrice.png differ diff --git a/website/server/models/user/schema.js b/website/server/models/user/schema.js index ccf2c3e966..00b28f22da 100644 --- a/website/server/models/user/schema.js +++ b/website/server/models/user/schema.js @@ -127,6 +127,7 @@ let schema = new Schema({ allYourBase: Boolean, dustDevil: Boolean, aridAuthority: Boolean, + kickstarter2019: Boolean, }, backer: {