Merge branch 'develop' into paglias/realtime-chat-v1

This commit is contained in:
Matteo Pagliazzi
2016-09-09 20:13:21 +02:00
63 changed files with 483 additions and 125 deletions

View File

@@ -15,7 +15,11 @@
"defaultReward1Text": "15 minute break",
"defaultReward1Notes": "Custom rewards can come in many forms. Some people will hold off watching their favorite show unless they have the gold to pay for it.",
"defaultTag1": "morning",
"defaultTag2": "afternoon",
"defaultTag3": "evening"
"defaultTag1": "Work",
"defaultTag2": "Exercise",
"defaultTag3": "Health + Wellness",
"defaultTag4": "School",
"defaultTag5": "Teams",
"defaultTag6": "Chores",
"defaultTag7": "Creativity"
}

View File

@@ -3,56 +3,70 @@
"faqQuestion0": "I'm confused. Where do I get an overview?",
"iosFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > Customize Avatar.\n\n Some basic ways to interact: click the (+) in the upper-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-left-hand corner, and expand and contract checklists by clicking on the checklist bubble.",
"androidFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn experience and gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as Pets, Skills, and Quests! You can customize your character under Menu > [Inventory >] Avatar.\n\n Some basic ways to interact: click the (+) in the lower-right-hand corner to add a new task. Tap on an existing task to edit it, and swipe left on a task to delete it. You can sort tasks using Tags in the upper-right-hand corner, and expand and contract checklists by clicking on the checklist count box.",
"webFaqAnswer0": "First, you'll set up tasks that you want to do in your everyday life. Then, as you complete the tasks in real life and check them off, you'll earn Experience and Gold. Gold is used to buy equipment and some items, as well as custom rewards. Experience causes your character to level up and unlock content such as pets, skills, and quests! For more detail, check out a step-by-step overview of the game at [Help -> Overview for New Users](https://habitica.com/static/overview).",
"faqQuestion1": "How do I set up my tasks?",
"iosFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
"androidFaqAnswer1": "Good Habits (the ones with a +) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a -) are tasks that you should avoid, like biting nails. Habits with a + and a - have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award experience and gold. Bad Habits subtract health.\n\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by tapping to edit it. If you skip a Daily that is due, your character will take damage overnight. Be careful not to add too many Dailies at once!\n\n To-Dos are your To-Do list. Completing a To-Do earns you gold and experience. You never lose health from To-Dos. You can add a due date to a To-Do by tapping to edit.",
"webFaqAnswer1": "Good Habits (the ones with a <span class='glyphicon glyphicon-plus'></span>) are tasks that you can do many times a day, such as eating vegetables. Bad Habits (the ones with a <span class='glyphicon glyphicon-minus'></span>) are tasks that you should avoid, like biting nails. Habits with a <span class='glyphicon glyphicon-plus'></span> and a <span class='glyphicon glyphicon-minus'></span> have a good choice and a bad choice, like taking the stairs vs. taking the elevator. Good Habits award Experience and Gold. Bad Habits subtract Health.\n<br><br>\n Dailies are tasks that you have to do every day, like brushing your teeth or checking your email. You can adjust the days that a Daily is due by clicking the pencil item to edit it. If you skip a Daily that is due, your avatar will take damage overnight. Be careful not to add too many Dailies at once!\n<br><br>\n To-Dos are your To-Do list. Completing a To-Do earns you Gold and Experience. You never lose Health from To-Dos. You can add a due date to a To-Do by clicking the pencil icon to edit.",
"faqQuestion2": "What are some sample tasks?",
"iosFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n<br><br>\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"androidFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n<br><br>\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"webFaqAnswer2": "The wiki has four lists of sample tasks to use as inspiration:\n * [Sample Habits](http://habitica.wikia.com/wiki/Sample_Habits)\n * [Sample Dailies](http://habitica.wikia.com/wiki/Sample_Dailies)\n * [Sample To-Dos](http://habitica.wikia.com/wiki/Sample_To-Dos)\n * [Sample Custom Rewards](http://habitica.wikia.com/wiki/Sample_Custom_Rewards)",
"faqQuestion3": "Why do my tasks change color?",
"iosFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it's a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
"androidFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if it's a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
"webFaqAnswer3": "Your tasks change color based on how well you are currently accomplishing them! Each new task starts out as a neutral yellow. Perform Dailies or positive Habits more frequently and they move toward blue. Miss a Daily or give in to a bad Habit and the task moves toward red. The redder a task, the more rewards it will give you, but if its a Daily or bad Habit, the more it will hurt you! This helps motivate you to complete the tasks that are giving you trouble.",
"faqQuestion4": "Why did my avatar lose health, and how do I regain it?",
"iosFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you tap a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your Party and one of your Party mates did not complete all their Dailies, the Boss will attack you.\n\n The main way to heal is to gain a level, which restores all your health. You can also buy a Health Potion with gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a Party with a Healer, they can heal you as well.",
"androidFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you tap a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your Party and one of your Party mates did not complete all their Dailies, the Boss will attack you.\n\n The main way to heal is to gain a level, which restores all your health. You can also buy a Health Potion with gold from the Rewards tab on the Tasks page. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a Party with a Healer, they can heal you as well.",
"webFaqAnswer4": "There are several things that can cause you to take damage. First, if you left Dailies incomplete overnight, they will damage you. Second, if you click a bad Habit, it will damage you. Finally, if you are in a Boss Battle with your party and one of your party mates did not complete all their Dailies, the Boss will attack you.\n<br><br>\n The main way to heal is to gain a level, which restores all your Health. You can also buy a Health Potion with Gold from the Rewards column. Plus, at level 10 or above, you can choose to become a Healer, and then you will learn healing skills. If you are in a party (under Social > Party) with a Healer, they can heal you as well.",
"faqQuestion5": "How do I play Habitica with my friends?",
"iosFaqAnswer5": "The best way is to invite them to a Party with you! Parties can go on quests, battle monsters, and cast skills to support each other. Go to Menu > Party and click \"Create New Party\" if you don't already have a Party. Then tap on the Members list, and tap Invite in the upper right-hand corner to invite your friends by entering their User ID (a string of numbers and letters that they can find under Settings > Account Details on the app, and Settings > API on the website). On the website, you can also invite friends via email, which we will add to the app in a future update.\n\nOn the website, you and your friends can also join Guilds, which are public chat rooms. Guilds will be added to the app in a future update!",
"androidFaqAnswer5": "The best way is to invite them to a Party with you! Parties can go on quests, battle monsters, and cast skills to support each other. Go to Menu > Party and click \"Create New Party\" if you don't already have a Party. Then tap on the Members list, then click Options Menu > Invite Friends in the upper-right-hand corner to invite your friends by entering their email or User ID (a string of numbers and letters that they can find under Settings > Account Details on the app, and Settings > API on the website).You can also join guilds together (Social > Guilds). Guilds are chat rooms focusing on a shared interest or the pursuit of a common goal, and can be public or private. You can join as many guilds as you'd like, but only one party.\n\n For more detailed info, check out the wiki pages on [Parties](http://habitrpg.wikia.com/wiki/Party) and [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
"webFaqAnswer5": "The best way is to invite them to a party with you, under Social > Party! Parties can go on quests, battle monsters, and cast skills to support each other. You can also join guilds together (Social > Guilds). Guilds are chat rooms focusing on a shared interest or the pursuit of a common goal, and can be public or private. You can join as many guilds as you'd like, but only one party.\n<br><br>\n For more detailed info, check out the wiki pages on [Parties](http://habitrpg.wikia.com/wiki/Party) and [Guilds](http://habitrpg.wikia.com/wiki/Guilds).",
"faqQuestion6": "How do I get a Pet or Mount?",
"iosFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Menu > Items.\n\n To hatch a Pet, you'll need an egg and a hatching potion. Tap on the egg to determine the species you want to hatch, and select \"Hatch Egg.\" Then choose a hatching potion to determine its color! Go to Menu > Pets to equip your new Pet to your avatar by clicking on it. \n\n You can also grow your Pets into Mounts by feeding them under Menu > Pets. Tap on a Pet, and then select \"Feed Pet\"! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Menu > Mounts and tap on it to equip it to your avatar.\n\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
"androidFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Menu > Items.\n\n To hatch a Pet, you'll need an egg and a hatching potion. Tap on the egg to determine the species you want to hatch, and select \"Hatch with potion.\" Then choose a hatching potion to determine its color! To equip your new Pet, go to Menu > Stable > Pets, select a species, click on the desired Pet, and select \"Use\"(Your avatar doesn't update to reflect the change). \n\n You can also grow your Pets into Mounts by feeding them under Menu > Stable [ > Pets ]. Tap on a Pet, and then select \"Feed\"! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). To equip your Mount, go to Menu > Stable > Mounts, select a species, click on the desired Mount, and select \"Use\"(Your avatar doesn't update to reflect the change).\n\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
"webFaqAnswer6": "At level 3, you will unlock the Drop System. Every time you complete a task, you'll have a random chance at receiving an egg, a hatching potion, or a piece of food. They will be stored in Inventory > Market.\n<br><br>\n To hatch a Pet, you'll need an egg and a hatching potion. Click on the egg to determine the species you want to hatch, and then click on the hatching potion to determine its color! Go to Inventory > Pets to equip it to your avatar by clicking on it.\n<br><br>\n You can also grow your Pets into Mounts by feeding them under Inventory > Pets. Click on a type of food, and then select the pet you want to feed! You'll have to feed a pet many times before it becomes a Mount, but if you can figure out its favorite food, it will grow more quickly. Use trial and error, or [see the spoilers here](http://habitica.wikia.com/wiki/Food#Food_Preferences). Once you have a Mount, go to Inventory > Mounts and click on it to equip it to your avatar.\n<br><br>\n You can also get eggs for Quest Pets by completing certain Quests. (See below to learn more about Quests.)",
"faqQuestion7": "How do I become a Warrior, Mage, Rogue, or Healer?",
"iosFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their Party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most gold and find the most item drops, and they can help their Party do the same. Finally, Healers can heal themselves and their Party members.\n\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click “Decide Later” and choose later under Menu > Choose Class.",
"androidFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their Party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most gold and find the most item drops, and they can help their Party do the same. Finally, Healers can heal themselves and their Party members.\n\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click “Opt Out” and choose later under Menu > Choose Class.",
"webFaqAnswer7": "At level 10, you can choose to become a Warrior, Mage, Rogue, or Healer. (All players start as Warriors by default.) Each Class has different equipment options, different Skills that they can cast after level 11, and different advantages. Warriors can easily damage Bosses, withstand more damage from their tasks, and help make their party tougher. Mages can also easily damage Bosses, as well as level up quickly and restore Mana for their party. Rogues earn the most Gold and find the most item drops, and they can help their party do the same. Finally, Healers can heal themselves and their party members.\n<br><br>\n If you don't want to choose a Class immediately -- for example, if you are still working to buy all the gear of your current class -- you can click \"Opt Out\" and re-enable it later under User > Stats.",
"faqQuestion8": "What is the blue stat bar that appears in the Header after level 10?",
"iosFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 under Menu > Use Skills. Unlike your health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You'll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"androidFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 under Menu > Skills. Unlike your health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. You'll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"webFaqAnswer8": "The blue bar that appeared when you hit level 10 and chose a Class is your Mana bar. As you continue to level up, you will unlock special Skills that cost Mana to use. Each Class has different Skills, which appear after level 11 in a special section in the Rewards Column. Unlike your Health bar, your Mana bar does not reset when you gain a level. Instead, Mana is gained when you complete Good Habits, Dailies, and To-Dos, and lost when you indulge bad Habits. Youll also regain some Mana overnight -- the more Dailies you completed, the more you will gain.",
"faqQuestion9": "How do I fight monsters and go on Quests?",
"iosFaqAnswer9": "First, you need to join or start a Party (see above). Although you can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n\n Next, you need a Quest Scroll, which are stored under Menu > Items. There are three ways to get a scroll:\n\n - At level 15, you get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively. \n - When you invite people to your Party, you'll be rewarded with the Basi-List Scroll!\n - You can buy Quests from the Quests Page on the [website](https://habitica.com/#/options/inventory/quests) for Gold and Gems. (We will add this feature to the app in a future update.)\n\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading by pulling down on the screen may be required to see the Boss's health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your Party at the same time that you damage the Boss. \n\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
"androidFaqAnswer9": "First, you need to join or start a Party (see above). Although you can battle monsters alone, we recommend playing in a group, because this will make Quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n\n Next, you need a Quest Scroll, which are stored under Menu > Items. There are three ways to get a scroll:\n\n - At level 15, you get a Quest-line, aka three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively. \n - When you invite people to your Party, you'll be rewarded with the Basi-List Scroll!\n - You can buy Quests from the Quests Page on the [website](https://habitica.com/#/options/inventory/quests) for Gold and Gems. (We will add this feature to the app in a future update.)\n\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading by pulling down on the screen may be required to see the Boss's health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your Party at the same time that you damage the Boss. \n\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
"webFaqAnswer9": "First, you need to join or start a party (under Social > Party). Although you can battle monsters alone, we recommend playing in a group, because this will make quests much easier. Plus, having a friend to cheer you on as you accomplish your tasks is very motivating!\n<br><br>\n Next, you need a Quest Scroll, which are stored under Inventory > Quests. There are three ways to get a scroll:\n<br><br>\n * When you invite people to your party, youll be rewarded with the Basi-List Scroll!\n * At level 15, you get a Quest-line, i.e., three linked quests. More Quest-lines unlock at levels 30, 40, and 60 respectively.\n * You can buy Quests from the Quests Page (Inventory > Quests) for Gold and Gems.\n<br><br>\n To battle the Boss or collect items for a Collection Quest, simply complete your tasks normally, and they will be tallied into damage overnight. (Reloading may be required to see the Boss's Health bar go down.) If you are fighting a Boss and you missed any Dailies, the Boss will damage your party at the same time that you damage the Boss.\n<br><br>\n After level 11 Mages and Warriors will gain Skills that allow them to deal additional damage to the Boss, so these are excellent classes to choose at level 10 if you want to be a heavy hitter.",
"faqQuestion10": "What are Gems, and how do I get them?",
"iosFaqAnswer10": "Gems are purchased with real money by tapping on the gem icon in the header. When people buy gems, they are helping us to keep the site running. We're very grateful for their support!\n\n In addition to buying gems directly, there are three other ways players can gain gems:\n\n * Win a Challenge on the [website](https://habitica.com) that has been set up by another player under Social > Challenges. (We will be adding Challenges to the app in a future update!)\n * Subscribe on the [website](https://habitica.com/#/options/settings/subscription) and unlock the ability to buy a certain number of gems per month.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\n Keep in mind that items purchased with gems do not offer any statistical advantages, so players can still make use of the app without them!",
"androidFaqAnswer10": "Gems are purchased with real money by tapping on the gem icon in the header. When people buy gems, they are helping us to keep the site running. We're very grateful for their support!\n\n In addition to buying gems directly, there are three other ways players can gain gems:\n\n * Win a Challenge on the [website](https://habitica.com) that has been set up by another player under Social > Challenges. (We will be adding Challenges to the app in a future update!)\n * Subscribe on the [website](https://habitica.com/#/options/settings/subscription) and unlock the ability to buy a certain number of gems per month.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica).\n\n Keep in mind that items purchased with gems do not offer any statistical advantages, so players can still make use of the app without them!",
"webFaqAnswer10": "Gems are [purchased with real money](https://habitica.com/#/options/settings/subscription), although [subscribers](https://habitica.com/#/options/settings/subscription) can purchase them with Gold. When people subscribe or buy Gems, they are helping us to keep the site running. We're very grateful for their support!\n<br><br>\n In addition to buying Gems directly or becoming a subscriber, there are two other ways players can gain Gems:\n<br><br>\n * Win a Challenge that has been set up by another player under Social > Challenges.\n * Contribute your skills to the Habitica project. See this wiki page for more details: [Contributing to Habitica](http://habitica.wikia.com/wiki/Contributing_to_Habitica)\n<br><br>\n Keep in mind that items purchased with Gems do not offer any statistical advantages, so players can still make use of the site without them!",
"faqQuestion11": "How do I report a bug or request a feature?",
"iosFaqAnswer11": "You can report a bug, request a feature, or send feedback under Menu > Report a Bug and Menu > Send Feedback! We'll do everything we can to assist you.",
"androidFaqAnswer11": "You can report a bug, request a feature, or send feedback under About > Report a Bug and About > Send us Feedback! We'll do everything we can to assist you.",
"webFaqAnswer11": "To report a bug, go to [Help > Report a Bug](https://habitica.com/#/options/groups/guilds/a29da26b-37de-4a71-b0c6-48e72a900dac) and read the points above the chat box. If you're unable to log in to Habitica, send your login details to <a href=\"mailto:admin@habitica.com\">admin@habitica.com</a>. Don't worry, we'll get you fixed up soon!\n<br><br>\n Feature requests are collected on Trello. Go to [Help > Request a Feature](https://trello.com/c/odmhIqyW/440-read-first-table-of-contents) and follow the instructions. Ta-da!",
"faqQuestion12": "How do I battle a World Boss?",
"iosFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
"androidFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
"webFaqAnswer12": "World Bosses are special monsters that appear in the Tavern. All active users are automatically battling the Boss, and their tasks and skills will damage the Boss as usual.\n<br><br>\n You can also be in a normal Quest at the same time. Your tasks and skills will count towards both the World Boss and the Boss/Collection Quest in your party.\n<br><br>\n A World Boss will never hurt you or your account in any way. Instead, it has a Rage Bar that fills when users skip Dailies. If its Rage bar fills, it will attack one of the Non-Player Characters around the site and their image will change.\n<br><br>\n You can read more about [past World Bosses](http://habitica.wikia.com/wiki/World_Bosses) on the wiki.",
"iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
"androidFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the Tavern chat under Menu > Tavern! We're happy to help.",
"webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.wikia.com/wiki/FAQ), come ask in the [Newbies Guild](https://habitica.com/#/options/groups/guilds/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help."
}

