Merge branch 'develop' into KristianTashkov-nvm-in-vagrant-develop
1
.gitignore
vendored
|
|
@ -27,5 +27,6 @@ website/public/docs
|
|||
coverage
|
||||
coverage.html
|
||||
common/dist/scripts/*
|
||||
common/dist/sprites/habitrpg-shared.css
|
||||
|
||||
test/spec/mocks/translations.js
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||
config.vm.provider "virtualbox" do |v|
|
||||
v.memory = 768
|
||||
v.cpus = 1
|
||||
v.customize ["setextradata", :id, "VBoxInternal2/SharedFoldersEnableSymlinksCreate/vagrant", "1"]
|
||||
end
|
||||
config.vm.box = "thepeopleseason/habitrpg"
|
||||
config.ssh.forward_agent = true
|
||||
|
|
|
|||
1
common/dist/sprites/habitrpg-shared.css
vendored
|
|
@ -18,7 +18,7 @@
|
|||
}
|
||||
.2014_Fall_Warrior_PROMO {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -452px -417px;
|
||||
background-position: -306px -402px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -54,7 +54,7 @@
|
|||
}
|
||||
.promo_dilatoryDistress {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -91px -1085px;
|
||||
background-position: -364px -1085px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -78,7 +78,7 @@
|
|||
}
|
||||
.promo_enchanted_armoire_201509 {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -364px -1085px;
|
||||
background-position: -91px -1085px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -90,7 +90,7 @@
|
|||
}
|
||||
.promo_enchanted_armoire_201601 {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -306px -402px;
|
||||
background-position: -452px -417px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -318,13 +318,13 @@
|
|||
}
|
||||
.promo_springclasses2014 {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -648px -724px;
|
||||
background-position: -1119px 0px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
.promo_springclasses2015 {
|
||||
background-image: url(spritesmith-largeSprites-0.png);
|
||||
background-position: -1119px 0px;
|
||||
background-position: -648px -724px;
|
||||
width: 288px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
|
|||
BIN
common/dist/sprites/spritesmith-largeSprites-0.png
vendored
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 213 KiB |
560
common/dist/sprites/spritesmith-main-0.css
vendored
BIN
common/dist/sprites/spritesmith-main-0.png
vendored
|
Before Width: | Height: | Size: 274 KiB After Width: | Height: | Size: 274 KiB |
1760
common/dist/sprites/spritesmith-main-1.css
vendored
BIN
common/dist/sprites/spritesmith-main-1.png
vendored
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
1274
common/dist/sprites/spritesmith-main-10.css
vendored
BIN
common/dist/sprites/spritesmith-main-10.png
vendored
|
Before Width: | Height: | Size: 153 KiB After Width: | Height: | Size: 152 KiB |
1716
common/dist/sprites/spritesmith-main-11.css
vendored
BIN
common/dist/sprites/spritesmith-main-11.png
vendored
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 98 KiB |
1576
common/dist/sprites/spritesmith-main-2.css
vendored
BIN
common/dist/sprites/spritesmith-main-2.png
vendored
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
492
common/dist/sprites/spritesmith-main-3.css
vendored
BIN
common/dist/sprites/spritesmith-main-3.png
vendored
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 142 KiB |
72
common/dist/sprites/spritesmith-main-4.css
vendored
|
|
@ -858,13 +858,13 @@
|
|||
}
|
||||
.headAccessory_mystery_301405 {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -910px -1061px;
|
||||
background-position: -1001px -1061px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_mystery_301405 {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1001px -1061px;
|
||||
background-position: -910px -1061px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -882,13 +882,13 @@
|
|||
}
|
||||
.shop_headAccessory_mystery_301405 {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1066px -1516px;
|
||||
background-position: -1025px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_mystery_301405 {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1025px -1516px;
|
||||
background-position: -1066px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
@ -948,97 +948,97 @@
|
|||
}
|
||||
.headAccessory_special_spring2015Healer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: 0px -1152px;
|
||||
background-position: -728px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_spring2015Mage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -91px -1152px;
|
||||
background-position: -819px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_spring2015Rogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -182px -1152px;
|
||||
background-position: -910px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_spring2015Warrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -273px -1152px;
|
||||
background-position: -1001px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_springHealer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -364px -1152px;
|
||||
background-position: -1092px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_springMage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -455px -1152px;
|
||||
background-position: -1183px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_springRogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -546px -1152px;
|
||||
background-position: -1300px 0px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.headAccessory_special_springWarrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -637px -1152px;
|
||||
background-position: -1300px -91px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_spring2015Healer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -728px -1152px;
|
||||
background-position: 0px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_spring2015Mage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -819px -1152px;
|
||||
background-position: -91px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_spring2015Rogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -910px -1152px;
|
||||
background-position: -182px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_spring2015Warrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1001px -1152px;
|
||||
background-position: -273px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_springHealer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1092px -1152px;
|
||||
background-position: -364px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_springMage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1183px -1152px;
|
||||
background-position: -455px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_springRogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1300px 0px;
|
||||
background-position: -546px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
.head_special_springWarrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -1300px -91px;
|
||||
background-position: -637px -1152px;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
}
|
||||
|
|
@ -1128,97 +1128,97 @@
|
|||
}
|
||||
.shop_headAccessory_special_spring2015Healer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -615px -1516px;
|
||||
background-position: -287px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_spring2015Mage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -574px -1516px;
|
||||
background-position: -246px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_spring2015Rogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -533px -1516px;
|
||||
background-position: -205px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_spring2015Warrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -492px -1516px;
|
||||
background-position: -164px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_springHealer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -451px -1516px;
|
||||
background-position: -123px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_springMage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -410px -1516px;
|
||||
background-position: -82px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_springRogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -369px -1516px;
|
||||
background-position: -41px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_headAccessory_special_springWarrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -328px -1516px;
|
||||
background-position: 0px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_spring2015Healer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -287px -1516px;
|
||||
background-position: -615px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_spring2015Mage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -246px -1516px;
|
||||
background-position: -574px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_spring2015Rogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -205px -1516px;
|
||||
background-position: -533px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_spring2015Warrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -164px -1516px;
|
||||
background-position: -492px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_springHealer {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -123px -1516px;
|
||||
background-position: -451px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_springMage {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -82px -1516px;
|
||||
background-position: -410px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_springRogue {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: -41px -1516px;
|
||||
background-position: -369px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
.shop_head_special_springWarrior {
|
||||
background-image: url(spritesmith-main-4.png);
|
||||
background-position: 0px -1516px;
|
||||
background-position: -328px -1516px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
|
|
|||
BIN
common/dist/sprites/spritesmith-main-4.png
vendored
|
Before Width: | Height: | Size: 129 KiB After Width: | Height: | Size: 129 KiB |
500
common/dist/sprites/spritesmith-main-5.css
vendored
BIN
common/dist/sprites/spritesmith-main-5.png
vendored
|
Before Width: | Height: | Size: 374 KiB After Width: | Height: | Size: 370 KiB |
614
common/dist/sprites/spritesmith-main-6.css
vendored
BIN
common/dist/sprites/spritesmith-main-6.png
vendored
|
Before Width: | Height: | Size: 175 KiB After Width: | Height: | Size: 175 KiB |
648
common/dist/sprites/spritesmith-main-7.css
vendored
BIN
common/dist/sprites/spritesmith-main-7.png
vendored
|
Before Width: | Height: | Size: 142 KiB After Width: | Height: | Size: 134 KiB |
978
common/dist/sprites/spritesmith-main-8.css
vendored
BIN
common/dist/sprites/spritesmith-main-8.png
vendored
|
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 145 KiB |
1000
common/dist/sprites/spritesmith-main-9.css
vendored
BIN
common/dist/sprites/spritesmith-main-9.png
vendored
|
Before Width: | Height: | Size: 144 KiB After Width: | Height: | Size: 142 KiB |
|
After Width: | Height: | Size: 22 KiB |
BIN
common/img/sprites/spritesmith/quests/quest_sabretooth.png
Normal file
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 436 B |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
After Width: | Height: | Size: 3.6 KiB |
|
After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.2 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
|
After Width: | Height: | Size: 3.5 KiB |
|
|
@ -125,8 +125,9 @@
|
|||
"mageText": "Mages learn swiftly, gaining Experience and Levels faster than other classes. They also get a great deal of Mana for using special abilities. Play a Mage if you enjoy the tactical game aspects of Habit, or if you are strongly motivated by leveling up and unlocking advanced features!",
|
||||
"rogueText": "Rogues love to accumulate wealth, gaining more Gold than anyone else, and are adept at finding random items. Their iconic Stealth ability lets them duck the consequences of missed Dailies. Play a Rogue if you find strong motivation from Rewards and Achievements, striving for loot and badges!",
|
||||
"healerText": "Healers stand impervious against harm, and extend that protection to others. Missed Dailies and bad Habits don't faze them much, and they have ways to recover Health from failure. Play a Healer if you enjoy assisting others in your Party, or if the idea of cheating Death through hard work inspires you!",
|
||||
"optOut": "Opt Out",
|
||||
"optOutText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
|
||||
"optOutOfClasses": "Opt Out",
|
||||
"optOutOfPMs": "Opt Out",
|
||||
"optOutOfClassesText": "Can't be bothered with classes? Want to choose later? Opt out - you'll be a warrior with no special abilities. You can read about the class system later on the wiki and enable classes at any time under User -> Stats.",
|
||||
"select": "Select",
|
||||
"stealth": "Stealth",
|
||||
"stealthNewDay": "When a new day begins, you will avoid damage from this many missed Dailies.",
|
||||
|
|
|
|||
|
|
@ -147,6 +147,10 @@
|
|||
"questEggUnicornMountText": "Winged Unicorn",
|
||||
"questEggUnicornAdjective": "a magical",
|
||||
|
||||
"questEggSabretoothText": "Sabretooth Tiger",
|
||||
"questEggSabretoothMountText": "Sabretooth Tiger",
|
||||
"questEggSabretoothAdjective": "a ferocious",
|
||||
|
||||
"eggNotes": "Find a hatching potion to pour on this egg, and it will hatch into <%= eggAdjective(locale) %> <%= eggText(locale) %>.",
|
||||
|
||||
"hatchingPotionBase": "Base",
|
||||
|
|
|
|||
13
common/locales/en/merch.json
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"merch" : "Merchandise",
|
||||
|
||||
"merch-teespring-summary" : "Teespring is a platform that makes it easy for anyone to create and sell high-quality products people love, with no cost or risk.",
|
||||
"merch-teespring-goto" : "Get a Habitica T-shirt",
|
||||
|
||||
"merch-teespring-eu-summary" : "EUROPEAN VERSION : Teespring is a platform that makes it easy for anyone to create and sell high-quality products people love, with no cost or risk.",
|
||||
"merch-teespring-eu-goto" : "Get a Habitica T-shirt (EU)",
|
||||
|
||||
"merch-stickermule-summary" : "Stick proud Melior wherever you (or someone else) need a reminder of both present and future accomplishments!",
|
||||
"merch-stickermule-goto" : "Get Habitica stickers"
|
||||
|
||||
}
|
||||
|
|
@ -16,11 +16,12 @@
|
|||
"displayPotionForGold": "Do you want to sell a <strong><%= itemType %> Potion</strong>?",
|
||||
"sellForGold": "Sell it for <%= gold %> Gold",
|
||||
"buyGems": "Buy Gems",
|
||||
"purchaseGems": "Purchase Gems",
|
||||
"justin": "Justin",
|
||||
"ian": "Ian",
|
||||
"ianText": "Welcome to the Quest Shop! Here you can use Quest Scrolls to battle monsters with your friends. Be sure to check out our fine array of Quest Scrolls for purchase on the right!",
|
||||
"ianBrokenText": "Welcome to the Quest Shop... Here you can use Quest Scrolls to battle monsters with your friends... Be sure to check out our fine array of Quest Scrolls for purchase on the right...",
|
||||
"USD": "USD",
|
||||
"USD": "(USD)",
|
||||
"newStuff": "New Stuff",
|
||||
"cool": "Tell Me Later",
|
||||
"dismissAlert": "Dismiss This Alert",
|
||||
|
|
@ -30,9 +31,9 @@
|
|||
"donationDesc": "20 Gems, Donation to Habitica",
|
||||
"payWithCard": "Pay with Card",
|
||||
"payNote": "Note: PayPal sometimes takes a long time to clear. We recommend paying with card.",
|
||||
"card": "Card",
|
||||
"card": "Credit Card (using Stripe)",
|
||||
"amazonInstructions": "Click the button to pay using Amazon Payments",
|
||||
"paymentMethods": "Payment Methods:",
|
||||
"paymentMethods": "Purchase using",
|
||||
|
||||
"classGear": "Class Gear",
|
||||
"classGearText": "First: don't panic! Your old gear is in your inventory, and you're now wearing your apprentice <strong><%= klass %></strong> equipment. Wearing your class's gear grants you a 50% bonus to stats. However, feel free to switch back to your old gear.",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"questEvilSantaText": "Trapper Santa",
|
||||
"questEvilSantaNotes": "You hear moaning roars deep in the icefields. You follow the roars and growls - punctuated by another voice's cackling - to a clearing in the woods where you see a fully-grown polar bear. She's caged and shackled, roaring for life. Dancing atop the cage is a malicious little imp wearing castaway Christmas costumes. Vanquish Trapper Santa, and save the beast!",
|
||||
"questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. A grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the whisperer listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured. Help her find her baby!",
|
||||
"questEvilSantaNotes": "You hear agonized roars deep in the icefields. You follow the growls - punctuated by the sound of cackling - to a clearing in the woods, where you see a fully-grown polar bear. She's caged and shackled, fighting for her life. Dancing atop the cage is a malicious little imp wearing a castaway costume. Vanquish Trapper Santa, and save the beast!",
|
||||
"questEvilSantaCompletion": "Trapper Santa squeals in anger, and bounces off into the night. The grateful she-bear, through roars and growls, tries to tell you something. You take her back to the stables, where Matt Boch the Beast Master listens to her tale with a gasp of horror. She has a cub! He ran off into the icefields when mama bear was captured.",
|
||||
"questEvilSantaBoss": "Trapper Santa",
|
||||
"questEvilSantaDropBearCubPolarMount": "Polar Bear (Mount)",
|
||||
|
||||
"questEvilSanta2Text": "Find The Cub",
|
||||
"questEvilSanta2Notes": "Mama bear's cub had run off into the icefields when she was captured by the trapper. At the edge of the woods, she sniffs the air. You hear twig-snaps and snow crunch through the crystaline sound of the forest. Paw prints! You both start racing to follow the trail. Find all the prints and broken twigs, and retrieve her cub!",
|
||||
"questEvilSanta2Completion": "You've found the cub! Mama and baby bear couldn't be more grateful. As a token, they've decided to keep you company till the end of days.",
|
||||
"questEvilSanta2Notes": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystaline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!",
|
||||
"questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
|
||||
"questEvilSanta2CollectTracks": "Tracks",
|
||||
"questEvilSanta2CollectBranches": "Broken Twigs",
|
||||
"questEvilSanta2DropBearCubPolarPet": "Polar Bear (Pet)",
|
||||
|
|
@ -325,5 +325,12 @@
|
|||
"questUnicornCompletion": "Impressed by your diligence and strength, the Unicorn Queen at last agrees that your cause is worthy. She allows you to ride on her back as she soars to the source of Conquest Creek. As she lowers her golden horn to the befouled waters, a brilliant blue light rises from the water’s surface. It is so blinding that you are forced to close your eyes. When you open them a moment later, the unicorn is gone. However, @rosiesully lets out a cry of delight: the water is now clear, and three shining eggs rest at the creek’s edge.",
|
||||
"questUnicornBoss": "The Unicorn Queen",
|
||||
"questUnicornDropUnicornEgg": "Unicorn (Egg)",
|
||||
"questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market"
|
||||
"questUnicornUnlockText": "Unlocks purchasable Unicorn eggs in the Market",
|
||||
|
||||
"questSabretoothText": "The Sabre Cat",
|
||||
"questSabretoothNotes": "A roaring monster is terrorizing Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. It's been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @Inventrix and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoikalm Steppes. \"It was perfectly friendly at first – I don't know what happened. Please, you have to help us recapture it! Only a champion of Habitica can subdue this prehistoric beast!\"",
|
||||
"questSabretoothCompletion": "After a long and tiring battle, you wrestle the Zombie Sabre Cat to the ground. As you are finally able to approach, you notice a nasty cavity in one of its sabre teeth. Realising the true cause of the cat's wrath, you're able to get the cavity filled by @Fandekasp, and advise everyone to avoid feeding their friend sweets in future. The Sabre Cat flourishes, and in gratitude, its tamers send you a generous reward – a clutch of sabretooth eggs!",
|
||||
"questSabretoothBoss": "Zombie Sabre Cat",
|
||||
"questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
|
||||
"questSabretoothUnlockText": "Unlocks purchasable Sabretooth eggs in the Market"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
"newTaskEditPop": "With this option set, new tasks will immediately open for you to add details like notes and tags.",
|
||||
"dailyDueDefaultView": "Set Dailies default to 'due' tab",
|
||||
"dailyDueDefaultViewPop": "With this option set, the Dailies tasks will default to 'due' instead of 'all'",
|
||||
"reverseChatOrder": "Show chat messages in reverse order",
|
||||
"startCollapsed": "Tag list in tasks starts collapsed",
|
||||
"startCollapsedPop": "With this option set, the list of task tags will be hidden when you first open a task for editing.",
|
||||
"startAdvCollapsed": "Advanced Options in tasks start collapsed",
|
||||
|
|
@ -106,6 +107,7 @@
|
|||
"invitedQuest": "Invited to Quest",
|
||||
"kickedGroup": "Kicked from group",
|
||||
"remindersToLogin": "Reminders to check in to Habitica",
|
||||
"subscribeUsing": "Subscribe using",
|
||||
"unsubscribedSuccessfully": "Unsubscribed successfully!",
|
||||
"unsubscribedTextUsers": "You have successfully unsubscribed from all Habitica emails. You can enable only the emails you want to receive from <a href=\"/#/options/settings/notifications\">the settings</a> (requires login).",
|
||||
"unsubscribedTextOthers": "You won't receive any other email from Habitica.",
|
||||
|
|
@ -113,6 +115,7 @@
|
|||
"unsubscribeAllEmailsText": "By checking this box, I certify that I understand that by unsubscribing from all emails, Habitica will never be able to notify me via email about important changes to the site or my account.",
|
||||
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
|
||||
"subscriptionRateText": "Recurring $<%= price %> USD every <%= months %> months",
|
||||
"recurringText": "recurring",
|
||||
"benefits": "Benefits",
|
||||
"coupon": "Coupon",
|
||||
"couponPlaceholder": "Enter Coupon Code",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"retainHistoryText": "Makes your full history available in graphs and export. Non-subscriber histories get consolidated for database optimization.",
|
||||
"doubleDrops": "Daily drop-caps doubled",
|
||||
"doubleDropsText": "Complete your stable faster!",
|
||||
"mysteryItem": "Unique Monthly Subscription Items",
|
||||
"mysteryItem": "Exclusive Monthly Items",
|
||||
"mysteryItemText": "Each month you will receive a unique cosmetic item for your avatar! Plus, for every three months of consecutive subscription, the Mysterious Time Travelers will grant you access to historic (and futuristic!) cosmetic items.",
|
||||
"supportDevs": "Supports the developers",
|
||||
"supportDevsText": "Your subscription helps keep Habitica thriving and helps fund the development of new features. Thank you for your generosity!",
|
||||
|
|
@ -28,6 +28,7 @@
|
|||
"subscribed": "Subscribed",
|
||||
"manageSub": "Click to manage subscription",
|
||||
"cancelSub": "Cancel Subscription",
|
||||
"canceledSubscription": "Canceled Subscription",
|
||||
"adminSub": "Administrator Subscriptions",
|
||||
"morePlans": "More Plans<br/>Coming Soon",
|
||||
"organizationSub": "Private Organization",
|
||||
|
|
@ -54,8 +55,16 @@
|
|||
"checkout": "Checkout",
|
||||
"sureCancelSub": "Are you sure you want to cancel your subscription?",
|
||||
"subCanceled": "Subscription will become inactive on",
|
||||
"buyGemsGoldTitle": "To Buy Gems with Gold",
|
||||
"becomeSubscriber": "Become a Subscriber",
|
||||
"subGemPop": "Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold. You can see how many Gems are available to buy at the corner of the Gem icon.",
|
||||
"subGemName": "Subscriber Gems",
|
||||
"freeGemsTitle": "Obtain Gems for Free",
|
||||
"maxBuyGems": "You have bought all the gems you can this month. Thanks for subscribing!",
|
||||
"buyGemsAllow1": "You can buy",
|
||||
"buyGemsAllow2": "more gems this month",
|
||||
"purchaseGemsSeparately": "Purchase Additional Gems",
|
||||
"subFreeGemsHow": "Habitica players can earn gems for free by winning <a href=\"/#/options/groups/challenges\">challenges</a> that award gems as a prize, or as a <a href=\"http://habitica.wikia.com/wiki/Contributing_to_Habitica\">contributor reward by helping the development of Habitica.</a>",
|
||||
"timeTravelers": "Time Travelers",
|
||||
"timeTravelersTitleNoSub": "<%= linkStartTyler %>Tyler<%= linkEnd %> and <%= linkStartVicky %>Vicky<%= linkEnd %>",
|
||||
"timeTravelersTitle": "Mysterious Time Travelers",
|
||||
|
|
|
|||
|
|
@ -932,6 +932,13 @@ api.questEggs = {
|
|||
return u.achievements.quests && (u.achievements.quests.unicorn != null) > 0;
|
||||
})
|
||||
},
|
||||
Sabretooth: {
|
||||
text: t('questEggSabretoothText'),
|
||||
adjective: t('questEggSabretoothAdjective'),
|
||||
canBuy: (function(u) {
|
||||
return u.achievements.quests && (u.achievements.quests.sabretooth != null) > 0;
|
||||
})
|
||||
},
|
||||
};
|
||||
|
||||
_.each(api.questEggs, function(egg, key) {
|
||||
|
|
@ -1623,7 +1630,6 @@ api.quests = {
|
|||
notes: t('questEvilSanta2Notes'),
|
||||
completion: t('questEvilSanta2Completion'),
|
||||
value: 4,
|
||||
previous: 'evilsanta',
|
||||
category: 'pet',
|
||||
collect: {
|
||||
tracks: {
|
||||
|
|
@ -2982,6 +2988,38 @@ api.quests = {
|
|||
unlock: t('questUnicornUnlockText')
|
||||
}
|
||||
},
|
||||
sabretooth: {
|
||||
text: t('questSabretoothText'),
|
||||
notes: t('questSabretoothNotes'),
|
||||
completion: t('questSabretoothCompletion'),
|
||||
value: 4,
|
||||
category: 'pet',
|
||||
boss: {
|
||||
name: t('questSabretoothBoss'),
|
||||
hp: 1000,
|
||||
str: 2
|
||||
},
|
||||
drop: {
|
||||
items: [
|
||||
{
|
||||
type: 'eggs',
|
||||
key: 'Sabretooth',
|
||||
text: t('questSabretoothDropSabretoothEgg')
|
||||
}, {
|
||||
type: 'eggs',
|
||||
key: 'Sabretooth',
|
||||
text: t('questSabretoothDropSabretoothEgg')
|
||||
}, {
|
||||
type: 'eggs',
|
||||
key: 'Sabretooth',
|
||||
text: t('questSabretoothDropSabretoothEgg')
|
||||
}
|
||||
],
|
||||
gp: 67,
|
||||
exp: 650,
|
||||
unlock: t('questSabretoothUnlockText')
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
_.each(api.quests, function(v, key) {
|
||||
|
|
|
|||
|
|
@ -7,10 +7,9 @@ import {
|
|||
MAX_LEVEL,
|
||||
MAX_STAT_POINTS,
|
||||
} from './constants';
|
||||
import { preenHistory, preenUserHistory } from './preenUserHistory';
|
||||
import * as statHelpers from './statHelpers';
|
||||
|
||||
var $w, _, api, content, i18n, moment, sortOrder,
|
||||
var $w, _, api, content, i18n, preenHistory, moment, sortOrder,
|
||||
indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
||||
moment = require('moment');
|
||||
|
|
@ -32,8 +31,6 @@ api.maxHealth = MAX_HEALTH;
|
|||
api.tnl = statHelpers.toNextLevel;
|
||||
api.diminishingReturns = statHelpers.diminishingReturns;
|
||||
|
||||
api.preenHistory = preenHistory;
|
||||
|
||||
$w = api.$w = function(s) {
|
||||
return s.split(' ');
|
||||
};
|
||||
|
|
@ -82,6 +79,47 @@ api.planGemLimits = {
|
|||
convCap: 25
|
||||
};
|
||||
|
||||
/*
|
||||
Preen history for users with > 7 history entries
|
||||
This takes an infinite array of single day entries [day day day day day...], and turns it into a condensed array
|
||||
of averages, condensing more the further back in time we go. Eg, 7 entries each for last 7 days; 1 entry each week
|
||||
of this month; 1 entry for each month of this year; 1 entry per previous year: [day*7 week*4 month*12 year*infinite]
|
||||
*/
|
||||
|
||||
preenHistory = function(history) {
|
||||
var newHistory, preen, thisMonth;
|
||||
history = _.filter(history, function(h) {
|
||||
return !!h;
|
||||
});
|
||||
newHistory = [];
|
||||
preen = function(amount, groupBy) {
|
||||
var groups;
|
||||
groups = _.chain(history).groupBy(function(h) {
|
||||
return moment(h.date).format(groupBy);
|
||||
}).sortBy(function(h, k) {
|
||||
return k;
|
||||
}).value();
|
||||
groups = groups.slice(-amount);
|
||||
groups.pop();
|
||||
return _.each(groups, function(group) {
|
||||
newHistory.push({
|
||||
date: moment(group[0].date).toDate(),
|
||||
value: _.reduce(group, (function(m, obj) {
|
||||
return m + obj.value;
|
||||
}), 0) / group.length
|
||||
});
|
||||
return true;
|
||||
});
|
||||
};
|
||||
preen(50, "YYYY");
|
||||
preen(moment().format('MM'), "YYYYMM");
|
||||
thisMonth = moment().format('YYYYMM');
|
||||
newHistory = newHistory.concat(_.filter(history, function(h) {
|
||||
return moment(h.date).format('YYYYMM') === thisMonth;
|
||||
}));
|
||||
return newHistory;
|
||||
};
|
||||
|
||||
/*
|
||||
Preen 3-day past-completed To-Dos from Angular & mobile app
|
||||
*/
|
||||
|
|
@ -2547,7 +2585,15 @@ api.wrap = function(user, main) {
|
|||
date: now,
|
||||
value: expTally
|
||||
});
|
||||
preenUserHistory(user);
|
||||
if (!((ref1 = user.purchased) != null ? (ref2 = ref1.plan) != null ? ref2.customerId : void 0 : void 0)) {
|
||||
user.fns.preenUserHistory();
|
||||
if (typeof user.markModified === "function") {
|
||||
user.markModified('history');
|
||||
}
|
||||
if (typeof user.markModified === "function") {
|
||||
user.markModified('dailys');
|
||||
}
|
||||
}
|
||||
user.stats.buffs = perfect ? ((base3 = user.achievements).perfect != null ? base3.perfect : base3.perfect = 0, user.achievements.perfect++, lvlDiv2 = Math.ceil(api.capByLevel(user.stats.lvl) / 2), {
|
||||
str: lvlDiv2,
|
||||
int: lvlDiv2,
|
||||
|
|
@ -2592,6 +2638,28 @@ api.wrap = function(user, main) {
|
|||
}
|
||||
return _progress;
|
||||
},
|
||||
preenUserHistory: function(minHistLen) {
|
||||
if (minHistLen == null) {
|
||||
minHistLen = 7;
|
||||
}
|
||||
_.each(user.habits.concat(user.dailys), function(task) {
|
||||
var ref;
|
||||
if (((ref = task.history) != null ? ref.length : void 0) > minHistLen) {
|
||||
task.history = preenHistory(task.history);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
_.defaults(user.history, {
|
||||
todos: [],
|
||||
exp: []
|
||||
});
|
||||
if (user.history.exp.length > minHistLen) {
|
||||
user.history.exp = preenHistory(user.history.exp);
|
||||
}
|
||||
if (user.history.todos.length > minHistLen) {
|
||||
return user.history.todos = preenHistory(user.history.todos);
|
||||
}
|
||||
},
|
||||
ultimateGear: function() {
|
||||
var base, owned;
|
||||
owned = typeof window !== "undefined" && window !== null ? user.items.gear.owned : user.items.gear.owned.toObject();
|
||||
|
|
|
|||
|
|
@ -1,88 +0,0 @@
|
|||
import _ from 'lodash';
|
||||
import moment from 'moment';
|
||||
|
||||
// Aggregate entries
|
||||
function _aggregate (history, aggregateBy) {
|
||||
return _.chain(history)
|
||||
.groupBy(entry => { // group entries by aggregateBy
|
||||
return moment(entry.date).format(aggregateBy);
|
||||
})
|
||||
.sortBy((entry, key) => key) // sort by date
|
||||
.map(entries => {
|
||||
return {
|
||||
date: Number(entries[0].date),
|
||||
value: _.reduce(entries, (previousValue, entry) => {
|
||||
return previousValue + entry.value;
|
||||
}, 0) / entries.length,
|
||||
};
|
||||
})
|
||||
.value();
|
||||
}
|
||||
|
||||
/* Preen an array of history entries
|
||||
|
||||
Free users:
|
||||
- 1 value for each day of the past 60 days (no compression)
|
||||
- 1 value each month for the previous 10 months
|
||||
- 1 value each year for the previous years
|
||||
|
||||
Subscribers and challenges:
|
||||
- 1 value for each day of the past 365 days (no compression)
|
||||
- 1 value each month for the previous 12 months
|
||||
- 1 value each year for the previous years
|
||||
*/
|
||||
export function preenHistory (history, isSubscribed, timezoneOffset) {
|
||||
history = _.filter(history, historyEntry => Boolean(historyEntry)); // Filter missing entries
|
||||
let now = timezoneOffset ? moment().zone(timezoneOffset) : moment();
|
||||
// Date after which to begin compressing data
|
||||
let cutOff = now.subtract(isSubscribed ? 365 : 60, 'days').startOf('day');
|
||||
|
||||
// Keep uncompressed entries (modifies history)
|
||||
let newHistory = _.remove(history, entry => {
|
||||
let date = moment(entry.date);
|
||||
return date.isSame(cutOff) || date.isAfter(cutOff);
|
||||
});
|
||||
|
||||
// Date after which to begin compressing data by year
|
||||
let monthsCutOff = cutOff.subtract(isSubscribed ? 12 : 10, 'months').startOf('day');
|
||||
let aggregateByMonth = _.remove(history, entry => {
|
||||
let date = moment(entry.date);
|
||||
return date.isSame(monthsCutOff) || date.isAfter(monthsCutOff);
|
||||
});
|
||||
// Aggregate remaining entries by month and year
|
||||
if (aggregateByMonth.length > 0) newHistory.unshift(..._aggregate(aggregateByMonth, 'YYYYMM'));
|
||||
if (history.length > 0) newHistory.unshift(..._aggregate(history, 'YYYY'));
|
||||
|
||||
return newHistory;
|
||||
}
|
||||
|
||||
// Preen history for users and tasks. This code runs only on the server.
|
||||
export function preenUserHistory (user) {
|
||||
let isSubscribed = user.purchased && user.purchased.plan && user.purchased.plan.customerId;
|
||||
let minHistoryLength = isSubscribed ? 365 : 60;
|
||||
|
||||
function _processTask (task, index) {
|
||||
if (task.history && task.history.length > minHistoryLength) {
|
||||
task.history = preenHistory(task.history, isSubscribed, user.preferences.timezoneOffset);
|
||||
if (user.markModified) user.markModified(`${task.type}s.${index}.history`);
|
||||
}
|
||||
}
|
||||
|
||||
_.each(user.habits, _processTask);
|
||||
_.each(user.dailys, _processTask);
|
||||
|
||||
_.defaults(user.history, {
|
||||
todos: [],
|
||||
exp: [],
|
||||
});
|
||||
|
||||
if (user.history.exp.length > minHistoryLength) {
|
||||
user.history.exp = preenHistory(user.history.exp, isSubscribed, user.preferences.timezoneOffset);
|
||||
user.markModified('history.exp');
|
||||
}
|
||||
|
||||
if (user.history.todos.length > minHistoryLength) {
|
||||
user.history.todos = preenHistory(user.history.todos, isSubscribed, user.preferences.timezoneOffset);
|
||||
user.markModified('history.todos');
|
||||
}
|
||||
}
|
||||
|
|
@ -30,7 +30,6 @@ let holidayQuests = {
|
|||
text: t('questEvilSanta2Text'),
|
||||
notes: t('questEvilSanta2Notes'),
|
||||
completion: t('questEvilSanta2Completion'),
|
||||
previous: 'evilsanta',
|
||||
collect: {
|
||||
tracks: {
|
||||
text: t('questEvilSanta2CollectTracks'),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,106 @@
|
|||
var migrationName = '20160111_challenges_condense_same_day_history_entries.js';
|
||||
|
||||
/*
|
||||
* Compress challenges tasks history entries so that only one entry per day is kept
|
||||
*/
|
||||
|
||||
var dbserver = '';
|
||||
var dbname = '';
|
||||
|
||||
var mongo = require('mongoskin');
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
|
||||
var dbChallenges = mongo.db(dbserver + '/' + dbname + '?auto_reconnect').collection('challenges');
|
||||
|
||||
// Find all challenges
|
||||
var query = {
|
||||
};
|
||||
|
||||
// we only want habits and dailies (rewards and todos don't have history)
|
||||
var fields = {
|
||||
'habits': 1,
|
||||
'dailys': 1,
|
||||
};
|
||||
|
||||
function compressEntries (history) {
|
||||
return _.chain(history)
|
||||
.filter(function(entry) {
|
||||
return !!entry;
|
||||
})
|
||||
.groupBy(function(entry) { // group by day
|
||||
return moment(entry.date).format('YYYYMMDD');
|
||||
})
|
||||
.sortBy(function(entry, key) { // sort by date and transform back to array of array of entries
|
||||
return key;
|
||||
})
|
||||
.map(function(entries) { // aggregate the value
|
||||
return {
|
||||
date: Number(entries[0].date),
|
||||
value: _.reduce(entries, function (previousValue, entry) {
|
||||
return previousValue + entry.value;
|
||||
}, 0) / entries.length,
|
||||
};
|
||||
})
|
||||
.value();
|
||||
};
|
||||
|
||||
console.warn('Updating challenges...');
|
||||
var progressCount = 100;
|
||||
var count = 0;
|
||||
|
||||
dbChallenges.findEach(query, fields, {batchSize: 250}, function(err, challenge) {
|
||||
if (err) { return exiting(1, 'ERROR! ' + err); }
|
||||
if (!challenge) {
|
||||
console.warn('All appropriate challenges found.');
|
||||
return displayData();
|
||||
}
|
||||
count++;
|
||||
|
||||
// specify challenge data to change:
|
||||
var set = {};
|
||||
|
||||
if (challenge.habits && challenge.habits.length > 0) {
|
||||
challenge.habits.forEach(function(habit, index) {
|
||||
if (habit.history && habit.history.length > 1) {
|
||||
var originalL = habit.history.length;
|
||||
habit.history = compressEntries(habit.history);
|
||||
if (originalL > 1000) console.log(originalL, habit.history.length);
|
||||
set['habits.' + index + '.history'] = habit.history;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (challenge.dailys && challenge.dailys.length > 0) {
|
||||
challenge.dailys.forEach(function(daily, index) {
|
||||
if (daily.history && daily.history.length > 1) {
|
||||
var originalL = daily.history.length;
|
||||
daily.history = compressEntries(daily.history);
|
||||
if (originalL > 1000) console.log(originalL, daily.history.length);
|
||||
set['dailys.' + index + '.history'] = daily.history;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
dbChallenges.update({_id: challenge._id}, {$set: set}, function(err) {
|
||||
if(err) throw err;
|
||||
console.log('updated a challenge');
|
||||
});
|
||||
if (count%progressCount == 0) console.warn(count + ' ' + challenge._id);
|
||||
});
|
||||
|
||||
function displayData() {
|
||||
console.warn('\n' + count + ' challenges 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);
|
||||
}
|
||||
34
test/api/v2/challenges/GET-challenges_id.test.js
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import {
|
||||
createAndPopulateGroup,
|
||||
generateChallenge,
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /challenges/:id', () => {
|
||||
context('Member of a challenge', () => {
|
||||
let leader, party, challenge;
|
||||
|
||||
before(async () => {
|
||||
let {
|
||||
group,
|
||||
groupLeader,
|
||||
} = await createAndPopulateGroup();
|
||||
|
||||
party = group;
|
||||
leader = groupLeader;
|
||||
challenge = await generateChallenge(leader, party, {
|
||||
name: 'a created challenge',
|
||||
shortName: 'aCreatedChallenge',
|
||||
description: 'a description for the challenge',
|
||||
});
|
||||
});
|
||||
|
||||
it('returns the challenge object', async () => {
|
||||
let fetchedChallenge = await leader.get(`/challenges/${challenge._id}`);
|
||||
|
||||
expect(fetchedChallenge.name).to.eql(challenge.name);
|
||||
expect(fetchedChallenge.shortName).to.eql(challenge.shortName);
|
||||
expect(fetchedChallenge.description).to.eql(challenge.description);
|
||||
expect(fetchedChallenge.members).to.have.a.lengthOf(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -2,7 +2,7 @@ import {
|
|||
generateGroup,
|
||||
generateUser,
|
||||
resetHabiticaDB,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /groups', () => {
|
||||
const NUMBER_OF_PUBLIC_GUILDS = 3;
|
||||
|
|
@ -22,6 +22,7 @@ describe('GET /groups', () => {
|
|||
name: 'public guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
}, {
|
||||
members: [leader._id, user._id],
|
||||
});
|
||||
|
||||
|
|
@ -35,6 +36,7 @@ describe('GET /groups', () => {
|
|||
name: 'private guild - is member',
|
||||
type: 'guild',
|
||||
privacy: 'private',
|
||||
}, {
|
||||
members: [leader._id, user._id],
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import {
|
||||
find,
|
||||
each,
|
||||
|
|
@ -24,7 +24,7 @@ describe('GET /groups/:id', () => {
|
|||
groupDetails,
|
||||
});
|
||||
|
||||
leader = groupData.leader;
|
||||
leader = groupData.groupLeader;
|
||||
member = groupData.members[0];
|
||||
createdGroup = groupData.group;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
generateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups', () => {
|
||||
context('All groups', () => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
generateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id', () => {
|
||||
context('user is not the leader of the group', () => {
|
||||
|
|
@ -58,10 +58,10 @@ describe('POST /groups/:id', () => {
|
|||
description: 'New group description',
|
||||
});
|
||||
|
||||
let group = await user.get(`/groups/${usersGroup._id}`);
|
||||
await usersGroup.sync();
|
||||
|
||||
expect(group.name).to.eql('New Group Title');
|
||||
expect(group.description).to.eql('New group description');
|
||||
expect(usersGroup.name).to.eql('New Group Title');
|
||||
expect(usersGroup.description).to.eql('New group description');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@ import {
|
|||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
import { each, find } from 'lodash';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import { each } from 'lodash';
|
||||
|
||||
describe('POST /groups/:id/join', () => {
|
||||
context('user is already a member of the group', () => {
|
||||
|
|
@ -30,10 +30,9 @@ describe('POST /groups/:id/join', () => {
|
|||
it(`allows user to join a ${groupType}`, async () => {
|
||||
await invitee.post(`/groups/${group._id}/join`);
|
||||
|
||||
let members = (await invitee.get(`/groups/${group._id}`)).members;
|
||||
let userInGroup = find(members, '_id', invitee._id);
|
||||
await group.sync();
|
||||
|
||||
expect(userInGroup).to.exist;
|
||||
expect(group.members).to.include(invitee._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -79,10 +78,9 @@ describe('POST /groups/:id/join', () => {
|
|||
it('allows user to join a public guild', async () => {
|
||||
await user.post(`/groups/${group._id}/join`);
|
||||
|
||||
let members = (await user.get(`/groups/${group._id}`)).members;
|
||||
let userInGroup = find(members, '_id', user._id);
|
||||
await group.sync();
|
||||
|
||||
expect(userInGroup).to.exist;
|
||||
expect(group.members).to.include(user._id);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -98,14 +96,16 @@ describe('POST /groups/:id/join', () => {
|
|||
},
|
||||
});
|
||||
group = groupData.group;
|
||||
await groupData.leader.post(`/groups/${group._id}/leave`);
|
||||
await groupData.groupLeader.post(`/groups/${group._id}/leave`);
|
||||
user = await generateUser();
|
||||
});
|
||||
|
||||
it('makes the joining user the leader', async () => {
|
||||
await user.post(`/groups/${group._id}/join`);
|
||||
|
||||
await expect(user.get(`/groups/${group._id}`)).to.eventually.have.deep.property('leader._id', user._id);
|
||||
await group.sync();
|
||||
|
||||
await expect(group.leader).to.eql(user._id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
import {
|
||||
checkExistence,
|
||||
createAndPopulateGroup,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
import { find } from 'lodash';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/leave', () => {
|
||||
context('user is not member of the group', () => {
|
||||
|
|
@ -29,10 +28,9 @@ describe('POST /groups/:id/leave', () => {
|
|||
it('leaves the group', async () => {
|
||||
await user.post(`/groups/${group._id}/leave`);
|
||||
|
||||
let members = (await user.get(`/groups/${group._id}`)).members;
|
||||
let userInGroup = find(members, '_id', user._id);
|
||||
await group.sync();
|
||||
|
||||
expect(userInGroup).to.not.be.ok;
|
||||
expect(group.members).to.not.include(user._id);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -48,7 +46,7 @@ describe('POST /groups/:id/leave', () => {
|
|||
},
|
||||
});
|
||||
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
|
|
@ -71,7 +69,7 @@ describe('POST /groups/:id/leave', () => {
|
|||
},
|
||||
});
|
||||
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
});
|
||||
|
||||
|
|
@ -95,7 +93,7 @@ describe('POST /groups/:id/leave', () => {
|
|||
},
|
||||
});
|
||||
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
invitee1 = groupData.invitees[0];
|
||||
invitee2 = groupData.invitees[1];
|
||||
|
|
@ -122,7 +120,7 @@ describe('POST /groups/:id/leave', () => {
|
|||
},
|
||||
});
|
||||
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
group = groupData.group;
|
||||
invitee1 = groupData.invitees[0];
|
||||
invitee2 = groupData.invitees[1];
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/removeMember', () => {
|
||||
context('user is not member of the group', () => {
|
||||
|
|
@ -24,7 +24,7 @@ describe('POST /groups/:id/removeMember', () => {
|
|||
privacy: 'public',
|
||||
},
|
||||
}).then((res) => {
|
||||
leader = res.leader;
|
||||
leader = res.groupLeader;
|
||||
member = res.members[0];
|
||||
group = res.group;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('DELETE /groups/:id/chat', () => {
|
||||
let group, message, user;
|
||||
|
|
@ -14,7 +14,7 @@ describe('DELETE /groups/:id/chat', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'Some message' });
|
||||
}).then((res) => {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {
|
||||
createAndPopulateGroup,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('GET /groups/:id/chat', () => {
|
||||
context('group with multiple messages', () => {
|
||||
|
|
@ -16,7 +16,7 @@ describe('GET /groups/:id/chat', () => {
|
|||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
member = groupData.members[0];
|
||||
|
||||
await member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
createAndPopulateGroup,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat', () => {
|
||||
let group, user;
|
||||
|
|
@ -14,7 +14,7 @@ describe('POST /groups/:id/chat', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,27 +2,26 @@ import {
|
|||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/clearflags', () => {
|
||||
let group;
|
||||
let guild;
|
||||
|
||||
beforeEach(async () => {
|
||||
return createAndPopulateGroup({
|
||||
let { group } = await createAndPopulateGroup({
|
||||
groupDetails: {
|
||||
type: 'guild',
|
||||
privacy: 'public',
|
||||
members: 1,
|
||||
flagCount: 1,
|
||||
chat: [{
|
||||
id: 'message-to-clear',
|
||||
flagCount: 1,
|
||||
flags: { 'some-id': true },
|
||||
}],
|
||||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
});
|
||||
|
||||
guild = group;
|
||||
});
|
||||
|
||||
context('non admin', () => {
|
||||
|
|
@ -33,7 +32,7 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||
});
|
||||
|
||||
it('cannot clear flags', async () => {
|
||||
return expect(nonadmin.post(`/groups/${group._id}/chat/message-to-clear/clearflags`))
|
||||
return expect(nonadmin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 401,
|
||||
text: t('messageGroupChatAdminClearFlagCount'),
|
||||
|
|
@ -53,23 +52,23 @@ describe('POST /groups/:id/chat/:id/clearflags', () => {
|
|||
});
|
||||
|
||||
it('clears flags', async () => {
|
||||
return admin.post(`/groups/${group._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${group._id}/chat`);
|
||||
return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${guild._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flagCount).to.eql(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('leaves old flags on the flag object', async () => {
|
||||
return admin.post(`/groups/${group._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${group._id}/chat`);
|
||||
return admin.post(`/groups/${guild._id}/chat/message-to-clear/clearflags`).then(() => {
|
||||
return admin.get(`/groups/${guild._id}/chat`);
|
||||
}).then((messages) => {
|
||||
expect(messages[0].flags).to.have.property('some-id', true);
|
||||
});
|
||||
});
|
||||
|
||||
it('returns error if message does not exist', async () => {
|
||||
return expect(admin.post(`/groups/${group._id}/chat/non-existant-message/clearflags`))
|
||||
return expect(admin.post(`/groups/${guild._id}/chat/non-existant-message/clearflags`))
|
||||
.to.eventually.be.rejected.and.eql({
|
||||
code: 404,
|
||||
text: t('messageGroupChatNotFound'),
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/flag', () => {
|
||||
context('another member\'s message', () => {
|
||||
|
|
@ -17,7 +17,7 @@ describe('POST /groups/:id/chat/:id/flag', () => {
|
|||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
|
|
@ -56,7 +56,7 @@ describe('POST /groups/:id/chat/:id/flag', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' });
|
||||
}).then((res) => {
|
||||
|
|
@ -84,7 +84,7 @@ describe('POST /groups/:id/chat/:id/flag', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -121,7 +121,7 @@ describe('POST /groups/:id/chat/:id/flag', () => {
|
|||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
});
|
||||
|
||||
it('changes only the message that is flagged', async () => {
|
||||
|
|
@ -166,7 +166,7 @@ describe('POST /groups/:id/chat/:id/flag', () => {
|
|||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
createAndPopulateGroup,
|
||||
generateUser,
|
||||
translate as t,
|
||||
} from '../../../../helpers/api-integration.helper';
|
||||
} from '../../../../helpers/api-integration/v2';
|
||||
|
||||
describe('POST /groups/:id/chat/:id/like', () => {
|
||||
context('another member\'s message', () => {
|
||||
|
|
@ -17,7 +17,7 @@ describe('POST /groups/:id/chat/:id/like', () => {
|
|||
members: 1,
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
member = res.members[0];
|
||||
|
||||
return member.post(`/groups/${group._id}/chat`, null, { message: 'Group member message' });
|
||||
|
|
@ -53,7 +53,7 @@ describe('POST /groups/:id/chat/:id/like', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
|
||||
return user.post(`/groups/${group._id}/chat`, null, { message: 'User\'s own message' });
|
||||
}).then((res) => {
|
||||
|
|
@ -94,7 +94,7 @@ describe('POST /groups/:id/chat/:id/like', () => {
|
|||
});
|
||||
|
||||
group = groupData.group;
|
||||
user = groupData.leader;
|
||||
user = groupData.groupLeader;
|
||||
});
|
||||
|
||||
it('changes only the message that is liked', async () => {
|
||||
|
|
@ -134,7 +134,7 @@ describe('POST /groups/:id/chat/:id/like', () => {
|
|||
},
|
||||
}).then((res) => {
|
||||
group = res.group;
|
||||
user = res.leader;
|
||||
user = res.groupLeader;
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import {
|
|||
generateUser,
|
||||
requester,
|
||||
translate as t,
|
||||
} from '../../../helpers/api-integration.helper';
|
||||
} from '../../../helpers/api-integration/v2';
|
||||
import { v4 as generateRandomUserName } from 'uuid';
|
||||
import { each } from 'lodash';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import {requester} from '../../../helpers/api-integration.helper';
|
||||
import {requester} from '../../../helpers/api-integration/v2';
|
||||
|
||||
describe('Status', () => {
|
||||
it('returns a status of up when server is up', async () => {
|
||||
|
|
|
|||