View File

@@ -135,6 +135,14 @@
"presskit": "Press Kit",
"presskitDownload": "Download all images:",
"presskitText": "Thanks for your interest in Habitica! The following images can be used for articles or videos about Habitica. For more information, please contact the staff at admin@habitica.com.",
"pkVideo": "Video",
"pkPromo": "Promos",
"pkLogo": "Logos",
"pkBoss": "Bosses",
"pkSamples": "Sample Screens",
"pkWebsite": "Website",
"pkiOS": "iOS",
"pkAndroid": "Android",
"privacy": "Privacy Policy",
"psst": "Psst",
"punishByline": "Break bad habits and procrastination cycles with immediate consequences.",

View File

@@ -2940,6 +2940,14 @@ api.userDefaults = {
name: t('defaultTag2')
}, {
name: t('defaultTag3')
}, {
name: t('defaultTag4')
}, {
name: t('defaultTag5')
}, {
name: t('defaultTag6')
}, {
name: t('defaultTag7')
}
]
};

2
npm-shrinkwrap.json generated
View File

@@ -1,6 +1,6 @@
{
"name": "habitica",
"version": "3.39.1",
"version": "3.40.0",
"dependencies": {
"abbrev": {
"version": "1.0.9",

View File

@@ -1,10 +1,10 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "3.39.1",
"version": "3.40.0",
"main": "./website/server/index.js",
"dependencies": {
"@slack/client": "slackhq/node-slack-sdk#2ee794cd31326c54f38c518eef2b9d223327d939",
"@slack/client": "3.6.0",
"accepts": "^1.3.2",
"amazon-payments": "0.0.4",
"amplitude": "^2.0.3",

View File

@@ -33,6 +33,25 @@ describe('POST /user/auth/local/register', () => {
expect(user.auth.local.username).to.eql(username);
});
it('provides default tags and tasks', async () => {
let username = generateRandomUserName();
let email = `${username}@example.com`;
let password = 'password';
let user = await api.post('/user/auth/local/register', {
username,
email,
password,
confirmPassword: password,
});
expect(user.tags).to.have.a.lengthOf(7);
expect(user.tasksOrder.todos).to.have.a.lengthOf(1);
expect(user.tasksOrder.dailys).to.have.a.lengthOf(0);
expect(user.tasksOrder.rewards).to.have.a.lengthOf(0);
expect(user.tasksOrder.habits).to.have.a.lengthOf(0);
});
it('requires password and confirmPassword to match', async () => {
let username = generateRandomUserName();
let email = `${username}@example.com`;

View File

@@ -18,7 +18,7 @@ describe('POST /user/auth/social', () => {
user = await generateUser();
let expectedResult = {id: facebookId};
let passportFacebookProfile = sinon.stub(passport._strategies.facebook, 'userProfile');
let passportFacebookProfile = sandbox.stub(passport._strategies.facebook, 'userProfile');
passportFacebookProfile.yields(null, expectedResult);
});

View File

@@ -1,6 +1,8 @@
/* eslint-disable global-require */
import moment from 'moment';
import nconf from 'nconf';
import Bluebird from 'bluebird';
import requireAgain from 'require-again';
import { recoverCron, cron } from '../../../../../website/server/libs/cron';
import { model as User } from '../../../../../website/server/models/user';
import * as Tasks from '../../../../../website/server/models/task';
@@ -9,6 +11,8 @@ import common from '../../../../../common';
// const scoreTask = common.ops.scoreTask;
let pathToCronLib = '../../../../../website/server/libs/cron';
describe('cron', () => {
let user;
let tasksByType = {habits: [], dailys: [], todos: [], rewards: []};
@@ -159,7 +163,7 @@ describe('cron', () => {
expect(user.purchased.plan.consecutive.trinkets).to.equal(0);
});
it('doest not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => {
it('does not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', () => {
user.purchased.plan.consecutive.count = 5;
cron({user, tasksByType, daysMissed, analytics});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
@@ -319,6 +323,19 @@ describe('cron', () => {
expect(user.stats.hp).to.be.lessThan(hpBefore);
});
it('should not do damage for missing a daily when CRON_SAFE_MODE is set', () => {
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
let cronOverride = requireAgain(pathToCronLib).cron;
daysMissed = 1;
let hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
cronOverride({user, tasksByType, daysMissed, analytics});
expect(user.stats.hp).to.equal(hpBefore);
});
it('should not do damage for missing a daily if user stealth buff is greater than or equal to days missed', () => {
daysMissed = 1;
let hpBefore = user.stats.hp;
@@ -443,6 +460,23 @@ describe('cron', () => {
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
});
it('still grants a perfect day when CRON_SAFE_MODE is set', () => {
sandbox.stub(nconf, 'get').withArgs('CRON_SAFE_MODE').returns('true');
let cronOverride = requireAgain(pathToCronLib).cron;
daysMissed = 1;
tasksByType.dailys[0].completed = false;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({days: 1});
let previousBuffs = clone(user.stats.buffs);
cronOverride({user, tasksByType, daysMissed, analytics});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
expect(user.stats.buffs.int).to.be.greaterThan(previousBuffs.int);
expect(user.stats.buffs.per).to.be.greaterThan(previousBuffs.per);
expect(user.stats.buffs.con).to.be.greaterThan(previousBuffs.con);
});
it('clears buffs if user does not have a perfect day', () => {
daysMissed = 1;
tasksByType.dailys[0].completed = false;

View File

@@ -1,7 +1,7 @@
'use strict';
describe('Inventory Controller', function() {
var scope, ctrl, user, rootScope;
var scope, ctrl, user, rootScope, shared, achievement;
beforeEach(function() {
module(function($provide) {
@@ -15,7 +15,7 @@ describe('Inventory Controller', function() {
$provide.value('$window', mockWindow);
});
inject(function($rootScope, $controller, Shared, User, $location, $window) {
inject(function($rootScope, $controller, Shared, User, $location, $window, Achievement) {
user = specHelper.newUser({
balance: 4,
items: {
@@ -32,6 +32,8 @@ describe('Inventory Controller', function() {
});
Shared.wrap(user);
shared = Shared;
achievement = Achievement;
scope = $rootScope.$new();
rootScope = $rootScope;
@@ -118,6 +120,27 @@ describe('Inventory Controller', function() {
expect(rootScope.openModal).to.not.be.called;
});
it('shows beastMaster achievement modal if user has all 90 pets', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "beastMasterProgress").returns(90);
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('beastMaster');
});
it('shows triadBingo achievement modal if user has all pets twice and all mounts', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "mountMasterProgress").returns(90);
sandbox.stub(shared.count, "dropPetsCurrentlyOwned").returns(90);
scope.chooseEgg('Cactus');
scope.choosePotion('Base');
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('triadBingo');
});
});
describe('Feeding and Raising Pets', function() {
@@ -194,6 +217,16 @@ describe('Inventory Controller', function() {
expect(rootScope.openModal).to.have.been.calledOnce;
expect(rootScope.openModal).to.have.been.calledWith('raisePet');
});
it('shows mountMaster achievement modal if user has all 90 mounts', function(){
sandbox.stub(achievement, 'displayAchievement');
sandbox.stub(shared.count, "mountMasterProgress").returns(90);
scope.chooseFood('Meat');
scope.choosePet('PandaCub','Base');
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('mountMaster');
});
});
it('sells an egg', function(){

View File

@@ -1,33 +1,49 @@
'use strict';
describe('Notification Controller', function() {
var user, scope, rootScope, ctrl;
var user, scope, rootScope, fakeBackend, achievement, ctrl;
beforeEach(function() {
user = specHelper.newUser();
user._id = "unique-user-id";
var userSync = sinon.stub().returns({
then: function then (f) { f(); }
});
let User = {
user,
readNotification: function noop () {},
sync: userSync
};
module(function($provide) {
$provide.value('User', {user: user});
$provide.value('User', User);
$provide.value('Guide', {});
});
inject(function(_$rootScope_, _$controller_) {
inject(function(_$rootScope_, $httpBackend, _$controller_, Achievement, Shared) {
scope = _$rootScope_.$new();
rootScope = _$rootScope_;
// Load RootCtrl to ensure shared behaviors are loaded
_$controller_('RootCtrl', {$scope: scope, User: {user: user}});
fakeBackend = $httpBackend;
fakeBackend.when('GET', 'partials/main.html').respond({});
ctrl = _$controller_('NotificationCtrl', {$scope: scope, User: {user: user}});
achievement = Achievement;
Shared.wrap(user);
// Load RootCtrl to ensure shared behaviors are loaded
_$controller_('RootCtrl', {$scope: scope, User});
ctrl = _$controller_('NotificationCtrl', {$scope: scope, User});
});
sandbox.stub(rootScope, 'openModal');
sandbox.stub(achievement, 'displayAchievement');
});
describe('Quest Invitation modal watch', function() {
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
});
it('opens quest invitation modal', function() {
user.party.quest.RSVPNeeded = true;
delete user.party.quest.completed;
@@ -55,10 +71,6 @@ describe('Notification Controller', function() {
});
describe('Quest Completion modal watch', function() {
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
});
it('opens quest completion modal', function() {
user.party.quest.completed = "hedgebeast";
scope.$digest();
@@ -84,4 +96,94 @@ describe('Notification Controller', function() {
expect(rootScope.openModal).to.not.be.called;
});
});
describe('User challenge won notification watch', function() {
it('opens challenge won modal when a challenge-won notification is recieved', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'WON_CHALLENGE'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('wonChallenge');
});
it('does not open challenge won modal if no new challenge-won notification is recieved', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('wonChallenge');
});
});
describe('User streak achievement notification watch', function() {
it('opens streak achievement modal when a streak-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'STREAK_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('streak', {size: 'md'});
});
it('does not open streak achievement modal if no new streak-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('streak', {size: 'md'});
});
});
describe('User ultimate gear set achievement notification watch', function() {
it('opens ultimate gear set achievement modal when an ultimate-gear-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'ULTIMATE_GEAR_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('ultimateGear', {size: 'md'});
});
it('does not open ultimate gear set achievement modal if no new ultimate-gear-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('ultimateGear', {size: 'md'});
});
});
describe('User rebirth achievement notification watch', function() {
it('opens rebirth achievement modal when a rebirth-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'REBIRTH_ACHIEVEMENT'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('rebirth');
});
it('does not open rebirth achievement modal if no new rebirth-achievement notification is recieved', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('rebirth');
});
});
describe('User contributor achievement notification watch', function() {
it('opens contributor achievement modal when a new-contributor-level notification is recieved', function() {
rootScope.$digest();
rootScope.userNotifications.push({type: 'NEW_CONTRIBUTOR_LEVEL'});
rootScope.$digest();
expect(achievement.displayAchievement).to.be.called;
expect(achievement.displayAchievement).to.be.calledWith('contributor', {size: 'md'});
});
it('does not open contributor achievement modal if no new new-contributor-level notification is recieved', function() {
rootScope.$digest();
rootScope.$digest();
expect(achievement.displayAchievement).to.not.be.calledWith('contributor', {size: 'md'});
});
});
});

View File

@@ -1,8 +1,7 @@
'use strict';
describe("Party Controller", function() {
var scope, ctrl, user, User, questsService, groups, rootScope, $controller, deferred;
var party;
var scope, ctrl, user, User, questsService, groups, achievement, rootScope, $controller, deferred, party;
beforeEach(function() {
user = specHelper.newUser(),
@@ -23,7 +22,7 @@ describe("Party Controller", function() {
$provide.value('User', User);
});
inject(function(_$rootScope_, _$controller_, Groups, Quests, _$q_){
inject(function(_$rootScope_, _$controller_, Groups, Quests, _$q_, Achievement){
rootScope = _$rootScope_;
@@ -33,6 +32,7 @@ describe("Party Controller", function() {
groups = Groups;
questsService = Quests;
achievement = Achievement;
// Load RootCtrl to ensure shared behaviors are loaded
$controller('RootCtrl', {$scope: scope, User: User});
@@ -61,7 +61,7 @@ describe("Party Controller", function() {
};
beforeEach(function() {
sandbox.stub(rootScope, 'openModal');
sandbox.stub(achievement, 'displayAchievement');
});
context('party has 1 member', function() {
@@ -71,7 +71,7 @@ describe("Party Controller", function() {
initializeControllerWithStubbedState();
expect(User.set).to.not.be.called;
expect(rootScope.openModal).to.not.be.called;
expect(achievement.displayAchievement).to.not.be.called;
});
});
@@ -87,8 +87,8 @@ describe("Party Controller", function() {
expect(User.set).to.be.calledWith(
{ 'achievements.partyUp': true }
);
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('achievements/partyUp');
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('partyUp');
done();
}, 1000);
});
@@ -112,8 +112,8 @@ describe("Party Controller", function() {
expect(User.set).to.be.calledWith(
{ 'achievements.partyOn': true }
);
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('achievements/partyOn');
expect(achievement.displayAchievement).to.be.calledOnce;
expect(achievement.displayAchievement).to.be.calledWith('partyOn');
done();
}, 1000);
});
@@ -131,9 +131,9 @@ describe("Party Controller", function() {
expect(User.set).to.be.calledWith(
{ 'achievements.partyOn': true}
);
expect(rootScope.openModal).to.have.been.called;
expect(rootScope.openModal).to.be.calledWith('achievements/partyUp');
expect(rootScope.openModal).to.be.calledWith('achievements/partyOn');
expect(achievement.displayAchievement).to.have.been.called;
expect(achievement.displayAchievement).to.be.calledWith('partyUp');
expect(achievement.displayAchievement).to.be.calledWith('partyOn');
done();
}, 1000);
});
@@ -147,7 +147,7 @@ describe("Party Controller", function() {
initializeControllerWithStubbedState();
expect(User.set).to.not.be.called;
expect(rootScope.openModal).to.not.be.called;
expect(achievement.displayAchievement).to.not.be.called;
});
});
});

View File

@@ -0,0 +1,20 @@
describe('timezoneOffsetToUtc', function() {
beforeEach(module('habitrpg'));
it('formats the timezone offset with a - sign if the offset is positive', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(90)).to.eql('UTC-1:30');
}));
it('formats the timezone offset with a + sign if the offset is negative', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(-525)).to.eql('UTC+8:45');
}));
it('prepends the minutes with a 0 if the minute the offset is less than 10', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(60)).to.eql('UTC-1:00');
}));
it('returns the string UTC+0:00 if the offset is 0', inject(function(timezoneOffsetToUtcFilter) {
expect(timezoneOffsetToUtcFilter(0)).to.eql('UTC+0:00');
}));
});

View File

@@ -0,0 +1,55 @@
'use strict';
describe('achievementServices', function() {
var achievementService, rootScope;
beforeEach(function() {
rootScope = { 'openModal': sandbox.stub() };
module(function($provide) {
$provide.value('$rootScope', rootScope);
});
inject(function(Achievement) {
achievementService = Achievement;
});
});
describe('#displayAchievement', function() {
it('passes given achievement name to openModal', function() {
achievementService.displayAchievement('beastMaster');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith('achievements/beastMaster');
});
it('calls openModal with UserCtrl and small modal size if no other size is given', function() {
achievementService.displayAchievement('test');
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'sm' }
);
});
it('calls openModal with UserCtrl and specified modal size if one is given', function() {
achievementService.displayAchievement('test', {size: 'md'});
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'md' }
);
});
it('calls openModal with UserCtrl and default \'sm\' size if invalid size is given', function() {
achievementService.displayAchievement('test', {size: 'INVALID_SIZE'});
expect(rootScope.openModal).to.be.calledOnce;
expect(rootScope.openModal).to.be.calledWith(
'achievements/test',
{ controller: 'UserCtrl', size: 'sm' }
);
});
});
});

View File

@@ -39,7 +39,7 @@ var specHelper = {};
progress: {down: 0}
}
},
preferences: {},
preferences: { suppressModals: {} },
habits: [],
dailys: [],
todos: [],

View File

@@ -1,6 +1,6 @@
habitrpg.controller("InventoryCtrl",
['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', 'Quests', 'Stats', 'Social',
function($rootScope, $scope, Shared, $window, User, Content, Analytics, Quests, Stats, Social) {
['$rootScope', '$scope', 'Shared', '$window', 'User', 'Content', 'Analytics', 'Quests', 'Stats', 'Social', 'Achievement',
function($rootScope, $scope, Shared, $window, User, Content, Analytics, Quests, Stats, Social, Achievement) {
var user = User.user;
@@ -167,7 +167,7 @@ habitrpg.controller("InventoryCtrl",
if(!user.achievements.beastMaster
&& $scope.petCount >= 90) {
User.user.achievements.beastMaster = true;
$rootScope.openModal('achievements/beastMaster', {controller:'UserCtrl', size:'sm'});
Achievement.displayAchievement('beastMaster');
}
// Checks if Triad Bingo has been reached for the first time
@@ -175,7 +175,7 @@ habitrpg.controller("InventoryCtrl",
&& $scope.mountCount >= 90
&& Shared.count.dropPetsCurrentlyOwned(User.user.items.pets) >= 90) {
User.user.achievements.triadBingo = true;
$rootScope.openModal('achievements/triadBingo', {controller:'UserCtrl', size:'sm'});
Achievement.displayAchievement('triadBingo');
}
}
@@ -216,7 +216,7 @@ habitrpg.controller("InventoryCtrl",
if(!user.achievements.mountMaster
&& $scope.mountCount >= 90) {
User.user.achievements.mountMaster = true;
$rootScope.openModal('achievements/mountMaster', {controller:'UserCtrl', size:'sm'});
Achievement.displayAchievement('mountMaster');
}
// Selecting Pet

View File

@@ -1,8 +1,8 @@
'use strict';
habitrpg.controller('NotificationCtrl',
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics',
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics) {
['$scope', '$rootScope', 'Shared', 'Content', 'User', 'Guide', 'Notification', 'Analytics', 'Achievement',
function ($scope, $rootScope, Shared, Content, User, Guide, Notification, Analytics, Achievement) {
$rootScope.$watch('user.stats.hp', function (after, before) {
if (after <= 0){
@@ -98,24 +98,24 @@ habitrpg.controller('NotificationCtrl',
break;
case 'WON_CHALLENGE':
User.sync().then( function() {
$rootScope.openModal('wonChallenge', {controller: 'UserCtrl', size: 'sm'});
Achievement.displayAchievement('wonChallenge');
});
break;
case 'STREAK_ACHIEVEMENT':
Notification.streak(User.user.achievements.streak);
$rootScope.playSound('Achievement_Unlocked');
if (!User.user.preferences.suppressModals.streak) {
$rootScope.openModal('achievements/streak', {controller:'UserCtrl'});
Achievement.displayAchievement('streak', {size: 'md'});
}
break;
case 'ULTIMATE_GEAR_ACHIEVEMENT':
$rootScope.openModal('achievements/ultimateGear', {controller:'UserCtrl'});
Achievement.displayAchievement('ultimateGear', {size: 'md'});
break;
case 'REBIRTH_ACHIEVEMENT':
$rootScope.openModal('achievements/rebirth', {controller:'UserCtrl', size: 'sm'});
Achievement.displayAchievement('rebirth');
break;
case 'NEW_CONTRIBUTOR_LEVEL':
$rootScope.openModal('achievements/contributor',{controller:'UserCtrl'});
Achievement.displayAchievement('contributor', {size: 'md'});
break;
case 'CRON':
if (notification.data) {
@@ -135,7 +135,7 @@ habitrpg.controller('NotificationCtrl',
}
// Since we don't use localStorage anymore, notifications for achievements and new contributor levels
// are now stored user.notifications.
// are now stored in user.notifications.
$rootScope.$watchCollection('userNotifications', function (after) {
if (!User.user._wrapped) return;
handleUserNotifications(after);

View File

@@ -1,7 +1,7 @@
'use strict';
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social',
function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social) {
habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','Challenges','$state','$compile','Analytics','Quests','Social', 'Achievement',
function($rootScope, $scope, Groups, Chat, User, Challenges, $state, $compile, Analytics, Quests, Social, Achievement) {
var PARTY_LOADING_MESSAGES = 4;
@@ -47,14 +47,14 @@ habitrpg.controller("PartyCtrl", ['$rootScope','$scope','Groups','Chat','User','
if(!user.achievements.partyUp
&& $scope.group.memberCount >= 2) {
User.set({'achievements.partyUp':true});
$rootScope.openModal('achievements/partyUp', {controller:'UserCtrl', size:'sm'});
Achievement.displayAchievement('partyUp');
}
// Checks if user's party has reached 4 players for the first time.
if(!user.achievements.partyOn
&& $scope.group.memberCount >= 4) {
User.set({'achievements.partyOn':true});
$rootScope.openModal('achievements/partyOn', {controller:'UserCtrl', size:'sm'});
Achievement.displayAchievement('partyOn');
}
}

View File

@@ -6,6 +6,10 @@
habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$http', '$state', '$stateParams', 'Notification', 'Groups', 'Shared', 'Content', '$modal', '$timeout', 'ApiUrl', 'Payments','$sce','$window','Analytics','TAVERN_ID', 'Pusher',
function($scope, $rootScope, $location, User, $http, $state, $stateParams, Notification, Groups, Shared, Content, $modal, $timeout, ApiUrl, Payments, $sce, $window, Analytics, TAVERN_ID, Pusher) {
var user = User.user;
var IGNORE_SCROLL_PAGES = {
'options.social.challenges.detail': true,
'options.social.challenges': true
};
// Setup page once user is synced
var clearAppLoadedListener = $rootScope.$watch('appLoaded', function (after) {
@@ -25,7 +29,9 @@ habitrpg.controller("RootCtrl", ['$scope', '$rootScope', '$location', 'User', '$
function(event, toState, toParams, fromState, fromParams){
$rootScope.pageTitle = $state.current.title;
if (!($state.current.name in IGNORE_SCROLL_PAGES)) {
$window.scrollTo(0, 0);
}
if (!!fromState.name) Analytics.track({'hitType':'pageview','eventCategory':'navigation','eventAction':'navigate','page':'/#/'+toState.name});
if (toState.name=='options.social.inbox' && User.user.inbox && User.user.inbox.newMessages > 0) {

View File

@@ -0,0 +1,28 @@
'use strict';
/**
* Services that handle achievement logic.
*/
angular.module('habitrpg').factory('Achievement',
['$rootScope', function($rootScope) {
var sizes = ['sm', 'md', 'lg'];
var DEFAULT_SIZE = 'sm';
function displayAchievement(achievementName, options) {
options = options || {};
if (options.size && sizes.indexOf(options.size) === -1) {
delete options.size;
}
$rootScope.openModal('achievements/' + achievementName, {
controller: 'UserCtrl',
size: options.size || DEFAULT_SIZE
});
}
return {
displayAchievement: displayAchievement
};
}]);

View File

@@ -195,19 +195,28 @@ function($rootScope, User, $timeout, $state, Analytics) {
}
};
step.onHide = function(){
var ups={};
if (!$rootScope.stepAwarded) $rootScope.stepAwarded = {};
if (!$rootScope.stepAwarded[i]) {
$rootScope.stepAwarded[i] = true;
ups['stats.gp'] = User.user.stats.gp + (step.gold || 0);
ups['stats.exp'] = User.user.stats.exp + (step.experience || 0);
var ups = {};
var lastKnownStep = User.user.flags.tour[k];
// Return early if user has already completed this tutorial
if (lastKnownStep === -2) {
return;
}
if (i > lastKnownStep) {
if (step.gold) ups['stats.gp'] = User.user.stats.gp + step.gold;
if (step.experience) ups['stats.exp'] = User.user.stats.exp + step.experience;
ups['flags.tour.'+k] = i;
}
if (step.final) { // -2 indicates complete
ups['flags.tour.'+k] = -2;
$rootScope.stepAwarded = null;
Analytics.track({'hitType':'event','eventCategory':'behavior','eventAction':'tutorial','eventLabel':k+'-web','eventValue':i+1,'complete':true})
}
User.set(ups);
// User.set() doesn't include a check for level changes, so manually check here.
if (step.experience) {
User.user.fns.updateStats(User.user.stats);
}
}
}).value();
});
@@ -245,8 +254,6 @@ function($rootScope, User, $timeout, $state, Analytics) {
if (page === -1) page = 0;
var curr = User.user.flags.tour[chapter];
if (page != curr+1 && !force) return;
var updates = {};updates['flags.tour.'+chapter] = page;
User.set(updates);
var chap = tour[chapter], opts = chap._options;
opts.steps = [];
_.times(page, function(p){
@@ -259,9 +266,14 @@ function($rootScope, User, $timeout, $state, Analytics) {
chap.goTo(end);
} else {
chap.setCurrentStep(end);
if (page > 0) {
chap.init();
chap.goTo(page);
} else {
chap.start();
}
}
}
//Init and show the welcome tour (only after user is pulled from server & wrapped).
var watcher = $rootScope.$watch('User.user._wrapped', function(wrapped){

View File

@@ -60,6 +60,7 @@
"js/services/userServices.js",
"js/services/hallServices.js",
"js/services/pusherService.js",
"js/services/achievementServices.js",
"js/filters/money.js",
"js/filters/roundLargeNumbers.js",

View File

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

Before

Width:  |  Height:  |  Size: 30 KiB

After

Width:  |  Height:  |  Size: 30 KiB

View File

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

View File

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

View File

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 176 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

View File

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

View File

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 28 KiB

View File

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 308 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

View File

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

View File

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 32 KiB

View File

Before

Width:  |  Height:  |  Size: 171 KiB

After

Width:  |  Height:  |  Size: 171 KiB

View File

Before

Width:  |  Height:  |  Size: 169 KiB

After

Width:  |  Height:  |  Size: 169 KiB

View File

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 53 KiB

View File

Before

Width:  |  Height:  |  Size: 372 KiB

After

Width:  |  Height:  |  Size: 372 KiB

View File

Before

Width:  |  Height:  |  Size: 324 KiB

After

Width:  |  Height:  |  Size: 324 KiB

View File

Before

Width:  |  Height:  |  Size: 360 KiB

After

Width:  |  Height:  |  Size: 360 KiB

View File

Before

Width:  |  Height:  |  Size: 296 KiB

After

Width:  |  Height:  |  Size: 296 KiB

View File

Before

Width:  |  Height:  |  Size: 191 KiB

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

View File

@@ -231,8 +231,6 @@ script(type='text/ng-template', id='partials/options.settings.api.html')
h6=env.t('APIToken')
pre.prettyprint {{User.settings.auth.apiToken}}
small!=env.t("APITokenWarning")
h6=env.t('qrCode')
img.img-rendering-auto(src='https://chart.googleapis.com/chart?cht=qr&chs=200x200&chl=%7B%22address%22%3A%22https%3A%2F%2Fhabitrpg.com%22%2C%22user%22%3A%22{{user.id}}%22%2C%22key%22%3A%22{{User.settings.auth.apiToken}}%22%7D&choe=UTF-8&chld=L', alt='qrcode')
br
h3=env.t('thirdPartyApps')
ul

View File

@@ -63,7 +63,7 @@ script(type='text/ng-template', id='partials/options.social.hall.heroes.html')
.checkbox
label
input(type='checkbox', ng-model='hero.flags.chatRevoked')
| Chat Priveleges Revoked
| Chat Privileges Revoked
.form-group
.checkbox
label

View File

@@ -280,7 +280,7 @@ nav.toolbar(ng-controller='MenuCtrl')
ul.toolbar-bailey(ng-class='{inactive: !_expandedMenu.menu}')
li.toolbar-bailey-container(ng-if='user.flags.tour.intro!=-2')
.npc_justin_head.npc_bailey_head(popover='Continue Tour', popover-trigger='mouseenter', popover-placement='bottom', ng-click='Guide.goto("intro", user.flags.tour.intro, true)')
.npc_justin_head.npc_bailey_head(popover='Continue Tour', popover-trigger='mouseenter', popover-placement='bottom', ng-click='Guide.goto("intro", user.flags.tour.intro + 1, true)')
ul.toolbar-bailey(ng-class='{inactive: !_expandedMenu.menu}')
li.toolbar-bailey-container(ng-if='user.flags.newStuff')

View File

@@ -1,6 +1,6 @@
include ../avatar/generated_avatar
script(type='text/ng-template', id='modals/wonChallenge.html')
script(type='text/ng-template', id='modals/achievements/wonChallenge.html')
- var tweet = env.t('wonChallengeShare');
.modal-content(style='min-width:28em')
.modal-body.text-center

View File

@@ -11,60 +11,76 @@ block extraHead
style.
.press-img {
max-width: 500px;
max-height: 500px;
padding: 5px;
}
block content
h2= env.t('presskit')
div.jumbotron
h1= env.t('presskit')
p= env.t('presskitText')
p= env.t('presskitDownload')
| &nbsp;
a.btn.btn-success(href='/presskit/presskit.zip') presskit.zip
p
a.btn.btn-lg.btn-success(href='/presskit/presskit.zip') presskit.zip
h2= env.t('pkVideo')
| <iframe width="560" height="315" src="https://www.youtube.com/embed/hgdeJnSili0" frameborder="0" allowfullscreen></iframe>
-
var imgs = [
'Habitica Promo',
'Habitica Promo - Thin',
'Habitica Logo - Icon with Text',
'Habitica Logo - Text',
'Habitica Logo - Icon',
'Habitica Logo - iOS',
'Habitica Logo - Android',
var imgs = {
'Promo': [
'Promo',
'Promo - Thin'
],
'Logo': [
'Icon with Text',
'Habitica Gryphon',
'Boss - Basi-List',
'Boss - Stagnant Dishes',
'Boss - SnackLess Monster',
'Boss - Laundromancer',
'Boss - Battling the Ghost Stag',
'Boss - Necro-Vice',
'World Boss - Dread Drag\'on of Dilatory',
'Sample Screen - Tasks Page',
'Sample Screen - Market',
'Sample Screen - Equipment',
'Sample Screen - Guilds',
'Sample Screen - Challenges',
'Sample Screen - Tasks Page (iOS)',
'Sample Screen - Level Up (iOS)',
'Sample Screen - Pets (iOS)',
'Sample Screen - Party (iOS)',
'Sample Screen - Boss (iOS)'
'iOS',
'Android'
],
'Boss': [
'Basi-List',
'Stagnant Dishes',
'SnackLess Monster',
'Laundromancer',
'Necro-Vice',
'Battling the Ghost Stag',
'Dread Drag\'on of Dilatory'
],
'Samples': {
'Website': [
'Tasks Page',
'Equipment',
'Market',
'Guilds',
'Challenges'
],
'iOS': [
'Tasks Page',
'Level Up',
'Pets',
'Party',
'Boss'
],
'Android': [
'User',
'Tasks Page',
'Reward',
'Level Up',
'Tavern',
'Party'
]
}
}
//-#carousel.carousel.slide(data-ride='carousel')
ol.carousel-indicators
each img, i in imgs
li(class=i==0?'active':'', data-target='#carousel', data-slide-to=i)
.carousel-inner(role='listbox')
each img, i in imgs
.item(class=i==0?'active':'')
img(src="/presskit/#{img}.png")
//.carousel-caption=img
a.left.carousel-control(href='#carousel', role='button', data-slide='prev')
span.glyphicon.glyphicon-chevron-left(aria-hidden='true')
span.sr-only= env.t('previous')
a.right.carousel-control(href='#carousel', role='button', data-slide='next')
span.glyphicon.glyphicon-chevron-right(aria-hidden='true')
span.sr-only= env.t('next')
ul.list-unstyled
each img in imgs
li
img.img-rendering-auto.press-img(src="/presskit/#{img}.png")
each images, category in imgs
div
h2= env.t('pk' + category)
if Array.isArray(images)
each img in images
img.img-rendering-auto.press-img(src="/presskit/#{category}/#{img}.png")
else
each item, cat in images
div
h3= env.t('pk' + cat)
each img in item
img.img-rendering-auto.press-img(src="/presskit/#{category}/#{cat}/#{img}.png")