Compare commits

..

39 Commits

Author SHA1 Message Date
Kalista Payne 9a76be3ce2 fix(lint): moar lf 2026-05-12 15:48:45 -05:00
Kalista Payne 9231e338b7 fix(lint): line endings 2026-05-12 15:45:39 -05:00
Hafiz a5b38beadb Merge remote-tracking branch 'origin/develop' into fiz/summary_updates 2026-05-12 13:39:44 -05:00
Kalista Payne c581b88213 Summer 2026 Content (#15651)
* feat(content): June-August 2026

* fix(content): it's secretly a bright kite
2026-05-12 13:09:47 -05:00
Kalista Payne 0078d8f2b2 5.47.9 2026-05-12 11:38:53 -05:00
Weblate 54ea0aab18 Merge branch 'origin/develop' into Weblate. 2026-05-12 18:36:09 +02:00
Kalista Payne 24b2a5beb8 chore(git): update submodule 2026-05-12 11:34:50 -05:00
Kalista Payne fe9332dff4 fix(typo): missing word in Verdant Page item 2026-05-12 11:26:32 -05:00
Kalista Payne c6582e4c3c Squashed commit of the following:
commit 39e29846dd
Author: Kalista Payne <kalista@habitica.com>
Date:   Wed May 6 22:45:25 2026 -0500

    fix(lint): no-undef

commit 75bbb5a88a
Author: Kalista Payne <kalista@habitica.com>
Date:   Wed May 6 22:24:10 2026 -0500

    fix(paypal): cancel sub when payment skipped
2026-05-12 11:13:08 -05:00
Kalista Payne b4b7980eee Rate limit redux (#15650)
* Improve rate limit handling

* Improve rate limiter options

* Fix lint

* fixed url

---------

Co-authored-by: Phillip Thelen <phillip@thelen.space>
2026-05-12 11:11:00 -05:00
Weblate 6069fbd61f Translated using Weblate (French)
Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (French)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Ukrainian)

Currently translated at 74.3% (188 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Serbian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Serbian)

Currently translated at 52.0% (143 of 275 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Dutch)

Currently translated at 77.0% (215 of 279 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Dutch)

Currently translated at 99.2% (130 of 131 strings)

Translated using Weblate (Dutch)

Currently translated at 83.2% (243 of 292 strings)

Translated using Weblate (Dutch)

Currently translated at 86.8% (384 of 442 strings)

Translated using Weblate (Dutch)

Currently translated at 73.0% (2595 of 3551 strings)

Translated using Weblate (Dutch)

Currently translated at 98.9% (189 of 191 strings)

Translated using Weblate (Dutch)

Currently translated at 80.2% (699 of 871 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Spanish)

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Serbian)

Currently translated at 32.3% (54 of 167 strings)

Translated using Weblate (Serbian)

Currently translated at 26.9% (45 of 167 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (German)

Currently translated at 99.6% (940 of 943 strings)

Translated using Weblate (German)

Currently translated at 99.6% (940 of 943 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (German)

Currently translated at 85.0% (17 of 20 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Serbian)

Currently translated at 26.9% (45 of 167 strings)

Translated using Weblate (Bulgarian)

Currently translated at 54.6% (515 of 943 strings)

Translated using Weblate (Bulgarian)

Currently translated at 53.7% (507 of 943 strings)

Translated using Weblate (Bulgarian)

Currently translated at 53.4% (504 of 943 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Portuguese)

Currently translated at 98.0% (198 of 202 strings)

Translated using Weblate (Bulgarian)

Currently translated at 53.3% (503 of 943 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 96.4% (244 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 96.4% (244 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 91.3% (231 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 91.3% (231 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 86.1% (218 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 86.1% (218 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 85.7% (217 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 85.7% (217 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.9% (215 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.9% (215 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.5% (214 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.5% (214 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.1% (213 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 84.1% (213 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 83.7% (212 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 83.7% (212 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 83.3% (211 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 83.3% (211 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 82.6% (209 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 81.4% (206 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 81.4% (206 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 81.0% (205 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 81.0% (205 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 80.6% (204 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 80.6% (204 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 80.2% (203 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 80.2% (203 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 79.8% (202 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 79.8% (202 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 79.0% (200 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 79.0% (200 of 253 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (413 of 413 strings)

Co-authored-by: Antonio Lila Rusciano <antoniorusciano2005@gmail.com>
Co-authored-by: Carmen Ruiz Gomez <carmenruizgomez12@gmail.com>
Co-authored-by: Duggu Ghosh <duggu52d@gmail.com>
Co-authored-by: George <dyshlenko2@gmail.com>
Co-authored-by: Kris Fremen <me@krisfremen.com>
Co-authored-by: Lea Sophie Diekmann <dielea2012@gmail.com>
Co-authored-by: Mausam <mausam_b@protonmail.com>
Co-authored-by: Oscar Trente <vincent@lamblot.net>
Co-authored-by: Serhii <serzh.photograf@gmail.com>
Co-authored-by: Stelio Passaris <habitica@stelio.net>
Co-authored-by: Tanishq Saini <tanishqsaini005@gmail.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: elsaaa <elsachaton@lavache.com>
Co-authored-by: kran fall <wisal50835@codoteam.com>
Co-authored-by: Павел <goncharovps@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/bg/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/de/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/character/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/character/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/content/es/
Translate-URL: https://translate.habitica.com/projects/habitica/content/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/death/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/front/es/
Translate-URL: https://translate.habitica.com/projects/habitica/front/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/front/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/es/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/de/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/sr/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/nl/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Death
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Npc
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2026-05-12 14:54:18 +02:00
Phillip Thelen 38a591bdd1 Trim group chat messages (#15646)
Co-authored-by: Kalista Payne <sabrecat@gmail.com>
2026-05-07 12:31:43 -05:00
Hafiz a99e6f097a remove unused test 2026-05-07 12:20:43 -05:00
Hafiz 66fee441f2 scheduling warning and summary UI updates 2026-05-07 12:14:29 -05:00
Kalista Payne 2736d8acf3 5.47.8 2026-05-06 15:36:06 -05:00
Kalista Payne 8fe13dbb23 Revert "Improve rate limit handling (#15649)"
This reverts commit 1482f6c225.
2026-05-06 15:35:56 -05:00
Kalista Payne 4581bb9315 5.47.7 2026-05-06 14:57:31 -05:00
Weblate 2999212379 Merge branch 'origin/develop' into Weblate. 2026-05-06 21:55:54 +02:00
Weblate d2bd246e6e Translated using Weblate (Russian)
Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (Russian)

Currently translated at 82.3% (2924 of 3551 strings)

Translated using Weblate (German)

Currently translated at 98.6% (3503 of 3551 strings)

Translated using Weblate (Russian)

Currently translated at 98.4% (188 of 191 strings)

Translated using Weblate (Swedish)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Russian)

Currently translated at 82.2% (2919 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Russian)

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Russian)

Currently translated at 82.1% (2916 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Russian)

Currently translated at 75.4% (191 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Russian)

Currently translated at 98.0% (198 of 202 strings)

Translated using Weblate (Russian)

Currently translated at 75.0% (190 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Russian)

Currently translated at 75.0% (190 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (German)

Currently translated at 98.6% (3502 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 97.9% (3477 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 92.1% (3271 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 90.8% (3226 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 90.7% (3223 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 90.7% (3222 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 90.6% (3219 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 90.2% (3204 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.5% (3180 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.5% (3179 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.4% (3176 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.1% (3164 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.0% (3163 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.0% (3162 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 88.9% (3159 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 88.9% (3157 of 3551 strings)

Translated using Weblate (Portuguese)

Currently translated at 59.5% (2116 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 88.8% (3155 of 3551 strings)

Translated using Weblate (Portuguese)

Currently translated at 59.5% (2116 of 3551 strings)

Translated using Weblate (Portuguese)

Currently translated at 59.5% (2116 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 88.3% (3137 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 86.8% (3084 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 86.5% (3074 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 86.1% (3060 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 85.2% (3026 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 99.4% (866 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 99.3% (865 of 871 strings)

Translated using Weblate (Hebrew)

Currently translated at 92.4% (134 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 98.5% (858 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 95.5% (279 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 87.3% (255 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 83.9% (2982 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 83.8% (2978 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 83.8% (2978 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 83.8% (2978 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 95.7% (834 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Italian)

Currently translated at 81.3% (2889 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 95.7% (834 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 95.1% (829 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 94.7% (825 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 94.1% (820 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 93.3% (813 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 91.7% (799 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Italian)

Currently translated at 90.9% (792 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 81.1% (2883 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 89.6% (781 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 89.6% (781 of 871 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.6% (278 of 279 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (20 of 20 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.5% (3500 of 3551 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Italian)

Currently translated at 86.3% (252 of 292 strings)

Translated using Weblate (Italian)

Currently translated at 81.1% (2883 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Italian)

Currently translated at 81.0% (2879 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 96.6% (427 of 442 strings)

Translated using Weblate (Chinese (Traditional))

Currently translated at 75.0% (190 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 79.0% (200 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (Italian)

Currently translated at 80.6% (2864 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 77.4% (196 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 89.2% (777 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Italian)

Currently translated at 80.6% (2864 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 60.8% (154 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 85.8% (748 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (8 of 8 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (2 of 2 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (47 of 47 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (Italian)

Currently translated at 87.4% (244 of 279 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (20 of 20 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 80.5% (2862 of 3551 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Italian)

Currently translated at 37.5% (95 of 253 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (15 of 15 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (Italian)

Currently translated at 96.3% (265 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 96.3% (265 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 96.3% (265 of 275 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (Czech)

Currently translated at 92.1% (105 of 114 strings)

Translated using Weblate (Czech)

Currently translated at 91.2% (104 of 114 strings)

Translated using Weblate (Czech)

Currently translated at 85.6% (173 of 202 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (French)

Currently translated at 100.0% (3551 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.6% (3503 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Turkish)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 90.7% (3222 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 90.5% (3216 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 90.4% (3212 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 87.2% (3099 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 87.2% (3097 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 87.1% (3096 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 85.9% (3051 of 3551 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.5% (246 of 247 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 99.4% (190 of 191 strings)

Translated using Weblate (Polish)

Currently translated at 70.0% (14 of 20 strings)

Translated using Weblate (Polish)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Polish)

Currently translated at 99.5% (939 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 84.7% (3008 of 3551 strings)

Translated using Weblate (Russian)

Currently translated at 99.6% (940 of 943 strings)

Translated using Weblate (Russian)

Currently translated at 99.6% (940 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 83.7% (2974 of 3551 strings)

Translated using Weblate (French)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 49.0% (1740 of 3551 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.5% (3499 of 3551 strings)

Translated using Weblate (Portuguese (Brazil))

Currently translated at 98.4% (3497 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 80.7% (2868 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 79.8% (2835 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 79.2% (2815 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 77.4% (2749 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (Japanese)

Currently translated at 99.6% (868 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (94 of 94 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (58 of 58 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 75.8% (2693 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (253 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (275 of 275 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (279 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 74.9% (2662 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.0% (248 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Korean)

Currently translated at 41.8% (106 of 253 strings)

Translated using Weblate (Korean)

Currently translated at 99.1% (113 of 114 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Korean)

Currently translated at 95.0% (19 of 20 strings)

Translated using Weblate (Korean)

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (3475 of 3551 strings)

Translated using Weblate (Japanese)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Japanese)

Currently translated at 99.5% (246 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 73.8% (2621 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 93.5% (261 of 279 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (292 of 292 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.0% (248 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 73.8% (2621 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.0% (248 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 73.3% (2606 of 3551 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (145 of 145 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (20 of 20 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.0% (248 of 253 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (413 of 413 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (202 of 202 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (114 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (943 of 943 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (German)

Currently translated at 98.5% (3500 of 3551 strings)

Translated using Weblate (Ukrainian)

Currently translated at 66.0% (167 of 253 strings)

Translated using Weblate (Ukrainian)

Currently translated at 66.0% (167 of 253 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (247 of 247 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (20 of 20 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 55.0% (11 of 20 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 98.9% (90 of 91 strings)

Translated using Weblate (Spanish (Latin America))

Currently translated at 99.0% (200 of 202 strings)

Translated using Weblate (Czech)

Currently translated at 84.5% (797 of 943 strings)

Translated using Weblate (Czech)

Currently translated at 59.2% (163 of 275 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (56 of 56 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (871 of 871 strings)

Translated using Weblate (Japanese)

Currently translated at 97.8% (3473 of 3551 strings)

Translated using Weblate (Japanese)

Currently translated at 90.0% (18 of 20 strings)

Translated using Weblate (Japanese)

Currently translated at 98.4% (435 of 442 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 97.2% (284 of 292 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 98.9% (862 of 871 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 100.0% (91 of 91 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 97.3% (111 of 114 strings)

Translated using Weblate (English (United Kingdom))

Currently translated at 70.0% (14 of 20 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (131 of 131 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (442 of 442 strings)

Translated using Weblate (Chinese (Simplified))

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (191 of 191 strings)

Translated using Weblate (Ukrainian)

Currently translated at 59.6% (151 of 253 strings)

Translated using Weblate (Ukrainian)

Currently translated at 84.0% (732 of 871 strings)

Translated using Weblate (Czech)

Currently translated at 84.0% (793 of 943 strings)

Translated using Weblate (Czech)

Currently translated at 100.0% (167 of 167 strings)

Translated using Weblate (Ukrainian)

Currently translated at 53.2% (1890 of 3551 strings)

Co-authored-by: Aleksandr <aichernyaev@yandex.ru>
Co-authored-by: Andrea Brunato <andrea.brunato@live.com>
Co-authored-by: Antonio Lila Rusciano <antoniorusciano2005@gmail.com>
Co-authored-by: Antonio Rusciano <antoniorusciano2005@gmail.com>
Co-authored-by: Begümay Çınar <begumay@proton.me>
Co-authored-by: Daniel Costa Carvalho <danielcostacarvalho@gmail.com>
Co-authored-by: Dayra G.R <ale.ro.bless1501@gmail.com>
Co-authored-by: Deleted User <noreply+1630@weblate.org>
Co-authored-by: Igor <777igor93@gmail.com>
Co-authored-by: Illana Beatriz Rocha de Oliveira <dev.illanabeatriz@gmail.com>
Co-authored-by: Juhyung bang <juheng0912@gmail.com>
Co-authored-by: Karel <kcharlik@gmail.com>
Co-authored-by: Lenka Pavlíčková <lenkapavlickova2@email.cz>
Co-authored-by: Maria Morant <luisa.morant@yahoo.com>
Co-authored-by: Matej Boura <B.Matej@email.cz>
Co-authored-by: Omar Bertolla <scaram@icloud.com>
Co-authored-by: Sandra Marcial <sandramarcial80@gmail.com>
Co-authored-by: Sara Amit Cohen <saragirl93@gmail.com>
Co-authored-by: Serhii <serzh.photograf@gmail.com>
Co-authored-by: Sonia <sophishport@gmail.com>
Co-authored-by: Sophie LE MASLE <sophiesuff@gmail.com>
Co-authored-by: Stelio Passaris <habitica@stelio.net>
Co-authored-by: Sugo Gangotti <giacomo@ergonomia.it>
Co-authored-by: Summer_GUI <heyang94@163.com>
Co-authored-by: Toro Mor <thomas.bizer@gmx.de>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Wera <weramimi05@gmail.com>
Co-authored-by: Zhi Hao Li <zhihaoli000@gmail.com>
Co-authored-by: qazplm <513121975@qq.com>
Co-authored-by: Павел <goncharovps@gmail.com>
Co-authored-by: いんこ <ayakabooker@gmail.com>
Co-authored-by: 김수빈 <kmsb0319@gmail.com>
Co-authored-by: ? <importantdata78@gmail.com>
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/achievements/sv/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/it/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/backgrounds/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/it/
Translate-URL: https://translate.habitica.com/projects/habitica/challenge/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/character/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/character/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/character/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/character/it/
Translate-URL: https://translate.habitica.com/projects/habitica/character/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/communityguidelines/it/
Translate-URL: https://translate.habitica.com/projects/habitica/content/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/content/it/
Translate-URL: https://translate.habitica.com/projects/habitica/content/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/content/tr/
Translate-URL: https://translate.habitica.com/projects/habitica/contrib/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/death/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/death/it/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/defaulttasks/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/it/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/faq/zh_Hant/
Translate-URL: https://translate.habitica.com/projects/habitica/front/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/front/it/
Translate-URL: https://translate.habitica.com/projects/habitica/front/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/front/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/front/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/front/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/de/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/it/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/gear/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/fr/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/it/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/generic/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/it/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/groups/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/limited/it/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/messages/it/
Translate-URL: https://translate.habitica.com/projects/habitica/noscript/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/it/
Translate-URL: https://translate.habitica.com/projects/habitica/npc/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/overview/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/pets/it/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/quests/it/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/it/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/uk/
Translate-URL: https://translate.habitica.com/projects/habitica/questscontent/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/es_419/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/it/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ja/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/ko/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pl/
Translate-URL: https://translate.habitica.com/projects/habitica/rebirth/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/cs/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/settings/it/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/it/
Translate-URL: https://translate.habitica.com/projects/habitica/spells/zh_Hans/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/it/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/pt_BR/
Translate-URL: https://translate.habitica.com/projects/habitica/subscriber/ru/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/en_GB/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/he/
Translate-URL: https://translate.habitica.com/projects/habitica/tasks/it/
Translation: Habitica/Achievements
Translation: Habitica/Backgrounds
Translation: Habitica/Challenge
Translation: Habitica/Character
Translation: Habitica/Communityguidelines
Translation: Habitica/Content
Translation: Habitica/Contrib
Translation: Habitica/Death
Translation: Habitica/Defaulttasks
Translation: Habitica/Faq
Translation: Habitica/Front
Translation: Habitica/Gear
Translation: Habitica/Generic
Translation: Habitica/Groups
Translation: Habitica/Limited
Translation: Habitica/Messages
Translation: Habitica/Noscript
Translation: Habitica/Npc
Translation: Habitica/Overview
Translation: Habitica/Pets
Translation: Habitica/Quests
Translation: Habitica/Questscontent
Translation: Habitica/Rebirth
Translation: Habitica/Settings
Translation: Habitica/Spells
Translation: Habitica/Subscriber
Translation: Habitica/Tasks
2026-05-06 21:55:43 +02:00
Kalista Payne 1482f6c225 Improve rate limit handling (#15649)
* Improve rate limit handling

* fix(lint): blank lines

---------

Co-authored-by: Phillip Thelen <phillip@thelen.space>
2026-05-06 14:47:33 -05:00
Fiz 1178da3a26 fix(quests): stuck "you were invited" banner after accept/reject (#15647)
wrap the conditional group update and the user save in a single transaction so a failure                                                                                                              between them can't leave members[uid] and RSVPNeeded out of sync
2026-05-05 11:09:30 -05:00
Kalista Payne 819ed2b355 Task dropdown and keyboard navigation fixes (#15648)
* fix(css): kebab z, focus highlights

* fix(nav): better tab behavior
2026-04-30 17:00:21 -05:00
Kalista Payne a92999fc11 5.47.6 2026-04-10 11:49:43 -05:00
Kalista Payne 3489b88752 fix(auth): downgrade helmet 2026-04-10 11:42:27 -05:00
Kalista Payne 94bda30385 5.47.5 2026-04-09 12:54:31 -05:00
Kalista Payne e8bbdc2cb8 fix(auth): disable broken CSP for now 2026-04-09 12:44:27 -05:00
Kalista Payne d465efaf96 fix(test): we have 1 item sets now 2026-04-08 15:35:47 -05:00
Kalista Payne 3a08de7ab3 fix(sorting): rerender task column for accuracy 2026-04-08 15:35:47 -05:00
Phillip Thelen e6ffd69148 feat(analytics): initial Habitica-owned solution 2026-04-08 15:35:47 -05:00
Kalista Payne 746fcfff49 Warning to avoid SPI (sensitive personal information) (#15638)
* feat(tasks): warn about adding SPI

* fix(links): unmangle, distinct jumps

* fix(spi): unlink

* fix(lint): punctuations
2026-04-08 15:35:47 -05:00
Kalista Payne 8aa343d390 5.47.4 2026-04-08 15:35:33 -05:00
Kalista Payne d80c43c82a test(ipn): log IPN data for troubleshooting 2026-04-08 15:35:03 -05:00
Kalista Payne 80e4b8617a fix(csp): habitica.com isn't *.habitica.com 2026-04-07 16:00:36 -05:00
Hafiz d13cc871a2 formatting 2026-04-02 17:37:05 -05:00
Hafiz a2be823817 dailies monthly weeks of month summary
update monthly day-of-week scheduling summary, add scheduling summary to task form, and add 5th week warning
2026-04-02 17:30:02 -05:00
Phillip Thelen e4b76bd212 remove trailing space 2026-04-02 17:25:16 -05:00
Phillip Thelen 764cbfc6c3 fix lint 2026-04-02 17:25:16 -05:00
Phillip Thelen de31a8c5b8 Add Web UI to set email if apple does not provide one 2026-04-02 17:25:16 -05:00
Phillip Thelen af0d80bef8 apply email passed via body if it is missing from apple profile 2026-04-02 17:25:16 -05:00
247 changed files with 6384 additions and 4807 deletions
+1
View File
@@ -21,3 +21,4 @@ services:
timeout: 30s
start_period: 0s
retries: 30
+172 -256
View File
@@ -1,12 +1,12 @@
{
"name": "habitica",
"version": "5.47.3",
"version": "5.47.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "habitica",
"version": "5.47.3",
"version": "5.47.9",
"hasInstallScript": true,
"dependencies": {
"@babel/core": "^7.22.10",
@@ -46,7 +46,7 @@
"gulp.spritesmith": "^6.13.0",
"habitica-markdown": "^4.1.0",
"heapdump": "^0.3.15",
"helmet": "^8.1.0",
"helmet": "^4.6.0",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0",
"jsonwebtoken": "^9.0.2",
@@ -57,7 +57,7 @@
"micromustache": "^8.0.3",
"moment": "^2.29.4",
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"mongoose": "^8.23.0",
"morgan": "^1.10.1",
"nan": "^2.25.0",
"nconf": "^0.12.1",
@@ -2623,6 +2623,19 @@
"node": ">=12.0.0"
}
},
"node_modules/@google-cloud/trace-agent/node_modules/gcp-metadata": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^5.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@google-cloud/trace-agent/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -3051,9 +3064,9 @@
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.3.2.tgz",
"integrity": "sha512-QgA5AySqB27cGTXBFmnpifAi7HxoGUeezwo6p9dI03MuDB6Pp33zgclqVb6oVK3j6I9Vesg0+oojW2XxB59SGg==",
"version": "1.4.6",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.4.6.tgz",
"integrity": "sha512-y+x3H1xBZd38n10NZF/rEBlvDOOMQ6LKUTHqr8R9VkJ+mmQOYtJFxIlkkK8fZrtOiL6VixbOBWMbZGBdal3Z1g==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
@@ -6299,6 +6312,7 @@
"resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz",
"integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==",
"dev": true,
"license": "MIT",
"dependencies": {
"readable-stream": "^2.3.5",
"safe-buffer": "^5.1.1"
@@ -6308,13 +6322,15 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
"dev": true
"dev": true,
"license": "MIT"
},
"node_modules/bl/node_modules/readable-stream": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
"dev": true,
"license": "MIT",
"dependencies": {
"core-util-is": "~1.0.0",
"inherits": "~2.0.3",
@@ -6330,6 +6346,7 @@
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dev": true,
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.1.0"
}
@@ -11207,18 +11224,6 @@
"node": ">=12"
}
},
"node_modules/gcp-metadata": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
"dependencies": {
"gaxios": "^5.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
@@ -11962,6 +11967,19 @@
"node": ">=12"
}
},
"node_modules/google-auth-library/node_modules/gcp-metadata": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-5.3.0.tgz",
"integrity": "sha512-FNTkdNEnBdlqF2oatizolQqNANMrcqJt6AAYt99B3y1aLLC8Hc5IOBb+ZnnzllodEEf6xMBp6wRcBbc16fa65w==",
"license": "Apache-2.0",
"dependencies": {
"gaxios": "^5.0.0",
"json-bigint": "^1.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/google-auth-library/node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@@ -12780,11 +12798,12 @@
}
},
"node_modules/helmet": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz",
"integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==",
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/helmet/-/helmet-4.6.0.tgz",
"integrity": "sha512-HVqALKZlR95ROkrnesdhbbZJFi/rIVSoNq6f3jA/9u6MIbTsPh3xZwihjeI5+DO/2sOV6HMHooXcEOuwskHpTg==",
"license": "MIT",
"engines": {
"node": ">=18.0.0"
"node": ">=10.0.0"
}
},
"node_modules/hex2dec": {
@@ -15522,143 +15541,14 @@
}
},
"node_modules/mongodb": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
"integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
"dev": true,
"dependencies": {
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"optional-require": "^1.1.8",
"safe-buffer": "^5.1.2"
},
"engines": {
"node": ">=4"
},
"optionalDependencies": {
"saslprep": "^1.0.0"
},
"peerDependenciesMeta": {
"aws4": {
"optional": true
},
"bson-ext": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"mongodb-extjson": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
"version": "6.20.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz",
"integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==",
"license": "Apache-2.0",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz",
"integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
"version": "14.1.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.0.tgz",
"integrity": "sha512-jlf/foYIKywAt3x/XWKZ/3rz8OSJPiWktjmk891alJUEjiVxKX9LEO92qH3hv4aJ0mN3MWPvGMCy8jQi95xK4w==",
"license": "MIT",
"dependencies": {
"tr46": "^5.0.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongodb/node_modules/bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
"dev": true,
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/mongoose": {
"version": "8.9.7",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.9.7.tgz",
"integrity": "sha512-mvNXmU0V8qZzMR/qoK2mjT4Ti2ALdtfS0teK+twxhlGkwzOD76V02/zWajTu2MJ7QyEmZe9OWvnJsIY0iAuX3Q==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.1",
"kareem": "2.6.3",
"mongodb": "~6.12.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose/node_modules/kerberos": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/kerberos/-/kerberos-2.2.2.tgz",
"integrity": "sha512-42O7+/1Zatsc3MkxaMPpXcIl/ukIrbQaGoArZEAr6GcEi2qhfprOBYOPhj+YvSMJkEkdpTjApUx+2DuWaKwRhg==",
"hasInstallScript": true,
"optional": true,
"peer": true,
"dependencies": {
"node-addon-api": "^6.1.0",
"prebuild-install": "^7.1.2"
},
"engines": {
"node": ">=12.9.0"
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.12.0.tgz",
"integrity": "sha512-RM7AHlvYfS7jv7+BXund/kR64DryVI+cHbVAy9P61fnb1RcWZqOW1/Wj2YhqMCx+MuYhqTRGv7AwHBzmsCKBfA==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.9",
"bson": "^6.10.1",
"mongodb-connection-string-url": "^3.0.0"
"@mongodb-js/saslprep": "^1.3.0",
"bson": "^6.10.4",
"mongodb-connection-string-url": "^3.0.2"
},
"engines": {
"node": ">=16.20.1"
@@ -15669,7 +15559,7 @@
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"snappy": "^7.3.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
@@ -15696,104 +15586,77 @@
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz",
"integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==",
"license": "Apache-2.0",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^14.1.0 || ^13.0.0"
}
},
"node_modules/mongodb-connection-string-url/node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
"integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.1"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongodb-connection-string-url/node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/mongodb-connection-string-url/node_modules/whatwg-url": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
"integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
"license": "MIT",
"dependencies": {
"tr46": "^5.1.0",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=18"
}
},
"node_modules/mongoose": {
"version": "8.23.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.23.0.tgz",
"integrity": "sha512-Bul4Ha6J8IqzFrb0B1xpVzkC3S0sk43dmLSnhFOn8eJlZiLwL5WO6cRymmjaADdCMjUcCpj2ce8hZI6O4ZFSug==",
"license": "MIT",
"dependencies": {
"bson": "^6.10.4",
"kareem": "2.6.3",
"mongodb": "~6.20.0",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/mongoose/node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"optional": true,
"peer": true
},
"node_modules/mongoose/node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"optional": true,
"peer": true,
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/node-addon-api": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-6.1.0.tgz",
"integrity": "sha512-+eawOlIgy680F0kBzPUNFhMZGtJ1YmqM6l4+Crf4IkImjYrO/mqPwRMh352g23uIaQKFItcQ64I7KMaJxHgAVA==",
"optional": true,
"peer": true
},
"node_modules/mongoose/node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"optional": true,
"peer": true,
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/semver": {
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
"optional": true,
"peer": true,
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mongoose/node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true,
"peer": true,
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/monk": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/monk/-/monk-7.3.4.tgz",
@@ -15848,6 +15711,56 @@
"integrity": "sha512-jSTz73B/+pGTTvhu5Ym8xsG6+QqaWab53UXnXdNNlTijTdLvcHABCLJXudQiJxob5N1Mzr5EOSx5ziwn2sihPQ==",
"dev": true
},
"node_modules/monk/node_modules/bson": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/bson/-/bson-1.1.6.tgz",
"integrity": "sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=0.6.19"
}
},
"node_modules/monk/node_modules/mongodb": {
"version": "3.7.4",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.4.tgz",
"integrity": "sha512-K5q8aBqEXMwWdVNh94UQTwZ6BejVbFhh1uB6c5FKtPE9eUMZPUO3sRZdgIEcHSrAWmxzpG/FeODDKL388sqRmw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"bl": "^2.2.1",
"bson": "^1.1.4",
"denque": "^1.4.1",
"optional-require": "^1.1.8",
"safe-buffer": "^5.1.2"
},
"engines": {
"node": ">=4"
},
"optionalDependencies": {
"saslprep": "^1.0.0"
},
"peerDependenciesMeta": {
"aws4": {
"optional": true
},
"bson-ext": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"mongodb-extjson": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"node_modules/morgan": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz",
@@ -17194,10 +17107,11 @@
}
},
"node_modules/optional-require": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.8.tgz",
"integrity": "sha512-jq83qaUb0wNg9Krv1c5OQ+58EK+vHde6aBPzLvPPqJm89UQWsvSuFy9X/OSNJnFeSOKo7btE0n8Nl2+nE+z5nA==",
"version": "1.1.10",
"resolved": "https://registry.npmjs.org/optional-require/-/optional-require-1.1.10.tgz",
"integrity": "sha512-0r3OB9EIQsP+a5HVATHq2ExIy2q/Vaffoo4IAikW1spCYswhLxqWQS0i3GwS3AdY/OIP4SWZHLGz8CMU558PGw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"require-at": "^1.0.6"
},
@@ -18679,6 +18593,7 @@
"resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz",
"integrity": "sha512-7i1auJbMUrXEAZCOQ0VNJgmcT2VOKPRl2YGJwgpHpC9CE91Mv4/4UYIUm4chGJaI381ZDq1JUicFii64Hapd8g==",
"dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=4"
}
@@ -18977,6 +18892,7 @@
"resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz",
"integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==",
"dev": true,
"license": "MIT",
"optional": true,
"dependencies": {
"sparse-bitfield": "^3.0.3"
+3 -3
View File
@@ -1,7 +1,7 @@
{
"name": "habitica",
"description": "A habit tracker app which treats your goals like a Role Playing Game.",
"version": "5.47.3",
"version": "5.47.9",
"main": "./website/server/index.js",
"dependencies": {
"@babel/core": "^7.22.10",
@@ -41,7 +41,7 @@
"gulp.spritesmith": "^6.13.0",
"habitica-markdown": "^4.1.0",
"heapdump": "^0.3.15",
"helmet": "^8.1.0",
"helmet": "^4.6.0",
"in-app-purchase": "^1.11.3",
"js2xmlparser": "^5.0.0",
"jsonwebtoken": "^9.0.2",
@@ -52,7 +52,7 @@
"micromustache": "^8.0.3",
"moment": "^2.29.4",
"moment-recur": "git://github.com/HabitRPG/moment-recur.git#d3e8e6da0806f13b74dd2e4d7d9053e6a63db119",
"mongoose": "^8.9.5",
"mongoose": "^8.23.0",
"morgan": "^1.10.1",
"nan": "^2.25.0",
"nconf": "^0.12.1",
-560
View File
@@ -1,560 +0,0 @@
/* eslint-disable camelcase */
import nconf from 'nconf';
import Amplitude from 'amplitude';
import * as analyticsService from '../../../../website/server/libs/analyticsService';
describe('analyticsService', () => {
beforeEach(() => {
sandbox.stub(Amplitude.prototype, 'track').returns(Promise.resolve());
});
afterEach(() => {
sandbox.restore();
});
describe('#getServiceByEnvironment', () => {
it('returns mock methods when not in production', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
expect(analyticsService.getAnalyticsServiceByEnvironment())
.to.equal(analyticsService.mockAnalyticsService);
});
it('returns real methods when in production', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
expect(analyticsService.getAnalyticsServiceByEnvironment().track)
.to.equal(analyticsService.track);
expect(analyticsService.getAnalyticsServiceByEnvironment().trackPurchase)
.to.equal(analyticsService.trackPurchase);
});
});
describe('#track', () => {
let eventType; let
data;
beforeEach(() => {
eventType = 'Cron';
data = {
category: 'behavior',
uuid: 'unique-user-id',
resting: true,
cronCount: 5,
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
user: {
preferences: {
analyticsConsent: true,
},
},
};
});
context('Amplitude', () => {
it('calls out to amplitude', () => analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledOnce;
}));
it('uses a dummy user id if none is provided', () => {
delete data.uuid;
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
user_id: 'no-user-id-was-provided',
});
});
});
context('platform', () => {
it('logs web platform', () => {
data.headers = { 'x-client': 'habitica-web' };
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Web',
});
});
});
it('logs iOS platform', () => {
data.headers = { 'x-client': 'habitica-ios' };
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'iOS',
});
});
});
it('logs Android platform', () => {
data.headers = { 'x-client': 'habitica-android' };
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Android',
});
});
});
it('logs 3rd Party platform', () => {
data.headers = { 'x-client': 'some-third-party' };
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: '3rd Party',
});
});
});
it('logs unknown if headers are not passed in', () => {
delete data.headers;
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Unknown',
});
});
});
});
context('Operating System', () => {
it('sets default', () => {
data.headers = {
'x-client': 'third-party',
'user-agent': 'foo',
};
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'Other',
os_version: '0',
});
});
});
it('sets iOS', () => {
data.headers = {
'x-client': 'habitica-ios',
'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)',
};
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'iOS',
os_version: '9.3.0',
});
});
});
it('sets Android', () => {
data.headers = {
'x-client': 'habitica-android',
'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
};
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'Android',
os_version: '4.0.4',
});
});
});
it('sets Unknown if headers are not passed in', () => {
delete data.headers;
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: undefined,
os_version: undefined,
});
});
});
});
it('sends details about event', () => analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
category: 'behavior',
resting: true,
cronCount: 5,
},
});
}));
it('sends english item name for gear if itemKey is provided', () => {
data.itemKey = 'headAccessory_special_foxEars';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Fox Ears',
},
});
});
});
it('sends english item name for egg if itemKey is provided', () => {
data.itemKey = 'Wolf';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Wolf Egg',
},
});
});
});
it('sends english item name for food if itemKey is provided', () => {
data.itemKey = 'Cake_Skeleton';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Bare Bones Cake',
},
});
});
});
it('sends english item name for hatching potion if itemKey is provided', () => {
data.itemKey = 'Golden';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Golden Hatching Potion',
},
});
});
});
it('sends english item name for quest if itemKey is provided', () => {
data.itemKey = 'atom1';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Attack of the Mundane, Part 1: Dish Disaster!',
},
});
});
});
it('sends english item name for purchased spell if itemKey is provided', () => {
data.itemKey = 'seafoam';
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
itemKey: data.itemKey,
itemName: 'Seafoam',
},
});
});
});
it('sends user data if provided', () => {
const stats = {
class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
};
const user = {
stats,
contributor: { level: 1 },
purchased: { plan: { planId: 'foo-plan' } },
flags: { tour: { intro: -2 } },
habits: [{ _id: 'habit' }],
dailys: [{ _id: 'daily' }],
todos: [{ _id: 'todo' }],
rewards: [{ _id: 'reward' }],
balance: 12,
loginIncentives: 1,
preferences: {
analyticsConsent: true,
},
};
data.user = user;
return analyticsService.track(eventType, data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
user_properties: {
Class: 'wizard',
Experience: 5,
Gold: 23,
Health: 10,
Level: 4,
Mana: 30,
tutorialComplete: true,
'Number Of Tasks': {
habits: 1,
dailys: 1,
todos: 1,
rewards: 1,
},
contributorLevel: 1,
subscription: 'foo-plan',
balance: 12,
balanceGemAmount: 48,
loginIncentives: 1,
},
});
});
});
});
});
describe('#trackPurchase', () => {
let data;
beforeEach(() => {
data = {
uuid: 'user-id',
sku: 'paypal-checkout',
paymentMethod: 'PayPal',
itemPurchased: 'Gems',
purchaseValue: 8,
purchaseType: 'checkout',
gift: false,
quantity: 1,
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
user: {
preferences: {
analyticsConsent: true,
},
},
};
});
context('Amplitude', () => {
it('calls out to amplitude', () => analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledOnce;
}));
it('uses a dummy user id if none is provided', () => {
delete data.uuid;
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
user_id: 'no-user-id-was-provided',
});
});
});
context('platform', () => {
it('logs web platform', () => {
data.headers = { 'x-client': 'habitica-web' };
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Web',
});
});
});
it('logs iOS platform', () => {
data.headers = { 'x-client': 'habitica-ios' };
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'iOS',
});
});
});
it('logs Android platform', () => {
data.headers = { 'x-client': 'habitica-android' };
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Android',
});
});
});
it('logs 3rd Party platform', () => {
data.headers = { 'x-client': 'some-third-party' };
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: '3rd Party',
});
});
});
it('logs unknown if headers are not passed in', () => {
delete data.headers;
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
platform: 'Unknown',
});
});
});
});
context('Operating System', () => {
it('sets default', () => {
data.headers = {
'x-client': 'third-party',
'user-agent': 'foo',
};
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'Other',
os_version: '0',
});
});
});
it('sets iOS', () => {
data.headers = {
'x-client': 'habitica-ios',
'user-agent': 'Habitica/148 (iPhone; iOS 9.3; Scale/2.00)',
};
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'iOS',
os_version: '9.3.0',
});
});
});
it('sets Android', () => {
data.headers = {
'x-client': 'habitica-android',
'user-agent': 'Mozilla/5.0 (Linux; Android 4.0.4; Galaxy Nexus Build/IMM76B) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.133 Mobile Safari/535.19',
};
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: 'Android',
os_version: '4.0.4',
});
});
});
it('sets Unknown if headers are not passed in', () => {
delete data.headers;
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
os_name: undefined,
os_version: undefined,
});
});
});
});
it('sends details about purchase', () => analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
event_properties: {
gift: false,
itemPurchased: 'Gems',
paymentMethod: 'PayPal',
purchaseType: 'checkout',
quantity: 1,
sku: 'paypal-checkout',
},
});
}));
it('sends user data if provided', () => {
const stats = {
class: 'wizard', exp: 5, gp: 23, hp: 10, lvl: 4, mp: 30,
};
const user = {
stats,
contributor: { level: 1 },
purchased: { plan: { planId: 'foo-plan' } },
flags: { tour: { intro: -2 } },
habits: [{ _id: 'habit' }],
dailys: [{ _id: 'daily' }],
todos: [{ _id: 'todo' }],
rewards: [{ _id: 'reward' }],
preferences: {
analyticsConsent: true,
},
};
data.user = user;
return analyticsService.trackPurchase(data)
.then(() => {
expect(Amplitude.prototype.track).to.be.calledWithMatch({
user_properties: {
Class: 'wizard',
Experience: 5,
Gold: 23,
Health: 10,
Level: 4,
Mana: 30,
tutorialComplete: true,
'Number Of Tasks': {
habits: 1,
dailys: 1,
todos: 1,
rewards: 1,
},
contributorLevel: 1,
subscription: 'foo-plan',
},
});
});
});
});
});
describe('mockAnalyticsService', () => {
it('has stubbed track method', () => {
expect(analyticsService.mockAnalyticsService).to.respondTo('track');
});
it('has stubbed trackPurchase method', () => {
expect(analyticsService.mockAnalyticsService).to.respondTo('trackPurchase');
});
});
});
+116 -136
View File
@@ -13,7 +13,6 @@ import { cron, cronWrapper } from '../../../../website/server/libs/cron';
import { model as User } from '../../../../website/server/models/user';
import * as Tasks from '../../../../website/server/models/task';
import common from '../../../../website/common';
import * as analytics from '../../../../website/server/libs/analyticsService';
import { model as Group } from '../../../../website/server/models/group';
const CRON_TIMEOUT_WAIT = new Date(5 * 60 * 1000).getTime();
@@ -41,20 +40,17 @@ describe('cron', async () => {
},
},
});
sinon.spy(analytics, 'track');
});
afterEach(async () => {
if (clock !== null) clock.restore();
analytics.track.restore();
});
it('updates user.preferences.timezoneOffsetAtLastCron', async () => {
const timezoneUtcOffsetFromUserPrefs = -1;
await cron({
user, tasksByType, daysMissed, analytics, timezoneUtcOffsetFromUserPrefs,
user, tasksByType, daysMissed, timezoneUtcOffsetFromUserPrefs,
});
expect(user.preferences.timezoneOffsetAtLastCron).to.equal(1);
@@ -63,7 +59,7 @@ describe('cron', async () => {
it('resets user.items.lastDrop.count', async () => {
user.items.lastDrop.count = 4;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.items.lastDrop.count).to.equal(0);
});
@@ -71,26 +67,11 @@ describe('cron', async () => {
it('increments user cron count', async () => {
const cronCountBefore = user.flags.cronCount;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.flags.cronCount).to.be.greaterThan(cronCountBefore);
});
it('calls analytics', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(analytics.track.callCount).to.equal(1);
});
it('calls analytics when user is sleeping', async () => {
user.preferences.sleep = true;
await cron({
user, tasksByType, daysMissed, analytics,
});
expect(analytics.track.callCount).to.equal(1);
});
describe('end of the month perks', async () => {
beforeEach(async () => {
user.purchased.plan.customerId = 'subscribedId';
@@ -101,7 +82,7 @@ describe('cron', async () => {
user.purchased.plan.dateUpdated = new Date('2018-12-11');
clock = sinon.useFakeTimers(new Date('2019-01-29'));
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.mysteryItems.length).to.eql(2);
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
@@ -112,7 +93,7 @@ describe('cron', async () => {
user.purchased.plan.dateUpdated = new Date('2018-11-11');
clock = sinon.useFakeTimers(new Date('2019-01-29'));
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.mysteryItems.length).to.eql(4);
const filteredNotifications = user.notifications.filter(n => n.type === 'NEW_MYSTERY_ITEMS');
@@ -122,7 +103,7 @@ describe('cron', async () => {
it('resets plan.gemsBought on a new month', async () => {
user.purchased.plan.gemsBought = 10;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.gemsBought).to.equal(0);
});
@@ -131,7 +112,7 @@ describe('cron', async () => {
user.purchased.plan.gemsBought = 10;
user.purchased.plan.dateUpdated = undefined;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.gemsBought).to.equal(0);
});
@@ -142,7 +123,7 @@ describe('cron', async () => {
user.purchased.plan.gemsBought = 10;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.gemsBought).to.equal(10);
});
@@ -150,7 +131,7 @@ describe('cron', async () => {
it('resets plan.dateUpdated on a new month', async () => {
const currentMonth = moment().startOf('month');
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(moment(user.purchased.plan.dateUpdated).startOf('month').isSame(currentMonth)).to.eql(true);
});
@@ -158,7 +139,7 @@ describe('cron', async () => {
it('increments plan.consecutive.count', async () => {
user.purchased.plan.consecutive.count = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.count).to.equal(1);
});
@@ -166,7 +147,7 @@ describe('cron', async () => {
it('increments plan.cumulativeCount', async () => {
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.cumulativeCount).to.equal(1);
});
@@ -175,7 +156,7 @@ describe('cron', async () => {
user.purchased.plan.dateUpdated = moment().subtract(2, 'months').toDate();
user.purchased.plan.consecutive.count = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.count).to.equal(2);
});
@@ -184,7 +165,7 @@ describe('cron', async () => {
user.purchased.plan.dateUpdated = moment().subtract(3, 'months').toDate();
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.cumulativeCount).to.equal(3);
});
@@ -196,7 +177,7 @@ describe('cron', async () => {
user.purchased.plan.consecutive.trinkets = 1;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.trinkets).to.equal(1);
@@ -206,7 +187,7 @@ describe('cron', async () => {
user.purchased.plan.consecutive.gemCapExtra = 26;
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
@@ -214,7 +195,7 @@ describe('cron', async () => {
it('does not reset plan stats if we are before the last day of the cancelled month', async () => {
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.customerId).to.exist;
});
@@ -225,7 +206,7 @@ describe('cron', async () => {
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.customerId).to.not.exist;
@@ -264,7 +245,7 @@ describe('cron', async () => {
// Add 2 days so that we're sure we're not affected by any start-of-month effects
// e.g., from time zone oddness.
await cron({
user: user1, tasksByType, daysMissed, analytics,
user: user1, tasksByType, daysMissed,
});
expect(user1.purchased.plan.consecutive.count).to.equal(1);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(2);
@@ -276,7 +257,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user1, tasksByType, daysMissed, analytics,
user: user1, tasksByType, daysMissed,
});
expect(user1.purchased.plan.consecutive.count).to.equal(10);
expect(user1.purchased.plan.consecutive.trinkets).to.equal(11);
@@ -311,7 +292,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
user: user3, tasksByType, daysMissed,
});
expect(user3.purchased.plan.consecutive.count).to.equal(1);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(2);
@@ -323,7 +304,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user3, tasksByType, daysMissed, analytics,
user: user3, tasksByType, daysMissed,
});
expect(user3.purchased.plan.consecutive.count).to.equal(10);
expect(user3.purchased.plan.consecutive.trinkets).to.equal(11);
@@ -358,7 +339,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user6, tasksByType, daysMissed, analytics,
user: user6, tasksByType, daysMissed,
});
expect(user6.purchased.plan.consecutive.count).to.equal(1);
expect(user6.purchased.plan.consecutive.trinkets).to.equal(2);
@@ -391,7 +372,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
user: user12, tasksByType, daysMissed,
});
expect(user12.purchased.plan.consecutive.count).to.equal(1);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(2);
@@ -403,7 +384,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user12, tasksByType, daysMissed, analytics,
user: user12, tasksByType, daysMissed,
});
expect(user12.purchased.plan.consecutive.count).to.equal(10);
expect(user12.purchased.plan.consecutive.trinkets).to.equal(11);
@@ -439,7 +420,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user3g, tasksByType, daysMissed, analytics,
user: user3g, tasksByType, daysMissed,
});
expect(user3g.purchased.plan.consecutive.count).to.equal(1);
expect(user3g.purchased.plan.cumulativeCount).to.equal(1);
@@ -452,7 +433,7 @@ describe('cron', async () => {
.add(2, 'days')
.toDate());
await cron({
user: user3g, tasksByType, daysMissed, analytics,
user: user3g, tasksByType, daysMissed,
});
// subscription has been erased by now
expect(user3g.purchased.plan.consecutive.count).to.equal(0);
@@ -471,7 +452,7 @@ describe('cron', async () => {
it('resets plan.gemsBought on a new month', async () => {
user.purchased.plan.gemsBought = 10;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.gemsBought).to.equal(0);
});
@@ -482,14 +463,14 @@ describe('cron', async () => {
user.purchased.plan.gemsBought = 10;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.gemsBought).to.equal(10);
});
it('does not reset plan.dateUpdated on a new month', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.dateUpdated).to.be.empty;
});
@@ -497,7 +478,7 @@ describe('cron', async () => {
it('does not increment plan.consecutive.count', async () => {
user.purchased.plan.consecutive.count = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.count).to.equal(0);
});
@@ -505,7 +486,7 @@ describe('cron', async () => {
it('does not increment plan.cumulativeCount', async () => {
user.purchased.plan.cumulativeCount = 0;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.cumulativeCount).to.equal(0);
});
@@ -513,7 +494,7 @@ describe('cron', async () => {
it('does not increment plan.consecutive.trinkets when user has reached a month that is a multiple of 3', async () => {
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.trinkets).to.equal(0);
});
@@ -521,7 +502,7 @@ describe('cron', async () => {
it('does not increment plan.consecutive.gemCapExtra when user has reached a month that is a multiple of 3', async () => {
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(0);
});
@@ -530,7 +511,7 @@ describe('cron', async () => {
user.purchased.plan.consecutive.gemCapExtra = 26;
user.purchased.plan.consecutive.count = 5;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.consecutive.gemCapExtra).to.equal(26);
});
@@ -538,7 +519,7 @@ describe('cron', async () => {
it('does nothing to plan stats if we are before the last day of the cancelled month', async () => {
user.purchased.plan.dateTerminated = moment(new Date()).add({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.purchased.plan.customerId).to.not.exist;
});
@@ -564,7 +545,7 @@ describe('cron', async () => {
it('should make uncompleted todos redder', async () => {
const valueBefore = tasksByType.todos[0].value;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.todos[0].value).to.be.lessThan(valueBefore);
});
@@ -573,7 +554,7 @@ describe('cron', async () => {
tasksByType.todos[0].completed = true;
const valueBefore = tasksByType.todos[0].value;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.todos[0].value).to.equal(valueBefore);
});
@@ -582,7 +563,7 @@ describe('cron', async () => {
tasksByType.todos[0].completed = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.history.todos).to.be.lengthOf(1);
@@ -608,7 +589,7 @@ describe('cron', async () => {
expect(user.tasksOrder.todos).to.be.lengthOf(3);
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
// user.tasksOrder.todos should be filtered while tasks by type remains unchanged
@@ -635,7 +616,7 @@ describe('cron', async () => {
const original = user.tasksOrder.todos; // Preserve the original order
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
let listsAreEqual = true;
@@ -675,7 +656,7 @@ describe('cron', async () => {
tasksByType.dailys[0].everyX = 5;
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].isDue).to.be.false;
});
@@ -686,7 +667,7 @@ describe('cron', async () => {
tasksByType.dailys[0].everyX = 5;
tasksByType.dailys[0].startDate = moment().toDate();
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].isDue).to.exist;
});
@@ -696,14 +677,14 @@ describe('cron', async () => {
tasksByType.dailys[0].everyX = 5;
tasksByType.dailys[0].startDate = moment().add(1, 'days').toDate();
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].nextDue.length).to.eql(6);
});
it('should add history', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].history).to.be.lengthOf(1);
});
@@ -711,7 +692,7 @@ describe('cron', async () => {
it('should set tasks completed to false', async () => {
tasksByType.dailys[0].completed = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].completed).to.be.false;
});
@@ -720,7 +701,7 @@ describe('cron', async () => {
user.preferences.sleep = true;
tasksByType.dailys[0].completed = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].completed).to.be.false;
});
@@ -729,7 +710,7 @@ describe('cron', async () => {
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
tasksByType.dailys[0].completed = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
});
@@ -739,7 +720,7 @@ describe('cron', async () => {
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
tasksByType.dailys[0].completed = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
});
@@ -749,7 +730,7 @@ describe('cron', async () => {
tasksByType.dailys[0].checklist.push({ title: 'test', completed: false });
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.dailys[0].checklist[0].completed).to.be.false;
});
@@ -759,7 +740,7 @@ describe('cron', async () => {
const hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.hp).to.be.lessThan(hpBefore);
});
@@ -770,7 +751,7 @@ describe('cron', async () => {
const hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.hp).to.equal(hpBefore);
});
@@ -784,7 +765,7 @@ describe('cron', async () => {
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
cronOverride({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.hp).to.equal(hpBefore);
@@ -797,7 +778,7 @@ describe('cron', async () => {
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.hp).to.equal(hpBefore);
@@ -808,7 +789,7 @@ describe('cron', async () => {
let hpBefore = user.stats.hp;
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
const hpDifferenceOfFullyIncompleteDaily = hpBefore - user.stats.hp;
@@ -816,7 +797,7 @@ describe('cron', async () => {
tasksByType.dailys[0].checklist.push({ title: 'test', completed: true });
tasksByType.dailys[0].checklist.push({ title: 'test2', completed: false });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
const hpDifferenceOfPartiallyIncompleteDaily = hpBefore - user.stats.hp;
@@ -829,7 +810,7 @@ describe('cron', async () => {
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
const progress = await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(progress.down).to.equal(-1);
@@ -841,7 +822,7 @@ describe('cron', async () => {
tasksByType.dailys[0].startDate = moment(new Date()).subtract({ days: 1 });
const progress = await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(progress.down).to.equal(0);
@@ -862,7 +843,7 @@ describe('cron', async () => {
tasksByType.dailys[1].frequency = 'daily';
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.hp).to.equal(48);
@@ -886,7 +867,7 @@ describe('cron', async () => {
tasksByType.habits[0].down = false;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].value).to.be.lessThan(1);
@@ -897,7 +878,7 @@ describe('cron', async () => {
tasksByType.habits[0].up = false;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].value).to.be.lessThan(1);
@@ -909,7 +890,7 @@ describe('cron', async () => {
tasksByType.habits[0].down = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].value).to.equal(1);
@@ -928,7 +909,7 @@ describe('cron', async () => {
tasksByType.habits[0].counterDown = 1;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -941,7 +922,7 @@ describe('cron', async () => {
tasksByType.habits[0].counterDown = 1;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -955,7 +936,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -964,7 +945,7 @@ describe('cron', async () => {
// should reset
daysMissed = 8;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -988,7 +969,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -1002,7 +983,7 @@ describe('cron', async () => {
// should reset after user CDS
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1026,7 +1007,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -1036,7 +1017,7 @@ describe('cron', async () => {
// should reset
daysMissed = 2;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1060,7 +1041,7 @@ describe('cron', async () => {
// should reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1084,7 +1065,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -1098,7 +1079,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -1107,7 +1088,7 @@ describe('cron', async () => {
// should reset
daysMissed = 32;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1132,7 +1113,7 @@ describe('cron', async () => {
// should reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1156,7 +1137,7 @@ describe('cron', async () => {
// should not reset
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(1);
@@ -1166,7 +1147,7 @@ describe('cron', async () => {
// should reset
daysMissed = 2;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(tasksByType.habits[0].counterUp).to.equal(0);
@@ -1199,7 +1180,7 @@ describe('cron', async () => {
user.stats.lvl = 2;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.history.exp).to.have.lengthOf(1);
@@ -1212,7 +1193,7 @@ describe('cron', async () => {
tasksByType.dailys[0].isDue = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.achievements.perfect).to.equal(1);
@@ -1224,7 +1205,7 @@ describe('cron', async () => {
tasksByType.dailys[0].isDue = false;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.achievements.perfect).to.equal(0);
@@ -1238,7 +1219,7 @@ describe('cron', async () => {
const previousBuffs = user.stats.buffs.toObject();
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
@@ -1256,7 +1237,7 @@ describe('cron', async () => {
const previousBuffs = user.stats.buffs.toObject();
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
@@ -1280,7 +1261,7 @@ describe('cron', async () => {
};
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.equal(0);
@@ -1307,7 +1288,7 @@ describe('cron', async () => {
};
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.equal(0);
@@ -1333,7 +1314,7 @@ describe('cron', async () => {
};
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.equal(0);
@@ -1360,7 +1341,7 @@ describe('cron', async () => {
};
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.equal(0);
@@ -1381,7 +1362,7 @@ describe('cron', async () => {
const previousBuffs = user.stats.buffs.toObject();
cronOverride({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
@@ -1401,7 +1382,7 @@ describe('cron', async () => {
const previousBuffs = user.stats.buffs.toObject();
cronOverride({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.buffs.str).to.be.greaterThan(previousBuffs.str);
@@ -1420,7 +1401,7 @@ describe('cron', async () => {
tasksByType.dailys[0].completed = true;
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.mp).to.be.greaterThan(mpBefore);
@@ -1436,7 +1417,7 @@ describe('cron', async () => {
tasksByType.dailys[0].completed = true;
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.mp).to.equal(mpBefore);
@@ -1449,7 +1430,7 @@ describe('cron', async () => {
user.stats.mp = 120;
stubbedStatsComputed.returns(Object.assign(statsComputedRes, { maxMP: 100 }));
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.stats.mp).to.equal(common.statsComputed(user).maxMP);
@@ -1482,7 +1463,7 @@ describe('cron', async () => {
it('resets user progress', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.party.quest.progress.up).to.equal(0);
expect(user.party.quest.progress.down).to.equal(0);
@@ -1491,7 +1472,7 @@ describe('cron', async () => {
it('applies the user progress', async () => {
const progress = await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(progress.down).to.equal(-1);
});
@@ -1529,19 +1510,19 @@ describe('cron', async () => {
describe('login incentives', async () => {
it('increments incentive counter each cron', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(1);
user.lastCron = moment(new Date()).subtract({ days: 1 });
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(2);
});
it('pushes a notification of the day\'s incentive each cron', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.notifications.length).to.eql(1);
expect(user.notifications[0].type).to.eql('LOGIN_INCENTIVE');
@@ -1549,13 +1530,13 @@ describe('cron', async () => {
it('replaces previous notifications', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
const filteredNotifications = user.notifications.filter(n => n.type === 'LOGIN_INCENTIVE');
@@ -1566,7 +1547,7 @@ describe('cron', async () => {
it('increments loginIncentives by 1 even if days are skipped in between', async () => {
daysMissed = 3;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(1);
});
@@ -1574,14 +1555,14 @@ describe('cron', async () => {
it('increments loginIncentives by 1 even if user is sleeping', async () => {
user.preferences.sleep = true;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(1);
});
it('awards user bard robes if login incentive is 1', async () => {
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(1);
expect(user.items.gear.owned.armor_special_bardRobes).to.eql(true);
@@ -1591,7 +1572,7 @@ describe('cron', async () => {
it('awards user incentive backgrounds if login incentive is 2', async () => {
user.loginIncentives = 1;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(2);
expect(user.purchased.background.blue).to.eql(true);
@@ -1605,7 +1586,7 @@ describe('cron', async () => {
it('awards user Bard Hat if login incentive is 3', async () => {
user.loginIncentives = 2;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(3);
expect(user.items.gear.owned.head_special_bardHat).to.eql(true);
@@ -1615,7 +1596,7 @@ describe('cron', async () => {
it('awards user RoyalPurple Hatching Potion if login incentive is 4', async () => {
user.loginIncentives = 3;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(4);
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
@@ -1625,7 +1606,7 @@ describe('cron', async () => {
it('awards user a Chocolate, Meat and Pink Contton Candy if login incentive is 5', async () => {
user.loginIncentives = 4;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(5);
@@ -1639,7 +1620,7 @@ describe('cron', async () => {
it('awards user moon quest if login incentive is 7', async () => {
user.loginIncentives = 6;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(7);
expect(user.items.quests.moon1).to.eql(1);
@@ -1649,7 +1630,7 @@ describe('cron', async () => {
it('awards user RoyalPurple Hatching Potion if login incentive is 10', async () => {
user.loginIncentives = 9;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(10);
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
@@ -1659,7 +1640,7 @@ describe('cron', async () => {
it('awards user a Strawberry, Patato and Blue Contton Candy if login incentive is 14', async () => {
user.loginIncentives = 13;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(14);
@@ -1673,7 +1654,7 @@ describe('cron', async () => {
it('awards user a bard instrument if login incentive is 18', async () => {
user.loginIncentives = 17;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(18);
expect(user.items.gear.owned.weapon_special_bardInstrument).to.eql(true);
@@ -1683,7 +1664,7 @@ describe('cron', async () => {
it('awards user second moon quest if login incentive is 22', async () => {
user.loginIncentives = 21;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(22);
expect(user.items.quests.moon2).to.eql(1);
@@ -1693,7 +1674,7 @@ describe('cron', async () => {
it('awards user a RoyalPurple hatching potion if login incentive is 26', async () => {
user.loginIncentives = 25;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(26);
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
@@ -1703,7 +1684,7 @@ describe('cron', async () => {
it('awards user Fish, Milk, Rotten Meat and Honey if login incentive is 30', async () => {
user.loginIncentives = 29;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(30);
@@ -1718,7 +1699,7 @@ describe('cron', async () => {
it('awards user a RoyalPurple hatching potion if login incentive is 35', async () => {
user.loginIncentives = 34;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(35);
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
@@ -1728,7 +1709,7 @@ describe('cron', async () => {
it('awards user the third moon quest if login incentive is 40', async () => {
user.loginIncentives = 39;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(40);
expect(user.items.quests.moon3).to.eql(1);
@@ -1738,7 +1719,7 @@ describe('cron', async () => {
it('awards user a RoyalPurple hatching potion if login incentive is 45', async () => {
user.loginIncentives = 44;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(45);
expect(user.items.hatchingPotions.RoyalPurple).to.eql(1);
@@ -1748,7 +1729,7 @@ describe('cron', async () => {
it('awards user a saddle if login incentive is 50', async () => {
user.loginIncentives = 49;
await cron({
user, tasksByType, daysMissed, analytics,
user, tasksByType, daysMissed,
});
expect(user.loginIncentives).to.eql(50);
expect(user.items.food.Saddle).to.eql(1);
@@ -1766,7 +1747,6 @@ describe('cron wrapper', () => {
res = generateRes();
req = generateReq();
user = await res.locals.user.save();
res.analytics = analytics;
});
afterEach(() => {
+100
View File
@@ -0,0 +1,100 @@
import nconf from 'nconf';
import requireAgain from 'require-again';
import { model as User } from '../../../../website/server/models/user';
import { RegistrationEventModel } from '../../../../website/server/models/analytics/registrationEvent';
import { SubscriptionEventModel } from '../../../../website/server/models/analytics/subscriptionEvent';
describe('localAnalytics', () => {
let user;
let localAnalytics;
before(() => {
const nconfGetStub = sandbox.stub(nconf, 'get');
nconfGetStub.withArgs('ANALYTICS_DB').returns('analytics');
nconfGetStub.withArgs('DISABLE_LOCAL_ANALYTICS').returns(false);
localAnalytics = requireAgain('../../../../website/server/libs/localAnalytics');
});
beforeEach(async () => {
user = new User({
auth: {
local: {
username: 'username',
email: 'email@example.com',
},
},
registeredThrough: 'habitica-web',
});
});
describe('trackRegistrationEvent', () => {
afterEach(async () => {
await RegistrationEventModel.deleteMany({});
});
it('creates a registration event when a user registers', async () => {
user._id = '00000000-0000-0000-0000-000000000001';
await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.1' });
const registrationEvents = await RegistrationEventModel.find({ userId: user._id });
expect(registrationEvents).to.have.lengthOf(1);
expect(registrationEvents[0]).to.have.property('userId', user._id);
expect(registrationEvents[0]).to.have.property('ipAddress', '127.0.0.1');
});
it('saves the correct data to the database', async () => {
user._id = '00000000-0000-0000-0000-000000000002';
user.auth.google = { id: 'abc', emails: [{ value: 'email@example.com' }] };
await localAnalytics.trackRegistrationEvent({ user, ipAddress: '127.0.0.2' });
const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id });
expect(registrationEvent).to.have.property('userId', user._id);
expect(registrationEvent).to.have.property('ipAddress', '127.0.0.2');
expect(registrationEvent).to.have.property('authenticationMethod', 'google');
});
});
describe('trackSubscriptionEvent', () => {
afterEach(async () => {
await SubscriptionEventModel.deleteMany({});
});
it('creates a subscription event when a user subscribes', async () => {
user._id = '00000000-0000-0000-0000-000000000003';
await localAnalytics.trackSubscriptionEvent({
eventType: 'subscribed',
user,
paymentMethod: 'stripe',
customerId: 'cus_123',
planId: 'plan_123',
});
const subscriptionEvents = await SubscriptionEventModel.find({ userId: user._id });
expect(subscriptionEvents).to.have.lengthOf(1);
expect(subscriptionEvents[0]).to.have.property('userId', user._id);
expect(subscriptionEvents[0]).to.have.property('eventType', 'subscribed');
expect(subscriptionEvents[0]).to.have.property('paymentMethod', 'stripe');
expect(subscriptionEvents[0]).to.have.property('customerId', 'cus_123');
expect(subscriptionEvents[0]).to.have.property('planId', 'plan_123');
});
it('creates a subscription event with cancellation reason when a user cancels', async () => {
user._id = '00000000-0000-0000-0000-000000000004';
await localAnalytics.trackSubscriptionEvent({
eventType: 'cancelled',
user,
paymentMethod: 'stripe',
customerId: 'cus_456',
planId: 'plan_456',
cancellationReason: 'No longer needed',
});
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
expect(subscriptionEvent).to.have.property('userId', user._id);
expect(subscriptionEvent).to.have.property('eventType', 'cancelled');
expect(subscriptionEvent).to.have.property('paymentMethod', 'stripe');
expect(subscriptionEvent).to.have.property('customerId', 'cus_456');
expect(subscriptionEvent).to.have.property('planId', 'plan_456');
expect(subscriptionEvent).to.have.property('cancellationReason', 'No longer needed');
});
});
});
+64 -47
View File
@@ -3,7 +3,6 @@ import moment from 'moment';
import * as sender from '../../../../../website/server/libs/email';
import common from '../../../../../website/common';
import api from '../../../../../website/server/libs/payments/payments';
import * as analytics from '../../../../../website/server/libs/analyticsService';
import * as notifications from '../../../../../website/server/libs/pushNotifications';
import { model as User } from '../../../../../website/server/models/user';
import { translate as t } from '../../../../helpers/api-integration/v3';
@@ -13,6 +12,7 @@ import {
import * as worldState from '../../../../../website/server/libs/worldState';
import { TransactionModel } from '../../../../../website/server/models/transaction';
import { REPEATING_EVENTS } from '../../../../../website/common/script/content/constants/events';
import { SubscriptionEventModel } from '../../../../../website/server/models/analytics/subscriptionEvent';
describe('payments/index', () => {
let user;
@@ -36,8 +36,6 @@ describe('payments/index', () => {
sandbox.stub(sender, 'sendTxn');
sandbox.stub(user, 'sendMessage');
sandbox.stub(analytics.mockAnalyticsService, 'trackPurchase');
sandbox.stub(analytics.mockAnalyticsService, 'track');
sandbox.stub(notifications, 'sendNotification');
data = {
@@ -97,6 +95,16 @@ describe('payments/index', () => {
expect(recipient.items.pets['Jackalope-RoyalPurple']).to.eql(5);
});
it('tracks subscription events', async () => {
await api.createSubscription(data);
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: recipient._id });
expect(subscriptionEvent).to.exist;
expect(subscriptionEvent).to.have.property('eventType', 'subscribed');
expect(subscriptionEvent).to.have.property('userId', recipient._id);
expect(subscriptionEvent).to.have.property('planId', 'basic_3mo');
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
});
it('adds extra months to an existing subscription', async () => {
recipient.purchased.plan = plan;
@@ -298,28 +306,6 @@ describe('payments/index', () => {
expect(notifications.sendNotification).to.be.calledOnce;
});
it('tracks subscription purchase as gift', async () => {
await api.createSubscription(data);
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
uuid: user._id,
groupId: undefined,
itemPurchased: 'Subscription',
sku: 'payment method-subscription',
purchaseType: 'subscribe',
paymentMethod: data.paymentMethod,
quantity: 1,
gift: true,
purchaseValue: 15,
firstPurchase: true,
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
});
});
context('No Active Promotion', () => {
beforeEach(() => {
sinon.stub(worldState, 'getCurrentEventList').returns([]);
@@ -455,6 +441,16 @@ describe('payments/index', () => {
expect(user.purchased.plan.dateCreated).to.exist;
});
it('tracks subscription events', async () => {
await api.createSubscription(data);
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
expect(subscriptionEvent).to.exist;
expect(subscriptionEvent).to.have.property('userId', user._id);
expect(subscriptionEvent).to.have.property('ipAddress');
expect(subscriptionEvent).to.have.property('planId', 'basic_3mo');
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
});
it('sets plan.dateCreated if it did not previously exist', async () => {
expect(user.purchased.plan.dateCreated).to.not.exist;
@@ -543,29 +539,24 @@ describe('payments/index', () => {
expect(sender.sendTxn).to.be.calledWith(data.user, 'subscription-begins');
});
it('tracks subscription purchase', async () => {
await api.createSubscription(data);
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledOnce;
expect(analytics.mockAnalyticsService.trackPurchase).to.be.calledWith({
uuid: user._id,
groupId: undefined,
itemPurchased: 'Subscription',
sku: 'payment method-subscription',
purchaseType: 'subscribe',
paymentMethod: data.paymentMethod,
quantity: 1,
gift: false,
purchaseValue: 15,
firstPurchase: true,
headers: {
'x-client': 'habitica-web',
'user-agent': '',
},
});
});
context('Upgrades subscription', () => {
it('tracks subscription events', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
data.sub.key = 'basic_6mo';
data.updatedFrom = { key: 'basic_earned' };
await api.createSubscription(data);
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_6mo' });
expect(subscriptionEvent).to.exist;
expect(subscriptionEvent).to.have.property('eventType', 'upgraded');
expect(subscriptionEvent).to.have.property('userId', user._id);
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
});
it('from basic_earned to basic_6mo', async () => {
data.sub.key = 'basic_earned';
expect(user.purchased.plan.planId).to.not.exist;
@@ -608,6 +599,23 @@ describe('payments/index', () => {
});
context('Downgrades subscription', () => {
it('tracks subscription events', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
await api.createSubscription(data);
data.sub.key = 'basic_earned';
data.updatedFrom = { key: 'basic_6mo' };
await api.createSubscription(data);
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id, planId: 'basic_earned' });
expect(subscriptionEvent).to.exist;
expect(subscriptionEvent).to.have.property('eventType', 'downgraded');
expect(subscriptionEvent).to.have.property('userId', user._id);
expect(subscriptionEvent).to.have.property('paymentMethod', 'Payment Method');
});
it('from basic_6mo to basic_earned', async () => {
data.sub.key = 'basic_6mo';
expect(user.purchased.plan.planId).to.not.exist;
@@ -1136,6 +1144,15 @@ describe('payments/index', () => {
expect(daysTillTermination).to.be.within(29, 30); // 1 month +/- 1 days
});
it('tracks subscription events', async () => {
await api.cancelSubscription(data);
const subscriptionEvent = await SubscriptionEventModel.findOne({ userId: user._id });
expect(subscriptionEvent).to.exist;
expect(subscriptionEvent).to.have.property('eventType', 'cancelled');
expect(subscriptionEvent).to.have.property('userId', user._id);
});
it('adds extraMonths to dateTerminated value', async () => {
user.purchased.plan.extraMonths = 2;
@@ -1,50 +0,0 @@
/* eslint-disable global-require */
import nconf from 'nconf';
import requireAgain from 'require-again';
import {
generateRes,
generateReq,
generateNext,
} from '../../../helpers/api-unit.helper';
import * as analyticsService from '../../../../website/server/libs/analyticsService';
describe('analytics middleware', () => {
let res; let req; let
next;
const pathToAnalyticsMiddleware = '../../../../website/server/middlewares/analytics';
beforeEach(() => {
res = generateRes();
req = generateReq();
next = generateNext();
});
it('attaches analytics object to res', () => {
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
attachAnalytics(req, res, next);
expect(res.analytics).to.exist;
});
it('attaches stubbed methods for non-prod environments', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(false);
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
attachAnalytics(req, res, next);
expect(res.analytics.track).to.eql(analyticsService.mockAnalyticsService.track);
expect(res.analytics.trackPurchase).to.eql(analyticsService.mockAnalyticsService.trackPurchase);
});
it('attaches real methods for prod environments', () => {
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
const attachAnalytics = requireAgain(pathToAnalyticsMiddleware).default;
attachAnalytics(req, res, next);
expect(res.analytics.track).to.eql(analyticsService.track);
expect(res.analytics.trackPurchase).to.eql(analyticsService.trackPurchase);
});
});
+24 -12
View File
@@ -32,7 +32,8 @@ describe('rateLimiter middleware', () => {
it('is disabled when the env var is not defined', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns(undefined);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
@@ -43,7 +44,8 @@ describe('rateLimiter middleware', () => {
it('is disabled when the env var is an not "true"', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('false');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
@@ -55,7 +57,8 @@ describe('rateLimiter middleware', () => {
it('does not throw when there are available points', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
@@ -77,7 +80,8 @@ describe('rateLimiter middleware', () => {
sandbox.stub(RateLimiterMemory.prototype, 'consume')
.returns(Promise.reject(new Error('Unknown error.')));
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
await attachRateLimiter(req, res, next);
expect(next).to.have.been.calledOnce;
@@ -92,7 +96,8 @@ describe('rateLimiter middleware', () => {
it('does not throw when LIVELINESS_PROBE_KEY is correct', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.query.liveliness = 'abc';
await attachRateLimiter(req, res, next);
@@ -107,7 +112,8 @@ describe('rateLimiter middleware', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('abc');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.query.liveliness = 'das';
await attachRateLimiter(req, res, next);
@@ -124,7 +130,8 @@ describe('rateLimiter middleware', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns(undefined);
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
await attachRateLimiter(req, res, next);
@@ -140,7 +147,8 @@ describe('rateLimiter middleware', () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('LIVELINESS_PROBE_KEY').returns('');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.query.liveliness = '';
await attachRateLimiter(req, res, next);
@@ -156,7 +164,8 @@ describe('rateLimiter middleware', () => {
it('throws when there are no available points remaining', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
// call for 31 times
for (let i = 0; i < 31; i += 1) {
@@ -180,7 +189,8 @@ describe('rateLimiter middleware', () => {
it('uses the user id if supplied or the ip address', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(1);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.ip = 1;
await attachRateLimiter(req, res, next);
@@ -210,7 +220,8 @@ describe('rateLimiter middleware', () => {
it('applies increased cost for registration calls with and without user id', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_REGISTRATION_COST').returns(3);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.path = '/api/v4/user/auth/local/register';
req.ip = 1;
@@ -241,7 +252,8 @@ describe('rateLimiter middleware', () => {
it('applies increased cost for unauthenticated API calls', async () => {
nconfGetStub.withArgs('RATE_LIMITER_ENABLED').returns('true');
nconfGetStub.withArgs('RATE_LIMITER_IP_COST').returns(10);
const attachRateLimiter = requireAgain(pathToRateLimiter).default;
const setupRateLimiter = requireAgain(pathToRateLimiter).default;
const attachRateLimiter = setupRateLimiter();
req.ip = 1;
await attachRateLimiter(req, res, next);
+54
View File
@@ -6,6 +6,8 @@ import {
SPAM_MESSAGE_LIMIT,
SPAM_MIN_EXEMPT_CONTRIB_LEVEL,
SPAM_WINDOW_LENGTH,
MAX_CHAT_COUNT,
MAX_SUBBED_GROUP_CHAT_COUNT,
INVITES_LIMIT,
model as Group,
} from '../../../../website/server/models/group';
@@ -18,6 +20,7 @@ import {
import * as email from '../../../../website/server/libs/email';
import { TAVERN_ID } from '../../../../website/common/script/constants';
import shared from '../../../../website/common';
import { chatModel as Chat } from '../../../../website/server/models/message';
describe('Group Model', () => {
let party; let questLeader; let participatingMember;
@@ -1356,6 +1359,29 @@ describe('Group Model', () => {
});
});
describe('#getEffectiveChatLimit', () => {
it('returns the correct chat limit', () => {
const group = new Group();
expect(group.getEffectiveChatLimit()).to.eql(MAX_CHAT_COUNT);
});
it('returns the passed limit if it is lower than the max', () => {
const group = new Group();
expect(group.getEffectiveChatLimit(10)).to.eql(10);
});
it('returns the max if the passed limit is higher', () => {
const group = new Group();
expect(group.getEffectiveChatLimit(MAX_CHAT_COUNT + 10)).to.eql(MAX_CHAT_COUNT);
});
it('returns the max for group plans', () => {
const group = new Group();
group.purchased.plan.customerId = '110002222333';
expect(group.getEffectiveChatLimit()).to.eql(MAX_SUBBED_GROUP_CHAT_COUNT);
});
});
describe('#sendChat', () => {
beforeEach(() => {
sandbox.spy(User, 'updateOne');
@@ -1462,6 +1488,34 @@ describe('Group Model', () => {
});
});
describe('#trimChat', () => {
it('Only checks last message when not enough messages to trim', async () => {
sandbox.spy(Chat, 'find');
sandbox.spy(Chat, 'deleteMany');
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await party.trimChat();
expect(Chat.find).to.be.calledOnce;
expect(Chat.deleteMany).to.not.be.called;
expect(await Chat.countDocuments({ groupId: party._id })).to.eql(3);
});
it('Deletes messages over the limit', async () => {
sandbox.spy(Chat, 'find');
sandbox.spy(Chat, 'deleteMany');
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await Chat.insertOne({ groupId: party._id, timestamp: new Date() });
await party.trimChat(1);
expect(Chat.find).to.be.calledOnce;
expect(Chat.deleteMany).to.be.calledOnce;
expect(await Chat.countDocuments({ groupId: party._id })).to.eql(1);
});
});
describe('#startQuest', () => {
context('Failure Conditions', () => {
it('throws an error if group is not a party', async () => {
@@ -1,19 +0,0 @@
import {
generateUser,
requester,
} from '../../../../helpers/api-integration/v3';
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
describe('POST /analytics/track/:eventName', () => {
it('calls res.analytics', async () => {
const user = await generateUser();
sandbox.spy(analytics, 'track');
const requestWithHeaders = requester(user, { 'x-client': 'habitica-web' });
await requestWithHeaders.post('/analytics/track/eventName', { data: 'example' }, { 'x-client': 'habitica-web' });
expect(analytics.track).to.be.calledOnce;
expect(analytics.track).to.be.calledWith('eventName', sandbox.match({ data: 'example' }));
sandbox.restore();
});
});
@@ -91,6 +91,23 @@ describe('POST /groups/:groupId/quests/accept', () => {
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
});
it('heals stuck RSVPNeeded when group already has the user accepted', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
await partyMembers[0].sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
expect(res).to.exist;
await partyMembers[0].sync();
await questingGroup.sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(true);
});
it('does not accept invite for a quest already underway', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
@@ -100,6 +100,23 @@ describe('POST /groups/:groupId/quests/reject', () => {
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.false;
});
it('heals stuck RSVPNeeded when group already has the user rejected', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
await partyMembers[0].updateOne({ 'party.quest.RSVPNeeded': true });
await partyMembers[0].sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.be.true;
const res = await partyMembers[0].post(`/groups/${questingGroup._id}/quests/reject`);
expect(res).to.exist;
await partyMembers[0].sync();
await questingGroup.sync();
expect(partyMembers[0].party.quest.RSVPNeeded).to.equal(false);
expect(questingGroup.quest.members[partyMembers[0]._id]).to.equal(false);
});
it('return an error when a user rejects an invite already accepted', async () => {
await leader.post(`/groups/${questingGroup._id}/quests/invite/${PET_QUEST}`);
await partyMembers[0].post(`/groups/${questingGroup._id}/quests/accept`);
@@ -1,7 +1,6 @@
import {
generateUser,
} from '../../../../helpers/api-integration/v3';
import { mockAnalyticsService as analytics } from '../../../../../website/server/libs/analyticsService';
describe('POST /user/sleep', () => {
let user;
@@ -23,15 +22,4 @@ describe('POST /user/sleep', () => {
await user.sync();
expect(user.preferences.sleep).to.be.false;
});
it('sends sleep status to analytics service', async () => {
sandbox.spy(analytics, 'track');
await user.post('/user/sleep');
await user.sync();
expect(analytics.track).to.be.calledOnce;
expect(analytics.track).to.be.calledWith('sleep', sandbox.match.has('status', user.preferences.sleep));
sandbox.restore();
});
});
@@ -9,6 +9,7 @@ import {
} from '../../../../../helpers/api-integration/v3';
import { ApiUser } from '../../../../../helpers/api-integration/api-classes';
import { encrypt } from '../../../../../../website/server/libs/encryption';
import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent';
function generateRandomUserName () {
return (Date.now() + uuid()).substring(0, 20);
@@ -41,6 +42,25 @@ describe('POST /user/auth/local/register', () => {
expect(user.newUser).to.eql(true);
});
it('tracks a registration event', async () => {
const username = generateRandomUserName();
const email = `${username}@example.com`;
const password = 'password';
const user = await api.post('/user/auth/local/register', {
username,
email,
password,
confirmPassword: password,
});
const registrationEvent = await RegistrationEventModel.findOne({ userId: user._id });
expect(registrationEvent).to.exist;
expect(registrationEvent).to.have.property('userId', user._id);
expect(registrationEvent).to.have.property('ipAddress');
expect(registrationEvent).to.have.property('authenticationMethod', 'local');
});
it('registers a new user and sets verifiedUsername to true', async () => {
const username = generateRandomUserName();
const email = `${username}@example.com`;
@@ -7,6 +7,7 @@ import {
getProperty,
} from '../../../../../helpers/api-integration/v3';
import apiErrorMessages from '../../../../../../website/common/script/errors/apiErrorMessages';
import { RegistrationEventModel } from '../../../../../../website/server/models/analytics/registrationEvent';
describe('POST /user/auth/social', () => {
let api;
@@ -65,6 +66,19 @@ describe('POST /user/auth/social', () => {
await expect(getProperty('users', response.id, 'profile.name')).to.eventually.equal('a google user');
});
it('tracks a registration event', async () => {
const socialUser = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
});
const registrationEvent = await RegistrationEventModel.findOne({ userId: socialUser.id });
expect(registrationEvent).to.exist;
expect(registrationEvent).to.have.property('userId', socialUser.id);
expect(registrationEvent).to.have.property('ipAddress');
expect(registrationEvent).to.have.property('authenticationMethod', 'google');
});
it('includes sanitized version of provided username', async () => {
const response = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
@@ -231,6 +245,17 @@ describe('POST /user/auth/social', () => {
expect(response.newUser).to.be.false;
});
it('does not track a registration event for existing users', async () => {
const beforeEvents = await RegistrationEventModel.find({ userId: user._id });
await user.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
network,
});
const registrationEvents = await RegistrationEventModel.find({ userId: user._id });
expect(registrationEvents).to.have.lengthOf(beforeEvents.length);
});
it('does not log into other account if social auth already exists', async () => {
const registerResponse = await api.post(endpoint, {
authResponse: { access_token: randomAccessToken }, // eslint-disable-line camelcase
+1 -10
View File
@@ -13,7 +13,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
describe('shared.ops.buy', () => {
let user;
const analytics = { track () {} };
beforeEach(() => {
user = generateUser({
@@ -32,12 +31,6 @@ describe('shared.ops.buy', () => {
},
},
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
it('returns error when key is not provided', async () => {
@@ -51,10 +44,8 @@ describe('shared.ops.buy', () => {
it('buys health potion', async () => {
user.stats.hp = 30;
await buy(user, { params: { key: 'potion' } }, analytics);
await buy(user, { params: { key: 'potion' } });
expect(user.stats.hp).to.eql(45);
expect(analytics.track).to.be.calledOnce;
});
it('adds equipment to inventory', async () => {
+3 -7
View File
@@ -29,10 +29,9 @@ describe('shared.ops.buyArmoire', () => {
const YIELD_EQUIPMENT = 0.5;
const YIELD_FOOD = 0.7;
const YIELD_EXP = 0.9;
const analytics = { track () {} };
async function buyArmoire (_user, _req, _analytics) {
const buyOp = new BuyArmoireOperation(_user, _req, _analytics);
async function buyArmoire (_user, _req) {
const buyOp = new BuyArmoireOperation(_user, _req);
return buyOp.purchase();
}
@@ -50,12 +49,10 @@ describe('shared.ops.buyArmoire', () => {
user.items.food = {};
sandbox.stub(randomValFns, 'trueRandom');
sinon.stub(analytics, 'track');
});
afterEach(() => {
randomValFns.trueRandom.restore();
analytics.track.restore();
});
context('failure conditions', () => {
@@ -147,7 +144,7 @@ describe('shared.ops.buyArmoire', () => {
expect(_.size(user.items.gear.owned)).to.equal(2);
await buyArmoire(user, {}, analytics);
await buyArmoire(user, {});
expect(_.size(user.items.gear.owned)).to.equal(3);
@@ -155,7 +152,6 @@ describe('shared.ops.buyArmoire', () => {
expect(armoireCount).to.eql(_.size(getFullArmoire()) - 2);
expect(user.stats.gp).to.eql(100);
expect(analytics.track).to.be.calledTwice;
});
});
});
+3 -12
View File
@@ -1,6 +1,5 @@
/* eslint-disable camelcase */
import sinon from 'sinon'; // eslint-disable-line no-shadow
import {
generateUser,
} from '../../../helpers/common.helper';
@@ -11,15 +10,14 @@ import i18n from '../../../../website/common/script/i18n';
import { BuyGemOperation } from '../../../../website/common/script/ops/buy/buyGem';
import planGemLimits from '../../../../website/common/script/libs/planGemLimits';
async function buyGem (user, req, analytics) {
const buyOp = new BuyGemOperation(user, req, analytics);
async function buyGem (user, req) {
const buyOp = new BuyGemOperation(user, req);
return buyOp.purchase();
}
describe('shared.ops.buyGem', () => {
let user;
const analytics = { track () {} };
const goldPoints = 40;
const gemsBought = 40;
const userGemAmount = 10;
@@ -35,23 +33,16 @@ describe('shared.ops.buyGem', () => {
},
},
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
context('Gems', () => {
it('purchases gems', async () => {
const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } }, analytics);
const [, message] = await buyGem(user, { params: { type: 'gems', key: 'gem' } });
expect(message).to.equal(i18n.t('plusGem', { count: 1 }));
expect(user.balance).to.equal(userGemAmount + 0.25);
expect(user.purchased.plan.gemsBought).to.equal(1);
expect(user.stats.gp).to.equal(goldPoints - planGemLimits.convRate);
expect(analytics.track).to.be.calledOnce;
});
it('purchases gems with a different language than the default', async () => {
+3 -10
View File
@@ -10,10 +10,9 @@ import i18n from '../../../../website/common/script/i18n';
describe('shared.ops.buyHealthPotion', () => {
let user;
const analytics = { track () {} };
async function buyHealthPotion (_user, _req, _analytics) {
const buyOp = new BuyHealthPotionOperation(_user, _req, _analytics);
async function buyHealthPotion (_user, _req) {
const buyOp = new BuyHealthPotionOperation(_user, _req);
return buyOp.purchase();
}
@@ -32,19 +31,13 @@ describe('shared.ops.buyHealthPotion', () => {
},
stats: { gp: 200 },
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
context('Potion', () => {
it('recovers 15 hp', async () => {
user.stats.hp = 30;
await buyHealthPotion(user, {}, analytics);
await buyHealthPotion(user, {});
expect(user.stats.hp).to.eql(45);
expect(analytics.track).to.be.calledOnce;
});
it('does not increase hp above 50', async () => {
+5 -9
View File
@@ -13,15 +13,14 @@ import {
import i18n from '../../../../website/common/script/i18n';
import { errorMessage } from '../../../../website/common/script/libs/errorMessage';
async function buyGear (user, req, analytics) {
const buyOp = new BuyMarketGearOperation(user, req, analytics);
async function buyGear (user, req) {
const buyOp = new BuyMarketGearOperation(user, req);
return buyOp.purchase();
}
describe('shared.ops.buyMarketGear', () => {
let user;
const analytics = { track () {} };
let clock;
beforeEach(() => {
@@ -47,14 +46,12 @@ describe('shared.ops.buyMarketGear', () => {
sinon.stub(shared, 'randomVal');
sinon.stub(shared.onboarding, 'checkOnboardingStatus');
sinon.stub(shared.fns, 'predictableRandom');
sinon.stub(analytics, 'track');
});
afterEach(() => {
shared.randomVal.restore();
shared.fns.predictableRandom.restore();
shared.onboarding.checkOnboardingStatus.restore();
analytics.track.restore();
if (clock) {
clock.restore();
@@ -65,7 +62,7 @@ describe('shared.ops.buyMarketGear', () => {
it('adds equipment to inventory', async () => {
user.stats.gp = 31;
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
await buyGear(user, { params: { key: 'armor_warrior_1' } });
expect(user.items.gear.owned).to.eql({
weapon_warrior_0: true,
@@ -92,13 +89,12 @@ describe('shared.ops.buyMarketGear', () => {
eyewear_special_whiteHalfMoon: true,
eyewear_special_yellowHalfMoon: true,
});
expect(analytics.track).to.be.calledOnce;
});
it('adds the onboarding achievement to the user and checks the onboarding status', async () => {
user.stats.gp = 31;
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
await buyGear(user, { params: { key: 'armor_warrior_1' } });
expect(user.addAchievement).to.be.calledOnce;
expect(user.addAchievement).to.be.calledWith('purchasedEquipment');
@@ -111,7 +107,7 @@ describe('shared.ops.buyMarketGear', () => {
user.stats.gp = 31;
user.achievements.purchasedEquipment = true;
await buyGear(user, { params: { key: 'armor_warrior_1' } }, analytics);
await buyGear(user, { params: { key: 'armor_warrior_1' } });
expect(user.addAchievement).to.not.be.called;
});
+2 -5
View File
@@ -14,7 +14,6 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
describe('shared.ops.buyMysterySet', () => {
let user;
const analytics = { track () {} };
let clock;
beforeEach(() => {
@@ -27,11 +26,9 @@ describe('shared.ops.buyMysterySet', () => {
},
},
});
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
if (clock) {
clock.restore();
}
@@ -93,7 +90,7 @@ describe('shared.ops.buyMysterySet', () => {
context('successful purchases', () => {
it('buys Steampunk Accessories Set', async () => {
user.purchased.plan.consecutive.trinkets = 1;
await buyMysterySet(user, { params: { key: '301404' } }, analytics);
await buyMysterySet(user, { params: { key: '301404' } });
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.items.gear.owned).to.have.property('weapon_warrior_0', true);
@@ -106,7 +103,7 @@ describe('shared.ops.buyMysterySet', () => {
it('buys mystery set if it is available', async () => {
clock = sinon.useFakeTimers(new Date('2024-01-16'));
user.purchased.plan.consecutive.trinkets = 1;
await buyMysterySet(user, { params: { key: '201601' } }, analytics);
await buyMysterySet(user, { params: { key: '201601' } });
expect(user.purchased.plan.consecutive.trinkets).to.eql(0);
expect(user.items.gear.owned).to.have.property('shield_mystery_201601', true);
+2 -5
View File
@@ -12,10 +12,9 @@ describe('shared.ops.buyQuestGems', () => {
let user;
let clock;
const goldPoints = 40;
const analytics = { track () {} };
async function buyQuest (_user, _req, _analytics) {
const buyOp = new BuyQuestWithGemOperation(_user, _req, _analytics);
async function buyQuest (_user, _req) {
const buyOp = new BuyQuestWithGemOperation(_user, _req);
return buyOp.purchase();
}
@@ -25,13 +24,11 @@ describe('shared.ops.buyQuestGems', () => {
});
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
clock = sinon.useFakeTimers(new Date('2024-01-16'));
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
clock.restore();
});
+8 -17
View File
@@ -12,21 +12,15 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
describe('shared.ops.buyQuest', () => {
let user;
const analytics = { track () {} };
async function buyQuest (_user, _req, _analytics) {
const buyOp = new BuyQuestWithGoldOperation(_user, _req, _analytics);
async function buyQuest (_user, _req) {
const buyOp = new BuyQuestWithGoldOperation(_user, _req);
return buyOp.purchase();
}
beforeEach(() => {
user = generateUser();
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
it('buys a Quest scroll', async () => {
@@ -35,12 +29,11 @@ describe('shared.ops.buyQuest', () => {
params: {
key: 'dilatoryDistress1',
},
}, analytics);
});
expect(user.items.quests).to.eql({
dilatoryDistress1: 1,
});
expect(user.stats.gp).to.equal(5);
expect(analytics.track).to.be.calledOnce;
});
it('if a user\'s count of a quest scroll is negative, it will be reset to 0 before incrementing when they buy a new one.', async () => {
@@ -49,10 +42,9 @@ describe('shared.ops.buyQuest', () => {
user.items.quests[key] = -1;
await buyQuest(user, {
params: { key },
}, analytics);
});
expect(user.items.quests[key]).to.equal(1);
expect(user.stats.gp).to.equal(5);
expect(analytics.track).to.be.calledOnce;
});
it('buys a Quest scroll with the right quantity if a string is passed for quantity', async () => {
@@ -61,13 +53,13 @@ describe('shared.ops.buyQuest', () => {
params: {
key: 'dilatoryDistress1',
},
}, analytics);
});
await buyQuest(user, {
params: {
key: 'dilatoryDistress1',
},
quantity: '3',
}, analytics);
});
expect(user.items.quests).to.eql({
dilatoryDistress1: 4,
@@ -82,7 +74,7 @@ describe('shared.ops.buyQuest', () => {
key: 'dilatoryDistress1',
},
quantity: 'a',
}, analytics);
});
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
@@ -187,12 +179,11 @@ describe('shared.ops.buyQuest', () => {
params: {
key: 'dilatoryDistress3',
},
}, analytics);
});
expect(user.items.quests).to.eql({
dilatoryDistress3: 1,
});
expect(user.stats.gp).to.equal(100);
expect(analytics.track).to.be.calledOnce;
});
});
+5 -11
View File
@@ -14,20 +14,17 @@ import { errorMessage } from '../../../../website/common/script/libs/errorMessag
describe('shared.ops.buySpecialSpell', () => {
let user;
let clock;
const analytics = { track () {} };
async function buySpecialSpell (_user, _req, _analytics) {
const buyOp = new BuySpellOperation(_user, _req, _analytics);
async function buySpecialSpell (_user, _req) {
const buyOp = new BuySpellOperation(_user, _req);
return buyOp.purchase();
}
beforeEach(() => {
user = generateUser();
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
if (clock) {
clock.restore();
}
@@ -78,7 +75,7 @@ describe('shared.ops.buySpecialSpell', () => {
params: {
key: 'thankyou',
},
}, analytics);
});
expect(user.stats.gp).to.equal(1);
expect(user.items.special.thankyou).to.equal(1);
@@ -89,7 +86,6 @@ describe('shared.ops.buySpecialSpell', () => {
expect(message).to.equal(i18n.t('messageBought', {
itemText: item.text(),
}));
expect(analytics.track).to.be.calledOnce;
});
it('buys a limited card when it is available', async () => {
@@ -101,7 +97,7 @@ describe('shared.ops.buySpecialSpell', () => {
params: {
key: 'nye',
},
}, analytics);
});
expect(user.stats.gp).to.equal(1);
expect(user.items.special.nye).to.equal(1);
@@ -112,7 +108,6 @@ describe('shared.ops.buySpecialSpell', () => {
expect(message).to.equal(i18n.t('messageBought', {
itemText: item.text(),
}));
expect(analytics.track).to.be.calledOnce;
});
it('throws an error if the card is not currently available', async () => {
@@ -140,7 +135,7 @@ describe('shared.ops.buySpecialSpell', () => {
params: {
key: 'seafoam',
},
}, analytics);
});
expect(user.stats.gp).to.equal(1);
expect(user.items.special.seafoam).to.equal(1);
@@ -151,7 +146,6 @@ describe('shared.ops.buySpecialSpell', () => {
expect(message).to.equal(i18n.t('messageBought', {
itemText: item.text(),
}));
expect(analytics.track).to.be.calledOnce;
});
it('throws an error if the spell is not currently available', async () => {
+3 -10
View File
@@ -13,21 +13,15 @@ import { BuyHourglassMountOperation } from '../../../../website/common/script/op
describe('common.ops.hourglassPurchase', () => {
let user;
const analytics = { track () {} };
async function buyMount (_user, _req, _analytics) {
const buyOp = new BuyHourglassMountOperation(_user, _req, _analytics);
async function buyMount (_user, _req) {
const buyOp = new BuyHourglassMountOperation(_user, _req);
return buyOp.purchase();
}
beforeEach(() => {
user = generateUser();
sinon.stub(analytics, 'track');
});
afterEach(() => {
analytics.track.restore();
});
context('failure conditions', () => {
@@ -131,12 +125,11 @@ describe('common.ops.hourglassPurchase', () => {
it('buys a pet', async () => {
user.purchased.plan.consecutive.trinkets = 2;
const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } }, analytics);
const [, message] = await hourglassPurchase(user, { params: { type: 'pets', key: 'MantisShrimp-Base' } });
expect(message).to.eql(i18n.t('hourglassPurchase'));
expect(user.purchased.plan.consecutive.trinkets).to.eql(1);
expect(user.items.pets).to.eql({ 'MantisShrimp-Base': 5 });
expect(analytics.track).to.be.calledOnce;
});
it('buys a mount', async () => {
+4 -8
View File
@@ -17,20 +17,17 @@ describe('shared.ops.purchase', () => {
let user;
let clock;
const goldPoints = 40;
const analytics = { track () {} };
before(() => {
user = generateUser({ 'stats.class': 'rogue' });
});
beforeEach(() => {
sinon.stub(analytics, 'track');
sinon.spy(pinnedGearUtils, 'removeItemByPath');
clock = sandbox.useFakeTimers(new Date('2024-01-10'));
});
afterEach(() => {
analytics.track.restore();
pinnedGearUtils.removeItemByPath.restore();
clock.restore();
});
@@ -187,11 +184,10 @@ describe('shared.ops.purchase', () => {
const type = 'eggs';
const key = 'Wolf';
await purchase(user, { params: { type, key } }, analytics);
await purchase(user, { params: { type, key } });
expect(user.items[type][key]).to.equal(1);
expect(pinnedGearUtils.removeItemByPath.notCalled).to.equal(true);
expect(analytics.track).to.be.calledOnce;
});
it('purchases hatchingPotions', async () => {
@@ -332,7 +328,7 @@ describe('shared.ops.purchase', () => {
const key = 'Wolf';
try {
await purchase(user, { params: { type, key }, quantity: 'jamboree' }, analytics);
await purchase(user, { params: { type, key }, quantity: 'jamboree' });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
@@ -345,7 +341,7 @@ describe('shared.ops.purchase', () => {
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: -2 }, analytics);
await purchase(user, { params: { type, key }, quantity: -2 });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
@@ -358,7 +354,7 @@ describe('shared.ops.purchase', () => {
user.balance = 10;
try {
await purchase(user, { params: { type, key }, quantity: 2.9 }, analytics);
await purchase(user, { params: { type, key }, quantity: 2.9 });
} catch (err) {
expect(err).to.be.an.instanceof(BadRequest);
expect(err.message).to.equal(i18n.t('invalidQuantity'));
-15
View File
@@ -54,19 +54,4 @@ describe('armoire', () => {
const febuaryItems = armoire.all;
expect(febuaryItems.length).to.equal(384);
});
it('sets have at least 2 items', () => {
const setMap = {};
forEach(armoire.all, item => {
// Gotta have one outlier
if (!item.set || item.set.startsWith('armoire-')) return;
if (setMap[item.set] === undefined) {
setMap[item.set] = 0;
}
setMap[item.set] += 1;
});
Object.keys(setMap).forEach(set => {
expect(setMap[set], set).to.be.at.least(2);
});
});
});
@@ -40,7 +40,6 @@ function _requestMaker (user, method, additionalSets = {}) {
|| route.indexOf('/paypal') === 0
|| route.indexOf('/amazon') === 0
|| route.indexOf('/stripe') === 0
|| route.indexOf('/analytics') === 0
) {
url += `${route}`;
} else {
@@ -695,6 +695,11 @@
width: 141px;
height: 147px;
}
.background_beach_with_volcano {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_beach_with_volcano.png');
width: 141px;
height: 147px;
}
.background_beehive {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_beehive.png');
width: 141px;
@@ -2346,6 +2351,11 @@
width: 141px;
height: 147px;
}
.background_tropical_coral_garden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_tropical_coral_garden.png');
width: 141px;
height: 147px;
}
.background_tulip_garden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_tulip_garden.png');
width: 141px;
@@ -2401,6 +2411,11 @@
width: 141px;
height: 147px;
}
.background_vegetable_garden {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_vegetable_garden.png');
width: 141px;
height: 147px;
}
.background_viking_ship {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/background_viking_ship.png');
width: 141px;
@@ -29880,6 +29895,11 @@
width: 114px;
height: 90px;
}
.broad_armor_armoire_kendoBogu {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_kendoBogu.png');
width: 114px;
height: 90px;
}
.broad_armor_armoire_lamplightersGreatcoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_armoire_lamplightersGreatcoat.png');
width: 114px;
@@ -30535,6 +30555,11 @@
width: 90px;
height: 90px;
}
.head_armoire_kendoMen {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_kendoMen.png');
width: 114px;
height: 90px;
}
.head_armoire_lamplightersTopHat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_armoire_lamplightersTopHat.png');
width: 114px;
@@ -30920,6 +30945,11 @@
width: 114px;
height: 90px;
}
.shield_armoire_gardenHose {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_gardenHose.png');
width: 114px;
height: 90px;
}
.shield_armoire_gardenersSpade {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_armoire_gardenersSpade.png');
width: 114px;
@@ -31550,6 +31580,11 @@
width: 114px;
height: 90px;
}
.slim_armor_armoire_kendoBogu {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_kendoBogu.png');
width: 114px;
height: 90px;
}
.slim_armor_armoire_lamplightersGreatcoat {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_armoire_lamplightersGreatcoat.png');
width: 114px;
@@ -31930,6 +31965,11 @@
width: 114px;
height: 90px;
}
.weapon_armoire_brightRainbowKite {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_brightRainbowKite.png');
width: 114px;
height: 90px;
}
.weapon_armoire_buoyantBubbles {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_buoyantBubbles.png');
width: 114px;
@@ -32030,6 +32070,11 @@
width: 114px;
height: 90px;
}
.weapon_armoire_gardenRake {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_gardenRake.png');
width: 114px;
height: 90px;
}
.weapon_armoire_gardenersWateringCan {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_gardenersWateringCan.png');
width: 114px;
@@ -32125,6 +32170,11 @@
width: 114px;
height: 90px;
}
.weapon_armoire_kendoShinai {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_kendoShinai.png');
width: 114px;
height: 90px;
}
.weapon_armoire_lamplighter {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_lamplighter.png');
width: 114px;
@@ -32210,6 +32260,11 @@
width: 114px;
height: 90px;
}
.weapon_armoire_pastelRainbowKite {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_pastelRainbowKite.png');
width: 114px;
height: 90px;
}
.weapon_armoire_pinkKite {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_armoire_pinkKite.png');
width: 114px;
@@ -34200,6 +34255,11 @@
width: 114px;
height: 90px;
}
.eyewear_mystery_202606 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/eyewear_mystery_202606.png');
width: 117px;
height: 120px;
}
.head_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202512.png');
width: 114px;
@@ -34220,11 +34280,31 @@
width: 114px;
height: 90px;
}
.head_mystery_202606 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_mystery_202606.png');
width: 117px;
height: 120px;
}
.shield_mystery_202605 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202605.png');
width: 114px;
height: 90px;
}
.shield_mystery_202606 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202606.png');
width: 117px;
height: 120px;
}
.shield_mystery_202607 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202607.png');
width: 117px;
height: 120px;
}
.shield_mystery_202608 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_mystery_202608.png');
width: 117px;
height: 120px;
}
.slim_armor_mystery_202512 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_mystery_202512.png');
width: 114px;
@@ -34250,6 +34330,16 @@
width: 114px;
height: 90px;
}
.weapon_mystery_202607 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202607.png');
width: 117px;
height: 120px;
}
.weapon_mystery_202608 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_mystery_202608.png');
width: 117px;
height: 120px;
}
.back_mystery_201402 {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/back_mystery_201402.png');
width: 90px;
@@ -37715,6 +37805,26 @@
width: 114px;
height: 90px;
}
.broad_armor_special_summer2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2026Healer.png');
width: 114px;
height: 90px;
}
.broad_armor_special_summer2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2026Mage.png');
width: 114px;
height: 117px;
}
.broad_armor_special_summer2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2026Rogue.png');
width: 114px;
height: 117px;
}
.broad_armor_special_summer2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summer2026Warrior.png');
width: 114px;
height: 90px;
}
.broad_armor_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/broad_armor_special_summerHealer.png');
width: 90px;
@@ -37965,6 +38075,26 @@
width: 114px;
height: 90px;
}
.head_special_summer2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2026Healer.png');
width: 114px;
height: 90px;
}
.head_special_summer2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2026Mage.png');
width: 114px;
height: 117px;
}
.head_special_summer2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2026Rogue.png');
width: 114px;
height: 117px;
}
.head_special_summer2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summer2026Warrior.png');
width: 114px;
height: 90px;
}
.head_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/head_special_summerHealer.png');
width: 90px;
@@ -38155,6 +38285,21 @@
width: 114px;
height: 90px;
}
.shield_special_summer2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2026Healer.png');
width: 114px;
height: 90px;
}
.shield_special_summer2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2026Rogue.png');
width: 114px;
height: 117px;
}
.shield_special_summer2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summer2026Warrior.png');
width: 114px;
height: 90px;
}
.shield_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/shield_special_summerHealer.png');
width: 90px;
@@ -38395,6 +38540,26 @@
width: 114px;
height: 90px;
}
.slim_armor_special_summer2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2026Healer.png');
width: 114px;
height: 90px;
}
.slim_armor_special_summer2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2026Mage.png');
width: 114px;
height: 117px;
}
.slim_armor_special_summer2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2026Rogue.png');
width: 114px;
height: 117px;
}
.slim_armor_special_summer2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summer2026Warrior.png');
width: 114px;
height: 90px;
}
.slim_armor_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/slim_armor_special_summerHealer.png');
width: 90px;
@@ -38635,6 +38800,26 @@
width: 114px;
height: 90px;
}
.weapon_special_summer2026Healer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2026Healer.png');
width: 114px;
height: 90px;
}
.weapon_special_summer2026Mage {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2026Mage.png');
width: 114px;
height: 117px;
}
.weapon_special_summer2026Rogue {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2026Rogue.png');
width: 114px;
height: 117px;
}
.weapon_special_summer2026Warrior {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summer2026Warrior.png');
width: 114px;
height: 90px;
}
.weapon_special_summerHealer {
background-image: url('https://habitica-assets.s3.amazonaws.com/mobileApp/images/weapon_special_summerHealer.png');
width: 90px;
@@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_3344_18)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9 12H7V10H9V12ZM16 2V14C16 15.1 15.1 16 14 16H2C0.9 16 0 15.1 0 14V2C0 0.9 0.9 0 2 0H14C15.1 0 16 0.9 16 2ZM14 2H2V14H14V2ZM9 4H7V9H9V4Z" fill="#4E4A57"/>
</g>
<defs>
<clipPath id="clip0_3344_18">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 450 B

@@ -66,6 +66,14 @@
<p class="purple-600">
{{ $t('usernameLimitations') }}
</p>
<input
v-if="needsEmailField"
id="emailInput"
v-model="email"
class="form-control dark"
type="text"
:placeholder="$t('email')"
>
<div class="custom-control custom-checkbox mb-4">
<input
id="privacyTOS"
@@ -225,7 +233,6 @@ export default {
if (!this.email && this.registrationMethod !== 'apple') {
return;
}
if ((!this.email || this.email === '') && this.registrationMethod === 'apple') {
this.needsEmailField = true;
}
@@ -198,7 +198,6 @@ import dailyIcon from '@/assets/svg/daily.svg?raw';
import todoIcon from '@/assets/svg/todo.svg?raw';
import rewardIcon from '@/assets/svg/reward.svg?raw';
import * as Analytics from '@/libs/analytics';
import { mapState } from '@/libs/store';
export default {
@@ -438,14 +437,6 @@ export default {
return false;
},
changeMirrorPreference (newVal) {
Analytics.track({
eventName: 'mirror tasks',
eventAction: 'mirror tasks',
eventCategory: 'behavior',
hitType: 'event',
mirror: newVal,
group: this.group._id,
}, { trackOnClient: true });
const groupsToMirror = this.user.preferences.tasks.mirrorGroupTasks || [];
if (newVal) { // we're turning copy ON for this group
groupsToMirror.push(this.group._id);
@@ -240,7 +240,6 @@
<script>
import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import notifications from '@/mixins/notifications';
import closeX from '../ui/closeX';
@@ -276,11 +275,6 @@ export default {
this.$store.state.party.data = party;
this.user.party._id = party._id;
Analytics.updateUser({
partyID: party._id,
partySize: 1,
});
this.$root.$emit('bv::hide::modal', 'create-party-modal');
await this.$router.push('/party');
},
@@ -314,7 +314,6 @@ import extend from 'lodash/extend';
import groupUtilities from '@/mixins/groupsUtilities';
import styleHelper from '@/mixins/styleHelper';
import { mapGetters } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import participantListModal from './participantListModal';
import groupFormModal from './groupFormModal';
import groupGemsModal from '@/components/groups/groupGemsModal';
@@ -560,7 +559,6 @@ export default {
if (this.isParty) {
data.type = 'party';
Analytics.updateUser({ partySize: null, partyID: null });
this.$store.state.partyMembers = [];
}
@@ -334,7 +334,6 @@ import orderBy from 'lodash/orderBy';
import * as quests from '@/../../common/script/content/quests';
import getItemInfo from '@/../../common/script/libs/getItemInfo';
import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import navigationBack from '@/assets/svg/navigation_back.svg?raw';
import questDialogContent from '../shops/quests/questDialogContent';
@@ -421,11 +420,6 @@ export default {
async questInit () {
this.loading = true;
Analytics.updateUser({
partyID: this.group._id,
partySize: this.group.memberCount,
});
const groupId = this.group._id || this.user.party._id;
const key = this.selectedQuest;
@@ -123,7 +123,6 @@
<script>
import orderBy from 'lodash/orderBy';
import * as Analytics from '@/libs/analytics';
import { mapGetters, mapActions } from '@/libs/store';
import MemberDetails from '../memberDetails';
import createPartyModal from '../groups/createPartyModal';
@@ -236,22 +235,8 @@ export default {
},
async createOrInviteParty () {
if (this.user.party._id) {
await Analytics.track({
eventName: 'Header Party CTA',
eventAction: 'Header Party CTA',
eventCategory: 'behavior',
hitType: 'event',
state: 'Find Party Members',
});
this.$router.push('/looking-for-party');
} else {
await Analytics.track({
eventName: 'Header Party CTA',
eventAction: 'Header Party CTA',
eventCategory: 'behavior',
hitType: 'event',
state: 'Get Started',
});
this.$root.$emit('bv::show::modal', 'create-party-modal');
}
},
@@ -114,7 +114,6 @@ import { mapState } from '@/libs/store';
import notifications from '@/mixins/notifications';
import guide from '@/mixins/guide';
import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager';
import * as Analytics from '@/libs/analytics';
import yesterdailyModal from './tasks/yesterdailyModal';
import newStuff from './news/modal';
@@ -648,15 +647,6 @@ export default {
// Reset daily analytics actions
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 0);
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 0);
} else {
// Note a failed cron event, for our records and investigation
Analytics.track({
eventName: 'cron failed',
eventAction: 'cron failed',
eventCategory: 'behavior',
hitType: 'event',
responseCode: response.status,
}, { trackOnClient: true });
}
// Sync
@@ -433,9 +433,6 @@ import lockableLabel from '@/components/tasks/modal-controls/lockableLabel';
import notificationsMixin from '@/mixins/notifications';
import paymentsMixin from '@/mixins/payments';
// analytics
import * as Analytics from '@/libs/analytics';
export default {
components: {
selectTranslatedArray,
@@ -536,16 +533,6 @@ export default {
this.close();
},
submit () {
if (this.paymentData.group && !this.paymentData.newGroup) {
Analytics.track({
hitType: 'event',
eventName: 'group plan upgrade',
eventAction: 'group plan upgrade',
eventCategory: 'behavior',
demographics: this.upgradedGroup.demographics,
type: this.paymentData.group.type,
}, { trackOnClient: true });
}
this.paymentData = {};
this.$root.$emit('bv::hide::modal', 'payments-success-modal');
},
@@ -83,7 +83,7 @@
</div>
</div>
<draggable
v-if="taskList.length > 0"
v-if="taskList.length > 0 && !rerendering"
ref="tasksList"
class="sortable-tasks"
:disabled="activeFilter.label === 'scheduled' || !canBeDragged()"
@@ -432,6 +432,7 @@ export default {
selectedItemToBuy: {},
dragging: false,
rerendering: false,
};
},
computed: {
@@ -548,8 +549,8 @@ export default {
if (this.taskListOverride) originTasks = this.taskListOverride;
// Server
const taskIdToReplace = filteredList[data.newIndex];
const newIndexOnServer = originTasks.findIndex(taskId => taskId === taskIdToReplace);
const taskIdToReplace = filteredList[data.newIndex]._id;
const newIndexOnServer = originTasks.findIndex(task => task._id === taskIdToReplace);
let newOrder;
if (taskToMove.group.id && !this.isUser) {
@@ -568,6 +569,9 @@ export default {
// Client
const deleted = originTasks.splice(data.oldIndex, 1);
originTasks.splice(data.newIndex, 0, deleted[0]);
this.rerendering = true;
await this.$nextTick();
this.rerendering = false;
},
async moveTo (task, where) { // where is 'top' or 'bottom'
const taskIdToMove = task._id;
+5 -17
View File
@@ -13,6 +13,8 @@
}, `type_${task.type}`
]"
@click="castEnd($event, task)"
tabindex="0"
@keypress.enter="$emit('editTask', task)"
>
<div
class="d-flex"
@@ -98,9 +100,7 @@
<div
class="task-clickable-area pt-1 pl-75 pb-0"
:class="{ 'cursor-auto': !teamManagerAccess }"
tabindex="0"
@click="edit($event, task)"
@keypress.enter="edit($event, task)"
>
<div class="d-flex justify-content-between">
<h3
@@ -432,10 +432,6 @@
outline: none;
transition: none;
border: $purple-400 solid 1px;
:not(task-best-control-inner-habit) { // round icon
border-radius: 4px;
}
}
.control-bottom-box {
@@ -462,16 +458,13 @@
&:hover:not(.task-not-editable.task-not-scoreable),
&:focus-within:not(.task-not-editable.task-not-scoreable) {
box-shadow: 0 1px 8px 0 rgba($black, 0.12), 0 4px 4px 0 rgba($black, 0.16);
z-index: 11;
}
}
.task:not(.groupTask) {
&:hover,
&:focus-within {
.left-control, .right-control, .task-content {
border-color: $purple-400;
}
&:hover, &:focus {
border: none;
outline: 1px solid $purple-400;
}
}
@@ -522,11 +515,6 @@
&-user {
padding-right: 0px;
}
&:focus {
border-radius: 4px;
border: $purple-400 solid 1px;
}
}
.task-title + .task-dropdown ::v-deep .dropdown-menu {
@@ -55,11 +55,31 @@
</div>
</div>
<div class="form-group">
<lockable-label
:class-override="cssClass('headings')"
:locked="challengeAccessRequired"
:text="`${$t('text')}*`"
/>
<div class="d-flex align-items-center">
<lockable-label
class="mr-auto"
:class-override="cssClass('headings')"
:locked="challengeAccessRequired"
:text="`${$t('text')}*`"
/>
<div
id="spi-alert"
class="d-flex align-items-center"
:class="cssClass('headings')"
>
<div
class="svg svg-icon color icon-16 mr-1"
v-html="icons.alert"
></div>
<small
class="my-1"
>
<a
:class="cssClass('headings')"
>{{ $t('avoidSPI') }}</a>
</small>
</div>
</div>
<input
ref="inputToFocus"
v-model="task.text"
@@ -79,10 +99,20 @@
@keydown.esc="autoCompleteMixinHandleEscape($event)"
>
</div>
<b-popover
:target="'spi-alert'"
triggers="hover"
placement="bottom"
offset="-128"
>
<div
v-html="$t('avoidSPIDetails', spiLinkData)">
</div>
</b-popover>
<div
class="form-group mb-0"
>
<div class="d-flex">
<div class="d-flex align-items-center">
<lockable-label
class="mr-auto"
:class-override="cssClass('headings')"
@@ -382,6 +412,25 @@
</div>
</div>
</div>
<p
v-if="task.type === 'daily' && schedulingSummary"
class="scheduling-summary mt-2 mb-0"
>
{{ schedulingSummary }}
</p>
<div
v-if="task.type === 'daily' && schedulingWarning"
class="scheduling-warning mt-2"
>
<span
class="scheduling-warning-icon"
v-html="icons.exclamationInfo"
></span>
<span
class="scheduling-warning-text"
v-html="schedulingWarning"
></span>
</div>
<div
v-if="!groupId"
class="tags-select option mt-3"
@@ -963,6 +1012,20 @@
box-shadow: 0px 1px 3px 0px rgba(26, 24, 29, 0.12), 0px 1px 2px 0px rgba(26, 24, 29, 0.24);
}
}
.b-popover {
margin-top: -5px;
max-width: 330px;
}
.popover-body {
text-align: left;
a {
color: $gray-500;
text-decoration: underline;
}
}
}
@media only screen and (max-width: 768px) {
@@ -1065,6 +1128,42 @@
height: 1rem;
}
.scheduling-summary {
font-family: 'Roboto', sans-serif;
font-weight: 400;
font-style: normal;
font-size: 12px;
line-height: 16px;
color: $gray-50;
text-align: left;
}
.scheduling-warning {
display: flex;
align-items: flex-start;
font-family: 'Roboto', sans-serif;
font-weight: 400;
font-style: normal;
font-size: 12px;
line-height: 16px;
color: $gray-50;
}
.scheduling-warning-icon {
display: inline-flex;
align-items: center;
justify-content: center;
width: 16px;
height: 16px;
flex-shrink: 0;
margin-right: 6px;
margin-top: -1px;
}
.scheduling-warning-text {
flex: 1;
}
label {
display: inline-flex;
align-items: center;
@@ -1195,7 +1294,9 @@ import goldIcon from '@/assets/svg/gold.svg?raw';
import chevronIcon from '@/assets/svg/chevron.svg?raw';
import calendarIcon from '@/assets/svg/calendar.svg?raw';
import gripIcon from '@/assets/svg/grip.svg?raw';
import exclamationInfoIcon from '@/assets/svg/exclaimation_info.svg?raw';
import InformationIcon from '@/components/ui/informationIcon.vue';
import alertIcon from '@/assets/svg/for-css/alert-white.svg?raw';
export default {
components: {
@@ -1231,6 +1332,8 @@ export default {
streak: streakIcon,
calendar: calendarIcon,
grip: gripIcon,
exclamationInfo: exclamationInfoIcon,
alert: alertIcon,
}),
members: [],
membersNameAndId: [],
@@ -1251,6 +1354,11 @@ export default {
{ key: 'per', label: 'perception', description: 'perTaskText' },
],
calendarHighlights: { dates: [new Date()] },
spiLinkData: {
firstLink: '<a href="/static/privacy#section_1" target="_blank">',
secondLink: '<a href="/static/privacy" target="_blank">',
linkClose: '</a>',
},
};
},
computed: {
@@ -1326,6 +1434,87 @@ export default {
}
return null;
},
schedulingSummary () {
if (!this.task || this.task.type !== 'daily') return '';
const { task } = this;
const everyXValue = +task.everyX;
let interval;
if (task.frequency === 'daily') {
interval = everyXValue === 1 ? this.$t('everyDay') : this.$t('everyXDays', { count: everyXValue });
} else if (task.frequency === 'weekly') {
interval = everyXValue === 1 ? this.$t('everyWeek') : this.$t('everyXWeeks', { count: everyXValue });
} else if (task.frequency === 'monthly') {
interval = everyXValue === 1 ? this.$t('everyMonth') : this.$t('everyXMonths', { count: everyXValue });
} else if (task.frequency === 'yearly') {
interval = everyXValue === 1 ? this.$t('everyYear') : this.$t('everyXYears', { count: everyXValue });
} else {
return '';
}
let details = '';
if (task.frequency === 'weekly') {
const dayNames = {
su: 'Sunday',
m: 'Monday',
t: 'Tuesday',
w: 'Wednesday',
th: 'Thursday',
f: 'Friday',
s: 'Saturday',
};
const activeDays = Object.keys(task.repeat || {}).filter(d => task.repeat[d]);
if (activeDays.length > 0) {
details = ` on ${activeDays.map(d => dayNames[d]).join(', ')}`;
}
} else if (task.frequency === 'monthly' && task.startDate) {
const dayOfMonth = moment(task.startDate).date();
if (task.weeksOfMonth && task.weeksOfMonth.length > 0) {
const weekNum = task.weeksOfMonth[0] + 1;
const weekStr = String(weekNum);
const lastDigit = weekStr.slice(-1);
let suffix = 'th';
if (lastDigit === '1' && weekStr !== '11') suffix = 'st';
if (lastDigit === '2' && weekStr !== '12') suffix = 'nd';
if (lastDigit === '3' && weekStr !== '13') suffix = 'rd';
const dayName = moment(task.startDate).format('dddd');
details = ` on the ${weekNum}${suffix} ${dayName} of the month`;
} else if (task.daysOfMonth && task.daysOfMonth.length > 0) {
const dom = task.daysOfMonth[0];
const domStr = String(dom);
const lastDigit = domStr.slice(-1);
let suffix = 'th';
if (lastDigit === '1' && domStr !== '11') suffix = 'st';
if (lastDigit === '2' && domStr !== '12') suffix = 'nd';
if (lastDigit === '3' && domStr !== '13') suffix = 'rd';
details = ` on the ${dom}${suffix}`;
} else {
const domStr = String(dayOfMonth);
const lastDigit = domStr.slice(-1);
let suffix = 'th';
if (lastDigit === '1' && domStr !== '11') suffix = 'st';
if (lastDigit === '2' && domStr !== '12') suffix = 'nd';
if (lastDigit === '3' && domStr !== '13') suffix = 'rd';
details = ` on the ${dayOfMonth}${suffix}`;
}
} else if (task.frequency === 'yearly' && task.startDate) {
details = ` on ${moment(task.startDate).format('MMMM Do')}`;
}
return `${this.$t('repeats')} ${interval}${details}`;
},
schedulingWarning () {
if (!this.task || this.task.type !== 'daily') return '';
const { task } = this;
if (task.frequency === 'monthly'
&& task.weeksOfMonth && task.weeksOfMonth.length > 0
&& task.weeksOfMonth[0] === 4
&& task.startDate) {
const dayName = moment(task.startDate).format('dddd');
return this.$t('fifthWeekWarning', { day: dayName });
}
return '';
},
repeatsOn: {
get () {
let repeatsOn = 'dayOfMonth';
@@ -222,14 +222,22 @@ export default {
return usernames;
},
summarySentence () {
let fifthWeekWarning = '';
if (this.task.type === 'daily' && this.task.frequency === 'monthly'
&& this.task.weeksOfMonth && this.task.weeksOfMonth.length > 0
&& this.task.weeksOfMonth[0] === 4) {
const activeDays = keys(pickBy(this.task.repeat, value => value === true));
const dayName = this.expandDayString[activeDays[0]];
fifthWeekWarning = ` ${this.$t('fifthWeekWarning', { day: dayName })}`;
}
if (this.task.type === 'daily' && moment().isBefore(this.task.startDate)) {
return `This is ${this.formattedDifficulty(this.task.priority)} task that will repeat
${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)}
starting on <strong>${moment(this.task.startDate).format('MM/DD/YYYY')}</strong>.`;
starting on <strong>${moment(this.task.startDate).format('MM/DD/YYYY')}</strong>.${fifthWeekWarning}`;
}
if (this.task.type === 'daily') {
return `This is ${this.formattedDifficulty(this.task.priority)} task that repeats
${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)}.`;
${this.formattedRepeatInterval(this.task.frequency, this.task.everyX)}${this.formattedDays(this.task.frequency, this.task.repeat, this.task.daysOfMonth, this.task.weeksOfMonth, this.task.startDate)}.${fifthWeekWarning}`;
}
if (this.task.date) {
return `This is ${this.formattedDifficulty(this.task.priority)} task that is due <strong>${moment(this.task.date).format('MM/DD/YYYY')}.`;
@@ -287,25 +295,14 @@ export default {
});
dayStringArray.push('</strong>');
} else if (weeksOfMonth.length > 0) {
switch (weeksOfMonth[0]) {
case 0:
dayStringArray.push('first');
break;
case 1:
dayStringArray.push('second');
break;
case 2:
dayStringArray.push('third');
break;
case 3:
dayStringArray.push('fourth');
break;
case 4:
dayStringArray.push('fifth');
break;
default:
break;
}
const weekNum = weeksOfMonth[0] + 1;
const weekNumStr = String(weekNum);
const lastDigit = weekNumStr.slice(-1);
let ordinalSuffix = 'th';
if (lastDigit === '1' && weekNumStr !== '11') ordinalSuffix = 'st';
if (lastDigit === '2' && weekNumStr !== '12') ordinalSuffix = 'nd';
if (lastDigit === '3' && weekNumStr !== '13') ordinalSuffix = 'rd';
dayStringArray.push(`${weekNum}${ordinalSuffix}`);
activeDays = keys(pickBy(repeat, value => value === true));
dayStringArray.push(` ${this.expandDayString[activeDays[0]]} of the month</strong>`);
}
@@ -343,9 +340,8 @@ export default {
if (numericX === 2) return '<strong>every other week</strong>';
return `<strong>every ${numericX} weeks</strong>`;
case 'monthly':
if (numericX === 1) return '<strong>every month</strong>';
if (numericX === 2) return '<strong>every other month</strong>';
return `<strong>every ${numericX} months</strong>`;
if (numericX === 1) return `<strong>${this.$t('everyMonth')}</strong>`;
return `<strong>${this.$t('everyXMonths', { count: numericX })}</strong>`;
case 'yearly':
if (numericX === 1) return '<strong>every year</strong>';
return `<strong>every ${everyX} years</strong>`;
-11
View File
@@ -6,7 +6,6 @@ import { mapState } from '@/libs/store';
import encodeParams from '@/libs/encodeParams';
import notificationsMixin from '@/mixins/notifications';
import { CONSTANTS, setLocalSetting } from '@/libs/userlocalManager';
import * as Analytics from '@/libs/analytics';
const { STRIPE_PUB_KEY } = import.meta.env;
@@ -207,16 +206,6 @@ export default {
alert(`Error while redirecting to Stripe: ${checkoutSessionResult.error.message}`);
throw checkoutSessionResult.error;
}
if (paymentType === 'groupPlan') {
Analytics.track({
hitType: 'event',
eventName: 'group plan create',
eventAction: 'group plan create',
eventCategory: 'behavior',
demographics: appState.newGroup.demographics,
type: appState.newGroup.type,
}, { trackOnClient: true });
}
} catch (err) {
console.error('Error while redirecting to Stripe', err); // eslint-disable-line
alert(`Error while redirecting to Stripe: ${err.message}`);
-10
View File
@@ -3,7 +3,6 @@ import Vue from 'vue';
import scoreTask from '@/../../common/script/ops/scoreTask';
import notifications from './notifications';
import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
export default {
@@ -58,15 +57,6 @@ export default {
const tasksScoredCount = getLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT);
if (!tasksScoredCount || tasksScoredCount < 2) {
Analytics.track({
eventName: 'task scored',
eventAction: 'task scored',
eventCategory: 'behavior',
hitType: 'event',
uuid: user._id,
taskType: task.type,
direction,
}, { trackOnClient: true });
if (!tasksScoredCount) {
setLocalSetting(CONSTANTS.keyConstants.TASKS_SCORED_COUNT, 1);
} else {
+1 -2
View File
@@ -4,6 +4,7 @@
:class="{
'casting-spell': castingSpell,
}"
@dragover.prevent
>
<!-- <banned-account-modal /> -->
<amazon-payments-modal v-if="!isStaticPage" />
@@ -130,7 +131,6 @@ import PrivacyBanner from '@/components/header/banners/privacy';
import AppFooter from '@/components/appFooter';
import notificationsDisplay from '@/components/notifications';
import { mapState } from '@/libs/store';
import * as Analytics from '@/libs/analytics';
import BuyModal from '@/components/shops/buyModal.vue';
import SelectMembersModal from '@/components/selectMembersModal.vue';
import notifications from '@/mixins/notifications';
@@ -276,7 +276,6 @@ export default {
}
}
Analytics.updateUser();
return this.loadAllTranslations();
}).then(() => {
this.$store.state.isUserLoaded = true;
-10
View File
@@ -1,6 +1,5 @@
import Vue from 'vue';
import VueRouter from 'vue-router';
import * as Analytics from '@/libs/analytics';
import getStore from '@/store';
import handleRedirect from './handleRedirect';
@@ -318,15 +317,6 @@ router.beforeEach(async (to, from, next) => {
router.app.$root.$emit('update-party');
}
if (to.name === 'lookingForParty') {
Analytics.track({
hitType: 'event',
eventName: 'View Find Members',
eventAction: 'View Find Members',
eventCategory: 'behavior',
}, { trackOnClient: true });
}
// Redirect old guild urls
if (to.hash.indexOf('#/options/groups/guilds/') !== -1) {
const splits = to.hash.split('/');
-8
View File
@@ -1,6 +1,5 @@
import axios from 'axios';
import Vue from 'vue';
import * as Analytics from '@/libs/analytics';
export async function getChat (store, payload) {
const response = await axios.get(`/api/v4/groups/${payload.groupId}/chat?limit=400`);
@@ -17,13 +16,6 @@ export async function postChat (store, payload) {
url += `?previousMsg=${payload.previousMsg}`;
}
if (group.type === 'party') {
Analytics.updateUser({
partyID: group.id,
partySize: group.memberCount,
});
}
const response = await axios.post(url, {
message: payload.message,
});
@@ -1,7 +1,6 @@
import axios from 'axios';
import omit from 'lodash/omit';
import findIndex from 'lodash/findIndex';
import * as Analytics from '@/libs/analytics';
import { loadAsyncResource } from '@/libs/asyncResource';
export async function getPublicGuilds (store, payload) {
@@ -74,7 +73,6 @@ export async function join (store, payload) {
if (invitationI !== -1) invitations.parties.splice(invitationI, 1);
user.party._id = groupId;
Analytics.updateUser({ partyID: groupId });
// load the party members so that they get shown in the header
store.dispatch('party:getMembers');
}
@@ -18,7 +18,6 @@ import * as shops from './shops';
import * as snackbars from './snackbars';
import * as worldState from './worldState';
import * as news from './news';
import * as analytics from './analytics';
import * as faq from './faq';
import * as blockers from './blockers';
@@ -44,7 +43,6 @@ const actions = flattenAndNamespace({
snackbars,
worldState,
news,
analytics,
faq,
blockers,
});
@@ -1,26 +1,6 @@
import axios from 'axios';
import * as Analytics from '@/libs/analytics';
// export async function initQuest (store) {
// }
export async function sendAction (store, payload) { // eslint-disable-line import/prefer-default-export, max-len
// @TODO: Maybe move this to server
let partyData = {};
if (store.state.party && store.state.party.data) {
partyData = {
partyID: store.state.party.data._id,
partySize: store.state.party.data.memberCount,
};
} else {
partyData = {
partyID: store.state.user.data.party._id,
partySize: store.state.partyMembers.data.length,
};
}
Analytics.updateUser(partyData);
const response = await axios.post(`/api/v4/groups/${payload.groupId}/${payload.action}`);
// @TODO: Update user?
+8 -14
View File
@@ -3,7 +3,6 @@ import Vue from 'vue';
import compact from 'lodash/compact';
import omit from 'lodash/omit';
import { loadAsyncResource } from '@/libs/asyncResource';
import * as Analytics from '@/libs/analytics';
import { CONSTANTS, getLocalSetting, setLocalSetting } from '@/libs/userlocalManager';
export function fetchUserTasks (store, options = {}) {
@@ -112,15 +111,6 @@ export async function create (store, createdTask) {
}
const tasksCreatedCount = getLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT);
if (!tasksCreatedCount || tasksCreatedCount < 2) {
const uuid = store.state.user.data._id;
Analytics.track({
eventName: 'task created',
eventAction: 'task created',
eventCategory: 'behavior',
hitType: 'event',
uuid,
taskType: taskRes.type,
}, { trackOnClient: true });
if (!tasksCreatedCount) {
setLocalSetting(CONSTANTS.keyConstants.TASKS_CREATED_COUNT, 1);
} else {
@@ -168,11 +158,15 @@ export async function collapseChecklist (store, task) {
}
export async function destroy (store, task) {
const list = store.state.tasks.data[`${task.type}s`];
const taskIndex = list.findIndex(t => t._id === task._id);
const type = `${task.type}s`;
const listIndex = store.state.tasks.data[type].findIndex(t => t._id === task._id);
const orderIndex = store.state.user.data.tasksOrder[type].indexOf(task._id);
if (taskIndex > -1) {
list.splice(taskIndex, 1);
if (listIndex > -1) {
store.state.tasks.data[type].splice(listIndex, 1);
}
if (orderIndex > -1) {
store.state.user.data.tasksOrder[type].splice(orderIndex, 1);
}
await axios.delete(`/api/v4/tasks/${task._id}`);
-4
View File
@@ -159,10 +159,6 @@ export default defineConfig({
target: DEV_BASE_URL,
changeOrigin: true,
},
'^/analytics': {
target: DEV_BASE_URL,
changeOrigin: true,
},
}
}
})
+16 -3
View File
@@ -213,7 +213,7 @@
"backgroundStormyRooftopsNotes": "Промъквайте се върху буреносни покриви.",
"backgroundWindyAutumnText": "Ветровита есен",
"backgroundWindyAutumnNotes": "Гонете листа през ветровита есен.",
"incentiveBackgrounds": "Комплект едноцветни фонове",
"incentiveBackgrounds": "Стандартни фонове",
"backgroundVioletText": "Виолетово",
"backgroundVioletNotes": "Енергичен виолетов фон.",
"backgroundBlueText": "Синьо",
@@ -494,7 +494,7 @@
"backgroundSnowglobeText": "Снежна топка",
"backgroundDesertWithSnowNotes": "Бъди свидетел на рядката и мълчалива красота на Снежната пустиня.",
"backgroundTeaPartyNotes": "Участвай в изискано Чаено парти.",
"backgroundButterflyGardenNotes": "Забавлявайте се с опрашителите в Градина на пеперудите",
"backgroundButterflyGardenNotes": "Купонясвайте с опрашители в градина за пеперуди",
"backgroundAnimalCloudsText": "Животински облаци",
"backgroundButterflyGardenText": "Градина на пеперудите",
"backgroundWinterNocturneText": "Зимен ноктюрн",
@@ -503,5 +503,18 @@
"hideLockedBackgrounds": "Скрий заключените фонове",
"backgroundSnowglobeNotes": "Разклати Снежната топка и заеми мястото си в микрокосмоса на снежния пейзаж.",
"backgroundAmongGiantFlowersText": "Сред гигантски цветя",
"backgroundAnimalCloudsNotes": "Използвай въображението си, за да намериш Животни в Облаците."
"backgroundAnimalCloudsNotes": "Използвай въображението си, за да намериш Животни в Облаците.",
"backgroundSucculentGardenNotes": "",
"backgroundSucculentGardenText": "Градина със сукуленти",
"backgroundHotAirBalloonText": "горещ въздух балон",
"backgroundHeatherFieldText": "пирен поле",
"backgroundRainyBarnyardText": "Дъждовен фермерски двор",
"backgroundRelaxationRiverText": "Релаксация Река",
"backgroundFlyingOverGlacierNotes": "",
"backgroundUnderwaterRuinsText": "Подводен Руини",
"backgroundBeachCabanaText": "Плаж Кабана",
"backgroundSaltLakeText": "Сол Езеро",
"backgroundWintryCastleText": "Зимен Замък",
"backgroundVikingShipText": "Викинг Кораб",
"backgroundCampingOutText": "Къмпинг Навън"
}
+2 -1
View File
@@ -164,5 +164,6 @@
"achievementRodentRulerModalText": "Nasbíral jsi všechny hlodavce!",
"achievementCatsText": "Vylíhly se všechny standardní barvy kočičích mazlíčků: gepard, lev, šavlozubý tygr a tygr!",
"achievementRodentRuler": "Vládce hlodavců",
"achievementCats": "Pasák koček"
"achievementCats": "Pasák koček",
"achievementDomesticated": "Hejá"
}
+62 -5
View File
@@ -117,7 +117,7 @@
"backgroundTavernNotes": "Navštiv krčmu města Habitica.",
"backgrounds102015": "Sada 17: zveřejněna v říjnu 2015",
"backgroundHarvestMoonText": "Měsíc při sklizni",
"backgroundHarvestMoonNotes": "Kdákání pod měsícem při sklizni.",
"backgroundHarvestMoonNotes": "Chechtej se pod sklizňovým měsícem.",
"backgroundSlimySwampText": "Slizká bažina",
"backgroundSlimySwampNotes": "Přebroď se slizkou bažinou.",
"backgroundSwarmingDarknessText": "Valící se temnota",
@@ -213,7 +213,7 @@
"backgroundStormyRooftopsNotes": "Propliž se přes bouřlivé střechy.",
"backgroundWindyAutumnText": "Větrný podzim",
"backgroundWindyAutumnNotes": "Hoň se za listy během větrného podzimu.",
"incentiveBackgrounds": "Prosté pozadí",
"incentiveBackgrounds": "Standardní pozadí",
"backgroundVioletText": "Fialová",
"backgroundVioletNotes": "Živá fialová tapeta.",
"backgroundBlueText": "Modrá",
@@ -736,7 +736,64 @@
"backgroundMaskMakersWorkshopNotes": "Vyzkoušej novou tvář v maskářově dílně.",
"backgroundCemeteryGateText": "Hřbitovní brána",
"backgroundCemeteryGateNotes": "Straš u hřbitovní brány.",
"backgroundAutumnBridgeText": "Podzimní most",
"backgroundAutumnBridgeNotes": "Obdivuj krásu podzimního mostu.",
"backgroundInsideACrystalText": "Uvnitř krystalu."
"backgroundAutumnBridgeText": "Most na podzim",
"backgroundAutumnBridgeNotes": "Obdivuj krásu mostu na podzim.",
"backgroundInsideACrystalText": "Uvnitř krystalu",
"backgrounds032023": "Sada 106: Zveřejněna v březnu 2023",
"backgroundOldTimeyBasketballCourtText": "Retro basketbalové hřiště",
"backgroundOldTimeyBasketballCourtNotes": "Zaházej si na koš na retro basketbalovém hřišti.",
"backgroundJungleWateringHoleText": "Napajedlo v džungli",
"backgroundJungleWateringHoleNotes": "Zastav se na doušek u džunglového napajedla.",
"backgroundMangroveForestText": "Mangrovový les",
"backgroundMangroveForestNotes": "Prozkoumej okraj mangrovového lesa.",
"backgrounds052023": "Sada 108: Zveřejněna v květnu 2023",
"backgroundInAPaintingText": "V obraze",
"backgroundFlyingOverHedgeMazeText": "Let nad labyrintem ze živého plotu",
"backgroundFlyingOverHedgeMazeNotes": "Žasněte při letu nad labyrintem ze živého plotu.",
"backgroundCretaceousForestText": "Křídový les",
"backgroundCretaceousForestNotes": "Vychutnejte si pradávnou zeleň křídového lesa.",
"backgroundLeafyTreeTunnelNotes": "Procházejte se tunelem z listnatých stromů.",
"backgroundSpringtimeShowerText": "Jarní přeháňka",
"backgroundSpringtimeShowerNotes": "Podívejte se na květnatou jarní přeháňku.",
"backgroundUnderWisteriaText": "Pod vistérií",
"backgrounds022023": "SADA 105: Vydáno v únoru 2023",
"backgroundInFrontOfFountainText": "Před Fontánou",
"backgroundInFrontOfFountainNotes": "Procházej se před Fontánou.",
"backgroundGoldenBirdcageText": "Zlatá klec",
"backgroundGoldenBirdcageNotes": "Schovej se v zlaté kleci.",
"backgroundFancyBedroomText": "Luxusní ložnice",
"backgroundFancyBedroomNotes": "Dopřej si luxus v luxusní ložnici.",
"backgrounds042023": "Sada 107: Zveřejněna v dubnu 2023",
"backgroundLeafyTreeTunnelText": "Tunel z listnatých stromů",
"backgroundUnderWisteriaNotes": "Odpočiňte si pod vistérií.",
"backgroundInAPaintingNotes": "Užijte si kreativní činnosti uvnitř obrazu.",
"backgrounds012023": "SADA 104: Vydáno v lednu 2023",
"backgroundRimeIceText": "Jinovatka",
"backgroundRimeIceNotes": "Pokochej se třpytivou jinovatkou.",
"backgroundSnowyTempleText": "Zasněžený chrám",
"backgroundSnowyTempleNotes": "Pokochej se klidným zasněženým chrámem.",
"backgroundWinterLakeWithSwansText": "Zimní jezero s labutěmi",
"backgroundWinterLakeWithSwansNotes": "Užij si přírodu u zimního jezera s labutěmi.",
"backgrounds122022": "SADA 103: Vydáno v prosinci 2022",
"backgroundBranchesOfAHolidayTreeText": "Větve svátečního stromku",
"backgroundBranchesOfAHolidayTreeNotes": "Dováděj na větvích svátečního stromku.",
"backgroundInsideACrystalNotes": "Vyhlédni z nitra krystalu.",
"backgroundSnowyVillageText": "Zasněžená vesnice",
"backgroundSnowyVillageNotes": "Pokochej se zasněženou vesnicí.",
"backgrounds062023": "Sada 109: Zveřejněna v červnu 2023",
"backgroundInAnAquariumText": "V akváriu",
"backgroundInAnAquariumNotes": "Zaplavejte si poklidně s rybkami v akváriu.",
"backgroundInsideAdventurersHideoutText": "V úkrytu dobrodruhů",
"backgroundInsideAdventurersHideoutNotes": "Naplánujte cestu v úkrytu dobrodruhů.",
"backgroundCraterLakeText": "Kráterové jezero",
"backgroundCraterLakeNotes": "Obdivujte nádherné kráterové jezero.",
"backgrounds072023": "Sada 110: Zveřejněna v červenci 2023",
"backgroundOnAPaddlewheelBoatText": "Na loďce s lopatkovým kolem",
"backgroundOnAPaddlewheelBoatNotes": "Projet se na loďce s lopatkovým kolem.",
"backgroundColorfulCoralText": "Barevný korál",
"backgroundColorfulCoralNotes": "Potopte se mezi barevné korály.",
"backgrounds082023": "Sada 111: zveřejněaa v srpnu 2023",
"backgroundBonsaiCollectionText": "Sbírka bonsají",
"backgroundBoardwalkIntoSunsetNotes": "Vydejte se po Stezce do západu slunce.",
"backgroundBoardwalkIntoSunsetText": "Stezka do západu slunce"
}
+1 -1
View File
@@ -54,7 +54,7 @@
"battleGear": "Bojová výzbroj",
"gear": "Výbava",
"autoEquipBattleGear": "Automaticky použít nové vybavení",
"costume": "Kostým",
"costume": "kostým",
"useCostume": "Použít kostým",
"costumePopoverText": "Vyber \"Použít kostým\", abys vybavil svého avatara, aniž bys nějak ovlivnil statistiky tvé bojové výzbroje! To znamená, že můžeš obléct svého avatara do jakéhokoliv vybavení chceš a stále mít tvojí nejlepší bojovou výzbroj na sobě.",
"autoEquipPopoverText": "Zvol tuto možnost pro automatické nasazení koupeného vybavení.",
+4 -2
View File
@@ -1,5 +1,5 @@
{
"stable": "Stáj",
"stable": "Mazlíčci a Mounty",
"pets": "Mazlíčci",
"activePet": "Aktivní mazlíček",
"noActivePet": "Bez aktivního mazlíčka",
@@ -109,5 +109,7 @@
"wackyPets": "Šílená zvířátka",
"invalidAmount": "Neplatný počet jídla,je vyžadováno pozitivní celé číslo",
"tooMuchFood": "Snažíš se dát svému zvířeti moc jídla, akce byla zrušena",
"notEnoughFood": "Nemáš dost jídla"
"notEnoughFood": "Nemáš dost jídla",
"veteranCactus": "Kaktus Veterán",
"veteranDragon": "Drak Veterán"
}
+21 -1
View File
@@ -160,5 +160,25 @@
"newPMNotificationTitle": "Nová zpráva od <%= name %>",
"displaynameIssueNewline": "Zobrazovaná jména nesmí obsahovat zpětné lomítko následované písmenem N.",
"resetAccount": "Resetovat účet",
"giftedSubscriptionWinterPromo": "Ahoj <%= username %>, získal/a jsi <%= monthCount %> měsíce/ů předplatného jako součást naší sváteční dárkové akce!"
"giftedSubscriptionWinterPromo": "Ahoj <%= username %>, získal/a jsi <%= monthCount %> měsíce/ů předplatného jako součást naší sváteční dárkové akce!",
"generalSettings": "Hlavní nastavení",
"siteData": "Údaje o webu",
"taskSettings": "Nastavení úkolu",
"confirmCancelChanges": "Jste si jistí? Neuložené změny přijdou vniveč.",
"account": "Účet",
"loginMethods": "Možnosti přihlášení",
"character": "Postava",
"siteLanguage": "Jazyk webu",
"showLevelUpModal": "Při dosažení vyšší úrovně",
"showHatchPetModal": "Při odchovu zvířátka",
"showRaisePetModal": "Jak z domácího mazlíčka vychovat jízdní zvíře",
"showStreakModal": "Při dosažení úspěchu v sérii",
"baileyAnnouncement": "Nejnovější oznámení společnosti Bailey",
"view": "Zobrazit",
"feedbackPlaceholder": "Vlož zpětnou vazbu",
"downloadCSV": "Stáhni si CSV",
"downloadAs": "Ulož jako",
"yourUserData": "Tvá uživatelská data",
"taskHistory": "Historie",
"yourUserDataDisclaimer": "Zde si lze stáhnout výpis historie úkolů nebo kompletní uživatelská data."
}
+4 -1
View File
@@ -935,5 +935,8 @@
"backgroundWaterfallWithRainbowText": "Wasserfall mit Regenbogen",
"backgroundWaterfallWithRainbowNotes": "Bewundere die atemberaubende Schönheit eines Wasserfalls mit Regenbogen.",
"backgrounds042026": "SET 143: Veröffentlicht im April 2026",
"backgrounds052026": "SET 144: Veröffentlicht im Mai 2026"
"backgrounds052026": "SET 144: Veröffentlicht im Mai 2026",
"backgroundRidingACometText": "Ein Kometenritt",
"backgroundRidingACometNotes": "Reise durch das All bei einem Kometenritt!",
"backgroundElvenCitadelText": "Elven Citadel"
}
+6 -2
View File
@@ -3484,7 +3484,7 @@
"shieldSpecialWinter2026WarriorText": "Raureif Schild",
"shieldSpecialWinter2026WarriorNotes": "Stoppe eiskalt Hindernisse mit diesem praktischen, pieksigen Schild. Erhöht Ausdauer um %= con %>. Limitierte Ausgabe Winterausrüstung 2025-2026.",
"headMystery202602Text": "Kirschblüte Fuchsohren",
"headMystery202602Notes": " Diese Ohren schärfen dein Gehör so sehr, dass du im nahenden Frühling das Wachsen der Blütenknospen an den Zweigen der Bäume hören kannst. Gewährt keinen Attributbonus. Februar 2026 Abonnentengegenstand.",
"headMystery202602Notes": "Diese Ohren schärfen dein Gehör so sehr, dass du im nahenden Frühling das Wachsen der Blütenknospen an den Zweigen der Bäume hören kannst. Gewährt keinen Attributbonus. Februar 2026 Abonnentengegenstand.",
"headArmoireLoneCowpokeHatNotes": "Howdy Kumpel! Hasst dus auch so, wenn du draußen auf dem Schießstand bist, an Aufgaben arbeitest und dir die Sonne in die Augen scheint? Also, gute Sache, dass du dafür jetzt nen Hut hast. Erhöht deine Wahrnehmung um <%= per %>. Verzauberter Schrank: Einsamer Cowboy Set (Item 1 of 2)",
"shieldSpecialWinter2026HealerText": "Sternenexplosion",
"shieldArmoireDoubleBassNotes": "Bom doo bom brrrr brr brr brrrr! Versammle deine Party, um euch zu erden oder zu tanzen, während ihr euch Musik von dieser tiefen Double Bass anhört. Erhört Ausdauer und Stärke um jeweils <%= attrs %>. Verzauberter Schwank: Musikinstrumente Set 2 (Gegenstand 3 von 3)",
@@ -3497,5 +3497,9 @@
"backMystery202602Notes": "Diese flauschigen Schweife haben die Farbe der Kirschblüte, eine Erinnerung, dass der Frühling auf dem Weg ist. Gewährt keinen Autobusbonus. Februar 2026 Abonnentengegenstand.",
"backArmoireHarpsichordText": "Cembalo",
"weaponSpecialSpring2026HealerText": "Schneeglöckchen Stab",
"weaponSpecialSpring2026HealerNotes": "Eine Gelegenheit für einen Neuanfang liegt direkt vor dir, und mit diesem prächtigen Stab wirst du bereit sein! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Frühlingsausrüstung 2026."
"weaponSpecialSpring2026HealerNotes": "Eine Gelegenheit für einen Neuanfang liegt direkt vor dir, und mit diesem prächtigen Stab wirst du bereit sein! Erhöht Intelligenz um <%= int %>. Limitierte Ausgabe Frühlingsausrüstung 2026.",
"armorSpecialSpring2026WarriorText": "Froschrüstung",
"armorSpecialSpring2026WarriorNotes": "Hüpf in Aktion, sobald der Schnee taut. Erhöht Ausdauer um <%= con %>. Limitierte Ausgabe Frühlingsausrüstung 2026.",
"armorSpecialSpring2026RogueText": "Birkenrinde Rüstung",
"armorSpecialSpring2026RogueNotes": "Trotze dem unvermeidlichen Frühlingsregen ebenso wie leichten Brisen. Erhöht Wahrnehmung um <%= per %>. Limitierte Ausgabe Frühlingsausrüstung 2026."
}
+7 -1
View File
@@ -11,5 +11,11 @@
"rebirthPop": "Beginne sofort von vorn mit einem Charakter auf Level 1, aber behalte Erfolge, Sammelgegenstände und Ausrüstung. Deine Aufgaben und ihre Verläufe bleiben erhalten, werden aber auf gelb zurückgesetzt. Deine Strähnen verfallen, außer für Aufgaben, die von aktiven Herausforderungen oder Gruppenplänen stammen. Gold, Erfahrung, Mana und alle Effekte von Fähigkeiten werden entfernt. All das wird sofort in Kraft treten.",
"rebirthName": "Sphäre der Wiedergeburt",
"rebirthComplete": "Du wurdest wiedergeboren!",
"nextFreeRebirth": "<strong><%= days %> Tage</strong> bis zur <strong>KOSTENLOSEN</strong> Sphäre der Wiedergeburt"
"nextFreeRebirth": "<strong><%= days %> Tage</strong> bis zur <strong>KOSTENLOSEN</strong> Sphäre der Wiedergeburt",
"rebirthUnlockedNewItem": "Ort der Wiedergeburt Freigeschaltet",
"rebirthUnlockedOrb": "Ein neues Abendteuer ist bverfügbar!",
"rebirthUnlockedDesc": "Nutze den Ort der Wiedergeburt um ein neues Leben in dein Habitica Abendteuer zu bekommen wenn du das Gefühl hast, alles erreicht zu haben. Du beginnst wieder bei Level 1 und es beginnt wieder von vorne.",
"rebirthNewAchievement": "Neue Auszeichnung",
"rebirthNewAdventure": "Ein neues Abendteuer beginnt nun!",
"rebirthStackInfo": "Diese Auszeichnung kann sich stapeln, jedes Mal, wenn du den Ort der Wiedergeburt nutzt."
}
@@ -1075,6 +1075,18 @@
"backgroundElvenCitadelText": "Elven Citadel",
"backgroundElvenCitadelNotes": "Take the scenic journey to an Elven Citadel.",
"backgrounds062026": "SET 145: Released June 2026",
"backgroundBeachWithVolcanoText": "Beach with Volcano",
"backgroundBeachWithVolcanoNotes": "Watch nature's wonder on a Beach with a Volcano.",
"backgrounds072026": "SET 146: Released July 2026",
"backgroundTropicalCoralGardenText": "Tropical Coral Garden",
"backgroundTropicalCoralGardenNotes": "Dive into a Tropical Coral Garden.",
"backgrounds082026": "SET 147: Released August 2026",
"backgroundVegetableGardenText": "Vegetable Garden",
"backgroundVegetableGardenNotes": "Plant tasty greens in a Vegetable Garden.",
"timeTravelBackgrounds": "Steampunk Backgrounds",
"backgroundAirshipText": "Airship",
"backgroundAirshipNotes": "Become a sky sailor on board your very own Airship.",
+61 -1
View File
@@ -587,6 +587,15 @@
"weaponSpecialSpring2026MageText": "Maypole Parasol",
"weaponSpecialSpring2026MageNotes": "An opportunity to celebrate approaches, and with this pretty parasol pole, you will be ready! Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"weaponSpecialSummer2026WarriorText": "Gator Machete",
"weaponSpecialSummer2026WarriorNotes": "This flashy, fancy weapon fits right into your swampcore aesthetic. Increases Strength by <%= str %>. Limited Edition Summer 2026 Gear.",
"weaponSpecialSummer2026RogueText": "Tsunami Blade",
"weaponSpecialSummer2026RogueNotes": "This clever, curvy weapon fits right into your seacore aesthetic. Increases Strength by <%= str %>. Limited Edition Summer 2026 Gear.",
"weaponSpecialSummer2026HealerText": "Puffin Lance",
"weaponSpecialSummer2026HealerNotes": "This fine, feather-adorned weapon fits right into your islandcore aesthetic. Increases Intelligence by <%= int %>. Limited Edition Summer 2026 Gear.",
"weaponSpecialSummer2026MageText": "Tiger Shark Spear",
"weaponSpecialSummer2026MageNotes": "This dangerous, double-ended weapon fits right into your oceancore aesthetic. Increases Intelligence by <%= int %> and Perception by <%= per %>. Limited Edition Summer 2026 Gear.",
"weaponMystery201411Text": "Pitchfork of Feasting",
"weaponMystery201411Notes": "Stab your enemies or dig in to your favorite foods - this versatile pitchfork does it all! Confers no benefit. November 2014 Subscriber Item.",
"weaponMystery201502Text": "Shimmery Winged Staff of Love and Also Truth",
@@ -637,6 +646,10 @@
"weaponMystery202601Notes": "An icy bubble shield that grants magical protection from opposing elements. Confers no benefit. January 2026 Subscriber Item.",
"weaponMystery202603Text": "Wisteria Wizard Staff",
"weaponMystery202603Notes": "Cast spells to warm the spring air and encourage the blossoms to bud! Confers no benefit. March 2026 Subscriber Item.",
"weaponMystery202607Text": "Oceanmancer's Fishy Familiars",
"weaponMystery202607Notes": "These colorful companions will channel your aqueous abilities. Confers no benefit. July 2026 Subscriber Item.",
"weaponMystery202608Text": "Beaming Magenta Blade",
"weaponMystery202608Notes": "Bright, beautiful, dangerous to your undone Dailies. Confers no benefit. August 2026 Subscriber Item.",
"weaponMystery301404Text": "Steampunk Cane",
"weaponMystery301404Notes": "Excellent for taking a turn about town. March 3015 Subscriber Item. Confers no benefit.",
@@ -865,6 +878,14 @@
"weaponArmoireBambooFluteNotes": "Hwhoooo! Hu-whooooo! Gather your party for a meditation session or self-care nap while relaxing to tunes played on this bamboo flute. Increases Constitution and Intelligence by <%= attrs %> each. Enchanted Armoire: Musical Instrument Set 2 (Item 2 of 3)",
"weaponArmoirePrettyPinkParasolText": "Pretty Pink Parasol",
"weaponArmoirePrettyPinkParasolNotes": "Pretty and practical is the preeminent permutation. And for a particularly impressive presentation, give this parasol a spin! Increases all stats by <%= attrs %> each. Enchanted Armoire: Pretty in Pink Set (Item 1 of 2)",
"weaponArmoireBrightRainbowKiteText": "Rainbow Kite",
"weaponArmoireBrightRainbowKiteNotes": "This kites colors are bright and loud. Watching it soar high will make you proud! Increases all stats by <%= attrs %> each. Enchanted Armoire: Rainbow Kite Set (Item 1 of 2).",
"weaponArmoirePastelRainbowKiteText": "Pastel Rainbow Kite",
"weaponArmoirePastelRainbowKiteNotes": "This kites colors are muted and soft. It dances and spins as it soars aloft! Increases all stats by <%= attrs %> each. Enchanted Armoire: Rainbow Kite Set (Item 2 of 2).",
"weaponArmoireKendoShinaiText": "Kendo Shinai",
"weaponArmoireKendoShinaiNotes": "Light and soft, you can use this bamboo practice sword as you strive to improve yourself. Increases Strength by <%= str %>. Enchanted Armoire: Kendo Set (Item 3 of 3).",
"weaponArmoireGardenRakeText": "Garden Rake",
"weaponArmoireGardenRakeNotes": "Step 1: Rake all the fallen leaves into a giant pile. Step 2: Celebrate a job well done by jumping into the pile. Step 3: Repeat. Increases Constitution by <%= con %>. Enchanted Armoire: Gardener Set 2 (Item 1 of 2).",
"armor": "armor",
"armorCapitalized": "Armor",
@@ -1432,6 +1453,15 @@
"armorSpecialSpring2026MageText": "Maypole Dancer Outfit",
"armorSpecialSpring2026MageNotes": "Arrive ready to dance, picnic, and enjoy the warm weather spring brings. Increases Intelligence by <%= int %>. Limited Edition Spring 2026 Gear.",
"armorSpecialSummer2026WarriorText": "Gator Suit",
"armorSpecialSummer2026WarriorNotes": "Conceal yourself in this suit, but dont hide from your problems. Gather your gator grit and meet your tasks like the alligator you are. Increases Constitution by <%= con %>. Limited Edition Summer 2026 Gear.",
"armorSpecialSummer2026RogueText": "Tsunami Suit",
"armorSpecialSummer2026RogueNotes": "Cloak yourself in this tsunami suit, but dont hide from your problems. Summon a strong storm to have your back and meet your tasks like the adventurer you are. Increases Perception by <%= per %>. Limited Edition Summer 2026 Gear.",
"armorSpecialSummer2026HealerText": "Puffin Suit",
"armorSpecialSummer2026HealerNotes": "Fit yourself in this suit, but dont hide from your problems. Produce your puffin power and tackle your tasks like the puffin you are. Increases Constitution by <%= con %>. Limited Edition Summer 2026 Gear.",
"armorSpecialSummer2026MageText": "Tiger Shark Suit",
"armorSpecialSummer2026MageNotes": "Slide into this suit, but dont hide from your problems. Show your shark shine and swim right up to face those tasks like the shark you are. Increases Intelligence by <%= int %>. Limited Edition Summer 2026 Gear.",
"armorMystery201402Text": "Messenger Robes",
"armorMystery201402Notes": "Shimmering and strong, these robes have many pockets to carry letters. Confers no benefit. February 2014 Subscriber Item.",
"armorMystery201403Text": "Forest Walker Armor",
@@ -1826,6 +1856,8 @@
"armorArmoireSoftYellowSuitNotes": "Yellow is an energetic color. Wear this to bed, and you will wake up with the sun the next morning ready to tackle a day full of tasks. Increases Constitution and Strength by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 2 of 3).",
"armorArmoireHandstandOutfitText": "Handstand",
"armorArmoireHandstandOutfitNotes": "Things sure do look different when youre upside-down, dont they? If youre feeling stuck, its time for a fresh perspective! Increases Perception by <%= per %>. Enchanted Armoire: Handstand Set (Item 1 of 1).",
"armorArmoireKendoBoguText": "Kendo Bōgu",
"armorArmoireKendoBoguNotes": "This might be training armor, but it offers more than enough protection for your path ahead. Increases Constitution by <%= con %>. Enchanted Armoire: Kendo Set (Item 2 of 3).",
"headgear": "helm",
"headgearCapitalized": "Headgear",
@@ -2387,6 +2419,15 @@
"headSpecialSpring2026MageText": "Mayflower Crown",
"headSpecialSpring2026MageNotes": "Make a joyous statement with bright blooms encircling your head. Increases Perception by <%= per %>. Limited Edition Spring 2026 Gear.",
"headSpecialSummer2026WarriorText": "Gator Helm",
"headSpecialSummer2026WarriorNotes": "Go forth and be productive! If you get any pushback, just snap back and show your sharp teeth. Increases Strength by <%= str %>. Limited Edition Summer 2026 Gear.",
"headSpecialSummer2026RogueText": "Tsunami Helm",
"headSpecialSummer2026RogueNotes": "Go forth and be productive! If you lose your way, just follow the flow. Increases Perception by <%= per %>. Limited Edition Summer 2026 Gear.",
"headSpecialSummer2026HealerText": "Puffin Helm",
"headSpecialSummer2026HealerNotes": "Go forth and be productive! If you encounter complications, just gather them up in your colorful beak and take them somewhere else. Increases Intelligence by <%= int %>. Limited Edition Summer 2026 Gear.",
"headSpecialSummer2026MageText": "Tiger Shark Helm",
"headSpecialSummer2026MageNotes": "Go forth and be productive! If an obstacle dares to get in your way, just crush it with your mighty jaws. Increases Perception by <%= per %>. Limited Edition Summer 2026 Gear.",
"headSpecialGaymerxText": "Rainbow Warrior Helm",
"headSpecialGaymerxNotes": "In celebration of the GaymerX Conference, this special helmet is decorated with a radiant, colorful rainbow pattern! GaymerX is a game convention celebrating LGTBQ and gaming and is open to everyone.",
@@ -2578,6 +2619,8 @@
"headMystery202603Notes": "This jaunty hat not only enhances your magical ability, it also has a lovely spring scent! Confers no benefit. March 2026 Subscriber Item.",
"headMystery202604Text": "Audacious Astronaut Helmet",
"headMystery202604Notes": "In space, no one can hear you check off your To Dos. But the real reward is your sense of personal accomplishment! Confers no benefit. April 2026 Subscriber Item.",
"headMystery202606Text": "Holiday Hat",
"headMystery202606Notes": "Holidays are made for enjoying the sunshine - but dont get burned! Confers no benefit. June 2026 Subscriber Item.",
"headMystery301404Text": "Fancy Top Hat",
"headMystery301404Notes": "A fancy top hat for the finest of gentlefolk! January 3015 Subscriber Item. Confers no benefit.",
@@ -2812,6 +2855,8 @@
"headArmoireFloppyYellowHatNotes": "Many spells have been sewn into this simple hat, giving it a youthful yellow color. Increases all stats by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 1 of 3).",
"headArmoireVerdantArmingCapText": "Verdant Page Arming Cap",
"headArmoireVerdantArmingCapNotes": "This comfy, cushioned coif makes you battle-ready and helps you withstand anything heavy that could come your way. Increases Perception and Constitution by <%= attrs %> each. Enchanted Armoire: Verdant Page Set (Item 1 of 2).",
"headArmoireKendoMenText": "Kendo Men",
"headArmoireKendoMenNotes": "You might be surprised by how well you can see through the grille as you follow the way of the sword. Increases Perception by <%= per %>. Enchanted Armoire: Kendo Set (Item 1 of 3).",
"offhand": "off-hand item",
"offHandCapitalized": "Off-Hand Item",
@@ -3136,6 +3181,11 @@
"shieldSpecialSpring2026HealerText": "Snowdrop Leaf",
"shieldSpecialSpring2026HealerNotes": "Create a light breeze with this fan as the days grow warmer. It doubles as a writing utensil in a pinch. Increases Constitution by <%= con %>. Limited Edition Spring 2026 Gear.",
"shieldSpecialSummer2026WarriorText": "Gator Shield",
"shieldSpecialSummer2026WarriorNotes": "Deflect oncoming challenges with this stylish, shiny shield. And when youve successfully cleared your list, crank up the music and have a party! Increases Constitution by <%= con %>. Limited Edition Summer 2026 Gear.",
"shieldSpecialSummer2026HealerText": "Puffin Potion",
"shieldSpecialSummer2026HealerNotes": "Keep your colony of fellow puffins healthy with this potion. It tastes great with fish! Increases Constitution by <%= con %>. Limited Edition Summer 2026 Gear.",
"shieldMystery201601Text": "Resolution Slayer",
"shieldMystery201601Notes": "This blade can be used to parry away all distractions. Confers no benefit. January 2016 Subscriber Item.",
"shieldMystery201701Text": "Time-Freezer Shield",
@@ -3168,6 +3218,12 @@
"shieldMystery202511Notes": "This rugged shield of icy rock protects you from bad Habits but won't freeze your hands. Confers no benefit. November 2025 Subscriber Item.",
"shieldMystery202605Text": "Nightfall Shield",
"shieldMystery202605Notes": "Let the moons shining light protect you from dangers in the dark. Confers no benefit. May 2026 Subscriber Item.",
"shieldMystery202606Text": "Holiday Hammock",
"shieldMystery202606Notes": "Between tasks, hop in this hammock, relax, and enjoy the scenery! Confers no benefit. June 2026 Subscriber Item.",
"shieldMystery202607Text": "Oceanmancer's Briny Bubble",
"shieldMystery202607Notes": "Tumultuous waters bend to your mighty magical will. Confers no benefit. July 2026 Subscriber Item.",
"shieldMystery202608Text": "Brilliant Emerald Blade",
"shieldMystery202608Notes": "Slice and dice all your tasks into manageable pieces! Confers no benefit. August 2026 Subscriber Item.",
"shieldMystery301405Text": "Clock Shield",
"shieldMystery301405Notes": "Time is on your side with this towering clock shield! Confers no benefit. June 3015 Subscriber Item.",
@@ -3353,7 +3409,9 @@
"shieldArmoireSoftYellowPillowText": "Soft Yellow Pillow",
"shieldArmoireSoftYellowPillowNotes": "The experienced warrior packs a pillow for any expedition. Grow and shine as you consolidate all youve learned during past adventures… even while you nap. Increases Intelligence and Perception by <%= attrs %> each. Enchanted Armoire: Yellow Loungewear Set (Item 3 of 3).",
"shieldArmoireVerdantBannerText": "Verdant Page Banner",
"shieldArmoireVerdantBannerNotes": "Wave your banner high to signal friends its time to rally together! Intelligence by <%= int %>. Enchanted Armoire: Verdant Page Set (Item 2 of 2).",
"shieldArmoireVerdantBannerNotes": "Wave your banner high to signal friends its time to rally together! Increases Intelligence by <%= int %>. Enchanted Armoire: Verdant Page Set (Item 2 of 2).",
"shieldArmoireGardenHoseText": "Garden Hose",
"shieldArmoireGardenHoseNotes": "This magical hose never kinks and can infinitely stretch to reach every inch of your space. All your flowers, trees, shrubs, and thirsty pets can enjoy a drink from it. Increases Perception by <%= per %>. Enchanted Armoire: Gardener Set 2 (Item 2 of 2).",
"back": "Back Accessory",
"backBase0Text": "No Back Accessory",
@@ -3805,6 +3863,8 @@
"eyewearMystery202503Notes": "This piercing gaze will strike terror into any fighter who dares to challenge you! Confers no benefit. March 2025 Subscriber Item.",
"eyewearMystery202510Text": "Gliding Ghoul Eyes",
"eyewearMystery202510Notes": "These spooky eyes glow like the Harvest Moon. Confers no benefit. October 2025 Subscriber Item.",
"eyewearMystery202606Text": "Holiday Shades",
"eyewearMystery202606Notes": "Your eyes are shaded but your outlook is still sunny! Confers no benefit. June 2026 Subscriber Item.",
"eyewearMystery301404Text": "Eyewear Goggles",
"eyewearMystery301404Notes": "No eyewear could be fancier than a pair of goggles - except, perhaps, for a monocle. Confers no benefit. April 3015 Subscriber Item.",
+3 -1
View File
@@ -243,5 +243,7 @@
"playerReportModalBody": "You should only report a player who violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habiticas Community Guidelines.",
"targetUserNotExist": "Target User: '<%= userName %>' does not exist.",
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
"confirmPurchase": "Confirm Purchase"
"confirmPurchase": "Confirm Purchase",
"avoidSPI": "Avoid SPI",
"avoidSPIDetails": "For your privacy, avoid including <%= firstLink %>sensitive personal information<%= linkClose %> (SPI) when using Habitica. Your account data, including tasks, is stored on our servers so you can access it from any device.<br><br>To learn more, review our <%= secondLink %>Privacy Policy<%= linkClose %>."
}
+42 -38
View File
@@ -209,44 +209,48 @@
"fall2023BogCreatureHealerSet": "Bog Creature (Healer)",
"winter2024SnowyOwlRogueSet": "Snowy Owl (Rogue)",
"winter2024FrozenHealerSet": "Frozen (Healer)",
"winter2024PeppermintBarkWarriorSet": "Peppermint Bark Set (Warrior)",
"winter2024NarwhalWizardMageSet": "Narwhal Wizard Set (Mage)",
"spring2024FluoriteWarriorSet": "Fluorite Set (Warrior)",
"spring2024HibiscusMageSet": "Hibiscus Set (Mage)",
"spring2024BluebirdHealerSet": "Bluebird Set (Healer)",
"spring2024MeltingSnowRogueSet": "Melting Snow Set (Rogue)",
"summer2024WhaleSharkWarriorSet": "Whale Shark Set (Warrior)",
"summer2024SeaAnemoneMageSet": "Sea Anemone Set (Mage)",
"summer2024SeaSnailHealerSet": "Sea Snail Set (Healer)",
"summer2024NudibranchRogueSet": "Nudibranch Set (Rogue)",
"fall2024FieryImpWarriorSet": "Fiery Imp Set (Warrior)",
"fall2024UnderworldSorcerorMageSet": "Underworld Sorceror Set (Mage)",
"fall2024SpaceInvaderHealerSet": "Space Invader Set (Healer)",
"fall2024BlackCatRogueSet": "Black Cat Set (Rogue)",
"winter2025MooseWarriorSet": "Moose Set (Warrior)",
"winter2025AuroraMageSet": "Aurora Set (Mage)",
"winter2025StringLightsHealerSet": "String Lights Set (Healer)",
"winter2025SnowRogueSet": "Snow Set (Rogue)",
"spring2025SunshineWarriorSet": "Sunshine Set (Warrior)",
"spring2025CrystalPointRogueSet": "Crystal Point Set (Rogue)",
"spring2025PlumeriaHealerSet": "Plumeria Set (Healer)",
"spring2025MantisMageSet": "Mantis Set (Mage)",
"summer2025ScallopWarriorSet": "Scallop Set (Warrior)",
"summer2025SquidRogueSet": "Squid Set (Rogue)",
"summer2025SeaAngelHealerSet": "Sea Angel Set (Healer)",
"summer2025FairyWrasseMageSet": "Fairy Wrasse Set (Mage)",
"fall2025SasquatchWarriorSet": "Sasquatch Set (Warrior)",
"fall2025SkeletonRogueSet": "Skeleton Set (Rogue)",
"fall2025KoboldHealerSet": "Kobold Set (Healer)",
"fall2025MaskedGhostMageSet": "Masked Ghost Set (Mage)",
"winter2026RimeReaperWarriorSet": "Rime Reaper Set (Warrior)",
"winter2026SkiRogueSet": "Ski Set (Rogue)",
"winter2026PolarBearHealerSet": "Polar Bear Set (Healer)",
"winter2026MidwinterCandleMageSet": "Midwinter Candle Set (Mage)",
"spring2026FrogWarriorSet": "Frog Set (Warrior)",
"spring2026BranchRogueSet": "Spring Branch Set (Rogue)",
"spring2026SnowdropHealerSet": "Snowdrop Set (Healer)",
"spring2026MaypoleMageSet": "Maypole Set (Mage)",
"winter2024PeppermintBarkWarriorSet": "Peppermint Bark (Warrior)",
"winter2024NarwhalWizardMageSet": "Narwhal Wizard (Mage)",
"spring2024FluoriteWarriorSet": "Fluorite (Warrior)",
"spring2024HibiscusMageSet": "Hibiscus (Mage)",
"spring2024BluebirdHealerSet": "Bluebird (Healer)",
"spring2024MeltingSnowRogueSet": "Melting Snow (Rogue)",
"summer2024WhaleSharkWarriorSet": "Whale Shark (Warrior)",
"summer2024SeaAnemoneMageSet": "Sea Anemone (Mage)",
"summer2024SeaSnailHealerSet": "Sea Snail (Healer)",
"summer2024NudibranchRogueSet": "Nudibranch (Rogue)",
"fall2024FieryImpWarriorSet": "Fiery Imp (Warrior)",
"fall2024UnderworldSorcerorMageSet": "Underworld Sorceror (Mage)",
"fall2024SpaceInvaderHealerSet": "Space Invader (Healer)",
"fall2024BlackCatRogueSet": "Black Cat (Rogue)",
"winter2025MooseWarriorSet": "Moose (Warrior)",
"winter2025AuroraMageSet": "Aurora (Mage)",
"winter2025StringLightsHealerSet": "String Lights (Healer)",
"winter2025SnowRogueSet": "Snow (Rogue)",
"spring2025SunshineWarriorSet": "Sunshine (Warrior)",
"spring2025CrystalPointRogueSet": "Crystal Point (Rogue)",
"spring2025PlumeriaHealerSet": "Plumeria (Healer)",
"spring2025MantisMageSet": "Mantis (Mage)",
"summer2025ScallopWarriorSet": "Scallop (Warrior)",
"summer2025SquidRogueSet": "Squid (Rogue)",
"summer2025SeaAngelHealerSet": "Sea Angel (Healer)",
"summer2025FairyWrasseMageSet": "Fairy Wrasse (Mage)",
"fall2025SasquatchWarriorSet": "Sasquatch (Warrior)",
"fall2025SkeletonRogueSet": "Skeleton (Rogue)",
"fall2025KoboldHealerSet": "Kobold (Healer)",
"fall2025MaskedGhostMageSet": "Masked Ghost (Mage)",
"winter2026RimeReaperWarriorSet": "Rime Reaper (Warrior)",
"winter2026SkiRogueSet": "Ski (Rogue)",
"winter2026PolarBearHealerSet": "Polar Bear (Healer)",
"winter2026MidwinterCandleMageSet": "Midwinter Candle (Mage)",
"spring2026FrogWarriorSet": "Frog (Warrior)",
"spring2026BranchRogueSet": "Spring Branch (Rogue)",
"spring2026SnowdropHealerSet": "Snowdrop (Healer)",
"spring2026MaypoleMageSet": "Maypole (Mage)",
"summer2026AlligatorWarriorSet": "Alligator (Warrior)",
"summer2026PuffinHealerSet": "Puffin (Healer)",
"summer2026TigerSharkMageSet": "Tiger Shark (Mage)",
"summer2026TsunamiRogueSet": "Tsunami (Rogue)",
"winterPromoGiftHeader": "GIFT A SUBSCRIPTION, GET ONE FREE!",
"winterPromoGiftDetails1": "Until January 6th only, when you gift somebody a subscription, you get the same subscription for yourself for free!",
"winterPromoGiftDetails2": "Please note that if you or your gift recipient already have a recurring subscription, the gifted subscription will only start after that subscription is cancelled or has expired. Thanks so much for your support! <3",
@@ -186,6 +186,9 @@
"mysterySet202603": "Wisteria Wizard Set",
"mysterySet202604": "Audacious Astronaut Set",
"mysterySet202605": "Nightfall Nimbus Set",
"mysterySet202606": "Holiday Hammock Set",
"mysterySet202607": "Oceanmancer Set",
"mysterySet202608": "Beaming Blades Set",
"mysterySet301404": "Steampunk Standard Set",
"mysterySet301405": "Steampunk Accessories Set",
"mysterySet301703": "Peacock Steampunk Set",
+10
View File
@@ -123,6 +123,16 @@
"dayOfMonth": "Day of the Month",
"month": "Month",
"months": "Months",
"every": "every",
"everyDay": "every day",
"everyXDays": "every <%= count %> days",
"everyWeek": "every week",
"everyXWeeks": "every <%= count %> weeks",
"everyMonth": "every month",
"everyXMonths": "every <%= count %> months",
"everyYear": "every year",
"everyXYears": "every <%= count %> years",
"fifthWeekWarning": "This task <strong>will not</strong> appear due during months with fewer <%= day %>s",
"week": "Week",
"weeks": "Weeks",
"year": "Year",
@@ -63,11 +63,11 @@
"letsGetStarted": "Let's get started!",
"onboardingProgress": "<%= percentage %>% progress",
"gettingStartedDesc": "Complete these onboarding tasks and youll earn <strong>5 Achievements</strong> and <strong class=\"gold-amount\">100 Gold</strong> once youre done!",
"achievementRosyOutlookModalText": "You tamed all the Cotton Candy Pink Mounts!",
"achievementRosyOutlookText": "Has tamed all Cotton Candy Pink Mounts.",
"achievementRosyOutlookModalText": "You tamed all the Candyfloss Pink Mounts!",
"achievementRosyOutlookText": "Has tamed all Candyfloss Pink Mounts.",
"achievementRosyOutlook": "Rosy Outlook",
"achievementTickledPinkModalText": "You collected all the Cotton Candy Pink Pets!",
"achievementTickledPinkText": "Has collected all Cotton Candy Pink Pets.",
"achievementTickledPinkModalText": "You collected all the Candyfloss Pink Pets!",
"achievementTickledPinkText": "Has collected all Candyfloss Pink Pets.",
"achievementTickledPink": "Tickled Pink",
"foundNewItemsCTA": "Head to your Inventory and try combining your new hatching potion and egg!",
"foundNewItemsExplanation": "Completing tasks gives you a chance to find items, like Eggs, Hatching Potions, and Pet Food.",
@@ -109,11 +109,11 @@
"achievementSeasonalSpecialistModalText": "You completed all the seasonal quests!",
"achievementSeasonalSpecialistText": "Has completed all the Spring and Winter seasonal quests: Egg Hunt, Trapper Santa, and Find the Cub!",
"achievementSeasonalSpecialist": "Seasonal Specialist",
"achievementVioletsAreBlueText": "Has collected all Cotton Candy Blue Pets.",
"achievementVioletsAreBlueModalText": "You collected all the Cotton Candy Blue Pets!",
"achievementWildBlueYonderModalText": "You tamed all the Cotton Candy Blue Mounts!",
"achievementVioletsAreBlueText": "Has collected all Candyfloss Blue Pets.",
"achievementVioletsAreBlueModalText": "You collected all the Candyfloss Blue Pets!",
"achievementWildBlueYonderModalText": "You tamed all the Candyfloss Blue Mounts!",
"achievementDomesticated": "E-I-E-I-O",
"achievementWildBlueYonderText": "Has tamed all Cotton Candy Blue Mounts.",
"achievementWildBlueYonderText": "Has tamed all Candyfloss Blue Mounts.",
"achievementVioletsAreBlue": "Violets are Blue",
"achievementWildBlueYonder": "Wild Blue Yonder",
"achievementDomesticatedModalText": "You collected all the domesticated pets!",
@@ -123,7 +123,7 @@
"achievementZodiacZookeeperText": "Has hatched all standard colours of zodiac pets: Rat, Cow, Bunny, Snake, Horse, Sheep, Monkey, Rooster, Wolf, Tiger, Flying Pig, and Dragon!",
"achievementShadyCustomer": "Shady Customer",
"achievementShadyCustomerText": "Has collected all Shade Pets.",
"achievementShadyCustomerModalText": "You colleted all the Shade Pets!",
"achievementShadyCustomerModalText": "You collected all the Shade Pets!",
"achievementShadeOfItAll": "The Shade of It All",
"achievementShadeOfItAllText": "Has tamed all Shade Mounts.",
"achievementShadeOfItAllModalText": "You tamed all the Shade Mounts!",
+16 -5
View File
@@ -37,7 +37,7 @@
"backgroundHauntedHouseText": "Haunted House",
"backgroundHauntedHouseNotes": "Sneak through a Haunted House.",
"backgroundPumpkinPatchText": "Pumpkin Patch",
"backgroundPumpkinPatchNotes": "Carve jack-o-lanterns in a Pumpkin Patch.",
"backgroundPumpkinPatchNotes": "Carve jack-o'-lanterns in a Pumpkin Patch.",
"backgrounds112014": "SET 6: Released November 2014",
"backgroundHarvestFeastText": "Harvest Feast",
"backgroundHarvestFeastNotes": "Enjoy a Harvest Feast.",
@@ -665,7 +665,7 @@
"backgroundRopeBridgeText": "Rope Bridge",
"backgrounds112021": "SET 90: Released November 2021",
"backgroundFortuneTellersShopText": "Fortune Teller's Shop",
"backgroundFortuneTellersShopNotes": "Seek tantalizing hints of your future in a Fortune Teller's Shop.",
"backgroundFortuneTellersShopNotes": "Seek tantalising hints of your future in a Fortune Tellers Shop.",
"backgroundInsideAPotionBottleText": "Inside a Potion Bottle",
"backgroundInsideAPotionBottleNotes": "Peer through the glass while hoping for rescue from Inside a Potion Bottle.",
"backgroundSpiralStaircaseText": "Spiral Staircase",
@@ -865,8 +865,8 @@
"backgroundSpectralCandleRoomNotes": "Commune with spirits in a Room of Spectral Candles.",
"backgroundMonstrousCaveText": "Monstrous Cave",
"backgroundMonstrousCaveNotes": "Gaze into the maw of the Monstrous Cave.",
"backgroundJackOLanternStacksText": "Jack O'Lantern Stacks",
"backgroundJackOLanternStacksNotes": "Admire a field of Jack OLantern Stacks.",
"backgroundJackOLanternStacksText": "Jack-o-Lantern Stacks",
"backgroundJackOLanternStacksNotes": "Admire a field of Jack-o-Lantern Stacks.",
"backgrounds062024": "Set 121: Released June 2024",
"backgroundShellGateText": "Shell Gate",
"backgroundShellGateNotes": "March through the decorated coral of a Shell Gate.",
@@ -930,5 +930,16 @@
"backgroundWinterDesertWithSaguarosText": "Winter Desert with Saguaros",
"backgroundWinterDesertWithSaguarosNotes": "Breathe the crisp air of a Winter Desert with Saguaros.",
"backgrounds022026": "SET 141: Released February 2026",
"backgroundElegantPalaceText": "Elegant Palace"
"backgroundElegantPalaceText": "Elegant Palace",
"backgroundWaterfallWithRainbowText": "Waterfall with Rainbow",
"backgroundWaterfallWithRainbowNotes": "Admire the breathtaking beauty of a Waterfall with a Rainbow.",
"backgroundRidingACometText": "Riding a Comet",
"backgrounds032026": "SET 142: Released March 2026",
"backgrounds042026": "SET 143: Released April 2026",
"backgroundRidingACometNotes": "Travel through space while Riding a Comet!",
"backgroundElvenCitadelText": "Elven Citadel",
"backgroundElvenCitadelNotes": "Take a scenic journey to an Elven Citadel.",
"backgrounds052026": "SET 144: Released May 2026",
"backgroundOnAStrangePlanetText": "On a Strange Planet",
"backgroundOnAStrangePlanetNotes": "Venture where no Habitican has gone before: On a Strange Planet."
}
+10 -7
View File
@@ -5,7 +5,7 @@
"keepIt": "Keep It",
"removeIt": "Remove It",
"brokenChallenge": "Broken Challenge Link",
"challengeCompleted": "This challenge has been completed, and the winner was <span class=\"badge\"><%= user %></span>! What to do with the orphan tasks?",
"challengeCompleted": "Challenge Completed!",
"unsubChallenge": "Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?",
"challenges": "Challenges",
"endDate": "Ends",
@@ -40,12 +40,12 @@
"onlyGroupLeaderChal": "Only the group leader can create challenges",
"tavChalsMinPrize": "Prize must be at least 1 Gem for Public Challenges.",
"cantAfford": "You can't afford this prize. Purchase more gems or lower the prize amount.",
"challengeIdRequired": "\"challengeId\" must be a valid UUID.",
"winnerIdRequired": "\"winnerId\" must be a valid UUID.",
"challengeIdRequired": "challengeId must be a valid UUID.",
"winnerIdRequired": "winnerId must be a valid UUID.",
"challengeNotFound": "Challenge not found or you don't have access.",
"onlyLeaderDeleteChal": "Only the challenge leader can delete it.",
"onlyLeaderUpdateChal": "Only the challenge leader can update it.",
"winnerNotFound": "Winner with id \"<%= userId %>\" not found or not part of the challenge.",
"winnerNotFound": "Winner with id <%= userId %> not found or not part of the challenge.",
"onlyChalLeaderEditTasks": "Tasks belonging to a challenge can only be edited by the leader.",
"userAlreadyInChallenge": "User is already participating in this challenge.",
"cantOnlyUnlinkChalTask": "Only broken challenges tasks can be un-linked.",
@@ -103,11 +103,14 @@
"flaggedAndHidden": "Challenge flagged and hidden",
"messageChallengeFlagAlreadyReported": "You have already reported this Challenge.",
"flaggedNotHidden": "Challenge flagged once, not hidden",
"cannotClone": "This Challenge cannot be cloned because one or more players have reported it as inappropriate. A staff member will contact you shortly with instructions. If over 48 hours have passed and you have not heard from them, please email admin@habitica.com for assistance.",
"cannotClone": "This Challenge cannot be cloned because one or more players have reported it as inappropriate. A staff member will contact you shortly with instructions. If over 48 hours have passed and you have not heard from them, please e-mail admin@habitica.com for assistance.",
"resetFlags": "Reset Flags",
"cannotClose": "This Challenge cannot be closed because one or more players have reported it as inappropriate. A staff members will contact you shortly with instructions. If over 48 hours have passed and you have not heard from them, please email admin@habitica.com for assistance.",
"cannotClose": "This Challenge cannot be closed because one or more players have reported it as inappropriate. A staff members will contact you shortly with instructions. If over 48 hours have passed and you have not heard from them, please e-mail admin@habitica.com for assistance.",
"abuseFlagModalBodyChallenge": "You should only report a Challenge that violates the <%= firstLinkStart %>Community Guidelines<%= linkEnd %> and/or <%= secondLinkStart %>Terms of Service<%= linkEnd %>. Submitting a false report is a violation of Habitica's Community Guidelines.",
"cannotMakeChallenge": "You are unable to create public Challenges as your account currently does not have chat privileges. Please contact admin@habitica.com for more information.",
"deleteChallengeRefundDescription": "If you delete this Challenge, you will be refunded the Gem prize and the Challenge tasks will remain on the participants' task boards.",
"brokenTask": "Broken Challenge Link"
"brokenTask": "Broken Challenge Link",
"brokenTaskDescription": "This task was part of a challenge, but has been removed from it. What would you like to do?",
"brokenChallengeDescription": "This task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?",
"challengeCompletedDescription": "The winner was <%= user %>! What to do with the orphan tasks?"
}
+15 -8
View File
@@ -1,5 +1,5 @@
{
"communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the <a href='https://habitica.com/static/community-guidelines' target='_blank'>Community Guidelines</a> (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to email <%= hrefBlankCommunityManagerEmail %>!",
"communityGuidelinesWarning": "Please keep in mind that your Display Name, profile photo, and blurb must comply with the <a href='https://habitica.com/static/community-guidelines' target='_blank'>Community Guidelines</a> (e.g. no profanity, no adult topics, no insults, etc). If you have any questions about whether or not something is appropriate, feel free to e-mail <%= hrefBlankCommunityManagerEmail %>!",
"profile": "Profile",
"avatar": "Customise Avatar",
"editAvatar": "Customise Avatar",
@@ -56,14 +56,14 @@
"autoEquipBattleGear": "Auto-equip new gear",
"costume": "Costume",
"useCostume": "Use Costume",
"costumePopoverText": "Select \"Use Costume\" to equip items to your avatar without affecting the Stats from your Battle Gear! This means that you can dress up your avatar in whatever outfit you like while still having your best Battle Gear equipped.",
"costumePopoverText": "Select Use Costume to equip items to your avatar without affecting the Stats from your Battle Gear! This means that you can dress up your avatar in whatever outfit you like while still having your best Battle Gear equipped.",
"autoEquipPopoverText": "Select this option to automatically equip gear as soon as you purchase it.",
"costumeDisabled": "You have disabled your costume.",
"gearAchievement": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set for a class! You have attained the following complete sets:",
"gearAchievementNotification": "You have earned the \"Ultimate Gear\" Achievement for upgrading to the maximum gear set for a class!",
"gearAchievement": "You have earned the Ultimate Gear Achievement for upgrading to the maximum gear set for a class! You have attained the following complete sets:",
"gearAchievementNotification": "You have earned the Ultimate Gear Achievement for upgrading to the maximum gear set for a class!",
"moreGearAchievements": "To attain more Ultimate Gear badges, change classes on <a href='/user/settings/site' target='_blank'>the Settings &gt; Site page</a> and buy your new class's gear!",
"armoireUnlocked": "For more equipment, check out the <strong>Enchanted Armoire!</strong> Click on the Enchanted Armoire Reward for a random chance at special Equipment! It may also give you random XP or food items.",
"ultimGearName": "Ultimate Gear - <%= ultClass %>",
"ultimGearName": "Ultimate Gear<%= ultClass %>",
"ultimGearText": "Has upgraded to the maximum weapon and armour set for the <%= ultClass %> class.",
"level": "Level",
"levelUp": "Level Up!",
@@ -86,7 +86,7 @@
"noMoreAllocate": "Now that you've hit level 100, you won't gain any more Stat Points. You can continue levelling up, or start a new adventure at level 1 by using the <a href='/shops/market'>Orb of Rebirth</a>.",
"stats": "Stats",
"strength": "Strength",
"strText": "Strength increases the chance of random \"critical hits\" and the Gold, Experience, and drop chance boost from them. It also helps deal damage to boss monsters.",
"strText": "Strength increases the chance of random critical hits and the Gold, Experience, and drop chance boost from them. It also helps deal damage to boss monsters.",
"constitution": "Constitution",
"conText": "Constitution reduces the damage you take from negative Habits and missed Dailies.",
"perception": "Perception",
@@ -122,7 +122,7 @@
"taskAllocationPop": "Assigns points based on the Strength, Intelligence, Constitution, and Perception categories associated with the tasks you complete",
"distributePoints": "Distribute Unallocated Points",
"distributePointsPop": "Assigns all unallocated Stat Points according to the selected allocation scheme.",
"warriorText": "Warriors score more and better \"critical hits\", which randomly give bonus Gold, Experience, and drop chance for scoring a task. They also deal heavy damage to boss monsters. Play a Warrior if you find motivation from unpredictable jackpot-style rewards, or want to dish out the hurt in boss Quests!",
"warriorText": "Warriors score more and better critical hits, which randomly give bonus Gold, Experience, and drop chance for scoring a task. They also deal heavy damage to boss monsters. Play a Warrior if you find motivation from unpredictable jackpot-style rewards, or want to dish out the hurt in boss Quests!",
"wizardText": "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 Habitica, or if you are strongly motivated by levelling up and unlocking advanced features!",
"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 Habitica, or if you are strongly motivated by levelling 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!",
@@ -193,5 +193,12 @@
"customizations": "Customisations",
"skins": "Skins",
"pointsAvailable": "Points Available",
"assignedStat": "Assigned Stat"
"assignedStat": "Assigned Stat",
"strTaskText": "Increases critical hit chance and damage when scoring tasks. Also increases damage dealt to Quest bosses.",
"perTaskText": "Increases item drop chance, daily item drop cap, task streak bonuses, and Gold earned when completing tasks.",
"autoAllocate": "Auto Allocate",
"allocationMethod": "Allocation Method",
"statAllocationInfo": "Each level earns you one point to assign to a Stat of your choice. You can do so manually, or let the game decide for you using one of the Automatic Allocation options.",
"conTaskText": "Reduces damage taken from missed Dailies and negative Habits. Does not reduce damage from Quest bosses.",
"intTaskText": "Increases Experience earned from tasks. Also increases your Mana cap and Mana regeneration rate."
}
@@ -12,10 +12,10 @@
"commGuideList02C": "<strong>Do not post images or text that are violent, threatening, or sexually explicit/suggestive, or that promote discrimination, bigotry, racism, sexism, hatred, harassment or harm against any individual or group</strong>. Not even as a joke or meme. This includes slurs as well as statements. Not everyone has the same sense of humour, and so something that you consider a joke may be hurtful to another.",
"commGuideList02D": "<strong>Be mindful that Habiticans are of all ages and backgrounds</strong>. Challenges and player profiles should not mention adult topics, use profanity, or promote contention or conflict.",
"commGuideList02E": "<strong>If a staff member tells you that a term is disallowed on Habitica, even if it is a term that you did not realise was problematic, that decision is final.</strong> Additionally, slurs will be dealt with very severely, as they are also a violation of the Terms of Service.",
"commGuideList02G": "<strong>Comply immediately with any Staff request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, etc. Do not argue with Staff. If you have concerns or comments about Staff actions, email <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
"commGuideList02G": "<strong>Comply immediately with any Staff request.</strong> This could include, but is not limited to, requesting you limit your posts in a particular space, editing your profile to remove unsuitable content, etc. Do not argue with Staff. If you have concerns or comments about Staff actions, e-mail <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> to contact our community manager.",
"commGuideList02J": "<strong>Do not spam</strong>. Spamming may include, but is not limited to: sending multiple unsolicited private messages, sending nonsensical messages, sending multiple promotional messages about a Party or Challenge, or creating multiple similar or low quality Challenges in a row. Staff has discretion to determine what messages are considered spamming.",
"commGuideList02K": "<strong>Do not send links without explanation or context</strong>. If players clicking on a link will result in any benefit to you, you need to disclose that. This applies in messages as well as Challenges.",
"commGuideList02L": "<strong>We highly discourage the exchange of personal information--particularly information that can be used to identify you</strong>. Identifying information can include but is not limited to: your address, your email, and your password or API token. If you are asked for personal information in a Party chat or private message, we highly recommend that you do not respond, and alert the Staff by either reporting the message or contacting <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with screenshots of the messages if more context is needed.",
"commGuideList02L": "<strong>We highly discourage the exchange of personal informationparticularly information that can be used to identify you</strong>. Identifying information can include but is not limited to: your address, your e-mail, and your password or API token. If you are asked for personal information in a Party chat or private message, we highly recommend that you do not respond, and alert the Staff by either reporting the message or contacting <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with screenshots of the messages if more context is needed.",
"commGuidePara037": "<strong>No Guilds, Public or Private, should be created for the purpose of attacking any group or individual</strong>. Creating such a Guild is grounds for an instant ban. Fight bad habits, not your fellow adventurers!",
"commGuideHeadingInfractionsEtc": "Infractions, Consequences, and Restoration",
"commGuideHeadingInfractions": "Infractions",
@@ -27,21 +27,21 @@
"commGuideList05A": "Other breaches of the Terms and Conditions not specified here",
"commGuideList05B": "Hate Speech/Images, Harassment/Stalking, Cyber-Bullying, Flaming, and Trolling",
"commGuideList05C": "Violation of Probation",
"commGuideList05D": "Impersonation of Staff - this includes claiming player-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Staff",
"commGuideList05D": "Impersonation of Staffthis includes claiming player-created spaces not affiliated with Habitica are official and/or moderated by Habitica or its Staff",
"commGuideList05E": "Repeated Moderate Infractions",
"commGuideList05F": "Creation of a duplicate account to avoid consequences",
"commGuideList05G": "Intentional deception of Staff in order to avoid consequences or to get another user in trouble",
"commGuideHeadingModerateInfractions": "Moderate Infractions",
"commGuidePara054": "These infractions will have moderate consequences. When in conjunction with multiple infractions, the consequences may grow more severe.",
"commGuidePara055": "The following are some examples of Moderate Infractions. This is not a comprehensive list.",
"commGuideList06A": "Ignoring, disrespecting or arguing with Staff. If you are concerned about one of the rules or the behavior of the staff, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
"commGuideList06A": "Ignoring, disrespecting or arguing with Staff. If you are concerned about one of the rules or the behaviour of the staff, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>.",
"commGuideList06C": "Intentionally flagging innocent Challenges, profiles, or messages.",
"commGuideList06E": "Repeatedly Committing Minor Infractions",
"commGuideHeadingMinorInfractions": "Minor Infractions",
"commGuidePara056": "Minor Infractions, while discouraged, still have minor consequences. If they continue to occur, they can lead to more severe consequences over time. Minor infractions are typically first time violations of these Guidelines but may include other circumstances.",
"commGuidePara057": "The following are some examples of Minor Infractions. This is not a comprehensive list.",
"commGuideList07A": "First-time violation of Public Space Guidelines",
"commGuideList07B": "Any statements or actions that trigger a \"Please Don't\" from a Staff member. When you are asked not to do something publicly, this in itself can be a consequence. If Staff have to issue many of these corrections to the same person, it may count as a larger infraction",
"commGuideList07B": "Any statements or actions that trigger a Please Dont” from a Staff member. When you are asked not to do something publicly, this in itself can be a consequence. If Staff have to issue many of these corrections to the same person, it may count as a larger infraction.",
"commGuidePara057A": "Some posts may be hidden because they contain sensitive information or might give people the wrong idea. Typically this does not count as an infraction, particularly not the first time it happens!",
"commGuideHeadingConsequences": "Consequences",
"commGuidePara059": "<strong>Community infractions have direct consequences.</strong> Some sample consequences are outlined below.",
@@ -58,20 +58,20 @@
"commGuideList11E": "Edits of problematic content by Staff",
"commGuideHeadingRestoration": "Restoration",
"commGuidePara061": "Habitica is devoted to self-improvement, and we believe in second chances. <strong>If you commit an infraction and receive a consequence, view it as a chance to evaluate your actions and strive to be a better member of the community</strong>.",
"commGuidePara062": "<strong>If you wish to ask questions about your infraction or consequences, apologize, or make a plea for reinstatement, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with your User ID or @username</strong>. It is <strong>your</strong> responsibility to reach out.",
"commGuidePara062": "<strong>If you wish to ask questions about your infraction or consequences, apologise, or make a plea for reinstatement, please contact us at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> with your User ID or @username</strong>. It is <strong>your</strong> responsibility to reach out.",
"commGuidePara063": "If you do not understand your consequences or the nature of your infraction, or if you have other questions related to the matter, you can contact the staff to discuss it at <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a>. Cooperate with any restrictions which have been imposed, and endeavor to meet the requirements to have any penalties lifted.",
"commGuideHeadingMeet": "Meet the Staff",
"commGuidePara007": "The Habitica Staff keep the app and sites running and can act as chat moderators. They have purple tags marked with crowns. Their title is \"Heroic\".",
"commGuidePara007": "The Habitica Staff keep the app and sites running and can act as chat moderators. They have purple tags marked with crowns. Their title is Heroic.",
"commGuidePara009": "The current Staff Members are (from left to right):",
"commGuideAKA": "<%= habitName %> aka <%= realName %>",
"commGuideOnGitHub": "<%= gitHubName %> on GitHub",
"commGuidePara011b": "on GitHub/Fandom",
"commGuidePara011c": "on the Wiki",
"commGuidePara011d": "on GitHub",
"commGuidePara013": "In a community as big as Habitica, players come and go, and sometimes a Staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honor their work!",
"commGuidePara013": "In a community as big as Habitica, players come and go, and sometimes a Staff member or moderator needs to lay down their noble mantle and relax. The following are Staff and Moderators Emeritus. They no longer act with the power of a Staff member or Moderator, but we would still like to honour their work!",
"commGuidePara014": "Staff and Moderators Emeritus:",
"commGuideHeadingFinal": "The Final Section",
"commGuidePara067": "So there you have it, brave Habitican -- the Community Guidelines! Wipe that sweat off of your brow and give yourself some EXP for reading it all. If you have any questions or concerns about these Community Guidelines, please reach out to us via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and we will be happy to help clarify things.",
"commGuidePara067": "So there you have it, brave Habiticanthe Community Guidelines! Wipe that sweat off your brow and give yourself some EXP for reading it all. If you have any questions or concerns about these Community Guidelines, please reach out to us via <a href='mailto:admin@habitica.com' target='_blank'>admin@habitica.com</a> and we will be happy to help clarify things.",
"commGuidePara068": "Now go forth, brave adventurer, and slay some Dailies!",
"commGuideHeadingLinks": "Useful Links",
"commGuideLink02": "<a href='https://habitica.fandom.com/wiki/Habitica_Wiki' target='_blank'>The Wiki</a>: the biggest collection of information about Habitica. Note that this space is unofficial, being hosted by Fandom and maintained by players.",
+39 -38
View File
@@ -190,8 +190,8 @@
"hatchingPotionShade": "Shade",
"hatchingPotionSkeleton": "Skeleton",
"hatchingPotionZombie": "Zombie",
"hatchingPotionCottonCandyPink": "Cotton Candy Pink",
"hatchingPotionCottonCandyBlue": "Cotton Candy Blue",
"hatchingPotionCottonCandyPink": "Candyfloss Pink",
"hatchingPotionCottonCandyBlue": "Candyfloss Blue",
"hatchingPotionGolden": "Golden",
"hatchingPotionSpooky": "Spooky",
"hatchingPotionPeppermint": "Peppermint",
@@ -249,11 +249,11 @@
"foodCakeBaseThe": "the Basic Cake",
"foodCakeBaseA": "a Basic Cake",
"foodCakeCottonCandyBlue": "Candy Blue Cake",
"foodCakeCottonCandyBlueThe": "the Candy Blue Cake",
"foodCakeCottonCandyBlueA": "a Candy Blue Cake",
"foodCakeCottonCandyPink": "Candy Pink Cake",
"foodCakeCottonCandyPinkThe": "the Candy Pink Cake",
"foodCakeCottonCandyPinkA": "a Candy Pink Cake",
"foodCakeCottonCandyBlueThe": "the Sweet Blue Cake",
"foodCakeCottonCandyBlueA": "a Sweet Blue Cake",
"foodCakeCottonCandyPink": "Sweet Pink Cake",
"foodCakeCottonCandyPinkThe": "the Sweet Pink Cake",
"foodCakeCottonCandyPinkA": "a Sweet Pink Cake",
"foodCakeShade": "Chocolate Cake",
"foodCakeShadeThe": "the Chocolate Cake",
"foodCakeShadeA": "a Chocolate Cake",
@@ -272,36 +272,36 @@
"foodCakeRed": "Strawberry Cake",
"foodCakeRedThe": "the Strawberry Cake",
"foodCakeRedA": "a Strawberry Cake",
"foodCandySkeleton": "Bare Bones Candy",
"foodCandySkeletonThe": "the Bare Bones Candy",
"foodCandySkeletonA": "Bare Bones Candy",
"foodCandyBase": "Basic Candy",
"foodCandyBaseThe": "the Basic Candy",
"foodCandyBaseA": "Basic Candy",
"foodCandyCottonCandyBlue": "Sour Blue Candy",
"foodCandyCottonCandyBlueThe": "the Sour Blue Candy",
"foodCandyCottonCandyBlueA": "Sour Blue Candy",
"foodCandyCottonCandyPink": "Sour Pink Candy",
"foodCandyCottonCandyPinkThe": "the Sour Pink Candy",
"foodCandyCottonCandyPinkA": "Sour Pink Candy",
"foodCandyShade": "Chocolate Candy",
"foodCandyShadeThe": "the Chocolate Candy",
"foodCandyShadeA": "Chocolate Candy",
"foodCandyWhite": "Vanilla Candy",
"foodCandyWhiteThe": "the Vanilla Candy",
"foodCandyWhiteA": "Vanilla Candy",
"foodCandyGolden": "Honey Sweety",
"foodCandyGoldenThe": "the Honey Candy",
"foodCandyGoldenA": "Honey Candy",
"foodCandyZombie": "Rotten Candy",
"foodCandyZombieThe": "the Rotten Candy",
"foodCandyZombieA": "Rotten Candy",
"foodCandyDesert": "Sand Candy",
"foodCandyDesertThe": "the Sand Candy",
"foodCandyDesertA": "Sand Candy",
"foodCandyRed": "Cinnamon Candy",
"foodCandyRedThe": "the Cinnamon Candy",
"foodCandyRedA": "Cinnamon Candy",
"foodCandySkeleton": "Bare Bones Sweet",
"foodCandySkeletonThe": "the Bare Bones Sweet",
"foodCandySkeletonA": "Bare Bones Sweet",
"foodCandyBase": "Basic Sweet",
"foodCandyBaseThe": "the Basic Sweet",
"foodCandyBaseA": "Basic Sweet",
"foodCandyCottonCandyBlue": "Sour Blue Sweet",
"foodCandyCottonCandyBlueThe": "the Sour Blue Sweet",
"foodCandyCottonCandyBlueA": "Sour Blue Sweet",
"foodCandyCottonCandyPink": "Sour Pink Sweet",
"foodCandyCottonCandyPinkThe": "the Sour Pink Sweet",
"foodCandyCottonCandyPinkA": "Sour Pink Sweet",
"foodCandyShade": "Chocolate Sweet",
"foodCandyShadeThe": "the Chocolate Sweet",
"foodCandyShadeA": "Chocolate Sweet",
"foodCandyWhite": "Vanilla Sweet",
"foodCandyWhiteThe": "the Vanilla Sweet",
"foodCandyWhiteA": "Vanilla Sweet",
"foodCandyGolden": "Honey Sweet",
"foodCandyGoldenThe": "the Honey Sweet",
"foodCandyGoldenA": "Honey Sweet",
"foodCandyZombie": "Rotten Sweet",
"foodCandyZombieThe": "the Rotten Sweet",
"foodCandyZombieA": "Rotten Sweet",
"foodCandyDesert": "Sand Sweet",
"foodCandyDesertThe": "the Sand Sweet",
"foodCandyDesertA": "Sand Sweet",
"foodCandyRed": "Cinnamon Sweet",
"foodCandyRedThe": "the Cinnamon Sweet",
"foodCandyRedA": "Cinnamon Sweet",
"foodSaddleText": "Saddle",
"foodSaddleNotes": "Instantly raises one of your pets into a mount.",
"foodSaddleSellWarningNote": "Hey! This is a pretty useful item! Are you familiar with how to use a Saddle with your Pets?",
@@ -410,5 +410,6 @@
"questEggPlatypusText": "Platypus",
"questEggPlatypusMountText": "Platypus",
"questEggPlatypusAdjective": "a perfectionist",
"hatchingPotionOpal": "Opal"
"hatchingPotionOpal": "Opal",
"hatchingPotionAlien": "Alien"
}
+6 -6
View File
@@ -1,5 +1,5 @@
{
"playerTiersDesc": "The coloured usernames you see in chat represent a person's contributor tier. The higher the tier, the more the person has contributed to Habitica through art, code, the community, or more!",
"playerTiersDesc": "The coloured usernames you see in chat represent a persons contributor tier. The higher the tier, the more the person has contributed to Habitica through art, code, the community, or more!",
"tier1": "Tier 1 (Friend)",
"tier2": "Tier 2 (Friend)",
"tier3": "Tier 3 (Elite)",
@@ -19,8 +19,8 @@
"staff": "Habitica Staff",
"heroic": "Heroic",
"modalContribAchievement": "Contributor Achievement!",
"contribModal": "<%= name %>, you awesome person! You're now a tier <%= level %> contributor for helping Habitica.",
"contribLink": "See what prizes you've earned for your contribution!",
"contribModal": "<%= name %>, you awesome person! Youre now a tier <%= level %> contributor for helping Habitica.",
"contribLink": "See what prizes youve earned for your contribution!",
"contribName": "Contributor",
"contribText": "Has contributed to Habitica, whether via code, art, music, writing, or other methods.",
"kickstartName": "Kickstarter Backer - $<%= key %> Tier",
@@ -30,7 +30,7 @@
"contribLevel": "Contrib Tier",
"hallContributors": "Hall of Contributors",
"hallPatrons": "Hall of Patrons",
"noAdminAccess": "You don't have admin access.",
"noAdminAccess": "You dont have admin access.",
"userNotFound": "User not found.",
"invalidUUID": "UUID must be valid",
"title": "Title",
@@ -43,7 +43,7 @@
"conRewardsURL": "https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica#contributor-tier-rewards",
"surveysSingle": "Helped Habitica grow, either by filling out a survey or helping with a major testing effort. Thank you!",
"surveysMultiple": "Helped Habitica grow on <%= count %> occasions, either by filling out a survey or helping with a major testing effort. Thank you!",
"blurbHallPatrons": "This is the Hall of Patrons, where we honour the noble adventurers who backed Habitica's original Kickstarter. We thank them for helping us bring Habitica to life!",
"blurbHallPatrons": "This is the Hall of Patrons, where we honour the noble adventurers who backed Habiticas original Kickstarter. We thank them for helping us bring Habitica to life!",
"blurbHallContributors": "This is the Hall of Contributors, where open-source contributors to Habitica are honoured. Whether through code, art, music, writing, or even just helpfulness, they have earned <a href='https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica#contributor-tier-rewards' target='_blank'>Gems, exclusive Equipment</a>, and <a href='https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica#contributor-tiers' target='_blank'>prestigious titles</a>. You can contribute to Habitica, too! <a href='https://github.com/HabitRPG/habitica/wiki/Contributing-to-Habitica' target='_blank'>Find out more here.</a>",
"noPrivAccess": "You don't have the required privileges."
"noPrivAccess": "You dont have the required privileges."
}
+2 -2
View File
@@ -1,10 +1,10 @@
{
"lostAllHealth": "You ran out of Health!",
"dontDespair": "Don't despair!",
"deathPenaltyDetails": "You lost a Level, your Gold, and a piece of Equipment, but you can get them all back with hard work! Good luck—you'll do great.",
"deathPenaltyDetails": "You lost a Level, your Gold, and a piece of Equipment, but you can get them all back with hard work! Good luck—youll do brilliantly.",
"refillHealthTryAgain": "Refill Health & Try Again",
"dyingOftenTips": "Is this happening often? <a href='/static/faq#prevent-damage' target='_blank'>Here are some tips!</a>",
"losingHealthWarning": "Careful - You're Losing Health!",
"losingHealthWarning": "CarefulYou're Losing Health!",
"losingHealthWarning2": "Don't let your Health drop to zero! If you do, you'll lose a level, your Gold, and a piece of equipment.",
"toRegainHealth": "To regain Health:",
"lowHealthTips1": "Level up to fully heal!",
@@ -52,5 +52,5 @@
"workTodoProject": "Work project >> Complete work project",
"workDailyImportantTaskNotes": "Tap to specify your most important task",
"workDailyImportantTask": "Most important task >> Worked on todays most important task",
"workHabitMail": "Process email"
"workHabitMail": "Process e-mail"
}
+33 -25
View File
@@ -2,7 +2,7 @@
"frequentlyAskedQuestions": "Frequently Asked Questions",
"iosFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.fandom.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](https://habitica.fandom.com/wiki/FAQ), use the Ask a Question form! We're happy to help.",
"webFaqStillNeedHelp": "If you have a question that isn't on this list or on the [Wiki FAQ](http://habitica.fandom.com/wiki/FAQ), come ask in the [Habitica Help guild](https://habitica.com/groups/guild/5481ccf3-5d2d-48a9-a871-70a7380cee5a)! We're happy to help.",
"webFaqStillNeedHelp": "If you have a question that isnt on this list or on the [Wiki FAQ](https://habitica.fandom.com/wiki/FAQ), use the Ask a Question form [LINK NEEDED]! We're happy to help.",
"commonQuestions": "Common Questions",
"faqQuestion25": "What are the different task types?",
"webFaqAnswer25": "Habitica uses three different task types to accommodate your needs: Habits, Dailies, and To Dos.\n\nHabits can be positive or negative and represent something you may want to track multiple times per day, or on an unset schedule. Positive Habits will provide you with rewards, like Gold and Experience (Exp), while Negative Habits will cause you to lose health points (HP).\n\nDailies are repeated tasks you want to complete on a more structured schedule. For example, once per day, three times a week, or four times a month. Missing Dailies causes you to lose HP, but the more difficult they are, the better the rewards!\n\nTo Dos are one-off tasks that provide rewards after you complete them. To Dos can have a due date, but you wont lose HP if you miss it.\n\nPick the task type that best fits what you want to achieve!",
@@ -17,7 +17,7 @@
"faqQuestion65": "Are Group Plans supported on the mobile apps?",
"parties": "Parties",
"faqQuestion28": "Can I pause my Dailies if I need a break?",
"webFaqAnswer26": "Positive Habits (Behaviors you want to encourage; should have a plus button)\n\n * Take vitamins\n * Floss teeth\n * One hour of studying\n\nNegative Habits (Behaviors you want to limit or avoid; should have a minus button)\n\n * Smoking\n * Doom scrolling\n * Biting nails\n\nDual Habits (Habits that involve a positive vs. negative option; should have both plus and minus buttons)\n\n * Drink water vs. drink soda\n * Study vs. procrastinate\n\nSample Dailies (Tasks you want to repeat on a regular schedule)\n * Wash dishes\n * Water plants\n * 30 minutes of physical activity\n\nSample To Dos (Tasks you only need to do once)\n\n * Schedule appointment\n * Organise closet\n * Finish essay",
"webFaqAnswer26": "Positive Habits (Behaviours you want to encourage; should have a plus button)\n\n * Take vitamins\n * Floss teeth\n * One hour of studying\n\nNegative Habits (Behaviours you want to limit or avoid; should have a minus button)\n\n * Smoking\n * Doom scrolling\n * Biting nails\n\nDual Habits (Habits that involve a positive vs. negative option; should have both plus and minus buttons)\n\n * Drink water vs. drink fizzy drink\n * Study vs. procrastinate\n\nSample Dailies (Tasks you want to repeat on a regular schedule)\n * Wash dishes\n * Water plants\n * 30 minutes of physical activity\n\nSample To Dos (Tasks you only need to do once)\n\n * Schedule appointment\n * Organise closet\n * Finish essay",
"webFaqAnswer27": "The colour of a task is a visual representation of the tasks value. All tasks start as yellow for neutral. Blue is better, and red is worse. Heres how each task type determines the tasks value:\n\nHabits become more blue or red based on whether you tap the plus or minus button. Positive and negative Habits degrade to yellow over time if you dont complete them. Dual Habits only change colour based on your inputs.\n\nDailies change colour based on how often they are completed, becoming more blue as theyre completed or more red if theyre missed.\n\nTo Dos gradually get more red the longer they stay incomplete.\n\nThe more red the task, the more Gold and Experience youll earn for completing it, so be sure to take on even your toughest tasks!",
"webFaqAnswer28": "Yes! The “Pause Damage” button can be found in Settings. It will prevent you from losing HP for missed Dailies. This is helpful if you are on holiday, need a rest, or for any other reason you might need a break. If you are participating in a Quest, your own pending progress will be paused, but you will still take damage from your Party members missed Dailies.\n\nTo pause specific Dailies, you can edit the scheduling to make it due every 0 days until youre ready to restart it.",
"webFaqAnswer29": "You can recover 15 HP by purchasing a Health Potion from your Rewards column for 25 Gold. Additionally, you will always recover full HP when you level up!",
@@ -26,27 +26,27 @@
"faqQuestion31": "Why did I lose HP when interacting with a non-negative task?",
"webFaqAnswer31": "If you complete a task and lose HP when you shouldnt have, you encountered a delay while the server was syncing changes made on other platforms. For example, if you use Gold, Mana, or lose HP on the mobile app and then complete a task on the website, the server is simply confirming everything is in sync.",
"faqQuestion32": "How can I choose a class?",
"webFaqAnswer32": "All players start as the Warrior class until they reach level 10. Once you reach level 10, youll be given the choice between selecting a new class or continuing as a Warrior.\n\nEach class has different Equipment and Skills. If you don't want to choose a class, you can select \"Opt Out.\" If you choose to opt out, you can always enable the Class System from Settings later.\n\nIf youd like to change your class after Level 10, you can do so by using the Orb of Rebirth. The Orb of Rebirth becomes available in the Market for 6 Gems at level 50 or for free at level 100.\n\nAlternatively, you can change class at any time from Settings for 3 Gems. This will not reset your level like Orb of Rebirth, but it will allow you to re-allocate the skill points youve accumulated as youve levelled up to match your new class.",
"webFaqAnswer32": "All players start as the Warrior class until they reach level 10. Once you reach level 10, youll be given the choice between selecting a new class or continuing as a Warrior.\n\nEach class has different Equipment and Skills. If you dont want to choose a class, you can select Opt Out. If you choose to opt out, you can always enable the Class System from Settings later.\n\nIf youd like to change your class after Level 10, you can do so by using the Orb of Rebirth. The Orb of Rebirth becomes available in the Market for 6 Gems at level 50 or for free at level 100.\n\nAlternatively, you can change class at any time from Settings for 3 Gems. This will not reset your level like Orb of Rebirth, but it will allow you to re-allocate the skill points youve accumulated as youve levelled up to match your new class.",
"faqQuestion33": "What is the blue bar that appears after level 10?",
"webFaqAnswer33": "After you unlock the Class System, you also unlock Skills that require Mana to be cast. Mana is determined by your INT stat and can be adjusted by Skills and Equipment.",
"webFaqAnswer34": "Pets like Food that matches their colour. Base Pets are the exception, but all Base Pets like the same item. You can see the specific foods each Pet likes below:\n\n * Base Pets like Meat\n * White Pets like Milk\n * Desert Pets like Potatoes\n * Red Pets like Strawberries\n * Shade Pets like Chocolate\n * Skeleton Pets like Fish\n * Zombie Pets like Rotten Meat\n * Cotton Candy Pink Pets like Pink Candyfloss\n * Cotton Candy Blue Pets like Blue Candyfloss\n * Golden Pets like Honey",
"webFaqAnswer34": "Pets like Food that matches their colour. Base Pets are the exception, but all Base Pets like the same item. You can see the specific foods each Pet likes below:\n\n * Base Pets like Meat\n * White Pets like Milk\n * Desert Pets like Potatoes\n * Red Pets like Strawberries\n * Shade Pets like Chocolate\n * Skeleton Pets like Fish\n * Zombie Pets like Rotten Meat\n * Candyfloss Pink Pets like Pink Candyfloss\n * Candyfloss Blue Pets like Blue Candyfloss\n * Golden Pets like Honey",
"webFaqAnswer35": "Once youve fed your Pet enough to raise it into a Mount, youll need to hatch that type of Pet again to have it in your stable.\n\nTo view Mounts on the mobile apps:\n\n * From the Menu, select “Pets & Mounts” and switch to the Mounts tab\n\nTo view Mounts on the website:\n\n * From the Inventory menu, select “Pets and Mounts” and scroll down to the Mounts section",
"faqQuestion36": "How do I change the appearance of my Avatar?",
"webFaqAnswer36": "There are endless ways to customise the appearance of your Habitica Avatar! You can change your Avatars body shape, hair style and colour, or skin colour, or add glasses or mobility aids by selecting \"Customise Avatar\" from the menu.\n\nTo customise your Avatar on the mobile apps:\n * From the menu, select “Customise Avatar”\n\nTo customise your Avatar on the website:\n * From the user menu in the navigation, select \"Customise Avatar\"",
"webFaqAnswer36": "There are endless ways to customise the appearance of your Habitica Avatar! You can change your Avatars body shape, hair style and colour, or skin colour, or add glasses or mobility aids by selecting Customise Avatar from the menu.\n\nTo customise your Avatar on the mobile apps:\n * From the menu, select “Customise Avatar”\n\nTo customise your Avatar on the website:\n * From the user menu in the navigation, select Customise Avatar",
"faqQuestion37": "Why isnt my Equipment displaying on my Avatar?",
"webFaqAnswer37": "Check to see if the Costume option is toggled on. If your Avatar is wearing a Costume, that set of Equipment will show instead of your Battle Gear.\n\nTo toggle the Costume on the mobile apps:\n * From the menu, select “Equipment” to find the Costume toggle\n\nTo toggle the Costume on the website:\n * From your Inventory, select “Equipment” and locate the Costume toggle in the Costume tab of the Equipment drawer",
"faqQuestion38": "Why can't I purchase certain items?",
"webFaqAnswer38": "New Habitica players can only purchase the basic Warrior class Equipment. Players must buy Equipment in sequential order to unlock the next piece.\n\nMany pieces of Equipment are class-specific, which means that a player can only buy Equipment belonging to their current class.",
"webFaqAnswer39": "If youre looking to get more Equipment, you can become a Habitica Subscriber, take a chance on the Enchanted Armoire, or splurge during one of Habiticas Grand Galas.\n\nHabitica subscribers receive a special exclusive gear set every month and Mystic Hourglasses to buy past Equipment sets from the Time Traveller Shop.\n\nThe Enchanted Armoire treasure chest in your Rewards has over 350 pieces of Equipment! For 100 Gold, youll have a chance at receiving either special Equipment, Food to raise your Pet to a Mount, or Experience to level up!\n\nDuring the four seasonal Grand Galas, brand-new class Equipment becomes available for purchase with Gold and previous Gala sets can be purchased with Gems.",
"webFaqAnswer39": "If youre looking to get more Equipment, you can become a Habitica Subscriber, take a chance on the Enchanted Armoire, or splurge during one of Habiticas Grand Galas.\n\nHabitica subscribers receive a special exclusive gear set every month and Mystic Hourglasses to buy past Equipment sets from the Time Travellers Shop.\n\nThe Enchanted Armoire treasure chest in your Rewards has over 350 pieces of Equipment! For 100 Gold, youll have a chance at receiving either special Equipment, Food to raise your Pet to a Mount, or Experience to level up!\n\nDuring the four seasonal Grand Galas, brand-new class Equipment becomes available for purchase with Gold and previous Gala sets can be purchased with Gems.",
"faqQuestion40": "What are Gems, and how do I get them?",
"webFaqAnswer40": "Gems are Habiticas in-app paid currency used to purchase Equipment, Avatar Customisations, Backgrounds, and more! Gems can be purchased in bundles or with Gold if youre a Habitica subscriber. You can also win Gems by being selected as the winner of a Challenge.",
"webFaqAnswer41": "Mystic Hourglasses are Habiticas exclusive Subscriber currency used in the Time Travellers Shop. Subscribers receive a Mystic Hourglass at the start of each month they have subscription benefits, along with a bunch of other perks. Be sure to check out our subscription options if youre interested in the special Backgrounds, Pets, Quests, and Equipment offered in the Time Travellers Shop!",
"webFaqAnswer41": "Mystic Hourglasses are Habiticas exclusive Subscriber currency used in the Time Travellers Shop. Subscribers receive a Mystic Hourglass at the start of each month they have subscription benefits, along with a bunch of other perks. Be sure to check out our subscription options if youre interested in the special Backgrounds, Pets, Quests, and Equipment offered in the Time Travellers Shop!",
"faqQuestion42": "What can I do to increase accountability?",
"webFaqAnswer42": "One of the best ways to motivate yourself and hold yourself accountable for accomplishing your tasks is to join a Party! Partying with other Habitica players is a great way to take on Quests to receive Pets and Equipment, receive buffs from Party members Skills, and boost your motivation.\n\nAnother way to increase accountability is to join a Challenge. Challenges automatically add tasks related to a specific goal to your lists! They also add an element of competition against other Habitica players that may motivate you as you strive for the Gem prize. There are official Challenges created by the Habitica Team as well as Challenges made by other players.",
"faqQuestion43": "How do I take on Quests?",
"webFaqAnswer43": "To begin a Quest, you will need to be a member of a Party. Parties can be solo adventures where you challenge Quests alone, or you can invite other Habitica players to tackle Quests at a quicker rate!\n\nChoose a Quest Scroll from your inventory by selecting the “Begin Quest” button from your Party. Complete your tasks as you normally would to progress on the Quest! Youll either build up damage against a monster if youre taking on a Boss Quest, or have a chance to find items if youre taking on a Collection Quest. All pending progress is applied the next day.\n\nWhen you do enough damage or collect all items, the Quest is complete and you will receive your rewards!",
"faqQuestion44": "How can I delete Challenge tasks?",
"webFaqAnswer44": "You will need to leave the Challenge or wait for the Challenge to be closed in order to delete the associated tasks. A red megaphone icon implies the Challenge has been closed and a gray megaphone implies the Challenge is still running.\n\nTo delete Challenge tasks on the **Android** app:\n 1. Tap on a task belonging to the Challenge.\n 2. Tap on \"Delete\" in the upper right corner of the screen.\n 3. Choose to remove the Challenge tasks from your task list.\n\nTo delete Challenge tasks on the **iOS** app:\n 1. Find the Challenge task you wish to delete and look at the megaphone icon.\n 2. If the megaphone icon is red, tap on the task and select \"Delete\" at the bottom\n 3. If the megaphone icon is grey, youll need to find the Challenge and leave it to remove the task.\n\nTo delete Challenge tasks on the **website**:\n 1. Find the Challenge task you wish to delete and look at the megaphone icon.\n 2. If the megaphone icon is red, click it then choose to remove the tasks from your task list.\n 3. If the megaphone icon is grey, you'll need to find the Challenge and leave it to remove the task.",
"webFaqAnswer44": "You will need to leave the Challenge or wait for the Challenge to be closed in order to delete the associated tasks. A red megaphone icon implies the Challenge has been closed and a grey megaphone implies the Challenge is still running.\n\nTo delete Challenge tasks on the **Android** app:\n 1. Tap on a task belonging to the Challenge.\n 2. Tap on Delete in the upper right corner of the screen.\n 3. Choose to remove the Challenge tasks from your task list.\n\nTo delete Challenge tasks on the **iOS** app:\n 1. Find the Challenge task you wish to delete and look at the megaphone icon.\n 2. If the megaphone icon is red, tap on the task and select Delete at the bottom\n 3. If the megaphone icon is grey, youll need to find the Challenge and leave it to remove the task.\n\nTo delete Challenge tasks on the **website**:\n 1. Find the Challenge task you wish to delete and look at the megaphone icon.\n 2. If the megaphone icon is red, click it then choose to remove the tasks from your task list.\n 3. If the megaphone icon is grey, youll need to find the Challenge and leave it to remove the task.",
"faqQuestion45": "My Avatar transformed into a snowman, starfish, flower, or ghost. How can I change back?",
"contentFaqTitle": "Habitica Content Release Change FAQ",
"contentFaqPara0": "Habitica has so much fun and engaging content to offer, and we want everyone to be able to enjoy it all! Changes are coming to make it easier for new players to get started on their collection as well as for veteran players to complete theirs!",
@@ -64,7 +64,7 @@
"contentAnswer22": "Magic Hatching Potions will no longer be tied to Galas and will instead be on their own monthly release schedule themed to the ongoing festivities.",
"contentAnswer30": "Shops will rotate a selection of their items every month. This will help keep the amount of content in the shops manageable and easy to browse. The new schedule will offer fresh items to check out each month for newer players while creating a predictable schedule for veteran collectors.",
"contentQuestion3": "How is the content release schedule changing?",
"contentAnswer300": "<strong>1st of each month:</strong> New Subscriber set is released. Subscriber sets available in the Time Travellers Shop rotate.",
"contentAnswer300": "<strong>1st of each month:</strong> New Subscriber set is released. Subscriber sets available in the Time Travellers Shop rotate.",
"contentAnswer301": "<strong>7th of each month:</strong> New Enchanted Armoire items and one new Background released. Backgrounds available in the Customisation Shop rotate.",
"contentAnswer400": "Pet Quests",
"contentAnswer401": "Magic Hatching Potion Quests",
@@ -82,10 +82,10 @@
"contentAnswer60": "All other current events will continue as normal! Everyone will still get their special rewards and themed food as they do now.",
"contentAnswer61": "Valentines Day and New Year cards will be released on set dates.",
"contentQuestion6": "What will happen to other seasonal events, like Habitoween, April Fools Day, and Birthday?",
"contentQuestion7": "What about other items available in the Time Travellers Shop besides past Subscriber Sets?",
"contentQuestion7": "What about other items available in the Time Travellers Shop besides past Subscriber Sets?",
"contentAnswer63": "Wacky Pets will remain available throughout most of April.",
"contentAnswer70": "Backgrounds, Quests, Pets, and Mounts available in the Time Travellers Shop will remain available all year round.",
"contentAnswer71": "Stay tuned for further updates on planned improvements to the Time Travellers Shop experience.",
"contentAnswer70": "Backgrounds, Quests, Pets, and Mounts available in the Time Travellers Shop will remain available all year round.",
"contentAnswer71": "Stay tuned for further updates on planned improvements to the Time Travellers Shop experience.",
"contentFaqPara3": "If you have any questions not covered by the answers above, you can always contact our team at <%= mailto %>! Were excited for this new content release schedule and looking forward to even more projects in the future to help make Habitica better for all players.",
"subscriptionBenefitsAdjustments": "Subscriber Benefit Adjustments",
"subscriptionBenefitsFaqTitle": "Subscriber Benefit Adjustments FAQ",
@@ -99,7 +99,7 @@
"contentAnswer02": "Brand new <strong>Pet Quests, Magic Hatching Potion Quests, and Magic Hatching Potions</strong> will be released to fill out this new schedule!",
"contentAnswer03": "Backgrounds, Hair Colours, Hair Styles, Skins, Animal Ears, Animal Tails, and Shirts will now be purchasable from the brand new <strong>Customisation Shop!</strong>",
"contentAnswer200": "<strong>Summer Splash</strong>: June 21 to Sept 20",
"contentAnswer201": "<strong>Fall Festival</strong>: Sept 21 to Dec 20",
"contentAnswer201": "<strong>Autumn Festival</strong>: Sept 21 to Dec 20",
"contentAnswer302": "<strong>14th of each month:</strong> Pet Quests, Potion Quests, and Quest Bundles available in the Quest Shop rotate.",
"contentAnswer303": "<strong>21st of each month:</strong> Magic Hatching Potions available in the Market rotate.",
"contentQuestion4": "What brand-new content is coming?",
@@ -114,7 +114,7 @@
"subscriptionDetail20": "Under the current structure, it can be difficult to understand how many Mystic Hourglasses you would receive and when.",
"subscriptionDetail21": "The four subscription tiers were known to cause complications when upgrading or downgrading to different tiers.",
"subscriptionDetail22": "Gifted and recurring subscriptions had conflicting benefit experiences and rules that we wanted to simplify.",
"subscriptionDetail24": "We wanted subscribers to have more than four chances per year to collect items from the Time Travellers Shop.",
"subscriptionDetail24": "We wanted subscribers to have more than four chances per year to collect items from the Time Travellers Shop.",
"subscriptionHeading3": "Release day rewards",
"subscriptionPara1": "To help ease the transition to the new schedule, existing subscribers can expect some extra goodies on release day. We want to sincerely thank you for your continued support through this change!",
"subscriptionDetail30": "Players with recurring 1 month subscriptions or Group Plan subscriptions will receive 2 Mystic Hourglasses and 20 Gems.",
@@ -138,7 +138,7 @@
"contentAnswer10": "Habitica has been around since 2013 (wow!) and over time weve released thousands of items players can collect. This can be overwhelming, especially for new players. We want to be sure that we showcase everything we have to offer, and that excellent items released earlier in our history arent overlooked.",
"contentAnswer11": "When new players join between Grand Galas they are often unaware of these events and miss out on the fun. We want to be sure all new players can join in on our seasonal festivities no matter when they choose to start their journeys.",
"subscriptionDetail00": "All subscribers, including those with gifted subscriptions, will receive 1 Mystic Hourglass at the start of each month they have subscriber benefits.",
"subscriptionDetail23": "Giving one Mystic Hourglass per month allows subscribers to enjoy the rotating items in the Time Travellers Shop.",
"subscriptionDetail23": "Giving one Mystic Hourglass per month allows subscribers to enjoy the rotating items in the Time Travellers Shop.",
"subscriptionDetail32": "Players with recurring 12 month subscriptions will receive the 12 Mystic Hourglass bonus noted above and 20 Gems.",
"subscriptionDetail001": "All subscribers will receive Mystic Hourglasses on the same schedule, matching the release schedule of the monthly Mystery Gear Sets.",
"subscriptionDetail25": "We understand that finances change and we didnt want to punish subscribers for lapsed subscriptions by taking away benefits they had earned.",
@@ -150,7 +150,7 @@
"subscriptionDetail110": "If you raise the amount of Gems you can buy each month then cancel your subscription, you can pick back up at the same amount any time in the future, even if you purchase a lower subscription tier.",
"subscriptionDetail420": "Just like Mystery Gear Sets, you will not miss out on any Mystic Hourglasses or Gem cap increases if you dont log in while subscribed. The next time you log in, you will receive all benefits owed for each month you were subscribed.",
"subscriptionDetail4400": "If you currently have unlocked <%= initialNumber %> Gems per month, you will be set to <%= roundedNumber %>.",
"subscriptionPara3": "We hope this new schedule will be more predictable, allow more access to the amazing stock of items in the Time Travellers Shop, and give even more motivation to make progress on your tasks each month!",
"subscriptionPara3": "We hope this new schedule will be more predictable, allow more access to the amazing stock of items in the Time Travellers Shop, and give even more motivation to make progress on your tasks each month!",
"subscriptionDetail45": "Will purchasing extra gifted subscriptions get me more Mystic Hourglasses or a higher Gem cap faster?",
"subscriptionDetail430": "Cancelling a recurring subscription will set a termination date for your benefits, but you will still have full access to all perks of a subscription before that date. That means you will still receive monthly Mystic Hourglasses and Gem cap increases at the start of each month you have access to those benefits.",
"subscriptionDetail451": "Each gifted subscription will add to the amount of months a player has subscription benefits, allowing them to continue receiving more Mystic Hourglasses and increases to their Gem cap each passing month.",
@@ -162,19 +162,19 @@
"faqQuestion48": "Can I play Habitica with others?",
"webFaqAnswer48": "Yes, with Parties! You can start your own Party or join an existing one. Partying with other Habitica players is a great way to take on Quests, receive buffs from Party members skills, and boost your motivation with additional accountability.",
"faqQuestion49": "How do I find a Party when I'm not in one?",
"webFaqAnswer49": "If you want to experience Habitica with others but dont know other players, searching for a Party is your best option! If you already know other players that have a Party, you can share your @username with them to be invited. Alternatively, you can create a new Party and invite them with their @username or email address.\n\nTo create or search for a Party, select “Party” in the navigation menu, then choose the option that works for you.",
"webFaqAnswer49": "If you want to experience Habitica with others but dont know other players, searching for a Party is your best option! If you already know other players that have a Party, you can share your @username with them to be invited. Alternatively, you can create a new Party and invite them with their @username or e-mail address.\n\nTo create or search for a Party, select “Party” in the navigation menu, then choose the option that works for you.",
"faqQuestion50": "How does searching for a Party work?",
"webFaqAnswer50": "After selecting \"Look for a Party\", youll be added to a list of players that want to join a Party. Party leaders can view this list and send invitations. Once you receive an invitation, you can accept it from your notifications to join the Party of your choosing!\n\nYou may get multiple invitations to different Parties. However, you can only be a member of one Party at a time.",
"webFaqAnswer50": "After selecting Look for a Party, youll be added to a list of players that want to join a Party. Party leaders can view this list and send invitations. Once you receive an invitation, you can accept it from your notifications to join the Party of your choosing!\n\nYou may get multiple invitations to different Parties. However, you can only be a member of one Party at a time.",
"faqQuestion51": "How long can I search for a Party after joining the list?",
"webFaqAnswer51": "You will remain in the list until you accept an invite to a Party or dont login for 7 days, whichever comes first. If you log in after being inactive for 7 days, well automatically add you back to the list as long as you don't have a pending invite.",
"faqQuestion52": "Can I stop searching for a Party?",
"webFaqAnswer52": "If you no longer want to find a Party, you can stop searching at any time.\n\nTo stop searching for a Party on the mobile apps:\n * From the menu, select \"Party\" and tap \"Leave\" at the bottom of the screen.\n\nTo stop searching for a Party on Habiticas website:\n * Select \"Party\" from the navigation and click \"Leave\" in the pop-up.",
"webFaqAnswer52": "If you no longer want to find a Party, you can stop searching at any time.\n\nTo stop searching for a Party on the mobile apps:\n * From the menu, select Party and tap Leave at the bottom of the screen.\n\nTo stop searching for a Party on Habiticas website:\n * Select Party from the navigation and click Leave in the pop-up.",
"faqQuestion53": "I have a Party, how do I find more members?",
"webFaqAnswer53": "If you are using Habiticas website, select \"Find Members\" from the Party dropdown. If youre using the Android app, tap \"Find Members\" above your Partys member list. This will display a list of players that are actively looking for a Party and can be invited to join.\n\nTo help find a good fit for your Party, you'll see some information, such as language, class, level, and how many days they have used Habitica. If youd like to chat with someone before sending an invite, you can view their Profile and send a message.",
"webFaqAnswer53": "If you are using Habiticas website, select Find Members from the Party dropdown. If youre using the Android app, tap Find Members above your Partys member list. This will display a list of players that are actively looking for a Party and can be invited to join.\n\nTo help find a good fit for your Party, youll see some information, such as language, class, level, and how many days they have used Habitica. If youd like to chat with someone before sending an invite, you can view their Profile and send a message.",
"faqQuestion54": "How many members can I invite to my Party?",
"webFaqAnswer54": "Parties have a maximum limit of 30 members and a minimum of 1 member. Pending invites count towards the member count. For example, 29 members and 1 pending invite would count as 30 members. To clear a pending invite, the invited player must accept or decline, or the Party leader must cancel the invite.",
"faqQuestion55": "Can I invite someone I already know?",
"webFaqAnswer55": "Yes! If you already have a Habitica players username or email address, you can invite them to join your Party. Heres how to send an invite on the different platforms:\n\nTo invite someone on the mobile apps:\n 1. From the menu, select \"Party\" and scroll down to the Members section\n 2. Tap \"Find Members\" then switch to the \"By Invite\" tab\n 3. Enter the usernames or email addresses of the players you want to invite and click \"Send Invite\"\n\nTo invite someone on the website:\n 1. Navigate to your Party and click \"Invite to Party\"\n 2. Enter the usernames or email addresses of the players you want to invite and click \"Send Invites\"",
"webFaqAnswer55": "Yes! If you already have a Habitica players username or e-mail address, you can invite them to join your Party. Heres how to send an invite on the different platforms:\n\nTo invite someone on the mobile apps:\n 1. From the menu, select Party and scroll down to the Members section\n 2. Tap Find Members then switch to the By Invite tab\n 3. Enter the usernames or e-mail addresses of the players you want to invite and click Send Invite\n\nTo invite someone on the website:\n 1. Navigate to your Party and click Invite to Party\n 2. Enter the usernames or e-mail addresses of the players you want to invite and click Send Invites",
"faqQuestion56": "How do I cancel a pending invitation to my Party?",
"webFaqAnswer56": "To cancel a pending invitation on the mobile apps:\n 1. When viewing your Party, scroll down to the bottom of your Member list\n 2. Find the player whose invite you wish to cancel and tap the “Cancel invitation” button.\n\nTo cancel a pending invitation on the website:\n1. Navigate to your Partys member list and switch to the “Invites” tab\n 2. Hover the player whose invite you wish to cancel\n 3. Click the three dots and choose “Cancel Invite”",
"faqQuestion57": "How do I stop unwanted invitations?",
@@ -186,11 +186,11 @@
"webFaqAnswer59": "Habitica Group Plans provide a shared experience by allowing members to easily add, assign, and complete tasks from a shared task board. With features like member roles, status view, and task assigning, Group Plans are great for families or teams of colleagues that have shared goals. Theyre also a great way to keep each other motivated on your journey to fight monsters and improve your life.",
"faqQuestion60": "How do I get started with my Group Plan?",
"faqQuestion61": "Can other Group Plan members create tasks?",
"webFaqAnswer61": "Only the Group Plan leader and managers can create shared tasks. If youd like a member to be able to create tasks, then you should promote them to manager.\n\nTo promote a Group Plan member to a manager on the website:\n 1. Navigate to your Group Plan then switch to the \"Group Information\" tab\n 2. View your Member List and click on the dot icon by the member you want to promote\n 3. Select \"Assign Manager\"",
"webFaqAnswer61": "Only the Group Plan leader and managers can create shared tasks. If youd like a member to be able to create tasks, then you should promote them to manager.\n\nTo promote a Group Plan member to a manager on the website:\n 1. Navigate to your Group Plan then switch to the Group Information tab\n 2. View your Member List and click on the dot icon by the member you want to promote\n 3. Select Assign Manager",
"faqQuestion62": "How do assigned tasks work?",
"webFaqAnswer62": "Group Plans give you the unique ability to assign shared tasks to other members of your Group Plan. When a shared task is assigned to a member, other members are prevented from completing it.\n\nYou can also assign a task to multiple members. For example, if everyone has to brush their teeth, create a task and assign it to each member. Each member will be able to complete the task and earn their individual rewards. The main task will show as complete once everyone has completed it.",
"faqQuestion63": "How do unassigned tasks work?",
"webFaqAnswer63": "Unassigned tasks can be completed by any member. For example, taking out the trash. Whoever takes out the trash can complete the unassigned task and it will show as completed for everyone.",
"webFaqAnswer63": "Unassigned tasks can be completed by any member. For example, taking out the rubbish. Whoever takes out the rubbish can complete the unassigned task and it will show as completed for everyone.",
"faqQuestion64": "How does the synchronised day reset work?",
"webFaqAnswer64": "Shared tasks will reset at the same time for everyone to keep the shared task board in sync. This time is visible on the shared task board and is determined by the Group Plan leaders day start time. Because shared tasks reset automatically, you will not get a chance to complete yesterdays uncompleted shared Dailies when you check in the next morning.\n\nShared Dailies will not do damage if they are missed, however they will degrade in colour to help visualise progress.",
"webFaqAnswer65": "While the mobile apps dont fully support all Group Plans functionality yet, you can still complete shared tasks from the iOS and Android apps!\n\nOn Android, you can tap your Display Name at the top of the screen when viewing your tasks to switch to your shared task board. From there you can view members, access your chat, and create, complete, or assign tasks.\n\nYou can also switch on a preference to copy shared tasks to your personal task board so you can complete all your tasks from one place.\n\nTo do this on the mobile apps:\n * Open Settings and switch on “Copy shared tasks”\n\nTo do this on Habiticas website:\n * Navigate to your Group Plan and switch on the “Copy tasks” toggle on the shared task board",
@@ -235,7 +235,7 @@
"sunsetFaqList7": "Currently many Challenges have tasks that require posts in Habiticas public chat spaces. Creators of those Challenges can adapt their tasks or move the chat requirement to posting on an outside service.",
"sunsetFaqList8": "Our existing <a href='https://habitica.com/static/faq'>FAQ</a> is a great resource and can be found from the Help menu, or Support on mobile. We are in the process of creating a more comprehensive and improved FAQ to help guide players moving forward.",
"sunsetFaqList9": "This <a href='https://habitica.wordpress.com/beginning-adventurers-guide/'>blog post</a> also provides a handy guide for new players.",
"sunsetFaqList10": "Players are also encouraged to email <a href='mailto:admin@habitica.com'>admin@habitica.com</a> with any questions for which they cannot find answers in the above links.",
"sunsetFaqList10": "Players are also encouraged to e-mail <a href='mailto:admin@habitica.com'>admin@habitica.com</a> with any questions for which they cannot find answers in the above links.",
"sunsetFaqPara20": "Habiticas Community Guidelines will be updated at the time Tavern and Guild service is discontinued. They will reflect that community rules for conduct are now in relation to player profiles, Challenges, and messages in private spaces. Our Terms of Service have always applied to both public and private spaces and do not require an immediate update in regard to this change.",
"sunsetFaqHeader12": "What will happen to Guild Bank Gems?",
"sunsetFaqPara21": "Gems in the Guild Bank will be refunded to the leader of the Guild on August 8th when Guild Services end.",
@@ -243,5 +243,13 @@
"contactAdmin": "Contact <a href='mailto:admin@habitica.com'>admin@habitica.com</a>",
"webFaqAnswer60": "Here are some quick tips to get you started with your new Habitica Group Plan:\n\n * Promote a member to a manager to give them the ability to create and edit tasks\n * Leave tasks unassigned if anyone can complete it, and it only needs to be done once\n * Assign a task to one person to make sure no one else can complete their task\n * Assign a task to multiple people if they all need to complete it\n * Toggle the ability to display shared tasks on your personal board to not miss anything\n * You get rewarded for the tasks you complete, even multi-assigned\n * Task completion rewards arent split between members\n * Use task colour on the team board to judge the average completion rate of tasks\n * Regularly review the tasks on the shared task board to make sure they are still relevant\n * Missing a Daily wont damage you or your team, but the task will degrade in colour",
"webFaqAnswer67": "Classes are different roles that your character can play. Each class provides its own set of unique benefits and skills as you level up. These skills can complement the way you interact with your tasks or help you contribute to completing Quests in your Party.\n\nYour class also determines the Equipment that will be available to you for purchase in your Rewards, the Market, and the Seasonal Shop.\n\nHeres a rundown of each class to help you choose which one is best suited to your playstyle:\n#### **Warrior**\n* Warriors deal high damage to bosses and have a high chance of critical hits when completing tasks, rewarding you extra Experience and Gold.\n* Strength is their primary stat, raising the damage they do.\n* Constitution is their secondary stat, lowering the damage they take.\n* Warriors' skills buff their Party mates' Constitution and Strength.\n* Consider playing as a Warrior if you love to fight bosses but also want some protection if you miss tasks occasionally.\n#### **Healer**\n* Healers have high defence and can heal themselves as well as their Party mates.\n* Constitution is their primary stat, increasing their heals and lowering the damage they take.\n* Intelligence is their secondary stat, increasing their Mana and Experience.\n* Healers' skills make their tasks less red and buff their Party mates' Constitution.\n* Consider playing as a Healer if you miss tasks often and need the ability to heal yourself or your Party members. Healers also level up quickly.\n#### **Mage**\n* Mages level up quickly, gain lots of Mana, and damage bosses in Quests.\n* Intelligence is their primary stat, increasing their Mana and Experience.\n* Perception is their secondary stat, increasing their Gold and item drops.\n* Mages' skills freeze their task streaks, restore their Party mates' Mana, and buff their Intelligence.\n* Consider playing as a Mage if you are motivated by progressing quickly through levels and contributing damage to boss Quests.\n#### **Rogue**\n* Rogues get the most item drops and Gold from completing tasks, and have a high chance of critical hits, getting even more Experience and Gold.\n* Perception is their primary stat, increasing their Gold and item drops.\n* Strength is their secondary stat, raising the damage they do.\n* Rogues' skills help them dodge missed Dailies, pilfer Gold, and buff their Party mates Perception.\n* Consider playing as a Rogue if youre highly motivated by rewards.",
"sunsetFaqPara2": "Habiticas primary purpose is and has always been to provide a gamified task management experience. Taverns and Guilds helped motivate players by helping them find others with similar goals. Some truly wonderful spaces were created and we had a chance to see the community thrive with helpful discussion. As the years passed, we noticed changes in how players use and rely on Habitica. Parties flourished, while Guilds and public spaces were used by less and less of our player base. In an ever changing internet landscape, the resources necessary to maintain these spaces became too disproportionate to the number of people actually participating in them."
"sunsetFaqPara2": "Habiticas primary purpose is and has always been to provide a gamified task management experience. Taverns and Guilds helped motivate players by helping them find others with similar goals. Some truly wonderful spaces were created and we had a chance to see the community thrive with helpful discussion. As the years passed, we noticed changes in how players use and rely on Habitica. Parties flourished, while Guilds and public spaces were used by less and less of our player base. In an ever changing internet landscape, the resources necessary to maintain these spaces became too disproportionate to the number of people actually participating in them.",
"faqQuestion68": "How do I prevent losing HP?",
"faqQuestion69": "What are character stats?",
"webFaqAnswer68": "If you find yourself losing HP often, try some of these tips:\n\n- Pause your Dailies. The “Pause Damage” button in Settings will prevent you from losing HP for missed Dailies.\n- Adjust the schedule of your Dailies. By setting them to never be due, you can still complete them for rewards without risking HP loss.\n- Try using class skills:\n\t- Rogues can cast Stealth to prevent damage from missed Dailies\n\t- Warriors can cast Brutal Smash to reduce a Dailys redness, lowering damage taken if missed\n\t- Healers can cast Searing Brightness to reduce Dailies redness, lowering damage taken if missed",
"faqQuestion70": "What are stat points?",
"webFaqAnswer70": "Stat points let you increase your character's core stats. You earn one stat point each time you level up (up to level 100), which you can assign manually or automatically using the Automatic Allocation feature. Stat allocation unlocks with the Class System at level 10.",
"faqQuestion71": "How does Automatic Allocation work?",
"webFaqAnswer69": "All players have four character stats that provide different benefits:\n\n* Strength—Increases critical hit chance and damage when scoring tasks. Also increases damage dealt to Quest bosses.\n* Intelligence—Increases Experience earned from tasks. Also increases your Mana cap and Mana regeneration rate.\n* Constitution—Reduces damage taken from missed Dailies and negative Habits. Does not reduce damage from Quest bosses.\n* Perception—Increases item drop chance, daily item drop cap, task streak bonuses, and Gold earned when completing tasks.\n\nStats can be increased through stat point allocation, Equipment, class skills, and levelling up. You also gain one bonus point to all stats every two levels, up to level 100.",
"webFaqAnswer71": "The Automatic Allocation feature automatically assigns stat points according to one of the following distribution methods:\n\n* Distribute evenly—Assigns the same number of points to each attribute\n* Distribute based on class—Assigns more points to the attributes important to your class\n* Distribute based on task activity—Assigns points based on Strength, Intelligence, Constitution, and Perception categories associated with the tasks you complete\n\nIf you choose not to use Automatic Allocation, you can manually assign your stat points from the Stats section."
}
+26 -23
View File
@@ -9,8 +9,8 @@
"companyContribute": "Contributing to Habitica",
"companyDonate": "Donate to Habitica",
"forgotPassword": "Forgot Password?",
"emailNewPass": "Email a Password Reset Link",
"forgotPasswordSteps": "Enter your username or the email address you used to register your Habitica account.",
"emailNewPass": "E-mail a Password Reset Link",
"forgotPasswordSteps": "Enter your username or the e-mail address you used to register your Habitica account.",
"sendLink": "Send Link",
"featuredIn": "Featured in",
"footerDevs": "Developers",
@@ -21,16 +21,16 @@
"free": "Join for free",
"guidanceForBlacksmiths": "Guidance for Blacksmiths",
"history": "History",
"invalidEmail": "A valid email address is required in order to perform a password reset.",
"invalidEmail": "A valid e-mail address is required in order to perform a password reset.",
"login": "Log In",
"logout": "Log Out",
"marketing1Header": "Build better habits one level at a time!",
"marketing1Lead1Title": "Gamify your life",
"marketing1Lead1": "Habitica is the perfect app for anyone who struggles with to-do lists. We use familiar game mechanics like rewarding you with Gold, Experience, and items to help you feel productive and boost your sense of accomplishment when you complete tasks. The better you are at your tasks, the more you progress in the game.",
"marketing1Lead1": "Habitica is the perfect app for anyone who struggles with to-do lists. We use familiar game mechanisms like rewarding you with Gold, Experience, and items to help you feel productive and boost your sense of accomplishment when you complete tasks. The better you are at your tasks, the more you progress in the game.",
"marketing1Lead2Title": "Gear up in style",
"marketing1Lead2": "Collect swords, armour, and much more with the Gold you earn from completing tasks. With hundreds of pieces to collect and choose from, you'll never run out of combinations to try. Optimise for stats, style, or both! ",
"marketing1Lead3Title": "Get rewarded for your effort",
"marketing1Lead3": "Having something to look forward to can be the difference between getting a task done, or having it taunt you for weeks. When life doesn't offer a reward, Habitica has you covered! Youll be rewarded for every task, but surprises are around every cornerso keep up your progress! ",
"marketing1Lead3": "Having something to look forward to can be the difference between getting a task done, or having it taunt you for weeks. When life doesnt offer a reward, Habitica has you covered! Youll be rewarded for every task, but surprises are around every cornerso keep up your progress! ",
"marketing2Header": "Team up with friends",
"marketing2Lead1Title": "Social productivity",
"marketing2Lead1": "Get a boost of motivation by collaborating, competing, and interacting with others! Habitica is built to harness the most effective part of any self-improvement program: social accountability.",
@@ -74,7 +74,7 @@
"pkAnswer7": "Habitica uses pixel art for several reasons. In addition to the fun nostalgia factor, pixel art is very approachable to our volunteer artists who want to chip in. It's much easier to keep our pixel art consistent even when lots of different artists contribute, and it lets us quickly generate a ton of new content!",
"pkQuestion8": "How has Habitica affected people's real lives?",
"pkAnswer8": "You can find lots of testimonials for how Habitica has helped people here: https://habitversary.tumblr.com",
"pkMoreQuestions": "Do you have a question thats not on this list? Send an email to admin@habitica.com!",
"pkMoreQuestions": "Do you have a question thats not on this list? Send an e-mail to admin@habitica.com!",
"pkPromo": "Promos",
"pkLogo": "Logos",
"pkBoss": "Bosses",
@@ -93,7 +93,7 @@
"localStorageClear": "Clear Data",
"localStorageClearExplanation": "This button will clear local storage and most cookies, and log you out.",
"username": "Username",
"emailOrUsername": "Username or Email (case-sensitive)",
"emailOrUsername": "Username or E-mail (case-sensitive)",
"work": "Work",
"reportAccountProblems": "Report Account Problems",
"reportCommunityIssues": "Report Community Issues",
@@ -104,35 +104,35 @@
"tweet": "Tweet",
"checkOutMobileApps": "Check out our mobile apps!",
"missingAuthHeaders": "Missing authentication headers.",
"missingUsernameEmail": "Missing username or email.",
"missingEmail": "Missing email.",
"missingUsernameEmail": "Missing username or e-mail.",
"missingEmail": "Missing e-mail.",
"missingUsername": "Missing username.",
"missingPassword": "Missing password.",
"missingNewPassword": "Missing new password.",
"invalidEmailDomain": "You cannot register with emails with the following domains: <%= domains %>",
"wrongPassword": "Password is incorrect. If you forgot your password, click \"Forgot Password.\"",
"invalidEmailDomain": "You cannot register with e-mails with the following domains: <%= domains %>",
"wrongPassword": "Password is incorrect. If you forgot your password, click Forgot Password.",
"incorrectDeletePhrase": "Please type <%= magicWord %> in all capital letters to delete your account.",
"notAnEmail": "Invalid email address.",
"emailTaken": "Email address is already used in an account.",
"newEmailRequired": "Missing new email address.",
"notAnEmail": "Invalid e-mail address.",
"emailTaken": "E-mail address is already used in an account.",
"newEmailRequired": "Missing new e-mail address.",
"usernameTime": "It's time to set your username!",
"usernameInfo": "Login names are now unique usernames that will be visible beside your display name and used for invitations, chat @mentions, and messaging.<br><br>If you'd like to learn more about this change, <a href='https://habitica.fandom.com/wiki/Player_Names' target='_blank'>visit our wiki</a>.",
"usernameTOSRequirements": "Usernames must conform to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>. If you didnt previously set a login name, your username was auto-generated.",
"usernameTaken": "Username already taken.",
"passwordConfirmationMatch": "Password confirmation doesn't match password.",
"passwordResetPage": "Reset Password",
"passwordReset": "If we have your email or username on file, instructions for setting a new password have been sent to your email.",
"invalidLoginCredentialsLong": "Your email, username, or password are incorrect. Please try again or use \"Forgot Password\".",
"passwordReset": "If we have your e-mail or username on file, instructions for setting a new password have been sent to your e-mail.",
"invalidLoginCredentialsLong": "Your e-mail, username, or password are incorrect. Please try again or use Forgot Password.",
"invalidCredentials": "There is no account that uses those credentials.",
"accountSuspended": "Your account @<%= username %> has been blocked. For additional information, or to request an appeal, email admin@habitica.com with your Habitica username or User ID.",
"accountSuspended": "Your account @<%= username %> has been blocked. For additional information, or to request an appeal, e-mail admin@habitica.com with your Habitica username or User ID.",
"accountSuspendedTitle": "Account has been suspended",
"unsupportedNetwork": "This network is not currently supported.",
"cantDetachSocial": "Account lacks another authentication method; can't detach this authentication method.",
"onlySocialAttachLocal": "Local authentication can be added to only a social account.",
"invalidReqParams": "Invalid request parameters.",
"memberIdRequired": "\"member\" must be a valid UUID.",
"heroIdRequired": "\"heroId\" must be a valid UUID.",
"cannotFulfillReq": "This email address is already in use. You can try logging in or use a different email to register. If you need help, reach out to admin@habitica.com.",
"memberIdRequired": "member must be a valid UUID.",
"heroIdRequired": "heroId must be a valid UUID.",
"cannotFulfillReq": "This e-mail address is already in use. You can try logging in or use a different e-mail to register. If you need help, reach out to admin@habitica.com.",
"modelNotFound": "This model does not exist.",
"signUpWithSocial": "Continue with <%= social %>",
"loginWithSocial": "Log in with <%= social %>",
@@ -163,7 +163,7 @@
"schoolAndWork": "School and Work",
"schoolAndWorkDesc": "Whether you're preparing a report for your teacher or your boss, it's easy to keep track of your progress as you tackle your toughest tasks.",
"muchmuchMore": "And much, much more!",
"muchmuchMoreDesc": "Our fully customisable task list means that you can shape Habitica to fit your personal goals. Work on creative projects, emphasise self-care, or pursue a different dream -- it's all up to you.",
"muchmuchMoreDesc": "Our fully customisable task list means that you can shape Habitica to fit your personal goals. Work on creative projects, emphasise self-care, or pursue a different dreamits all up to you.",
"levelUpAnywhere": "Level Up Anywhere",
"levelUpAnywhereDesc": "Our mobile apps make it simple to keep track of your tasks on-the-go. Accomplish your goals with a single tap, no matter where you are.",
"joinMany": "Join over <%= userCountInMillions %> million people having fun while accomplishing their goals!",
@@ -185,6 +185,9 @@
"missingClientHeader": "Missing x-client headers.",
"emailBlockedRegistration": "This E-Mail is blocked from registration. If you think this is a mistake, please contact us at admin@habitica.com.",
"minPasswordLengthLogin": "Your password is at least 8 characters long.",
"enterValidEmail": "Please enter a valid email address.",
"whatToCallYou": "What should we call you?"
"enterValidEmail": "Please enter a valid e-mail address.",
"whatToCallYou": "What should we call you?",
"acceptPrivacyTOS": "You confirm that you are at least 18 years old, and that you have read and agree to our <a href='/static/terms' target='_blank'>Terms of Service</a> and <a href='/static/privacy' target='_blank'>Privacy Policy</a>",
"emailAddress": "E-mail address",
"emailRequiredForSupport": "We require an e-mail address for user support. Please enter an e-mail address to continue creating your account."
}
File diff suppressed because it is too large Load Diff
+12 -10
View File
@@ -5,14 +5,14 @@
"onward": "Onward!",
"done": "Done",
"gotIt": "Got it!",
"titleTimeTravelers": "Time Travellers",
"titleTimeTravelers": "Time Travellers",
"titleSeasonalShop": "Seasonal Shop",
"saveEdits": "Save Edits",
"showMore": "Show More",
"showLess": "Show Less",
"markdownHelpLink": "Markdown formatting help",
"bold": "**Bold**",
"markdownImageEx": "![mandatory alt text](https://habitica.com/cake.png \"optional mouseover title\")",
"markdownImageEx": "![mandatory alt text](https://habitica.com/cake.png optional mouseover title)",
"code": "`code`",
"achievements": "Achievements",
"basicAchievs": "Basic Achievements",
@@ -63,7 +63,7 @@
"costumeContest": "Costume Contestant",
"costumeContestText": "Participated in the Habitoween Costume Contest. See some of the awesome entries at blog.habitrpg.com!",
"costumeContestTextPlural": "Participated in <%= count %> Habitoween Costume Contests. See some of the awesome entries at blog.habitrpg.com!",
"newPassSent": "If we have your email on file, instructions for setting a new password have been sent to your email.",
"newPassSent": "If we have your e-mail on file, instructions for setting a new password have been sent to your e-mail.",
"error": "Error",
"menu": "Menu",
"notifications": "Notifications",
@@ -85,7 +85,7 @@
"audioTheme_lunasolTheme": "Lunasol Theme",
"audioTheme_spacePenguinTheme": "SpacePenguin's Theme",
"audioTheme_maflTheme": "MAFL Theme",
"audioTheme_pizildenTheme": "Pizilden's Theme",
"audioTheme_pizildenTheme": "Pizildens Theme",
"audioTheme_farvoidTheme": "Farvoid Theme",
"reportBug": "Report a Bug",
"overview": "Overview for New Users",
@@ -93,10 +93,10 @@
"achievementStressbeast": "Saviour of Stoïkalm",
"achievementStressbeastText": "Helped defeat the Abominable Stressbeast during the 2014 Winter Wonderland Event!",
"achievementBurnout": "Saviour of the Flourishing Fields",
"achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Fall Festival Event!",
"achievementBurnoutText": "Helped defeat Burnout and restore the Exhaust Spirits during the 2015 Autumn Festival Event!",
"achievementBewilder": "Saviour of Mistiflying",
"achievementBewilderText": "Helped defeat the Be-Wilder during the 2016 Spring Fling Event!",
"achievementDysheartener": "Savior of the Shattered",
"achievementDysheartener": "Saviour of the Shattered",
"achievementDysheartenerText": "Helped defeat the Dysheartener during the 2018 Valentine's Event!",
"cards": "Cards",
"sentCardToUser": "You sent a card to <%= profileName %>",
@@ -161,7 +161,7 @@
"wonChallengeShare": "I won a challenge in Habitica!",
"orderBy": "Order By <%= item %>",
"you": "(you)",
"loading": "Loading...",
"loading": "Loading",
"userIdRequired": "User ID is required",
"resetFilters": "Clear all filters",
"applyFilters": "Apply Filters",
@@ -204,7 +204,7 @@
"leaveHabitica": "You are about to leave Habitica.com",
"leaveHabiticaText": "Habitica is not responsible for the content of any linked website that is not owned or operated by HabitRPG.<br>Please note that these websites' practices may differ from Habiticas community guidelines.",
"allNotifications": "All Notifications",
"reportEmailPlaceholder": "Your email address",
"reportEmailPlaceholder": "Your e-mail address",
"reportDescriptionText": "Include screenshots or Javascript console errors if helpful.",
"reportSent": "Thank you for your submission!",
"reportSentDescription": "Well get back to you once our team has a chance to review.",
@@ -219,7 +219,7 @@
"refreshList": "Refresh List",
"skipExternalLinkModal": "Hold CTRL (Windows) or Command (Mac) when clicking a link to skip this modal.",
"general": "General",
"reportEmailError": "Please provide a valid email address",
"reportEmailError": "Please provide a valid e-mail address",
"reportDescription": "Description",
"reportDescriptionPlaceholder": "Describe the bug in detail here",
"submitBugReport": "Submit Bug Report",
@@ -243,5 +243,7 @@
"targetUserNotExist": "Target User: '<%= userName %>' does not exist.",
"rememberToBeKind": "Please remember to be kind, respectful, and follow the <a href='/static/community-guidelines' target='_blank'>Community Guidelines</a>.",
"gem": "Gem",
"confirmPurchase": "Confirm Purchase"
"confirmPurchase": "Confirm Purchase",
"avoidSPI": "Avoid SPI",
"avoidSPIDetails": "For your privacy, avoid including <%= firstLink %>sensitive personal information<%= linkClose %> (SPI) when using Habitica. Your account data, including tasks, is stored on our servers so you can access it from any device.<br><br>To learn more, review our <%= secondLink %>Privacy Policy<%= linkClose %>."
}
+29 -17
View File
@@ -98,14 +98,14 @@
"leaderOnlyChallenges": "Only the group leader can create challenges",
"sendGift": "Send Gift",
"inviteFriends": "Invite Friends",
"inviteByEmail": "Invite by Email",
"inviteMembersHowTo": "Invite people via a valid email or 36-digit User ID. If an email isn't registered yet, we'll invite them to join Habitica.",
"inviteByEmail": "Invite by E-mail",
"inviteMembersHowTo": "Invite people via a valid e-mail or 36-digit User ID. If an e-mail isn't registered yet, we'll invite them to join Habitica.",
"sendInvitations": "Send Invites",
"invitationsSent": "Invitations sent!",
"invitationSent": "Invitation sent!",
"invitedFriend": "Invited a Friend",
"invitedFriendText": "This user invited a friend (or friends) who joined them on their adventure!",
"inviteLimitReached": "You have already sent the maximum number of email invitations. We have a limit to prevent spamming, however if you would like more, please contact us at <%= techAssistanceEmail %> and we'll be happy to discuss it!",
"inviteLimitReached": "You have already sent the maximum number of e-mail invitations. We have a limit to prevent spamming. However, if you would like more, please contact us at <%= techAssistanceEmail %> and we'll be happy to discuss it!",
"sendGiftHeading": "Send Gift to <%= name %>",
"sendGiftGemsBalance": "From <%= number %> Gems",
"sendGiftCost": "Total: $<%= cost %> USD",
@@ -121,7 +121,7 @@
"partyUpText": "Joined a Party with another person! Have fun battling monsters and supporting each other.",
"partyOnText": "Joined a Party with at least four people! Enjoy your increased accountability as you unite with your friends to vanquish your foes!",
"groupNotFound": "Group not found or you don't have access.",
"groupTypesRequired": "You must supply a valid \"type\" query string.",
"groupTypesRequired": "You must supply a valid type query string.",
"questLeaderCannotLeaveGroup": "You cannot leave your Party when you have started a quest. Abort the quest first.",
"cannotLeaveWhileActiveQuest": "You cannot leave Party during an active quest. Please leave the quest first.",
"onlyLeaderCanRemoveMember": "Only the group leader can remove a member!",
@@ -129,8 +129,8 @@
"memberCannotRemoveYourself": "You cannot remove yourself!",
"groupMemberNotFound": "User not found among group's members",
"mustBeGroupMember": "Must be member of the group.",
"canOnlyInviteEmailUuid": "Can only invite using User IDs, emails, or usernames.",
"inviteMissingEmail": "Missing email address in invite.",
"canOnlyInviteEmailUuid": "Can only invite using User IDs, e-mails, or usernames.",
"inviteMissingEmail": "Missing e-mail address in invite.",
"inviteMustNotBeEmpty": "Invite must not be empty.",
"partyMustbePrivate": "Parties must be private",
"userAlreadyInGroup": "UserID: <%= userId %>, User \"<%= username %>\" already in that group.",
@@ -141,9 +141,9 @@
"userAlreadyInAParty": "UserID: <%= userId %>, User \"<%= username %>\" already in a party.",
"userWithIDNotFound": "User with ID \"<%= userId %>\" not found.",
"userWithUsernameNotFound": "User with username \"<%= username %>\" not found.",
"userHasNoLocalRegistration": "User does not have a local registration (username, email, password).",
"userHasNoLocalRegistration": "User does not have a local registration (username, e-mail, password).",
"uuidsMustBeAnArray": "User ID invites must be an array.",
"emailsMustBeAnArray": "Email address invites must be an array.",
"emailsMustBeAnArray": "E-mail address invites must be an array.",
"usernamesMustBeAnArray": "Username invites must be an array.",
"canOnlyInviteMaxInvites": "You can only invite \"<%= maxInvites %>\" at a time",
"partyExceedsMembersLimit": "Party size is limited to <%= maxMembersParty %> members",
@@ -161,7 +161,7 @@
"userRequestsApproval": "<strong><%= userName %></strong> requests approval",
"userCountRequestsApproval": "<strong><%= userCount %> members</strong> request approval",
"youAreRequestingApproval": "You are requesting approval",
"chatPrivilegesRevoked": "You cannot do this because your chat privileges have been removed. For details or to ask if your privileges can be returned, please email our Community Manager at admin@habitica.com or ask your parent or guardian to email them. Please include your @Username in the email. If a moderator has already told you that your chat ban is temporary, you do not need to send an email.",
"chatPrivilegesRevoked": "You cannot do this because your chat privileges have been removed. For details or to ask if your privileges can be returned, please e-mail our Community Manager at admin@habitica.com or ask your parent or guardian to e-mail them. Please include your @Username in the e-mail. If a moderator has already told you that your chat ban is temporary, you do not need to send an e-mail.",
"to": "To:",
"from": "From:",
"assignTask": "Assign Task",
@@ -215,9 +215,9 @@
"liked": "Liked",
"inviteToGuild": "Invite to Group",
"inviteToParty": "Invite to Party",
"inviteEmailUsername": "Invite via Email or Username",
"inviteEmailUsernameInfo": "Invite users via a valid email or username. If an email isn't registered yet, we'll invite them to join.",
"emailOrUsernameInvite": "Email address or username",
"inviteEmailUsername": "Invite via E-mail or Username",
"inviteEmailUsernameInfo": "Invite users via a valid e-mail or username. If an e-mail isn't registered yet, we'll invite them to join.",
"emailOrUsernameInvite": "E-mail address or username",
"messageGuildLeader": "Message Group Leader",
"donateGems": "Donate Gems",
"updateGuild": "Update Group",
@@ -288,7 +288,7 @@
"worldBossBullet4": "Check the Tavern regularly to see World Boss progress and Rage attacks",
"worldBoss": "World Boss",
"groupPlanTitle": "Need More for Your Crew?",
"groupPlanDesc": "Organizing the household chores or managing a small class project? Habiticas Group Plans provide a shared task experience and dedicated chat space to help you and your group stay motivated.",
"groupPlanDesc": "Organising the household chores or managing a small class project? Habiticas Group Plans provide a shared task experience and dedicated chat space to help you and your group stay motivated.",
"billedMonthly": "*billed as a monthly subscription",
"teamBasedTasksList": "Shared Task Board",
"teamBasedTasksListDesc": "Group members can all work from the same task board to ensure the group is staying on top of things. Complete tasks from the shared task board or copy them to your personal tasks to complete them on the go.",
@@ -316,9 +316,9 @@
"whatIsGroupManagerDesc": "A Group Manager is a user role that do not have access to the group's billing details, but can create, assign, and approve shared Tasks for the Group's members. Promote Group Managers from the Groups member list.",
"goToTaskBoard": "Go to Task Board",
"sharedCompletion": "Completion Condition",
"recurringCompletion": "None - Group task does not complete",
"singleCompletion": "Single - Completes when any assigned user finishes",
"allAssignedCompletion": "All - Completes when all assigned users finish",
"recurringCompletion": "NoneGroup task does not complete",
"singleCompletion": "SingleCompletes when any assigned user finishes",
"allAssignedCompletion": "AllCompletes when all assigned users finish",
"pmReported": "Thank you for reporting this message.",
"groupActivityNotificationTitle": "<%= user %> posted in <%= group %>",
"suggestedGroup": "Suggested because youre new to Habitica.",
@@ -428,5 +428,17 @@
"groupManager": "Using for work",
"groupTeacher": "Using for education",
"groupPlanBillingFYI": "Group Plan subscriptions will automatically renew unless you cancel at least 24 hours before the end of your current period. You can cancel from the Group Billing tab of your Group Plan. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you will see an additional pro-rated charge for their benefits in your next billing cycle.",
"groupPlanBillingFYIShort": "Group Plan subscriptions will automatically renew unless you cancel at least 24 hours before the end of your current period. You can cancel from the Group Billing tab of your Group Plan. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you will see an additional pro-rated charge for their benefits in your next billing cycle."
"groupPlanBillingFYIShort": "Group Plan subscriptions will automatically renew unless you cancel at least 24 hours before the end of your current period. You can cancel from the Group Billing tab of your Group Plan. You will be charged within 24 hours before your subscription renews, based on the number of members in your Group Plan at that time. If you add members between payment periods, you will see an additional pro-rated charge for their benefits in your next billing cycle.",
"chooseAnOption": "Choose an Option",
"upgradeExistingGroup": "Upgrade an Existing Group",
"createNewGroup": "Create a New Group",
"yourParty": "Your Party",
"perMember": "per member",
"additionalMembersProrated": "Additional members invited during the month will be added to the next invoice cycle's total as a pro-rated charge.",
"oneMember": "1 member",
"membersCount": "<%= count %> members",
"pendingCount": "(<%= count %> pending)",
"previouslyUpgradedGroup": "Previously upgraded Group",
"inviteOthersForAdditional": "Invite others to your Group for an additional",
"upgradeCancelsPendingInvites": "Upgrading your Party will cancel all pending invites"
}
+23 -15
View File
@@ -8,30 +8,30 @@
"aquaticFriends": "Aquatic Friends",
"aquaticFriendsText": "Got splashed <%= count %> times by party members.",
"valentineCard": "Valentine's Day Card",
"valentineCardExplanation": "For enduring such a saccharine poem, you both receive the \"Adoring Friends\" badge!",
"valentineCardExplanation": "For enduring such a saccharine poem, you both receive the Adoring Friends badge!",
"valentineCardNotes": "Send a Valentine's Day card to a party member.",
"valentine0": "\"Roses are red\n\nMy Dailies are blue\n\nI'm happy that I'm\n\nIn a Party with you!\"",
"valentine1": "\"Roses are red\n\nViolets are nice\n\nLet's get together\n\nAnd fight against Vice!\"",
"valentine2": "\"Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\n'Cause it cost ten Gold.\"",
"valentine3": "\"Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!\"",
"valentine0": "Roses are red\n\nMy Dailies are blue\n\nIm happy that Im\n\nIn a Party with you!",
"valentine1": "Roses are red\n\nViolets are nice\n\nLets get together\n\nAnd fight against Vice!",
"valentine2": "Roses are red\n\nThis poem style is old\n\nI hope that you like this\n\nCause it cost ten Gold.",
"valentine3": "Roses are red\n\nIce Drakes are blue\n\nNo treasure is better\n\nThan time spent with you!",
"valentineCardAchievementTitle": "Adoring Friends",
"valentineCardAchievementText": "Aww, you and your friend must really care about each other! Sent or received <%= count %> Valentine's Day cards.",
"polarBear": "Polar Bear",
"turkey": "Turkey",
"gildedTurkey": "Gilded Turkey",
"polarBearPup": "Polar Bear Cub",
"jackolantern": "Jack-O-Lantern",
"ghostJackolantern": "Ghost Jack-O-Lantern",
"glowJackolantern": "Glow-in-the-Dark Jack-O-Lantern",
"jackolantern": "Jack-o-Lantern",
"ghostJackolantern": "Ghost Jack-o-Lantern",
"glowJackolantern": "Glow-in-the-Dark Jack-o-Lantern",
"seasonalShop": "Seasonal Shop",
"seasonalShopClosedTitle": "<%= linkStart %>Leslie<%= linkEnd %>",
"seasonalShopTitle": "<%= linkStart %>Seasonal Sorceress<%= linkEnd %>",
"seasonalShopClosedText": "The Seasonal Shop is currently closed!! Its only open during Habiticas four Grand Galas.",
"seasonalShopSummerText": "Happy Summer Splash!! Would you like to buy some rare items? Be sure to get them before the Gala ends!",
"seasonalShopFallText": "Happy Fall Festival!! Would you like to buy some rare items? Be sure to get them before the Gala ends!",
"seasonalShopFallText": "Happy Autumn Festival!! Would you like to buy some rare items? Be sure to get them before the Gala ends!",
"seasonalShopWinterText": "Happy Winter Wonderland!! Would you like to buy some rare items? Be sure to get them before the Gala ends!",
"seasonalShopSpringText": "Happy Spring Fling!! Would you like to buy some rare items? Be sure to get them before the Gala ends!",
"seasonalShopFallTextBroken": "Oh.... Welcome to the Seasonal Shop... We're stocking autumn Seasonal Edition goodies, or something... Everything here will be available to purchase during the Fall Festival event each year, but we're only open until 31 October... I guess you should to stock up now, or you'll have to wait... and wait... and wait... <strong>*sigh*</strong>",
"seasonalShopFallTextBroken": "Oh Welcome to the Seasonal Shop Were stocking autumn Seasonal Edition goodies, or something Everything here will be available to purchase during the Autumn Festival event each year, but were only open until 31 October I guess you should stock up now, or youll have to wait and wait and wait <strong>*sigh*</strong>",
"seasonalShopBrokenText": "My pavilion!!!!!!! My decorations!!!! Oh, the Dysheartener's destroyed everything :( Please help defeat it in the Tavern so I can rebuild!",
"seasonalShopRebirth": "If you bought any of this equipment in the past but don't currently own it, you can repurchase it in the Rewards Column. Initially, you'll only be able to purchase the items for your current class (Warrior by default), but fear not, the other class-specific items will become available if you switch to that class.",
"candycaneSet": "Candy Cane (Mage)",
@@ -48,7 +48,7 @@
"cocoaSet": "Cocoa (Rogue)",
"toAndFromCard": "To: <%= toName %>, From: <%= fromName %>",
"nyeCard": "New Year's Card",
"nyeCardExplanation": "For celebrating the new year together, you both receive the \"Auld Acquaintance\" badge!",
"nyeCardExplanation": "For celebrating the new year together, you both receive the Auld Acquaintance badge!",
"nyeCardNotes": "Send a New Year's card to a party member.",
"seasonalItems": "Seasonal Items",
"nyeCardAchievementTitle": "Auld Acquaintance",
@@ -147,7 +147,7 @@
"fall2019RavenSet": "Raven (Warrior)",
"fall2019LichSet": "Lich (Healer)",
"fall2019CyclopsSet": "Cyclops (Mage)",
"fall2019OperaticSpecterSet": "Operatic Specter (Rogue)",
"fall2019OperaticSpecterSet": "Operatic Spectre (Rogue)",
"summer2019HammerheadRogueSet": "Hammerhead (Rogue)",
"summer2019ConchHealerSet": "Conch (Healer)",
"summer2019WaterLilyMageSet": "Water Lily (Mage)",
@@ -164,11 +164,11 @@
"fall2020TwoHeadedRogueSet": "Two-Headed (Rogue)",
"fall2020ThirdEyeMageSet": "Third Eye (Mage)",
"fall2020DeathsHeadMothHealerSet": "Death's Head Moth (Healer)",
"royalPurpleJackolantern": "Royal Purple Jack-O-Lantern",
"royalPurpleJackolantern": "Royal Purple Jack-o-Lantern",
"g1g1Limitations": "This is a limited time event that starts on <%= promoStartMonth %> <%= promoStartOrdinal %> at <%= promoStartTime %> and will end <%= promoEndMonth %> <%= promoEndOrdinal %> at <%= promoEndTime %>. This promotion only applies when you gift to another Habitican. If you or your gift recipient already have a subscription, the gifted subscription will add months of credit that will only be used after the current subscription is cancelled or expires.",
"limitations": "Limitations",
"howItWorks": "How it Works",
"g1g1Returning": "In honor of the season, were bringing back a very special promotion. Now when you gift a subscription, youll receive the same in return!",
"g1g1Returning": "In honour of the season, were bringing back a very special promotion. Now when you gift a subscription, youll receive the same in return!",
"g1g1Event": "Gift One, Get One event going on now!",
"g1g1": "Gift One, Get One",
"winter2021HollyIvyRogueSet": "Holly and Ivy (Rogue)",
@@ -282,5 +282,13 @@
"summer2025ScallopWarriorSet": "Scallop Set (Warrior)",
"summer2025SquidRogueSet": "Squid Set (Rogue)",
"summer2025SeaAngelHealerSet": "Sea Angel Set (Healer)",
"summer2025FairyWrasseMageSet": "Fairy Wrasse Set (Mage)"
"summer2025FairyWrasseMageSet": "Fairy Wrasse Set (Mage)",
"spring2026FrogWarriorSet": "Frog Set (Warrior)",
"spring2026BranchRogueSet": "Spring Branch Set (Rogue)",
"spring2026SnowdropHealerSet": "Snowdrop Set (Healer)",
"spring2026MaypoleMageSet": "Maypole Set (Mage)",
"winter2026RimeReaperWarriorSet": "Rime Reaper Set (Warrior)",
"winter2026SkiRogueSet": "Ski Set (Rogue)",
"winter2026PolarBearHealerSet": "Polar Bear Set (Healer)",
"winter2026MidwinterCandleMageSet": "Midwinter Candle Set (Mage)"
}
+2 -2
View File
@@ -26,7 +26,7 @@
"messageAlreadyOwnGear": "You already own this item. Equip it by going to the equipment page.",
"previousGearNotOwned": "You need to purchase a lower level gear before this one.",
"messageHealthAlreadyMax": "You already have maximum health.",
"messageHealthAlreadyMin": "Oh no! You have already run out of health so it's too late to buy a health potion, but don't worry - you can revive!",
"messageHealthAlreadyMin": "Oh no! You have already run out of health so its too late to buy a health potion, but dont worryyou can revive!",
"armoireEquipment": "<%= image %> You found a piece of rare Equipment in the Armoire: <%= dropText %>! Awesome!",
"armoireFood": "<%= image %> You rummage in the Armoire and find <%= dropText %>. What's that doing in here?",
"armoireExp": "You wrestle with the Armoire and gain Experience. Take that!",
@@ -39,7 +39,7 @@
"messageGroupChatFlagAlreadyReported": "You have already reported this message",
"messageGroupChatNotFound": "Message not found!",
"messageGroupChatAdminClearFlagCount": "Only an admin can clear the flag count!",
"messageCannotFlagSystemMessages": "You cannot report a system message. If you need to report a violation of the Community Guidelines related to this message, please email a screenshot and explanation to our Community Manager at <%= communityManagerEmail %>.",
"messageCannotFlagSystemMessages": "You cannot report a system message. If you need to report a violation of the Community Guidelines related to this message, please e-mail a screenshot and explanation to our Community Manager at <%= communityManagerEmail %>.",
"messageCannotLeaveWhileQuesting": "You cannot accept this party invitation while you are in a quest. If you'd like to join this party, you must first abort your quest, which you can do from your party screen. You will be given back the quest scroll.",
"messageUserOperationProtected": "path `<%= operation %>` was not saved, as it's a protected path.",
"messageNotificationNotFound": "Notification not found.",
+2 -4
View File
@@ -1,6 +1,4 @@
{
"jsDisabledHeadingFull": "Alas! Your browser doesn't have JavaScript enabled and without it, Habitica can't work properly",
"jsDisabledHeadingFull": "Alas! Your browser doesnt have JavaScript enabled and without it, Habitica cant work properly",
"jsDisabledLink": "Please enable JavaScript to continue!"
}
}
+8 -8
View File
@@ -14,8 +14,8 @@
"mattBoch": "Matt Boch",
"mattBochText1": "Welcome to the stable! Im Matt, the beastmaster. Every time you complete a task, you'll have a random chance at receiving an Egg or a Hatching Potion to hatch Pets. When you hatch a Pet, it will appear here! Click a Pet's image to add it to your Avatar. Feed them with the Pet Food you find, and they'll grow into hardy Mounts.",
"welcomeToTavern": "Welcome to The Tavern!",
"sleepDescription": "Need a break? Pause Damage (located in Settings) to pause some of Habitica's more difficult game mechanics:",
"sleepBullet1": "Your missed Dailies won't damage you (bosses will still do damage caused by other Party member's missed Dailies)",
"sleepDescription": "Need a break? Pause Damage (located in Settings) to pause some of Habiticas more difficult game mechanisms:",
"sleepBullet1": "Your missed Dailies wont damage you (bosses will still do damage caused by other Party members missed Dailies)",
"sleepBullet2": "Your Task streaks and Habit counters will not reset",
"sleepBullet3": "Your damage to the Quest boss or found collection items will remain pending until you resume Damage",
"pauseDailies": "Pause Damage",
@@ -51,8 +51,8 @@
"shops": "Shops",
"custom": "Custom",
"wishlist": "Wishlist",
"wrongItemType": "The item type \"<%= type %>\" is not valid.",
"wrongItemPath": "The item path \"<%= path %>\" is not valid.",
"wrongItemType": "The item type <%= type %> is not valid.",
"wrongItemPath": "The item path <%= path %> is not valid.",
"unpinnedItem": "You unpinned <%= item %>! It will no longer display in your Rewards column.",
"purchasedItem": "You bought <%= itemName %>",
"ianTextMobile": "Can I interest you in some quest scrolls? Activate them to battle monsters with your Party!",
@@ -92,14 +92,14 @@
"skillsTitle": "<%= classStr %> Skills",
"toDo": "To Do",
"tourStatsPage": "This is your Stats page! Earn achievements by completing the listed tasks.",
"tourTavernPage": "Welcome to the Tavern, an all-ages chat room! You can keep your Dailies from hurting you in case of illness or travel by clicking \"Pause Damage\". Come say hi!",
"tourPartyPage": "Welcome to your new Party! You can invite other players to your Party by username, email, or from a list of players looking for a Party to earn the exclusive Basi-List Quest Scroll.<br/><br/>Select <a href='/static/faq#parties'>FAQ</a> from the Help dropdown to learn more about how Parties work.",
"tourTavernPage": "Welcome to the Tavern, an all-ages chat room! You can keep your Dailies from hurting you in case of illness or travel by clicking Pause Damage. Come and say hello!",
"tourPartyPage": "Welcome to your new Party! You can invite other players to your Party by username, e-mail, or from a list of players looking for a Party to earn the exclusive Basi-List Quest Scroll.<br/><br/>Select <a href='/static/faq#parties'>FAQ</a> from the Help dropdown to learn more about how Parties work.",
"tourChallengesPage": "Challenges are themed task lists created by users! Joining a Challenge will add its tasks to your account. Compete against other users to win Gem prizes!",
"tourMarketPage": "Every time you complete a task, you'll have a random chance at receiving an Egg, a Hatching Potion, or a piece of Pet Food. You can also buy these items here.",
"tourHallPage": "Welcome to the Hall of Heroes, where open-source contributors to Habitica are honoured. Whether through code, art, music, writing, or even just helpfulness, they have earned Gems, exclusive Equipment, and prestigious titles. You can contribute to Habitica, too!",
"tourPetsPage": "Welcome to the stable! Every time you complete a task, you'll have a random chance at receiving an Egg or a Hatching Potion to hatch Pets. When you hatch a Pet, it will appear here! Click a Pet's image to add it to your Avatar. Feed them with the Pet Food you find and they'll grow into hardy Mounts.",
"tourMountsPage": "Once you've fed a pet enough food to turn it into a mount, it will appear here. Click a mount to saddle up!",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your Stats. If you want to show different Equipment on your avatar without changing your Stats, click \"Enable Costume.\"",
"tourEquipmentPage": "This is where your Equipment is stored! Your Battle Gear affects your Stats. If you want to show different Equipment on your avatar without changing your Stats, click Enable Costume.",
"equipmentAlreadyOwned": "You already own that piece of equipment",
"tourOkay": "Okay!",
"tourSplendid": "Splendid!",
@@ -112,7 +112,7 @@
"welcome3notes": "As you improve your life, your avatar will level up and unlock pets, quests, equipment, and more!",
"limitedOffer": "Available until <%= date %>",
"paymentAutoRenew": "This subscription will auto-renew until it is cancelled. If you need to cancel this subscription, you can do so from your settings.",
"paymentCanceledDisputes": "Weve sent a cancellation confirmation to your email. If you dont see the email, please contact us to prevent future billing disputes.",
"paymentCanceledDisputes": "Weve sent a cancellation confirmation to your e-mail. If you dont see the e-mail, please contact us to prevent future invoice disputes.",
"cannotUnpinItem": "This item cannot be unpinned.",
"paymentSubBillingWithMethod": "Your subscription will be billed<br><strong>$<%= amount %>.00 USD</strong> every <strong><%= months %> month(s) </strong> via <strong><%= paymentMethod %></strong>.",
"invalidUnlockSet": "This set of items is invalid and cannot be unlocked.",
+5 -5
View File
@@ -1,10 +1,10 @@
{
"needTips": "Need some tips on how to begin? Here's a straightforward guide!",
"needTips": "Need some tips on how to begin? Heres a straightforward guide!",
"step1": "Step 1: Enter Tasks",
"webStep1Text": "Habitica is nothing without real-world goals, so enter a few tasks. You can add more later as you think of them! All tasks can be added by clicking the green \"Create\" button.\n* **Set up [To Do's](https://habitica.fandom.com/wiki/To_Do%27s):** Enter tasks you do once or rarely in the To Do's column, one at a time. You can click on the tasks to edit them and add checklists, due dates, and more!\n* **Set up [Dailies](http://habitica.fandom.com/wiki/Dailies):** Enter activities you need to do daily or on a particular day of the week, month, or year in the Dailies column. Click task to edit when it will be due and/or set a start date. You can also make it due on a repeating basis, for example, every 3 days.\n* **Set up [Habits](http://habitica.fandom.com/wiki/Habits):** Enter habits you want to establish in the Habits column. You can edit the Habit to change it to just a good habit :heavy_plus_sign: or a bad habit :heavy_minus_sign:\n* **Set up [Rewards](http://habitica.fandom.com/wiki/Rewards):** In addition to the in-game Rewards offered, add activities or treats which you want to use as a motivation to the Rewards column. It's important to give yourself a break or allow some indulgence in moderation!\n* If you need inspiration for which tasks to add, you can look at the wiki's pages on [Sample Habits](http://habitica.fandom.com/wiki/Sample_Habits), [Sample Dailies](http://habitica.fandom.com/wiki/Sample_Dailies), [Sample To Do's](https://habitica.fandom.com/wiki/Sample_To_Do%27s), and [Sample Rewards](http://habitica.fandom.com/wiki/Sample_Custom_Rewards).",
"webStep1Text": "Habitica is nothing without real-world goals, so enter a few tasks. You can add more later as you think of them! All tasks can be added by clicking the green Create button.\n* **Set up [To Dos](https://habitica.fandom.com/wiki/To_Do%27s):** Enter tasks you do once or rarely in the To Dos column, one at a time. You can click on the tasks to edit them and add checklists, due dates, and more!\n* **Set up [Dailies](http://habitica.fandom.com/wiki/Dailies):** Enter activities you need to do daily or on a particular day of the week, month, or year in the Dailies column. Click the task to edit when it will be due and/or set a start date. You can also make it due on a repeating basis; for example, every 3 days.\n* **Set up [Habits](http://habitica.fandom.com/wiki/Habits):** Enter habits you want to establish in the Habits column. You can edit the Habit to change it to just a good habit :heavy_plus_sign: or a bad habit :heavy_minus_sign:\n* **Set up [Rewards](http://habitica.fandom.com/wiki/Rewards):** In addition to the in-game Rewards offered, add activities or treats which you want to use as a motivation to the Rewards column. Its important to give yourself a break or allow some indulgence in moderation!\n* If you need inspiration for which tasks to add, you can look at the wikis pages on [Sample Habits](http://habitica.fandom.com/wiki/Sample_Habits), [Sample Dailies](http://habitica.fandom.com/wiki/Sample_Dailies), [Sample To Dos](https://habitica.fandom.com/wiki/Sample_To_Do%27s), and [Sample Rewards](http://habitica.fandom.com/wiki/Sample_Custom_Rewards).",
"step2": "Step 2: Gain Points by Doing Things in Real Life",
"webStep2Text": "Now, start tackling your goals from the list! As you complete tasks and check them off in Habitica, you will gain [Experience](https://habitica.fandom.com/wiki/Experience_Points), which helps you level up, and [Gold](https://habitica.fandom.com/wiki/Gold_Points), which allows you to purchase Rewards. If you fall into bad habits or miss your Dailies, you will lose [Health](https://habitica.fandom.com/wiki/Health_Points). In that way, the Habitica Experience and Health bars serve as a fun indicator of your progress toward your goals. You'll start seeing your real life improve as your character advances in the game.",
"webStep2Text": "Now, start tackling your goals from the list! As you complete tasks and check them off in Habitica, you will gain [Experience](https://habitica.fandom.com/wiki/Experience_Points), which helps you level up, and [Gold](https://habitica.fandom.com/wiki/Gold_Points), which allows you to purchase Rewards. If you fall into bad habits or miss your Dailies, you will lose [Health](https://habitica.fandom.com/wiki/Health_Points). In that way, the Habitica Experience and Health bars serve as a fun indicator of your progress toward your goals. Youll start seeing your real life improve as your character advances in the game.",
"step3": "Step 3: Customise and Explore Habitica",
"webStep3Text": "Once you're familiar with the basics, you can get even more out of Habitica with these nifty features:\n * Organise your Tasks with [tags](https://habitica.fandom.com/wiki/Tags) (edit a Task to add them).\n * Customise your [Avatar](https://habitica.fandom.com/wiki/Avatar) by clicking the user icon in the upper-right corner.\n * Buy your [Equipment](https://habitica.fandom.com/wiki/Equipment) under Rewards or from the [Shops](<%= shopUrl %>), and change it under [Inventory > Equipment](<%= equipUrl %>).\n * Connect with other users via the [Looking for Party tool](https://habitica.com/looking-for-party).\n * Hatch [Pets](https://habitica.fandom.com/wiki/Pets) by collecting [Eggs](https://habitica.fandom.com/wiki/Eggs) and [Hatching Potions](https://habitica.fandom.com/wiki/Hatching_Potions). [Feed](https://habitica.fandom.com/wiki/Food) them to create [Mounts](https://habitica.fandom.com/wiki/Mounts).\n * At level 10: Choose a particular [Class](https://habitica.fandom.com/wiki/Class_System) and then use Class-specific [skills](https://habitica.fandom.com/wiki/Skills) (levels 11 to 14).\n * Form a Party with your friends (by clicking [Party](<%= partyUrl %>) in the navigation bar) to stay accountable and earn a Quest scroll.\n * Defeat monsters and collect objects on [Quests](https://habitica.fandom.com/wiki/Quests) (you will be given a quest at level 15).",
"overviewQuestionsRevised": "Have questions? Check out the <a href='/static/faq'>FAQ</a>! If your question isn't mentioned there, you can ask for further help using this form: "
"webStep3Text": "Once youre familiar with the basics, you can get even more out of Habitica with these nifty features:\n * Organise your Tasks with [tags](https://habitica.fandom.com/wiki/Tags) (edit a Task to add them).\n * Customise your [Avatar](https://habitica.fandom.com/wiki/Avatar) by clicking the user icon in the upper-right corner.\n * Buy your [Equipment](https://habitica.fandom.com/wiki/Equipment) under Rewards or from the [Shops](<%= shopUrl %>), and change it under [Inventory > Equipment](<%= equipUrl %>).\n * Connect with other users via the [Looking for Party tool](https://habitica.com/looking-for-party).\n * Hatch [Pets](https://habitica.fandom.com/wiki/Pets) by collecting [Eggs](https://habitica.fandom.com/wiki/Eggs) and [Hatching Potions](https://habitica.fandom.com/wiki/Hatching_Potions). [Feed](https://habitica.fandom.com/wiki/Food) them to create [Mounts](https://habitica.fandom.com/wiki/Mounts).\n * At level 10: Choose a particular [Class](https://habitica.fandom.com/wiki/Class_System) and then use Class-specific [skills](https://habitica.fandom.com/wiki/Skills) (levels 11 to 14).\n * Form a Party with your friends (by clicking [Party](<%= partyUrl %>) in the navigation bar) to stay accountable and earn a Quest scroll.\n * Defeat monsters and collect objects on [Quests](https://habitica.fandom.com/wiki/Quests) (you will be given a quest at level 15).",
"overviewQuestionsRevised": "Have questions? Check out the <a href='/static/faq'>FAQ</a>! If your question isnt mentioned there, you can ask for further help using this form: "
}
+3 -3
View File
@@ -46,19 +46,19 @@
"dropsExplanationEggs": "Spend Gems to get eggs more quickly, if you don't want to wait for standard eggs to drop, or to repeat Quests to earn Quest eggs. <a href=\"https://habitica.fandom.com/wiki/Drops\">Learn more about the drop system.</a>",
"premiumPotionNoDropExplanation": "Magic Hatching Potions cannot be used on eggs received from Quests. The only way to get Magic Hatching Potions is by buying them below, not from random drops.",
"beastMasterProgress": "Beast Master Progress",
"beastAchievement": "You have earned the \"Beast Master\" Achievement for collecting all the pets!",
"beastAchievement": "You have earned the Beast Master Achievement for collecting all the pets!",
"beastMasterName": "Beast Master",
"beastMasterText": "Has found all 90 pets (incredibly difficult, congratulate this user!)",
"beastMasterText2": " and has released their pets a total of <%= count %> time(s)",
"mountMasterProgress": "Mount Master Progress",
"mountAchievement": "You have earned the \"Mount Master\" achievement for taming all the mounts!",
"mountAchievement": "You have earned the Mount Master achievement for taming all the mounts!",
"mountMasterName": "Mount Master",
"mountMasterText": "Has tamed all 90 mounts (even more difficult, congratulate this user!)",
"mountMasterText2": " and has released all 90 of their mounts a total of <%= count %> time(s)",
"triadBingoName": "Triad Bingo",
"triadBingoText": "Has found all 90 pets, all 90 mounts, and found all 90 pets AGAIN (HOW DID YOU DO THAT!)",
"triadBingoText2": " and has released all their Pets and Mounts a total of <%= count %> time(s)",
"triadBingoAchievement": "You have earned the \"Triad Bingo\" achievement for finding all the pets, taming all the mounts, and finding all the pets again!",
"triadBingoAchievement": "You have earned the Triad Bingo achievement for finding all the pets, taming all the mounts, and finding all the pets again!",
"hatchedPet": "You hatched a new <%= potion %> <%= egg %>!",
"hatchedPetGeneric": "You hatched a new pet!",
"hatchedPetHowToUse": "Visit [Pets and Mounts](<%= stableUrl %>) to feed and equip your newest pet!",
+3 -3
View File
@@ -34,7 +34,7 @@
"mustLvlQuest": "You must be level <%= level %> to buy this quest!",
"unlockByQuesting": "To unlock this quest, complete <%= title %>.",
"questConfirm": "Are you sure you want to start this Quest? Not all Party members have accepted the Quest invite. Quests start automatically after all members respond to the invite.",
"sureCancel": "Are you sure you want to cancel this Quest? Canceling the Quest will cancel all accepted and pending invitations. The Quest will be returned to the owner's inventory.",
"sureCancel": "Are you sure you want to cancel this Quest? Cancelling the Quest will cancel all accepted and pending invitations. The Quest will be returned to the owner's inventory.",
"sureAbort": "Are you sure you want to cancel this Quest? All progress will be lost. The Quest will be returned to the owner's inventory.",
"bossRageDescription": "When this bar fills, the boss will unleash a special attack!",
"startQuest": "Start Quest",
@@ -42,8 +42,8 @@
"questInviteNotFound": "No quest invitation found.",
"guildQuestsNotSupported": "Guilds cannot be invited on quests.",
"questNotOwned": "You don't own that quest scroll.",
"questNotGoldPurchasable": "Quest \"<%= key %>\" is not a Gold-purchasable quest.",
"questNotGemPurchasable": "Quest \"<%= key %>\" is not a Gem-purchasable quest.",
"questNotGoldPurchasable": "Quest <%= key %> is not a Gold-purchasable quest.",
"questNotGemPurchasable": "Quest <%= key %> is not a Gem-purchasable quest.",
"questAlreadyUnderway": "Your party is already on a quest. Try again when the current quest has ended.",
"questAlreadyAccepted": "You already accepted the quest invitation.",
"questLeaderCannotLeaveQuest": "Quest leader cannot leave quest",
+132 -123
View File
@@ -1,18 +1,18 @@
{
"questEvilSantaText": "Trapper Santa",
"questEvilSantaNotes": "You hear agonised 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!<br><br><strong>Note</strong>: “Trapper Santa” awards a stackable quest achievement but gives a rare mount that can only be added to your stable once.",
"questEvilSantaNotes": "You hear agonised roars deep in the icefields. You follow the growlspunctuated by the sound of cacklingto a clearing in the woods, where you see a fully-grown polar bear. Shes 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!<br><br><strong>Note</strong>: “Trapper Santa” awards a stackable quest achievement but gives a rare mount that can only be added to your stable once.",
"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": "When Trapper Santa captured the polar bear mount, her cub ran off into the icefields. You hear twig-snaps and snow crunch through the crystalline sound of the forest. Paw prints! You start racing to follow the trail. Find all the prints and broken twigs, and retrieve the cub!<br><br><strong>Note</strong>: “Find the Cub” awards a stackable quest achievement but gives a rare pet that can only be added to your stable once.",
"questEvilSanta2Completion": "You've found the cub! It will keep you company forever.",
"questEvilSanta2Completion": "Youve found the cub! It will keep you company forever.",
"questEvilSanta2CollectTracks": "Tracks",
"questEvilSanta2CollectBranches": "Broken Twigs",
"questEvilSanta2DropBearCubPolarPet": "Polar Bear (Pet)",
"questGryphonText": "The Fiery Gryphon",
"questGryphonNotes": "The grand beast master, <strong>baconsaur</strong>, has come to your party seeking help. \"Please, adventurers, you must help me! My prized gryphon has broken free and is terrorising Habit City! If you can stop her, I could reward you with some of her eggs!\"",
"questGryphonCompletion": "Defeated, the mighty beast ashamedly slinks back to its master. \"My word! Well done, adventurers!\" <strong>baconsaur</strong> exclaims, \"Please, have some of the gryphon's eggs. I am sure you will raise these young ones well!\"",
"questGryphonNotes": "The grand beast master, <strong>baconsaur</strong>, has come to your party seeking help. Please, adventurers, you must help me! My prized gryphon has broken free and is terrorising Habit City! If you can stop her, I could reward you with some of her eggs!",
"questGryphonCompletion": "Defeated, the mighty beast ashamedly slinks back to its master. My word! Well done, adventurers! <strong>baconsaur</strong> exclaims, Please, have some of the gryphons eggs. I am sure you will raise these young ones well!",
"questGryphonBoss": "Fiery Gryphon",
"questGryphonDropGryphonEgg": "Gryphon (Egg)",
"questGryphonUnlockText": "Unlocks Gryphon Eggs for purchase in the Market",
@@ -23,162 +23,162 @@
"questHedgehogDropHedgehogEgg": "Hedgehog (Egg)",
"questHedgehogUnlockText": "Unlocks Hedgehog Eggs for purchase in the Market",
"questGhostStagText": "The Spirit of Spring",
"questGhostStagNotes": "Ahh, Spring. The time of year when colour once again begins to fill the landscape. Gone are the cold, snowy mounds of winter. Where frost once stood, vibrant plant life takes its place. Luscious green leaves fill in the trees, grass returns to its former vivid hue, a rainbow of flowers rise along the plains, and a white mystical fog covers the land! ... Wait. Mystical fog? \"Oh no,\" <strong>InspectorCaracal</strong> says apprehensively, \"It would appear that some kind of spirit is the cause of this fog. Oh, and it is charging right at you.\"",
"questGhostStagCompletion": "The spirit, seemingly unwounded, lowers its nose to the ground. A calming voice envelops your party. \"I apologise for my behaviour. I have only just awoken from my slumber, and it would appear my wits have not completely returned to me. Please take these as a token of my apology.\" A cluster of eggs materialise on the grass before the spirit. Without another word, the spirit runs off into the forest with flowers falling in his wake.",
"questGhostStagNotes": "Ahh, Spring. The time of year when colour once again begins to fill the landscape. Gone are the cold, snowy mounds of winter. Where frost once stood, vibrant plant life takes its place. Luscious green leaves fill in the trees, grass returns to its former vivid hue, a rainbow of flowers rise along the plains, and a white mystical fog covers the land! Wait. Mystical fog? Oh no, <strong>InspectorCaracal</strong> says apprehensively, It would appear that some kind of spirit is the cause of this fog. Oh, and it is charging right at you.",
"questGhostStagCompletion": "The spirit, seemingly unwounded, lowers its nose to the ground. A calming voice envelops your party, “I apologise for my behaviour. I have only just awoken from my slumber, and it would appear my wits have not completely returned to me. Please take these as a token of my apology. A cluster of eggs materialises on the grass before the spirit. Without another word, the spirit runs off into the forest with flowers falling in his wake.",
"questGhostStagBoss": "Ghost Stag",
"questGhostStagDropDeerEgg": "Deer (Egg)",
"questGhostStagUnlockText": "Unlocks Deer Eggs for purchase in the Market",
"questRatText": "The Rat King",
"questRatNotes": "Rubbish! Massive piles of unchecked Dailies are lying all across Habitica. The problem has become so serious that hordes of rats are now seen everywhere. You notice @Pandah petting one of the beasts lovingly. She explains that rats are gentle creatures that feed on unchecked Dailies. The real problem is that the Dailies have fallen into the sewer, creating a dangerous pit that must be cleared. As you descend into the sewers, a massive rat, with blood red eyes and mangled yellow teeth, attacks you, defending its horde. Will you cower in fear or face the fabled Rat King?",
"questRatCompletion": "Your final strike saps the gargantuan rat's strength, his eyes fading to a dull grey. The beast splits into many tiny rats, which scurry off in fright. You notice @Pandah standing behind you, looking at the once mighty creature. She explains that the citizens of Habitica have been inspired by your courage and are quickly completing all their unchecked Dailies. She warns you that we must be vigilant, for should we let down our guard, the Rat King will return. As payment, @Pandah offers you several rat eggs. Noticing your uneasy expression, she smiles, \"They make wonderful pets.\"",
"questRatCompletion": "Your final strike saps the gargantuan rats strength, his eyes fading to a dull grey. The beast splits into many tiny rats, which scurry off in fright. You notice @Pandah standing behind you, looking at the once mighty creature. She explains that the citizens of Habitica have been inspired by your courage and are quickly completing all their unchecked Dailies. She warns you that we must be vigilant, for should we let down our guard, the Rat King will return. As payment, @Pandah offers you several rat eggs. Noticing your uneasy expression, she smiles, They make wonderful pets.",
"questRatBoss": "Rat King",
"questRatDropRatEgg": "Rat (Egg)",
"questRatUnlockText": "Unlocks Rat Eggs for purchase in the Market",
"questOctopusText": "The Call of Octothulu",
"questOctopusNotes": "@Urse, a wild-eyed young scribe, has asked for your help exploring a mysterious cave by the sea shore. Among the twilight tidepools stands a massive gate of stalactites and stalagmites. As you near the gate, a dark whirlpool begins to spin at its base. You stare in awe as a squid-like dragon rises through the maw. \"The sticky spawn of the stars has awakened,\" roars @Urse madly. \"After vigintillions of years, the great Octothulu is loose again, and ravening for delight!\"",
"questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tide-pool, set in a nest of gold coins. \"Probably just octopus eggs,\" you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
"questOctopusNotes": "@Urse, a wild-eyed young scribe, has asked for your help exploring a mysterious cave by the sea shore. Among the twilight tidepools stands a massive gate of stalactites and stalagmites. As you near the gate, a dark whirlpool begins to spin at its base. You stare in awe as a squid-like dragon rises through the maw. The sticky spawn of the stars has awakened, roars @Urse madly, “After vigintillions of years, the great Octothulu is loose again, and ravening for delight!",
"questOctopusCompletion": "With a final blow, the creature slips away into the whirlpool from which it came. You cannot tell if @Urse is happy with your victory or saddened to see the beast go. Wordlessly, your companion points to three slimy, gargantuan eggs in a nearby tide-pool, set in a nest of gold coins. Probably just octopus eggs, you say nervously. As you return home, @Urse frantically scribbles in a journal and you suspect this is not the last time you will hear of the great Octothulu.",
"questOctopusBoss": "Octothulu",
"questOctopusDropOctopusEgg": "Octopus (Egg)",
"questOctopusUnlockText": "Unlocks Octopus Eggs for purchase in the Market",
"questHarpyText": "Help! Harpy!",
"questHarpyNotes": "The brave adventurer @UncommonCriminal has disappeared into the forest, following the trail of a winged monster that was sighted several days ago. You are about to begin a search when a wounded parrot lands on your arm, an ugly scar marring its beautiful plumage. Attached to its leg is a scrawled note explaining that while defending the parrots, @UncommonCriminal was captured by a vicious Harpy, and desperately needs your help to escape. Will you follow the bird, defeat the Harpy, and save @UncommonCriminal?",
"questHarpyCompletion": "A final blow to the Harpy brings it down, feathers flying in all directions. After a quick climb to its nest you find @UncommonCriminal, surrounded by parrot eggs. As a team, you quickly place the eggs back in the nearby nests. The scarred parrot who found you caws loudly, dropping several eggs in your arms. \"The Harpy attack has left some eggs in need of protection,\" explains @UncommonCriminal. \"It seems you have been made an honorary parrot.\"",
"questHarpyCompletion": "A final blow to the Harpy brings it down, feathers flying in all directions. After a quick climb to its nest you find @UncommonCriminal, surrounded by parrot eggs. As a team, you quickly place the eggs back in the nearby nests. The scarred parrot who found you caws loudly, dropping several eggs in your arms. The Harpy attack has left some eggs in need of protection, explains @UncommonCriminal, “It seems you have been made an honorary parrot.",
"questHarpyBoss": "Harpy",
"questHarpyDropParrotEgg": "Parrot (Egg)",
"questHarpyUnlockText": "Unlocks Parrot Eggs for purchase in the Market",
"questRoosterText": "Rooster Rampage",
"questRoosterNotes": "For years the farmer @extrajordinary has used Roosters as an alarm clock. But now a giant Rooster has appeared, crowing louder than any before and waking up everyone in Habitica! The sleep-deprived Habiticans struggle through their daily tasks. @Pandoro decides the time has come to put a stop to this. \"Please, is there anyone who can teach that Rooster to crow quietly?\" You volunteer, approaching the Rooster early one morning but it turns, flapping its giant wings and showing its sharp claws, and crows a battle cry.",
"questRoosterNotes": "For years the farmer @extrajordinary has used Roosters as an alarm clock. But now a giant Rooster has appeared, crowing louder than any beforeand waking up everyone in Habitica! The sleep-deprived Habiticans struggle through their daily tasks. @Pandoro decides the time has come to put a stop to this. Please, is there anyone who can teach that Rooster to crow quietly? You volunteer, approaching the Rooster early one morningbut it turns, flapping its giant wings and showing its sharp claws, and crows a battle cry.",
"questRoosterCompletion": "With finesse and strength, you have tamed the wild beast. Its ears, once filled with feathers and half-remembered tasks, are now clear as day. It crows at you quietly, snuggling its beak into your shoulder. The next day youre set to take your leave, but @EmeraldOx runs up to you with a covered basket. “Wait! When I went into the farmhouse this morning, the Rooster had pushed these against the door where you slept. I think he wants you to have them.” You uncover the basket to see three delicate eggs.",
"questRoosterBoss": "Rooster",
"questRoosterDropRoosterEgg": "Rooster (Egg)",
"questRoosterUnlockText": "Unlocks Rooster Eggs for purchase in the Market",
"questSpiderText": "The Icy Arachnid",
"questSpiderNotes": "As the weather starts cooling down, delicate frost begins appearing on Habiticans' windowpanes in lacy webs... except for @Arcosine, whose windows are frozen completely shut by the Frost Spider currently taking up residence in his home. Oh dear.",
"questSpiderCompletion": "The Frost Spider collapses, leaving behind a small pile of frost and a few of her enchanted egg sacs. @Arcosine rather hurriedly offers them to you as a reward--perhaps you could raise some non-threatening spiders as pets of your own?",
"questSpiderNotes": "As the weather starts cooling down, delicate frost begins appearing on Habiticans windowpanes in lacy webs except for @Arcosine, whose windows are frozen completely shut by the Frost Spider currently taking up residence in his home. Oh dear.",
"questSpiderCompletion": "The Frost Spider collapses, leaving behind a small pile of frost and a few of her enchanted egg sacs. @Arcosine rather hurriedly offers them to you as a rewardperhaps you could raise some non-threatening spiders as pets of your own?",
"questSpiderBoss": "Spider",
"questSpiderDropSpiderEgg": "Spider (Egg)",
"questSpiderUnlockText": "Unlocks Spider Eggs for purchase in the Market",
"questGroupVice": "Vice the Shadow Wyrm",
"questVice1Text": "Vice, Part 1: Free Yourself of the Dragon's Influence",
"questVice1Notes": "They say there lies a terrible evil in the caverns of Mt. Habitica. A monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.<br><br>How can you expect to fight the beast if it already has control over you? Don't fall victim to laziness and vice! Work hard to fight against the dragon's dark influence and dispel his hold on you!",
"questVice1Boss": "Vice's Shade",
"questVice1Completion": "With Vice's influence over you dispelled, you feel a surge of strength you didn't know you had return to you. Congratulations! But a more frightening foe awaits...",
"questVice1Text": "Vice, Part 1: Free Yourself of the Dragons Influence",
"questVice1Notes": "They say there lies a terrible evil in the caverns of Mt. Habitica: a monster whose presence twists the wills of the strong heroes of the land, turning them towards bad habits and laziness! The beast is a grand dragon of immense power and comprised of the shadows themselves: Vice, the treacherous Shadow Wyrm. Brave Habiteers, stand up and defeat this foul beast once and for all, but only if you believe you can stand against its immense power.<br><br>How can you expect to fight the beast if it already has control over you? Dont fall victim to laziness and vice! Work hard to fight against the dragons dark influence and dispel his hold on you!",
"questVice1Boss": "Vices Shade",
"questVice1Completion": "With Vices influence over you dispelled, you feel a surge of strength you didnt know you had return to you. Congratulations! But a more frightening foe awaits",
"questVice1DropVice2Quest": "Vice Part 2 (Scroll)",
"questVice2Text": "Vice, Part 2: Find the Lair of the Wyrm",
"questVice2Notes": "Confident in yourselves and your ability to withstand the influence of Vice the Shadow Wyrm, your Party makes its way to Mt. Habitica. You approach the entrance to the mountain's caverns and pause. Swells of shadows, almost like fog, wisp out from the opening. It is near impossible to see anything in front of you. The light from your lanterns seem to end abruptly where the shadows begin. It is said that only magical light can pierce the dragon's infernal haze. If you can find enough light crystals, you could make your way to the dragon.",
"questVice2Notes": "Confident in yourselves and your ability to withstand the influence of Vice the Shadow Wyrm, your Party makes its way to Mt. Habitica. You approach the entrance to the mountains caverns and pause. Swells of shadows, almost like fog, wisp out from the opening. It is near impossible to see anything in front of you. The light from your lanterns seem to end abruptly where the shadows begin. It is said that only magical light can pierce the dragons infernal haze. If you can find enough light crystals, you could make your way to the dragon.",
"questVice2CollectLightCrystal": "Light Crystals",
"questVice2Completion": "As you lift the final crystal aloft, the shadows are dispelled, and your path forward is clear. With a quickening heart, you step forward into the cavern.",
"questVice2DropVice3Quest": "Vice Part 3 (Scroll)",
"questVice3Text": "Vice, Part 3: Vice Awakens",
"questVice3Notes": "After much effort, your party has discovered Vice's lair. The hulking monster eyes your party with distaste. As shadows swirl around you, a voice whispers through your head, \"More foolish citizens of Habitica come to stop me? Cute. You'd have been wise not to come.\" The scaly titan rears back its head and prepares to attack. This is your chance! Give it everything you've got and defeat Vice once and for all!",
"questVice3Completion": "The shadows dissipate from the cavern and a steely silence falls. My word, you've done it! You have defeated Vice! You and your party may finally breathe a sigh of relief. Enjoy your victory, brave Habiteers, but take the lessons you've learned from battling Vice and move forward. There are still Habits to be done and potentially worse evils to conquer!",
"questVice3Notes": "After much effort, your party has discovered Vices lair. The hulking monster eyes your party with distaste. As shadows swirl around you, a voice whispers through your head, More foolish citizens of Habitica come to stop me? Cute. Youd have been wise not to come. The scaly titan rears back its head and prepares to attack. This is your chance! Give it everything youve got and defeat Vice once and for all!",
"questVice3Completion": "The shadows dissipate from the cavern and a steely silence falls. My word, youve done it! You have defeated Vice! You and your party may finally breathe a sigh of relief. Enjoy your victory, brave Habiteers, but take the lessons youve learned from battling Vice and move forward. There are still Habits to be done and potentially worse evils to conquer!",
"questVice3Boss": "Vice, the Shadow Wyrm",
"questVice3DropWeaponSpecial2": "Stephen Weber's Shaft of the Dragon",
"questVice3DropWeaponSpecial2": "Stephen Webers Shaft of the Dragon",
"questVice3DropDragonEgg": "Dragon (Egg)",
"questVice3DropShadeHatchingPotion": "Shade Hatching Potion",
"questGroupMoonstone": "Recidivate Rising",
"questMoonstone1Text": "Recidivate, Part 1: The Moonstone Chain",
"questMoonstone1Notes": "A terrible affliction has struck Habiteers. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!<br><br>You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her spectre uselessly.<br><br>\"Dont bother,\" she hisses with a dry rasp. \"Without a chain of moonstones, nothing can harm me and master jeweller @aurakami scattered all the moonstones across Habitica long ago!\" Panting, you retreat... but you know what you must do.",
"questMoonstone1Notes": "A terrible affliction has struck Habiteers. Bad Habits thought long-dead are rising back up with a vengeance. Dishes lie unwashed, textbooks linger unread, and procrastination runs rampant!<br><br>You track some of your own returning Bad Habits to the Swamps of Stagnation and discover the culprit: the ghostly Necromancer, Recidivate. You rush in, weapons swinging, but they slide through her spectre uselessly.<br><br>Dont bother, she hisses with a dry rasp. Without a chain of moonstones, nothing can harm meand master jeweller @aurakami scattered all the moonstones across Habitica long ago! Panting, you retreat but you know what you must do.",
"questMoonstone1CollectMoonstone": "Moonstones",
"questMoonstone1Completion": "At last, you manage to pull the final moonstone from the swampy sludge. Its time to go fashion your collection into a weapon that can finally defeat Recidivate!",
"questMoonstone1DropMoonstone2Quest": "Recidivate, Part 2: Recidivate the Necromancer (Scroll)",
"questMoonstone2Text": "Recidivate, Part 2: Recidivate the Necromancer",
"questMoonstone2Notes": "The brave weaponsmith @InspectorCaracal helps you fashion the enchanted moonstones into a chain. Youre ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.<br><br>Rotting breath whispers in your ear. \"Back again? How delightful...\" You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. \"You may have bound me to the world once more,\" Recidivate snarls, \"but now it is time for you to leave it!\"",
"questMoonstone2Notes": "The brave weaponsmith @InspectorCaracal helps you fashion the enchanted moonstones into a chain. Youre ready to confront Recidivate at last, but as you enter the Swamps of Stagnation, a terrible chill sweeps over you.<br><br>Rotting breath whispers in your ear, “Back again? How delightful…” You spin and lunge, and under the light of the moonstone chain, your weapon strikes solid flesh. You may have bound me to the world once more, Recidivate snarls, but now it is time for you to leave it!",
"questMoonstone2Boss": "The Necromancer",
"questMoonstone2Completion": "Recidivate staggers backwards under your final blow, and for a moment, your heart brightens but then she throws back her head and lets out a horrible laugh. Whats happening?",
"questMoonstone2Completion": "Recidivate staggers backwards under your final blow, and for a moment, your heart brightensbut then she throws back her head and lets out a horrible laugh. Whats happening?",
"questMoonstone2DropMoonstone3Quest": "Recidivate, Part 3: Recidivate Transformed (Scroll)",
"questMoonstone3Text": "Recidivate, Part 3: Recidivate Transformed",
"questMoonstone3Notes": "Laughing wickedly, Recidivate crumples to the ground, and you strike at her again with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.<br><br>\"Foolish creature of flesh!\" she shouts. \"These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the specter of your most feared foe!\"<br><br>A sickly green fog rises from the swamp, and Recidivates body writhes and contorts into a shape that fills you with dread the undead body of Vice, horribly reborn.",
"questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.<br><br>@Baconsaur the beast master swoops down on a gryphon. \"I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunic your bravery speaks of a noble heart, and I believe you were meant to have it.\"",
"questMoonstone3Notes": "Laughing wickedly, Recidivate crumples to the ground, and you strike at her again with the moonstone chain. To your horror, Recidivate seizes the gems, eyes burning with triumph.<br><br>Foolish creature of flesh! she shouts, “These moonstones will restore me to a physical form, true, but not as you imagined. As the full moon waxes from the dark, so too does my power flourish, and from the shadows I summon the spectre of your most feared foe!<br><br>A sickly green fog rises from the swamp, and Recidivates body writhes and contorts into a shape that fills you with dreadthe undead body of Vice, horribly reborn.",
"questMoonstone3Completion": "Your breath comes hard and sweat stings your eyes as the undead Wyrm collapses. The remains of Recidivate dissipate into a thin grey mist that clears quickly under the onslaught of a refreshing breeze, and you hear the distant, rallying cries of Habiticans defeating their Bad Habits for once and for all.<br><br>@Baconsaur the beast master swoops down on a gryphon, “I saw the end of your battle from the sky, and I was greatly moved. Please, take this enchanted tunicyour bravery speaks of a noble heart, and I believe you were meant to have it.",
"questMoonstone3Boss": "Necro-Vice",
"questMoonstone3DropRottenMeat": "Rotten Meat (Food)",
"questMoonstone3DropZombiePotion": "Zombie Hatching Potion",
"questGroupGoldenknight": "The Golden Knight",
"questGoldenknight1Text": "The Golden Knight, Part 1: A Stern Talking-To",
"questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans' cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
"questGoldenknight1Notes": "The Golden Knight has been getting on poor Habiticans cases. Didn't do all of your Dailies? Checked off a negative Habit? She will use this as a reason to harass you about how you should follow her example. She is the shining example of a perfect Habitican, and you are naught but a failure. Well, that is not nice at all! Everyone makes mistakes. They should not have to be met with such negativity for it. Perhaps it is time you gather some testimonies from hurt Habiticans and give the Golden Knight a stern talking-to!",
"questGoldenknight1CollectTestimony": "Testimonies",
"questGoldenknight1Completion": "Look at all these testimonies! Surely this will be enough to convince the Golden Knight. Now all you need to do is find her.",
"questGoldenknight1DropGoldenknight2Quest": "The Golden Knight Part 2: Gold Knight (Scroll)",
"questGoldenknight2Text": "The Golden Knight, Part 2: Gold Knight",
"questGoldenknight2Notes": "Armed with dozens of Habiticans' testimonies, you finally confront the Golden Knight. You begin to recite the Habitcans' complaints to her, one by one. \"And @Pfeffernusse says that your constant bragging-\" The knight raises her hand to silence you and scoffs, \"Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine!\" She raises her morningstar and prepares to attack you!",
"questGoldenknight2Notes": "Armed with dozens of Habiticans testimonies, you finally confront the Golden Knight. You begin to recite the Habitcans complaints to her, one by one. And @Pfeffernusse says that your constant bragging—” The knight raises her hand to silence you and scoffs, Please, these people are merely jealous of my success. Instead of complaining, they should simply work as hard as I! Perhaps I shall show you the power you can attain through diligence such as mine! She raises her morningstar and prepares to attack you!",
"questGoldenknight2Boss": "Gold Knight",
"questGoldenknight2Completion": "The Golden Knight lowers her Morningstar in consternation. “I apologise for my rash outburst,” she says. “The truth is, its painful to think that Ive been inadvertently hurting others, and it made me lash out in defence… but perhaps I can still apologise?”",
"questGoldenknight2DropGoldenknight3Quest": "The Golden Knight Part 3: The Iron Knight (Scroll)",
"questGoldenknight3Text": "The Golden Knight, Part 3: The Iron Knight",
"questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, \"Father, no!\" but the knight shows no signs of stopping. She turns to you and says, \"I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isn't stopped he'll destroy us all. Here, use my morningstar and halt the Iron Knight!\"",
"questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. \"You are quite strong,\" he pants. \"I have been humbled, today.\" The Golden Knight approaches you and says, \"Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologizing to the other Habiticans.\" She mulls over in thought before turning back to you. \"Here: as our gift to you, I want you to keep my morningstar. It is yours now.\"",
"questGoldenknight3Notes": "@Jon Arinbjorn cries out to you to get your attention. In the aftermath of your battle, a new figure has appeared. A knight coated in stained-black iron slowly approaches you with sword in hand. The Golden Knight shouts to the figure, Father, no! but the knight shows no signs of stopping. She turns to you and says, I am sorry. I have been a fool, with a head too big to see how cruel I have been. But my father is crueler than I could ever be. If he isnt stopped hell destroy us all. Here, use my morningstar and halt the Iron Knight!",
"questGoldenknight3Completion": "With a satisfying clang, the Iron Knight falls to his knees and slumps over. You are quite strong, he pants, “I have been humbled, today. The Golden Knight approaches you and says, Thank you. I believe we have gained some humility from our encounter with you. I will speak with my father and explain the complaints against us. Perhaps, we should begin apologising to the other Habiticans. She mulls over in thought before turning back to you. Here: as our gift to you, I want you to keep my morningstar. It is yours now.",
"questGoldenknight3Boss": "The Iron Knight",
"questGoldenknight3DropHoney": "Honey (Food)",
"questGoldenknight3DropGoldenPotion": "Golden Hatching Potion",
"questGoldenknight3DropWeapon": "Mustaine's Milestone Mashing Morning Star (Off-hand Weapon)",
"questGoldenknight3DropWeapon": "Mustaines Milestone Mashing Morning Star (Off-hand Weapon)",
"questGroupEarnable": "Earnable Quests",
"questBasilistText": "The Basi-List",
"questBasilistNotes": "There's a commotion in the marketplace--the kind that should make you run away. Being a courageous adventurer, you run towards it instead, and discover a Basi-list, coalescing from a clump of incomplete To Do's! Nearby Habiticans are paralyzed with fear at the length of the Basi-list, unable to start working. From somewhere in the vicinity, you hear @Arcosine shout: \"Quick! Complete your To Do's and Dailies to defang the monster, before someone gets a paper cut!\" Strike fast, adventurer, and check something off - but beware! If you leave any Dailies undone, the Basi-list will attack you and your party!",
"questBasilistCompletion": "The Basi-list has scattered into paper scraps, which shimmer gently in rainbow colours. \"Whew!\" says @Arcosine. \"Good thing you guys were here!\" Feeling more experienced than before, you gather up some fallen gold from among the papers.",
"questBasilistNotes": "Theres a commotion in the marketplacethe kind that should make you run away. Being a courageous adventurer, you run towards it instead, and discover a Basi-list, coalescing from a clump of incomplete To Dos! Nearby Habiticans are paralysed with fear at the length of the Basi-list, unable to start working. From somewhere in the vicinity, you hear @Arcosine shout: Quick! Complete your To Dos and Dailies to defang the monster, before someone gets a paper cut! Strike fast, adventurer, and check something offbut beware! If you leave any Dailies undone, the Basi-list will attack you and your party!",
"questBasilistCompletion": "The Basi-list has scattered into paper scraps, which shimmer gently in rainbow colours. Whew! says @Arcosine, “Good thing you guys were here! Feeling more experienced than before, you gather up some fallen gold from among the papers.",
"questBasilistBoss": "The Basi-List",
"questEggHuntText": "Egg Hunt",
"questEggHuntNotes": "Overnight, strange plain eggs have appeared everywhere: in Matt's stables, behind the counter at the Tavern, and even among the pet eggs at the Marketplace! What a nuisance! \"Nobody knows where they came from, or what they might hatch into,\" says Megan, \"but we can't just leave them laying around! Work hard and search hard to help me gather up these mysterious eggs. Maybe if you collect enough, there will be some extras left over for you...\"",
"questEggHuntCompletion": "You did it! In gratitude, <strong>Megan</strong> gives you ten of the eggs. \"I bet the hatching potions will dye them beautiful colours! And I wonder what will happen when they turn into mounts....\"",
"questEggHuntNotes": "Overnight, strange plain eggs have appeared everywhere: in Matts stables, behind the counter at the Tavern, and even among the pet eggs at the Marketplace! What a nuisance! Nobody knows where they came from, or what they might hatch into, says Megan, “But we cant just leave them lying around! Work hard and search hard to help me gather up these mysterious eggs. Maybe if you collect enough, there will be some extras left over for you…”",
"questEggHuntCompletion": "You did it! In gratitude, <strong>Megan</strong> gives you ten of the eggs. I bet the hatching potions will dye them beautiful colours! And I wonder what will happen when they turn into mounts…”",
"questEggHuntCollectPlainEgg": "Plain Eggs",
"questEggHuntDropPlainEgg": "Plain Egg",
"questDilatoryText": "The Dread Drag'on of Dilatory",
"questDilatoryNotes": "We should have heeded the warnings.<br><br>Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. We've awoken something horrifying from the crevasse: <strong>the Dread Drag'on of Dilatory!</strong> Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.<br><br>\"This must be what dragged Dilatory down!\" yells Lemoness. \"It wasn't the weight of the neglected tasks - the Dark Red Dailies just attracted its attention!\"<br><br>\"It's surging with magical energy!\" @Baconsaur cries. \"To have lived this long, it must be able to heal itself! How can we defeat it?\"<br><br>Why, the same way we defeat all beasts - with productivity! Quickly, Habiticans, band together and strike through your tasks, and all of us will battle this monster together. (There's no need to abandon previous quests - we believe in your ability to double-strike!) It won't attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strike - and I don't like the way it's eyeing the Tavern....",
"questDilatoryBoss": "The Dread Drag'on of Dilatory",
"questDilatoryText": "The Dread Dragon of Dilatory",
"questDilatoryNotes": "We should have heeded the warnings.<br><br>Dark shining eyes. Ancient scales. Massive jaws, and flashing teeth. Weve awoken something horrifying from the crevasse: <strong>the Dread Dragon of Dilatory!</strong> Screaming Habiticans fled in all directions when it reared out of the sea, its terrifyingly long neck extending hundreds of feet out of the water as it shattered windows with its searing roar.<br><br>This must be what dragged Dilatory down! yells Lemoness, “It wasnt the weight of the neglected tasksthe Dark Red Dailies just attracted its attention!<br><br>Its surging with magical energy! @Baconsaur cries, “To have lived this long, it must be able to heal itself! How can we defeat it?<br><br>Why, the same way we defeat all beastswith productivity! Quickly, Habiticans, band together and strike through your tasks, and all of us will battle this monster together. (Theres no need to abandon previous questswe believe in your ability to double-strike!) It wont attack us individually, but the more Dailies we skip, the closer we get to triggering its Neglect Strikeand I don't like the way its eyeing the Tavern",
"questDilatoryBoss": "The Dread Dragon of Dilatory",
"questDilatoryBossRageTitle": "Neglect Strike",
"questDilatoryBossRageDescription": "When this bar has filled up, the Dread Drag'on of Dilatory will unleash great havoc on Habitica's terrain",
"questDilatoryBossRageDescription": "When this bar has filled up, the Dread Dragon of Dilatory will unleash great havoc on Habiticas terrain",
"questDilatoryDropMantisShrimpPet": "Mantis Shrimp (Pet)",
"questDilatoryDropMantisShrimpMount": "Mantis Shrimp (Mount)",
"questDilatoryBossRageTavern": "`Dread Drag'on Casts NEGLECT STRIKE!`\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and their dark-red colour has attracted the Drag'on's rage! With its fearsome Neglect Strike attack, it has decimated the Tavern! Luckily, we've set up an Inn in a nearby city, and you're free to keep chatting on the shore... but poor Daniel the Barkeep just saw his beloved building crumble around him!\n\nI hope the beast doesn't attack again!",
"questDilatoryBossRageStables": "`Dread Drag'on Casts NEGLECT STRIKE!`\n\nYikes! Once again we left too many Dailies undone. The Drag'on has unleashed its Neglect Strike against Matt and the stables! Pets have been fleeing in all directions. Luckily it seems like all of yours are safe!\n\nPoor Habitica! I hope this doesn't happen again. Hurry and do all your tasks!",
"questDilatoryBossRageMarket": "`Dread Drag'on Casts NEGLECT STRIKE!`\n\nAhhh!! Alex the Merchant just had his shop smashed to smithereens by the Drag'on's Neglect Strike! But it seems like we're really wearing this beast down. I doubt it has enough energy for another strike.\n\nSo do not waver, Habitica! Let's drive this beast away from our shores!",
"questDilatoryCompletion": "`The Defeat Of The Dread Drag'On Of Dilatory`\n\nWe've done it! With a final last roar, the Dread Drag'on collapses and swims far, far away. Crowds of cheering Habiticans line the shores! We've helped Matt, Daniel, and Alex rebuild their buildings. But what's this?\n\n`The Citizens Return!`\n\nNow that the Drag'on has fled, thousands of sparkling colours are ascending through the sea. It is a rainbow swarm of Mantis Shrimp... and among them, hundreds of merpeople!\n\n\"We are the lost citizens of Dilatory!\" explains their leader, Manta. \"When Dilatory sank, the Mantis Shrimp that lived in these waters used a spell to transform us into merpeople so that we could survive. But in its rage, the Dread Drag'on trapped us all in the dark crevasse. We have been imprisoned there for hundreds of years - but now at last we are free to rebuild our city!\"\n\n\"As a thank you,\" says his friend @Ottl, \"Please accept this Mantis Shrimp pet and Mantis Shrimp mount, as well as XP, gold, and our eternal gratitude.\"\n\n`Rewards`\n * Mantis Shrimp Pet\n * Mantis Shrimp Mount\n * Chocolate, Blue Candyfloss, Pink Candyfloss, Fish, Honey, Meat, Milk, Potato, Rotten Meat, Strawberry",
"questDilatoryBossRageTavern": "`Dread Dragon Casts NEGLECT STRIKE!`\n\nOh no! Despite our best efforts, weve let some Dailies get away from us, and their dark-red colour has attracted the Dragons rage! With its fearsome Neglect Strike attack, it has decimated the Tavern! Luckily, weve set up an Inn in a nearby city, and youre free to keep chatting on the shore but poor Daniel the Barkeep just saw his beloved building crumble around him!\n\nI hope the beast doesnt attack again!",
"questDilatoryBossRageStables": "`Dread Dragon Casts NEGLECT STRIKE!`\n\nYikes! Once again we left too many Dailies undone. The Dragon has unleashed its Neglect Strike against Matt and the stables! Pets have been fleeing in all directions. Luckily it seems like all of yours are safe!\n\nPoor Habitica! I hope this doesnt happen again. Hurry and do all your tasks!",
"questDilatoryBossRageMarket": "`Dread Dragon Casts NEGLECT STRIKE!`\n\nAhhh!! Alex the Merchant just had his shop smashed to smithereens by the Dragons Neglect Strike! But it seems like were really wearing this beast down. I doubt it has enough energy for another strike.\n\nSo do not waver, Habitica! Lets drive this beast away from our shores!",
"questDilatoryCompletion": "`The Defeat Of The Dread DragOn Of Dilatory`\n\nWeve done it! With a final last roar, the Dread Dragon collapses and swims far, far away. Crowds of cheering Habiticans line the shores! Weve helped Matt, Daniel, and Alex rebuild their buildings. But whats this?\n\n`The Citizens Return!`\n\nNow that the Dragon has fled, thousands of sparkling colours are ascending through the sea. It is a rainbow swarm of Mantis Shrimp and among them, hundreds of merpeople!\n\nWe are the lost citizens of Dilatory! explains their leader, Manta, “When Dilatory sank, the Mantis Shrimp that lived in these waters used a spell to transform us into merpeople so that we could survive. But in its rage, the Dread Dragon trapped us all in the dark crevasse. We have been imprisoned there for hundreds of yearsbut now at last we are free to rebuild our city!\n\nAs a thank you, says his friend @Ottl, Please accept this Mantis Shrimp pet and Mantis Shrimp mount, as well as XP, gold, and our eternal gratitude.\n\n`Rewards`\n * Mantis Shrimp Pet\n * Mantis Shrimp Mount\n * Chocolate, Blue Candyfloss, Pink Candyfloss, Fish, Honey, Meat, Milk, Potato, Rotten Meat, Strawberry",
"questSeahorseText": "The Dilatory Derby",
"questSeahorseNotes": "It's Derby Day, and Habiticans from all over the continent have travelled to Dilatory to race their pet seahorses! Suddenly, a great splashing and snarling breaks out at the racetrack, and you hear Seahorse Keeper @Kiwibot shouting above the roar of the waves. \"The gathering of seahorses has attracted a fierce Sea Stallion!\" she cries. \"He's smashing through the stables and destroying the ancient track! Can anyone calm him down?\"",
"questSeahorseCompletion": "The now-tame Sea Stallion swims docilely to your side. \"Oh, look!\" Kiwibot says. \"He wants us to take care of his children.\" She gives you three eggs. \"Raise them well,\" she says. \"You're welcome at the Dilatory Derby any day!\"",
"questSeahorseNotes": "Its Derby Day, and Habiticans from all over the continent have travelled to Dilatory to race their pet seahorses! Suddenly, a great splashing and snarling breaks out at the racetrack, and you hear Seahorse Keeper @Kiwibot shouting above the roar of the waves. The gathering of seahorses has attracted a fierce Sea Stallion! she cries, “Hes smashing through the stables and destroying the ancient track! Can anyone calm him down?",
"questSeahorseCompletion": "The now-tame Sea Stallion swims docilely to your side. Oh, look! Kiwibot says, “He wants us to take care of his children. She gives you three eggs. Raise them well, she says, “You're welcome at the Dilatory Derby any day!",
"questSeahorseBoss": "Sea Stallion",
"questSeahorseDropSeahorseEgg": "Seahorse (Egg)",
"questSeahorseUnlockText": "Unlocks Seahorse Eggs for purchase in the Market",
"questGroupAtom": "Attack of the Mundane",
"questAtom1Text": "Attack of the Mundane, Part 1: Dish Disaster!",
"questAtom1Notes": "You reach the shores of Washed-Up Lake for some well-earned relaxation... But the lake is polluted with unwashed dishes! How did this happen? Well, you simply cannot allow the lake to be in this state. There is only one thing you can do: clean the dishes and save your vacation spot! Better find some soap to clean up this mess. A lot of soap...",
"questAtom1Notes": "You reach the shores of Washed-Up Lake for some well-earned relaxation But the lake is polluted with unwashed dishes! How did this happen? Well, you simply cannot allow the lake to be in this state. There is only one thing you can do: clean the dishes and save your vacation spot! Better find some soap to clean up this mess. A lot of soap",
"questAtom1CollectSoapBars": "Bars of Soap",
"questAtom1Drop": "The SnackLess Monster (Scroll)",
"questAtom1Completion": "After some thorough scrubbing, all the dishes are stacked safely on the shore! You stand back and proudly survey your hard work.",
"questAtom2Text": "Attack of the Mundane, Part 2: The SnackLess Monster",
"questAtom2Notes": "Phew, this place is looking a lot nicer with all these dishes cleaned. Maybe you can finally have some fun now. Oh - there seems to be a pizza box floating in the lake. Well, what's one more thing to clean really? But alas, it is no mere pizza box! With a sudden rush the box lifts from the water to reveal itself to be the head of a monster. It cannot be! The fabled SnackLess Monster?! It is said it has existed hidden in the lake since prehistoric times: a creature spawned from the leftover food and trash of the ancient Habiticans. Yuck!",
"questAtom2Notes": "Phew, this place is looking a lot nicer with all these dishes cleaned. Maybe you can finally have some fun now. Ohthere seems to be a pizza box floating in the lake. Well, whats one more thing to clean really? But alas, it is no mere pizza box! With a sudden rush the box lifts from the water to reveal itself to be the head of a monster. It cannot be! The fabled SnackLess Monster?! It is said it has existed hidden in the lake since prehistoric times: a creature spawned from the leftover food and rubbish of the ancient Habiticans. Yuck!",
"questAtom2Boss": "The SnackLess Monster",
"questAtom2Drop": "The Laundromancer (Scroll)",
"questAtom2Completion": "With a deafening cry, and five delicious types of cheese bursting from its mouth, the Snackless Monster falls to pieces. Well done, brave adventurer! But wait... is there something else wrong with the lake?",
"questAtom2Completion": "With a deafening cry, and five delicious types of cheese bursting from its mouth, the Snackless Monster falls to pieces. Well done, brave adventurer! But wait is there something else wrong with the lake?",
"questAtom3Text": "Attack of the Mundane, Part 3: The Laundromancer",
"questAtom3Notes": "Just when you thought that your trials had ended, Washed-Up Lake begins to froth violently. “HOW DARE YOU!” booms a voice from beneath the water's surface. A robed, blue figure emerges from the water, wielding a magic toilet brush. Filthy laundry begins to bubble up to the surface of the lake. \"I am the Laundromancer!\" he angrily announces. \"You have some nerve - washing my delightfully dirty dishes, destroying my pet, and entering my domain with such clean clothes. Prepare to feel the soggy wrath of my anti-laundry magic!\"",
"questAtom3Completion": "The wicked Laundromancer has been defeated! Clean laundry falls in piles all around you. Things are looking much better around here. As you begin to wade through the freshly pressed armour, a glint of metal catches your eye, and your gaze falls upon a gleaming helm. The original owner of this shining item may be unknown, but as you put it on, you feel the warming presence of a generous spirit. Too bad they didn't sew on a nametag.",
"questAtom3Notes": "Just when you thought that your trials had ended, Washed-Up Lake begins to froth violently. “HOW DARE YOU!” booms a voice from beneath the waters surface. A robed, blue figure emerges from the water, wielding a magic toilet brush. Filthy laundry begins to bubble up to the surface of the lake. I am the Laundromancer! he angrily announces, “You have some nervewashing my delightfully dirty dishes, destroying my pet, and entering my domain with such clean clothes. Prepare to feel the soggy wrath of my anti-laundry magic!",
"questAtom3Completion": "The wicked Laundromancer has been defeated! Clean laundry falls in piles all around you. Things are looking much better around here. As you begin to wade through the freshly pressed armour, a glint of metal catches your eye, and your gaze falls upon a gleaming helm. The original owner of this shining item may be unknown, but as you put it on, you feel the warming presence of a generous spirit. Too bad they didnt sew on a name tag.",
"questAtom3Boss": "The Laundromancer",
"questAtom3DropPotion": "Base Hatching Potion",
"questOwlText": "The Night-Owl",
"questOwlNotes": "The Tavern light is lit 'til dawn<br>Until one eve the glow is gone!<br>How can we see for our all-nighters?<br>@Twitching cries, \"I need some fighters!<br>See that Night-Owl, starry foe?<br>Fight with haste and do not slow!<br>We'll drive its shadow from our door,<br>And make the night shine bright once more!\"",
"questOwlCompletion": "The Night-Owl fades before the dawn,<br>But even so, you feel a yawn.<br>Perhaps it's time to get some rest?<br>Then on your bed, you see a nest!<br>A Night-Owl knows it can be great<br>To finish work and stay up late,<br>But your new pets will softly peep<br>To tell you when it's time to sleep.",
"questOwlNotes": "The Tavern light is lit til dawn<br>Until one eve the glow is gone!<br>How can we see for our all-nighters?<br>@Twitching cries, I need some fighters!<br>See that Night-Owl, starry foe?<br>Fight with haste and do not slow!<br>Well drive its shadow from our door,<br>And make the night shine bright once more!",
"questOwlCompletion": "The Night-Owl fades before the dawn,<br>But even so, you feel a yawn.<br>Perhaps its time to get some rest?<br>Then on your bed, you see a nest!<br>A Night-Owl knows it can be great<br>To finish work and stay up late,<br>But your new pets will softly peep<br>To tell you when its time to sleep.",
"questOwlBoss": "The Night-Owl",
"questOwlDropOwlEgg": "Owl (Egg)",
"questOwlUnlockText": "Unlocks Owl Eggs for purchase in the Market",
"questPenguinText": "The Fowl Frost",
"questPenguinNotes": "Although it's a hot summer day in the southernmost tip of Habitica, an unnatural chill has fallen upon Lively Lake. Strong, frigid winds rush around as the shore begins to freeze over. Ice spikes jut up from the ground, pushing grass and dirt away. @Melynnrose and @Breadstrings run up to you.<br><br>\"Help!\" says @Melynnrose. \"We brought a giant penguin in to freeze the lake so we could all go ice skating, but we ran out of fish to feed him!\"<br><br>\"He got angry and is using his freeze breath on everything he sees!\" says @Breadstrings. \"Please, you have to subdue him before all of us are covered in ice!\" Looks like you need this penguin to... <em>cool down.</em>",
"questPenguinCompletion": "Upon the penguin's defeat, the ice melts away. The giant penguin settles down in the sunshine, slurping up an extra bucket of fish you found. He skates off across the lake, blowing gently downwards to create smooth, sparkling ice. What an odd bird! \"It appears he left behind a few eggs, as well,\" says @Painter de Cluster. <br><br>@Rattify laughs. \"Maybe these penguins will be a little more... chill?\"",
"questPenguinNotes": "Although its a hot summer day in the southernmost tip of Habitica, an unnatural chill has fallen upon Lively Lake. Strong, frigid winds rush around as the shore begins to freeze over. Ice spikes jut up from the ground, pushing grass and dirt away. @Melynnrose and @Breadstrings run up to you.<br><br>Help! says @Melynnrose, “We brought a giant penguin in to freeze the lake so we could all go ice skating, but we ran out of fish to feed him!<br><br>He got angry and is using his freeze breath on everything he sees! says @Breadstrings, “Please, you have to subdue him before all of us are covered in ice! Looks like you need this penguin to <em>cool down</em>.",
"questPenguinCompletion": "Upon the penguins defeat, the ice melts away. The giant penguin settles down in the sunshine, slurping up an extra bucket of fish you found. He skates off across the lake, blowing gently downwards to create smooth, sparkling ice. What an odd bird! It appears he left behind a few eggs, as well, says @Painter de Cluster. <br><br>@Rattify laughs, “Maybe these penguins will be a little more chill?",
"questPenguinBoss": "Frost Penguin",
"questPenguinDropPenguinEgg": "Penguin (Egg)",
"questPenguinUnlockText": "Unlocks Penguin Eggs for purchase in the Market",
"questStressbeastText": "The Abominable Stressbeast of the Stoïkalm Steppes",
"questStressbeastNotes": "Complete Dailies and To Do's to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied.<br><br>~*~<br><br>The first thing we hear are the footsteps, slower and more thundering than the stampede. One by one, Habiticans look outside their doors, and words fail us.<br><br>We've all seen Stressbeasts before, of course - tiny vicious creatures that attack during difficult times. But this? This towers taller than the buildings, with paws that could crush a dragon with ease. Frost swings from its stinking fur, and as it roars, the icy blast rips the roofs off our houses. A monster of this magnitude has never been mentioned outside of distant legend.<br><br>\"Beware, Habiticans!\" SabreCat cries. \"Barricade yourselves indoors - this is the Abominable Stressbeast itself!\"<br><br>\"That thing must be made of centuries of stress!\" Kiwibot says, locking the Tavern door tightly and shuttering the windows.<br><br>\"The Stoïkalm Steppes,\" Lemoness says, face grim. \"All this time, we thought they were placid and untroubled, but they must have been secretly hiding their stress somewhere. Over generations, it grew into this, and now it's broken free and attacked them - and us!\"<br><br>There's only one way to drive away a Stressbeast, Abominable or otherwise, and that's to attack it with completed Dailies and To Do's! Let's all band together and fight off this fearsome foe - but be sure not to slack on your tasks, or our undone Dailies may enrage it so much that it lashes out...",
"questStressbeastNotes": "Complete Dailies and To Dos to damage the World Boss! Incomplete Dailies fill the Stress Strike Bar. When the Stress Strike bar is full, the World Boss will attack an NPC. A World Boss will never damage individual players or accounts in any way. Only active accounts who are not resting in the inn will have their incomplete Dailies tallied.<br><br>~*~<br><br>The first thing we hear are the footsteps, slower and more thundering than the stampede. One by one, Habiticans look outside their doors, and words fail us.<br><br>Weve all seen Stressbeasts before, of coursetiny vicious creatures that attack during difficult times. But this? This towers taller than the buildings, with paws that could crush a dragon with ease. Frost swings from its stinking fur and, as it roars, the icy blast rips the roofs off our houses. A monster of this magnitude has never been mentioned outside of distant legend.<br><br>Beware, Habiticans! SabreCat cries, “Barricade yourselves indoorsthis is the Abominable Stressbeast itself!<br><br>That thing must be made of centuries of stress! Kiwibot says, locking the Tavern door tightly and shuttering the windows.<br><br>The Stoïkalm Steppes, Lemoness says, face grim, “All this time, we thought they were placid and untroubled, but they must have been secretly hiding their stress somewhere. Over generations, it grew into this, and now its broken free and attacked themand us!<br><br>Theres only one way to drive away a Stressbeast, Abominable or otherwise, and thats to attack it with completed Dailies and To Dos! Lets all band together and fight off this fearsome foebut be sure not to slack on your tasks, or our undone Dailies may enrage it so much that it lashes out",
"questStressbeastBoss": "The Abominable Stressbeast",
"questStressbeastBossRageTitle": "Stress Strike",
"questStressbeastBossRageDescription": "When this gauge fills, the Abominable Stressbeast will unleash its Stress Strike on Habitica!",
"questStressbeastDropMammothPet": "Mammoth (Pet)",
"questStressbeastDropMammothMount": "Mammoth (Mount)",
"questStressbeastBossRageStables": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and their dark-red colour has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least it's distracted for the moment. Hurry! Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questStressbeastBossRageBailey": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nAhh!!! Our incomplete Dailies caused the Abominable Stressbeast to become madder than ever and regain some of its health! Bailey the Town Crier was shouting for citizens to get to safety, and now it has seized her in its other hand! Look at her, valiantly reporting on the news as the Stressbeast swings her around viciously... Let's be worthy of her bravery by being as productive as we can to save our NPCs!",
"questStressbeastBossRageGuide": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nLook out! Justin the Guide is trying to distract the Stressbeast by running around its ankles, yelling productivity tips! The Abominable Stressbeast is stomping madly, but it seems like we're really wearing this beast down. I doubt it has enough energy for another strike. Don't give up... we're so close to finishing it off!",
"questStressbeastDesperation": "`Abominable Stressbeast reaches 500K health! Abominable Stressbeast uses Desperate Defence!`\n\nWe're almost there, Habiticans! With diligence and Dailies, we've whittled the Stressbeast's health down to only 500K! The creature roars and flails in desperation, rage building faster than ever. Bailey and Matt yell in terror as it begins to swing them around at a terrifying pace, raising a blinding snowstorm that makes it harder to hit.\n\nWe'll have to redouble our efforts, but take heart - this is a sign that the Stressbeast knows it is about to be defeated. Don't give up now!",
"questStressbeastCompletion": "<strong>The Abominable Stressbeast is DEFEATED!</strong><br><br>We've done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!<br><br><strong>Stoïkalm is Saved!</strong><br><br>SabreCat speaks gently to a small sabertooth. \"Please find the citizens of the Stoïkalm Steppes and bring them to us,\" he says. Several hours later, the sabertooth returns, with a herd of mammoth riders following slowly behind. You recognise the head rider as Lady Glaciate, the leader of Stoïkalm.<br><br>\"Mighty Habiticans,\" she says, \"My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals.\" Her sad gaze follows the falling snow. \"We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.\"<br><br>She turns to where @Baconsaur is snuggling with some of the baby mammoths. \"We have brought your animals an offering of food to apologise for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.\"",
"questStressbeastCompletionChat": "`The Abominable Stressbeast is DEFEATED!`\n\nWe've done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!\n\n`Stoïkalm is Saved!`\n\nSabreCat speaks gently to a small sabertooth. \"Please find the citizens of the Stoïkalm Steppes and bring them to us,\" he says. Several hours later, the sabertooth returns, with a herd of mammoth riders following slowly behind. You recognise the head rider as Lady Glaciate, the leader of Stoïkalm.\n\n\"Mighty Habiticans,\" she says, \"My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals.\" Her sad gaze follows the falling snow. \"We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.\"\n\nShe turns to where @Baconsaur is snuggling with some of the baby mammoths. \"We have brought your animals an offering of food to apologise for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.\"",
"questStressbeastBossRageStables": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nOh no! Despite our best efforts, weve let some Dailies get away from us, and their dark-red colour has infuriated the Abominable Stressbeast and caused it to regain some of its health! The horrible creature lunges for the stables, but Matt the Beast Master heroically leaps into the fray to protect the pets and mounts. The Stressbeast has seized Matt in its vicious grip, but at least its distracted for the moment. Hurry! Lets keep our Dailies in check and defeat this monster before it attacks again!",
"questStressbeastBossRageBailey": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nAhh!!! Our incomplete Dailies caused the Abominable Stressbeast to become madder than ever and regain some of its health! Bailey the Town Crier was shouting for citizens to get to safety, and now it has seized her in its other hand! Look at her, valiantly reporting on the news as the Stressbeast swings her around viciously Lets be worthy of her bravery by being as productive as we can to save our NPCs!",
"questStressbeastBossRageGuide": "`Abominable Stressbeast uses STRESS STRIKE!`\n\nThe surge of stress heals Abominable Stressbeast!\n\nLook out! Justin the Guide is trying to distract the Stressbeast by running around its ankles, yelling productivity tips! The Abominable Stressbeast is stomping madly, but it seems like were really wearing this beast down. I doubt it has enough energy for another strike. Dont give up were so close to finishing it off!",
"questStressbeastDesperation": "`Abominable Stressbeast reaches 500K health! Abominable Stressbeast uses Desperate Defence!`\n\nWere almost there, Habiticans! With diligence and Dailies, weve whittled the Stressbeasts health down to only 500K! The creature roars and flails in desperation, rage building faster than ever. Bailey and Matt yell in terror as it begins to swing them around at a terrifying pace, raising a blinding snowstorm that makes it harder to hit.\n\nWell have to redouble our efforts, but take heartthis is a sign that the Stressbeast knows it is about to be defeated. Dont give up now!",
"questStressbeastCompletion": "<strong>The Abominable Stressbeast is DEFEATED!</strong><br><br>Weve done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!<br><br><strong>Stoïkalm is Saved!</strong><br><br>SabreCat speaks gently to a small sabretooth. Please find the citizens of the Stoïkalm Steppes and bring them to us, he says. Several hours later, the sabretooth returns, with a herd of mammoth riders following slowly behind. You recognise the head rider as Lady Glaciate, the leader of Stoïkalm.<br><br>Mighty Habiticans, she says, My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals. Her sad gaze follows the falling snow. We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.<br><br>She turns to where @Baconsaur is snuggling with some of the baby mammoths. We have brought your animals an offering of food to apologise for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.",
"questStressbeastCompletionChat": "`The Abominable Stressbeast is DEFEATED!`\n\nWeve done it! With a final bellow, the Abominable Stressbeast dissipates into a cloud of snow. The flakes twinkle down through the air as cheering Habiticans embrace their pets and mounts. Our animals and our NPCs are safe once more!\n\n`Stoïkalm is Saved!`\n\nSabreCat speaks gently to a small sabretooth. Please find the citizens of the Stoïkalm Steppes and bring them to us, he says. Several hours later, the sabretooth returns, with a herd of mammoth riders following slowly behind. You recognise the head rider as Lady Glaciate, the leader of Stoïkalm.\n\nMighty Habiticans, she says, My citizens and I owe you the deepest thanks, and the deepest apologies. In an effort to protect our Steppes from turmoil, we began to secretly banish all of our stress into the icy mountains. We had no idea that it would build up over generations into the Stressbeast that you saw! When it broke loose, it trapped all of us in the mountains in its stead and went on a rampage against our beloved animals. Her sad gaze follows the falling snow. We put everyone at risk with our foolishness. Rest assured that in the future, we will come to you with our problems before our problems come to you.\n\nShe turns to where @Baconsaur is snuggling with some of the baby mammoths. We have brought your animals an offering of food to apologise for frightening them, and as a symbol of trust, we will leave some of our pets and mounts with you. We know that you will all take care good care of them.",
"questTRexText": "King of the Dinosaurs",
"questTRexNotes": "Now that ancient creatures from the Stoïkalm Steppes are roaming throughout all of Habitica, @Urse has decided to adopt a full-grown Tyrannosaur. What could go wrong?<br><br>Everything.",
"questTRexCompletion": "The wild dinosaur finally stops its rampage and settles down to make friends with the giant roosters. @Urse beams down at it. \"They're not such terrible pets, after all! They just need a little discipline. Here, take some Tyrannosaur eggs for yourself.\"",
@@ -195,13 +195,13 @@
"questRockText": "Escape the Cave Creature",
"questRockNotes": "Crossing Habitica's Meandering Mountains with some friends, you make camp one night in a beautiful cave laced with shining minerals. But when you wake up the next morning, the entrance has disappeared and the floor of the cave is shifting underneath you.<br><br>\"The mountain's alive!\" shouts your companion @pfeffernusse. \"These aren't crystals—these are teeth!\"<br><br>@Painter de Cluster grabs your hand. \"We'll have to find another way out—stay with me and don't get distracted, or we could be trapped in here forever!\"",
"questRockBoss": "Crystal Colossus",
"questRockCompletion": "Your diligence has allowed you to find a safe path through the living mountain. Standing in the sunshine, your friend @intune notices something glinting on the ground by the cave's exit. You stoop to pick it up, and see that it's a small rock with a vein of gold running through it. Beside it are a number of other rocks with rather peculiar shapes. They almost look like... eggs?",
"questRockCompletion": "Your diligence has allowed you to find a safe path through the living mountain. Standing in the sunshine, your friend @intune notices something glinting on the ground by the caves exit. You stoop to pick it up, and see that its a small rock with a vein of gold running through it. Beside it are a number of other rocks with rather peculiar shapes. They almost look like eggs?",
"questRockDropRockEgg": "Rock (Egg)",
"questRockUnlockText": "Unlocks Rock Eggs for purchase in the Market",
"questBunnyText": "The Killer Bunny",
"questBunnyNotes": "After many difficult days, you reach the peak of Mount Procrastination and stand before the imposing doors of the Fortress of Neglect. You read the inscription in the stone. \"Inside resides the creature that embodies your greatest fears, the reason for your inaction. Knock and face your demon!\" You tremble, imagining the horror within and feel the urge to flee as you have done so many times before. @Draayder holds you back. \"Steady, my friend! The time has come at last. You must do this!\"<br><br>You knock and the doors swing inward. From within the gloom you hear a deafening roar, and you draw your weapon.",
"questBunnyBoss": "Killer Bunny",
"questBunnyCompletion": "With one final blow the killer rabbit sinks to the ground. A sparkly mist rises from her body as she shrinks down into a tiny bunny... nothing like the cruel beast you faced a moment before. Her nose twitches adorably and she hops away, leaving some eggs behind. @Gully laughs. \"Mount Procrastination has a way of making even the smallest challenges seem insurmountable. Let's gather these eggs and head for home.\"",
"questBunnyCompletion": "With one final blow the killer rabbit sinks to the ground. A sparkly mist rises from her body as she shrinks down into a tiny bunny nothing like the cruel beast you faced a moment before. Her nose twitches adorably and she hops away, leaving some eggs behind. @Gully laughs. Mount Procrastination has a way of making even the smallest challenges seem insurmountable. Lets gather these eggs and head for home.",
"questBunnyDropBunnyEgg": "Bunny (Egg)",
"questBunnyUnlockText": "Unlocks Bunny Eggs for purchase in the Market",
"questSlimeText": "The Jam Regent",
@@ -211,13 +211,13 @@
"questSlimeDropSlimeEgg": "Marshmallow Slime (Egg)",
"questSlimeUnlockText": "Unlocks Marshmallow Slime Eggs for purchase in the Market",
"questSheepText": "The Thunder Ram",
"questSheepNotes": "As you wander the rural Taskan countryside with friends, taking a \"quick break\" from your obligations, you find a cosy yarn shop. You are so absorbed in your procrastination that you hardly notice the ominous clouds creep over the horizon. \"I've got a ba-a-a-ad feeling about this weather,\" mutters @Misceo, and you look up. The stormy clouds are swirling together, and they look a lot like a... \"We don't have time for cloud-gazing!\" @starsystemic shouts. \"It's attacking!\" The Thunder Ram hurtles forward, slinging bolts of lightning right at you!",
"questSheepNotes": "As you wander the rural Taskan countryside with friends, taking a quick break from your obligations, you find a cosy yarn shop. You are so absorbed in your procrastination that you hardly notice the ominous clouds creep over the horizon. I've got a ba-a-a-ad feeling about this weather, mutters @Misceo, and you look up. The stormy clouds are swirling together, and they look a lot like a… “We dont have time for cloud-gazing! @starsystemic shouts, “Its attacking! The Thunder Ram hurtles forward, slinging bolts of lightning right at you!",
"questSheepBoss": "Thunder Ram",
"questSheepCompletion": "Impressed by your diligence, the Thunder Ram is drained of its fury. It launches three huge hailstones in your direction, and then fades away with a low rumble. Upon closer inspection, you discover that the hailstones are actually three fluffy eggs. You gather them up, and then stroll home under a blue sky.",
"questSheepDropSheepEgg": "Sheep (Egg)",
"questSheepUnlockText": "Unlocks Sheep Eggs for purchase in the Market",
"questKrakenText": "The Kraken of Inkomplete",
"questKrakenNotes": "It's a warm, sunny day as you sail across the Inkomplete Bay, but your thoughts are clouded with worries about everything that you still need to do. It seems that as soon as you finish one task, another crops up, and then another...<br><br>Suddenly, the boat gives a horrible jolt, and slimy tentacles burst out of the water on all sides! \"We're being attacked by the Kraken of Inkomplete!\" Wolvenhalo cries.<br><br>\"Quickly!\" Lemoness calls to you. \"Strike down as many tentacles and tasks as you can, before new ones can rise up to take their place!\"",
"questKrakenNotes": "Its a warm, sunny day as you sail across the Inkomplete Bay, but your thoughts are clouded with worries about everything that you still need to do. It seems that as soon as you finish one task, another crops up, and then another<br><br>Suddenly, the boat gives a horrible jolt, and slimy tentacles burst out of the water on all sides! Were being attacked by the Kraken of Inkomplete! Wolvenhalo cries.<br><br>Quickly! Lemoness calls to you, “Strike down as many tentacles and tasks as you can, before new ones can rise up to take their place!",
"questKrakenBoss": "The Kraken of Inkomplete",
"questKrakenCompletion": "As the Kraken flees, several eggs float to the surface of the water. Lemoness examines them, and her suspicion turns to delight. \"Cuttlefish eggs!\" she says. \"Here, take them as a reward for everything you've completed.\"",
"questKrakenDropCuttlefishEgg": "Cuttlefish (Egg)",
@@ -246,8 +246,8 @@
"questDilatoryDistress2DropCottonCandyBluePotion": "Candyfloss Blue Hatching Potion",
"questDilatoryDistress2DropHeadgear": "Fire Coral Circlet (Headgear)",
"questDilatoryDistress3Text": "Dilatory Distress, Part 3: Not a Mere Maid",
"questDilatoryDistress3Notes": "You follow the mantis shrimps deep into the Crevasse, and discover an underwater fortress. Princess Adva, escorted by more watery skulls, awaits you inside the main hall. \"My father has sent you, has he not? Tell him I refuse to return. I am content to stay here and practise my sorcery. Leave now, or you shall feel the wrath of the ocean's new queen!\" Adva seems very adamant, but as she speaks you notice a strange, ruby pendant on her neck glowing ominously... Perhaps her delusions would cease should you break it?",
"questDilatoryDistress3Completion": "Finally, you manage to pull the bewitched pendant from Adva's neck and throw it away. Adva clutches her head. \"Where am I? What happened here?\" After hearing your story, she frowns. \"This necklace was given to me by a strange ambassador - a lady called 'Tzina'. I don't remember anything after that!\"<br><br>Back at Dilatory, Manta is overjoyed by your success. \"Allow me to reward you with this trident and shield! I ordered them from @aiseant and @starsystemic as a gift for Adva, but... I'd rather not put weapons in her hands any time soon.\"",
"questDilatoryDistress3Notes": "You follow the mantis shrimps deep into the Crevasse, and discover an underwater fortress. Princess Adva, escorted by more watery skulls, awaits you inside the main hall. My father has sent you, has he not? Tell him I refuse to return. I am content to stay here and practise my sorcery. Leave now, or you shall feel the wrath of the oceans new queen! Adva seems very adamant, but as she speaks you notice a strange, ruby pendant on her neck glowing ominously Perhaps her delusions would cease should you break it?",
"questDilatoryDistress3Completion": "Finally, you manage to pull the bewitched pendant from Advas neck and throw it away. Adva clutches her head, “Where am I? What happened here? After hearing your story, she frowns, “This necklace was given to me by a strange ambassadora lady called Tzina. I don't remember anything after that!<br><br>Back at Dilatory, Manta is overjoyed by your success. Allow me to reward you with this trident and shield! I ordered them from @aiseant and @starsystemic as a gift for Adva, but Id rather not put weapons in her hands any time soon.",
"questDilatoryDistress3Boss": "Adva, the Usurping Mermaid",
"questDilatoryDistress3DropFish": "Fish (Food)",
"questDilatoryDistress3DropWeapon": "Trident of Crashing Tides (Weapon)",
@@ -259,15 +259,15 @@
"questCheetahDropCheetahEgg": "Cheetah (Egg)",
"questCheetahUnlockText": "Unlocks Cheetah Eggs for purchase in the Market",
"questHorseText": "Ride the Night-Mare",
"questHorseNotes": "While relaxing in the Tavern with @beffymaroo and @JessicaChase, the talk turns to good-natured boasting about your adventuring accomplishments. Proud of your deeds, and perhaps getting a bit carried away, you brag that you can tame any task around. A nearby stranger turns toward you and smiles. One eye twinkles as he invites you to prove your claim by riding his horse.\nAs you all head for the stables, @UncommonCriminal whispers, \"You may have bitten off more than you can chew. That's no horse - that's a Night-Mare!\" Looking at its stamping hooves, you begin to regret your words...",
"questHorseCompletion": "It takes all your skill, but finally the horse stamps a couple of hooves and nuzzles you in the shoulder before allowing you to mount. You ride briefly but proudly around the Tavern grounds while your friends cheer. The stranger breaks into a broad grin.\n\"I can see that was no idle boast! Your determination is truly impressive. Take these eggs to raise horses of your own, and perhaps we'll meet again one day.\" You take the eggs, the stranger tips his hat... and vanishes.",
"questHorseNotes": "While relaxing in the Tavern with @beffymaroo and @JessicaChase, the talk turns to good-natured boasting about your adventuring accomplishments. Proud of your deeds, and perhaps getting a bit carried away, you brag that you can tame any task around. A nearby stranger turns toward you and smiles. One eye twinkles as he invites you to prove your claim by riding his horse.\nAs you all head for the stables, @UncommonCriminal whispers, You may have bitten off more than you can chew. Thats no horsethats a Night-Mare! Looking at its stamping hooves, you begin to regret your words",
"questHorseCompletion": "It takes all your skill, but finally the horse stamps a couple of hooves and nuzzles you in the shoulder before allowing you to mount. You ride briefly but proudly around the Tavern grounds while your friends cheer. The stranger breaks into a broad grin.\nI can see that was no idle boast! Your determination is truly impressive. Take these eggs to raise horses of your own, and perhaps well meet again one day. You take the eggs, the stranger tips his hat and vanishes.",
"questHorseBoss": "Night-Mare",
"questHorseDropHorseEgg": "Horse (Egg)",
"questHorseUnlockText": "Unlocks Horse Eggs for purchase in the Market",
"questBurnoutText": "Burnout and the Exhaust Spirits",
"questBurnoutNotes": "It is well past midnight, still and stiflingly hot, when Redphoenix and scout captain Kiwibot abruptly burst through the city gates. \"We need to evacuate all the wooden buildings!\" Redphoenix shouts. \"Hurry!\"<br><br>Kiwibot grips the wall as she catches her breath. \"It's draining people and turning them into Exhaust Spirits! That's why everything was delayed. That's where the missing people have gone. It's been stealing their energy!\"<br><br>\"'It'?'\" asks Lemoness.<br><br>And then the heat takes form.<br><br>It rises from the earth in a billowing, twisting mass, and the air chokes with the scent of smoke and sulphur. Flames lick across the molten ground and contort into limbs, writhing to horrific heights. Smoldering eyes snap open, and the creature lets out a deep and crackling cackle.<br><br> Kiwibot whispers a single word.<br><br><em>\"Burnout.\"</em>",
"questBurnoutCompletion": "<strong>Burnout is DEFEATED!</strong><br><br>With a great, soft sigh, Burnout slowly releases the ardent energy that was fueling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.<br><br>Ian, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!<br><br>\"Look!\" whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!<br><br>One of the glowing birds alights on the Joyful Reaper's skeletal arm, and she grins at it. \"It has been a long time since I've had the exquisite privilege to behold a phoenix in the Flourishing Fields,\" she says. \"Although given recent occurrences, I must say, this is highly thematically appropriate!\"<br><br>Her tone sobers, although (naturally) her grin remains. \"We're known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly won't make the same mistake twice!\"<br><br>She claps her hands. \"Now - let's celebrate!\"",
"questBurnoutCompletionChat": "`Burnout is DEFEATED!`\n\nWith a great, soft sigh, Burnout slowly releases the ardent energy that was fueling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.\n\nIan, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!\n\n\"Look!\" whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!\n\nOne of the glowing birds alights on the Joyful Reaper's skeletal arm, and she grins at it. \"It has been a long time since I've had the exquisite privilege to behold a phoenix in the Flourishing Fields,\" she says. \"Although given recent occurrences, I must say, this is highly thematically appropriate!\"\n\nHer tone sobers, although (naturally) her grin remains. \"We're known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly won't make the same mistake twice!\"\n\nShe claps her hands. \"Now - let's celebrate!\"\n\nAll Habiticans receive:\n\nPhoenix Pet\nPhoenix Mount\nAchievement: Savior of the Flourishing Fields\nBasic Candy\nVanilla Candy\nSand Candy\nCinnamon Candy\nChocolate Candy\nRotten Candy\nSour Pink Candy\nSour Blue Candy\nHoney Candy",
"questBurnoutCompletion": "<strong>Burnout is DEFEATED!</strong><br><br>With a great, soft sigh, Burnout slowly releases the ardent energy that was fuelling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.<br><br>Ian, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!<br><br>Look! whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!<br><br>One of the glowing birds alights on the Joyful Reapers skeletal arm, and she grins at it. It has been a long time since Ive had the exquisite privilege to behold a phoenix in the Flourishing Fields, she says, “Although given recent occurrences, I must say, this is highly thematically appropriate!<br><br>Her tone sobers, although (naturally) her grin remains. Were known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly wont make the same mistake twice!<br><br>She claps her hands. Nowlets celebrate!",
"questBurnoutCompletionChat": "`Burnout is DEFEATED!`\n\nWith a great, soft sigh, Burnout slowly releases the ardent energy that was fuelling its fire. As the monster curls quietly into ashes, its stolen energy shimmers through the air, rejuvenating the Exhaust Spirits and returning them to their true forms.\n\nIan, Daniel, and the Seasonal Sorceress cheer as Habiticans rush to greet them, and all the missing citizens of the Flourishing Fields embrace their friends and families. The final Exhaust Spirit transforms into the Joyful Reaper herself!\n\nLook! whispers @Baconsaur, as the ashes begin to glitter. Slowly, they resolve into hundreds of shining phoenixes!\n\nOne of the glowing birds alights on the Joyful Reapers skeletal arm, and she grins at it. It has been a long time since Ive had the exquisite privilege to behold a phoenix in the Flourishing Fields, she says, “Although given recent occurrences, I must say, this is highly thematically appropriate!\n\nHer tone sobers, although (naturally) her grin remains. Were known for being hard-working here, but we are also known for our feasts and festivities. Rather ironic, I suppose, that as we strove to plan a spectacular party, we refused to permit ourselves any time for fun. We certainly wont make the same mistake twice!\n\nShe claps her hands. Nowlets celebrate!\n\nAll Habiticans receive:\n\nPhoenix Pet\nPhoenix Mount\nAchievement: Saviour of the Flourishing Fields\nBasic Sweet\nVanilla Sweet\nSand Sweet\nCinnamon Sweet\nChocolate Sweet\nRotten Sweet\nSour Pink Sweet\nSour Blue Sweet\nHoney Sweet",
"questBurnoutBoss": "Burnout",
"questBurnoutBossRageTitle": "Exhaust Strike",
"questBurnoutBossRageDescription": "When this gauge fills, Burnout will unleash its Exhaust Strike on Habitica!",
@@ -275,7 +275,7 @@
"questBurnoutDropPhoenixMount": "Phoenix (Mount)",
"questBurnoutBossRageQuests": "`Burnout uses EXHAUST STRIKE!`\n\nOh no! Despite our best efforts, we've let some Dailies get away from us, and now Burnout is inflamed with energy! With a crackling snarl, it engulfs Ian the Quest Master in a surge of spectral fire. As fallen quest scrolls smoulder, the smoke clears, and you see that Ian has been drained of energy and turned into a drifting Exhaust Spirit!\n\nOnly defeating Burnout can break the spell and restore our beloved Quest Master. Let's keep our Dailies in check and defeat this monster before it attacks again!",
"questBurnoutBossRageSeasonalShop": "`Burnout uses EXHAUST STRIKE!`\n\nAhh!!! Our incomplete Dailies have fed the flames of Burnout, and now it has enough energy to strike again! It lets loose a gout of spectral flame that sears the Seasonal Shop. You're horrified to see that the cheery Seasonal Sorceress has been transformed into a drooping Exhaust Spirit.\n\nWe have to rescue our NPCs! Hurry, Habiticans, complete your tasks and defeat Burnout before it strikes for a third time!",
"questBurnoutBossRageTavern": "`Burnout uses EXHAUST STRIKE!`\n\nMany Habiticans have been hiding from Burnout in the Tavern, but no longer! With a screeching howl, Burnout rakes the Tavern with its white-hot hands. As the Tavern patrons flee, Daniel is caught in Burnout's grip, and transforms into an Exhaust Spirit right in front of you!\n\nThis hot-headed horror has gone on for too long. Don't give up... we're so close to vanquishing Burnout for once and for all!",
"questBurnoutBossRageTavern": "`Burnout uses EXHAUST STRIKE!`\n\nMany Habiticans have been hiding from Burnout in the Tavern, but no longer! With a screeching howl, Burnout rakes the Tavern with its white-hot hands. As the Tavern patrons flee, Daniel is caught in Burnouts grip, and transforms into an Exhaust Spirit right in front of you!\n\nThis hot-headed horror has gone on for too long. Dont give up were so close to vanquishing Burnout once and for all!",
"questFrogText": "Swamp of the Clutter Frog",
"questFrogNotes": "As you and your friends are slogging through the Swamps of Stagnation, @starsystemic points at a large sign. \"Stay on the path—if you can.\"<br><br>\"Surely that isn't hard!\" @RosemonkeyCT says. \"It's broad and clear.\"<br><br>But as you continue, you notice that path is gradually overtaken by the muck of the swamp, laced with bits of strange blue debris and clutter, until it's impossible to proceed.<br><br>As you look around, wondering how it got this messy, @Jon Arjinborn shouts, \"Look out!\" An angry frog leaps from the sludge, clad in dirty laundry and lit by blue fire. You will have to overcome this poisonous Clutter Frog to progress!",
"questFrogCompletion": "The frog cowers back into the muck, defeated. As it slinks away, the blue slime fades, leaving the way ahead clear.<br><br>Sitting in the middle of the path are three pristine eggs. \"You can even see the tiny tadpoles through the clear casing!\" @Breadstrings says. \"Here, you should take them.\"",
@@ -284,7 +284,7 @@
"questFrogUnlockText": "Unlocks Frog Eggs for purchase in the Market",
"questSnakeText": "The Serpent of Distraction",
"questSnakeNotes": "It takes a hardy soul to live in the Sand Dunes of Distraction. The arid desert is hardly a productive place, and the shimmering dunes have led many a traveller astray. However, something has even the locals spooked. The sands have been shifting and upturning entire villages. Residents claim a monster with an enormous serpentine body lies in wait under the sands, and they have all pooled together a reward for whomever will help them find and stop it. The much-lauded snake charmers @EmeraldOx and @PainterProphet have agreed to help you summon the beast. Can you stop the Serpent of Distraction?",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you can't help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. \"Thank you! It's not much, but I hope this can express our gratitude properly.\" He hands you some Gold and... some Snake eggs! You will see that majestic animal again after all.",
"questSnakeCompletion": "With assistance from the charmers, you banish the Serpent of Distraction. Though you were happy to help the inhabitants of the Dunes, you cant help but feel a little sad for your fallen foe. While you contemplate the sights, @LordDarkly approaches you. Thank you! Its not much, but I hope this can express our gratitude properly. He hands you some Gold and some Snake eggs! You will see that majestic animal again after all.",
"questSnakeBoss": "Serpent of Distraction",
"questSnakeDropSnakeEgg": "Snake (Egg)",
"questSnakeUnlockText": "Unlocks Snake Eggs for purchase in the Market",
@@ -295,34 +295,34 @@
"questUnicornDropUnicornEgg": "Unicorn (Egg)",
"questUnicornUnlockText": "Unlocks Unicorn Eggs for purchase 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. @InspectorCaracal and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoïkalm 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!",
"questSabretoothNotes": "A roaring monster is terrorising Habitica! The creature stalks through the wilds and woods, then bursts forth to attack before vanishing again. Its been hunting innocent pandas and frightening the flying pigs into fleeing their pens to roost in the trees. @InspectorCaracal and @icefelis explain that the Zombie Sabre Cat was set free while they were excavating in the ancient, untouched ice-fields of the Stoïkalm Steppes. It was perfectly friendly at firstI dont 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 cats wrath, youre 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 rewarda clutch of sabretooth eggs!",
"questSabretoothBoss": "Zombie Sabre Cat",
"questSabretoothDropSabretoothEgg": "Sabretooth (Egg)",
"questSabretoothUnlockText": "Unlocks Sabretooth Eggs for purchase in the Market",
"questMonkeyText": "Monstrous Mandrill and the Mischief Monkeys",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behavior. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!<br><br>\"It will take a dedicated adventurer to resist them,\" says @yamato.<br><br>\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyNotes": "The Sloensteadi Savannah is being torn apart by the Monstrous Mandrill and his Mischief Monkeys! They shriek loudly enough to drown out the sound of approaching deadlines, encouraging everyone to avoid their duties and keep monkeying around. Alas, plenty of people ape this bad behaviour. If no one stops these primates, soon everyone's tasks will be as red as the Monstrous Mandrill's face!<br><br>\"It will take a dedicated adventurer to resist them,\" says @yamato.<br><br>\"Quick, let's get this monkey off everyone's backs!\" @Oneironaut yells, and you charge into battle.",
"questMonkeyCompletion": "You did it! No bananas for those fiends today. Overwhelmed by your diligence, the monkeys flee in panic. \"Look,\" says @Misceo. \"They left a few eggs behind.\"<br><br>@Leephon grins. \"Maybe a well-trained pet monkey can help you as much as the wild ones hinder you!\"",
"questMonkeyBoss": "Monstrous Mandrill",
"questMonkeyDropMonkeyEgg": "Monkey (Egg)",
"questMonkeyUnlockText": "Unlocks Monkey Eggs for purchase in the Market",
"questSnailText": "The Snail of Drudgery Sludge",
"questSnailNotes": "You're excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, \"They have too many unimportant tasks and dailies, and they're getting stuck on things that don't matter! Pull them out!\"<br><br>\"You need to find the source of the ooze,\" @Pfeffernusse agrees, \"or the tasks that they cannot accomplish will drag them down forever!\"<br><br>Pulling out your weapon, you wade through the gooey mud.... and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailNotes": "Youre excited to begin questing in the abandoned Dungeons of Drudgery, but as soon as you enter, you feel the ground under your feet start to suck at your boots. You look up to the path ahead and see Habiticans mired in slime. @Overomega yells, They have too many unimportant tasks and dailies, and theyre getting stuck on things that dont matter! Pull them out!<br><br>You need to find the source of the ooze, @Pfeffernusse agrees, “Or the tasks that they cannot accomplish will drag them down forever!<br><br>Pulling out your weapon, you wade through the gooey mud and encounter the fearsome Snail of Drudgery Sludge.",
"questSnailCompletion": "You bring your weapon down on the great Snail's shell, cracking it in two, releasing a flood of water. The slime is washed away, and the Habiticans around you rejoice. \"Look!\" says @Misceo. \"There's a small group of snail eggs in the remnants of the muck.\"",
"questSnailBoss": "Snail of Drudgery Sludge",
"questSnailDropSnailEgg": "Snail (Egg)",
"questSnailUnlockText": "Unlocks Snail Eggs for purchase in the Market",
"questBewilderText": "The Be-Wilder",
"questBewilderNotes": "The party begins like any other.<br><br>The appetisers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centrepieces, happy to have a distraction from their least-favourite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.<br><br>As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.<br><br>“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.<br><br>“As you know,” the Fool continues, “my confusing illusions usually only last a single day. But Im pleased to announce that Ive discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend... the Be-Wilder!”<br><br>Lemoness pales suddenly, dropping her hors d'oeuvres. “Wait! Dont trust—”<br><br>But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as a monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.<br><br>“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”<br><br>Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.<br><br>“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “Theres no need to toil for your rewards any more. Ill just give you all the things that you desire!”<br><br>A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.<br><br>PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I dont think it is!”<br><br>Quickly, Habiticans, dont let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying—and hopefully, ourselves.",
"questBewilderCompletion": "<strong>The Be-Wilder is DEFEATED!</strong><br><br>We've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.<br><br><strong>Mistiflying is saved!</strong><br><br>The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”<br><br>The crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.<br><br>“Er, yes,” the April Fool says. “That is. What I meant to say was, Im dreadfully sorry.” He heaves a sigh. “I suppose it cant all be fun and games, after all. It might not hurt to focus occasionally. Maybe Ill get a head start on next years pranking.”<br><br>Redphoenix coughs meaningfully.<br><br>“I mean, get a head start on this years spring cleaning!” the April Fool says. “Nothing to fear, Ill have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”<br><br>Encouraged, the marching band starts up.<br><br>It isnt long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.<br><br>As Habiticans cuddle the magical fuzzy bees, the April Fools eyes light up. “Oho, Ive had a thought! Why dont you all keep some of these fuzzy Bee Pets and Mounts? Its a gift that perfectly symbolises the balance between hard work and sweet rewards, if Im going to get all boring and allegorical on you.” He winks. “Besides, they dont have stingers! Fools honour.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWe've done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex.... and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little…. carried away.”\n\nThe crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says. “That is. What I meant to say was, Im dreadfully sorry.” He heaves a sigh. “I suppose it cant all be fun and games, after all. It might not hurt to focus occasionally. Maybe Ill get a head start on next years pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this years spring cleaning!” the April Fool says. “Nothing to fear, Ill have Habit City in spit-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isnt long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fools eyes light up. “Oho, Ive had a thought! Why dont you all keep some of these fuzzy Bee Pets and Mounts? Its a gift that perfectly symbolises the balance between hard work and sweet rewards, if Im going to get all boring and allegorical on you.” He winks. “Besides, they dont have stingers! Fools honour.”",
"questBewilderNotes": "The party begins like any other.<br><br>The appetisers are excellent, the music is swinging, and even the dancing elephants have become routine. Habiticans laugh and frolic amid the overflowing floral centrepieces, happy to have a distraction from their least-favourite tasks, and the April Fool whirls among them, eagerly providing an amusing trick here and a witty twist there.<br><br>As the Mistiflying clock tower strikes midnight, the April Fool leaps onto the stage to give a speech.<br><br>“Friends! Enemies! Tolerant acquaintances! Lend me your ears.” The crowd chuckles as animal ears sprout from their heads, and they pose with their new accessories.<br><br>“As you know,” the Fool continues, “My confusing illusions usually only last a single day. But Im pleased to announce that Ive discovered a shortcut that will guarantee us non-stop fun, without having to deal with the pesky weight of our responsibilities. Charming Habiticans, meet my magical new friend the Be-Wilder!”<br><br>Lemoness pales suddenly, dropping her hors doeuvres. “Wait! Dont trust—”<br><br>But suddenly mists are pouring into the room, glittering and thick, and they swirl around the April Fool, coalescing into cloudy feathers and a stretching neck. The crowd is speechless as a monstrous bird unfolds before them, its wings shimmering with illusions. It lets out a horrible screeching laugh.<br><br>“Oh, it has been ages since a Habitican has been foolish enough to summon me! How wonderful it feels, to have a tangible form at last.”<br><br>Buzzing in terror, the magic bees of Mistiflying flee the floating city, which sags from the sky. One by one, the brilliant spring flowers wither up and wisp away.<br><br>“My dearest friends, why so alarmed?” crows the Be-Wilder, beating its wings. “Theres no need to toil for your rewards any more. Ill just give you all the things that you desire!”<br><br>A rain of coins pours from the sky, hammering into the ground with brutal force, and the crowd screams and flees for cover. “Is this a joke?” Baconsaur shouts, as the gold smashes through windows and shatters roof shingles.<br><br>PainterProphet ducks as lightning bolts crackle overhead, and fog blots out the sun. “No! This time, I dont think it is!”<br><br>Quickly, Habiticans, dont let this World Boss distract us from our goals! Stay focused on the tasks that you need to complete so we can rescue Mistiflying—and hopefully, ourselves.",
"questBewilderCompletion": "<strong>The Be-Wilder is DEFEATED!</strong><br><br>Weve done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex and the April Fool himself.<br><br><strong>Mistiflying is saved!</strong><br><br>The April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says, “Perhaps I got a little… carried away.”<br><br>The crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.<br><br>“Er, yes,” the April Fool says, “That is. What I meant to say was, Im dreadfully sorry.” He heaves a sigh, “I suppose it cant all be fun and games, after all. It might not hurt to focus occasionally. Maybe Ill get a head start on next years pranking.”<br><br>Redphoenix coughs meaningfully.<br><br>“I mean, get a head start on this years spring cleaning!” the April Fool says, “Nothing to fear, Ill have Habit City ship-shape soon. Luckily nobody is better than I at dual-wielding mops.”<br><br>Encouraged, the marching band starts up.<br><br>It isnt long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.<br><br>As Habiticans cuddle the magical fuzzy bees, the April Fools eyes light up. “Oho, Ive had a thought! Why dont you all keep some of these fuzzy Bee Pets and Mounts? Its a gift that perfectly symbolises the balance between hard work and sweet rewards, if Im going to get all boring and allegorical on you.” He winks, “Besides, they dont have stingers! Fools honour.”",
"questBewilderCompletionChat": "`The Be-Wilder is DEFEATED!`\n\nWeve done it! The Be-Wilder lets out a ululating cry as it twists in the air, shedding feathers like falling rain. Slowly, gradually, it coils into a cloud of sparkling mist. As the newly-revealed sun pierces the fog, it burns away, revealing the coughing, mercifully human forms of Bailey, Matt, Alex and the April Fool himself.\n\n`Mistiflying is saved!`\n\nThe April Fool has enough shame to look a bit sheepish. “Oh, hm,” he says. “Perhaps I got a little… carried away.”\n\nThe crowd mutters. Sodden flowers wash up on pavements. Somewhere in the distance, a roof collapses with a spectacular splash.\n\n“Er, yes,” the April Fool says, “That is. What I meant to say was, Im dreadfully sorry.” He heaves a sigh, “I suppose it cant all be fun and games, after all. It might not hurt to focus occasionally. Maybe Ill get a head start on next years pranking.”\n\nRedphoenix coughs meaningfully.\n\n“I mean, get a head start on this years spring cleaning!” the April Fool says, “Nothing to fear, Ill have Habit City ship-shape soon. Luckily nobody is better than I at dual-wielding mops.”\n\nEncouraged, the marching band starts up.\n\nIt isnt long before all is back to normal in Habit City. Plus, now that the Be-Wilder has evaporated, the magical bees of Mistiflying bustle back to work, and soon the flowers are blooming and the city is floating once more.\n\nAs Habiticans cuddle the magical fuzzy bees, the April Fools eyes light up. “Oho, Ive had a thought! Why dont you all keep some of these fuzzy Bee Pets and Mounts? Its a gift that perfectly symbolises the balance between hard work and sweet rewards, if Im going to get all boring and allegorical on you.” He winks. “Besides, they dont have stingers! Fools honour.”",
"questBewilderBossRageTitle": "Beguilement Strike",
"questBewilderBossRageDescription": "When this gauge fills, The Be-Wilder will unleash its Beguilement Strike on Habitica!",
"questBewilderDropBumblebeePet": "Magical Bee (Pet)",
"questBewilderDropBumblebeeMount": "Magical Bee (Mount)",
"questBewilderBossRageMarket": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nOh no! Despite our best efforts, we've gotten distracted by the Be-Wilders charming illusions and have forgotten to do some of our Dailies! With a cackling cry, the shining bird beats its wings, raising a swarm of mist around Alex the Merchant. When the fog clears, he has been possessed! “Have some free samples!” he shouts gleefully, and begins to hurl exploding eggs and potions at fleeing Habiticans. Not the most favourable of sales, to be sure.\n\nHurry! Let's stay focused on our Dailies to defeat this monster before it possesses someone else.",
"questBewilderBossRageStables": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nAhh!!! Once again the Be-Wilder has dazzled us into neglecting our Dailies, and now it has attacked Matt the Beast Master! With a swirl of mist, Matt transforms into a terrifying winged creature, and all the pets and mounts howl sadly in their stables. Quickly, stay focused on your tasks to defeat this dastardly distraction!",
"questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know whats going on?\n\nDon't give up... we're so close to defeating this bothersome bird for once and for all!",
"questBewilderBossRageBailey": "`The Be-Wilder uses BEGUILEMENT STRIKE!`\n\nLook out! In the middle of reporting the news, Bailey the Town Crier has been possessed by the Be-Wilder! She lets out an evil, uninformative screech as she rises into the air. Now how will we know whats going on?\n\nDont give up were so close to defeating this bothersome bird for once and for all!",
"questFalconText": "The Birds of Preycrastination",
"questFalconNotes": "Mt. Habitica is being overshadowed by a looming mountain of To Do's. It used to be a place to picnic and enjoy a sense of accomplishment, until the neglected tasks grew out of control. Now it's home to fearsome Birds of Preycrastination, foul creatures which stop Habiticans from completing their tasks!<br><br>\"It's too hard!\" they caw at @JonArinbjorn and @Onheiron. \"It'll take too long to do right now! It won't make any difference if you wait until tomorrow! Why don't you do something fun instead?\"<br><br>No more, you vow. You will climb your personal mountain of To Do's and defeat the Birds of Preycrastination!",
"questFalconCompletion": "Having finally triumphed over the Birds of Preycrastination, you settle down to enjoy the view and your well-earned rest.<br><br>\"Wow!\" says @Trogdorina. \"You won!\"<br><br>@Squish adds, \"Here, take these eggs I found as a reward.\"",
@@ -330,13 +330,13 @@
"questFalconDropFalconEgg": "Falcon (Egg)",
"questFalconUnlockText": "Unlocks Falcon Eggs for purchase in the Market",
"questTreelingText": "The Tangle Tree",
"questTreelingNotes": "It's the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same time until the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you aren't afraid you leap forward, ready to do battle.",
"questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safe although the tree you just reduced to a heap of wood chips won't be winning any prizes! \"Still a few kinks to work out there,\" @PainterProphet says. \"Perhaps someone else would do a better job of training the saplings. Do you fancy a go?\"",
"questTreelingNotes": "Its the annual Garden Competition, and everyone is talking about the mysterious project which @aurakami has promised to unveil. You join the crowd on the day of the big announcement, and marvel at the introduction of a moving tree. @fuzzytrees explains that the tree will help with garden maintenance, showing how it can mow the lawn, trim the hedge and prune the roses all at the same timeuntil the tree suddenly goes wild, turning its secateurs on its creator! The crowd panics as everyone tries to flee, but you arent afraidyou leap forward, ready to do battle.",
"questTreelingCompletion": "You dust yourself off as the last few leaves drift to the floor. In spite of the upset, the Garden Competition is now safealthough the tree you just reduced to a heap of wood chips wont be winning any prizes! Still a few kinks to work out there, @PainterProphet says, “Perhaps someone else would do a better job of training the saplings. Do you fancy a go?",
"questTreelingBoss": "Tangle Tree",
"questTreelingDropTreelingEgg": "Treeling (Egg)",
"questTreelingUnlockText": "Unlocks Treeling Eggs for purchase in the Market",
"questAxolotlText": "The Magical Axolotl",
"questAxolotlNotes": "From the depths of Washed-Up Lake you see rising bubbles and... fire? A little axolotl rises from the murky water spewing streaks of colours. Suddenly it begins to open its mouth and @streak yells, \"Look out!\" as the Magical Axolotl starts to gulp up your willpower!<br><br>The Magical Axolotl swells with spells, taunting you. \"Have you heard of my powers of regeneration? You'll tire before I do!\"<br><br>\"We can defeat you with the good habits we've built!\" @PainterProphet defiantly shouts. You steel yourself to be productive to defeat the Magical Axolotl and regain your stolen willpower!",
"questAxolotlNotes": "From the depths of Washed-Up Lake you see rising bubbles and fire? A little axolotl rises from the murky water spewing streaks of colours. Suddenly it begins to open its mouth and @streak yells, Look out! as the Magical Axolotl starts to gulp up your willpower!<br><br>The Magical Axolotl swells with spells, taunting you, “Have you heard of my powers of regeneration? Youll tire before I do!<br><br>We can defeat you with the good habits weve built! @PainterProphet defiantly shouts. You steel yourself to be productive to defeat the Magical Axolotl and regain your stolen willpower!",
"questAxolotlCompletion": "After defeating the Magical Axolotl, you realise that you regained your willpower all on your own.<br><br>\"The willpower? The regeneration? It was all just an illusion?\" @Kiwibot asks.<br><br>\"Most magic is,\" the Magical Axolotl replies. \"I'm sorry for tricking you. Please take these eggs as an apology. I trust you to raise them to use their magic for good habits and not evil!\"<br><br>You and @hazel40 clutch your new eggs in one hand and wave goodbye with the other as the Magical Axolotl returns to the lake.",
"questAxolotlBoss": "Magical Axolotl",
"questAxolotlDropAxolotlEgg": "Axolotl (Egg)",
@@ -363,7 +363,7 @@
"questCowDropCowEgg": "Cow (Egg)",
"questCowUnlockText": "Unlocks Cow Eggs for purchase in the Market",
"questBeetleText": "The CRITICAL BUG",
"questBeetleNotes": "Something in the domain of Habitica has gone awry. The Blacksmiths' forges have extinguished, and strange errors are appearing everywhere. With an ominous tremor, an insidious foe worms from the earth... a CRITICAL BUG! You brace yourself as it infects the land, and glitches begin to overtake the Habiticans around you. @starsystemic yells, \"We need to help the Blacksmiths get this Bug under control!\" It looks like you'll have to make this programmer's pest your top priority.",
"questBeetleNotes": "Something in the domain of Habitica has gone awry. The Blacksmiths forges have extinguished, and strange errors are appearing everywhere. With an ominous tremor, an insidious foe worms from the earth a CRITICAL BUG! You brace yourself as it infects the land, and glitches begin to overtake the Habiticans around you. @starsystemic yells, We need to help the Blacksmiths get this Bug under control! It looks like youll have to make this programmers pest your top priority.",
"questBeetleCompletion": "With a final attack, you crush the CRITICAL BUG. @starsystemic and the Blacksmiths rush up to you, overjoyed. \"I can't thank you enough for smashing that bug! Here, take these.\" You are presented with three shiny beetle eggs. Hopefully these little bugs will grow up to help Habitica, not hurt it.",
"questBeetleBoss": "CRITICAL BUG",
"questBeetleDropBeetleEgg": "Beetle (Egg)",
@@ -388,19 +388,19 @@
"questTaskwoodsTerror2DropArmor": "Pyromancer's Robes (Armour)",
"questTaskwoodsTerror3Text": "Terror in the Taskwoods, Part 3: Jacko of the Lantern",
"questTaskwoodsTerror3Notes": "Ready for battle, your group marches to the heart of the forest, where the renegade spirit is trying to destroy an ancient apple tree surrounded by fruitful berry bushes. His pumpkin-like head radiates a terrible light wherever it turns, and in his left hand he holds a long rod, with a lantern hanging from its tip. Instead of fire or flame, however, the lantern contains a dark crystal that chills you to the very bone.<br><br>The Joyful Reaper raises a bony hand to her mouth. \"That's—that's Jacko, the Lantern Spirit! But he's a helpful harvest ghost who guides our farmers. What could possibly drive the dear soul to act this way?\"<br><br>\"I don't know,\" says @bridgetteempress. \"But it looks like that 'dear soul' is about to attack us!\"",
"questTaskwoodsTerror3Completion": "After a long battle, you manage to land a well-aimed blow at the lantern that Jacko carries, and the crystal within shatters. Jacko suddenly snaps back to his senses and bursts into glowing tears. \"Oh, my beautiful forest! What have I done?!\" he wails. His tears extinguish the remaining fires, and the apple tree and wild berries are saved.<br><br>After you help him relax, he explains, \"I met this charming lady named Tzina, and she gave me this glowing crystal as a gift. At her urging, I put it in my lantern... but that's the last thing I recall.\" He turns to you with a golden smile. \"Perhaps you should take it for safekeeping while I help the wild orchards to regrow.\"",
"questTaskwoodsTerror3Completion": "After a long battle, you manage to land a well-aimed blow at the lantern that Jacko carries, and the crystal within shatters. Jacko suddenly snaps back to his senses and bursts into glowing tears. Oh, my beautiful forest! What have I done?! he wails. His tears extinguish the remaining fires, and the apple tree and wild berries are saved.<br><br>After you help him relax, he explains, I met this charming lady named Tzina, and she gave me this glowing crystal as a gift. At her urging, I put it in my lantern but thats the last thing I recall. He turns to you with a golden smile, “Perhaps you should take it for safekeeping while I help the wild orchards to regrow.",
"questTaskwoodsTerror3Boss": "Jacko of the Lantern",
"questTaskwoodsTerror3DropStrawberry": "Strawberry (Food)",
"questTaskwoodsTerror3DropWeapon": "Taskwoods Lantern (Two-Handed Weapon)",
"questFerretText": "The Nefarious Ferret",
"questFerretNotes": "Walking through Habit City, you see an unhappy crowd surrounding a red-robed Ferret.<br><br>\"That productivity potion you sold me is useless!\" @Beffymaroo complains. \"I watched three hours of TV last night instead of doing my chores!\"<br><br>\"Yeah!\" shouts @Pandah. \"And today I spent an hour rearranging my books instead of reading them!\"<br><br>The Nefarious Ferret spreads his hands innocently. \"That's more TV watching and book organizing than you'd normally get done, isn't it?\"<br><br>The crowd erupts in anger.<br><br>\"No refunds!\" crows the Nefarious Ferret. He fires a bolt of magic into the crowd, preparing to escape in the smoke.<br><br>\"Please, Habitican!\" @Faye says, grabbing your arm. \"Defeat the ferret and make him refund his dishonest earnings!\"",
"questFerretNotes": "Walking through Habit City, you see an unhappy crowd surrounding a red-robed Ferret.<br><br>That productivity potion you sold me is useless! @Beffymaroo complains, “I watched three hours of TV last night instead of doing my chores!<br><br>Yeah! shouts @Pandah, “And today I spent an hour rearranging my books instead of reading them!<br><br>The Nefarious Ferret spreads his hands innocently. Thats more TV watching and book organising than youd normally get done, isnt it?<br><br>The crowd erupts in anger.<br><br>No refunds! crows the Nefarious Ferret. He shoots a bolt of magic into the crowd, preparing to escape in the smoke.<br><br>Please, Habitican! @Faye says, grabbing your arm, “Defeat the ferret and make him refund his dishonest earnings!",
"questFerretCompletion": "You defeat the soft-furred swindler and @UncommonCriminal gives the crowd their refunds. There's even a little gold left over for you. Plus, it looks like the Nefarious Ferret dropped some eggs in his hurry to get away!",
"questFerretBoss": "Nefarious Ferret",
"questFerretDropFerretEgg": "Ferret (Egg)",
"questFerretUnlockText": "Unlocks Ferret Eggs for purchase in the Market",
"questDustBunniesText": "The Feral Dust Bunnies",
"questDustBunniesNotes": "It's been a while since you've done any dusting in here, but you're not too worried—a little dust never hurt anyone, right? It's not until you stick your hand into one of the dustiest corners and feel something bite that you remember @InspectorCaracal's warning: leaving harmless dust sit too long causes it to turn into vicious dust bunnies! You'd better defeat them before they cover all of Habitica in fine particles of dirt!",
"questDustBunniesCompletion": "The dust bunnies vanish into a puff of... well, dust. As it clears, you look around. You'd forgotten how nice this place looks when it's clean. You spy a small pile of gold where the dust used to be. Huh, you'd been wondering where that was!",
"questDustBunniesCompletion": "The dust bunnies vanish into a puff of well, dust. As it clears, you look around. Youd forgotten how nice this place looks when its clean. You spy a small pile of gold where the dust used to be. Huh, youd been wondering where that was!",
"questDustBunniesBoss": "Feral Dust Bunnies",
"questGroupMoon": "Lunar Battle",
"questMoon1Text": "Lunar Battle, Part 1: Find the Mysterious Shards",
@@ -409,7 +409,7 @@
"questMoon1CollectShards": "Lunar Shards",
"questMoon1DropHeadgear": "Lunar Warrior Helm (Headgear)",
"questMoon2Text": "Lunar Battle, Part 2: Stop the Overshadowing Stress",
"questMoon2Notes": "After studying the shards, @Starsystemic the Seer has some bad news. \"An ancient monster is approaching Habitica, and it is causing terrible stress to befall the citizens. I can draw the shadow out of people's hearts and into this tower, where it will take physical form, but youll need to defeat it before it breaks loose and spreads again.\" You nod, and she starts to chant. Dancing shadows fill the room, pressing tightly together. The cold wind swirls, the darkness deepens. The Overshadowing Stress rises from the floor, grins like a nightmare made real... and strikes!",
"questMoon2Notes": "After studying the shards, @Starsystemic the Seer has some bad news: “An ancient monster is approaching Habitica, and it is causing terrible stress to befall the citizens. I can draw the shadow out of peoples hearts and into this tower, where it will take physical form, but youll need to defeat it before it breaks loose and spreads again. You nod, and she starts to chant. Dancing shadows fill the room, pressing tightly together. The cold wind swirls, the darkness deepens. The Overshadowing Stress rises from the floor, grins like a nightmare made real and strikes!",
"questMoon2Completion": "The shadow explodes in a puff of dark air, leaving the room brighter and your hearts lighter. The stress blanketing Habitica is diminished, and you can all breathe a sigh of relief. Still, as you look up at the sky, you sense that this is not over: the monster knows someone destroyed its shadow. \"We'll keep careful watch in the coming weeks,\" says @Starsystemic, \"and I'll send you a quest scroll when it manifests.\"",
"questMoon2Boss": "Overshadowing Stress",
"questMoon2DropArmor": "Lunar Warrior Armour (Armour)",
@@ -419,7 +419,7 @@
"questMoon3Boss": "Monstrous Moon",
"questMoon3DropWeapon": "Lunar Scythe (Two-Handed Weapon)",
"questSlothText": "The Somnolent Sloth",
"questSlothNotes": "As you and your party venture through the Somnolent Snowforest, you're relieved to see a glimmering of green among the white snowdrifts... until an enormous sloth emerges from the frosty trees! Green emeralds shimmer hypnotically on its back.<br><br>\"Hello, adventurers... why don't you take it slow? You've been walking for a while... so why not... stop? Just lie down, and rest...\"<br><br>You feel your eyelids grow heavy, and you realise: It's the Somnolent Sloth! According to @JaizakAripaik, it got its name from the emeralds on its back which are rumoured to... send people to... sleep...<br><br>You shake yourself awake, fighting drowsiness. In the nick of time, @awakebyjava and @PainterProphet begin to shout spells, forcing your party awake. \"Now's our chance!\" @Kiwibot yells.",
"questSlothNotes": "As you and your party venture through the Somnolent Snowforest, youre relieved to see a glimmering of green among the white snowdrifts until an enormous sloth emerges from the frosty trees! Green emeralds shimmer hypnotically on its back.<br><br>Hello, adventurers why dont you take it slowly? You've been walking for a while so why not stop? Just lie down, and rest…”<br><br>You feel your eyelids grow heavy, and you realise: its the Somnolent Sloth! According to @JaizakAripaik, it got its name from the emeralds on its back which are rumoured to send people to sleep<br><br>You shake yourself awake, fighting drowsiness. In the nick of time, @awakebyjava and @PainterProphet begin to shout spells, forcing your party awake. Nows our chance! @Kiwibot yells.",
"questSlothCompletion": "You did it! As you defeat the Somnolent Sloth, its emeralds break off. \"Thank you for freeing me of my curse,\" says the sloth. \"I can finally sleep well, without those heavy emeralds on my back. Have these eggs as thanks, and you can have the emeralds too.\" The sloth gives you three sloth eggs and heads off for warmer climates.",
"questSlothBoss": "Somnolent Sloth",
"questSlothDropSlothEgg": "Sloth (Egg)",
@@ -447,7 +447,7 @@
"questStoikalmCalamity2CollectIcicleCoins": "Icicle Coins",
"questStoikalmCalamity2DropHeadgear": "Mammoth Rider Helm (Headgear)",
"questStoikalmCalamity3Text": "Stoïkalm Calamity, Part 3: Icicle Drake Quake",
"questStoikalmCalamity3Notes": "The twining tunnels of the icicle drake caverns shimmer with frost... and with untold riches. You gape, but Lady Glaciate strides past without a glance. \"Excessively flashy,\" she says. \"Obtained admirably, though, from respectable mercenary work and prudent banking investments. Look further.\" Squinting, you spot a towering pile of stolen items hidden in the shadows.<br><br>A sibilant voice hisses as you approach. \"My delicious hoard! You shall not steal it back from me!\" A sinuous body slides from the heap: the Icicle Drake Queen herself! You have just enough time to note the strange bracelets glittering on her wrists and the wildness glinting in her eyes before she lets out a howl that shakes the earth around you.",
"questStoikalmCalamity3Notes": "The twining tunnels of the icicle drake caverns shimmer with frost and with untold riches. You gape, but Lady Glaciate strides past without a glance. Excessively flashy, she says, “Obtained admirably, though, from respectable mercenary work and prudent banking investments. Look further. Squinting, you spot a towering pile of stolen items hidden in the shadows.<br><br>A sibilant voice hisses as you approach, “My delicious hoard! You shall not steal it back from me! A sinuous body slides from the heap: the Icicle Drake Queen herself! You have just enough time to note the strange bracelets glittering on her wrists and the wildness glinting in her eyes before she lets out a howl that shakes the earth around you.",
"questStoikalmCalamity3Completion": "You subdue the Icicle Drake Queen, giving Lady Glaciate time to shatter the glowing bracelets. The Queen stiffens in apparent mortification, then quickly covers it with a haughty pose. \"Feel free to remove these extraneous items,\" she says. \"I'm afraid they simply don't fit our decor.\"<br><br>\"Also, you stole them,\" @Beffymaroo says. \"By summoning monsters from the earth.\"<br><br>The Icicle Drake Queen looks miffed. \"Take it up with that wretched bracelet saleswoman,\" she says. \"It's Tzina you want. I was essentially unaffiliated.\"<br><br>Lady Glaciate claps you on the arm. \"You did well today,\" she says, handing you a spear and a horn from the pile. \"Be proud.\"",
"questStoikalmCalamity3Boss": "Icicle Drake Queen",
"questStoikalmCalamity3DropBlueCottonCandy": "Blue Candyfloss (Food)",
@@ -460,13 +460,13 @@
"questGuineaPigDropGuineaPigEgg": "Guinea Pig (Egg)",
"questGuineaPigUnlockText": "Unlocks Guinea Pig Eggs for purchase in the Market",
"questPeacockText": "The Push-and-Pull Peacock",
"questPeacockNotes": "You trek through the Taskwoods, wondering which of the enticing new goals you should pick. As you go deeper into the forest, you realise that you're not alone in your indecision. \"I could learn a new language, or go to the gym...\" @Cecily Perez mutters. \"I could sleep more,\" muses @Lilith of Alfheim, \"or spend time with my friends...\" It looks like @PainterProphet, @Pfeffernusse, and @Draayder are equally paralysed by the overwhelming options.<br><br>You realise that these ever-more-demanding feelings aren't really your own... you've stumbled straight into the trap of the pernicious Push-and-Pull Peacock! Before you can run, it leaps from the bushes. With each head pulling you in conflicting directions, you start to feel burnout overcoming you. You can't defeat both foes at once, so you only have one option—concentrate on the nearest task to fight back!",
"questPeacockNotes": "You trek through the Taskwoods, wondering which of the enticing new goals you should pick. As you go deeper into the forest, you realise that youre not alone in your indecision. I could learn a new language, or go to the gym…” @Cecily Perez mutters. I could sleep more, muses @Lilith of Alfheim, “Or spend time with my friends…” It looks like @PainterProphet, @Pfeffernusse, and @Draayder are equally paralysed by the overwhelming options.<br><br>You realise that these ever-more-demanding feelings arent really your own youve stumbled straight into the trap of the pernicious Push-and-Pull Peacock! Before you can run, it leaps from the bushes. With each head pulling you in conflicting directions, you start to feel burnout overcoming you. You cant defeat both foes at once, so you only have one option—concentrate on the nearest task to fight back!",
"questPeacockCompletion": "The Push-and-Pull Peacock is caught off guard by your sudden conviction. Defeated by your single-minded drive, its heads merge back into one, revealing the most beautiful creature you've ever seen. \"Thank you,\" the peacock says. \"Ive spent so long pulling myself in different directions that I lost sight of what I truly wanted. Please accept these eggs as a token of my gratitude.\"",
"questPeacockBoss": "Push-and-Pull Peacock",
"questPeacockDropPeacockEgg": "Peacock (Egg)",
"questPeacockUnlockText": "Unlocks Peacock Eggs for purchase in the Market",
"questButterflyText": "Bye, Bye, Butterfry",
"questButterflyNotes": "Your gardener friend @Megan sends you an invitation: “These warm days are the perfect time to visit Habiticas butterfly garden in the Taskan countryside. Come see the butterflies migrate!” When you arrive, however, the garden is in shambles—little more than scorched grass and dried-out weeds. Its been so hot that the Habiticans havent come out to water the flowers, and the dark-red Dailies have turned it into a dry, sun-baked, fire-hazard. There's only one butterfly there, and there's something odd about it...<br><br>“Oh no! This is the perfect hatching ground for the Flaming Butterfry,” cries @Leephon.<br><br>“If we dont catch it, itll destroy everything!” gasps @Eevachu.<br><br>Time to say bye, bye to Butterfry!",
"questButterflyNotes": "Your gardener friend @Megan sends you an invitation: “These warm days are the perfect time to visit Habiticas butterfly garden in the Taskan countryside. Come see the butterflies migrate!” When you arrive, however, the garden is in shambles—little more than scorched grass and dried-out weeds. Its been so hot that the Habiticans havent come out to water the flowers, and the dark-red Dailies have turned it into a dry, sun-baked, fire-hazard. Theres only one butterfly there, and theres something odd about it<br><br>“Oh no! This is the perfect hatching ground for the Flaming Butterfry,” cries @Leephon.<br><br>“If we dont catch it, itll destroy everything!” gasps @Eevachu.<br><br>Time to say bye-bye to Butterfry!",
"questButterflyCompletion": "After a blazing battle, the Flaming Butterfry is captured. “Great job catching that would-be arsonist,” says @Megan with a sigh of relief. “Still, its hard to vilify even the vilest butterfly. Wed better free this Butterfry someplace safe…like the desert.”<br><br>One of the other gardeners, @Beffymaroo, comes up to you, singed but smiling. “Will you help raise these foundling chrysalises we found? Perhaps next year well have a greener garden for them.”",
"questButterflyBoss": "Flaming Butterfry",
"questButterflyDropButterflyEgg": "Caterpillar (Egg)",
@@ -483,8 +483,8 @@
"questMayhemMistiflying1DropWhitePotion": "White Hatching Potion",
"questMayhemMistiflying1DropArmor": "Roguish Rainbow Messenger Robes (Armour)",
"questMayhemMistiflying2Text": "Mayhem in Mistiflying, Part 2: In Which the Wind Worsens",
"questMayhemMistiflying2Notes": "Mistiflying dips and rocks as the magical bees keeping it afloat are buffeted by the gale. After a desperate search for the April Fool, you find him inside a cottage, blithely playing cards with an angry, trussed-up skull.<br><br>@Katy133 raises their voice over the whistling wind. “Whats causing this? We defeated the skulls, but its getting worse!”<br><br>“That is a pickle,” the April Fool agrees. “Please be a dear and dont mention it to Lady Glaciate. Shes always threatening to call off our courtship on the grounds that I am catastrophically irresponsible, and I fear that she might misread this situation.” He shuffles the deck. “Perhaps you might follow the Mistiflies? Theyre immaterial, so the wind cant blow them away, and they tend to swarm around threats.” He nods out the window, where several of the citys patron creatures are fluttering towards the east. “Now let me concentratemy opponent has quite the poker face.”",
"questMayhemMistiflying2Completion": "You follow the Mistiflies to the site of a tornado, too stormy for you to enter.<br><br>“This should help,” says a voice directly in your ear, and you nearly fall off of your mount. The April Fool is somehow sitting directly behind you in the saddle. “I hear these messenger hoods emit an aura that guards against inclement weathervery useful to avoid losing missives as you fly around. Perhaps give it a try?”",
"questMayhemMistiflying2Notes": "Mistiflying dips and rocks as the magical bees keeping it afloat are buffeted by the gale. After a desperate search for the April Fool, you find him inside a cottage, blithely playing cards with an angry, trussed-up skull.<br><br>@Katy133 raises their voice over the whistling wind, “Whats causing this? We defeated the skulls, but its getting worse!”<br><br>“That is a pickle,” the April Fool agrees. “Please be a dear and dont mention it to Lady Glaciate. Shes always threatening to call off our courtship on the grounds that I am catastrophically irresponsible, and I fear that she might misread this situation.” He shuffles the deck. “Perhaps you might follow the Mistiflies? Theyre immaterial, so the wind cant blow them away, and they tend to swarm around threats.” He nods out the window, where several of the citys patron creatures are fluttering towards the east. “Now let me concentratemy opponent has quite the poker face.”",
"questMayhemMistiflying2Completion": "You follow the Mistiflies to the site of a tornado, too stormy for you to enter.<br><br>“This should help,” says a voice directly in your ear, and you nearly fall off your mount. The April Fool is somehow sitting directly behind you in the saddle. “I hear these messenger hoods emit an aura that guards against inclement weathervery useful to avoid losing missives as you fly around. Perhaps give it a try?”",
"questMayhemMistiflying2CollectRedMistiflies": "Red Mistiflies",
"questMayhemMistiflying2CollectBlueMistiflies": "Blue Mistiflies",
"questMayhemMistiflying2CollectGreenMistiflies": "Green Mistiflies",
@@ -499,8 +499,8 @@
"featheredFriendsText": "Feathered Friends Quest Bundle",
"featheredFriendsNotes": "Contains Quests to obtain Owl, Parrot, and Hawk Pet eggs: The Night-Owl, Help! Harpy!, and The Birds of Preycrastination.",
"questNudibranchText": "Infestation of the NowDo Nudibranchs",
"questNudibranchNotes": "You finally get around to checking your To Do's on a lazy day in Habitica. Bright against your deepest red tasks are a gaggle of vibrant blue sea slugs. You are entranced! Their sapphire colours make your most intimidating tasks look as easy as your best Habits. In a feverish stupor you get to work, tackling one task after the other in a ceaseless frenzy...<br><br>The next thing you know, @LilithofAlfheim is pouring cold water over you. “The NowDo Nudibranchs have been stinging you all over! You need to take a break!”<br><br>Shocked, you see that your skin is as bright red as your To Do list was. \"Being productive is one thing,\" @beffymaroo says, \"but you've also got to take care of yourself. Hurry, let's get rid of them!\"",
"questNudibranchCompletion": "You see the last of the NowDo Nudibranchs sliding off of a pile of completed tasks as @amadshade washes them away. One leaves behind a cloth bag, and you open it to reveal some gold and a few little ellipsoids you guess are eggs.",
"questNudibranchNotes": "You finally get around to checking your To Dos on a lazy day in Habitica. Bright against your deepest red tasks are a gaggle of vibrant blue sea slugs. You are entranced! Their sapphire colours make your most intimidating tasks look as easy as your best Habits. In a feverish stupor you get to work, tackling one task after the other in a ceaseless frenzy<br><br>The next thing you know, @LilithofAlfheim is pouring cold water over you. “The NowDo Nudibranchs have been stinging you all over! You need to take a break!”<br><br>Shocked, you see that your skin is as bright red as your To Do list was. Being productive is one thing, @beffymaroo says, “But youve also got to take care of yourself. Hurry, lets get rid of them!",
"questNudibranchCompletion": "You see the last of the NowDo Nudibranchs sliding off a pile of completed tasks as @amadshade washes them away. One leaves behind a cloth bag, and you open it to reveal some gold and a few little ellipsoids you guess are eggs.",
"questNudibranchBoss": "NowDo Nudibranch",
"questNudibranchDropNudibranchEgg": "Nudibranch (Egg)",
"questNudibranchUnlockText": "Unlocks Nudibranch Eggs for purchase in the Market",
@@ -519,13 +519,13 @@
"questGroupLostMasterclasser": "Mystery of the Masterclassers",
"questUnlockLostMasterclasser": "To unlock this quest, complete the final quests of these quest chains: 'Dilatory Distress', 'Mayhem in Mistiflying', 'Stoïkalm Calamity', and 'Terror in the Taskwoods'.",
"questLostMasterclasser1Text": "The Mystery of the Masterclassers, Part 1: Read Between the Lines",
"questLostMasterclasser1Notes": "Youre unexpectedly summoned by @beffymaroo and @Lemoness to Habit Hall, where youre astonished to find all four of Habiticas Masterclassers awaiting you in the wan light of dawn. Even the Joyful Reaper looks somber.<br><br>“Oho, youre here,” says the April Fool. “Now, we would not rouse you from your rest without a truly dire—”<br><br>“Help us investigate the recent bout of possessions,” interrupts Lady Glaciate. “All the victims blamed someone named Tzina.”<br><br>The April Fool is clearly affronted by the summary. “What about my speech?” he hisses to her. “With the fog and thunderstorm effects?”<br><br>“Were in a hurry,” she mutters back. “And my mammoths are still soggy from your incessant practicing.”<br><br>“Im afraid that the esteemed Master of Warriors is correct,” says King Manta. “Time is of the essence. Will you aid us?”<br><br>When you nod, he waves his hands to open a portal, revealing an underwater room. “Swim down with me to Dilatory, and we will scour my library for any references that might give us a clue.” At your look of confusion, he adds, “Dont worry, the paper was enchanted long before Dilatory sank. None of the books are the slightest bit damp!” He winks.“Unlike Lady Glaciates mammoths.”<br><br>“I heard that, Manta.”<br><br>As you dive into the water after the Master of Mages, your legs magically fuse into fins. Though your body is buoyant, your heart sinks when you see the thousands of bookshelves. Better start reading…",
"questLostMasterclasser1Notes": "Youre unexpectedly summoned by @beffymaroo and @Lemoness to Habit Hall, where youre astonished to find all four of Habiticas Masterclassers awaiting you in the wan light of dawn. Even the Joyful Reaper looks sombre.<br><br>“Oho, youre here,” says the April Fool. “Now, we would not rouse you from your rest without a truly dire—”<br><br>“Help us investigate the recent bout of possessions,” interrupts Lady Glaciate. “All the victims blamed someone named Tzina.”<br><br>The April Fool is clearly affronted by the summary. “What about my speech?” he hisses to her. “With the fog and thunderstorm effects?”<br><br>“Were in a hurry,” she mutters back. “And my mammoths are still soggy from your incessant practising.”<br><br>“Im afraid that the esteemed Master of Warriors is correct,” says King Manta. “Time is of the essence. Will you aid us?”<br><br>When you nod, he waves his hands to open a portal, revealing an underwater room. “Swim down with me to Dilatory, and we will scour my library for any references that might give us a clue.” At your look of confusion, he adds, “Dont worry, the paper was enchanted long before Dilatory sank. None of the books are the slightest bit damp!” He winks.“Unlike Lady Glaciates mammoths.”<br><br>“I heard that, Manta.”<br><br>As you dive into the water after the Master of Mages, your legs magically fuse into fins. Though your body is buoyant, your heart sinks when you see the thousands of bookshelves. Better start reading…",
"questLostMasterclasser1Completion": "After hours of poring through volumes, you still havent found any useful information.<br><br>“It seems impossible that there isnt even the tiniest reference to anything relevant,” says head librarian @Tuqjoi, and their assistant @stefalupagus nods in frustration.<br><br>King Mantas eyes narrow. “Not impossible…” he says. “<em>Intentional</em>.” For a moment, the water glows around his hands, and several of the books shudder. “Something is obscuring information,” he says. “Not just a static spell, but something with a will of its own. Something… alive.” He swims up from the table. “The Joyful Reaper needs to hear about this. Lets pack a meal for the road.”",
"questLostMasterclasser1CollectAncientTomes": "Ancient Tomes",
"questLostMasterclasser1CollectForbiddenTomes": "Forbidden Tomes",
"questLostMasterclasser1CollectHiddenTomes": "Hidden Tomes",
"questLostMasterclasser2Text": "The Mystery of the Masterclassers, Part 2: Assembling the a'Voidant",
"questLostMasterclasser2Notes": "The Joyful Reaper drums her bony fingers on some of the books that you brought. “Oh, dear,” the Master of Healers says. “There is a malevolent life essence at work. I might have guessed, considering the attacks by reanimated skulls during each incident.” Her assistant @tricksy.fox brings in a chest, and you are startled to see the contents that @beffymaroo unloads: the very same objects once used by this mysterious Tzina to possess people.<br><br>“Im going to use resonant healing magic to try to make this creature manifest,” the Joyful Reaper says, reminding you that the skeleton is a somewhat unconventional Healer. “Youll need to read the revealed information quickly, in case it breaks loose.”<br><br>As she concentrates, a twisting mist begins to siphon from the books and twine around the objects. Quickly, you flip through the pages, trying to read the new lines of text that are writhing into view. You catch only a few snippets: “Sands of the Timewastes”“the Great Disaster” —“split into four”— “permanently corrupted”— before a single name catches your eye: Zinnya.<br><br>Abruptly, the pages wrench free from your fingers and shred themselves as a howling creature explodes into being, coalescing around the possessed objects.<br><br>“Its an aVoidant!” the Joyful Reaper shouts, throwing up a protection spell. “Theyre ancient creatures of confusion and obscurity. If this Tzina can control one, she must have a frightening command over life magic. Quickly, attack it before it escapes back into the books!”<br><br>",
"questLostMasterclasser2Notes": "The Joyful Reaper drums her bony fingers on some of the books that you brought. “Oh, dear,” the Master of Healers says, “There is a malevolent life essence at work. I might have guessed, considering the attacks by reanimated skulls during each incident.” Her assistant @tricksy.fox brings in a chest, and you are startled to see the contents that @beffymaroo unloads: the very same objects once used by this mysterious Tzina to possess people.<br><br>“Im going to use resonant healing magic to try to make this creature manifest,” the Joyful Reaper says, reminding you that the skeleton is a somewhat unconventional Healer, “Youll need to read the revealed information quickly, in case it breaks loose.”<br><br>As she concentrates, a twisting mist begins to siphon from the books and twine around the objects. Quickly, you flip through the pages, trying to read the new lines of text that are writhing into view. You catch only a few snippets: “Sands of the Timewastes”“the Great Disaster”—“split into four”—“permanently corrupted”—before a single name catches your eye: Zinnya.<br><br>Abruptly, the pages wrench free from your fingers and shred themselves as a howling creature explodes into being, coalescing around the possessed objects.<br><br>“Its an aVoidant!” the Joyful Reaper shouts, throwing up a protection spell. “Theyre ancient creatures of confusion and obscurity. If this Tzina can control one, she must have a frightening command over life magic. Quickly, attack it before it escapes back into the books!”<br><br>",
"questLostMasterclasser2Completion": "The aVoidant succumbs at last, and you share the snippets that you read.<br><br>“None of those references sound familiar, even for someone as old as I,” the Joyful Reaper says. “Except… the Timewastes are a distant desert at the most hostile edge of Habitica. Portals often fail nearby, but swift mounts could get you there in no time. Lady Glaciate will be glad to assist.” Her voice grows amused. “Which means that the enamored Master of Rogues will undoubtedly tag along.” She hands you the glimmering mask. “Perhaps you should try to track the lingering magic in these items to its source. Ill go harvest some sustenance for your journey.”",
"questLostMasterclasser2Boss": "The a'Voidant",
"questLostMasterclasser2DropEyewear": "Aether Mask (Eyewear)",
@@ -539,11 +539,11 @@
"questLostMasterclasser3DropBodyAccessory": "Aether Amulet (Body Accessory)",
"questLostMasterclasser3DropBasePotion": "Base Hatching Potion",
"questLostMasterclasser3DropGoldenPotion": "Golden Hatching Potion",
"questLostMasterclasser3DropPinkPotion": "Cotton Candy Pink Hatching Potion",
"questLostMasterclasser3DropPinkPotion": "Candyfloss Pink Hatching Potion",
"questLostMasterclasser3DropShadePotion": "Shade Hatching Potion",
"questLostMasterclasser3DropZombiePotion": "Zombie Hatching Potion",
"questLostMasterclasser4Text": "The Mystery of the Masterclassers, Part 4: The Lost Masterclasser",
"questLostMasterclasser4Notes": "You surface from the portal, but youre still suspended in a strange, shifting netherworld. “That was bold,” says a cold voice. “I have to admit, I hadnt planned for a direct confrontation yet.” A woman rises from the churning whirlpool of darkness. “Welcome to the Realm of Void.”<br><br>You try to fight back your rising nausea. “Are you Zinnya?” you ask.<br><br>“That old name for a young idealist,” she says, mouth twisting, and the world writhes beneath you. “No. If anything, you should call me the Antizinnya now, given all that I have done and undone.”<br><br>Suddenly, the portal reopens behind you, and as the four Masterclassers burst out, bolting towards you, Antizinnyas eyes flash with hatred. “I see that my pathetic replacements have managed to follow you.”<br><br>You stare. “Replacements?”<br><br>“As the Master Aethermancer, I was the first Masterclasserthe only Masterclasser. These four are a mockery, each possessing only a fragment of what I once had! I commanded every spell and learned every skill. I shaped your very world to my whimuntil the traitorous aether itself collapsed under the weight of my talents and my perfectly reasonable expectations. I have been trapped for millennia in this resulting void, recuperating. Imagine my disgust when I learned how my legacy had been corrupted.” She lets out a low, echoing laugh. “My plan was to destroy their domains before destroying them, but I suppose the order is irrelevant.” With a burst of uncanny strength, she charges forward, and the Realm of Void explodes into chaos.",
"questLostMasterclasser4Notes": "You surface from the portal, but youre still suspended in a strange, shifting netherworld. “That was bold,” says a cold voice, “I have to admit, I hadnt planned for a direct confrontation yet.” A woman rises from the churning whirlpool of darkness. “Welcome to the Realm of Void.”<br><br>You try to fight back your rising nausea. “Are you Zinnya?” you ask.<br><br>“That old name for a young idealist,” she says, mouth twisting, and the world writhes beneath you, “No. If anything, you should call me the Antizinnya now, given all that I have done and undone.”<br><br>Suddenly, the portal reopens behind you, and as the four Masterclassers burst out, bolting towards you, Antizinnyas eyes flash with hatred. “I see that my pathetic replacements have managed to follow you.”<br><br>You stare, “Replacements?”<br><br>“As the Master Aethermancer, I was the first Masterclasserthe only Masterclasser. These four are a mockery, each possessing only a fragment of what I once had! I commanded every spell and learned every skill. I shaped your very world to my whimuntil the traitorous aether itself collapsed under the weight of my talents and my perfectly reasonable expectations. I have been trapped for millennia in this resulting void, recuperating. Imagine my disgust when I learned how my legacy had been corrupted.” She lets out a low, echoing laugh, “My plan was to destroy their domains before destroying them, but I suppose the order is irrelevant.” With a burst of uncanny strength, she charges forward, and the Realm of Void explodes into chaos.",
"questLostMasterclasser4Completion": "Under the onslaught of your final attack, the Lost Masterclasser screams in frustration, her body flickering into translucence. The thrashing void stills around her as she slumps forward, and for a moment, she seems to change, becoming younger, calmer, with an expression of peace upon her face… but then everything melts away with scarcely a whisper, and youre kneeling once more in the desert sand.<br><br>“It seems that we have much to learn about our own history,” King Manta says, staring at the broken ruins. “After the Master Aethermancer grew overwhelmed and lost control of her abilities, the outpouring of void must have leached the life from the entire land. Everything probably became deserts like this.”<br><br>“No wonder the ancients who founded Habitica stressed a balance of productivity and wellness,” the Joyful Reaper murmurs. “Rebuilding their world would have been a daunting task requiring considerable hard work, but they would have wanted to prevent such a catastrophe from happening again.”<br><br>“Oho, look at those formerly possessed items!” says the April Fool. Sure enough, all of them shimmer with a pale, glimmering translucence from the final burst of aether released when you laid Antizinnyas spirit to rest. “What a dazzling effect. I must take notes.”<br><br>“The concentrated remnants of aether in this area probably caused these animals to go invisible, too,” says Lady Glaciate, scratching a patch of emptiness behind the ears. You feel an unseen fluffy head nudge your hand, and suspect that youll have to do some explaining at the stables back home. As you look at the ruins one last time, you spot all that remains of the first Masterclasser: her shimmering cloak. Lifting it onto your shoulders, you head back to Habit City, pondering everything that you have learned.<br><br>",
"questLostMasterclasser4Boss": "Anti'zinnya",
"questLostMasterclasser4RageTitle": "Siphoning Void",
@@ -589,7 +589,7 @@
"questsRageStrikeLead": "Ian is Heartbroken!",
"questsRageStrikeRecap": "On March 6, our wonderful Ian the Quest Guide was deeply shaken when the Dysheartener shattered the ground around the Quest Shop. Quickly, tackle your tasks to defeat the monster and help rebuild!",
"questDysheartenerBossRageMarket": "`The Dysheartener uses SHATTERING HEARTBREAK!`\n\nHelp! After feasting on our incomplete Dailies, the Dysheartener lets out another Shattering Heartbreak attack, smashing the walls and floor of the Market! As stone rains down, Alex the Merchant weeps at his crushed merchandise, stricken by the destruction.\n\nWe can't let this happen again! Be sure to do all our your Dailies to prevent the Dysheartener from using its final strike.",
"questDysheartenerBossRageQuests": "`The Dysheartener uses SHATTERING HEARTBREAK!`\n\nAaaah! We've left our Dailies undone again, and the Dysheartener has mustered the energy for one final blow against our beloved shopkeepers. The countryside around Ian the Quest Master is ripped apart by its Shattering Heartbreak attack, and Ian is struck to the core by the horrific vision. We're so close to defeating this monster.... Hurry! Don't stop now!",
"questDysheartenerBossRageQuests": "`The Dysheartener uses SHATTERING HEARTBREAK!`\n\nAaaah! Weve left our Dailies undone again, and the Dysheartener has mustered the energy for one final blow against our beloved shopkeepers. The countryside around Ian the Quest Master is ripped apart by its Shattering Heartbreak attack, and Ian is struck to the core by the horrific vision. Were so close to defeating this monster Hurry! Dont stop now!",
"questDysheartenerDropHippogriffPet": "Hopeful Hippogriff (Pet)",
"questDysheartenerDropHippogriffMount": "Hopeful Hippogriff (Mount)",
"dysheartenerArtCredit": "Artwork by @AnnDeLune",
@@ -630,7 +630,7 @@
"birdBuddiesText": "Bird Buddies Quest Bundle",
"birdBuddiesNotes": "Contains Quests to obtain Peacock, Penguin, and Rooster Pet eggs: The Push-and-Pull Peacock, The Fowl Frost, and Rooster Rampage.",
"questVelociraptorText": "The Veloci-Rapper",
"questVelociraptorNotes": "Youre sharing honey cakes with @*~Seraphina~*, @Procyon P, and @Lilith of Alfheim by a lake in the Stoïkalm Steppes. Suddenly, a mournful voice interrupts your picnic.<br><br><em>My Habits took a hit, I missed my Dailies,<br>Im losing it, sinking with doubt and maybes,<br>At the top of my game I used to be so fly,<br>But now I just let my Due Dates go by.</em><br><br>@*~Seraphina~* peers behind a stand of grass. “Its the Veloci-Rapper. It seems... distraught?”<br><br>You pump a fist in determination. “There's only one thing to do. Rap battle time!”",
"questVelociraptorNotes": "Youre sharing honey cakes with @*~Seraphina~*, @Procyon P, and @Lilith of Alfheim by a lake in the Stoïkalm Steppes. Suddenly, a mournful voice interrupts your picnic.<br><br><em>My Habits took a hit, I missed my Dailies,<br>Im losing it, sinking with doubt and maybes,<br>At the top of my game I used to be so fly,<br>But now I just let my Due Dates go by.</em><br><br>@*~Seraphina~* peers behind a stand of grass. “Its the Veloci-Rapper. It seems distraught?”<br><br>You pump a fist in determination. “Theres only one thing to do. Rap battle time!”",
"questVelociraptorCompletion": "You burst through the grass, confronting the Veloci-Rapper.<br><br><em>See here, rapper, youre no quitter,<br>Youre Bad Habits' hardest hitter!<br>Check off your To Do's like a boss,<br>Dont mourn over one days loss!</em><br><br>Filled with renewed confidence, it bounds off to freestyle another day, leaving behind three eggs where it sat.",
"questVelociraptorBoss": "Veloci-Rapper",
"questVelociraptorDropVelociraptorEgg": "Velociraptor (Egg)",
@@ -651,14 +651,14 @@
"questSilverUnlockText": "Unlocks Silver Hatching Potions for purchase in the Market",
"questSilverCollectMoonRunes": "Moon Runes",
"questSilverCollectCancerRunes": "Cancer Zodiac Runes",
"questSilverCompletion": "You've delved. You've dredged. You've scavenged. At last you emerge from the Dungeons, laden with runes and bars of silver, covered in sludge but exhilarated with success. You journey back to Habit City and set to work in an alchemy lab. You and @starsystemic follow the formulas @QuartzFox found, under the careful supervision of @Edge. Finally, in a great puff of glitter and smoke, your concoction settles into the familiar viscosity of a Hatching Potion!<br><br>@Edge scoops the mixture into vials and grins. “Let's give it a try, shall we? Anyone got any Eggs?”<br><br>You rush to the stables, wondering what shining secrets may yet remain undiscovered...",
"questSilverCompletion": "Youve delved. Youve dredged. Youve scavenged. At last you emerge from the Dungeons, laden with runes and bars of silver, covered in sludge but exhilarated with success. You journey back to Habit City and set to work in an alchemy lab. You and @starsystemic follow the formulas @QuartzFox found, under the careful supervision of @Edge. Finally, in a great puff of glitter and smoke, your concoction settles into the familiar viscosity of a Hatching Potion!<br><br>@Edge scoops the mixture into vials and grins. “Lets give it a try, shall we? Anyone got any Eggs?”<br><br>You rush to the stables, wondering what shining secrets may yet remain undiscovered",
"questSilverNotes": "The recent discovery of Bronze Hatching Potions has all of Habitica talking. Could potions of even brighter metals be possible? You head to Habit City's central Public Library, accompanied by @QuartzFox and @starsystemic, and gather up great armloads of books on alchemy to study.<br><br>After hours of eye-straining labour, @QuartzFox lets out a not-quite-library-appropriate shout of triumph. “Aha! I've found it!” You hurry over to see. “A Silver Hatching Potion can be made with runes of the zodiac sign Cancer, dissolved in pure silver melted over flame infused with the power of Moon runes.”<br><br>“We'll need a lot of those ingredients,” muses @starsystemic. “In case an attempt goes wrong.”<br><br>“There's only one place to find huge quantities of such random crafting materials,” says @Edge, standing in the shadow of the stacks with arms crossed. Have they been there the whole time? “The Dungeons of Drudgery. Let's get going.”",
"questSilverText": "The Silver Solution",
"questDolphinUnlockText": "Unlocks Dolphin Eggs for purchase in the Market",
"questDolphinDropDolphinEgg": "Dolphin (Egg)",
"questDolphinCompletion": "Your battle of wills with the dolphin has left you tired but victorious. With your determination and encouragement, @mewrose, @khdarkwolf, and @confusedcicada pick themselves up and shake off the dolphins insidious telepathy. The four of you shield yourselves with a sense of accomplishment in your consistent Dailies, strong Habits, and completed To Do's until it closes its glowing eyes in silent acknowledgment of your successes. With that, it tumbles back into the bay. As you trade high-fives and congratulations, you notice three eggs wash ashore.<br><br>“Hm, I wonder what we can do with those,” @khdarkwolf muses.",
"questDolphinBoss": "Dolphin of Doubt",
"questDolphinNotes": "You walk upon the shores of Inkomplete Bay, pondering the daunting work ahead of you. A splash in the water catches your eye. A magnificent dolphin arcs over the waves. Sunlight glimmers off its fins and tail. But wait...thats not sunlight, and the dolphin doesnt dip back into the sea. It fixes its gaze on @khdarkwolf.<br><br>“Ill never finish all these Dailies,” said @khdarkwolf.<br><br>“Im not good enough to reach my goals,” said @confusedcicada as the dolphin turned its glare on them.<br><br>“Why did I even bother trying?” asked @mewrose, withering under the stare of the beast.<br><br>Its eyes meet yours, and feel your mind begin to sink under the rising tide of doubt. You steel yourself; someone has to defeat this creature, and its going to be you!",
"questDolphinNotes": "You walk upon the shores of Inkomplete Bay, pondering the daunting work ahead of you. A splash in the water catches your eye. A magnificent dolphin arcs over the waves. Sunlight glimmers off its fins and tail. But waitthats not sunlight, and the dolphin doesnt dip back into the sea. It fixes its gaze on @khdarkwolf.<br><br>“Ill never finish all these Dailies,” says @khdarkwolf.<br><br>“Im not good enough to reach my goals,” says @confusedcicada as the dolphin turns its glare on them.<br><br>“Why did I even bother trying?” asks @mewrose, withering under the stare of the beast.<br><br>Its eyes meet yours, and you feel your mind begin to sink under the rising tide of doubt. You steel yourself; someone has to defeat this creature, and its going to be you!",
"questDolphinText": "The Dolphin of Doubt",
"questBronzeUnlockText": "Unlocks Bronze Hatching Potions for purchase in the Market",
"questBronzeDropBronzePotion": "Bronze Hatching Potion",
@@ -672,7 +672,7 @@
"delightfulDinosNotes": "Contains Quests to obtain Triceratops, T-Rex, and Pterodactyl Pet eggs: The Trampling Triceratops, The Dinosaur Unearthed, and The Pterror-dactyl.",
"questAmberText": "The Amber Alliance",
"questAmberNotes": "Youre sitting in the Tavern with @beffymaroo and @-Tyr- when @Vikte bursts through the door and excitedly tells you about the rumours of another type of Magic Hatching Potion hidden in the Taskwoods. Having completed your Dailies, the three of you immediately agree to help @Vikte on their search. After all, whats the harm in a little adventure?<br><br>After walking through the Taskwoods for hours, youre beginning to regret joining such a wild chase. Youre about to head home, when you hear a surprised yelp and turn to see a huge lizard with shiny amber scales coiled around a tree, clutching @Vikte in her claws. @beffymaroo reaches for her sword.<br><br>“Wait!” cries @-Tyr-. “Its the Trerezin! Shes not dangerous, just dangerously clingy!”",
"questAmberCompletion": "“Trerezin?” @-Tyr- says calmly. “Could you let @Vikte go? I dont think theyre enjoying being so high up.”<br><br>The Trerezins amber skin blushes crimson and she gently lowers @Vikte to the ground. “My apologies! Its been so long since Ive had any guests that Ive forgotten my manners!” She slithers forward to greet you properly before disappearing into her treehouse, and returning with an armful of Amber Hatching Potions as thank-you gifts!<br><br>“Magic Potions!” @Vikte gasps.<br><br>“Oh, these old things?” The Trerezin's tongue flickers as she thinks. “How about this? Ill give you this whole stack if you promise to visit me every so often...”<br><br>And so you leave the Taskwoods, excited to tell everyone about the new potions—and your new friend!",
"questAmberCompletion": "“Trerezin?” @-Tyr- says calmly, “Could you let @Vikte go? I dont think theyre enjoying being so high up.”<br><br>The Trerezins amber skin blushes crimson and she gently lowers @Vikte to the ground. “My apologies! Its been so long since Ive had any guests that Ive forgotten my manners!” She slithers forward to greet you properly before disappearing into her treehouse, and returning with an armful of Amber Hatching Potions as thank-you gifts!<br><br>“Magic Potions!” @Vikte gasps.<br><br>“Oh, these old things?” The Trerezin's tongue flickers as she thinks. “How about this? Ill give you this whole stack if you promise to visit me every so often”<br><br>And so you leave the Taskwoods, excited to tell everyone about the new potions—and your new friend!",
"questAmberBoss": "Trerezin",
"questAmberDropAmberPotion": "Amber Hatching Potion",
"questAmberUnlockText": "Unlocks Amber Hatching Potions for purchase in the Market",
@@ -681,13 +681,13 @@
"questRubyCollectRubyGems": "Ruby Gems",
"questRubyCollectVenusRunes": "Venus Runes",
"questRubyCollectAquariusRunes": "Aquarius Zodiac Runes",
"questRubyCompletion": "With the necessary items safely packed away, the three of you rush back to Habit City and meet in @beffymaroo's lab. “Excellent work!” @beffymaroo says. “You've gathered the ingredients for the potion!”<br><br>@beffymaroo carefully combines the runes and the rubies to create a brilliant red potion and pours some of it on two pet eggs. As you observe the results, you notice that the two pets seem completely uninterested in one another!<br><br>“Did it not work?” @gully asks. But before anyone can answer, you suddenly realise that it isn't the potion that creates friendship and love, but rather it is the experience of working together toward a common goal. You come away from the quest having gained some new friends...and some flashy new pets!",
"questRubyCompletion": "With the necessary items safely packed away, the three of you rush back to Habit City and meet in @beffymaroos lab. “Excellent work!” @beffymaroo says, “Youve gathered the ingredients for the potion!”<br><br>@beffymaroo carefully combines the runes and the rubies to create a brilliant red potion and pours some of it onto two pet eggs. As you observe the results, you notice that the two pets seem completely uninterested in one another!<br><br>“Did it not work?” @gully asks. But before anyone can answer, you suddenly realise that it isnt the potion that creates friendship and love, but rather it is the experience of working together towards a common goal. You come away from the quest having gained some new friendsand some flashy new pets!",
"questRubyNotes": "The normally bustling peaks of the Stoïkalm Volcanoes lie silent in the snow. “I suppose the hikers and sight-seers are hibernating?” @gully says to you and @Aspiring_Advocate. “That makes our search easier.”<br><br>As you reach the summit, the chill wind merges with the steam billowing from the crater. “There!” @Aspiring_Advocate exclaims, pointing toward a hot spring. “What better place to find cool runes of Aquarius and passionate runes of Venus than where ice and fire meet?”<br><br>The three of you hurry toward the hot spring. “According to my research,” @Aspiring_Advocate says, “combining the runes with heart-shaped rubies will create a hatching potion that can foster friendship and love!”<br><br>Excited by the prospect of a new discovery, you all smile. “All right,” @gully says, “let's start searching!”",
"questRubyText": "Ruby Rapport",
"questWaffleRageTitle": "Maple Mire",
"questWaffleBoss": "Awful Waffle",
"questWaffleCompletion": "Battered and buttered but triumphant, you savour sweet victory as the Awful Waffle collapses into a pool of sticky goo.<br><br>“Wow, you really creamed that monster,” says Lady Glaciate, impressed.<br><br>“A piece of cake!” beams the April Fool.<br><br>“Kind of a shame, though,” says @beffymaroo. “It looked good enough to eat.”<br><br>The Fool takes a set of potion bottles from somewhere in his cape, fills them with the syrupy leavings of the Waffle, and mixes in a pinch of sparkling dust. The liquid swirls with colour—new Hatching Potions! He tosses them into your arms. “All that adventure has given me an appetite. Who wants to join me for breakfast?”",
"questWaffleNotes": "“April Fool!” storms a flustered Lady Glaciate. “You said your dessert-themed prank was over with and completely cleaned up!”<br><br>“Why, it was and is, my dear,” replies the Fool, puzzled. “And I am the most honest of Fools. What's wrong?”<br><br>“There's a giant sugary monster approaching Habit City!”<br><br>“Hmm,” muses the Fool. “I did raid a few lairs for the mystic reagents for my last event. Maybe I attracted some unwanted attention. Is it the Saccharine Serpent? The Torte-oise? Tiramisu Rex?”<br><br>“No! It's some sort of... Awful Waffle!”<br><br>“Huh. That's a new one! Perhaps it spawned from all the ambient shenanigan energy.” He turns to you and @beffymaroo with a lopsided smile. “I don't suppose you'd be available for some heroics?”",
"questWaffleNotes": "“April Fool!” storms a flustered Lady Glaciate, “You said your dessert-themed prank was over with and completely cleaned up!”<br><br>“Why, it was and is, my dear,” replies the Fool, puzzled, “And I am the most honest of Fools. Whats wrong?”<br><br>“Theres a giant sugary monster approaching Habit City!”<br><br>“Hmm,” muses the Fool. “I did raid a few lairs for the mystic reagents for my last event. Maybe I attracted some unwanted attention. Is it the Saccharine Serpent? The Torte-oise? Tiramisu Rex?”<br><br>“No! Its some sort of Awful Waffle!”<br><br>“Huh. Thats a new one! Perhaps it spawned from all the ambient shenanigan energy.” He turns to you and @beffymaroo with a lopsided smile, “I dont suppose youd be available for some heroics?”",
"questWaffleText": "Waffling with the Fool: Disaster Breakfast!",
"questWaffleUnlockText": "Unlocks Confection Hatching Potions for purchase in the Market",
"questWaffleDropDessertPotion": "Confection Hatching Potion",
@@ -726,7 +726,7 @@
"questStoneCollectCapricornRunes": "Capricorn Runes",
"questStoneCompletion": "The work clearing overgrowth and moving loose stones taxes you to the limits of your strength. But you divide the work among the team, and place stones in the paths behind you to help you find your way back to the others. The runes you find bolster your strength and determination, too, and in the end the garden doesn't look so neglected at all!<br><br>You convene at the Library as @starsystemic suggested, and find a Magic Potion formula that uses the runes you collected. “This is an unexpected reward for attending to our neglected tasks,” says @jjgame83.<br><br>@QuartzFox agrees, “And that is on top of having a beautiful garden to enjoy with our pets.”<br><br>“Let's start making some a-mazing Mossy Stone Hatching Potions!” says @starsystemic, and everyone joins in happily.",
"questStoneCollectMossyStones": "Mossy Stones",
"questFungiNotes": "Its been a rainy spring in Habitica and the ground around the stables is spongy and damp. You notice quite a few mushrooms have appeared along the wooden stable walls and fences. Theres a fog hanging about, not quite letting the sun peek through, and its a bit dispiriting.<br><br>Out of the mist you see the outline of the April Fool, not at all his usual bouncy self.<br><br>Id hoped to bring you all some delightful Fungi Magic Hatching Potions so that you can keep your mushroom friends from my special day forever,” he says, his expression alarmingly unsmiling. “But this cold fog is really getting to me, its making me feel too tired and dismal to work my usual magic.”<br><br>“Oh no, sorry to hear that,” you say, noticing your own increasingly somber mood. “This fog is really making the day gloomy. I wonder where it came from…”<br><br>A low rumble sounds across the fields, and you see an outline emerging from the mist. Youre alarmed to see a gigantic and unhappy looking mushroom creature, and the mist appears to be emanating from it.<br><br>“Aha,” says the Fool, “I think this fungal fellow may be the source of our blues. Lets see if we can summon a little cheer for our friend here and ourselves.”",
"questFungiNotes": "Its been a rainy spring in Habitica and the ground around the stables is spongy and damp. You notice quite a few mushrooms have appeared along the wooden stable walls and fences. Theres a fog hanging about, not quite letting the sun peek through, and its a bit dispiriting.<br><br>Out of the mist you see the outline of the April Fool, not at all his usual bouncy self.<br><br>Id hoped to bring you all some delightful Fungi Magic Hatching Potions so that you can keep your mushroom friends from my special day forever,” he says, his expression alarmingly unsmiling. “But this cold fog is really getting to me, its making me feel too tired and dismal to work my usual magic.”<br><br>“Oh no, sorry to hear that,” you say, noticing your own increasingly sombre mood. “This fog is really making the day gloomy. I wonder where it came from…”<br><br>A low rumble sounds across the fields, and you see an outline emerging from the mist. Youre alarmed to see a gigantic and unhappy looking mushroom creature, and the mist appears to be emanating from it.<br><br>“Aha,” says the Fool, “I think this fungal fellow may be the source of our blues. Lets see if we can summon a little cheer for our friend here and ourselves.”",
"questPinkMarbleUnlockText": "Unlocks Pink Marble Hatching Potions for purchase in the Market.",
"questFungiText": "The Moody Mushroom",
"questFungiCompletion": "You and the April Fool look at each other with a sign of relief as the mushroom retreats to the forest.<br><br>“Ah,” the Fool exclaims, “that was quite a mycelial melancholy. Im glad we could improve his mood, and ours too! I feel my energy coming back. Come with me and well get those Fungi potions going together.”",
@@ -764,8 +764,8 @@
"questVirtualPetDropVirtualPetPotion": "Virtual Pet Hatching Potion",
"questVirtualPetUnlockText": "Unlocks Virtual Pet Hatching Potion for purchase in the Market",
"questVirtualPetRageDescription": "This bar fills when you don't complete your Dailies. When it is full, the Wotchimon will take away some of your party's pending damage!",
"questPinkMarbleNotes": "After hearing rumours about a cave in the Meandering Mountains that has pink rocks and dust shooting out of it, your party starts to investigate. As you approach the cave, there is indeed a huge pink dust cloud and strangely, you hear a tiny voice's battle cry, followed by the sound of shattering rock.<br><br>@Empress42 accidentally inhales some of the dust and suddenly feels dreamy and less productive. “Same here!” says @QuartzFox, “I'm suddenly fantasising about a person that I barely know!”<br><br>@a_diamond peeks into the cave and finds a little being zipping around and smashing pink marbled rock to dust. “Take cover! This Cupid has been corrupted and is using his magic to cause limerence and unrealistic infatuations! We have to subdue him!”",
"questPinkMarbleCompletion": "You manage to pin the little guy down at last he was much tougher and faster than expected. Before he stirs again, you take away his quiver of glowing arrows. He blinks and suddenly looks around in surprise. “To escape my own sorrow and heartbreak for a while I pricked myself with one of my arrows… I don't remember anything after that!”<br><br>He is just about to flee the cave, notices that @Loremi has taken a sample of the marble dust and grins. “Try using some of this pink marble dust in a potion! Nurture the pets that hatch from it and you will find that real relationships are born from communication, mutual trust and care. I wish you luck, and I wish you love!”",
"questPinkMarbleNotes": "After hearing rumours about a cave in the Meandering Mountains that has pink rocks and dust shooting out of it, your party starts to investigate. As you approach the cave, there is indeed a huge pink dust cloudand strangely, you hear a tiny voices battle cry, followed by the sound of shattering rock.<br><br>@Empress42 accidentally inhales some of the dust and suddenly feels dreamy and less productive. “Same here!” says @QuartzFox, “Im suddenly fantasising about a person that I barely know!”<br><br>@a_diamond peeks into the cave and finds a little being zipping around and smashing pink marbled rock to dust. “Take cover! This Cupid has been corrupted and is using his magic to cause limerence and unrealistic infatuations! We have to subdue him!”",
"questPinkMarbleCompletion": "You manage to pin the little guy down at lasthe was much tougher and faster than expected. Before he stirs again, you take away his quiver of glowing arrows. He blinks and suddenly looks around in surprise. “To escape my own sorrow and heartbreak for a while I pricked myself with one of my arrows… I dont remember anything after that!”<br><br>He is just about to flee the cave, notices that @Loremi has taken a sample of the marble dust and grins. “Try using some of this pink marble dust in a potion! Nurture the pets that hatch from it and you will find that real relationships are born from communication, mutual trust and care. I wish you luck, and I wish you love!”",
"questPinkMarbleBoss": "Cupido",
"questPinkMarbleRageTitle": "Pink Punch",
"questPinkMarbleRageDescription": "This bar fills when you don't complete your Dailies. When it is full, Cupido will take away some of your party's pending damage!",
@@ -804,19 +804,19 @@
"questOtterUnlockText": "Unlocks Otter Eggs for Purchase in the Market",
"questJadeText": "A Jaded Jinx",
"questOtterBoss": "The Plotter",
"questJadeCompletion": "After countless setbacks, you somehow managed to roll the jade boulder up to the top of the mountain! The stony figure catches up to you and smiles. He gives the boulder a gentle push and you watch in horror as it rolls all the way back to the bottom.<br><br>“Why would you do that? Someone will have to do it all over again now!” you balk.<br><br>“Just because you have to do something more than once doesnt mean your accomplishments are meaningless,” the stone figure says. “For now, focus on what you achieved and enjoy a reward!”<br><br>You jolt awake back on your couch, phone fallen to the floor. In its place are three bottles filled with flowing jade! Maybe its time to clean todays dishes and then take a break to see how these potions work on some pet eggs...",
"questJadeCompletion": "After countless setbacks, you somehow managed to roll the jade boulder up to the top of the mountain! The stony figure catches up to you and smiles. He gives the boulder a gentle push and you watch in horror as it rolls all the way back to the bottom.<br><br>“Why would you do that? Someone will have to do it all over again now!” you balk.<br><br>“Just because you have to do something more than once doesnt mean your accomplishments are meaningless,” the stone figure says, “For now, focus on what you achieved and enjoy a reward!”<br><br>You jolt awake back on your couch, phone fallen to the floor. In its place are three bottles filled with flowing jade! Maybe its time to clean todays dishes and then take a break to see how these potions work on some pet eggs",
"questJadeBoss": "Jaded Jinx",
"questJadeDropJadePotion": "Jade Hatching Potion",
"questJadeUnlockText": "Unlocks Jade Hatching Potion for Purchase in the Market.",
"questGiraffeNotes": "Youre strolling across the tall grass of the Sloenstedi Savannah, enjoying a nice walk in nature as a break from your tasks. As you pass through the rolling landscape, you notice a collection of items in the distance. Its a pile of musical instruments, art supplies, electronic equipment, and more! You venture near for a better look.<br><br>“Hey, what do you think youre doing?” yells a voice from behind an acacia. A tall and imposing giraffe emerges, wearing a fancy pair of shades, a guitar, and a fancy camera around its long neck. “This is all my gear. Be careful and dont touch anything!”<br><br>You notice dust on many of the items. “Wow, you sure have a lot of hobbies!” you say. “Can you show me some art or play me a tune?”<br><br>The giraffes face falls as he looks at all his supplies. “I have so much of this stuff but dont know where to begin! Why don't you give me some of your motivation so I can have the productive energy I need to finally get started!”",
"questGiraffeUnlockText": "Unlocks Giraffe Eggs for purchase in the Market.",
"questChameleonNotes": "Its a beautiful day in a warm, rainy corner of the Taskwoods. Youre on the hunt for new additions to your leaf collection when a branch in front of you changes colour without warning! Then it moves!<br><br>Stumbling backwards, you realise this is not a branch at all, but a huge chameleon! Each part of his body keeps changing colours as his eyes dart in different directions.<br><br>“Are you all right?” you ask the chameleon.<br><br>“Ahhh, well,” he says, looking a little flustered. “Ive been trying to blend in… but its so overwhelming… the colours keep coming and going! Its hard to focus on just one....”<br><br>“Aha,” you say, “I think I can help. Well sharpen your focus with a little challenge! Get your colours ready!”<br><br>“Youre on!” replied the chameleon.",
"questCrabNotes": "Its a warm sunny morning, and youre enjoying a visit to the beach to catch up on some of the books on your summer reading list. Youre startled when you nearly step on a shiny crystal near a shallow hole in the sand.<br><br>“Ey, watch where youre goin! Im makin a burrow here!” says a voice. A surprisingly large crab with a decorative shell runs out in front of your toes, snapping her claws as she speaks.<br><br>“Hmm.. is this a burrow?” you ask, looking at the shallow depression. There are shells and crystals arranged around it, but its not much in the way of a hiding place.<br><br>The crab stammers. \"Ey, this is a judgment-free zone! I'm gettin' to it, I'm gettin' to it... I just got caught up on decorating. Sometimes a crab's gotta fiddle,\" she says, adjusting a shell.<br><br>\"Why don't you lend me a claw and help if you've got some big ideas on what a burrow should look like?\"",
"questRaccoonNotes": "Its a warm autumn day in Habitica and youre taking a slow stroll along Conquest Creek. You see some nifty semi-precious stones along the bank that would be perfect for a project youve been planning.<br><br>You start stashing your best finds in a pile beneath a tree. Strangely, each time you return the pile seems to be getting smaller, not larger...<br><br>That can't be right. You look all around but nothing odd stands out. Just as you turn around to head back to the creek, you catch a hand-like paw reaching out of a hollow in the trunk and snatch some of your stones!<br><br>\"Hey!\" you yell, \"Ive been working hard collecting those. Its not cool to take them without asking!\"<br><br>A masked face pops out of the hole and grins at you. \"Finders keepers!\" says the Raccoon. He slips back inside the tree, bags of stones in hand. You dive in after him! These stones are worth fighting for.",
"questDogNotes": "Youve been chosen for an expedition to map Habiticas underground cave systems! Researchers in Habit City theorise that there may be new tools for managing tasks or even undiscovered creatures in these depths.<br><br>As you map rocky tunnels near the foothills of the Meandering Mountains, you notice a glow emanating from a craggy entrance ahead. As you near you see… toys? Stuffed animals and rubber balls are scattered around the cave floor. Is that barking you hear?<br><br>A huge, three-headed dog jumps out, darting for the toy you were right about to pick up! You freeze, almost losing a limb there! But... the dogs mouths seem too occupied with toys to attack?<br><br>“Woof!” one of the dog's mouths barks, dropping a torn toy raccoon. “Are you here to help me clean?? I reeeally need to tidy up but every time I pick up a toy, I just end up playing with it… Here, think fast!!”<br><br>He launches a ball at you, and then another, and another. Those extra heads really make dodging a workout!",
"questCatNotes": "On this fine day you find yourself in Habit City's Enchanted Efficiency Emporium workshop. You've been assigned a tough task: create a new magic motivation spell to help Habiticans everywhere complete their goals with ease.<br><br>Sitting on a table in front of you is a variety of magical objects. All the tomes said they were supposed to resonate together with productive energy… but so far there's not even a spark of motivation.<br><br>The creaking of a door alerts you to a new guest entering your workshop. Scampering feet and a blur of fluff dart onto the table. A... cat? Before you even have a chance to compliment how fluffy she is, she's lifting a paw to one of the crystals you set up and… knocking it off the table!<br><br>\"Hey!\" you shout, \"You're really cute but I'm trying to do some work over here...\"<br><br>She looks at you with her pretty blue eyes, tilts her head, and bats a bundle of herbs off the table. \"I'm helping!\" she purrs.<br><br>You see her paw reaching out toward the rest of the items you've collected and dive to the floor to catch the next one to go down!",
"questOtterNotes": "To-do lists are great! You can spend hours meticulously documenting each step you need to take and feel productive without actually doing those things. Your three-page list gets stuffed into your pocket. Time for a refreshing walk!<br><br>You depart towards the Routine River to take a stroll along the banks. This is exactly what you needed to finally get started! Time to take out your to-do list andah! A gust of wind has your list flying out of your hand and headed right for the water!<br><br>Right before the paper hits the water, an otter head pops up to the surface intercepting the sheets sure demise. Phew! He grasps the list in his paws and a mischievous grin spreads across his face… uh-oh.<br><br>“Hmm...” he hums, flipping the paper around to read your list. “Looks like you need some help prioritising.” Riiiip.<br><br>The otter just ripped your carefully crafted list into pieces! “If you want to get these done, youre going to have to decide whats the most important first!” he says, tossing your list items into the breeze one at a time.",
"questJadeNotes": "Youre in your home staring at the stack of dirty dishes in the sink. The pile of dirty laundry in a random corner of the room. The empty cups and snack wrappers around your desk...<br><br>You sigh. “Why are there always more dishes… ? The mess is never-ending. Its so demotivating.\" You find yourself dazing on the couch, mindlessly scrolling the latest trends. Who knows how long youve been there...<br><br>When you look up from your phone, everything is green. This isnt your living room. Standing up, you find yourself on the side of a shiny verdant mountain.<br><br>Movement in the distance draws your attention. A stony green figure grunts, pushing a boulder up the rocky terrain. He makes some progress, but a small slip of his foot has the shiny boulder rolling back down, right towards you!<br><br>He spots you as he runs towards the chunk of jade hurdling your way! “So you think the dishes are bad?” the figure shouts, “Try this!”",
"questChameleonNotes": "Its a beautiful day in a warm, rainy corner of the Taskwoods. Youre on the hunt for new additions to your leaf collection when a branch in front of you changes colour without warning! Then it moves!<br><br>Stumbling backwards, you realise this is not a branch at all, but a huge chameleon! Each part of his body keeps changing colours as his eyes dart in different directions.<br><br>“Are you all right?” you ask the chameleon.<br><br>“Ahhh, well,” he says, looking a little flustered, “Ive been trying to blend in… but its so overwhelming… the colours keep coming and going! Its hard to focus on just one”<br><br>“Aha,” you say, “I think I can help. Well sharpen your focus with a little challenge! Get your colours ready!”<br><br>“Youre on!” replies the chameleon.",
"questCrabNotes": "Its a warm sunny morning, and youre enjoying a visit to the beach to catch up on some of the books on your summer reading list. Youre startled when you nearly step on a shiny crystal near a shallow hole in the sand.<br><br>“Ey, watch where youre goin! Im makin a burrow here!” says a voice. A surprisingly large crab with a decorative shell runs out in front of your toes, snapping her claws as she speaks.<br><br>“Hmm is this a burrow?” you ask, looking at the shallow depression. There are shells and crystals arranged around it, but its not much in the way of a hiding place.<br><br>The crab stammers. Ey, this is a judgement-free zone! Im gettin to it, Im gettin to it I just got caught up on decorating. Sometimes a crabs gotta fiddle, she says, adjusting a shell.<br><br>Why dont you lend me a claw and help if you've got some big ideas on what a burrow should look like?",
"questRaccoonNotes": "Its a warm autumn day in Habitica and youre taking a slow stroll along Conquest Creek. You see some nifty semi-precious stones along the bank that would be perfect for a project youve been planning.<br><br>You start stashing your best finds in a pile beneath a tree. Strangely, each time you return the pile seems to be getting smaller, not larger<br><br>That cant be right. You look all around but nothing odd stands out. Just as you turn around to head back to the creek, you catch a hand-like paw reaching out of a hollow in the trunk and snatching some of your stones!<br><br>Hey! you yell, Ive been working hard collecting those. Its not cool to take them without asking!<br><br>A masked face pops out of the hole and grins at you. Finders keepers! says the Raccoon. He slips back inside the tree, bags of stones in hand. You dive in after him! These stones are worth fighting for.",
"questDogNotes": "Youve been chosen for an expedition to map Habiticas underground cave systems! Researchers in Habit City theorise that there may be new tools for managing tasks or even undiscovered creatures in these depths.<br><br>As you map rocky tunnels near the foothills of the Meandering Mountains, you notice a glow emanating from a craggy entrance ahead. As you near you see… toys? Stuffed animals and rubber balls are scattered around the cave floor. Is that barking you hear?<br><br>A huge, three-headed dog jumps out, darting for the toy you were right about to pick up! You freeze, almost losing a limb there! But the dogs mouths seem too occupied with toys to attack?<br><br>“Woof!” one of the dogs mouths barks, dropping a torn toy raccoon. “Are you here to help me clean?? I reeeally need to tidy up but every time I pick up a toy, I just end up playing with it… Here, think fast!!”<br><br>He launches a ball at you, and then another, and another. Those extra heads really make dodging a workout!",
"questCatNotes": "On this fine day you find yourself in Habit Citys Enchanted Efficiency Emporium workshop. Youve been assigned a tough task: create a new magic motivation spell to help Habiticans everywhere complete their goals with ease.<br><br>Sitting on a table in front of you is a variety of magical objects. All the tomes said they were supposed to resonate together with productive energy… but so far theres not even a spark of motivation.<br><br>The creaking of a door alerts you to a new guest entering your workshop. Scampering feet and a blur of fluff dart onto the table. A cat? Before you even have a chance to compliment how fluffy she is, shes lifting a paw to one of the crystals you set up and… knocking it off the table!<br><br>Hey! you shout, Youre really cute but I'm trying to do some work over here…”<br><br>She looks at you with her pretty blue eyes, tilts her head, and bats a bundle of herbs off the table. “Im helping! she purrs.<br><br>You see her paw reaching out toward the rest of the items youve collected and dive to the floor to catch the next one to go down!",
"questOtterNotes": "To-do lists are great! You can spend hours meticulously documenting each step you need to take and feel productive without actually doing those things. Your three-page list gets stuffed into your pocket. Time for a refreshing walk!<br><br>You depart towards the Routine River to take a stroll along the banks. This is exactly what you needed to finally get started! Time to take out your to-do list andah! A gust of wind has your list flying out of your hand and headed right for the water!<br><br>Right before the paper hits the water, an otter head pops up to the surface intercepting the sheets sure demise. Phew! He grasps the list in his paws and a mischievous grin spreads across his face… uh-oh.<br><br>“Hmm” he hums, flipping the paper around to read your list, “Looks like you need some help prioritising.” Riiiip.<br><br>The otter just ripped your carefully crafted list into pieces! “If you want to get these done, youre going to have to decide whats the most important first!” he says, tossing your list items into the breeze one at a time.",
"questJadeNotes": "Youre in your home staring at the stack of dirty dishes in the sink; the pile of dirty laundry in a random corner of the room; the empty cups and snack wrappers around your desk<br><br>You sigh, “Why are there always more dishes…? The mess is never-ending. Its so demotivating. You find yourself dazing on the couch, mindlessly scrolling the latest trends. Who knows how long youve been there<br><br>When you look up from your phone, everything is green. This isnt your living room. Standing up, you find yourself on the side of a shiny verdant mountain.<br><br>Movement in the distance draws your attention. A stony green figure grunts, pushing a boulder up the rocky terrain. He makes some progress, but a small slip of his foot has the shiny boulder rolling back down, right towards you!<br><br>He spots you as he runs towards the chunk of jade hurdling your way! “So you think the dishes are bad?” the figure shouts, “Try this!”",
"questCatCompletion": "You've thankfully caught everything that pushy cat knocked off the table. As you sit on the floor you notice a bright glow coming from the objects in front of you. Looking up, the ones on the table are reacting too! Putting them at different elevations seems to be a breakthrough in your research!<br><br>\"You know, in the end you did help me. I guess I just needed some fresh eyes on my task to get me unstuck. I wish you would have given me a bit of a heads-up before you started pushing things around, though,\" you say to the cat, patting her gently.<br><br>\"That's a purrfectly reasonable request, please take these as my apology!\" she purrs, nudging some funny-looking eggs in your direction. \"I'm glad I could help you see things from a different purrspective.\"",
"questOtterCompletion": "As you caught the pieces of your list you started sorting them by which tasks were the most important and ended up with quite a manageable place to start!<br><br>“I see!” you tell the otter, “that goofy stunt really did help me think about what tasks I needed to prioritise.”<br><br>The otter splashes about, rubbing its cheeks with glee, “Im glad my little plot got you thinking about your tasks in a different way.” He dives under the water, resurfacing nearby, “Remember to keep your lists achievable. Rewards help too, so take these!”",
"questCatText": "A Purrplexing Predicament",
@@ -845,10 +845,10 @@
"questAlpacaNotes": "The sun beams down as you hike up the rocky trailheads of the Meandering Mountains. Youve been planning this expedition for your friend group for months, researching every aspect of the trip. The weight of supplies on your back is so much to bear, each step feels more like a burden than an adventure.<br><br>You hear a soft crunch of hooves on the trail behind you. A fluffy alpaca approaches with a gigantic stack of luggage on her back.<br><br>“Seems like youre dragging a bit, friend, and all youre carrying is a little backpack!” she says as she passes by.<br><br>“You make it look so easy,” you sigh. “I planned this trip for so long, but now that were here, Im not even having fun…”<br><br>“Dont get down on yourself,” the alpaca snorts. “Ill teach you a lesson I learned long ago!” She bucks, and suddenly a bundled bedroll is flying at you! How is this helping again?!",
"questAlpacaCompletion": "Luckily none of the bags the alpaca threw your way were heavy, but your hands are definitely full. “What was that about?” you ask, annoyed.<br><br>“If youre planning a trip with friends, you shouldnt be carrying your burden alone! Im sure your friends would rather you shake off a few things onto them than for you to collapse under the weight by yourself. Anyway, you can hand me those bags back. Im a seasoned pack animal and Ive made my point,” she says with a wink. “But keep that blue bundle as a reward for a hard lesson learned. Ill see you at the peak!”",
"questPlatypusText": "The Perfectionist Platypus",
"questOpalNotes": "Habiticas scholars have long searched for the fabled Opal Magic Hatching Potion. A potion so powerful it imbues Pets and their Mount counterparts with fiery colour and brilliance unlike any other gemstone or precious metal. The magic of opals is even rumoured to enhance planning, insight, and creativity. What a boost that would be for your tasks!<br><br>After much searching, you may have finally uncovered the answer. Opal Potions require raw opal stones to be forged with the magic runes of Libra and Mercury. These ancient items can only be found in one place... the perilous ruins of the lost city, on the edge of the Timewaste Desert.<br><br>You arrive at the ruins after days of riding your strongest Mount through the harsh and remote terrain. Among the sun-bleached and broken stones you see a bright glimmer. The search begins!",
"questOpalNotes": "Habiticas scholars have long searched for the fabled Opal Magic Hatching Potion: a potion so powerful it imbues Pets and their Mount counterparts with fiery colour and brilliance unlike any other gemstone or precious metal. The magic of opals is even rumoured to enhance planning, insight, and creativity. What a boost that would be for your tasks!<br><br>After much searching, you may have finally uncovered the answer. Opal Potions require raw opal stones to be forged with the magic runes of Libra and Mercury. These ancient items can only be found in one place the perilous ruins of the lost city, on the edge of the Timewaste Desert.<br><br>You arrive at the ruins after days of riding your strongest Mount through the harsh and remote terrain. Among the sun-bleached and broken stones you see a bright glimmer. The search begins!",
"questOpalCompletion": "At last, tired and dusty, you find the final runes and opal stone needed to forge the Magic Hatching Potion.<br><br>You begin the forging process the minute youre back in Habiticas main city. The power of the runes and opals fills your laboratory with rainbow light! In no time youve got three potions, and youre excited to hatch some new colourful pals.",
"questPlatypusNotes": "Its a beautiful day at Conquest Creek, only made worse by the worksheet in your hand. Why do cool adventures always get ruined by homework? Youre five questions deep about river ecosystems when they hit you with an essay response.<br><br>“Describe how an animal may adapt to river dwelling? Ugh, I dont know...”<br><br>After spending 30 minutes hopelessly stuck on how to even start, you hear a lot of frustrated-sounding splashing down the bank.<br><br>“Augh,” a voice comes bubbling from just under the surface. A frazzled looking platypus pops up. “This burrow isnt coming together at all! Each time I start it just looks wrong.” She dives back under the surface and her broad, flat tail throws a mighty splash right into your face.<br><br>“Wait, dont throw it all away—” you shout, as another slosh of creek water hits you. You may be able to help, and get some inspiration along the way!",
"questPlatypusCompletion": "After an exhausting exchange of water blasts and some encouraging words from your end, the platypus finally stops and comes to the surface with a sigh.<br><br>“You may be right. If I demand flawlessness Im just never going to finish! I can always make adjustments as I go. Seems like you know a little something about perfectionism.”<br><br>You look at your soggy worksheet “Yeah...”<br><br>“Sorry about that,” the platypus says. “Here, as an apology for getting your essay wet, please take some eggs I found in the mud.”",
"questPlatypusNotes": "Its a beautiful day at Conquest Creek, only made worse by the worksheet in your hand. Why do cool adventures always get ruined by homework? Youre five questions deep into river ecosystems when they hit you with an essay response.<br><br>“Describe how an animal may adapt to river dwelling? Ugh, I dont know”<br><br>After spending 30 minutes hopelessly stuck on how to even start, you hear a lot of frustrated-sounding splashing down the bank.<br><br>“Augh,” a voice comes bubbling from just under the surface. A frazzled looking platypus pops up. “This burrow isnt coming together at all! Each time I start it just looks wrong.” She dives back under the surface and her broad, flat tail throws a mighty splash right into your face.<br><br>“Wait, dont throw it all away—” you shout, as another slosh of creek water hits you. You may be able to help, and get some inspiration along the way!",
"questPlatypusCompletion": "After an exhausting exchange of water blasts and some encouraging words from your end, the platypus finally stops and comes to the surface with a sigh.<br><br>“You may be right. If I demand flawlessness Im just never going to finish! I can always make adjustments as I go. Seems like you know a little something about perfectionism.”<br><br>You look at your soggy worksheet “Yeah”<br><br>“Sorry about that,” the platypus says, “Here, as an apology for getting your essay wet, please take some eggs I found in the mud.”",
"questPlatypusBoss": "The Perfectionist Platypus",
"questPlatypusRageTitle": "Shocking Splash",
"questPlatypusRageDescription": "This bar fills when you don't complete your Dailies. When it's full, the Perfectionist Platypus will take away some of your party's MP!",
@@ -860,5 +860,14 @@
"questOpalCollectMercuryRunes": "Mercury Rune",
"questOpalCollectOpalGems": "Opal Gem",
"questOpalDropOpalPotion": "Opal Hatching Potion",
"questOpalUnlockText": "Unlocks Opal Hatching Potions for purchase in the Market"
"questOpalUnlockText": "Unlocks Opal Hatching Potions for purchase in the Market",
"questAlienCompletion": "Youve managed to wrestle back the stolen motivation with your determination and the Fools magic power. As you feel your drive returning, the UFO descends, and a ramp slowly comes out along with a large, green, one-eyed creature. While strange-looking, it doesnt seem threatening.<br><br>“Looks like we went a little far trying to harvest a little extra encouragement from your fine city,” it says. “Apologies for that, and fantastic work on getting it back. The extra aura of your efforts actually charged up the ships engine enough to get us home! Please, take these with our thanks.”<br><br>“Ooh potions,” says the Fool, “How delightful, and how convenient for me that you have them all ready to go!”",
"questAlienText": "Invasion of the Motivation Snatchers",
"questAlienNotes": "Its been a strange few days in Habitica. The great flying saucer still hovers near the Flourishing Fields. It hums oddly. Why is it lingering? April Fools Day has passed, and the Master of Rogues time in the spotlight has ended.<br><br>You wander towards the light of the spaceship. You may as well check it out and get a few steps in while youre at it.<br><br>As you get closer you see the April Fool, looking a bit grim. His face appears greenish in the light of the ships beam.<br><br>“'Twas my plan to get some potions for everyone, a little gift so all could enjoy their little extraterrestrial pals again! But I just cant work up the enthusiasm… I do believe I know why,” the Fool says, nodding toward the beam.<br><br>Little symbols are being sucked up into the ship. Its all your checked off tasks! No wonder your motivations been lackluster.<br><br>“Our motivation is being abducted!” you exclaim. “We have to rescue it before it ends up in deep space somewhere!”<br><br>The Fool smiles. “Concentrate your thoughts on the tasks you know you need to finish! Ill do the rest with a bit of magic.”",
"questAlienBoss": "Encouragement Thief, the Extraterrestrial",
"questAlienRageTitle": "Intergalactic Impediment",
"questAlienRageDescription": "This bar fills when you don't complete your Dailies. When it is full, the Extraterrestrial will discourage you by recovering some of its Health!",
"questAlienRageEffect": "Encouragement Thief uses Intergalactic Impediment! You've backslid right through hyperspace. Your opponent recovers HP!",
"questAlienDropAlienPotion": "Alien Hatching Potion",
"questAlienUnlockText": "Unlocks Alien Hatching Potion for purchase in the Market"
}
+8 -1
View File
@@ -11,5 +11,12 @@
"rebirthPop": "Instantly restart your character as a Level 1 Warrior while retaining achievements, collectibles, and equipment. Your tasks and their history will remain but they will be reset to yellow. Your streaks will be removed except from tasks belonging to active Challenges and Group Plans. Your Gold, Experience, Mana, and the effects of all Skills will be removed. All of this will take effect immediately.",
"rebirthName": "Orb of Rebirth",
"rebirthComplete": "You have been reborn!",
"nextFreeRebirth": "<strong><%= days %> days</strong> until <strong>FREE</strong> Orb of Rebirth"
"nextFreeRebirth": "<strong><%= days %> days</strong> until <strong>FREE</strong> Orb of Rebirth",
"rebirthUnlockedNewItem": "Orb of Rebirth Unlocked",
"rebirthUnlockedOrb": "A new adventure is available!",
"rebirthUnlockedDesc": "Use the Orb of Rebirth to breathe new life into your Habitica adventure once you feel you've achieved it all! Begin again at level 1 while keeping your tasks, Achievements, and Pets with this special item found in the Market.",
"rebirthNewAchievement": "New Achievement",
"rebirthNewAdventure": "A new adventure begins now!",
"rebirthAchievementPlural": "You've used the Orb of Rebirth <strong><%= number %></strong> times, and your highest level reached is <strong><%= level %></strong>.",
"rebirthStackInfo": "This achievement will stack each time you use the Orb of Rebirth."
}
+15 -15
View File
@@ -18,7 +18,7 @@
"resetAccPop": "Start over, removing all levels, gold, gear, history, and tasks.",
"deleteAccount": "Delete Account",
"deleteAccPop": "Cancel and remove your Habitica account.",
"feedback": "If you'd like to give us feedback, please enter it below - we'd love to hear your feedback! It will be anonymous unless you choose to enter your contact details. Don't speak English well? No problem! Use the language you prefer.",
"feedback": "If youd like to give us feedback, please enter it belowwed love to hear your feedback! It will be anonymous unless you choose to enter your contact details. Dont speak English well? No problem! Use the language you prefer.",
"dataExport": "Data Export",
"saveData": "Here are a few options for saving your data.",
"habitHistory": "Habit History",
@@ -38,8 +38,8 @@
"showHeader": "Show Header",
"changePass": "Change Password",
"changeUsername": "Change Username",
"changeEmail": "Change Email Address",
"newEmail": "New Email Address",
"changeEmail": "Change E-mail Address",
"newEmail": "New E-mail Address",
"oldPass": "Old Password",
"newPass": "New Password",
"confirmPass": "Confirm New Password",
@@ -48,7 +48,7 @@
"resetText1": "<b>Be careful!</b> This resets many parts of your account. This is highly discouraged, but some people find it useful in the beginning after playing with the site for a short time.",
"resetText2": "Another option is using an <b>Orb of Rebirth</b>, which will reset everything else while preserving your Tasks and Equipment.",
"deleteLocalAccountText": "<b>Are you sure?</b> This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type your password into the text box below.",
"deleteSocialAccountText": "<b>Are you sure?</b> This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If you're absolutely certain, type <b>\"<%= magicWord %>\"</b> into the text box below.",
"deleteSocialAccountText": "<b>Are you sure?</b> This will delete your account forever, and it can never be restored! You will need to register a new account to use Habitica again. Banked or spent Gems will not be refunded. If youre absolutely certain, type <b><%= magicWord %></b> into the text box below.",
"API": "API",
"APIv3": "API v3",
"APIText": "Copy these for use in third party applications. However, think of your API Token like a password, and do not share it publicly. You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.",
@@ -67,15 +67,15 @@
"invalidPasswordResetCode": "The supplied password reset code is invalid or has expired.",
"passwordChangeSuccess": "Your password was successfully changed to the one you just chose. You can now use it to access your account.",
"displayNameSuccess": "Display name successfully changed",
"emailSuccess": "Email successfully changed",
"emailSuccess": "E-mail successfully changed",
"detachSocial": "De-register <%= network %>",
"detachedSocial": "Successfully removed <%= network %> authentication from your account",
"addedLocalAuth": "Successfully added local authentication",
"data": "Data",
"email": "Email",
"email": "E-mail",
"registerWithSocial": "Register with <%= network %>",
"registeredWithSocial": "Registered with <%= network %>",
"emailNotifications": "Email Notifications",
"emailNotifications": "E-mail Notifications",
"wonChallenge": "You won a Challenge!",
"newPM": "Received Private Message",
"giftedGems": "Gifted Gems",
@@ -95,12 +95,12 @@
"kickedGroup": "Removed from group",
"remindersToLogin": "Reminders to check in to Habitica",
"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=\"/user/settings/notifications\">Settings > &gt; Notifications</a> (requires login).",
"unsubscribedTextUsers": "You have successfully unsubscribed from all Habitica e-mails. You can enable only the e-mails you want to receive from <a href=\"/user/settings/notifications\">Settings > &gt; Notifications</a> (requires login).",
"unsubscribedTextOthers": "You won't receive any other mail from Habitica.",
"unsubscribeAllEmails": "Unsubscribe from Emails",
"unsubscribeAllEmailsText": "Habitica will be unable to notify you via email about important changes to the site or your account.",
"unsubscribeAllEmails": "Unsubscribe from E-mails",
"unsubscribeAllEmailsText": "Habitica will be unable to notify you via e-mail about important changes to the site or your account.",
"unsubscribeAllPush": "Unsubscribe from all Push Notifications",
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from <%= emailType %> e-mails.",
"subscriptionRateText": "Recurring <strong>$<%= price %> USD</strong> every <strong><%= months %> months</strong>",
"benefits": "Benefits",
"coupon": "Coupon",
@@ -111,7 +111,7 @@
"promoPlaceholder": "Enter Promotion Code",
"saveCustomDayStart": "Save Custom Day Start",
"registration": "Registration",
"addLocalAuth": "Add Email and Password Login",
"addLocalAuth": "Add E-mail and Password Login",
"generateCodes": "Generate Codes",
"generate": "Generate",
"getCodes": "Get Codes",
@@ -167,7 +167,7 @@
"contentRelease": "Content releases + Events",
"bannedWordUsedInProfile": "Your Display Name or About text contained inappropriate language.",
"changeDisplayNameDisclaimer": "This is the name that will be displayed for your Avatar in Habitica.",
"changePasswordDisclaimer": "Passwords must be 8 characters or more. Changing your password will log you out of any other devices and third party tools you may use.",
"changePasswordDisclaimer": "Passwords must be 8 characters or more. Changing your password will log you out of any other devices and third-party tools you may use.",
"dateFormatDisclaimer": "Adjust the date formatting across Habitica.",
"enableAudio": "Enable Audio",
"playDemoAudio": "Play Demo",
@@ -194,7 +194,7 @@
"connect": "Connect",
"remove": "Remove",
"resetTextLocal": "If you're absolutely certain, type your password into the text box below.",
"resetTextSocial": "If you're absolutely certain, type <b>\"<%= magicWord %>\"</b> into the text box below.",
"resetTextSocial": "If youre absolutely certain, type <b><%= magicWord %></b> into the text box below.",
"APITokenDisclaimer": "<b>Your API Token is like a password; Do not share it publicly.</b> You may occasionally be asked for your User ID, but never post your API Token where others can see it, including on Github.<br><br><b>If you need a new API Token</b> (e.g., if you accidentally shared it), you can change your password to reset it. Once it is reset, you will need to log back in to any other devices you use Habitica on and provide the new API Token to third-party tools you may use.",
"audioThemeDisclaimer": "Audio themes add optional sound effects to the Habitica website. Volume levels are controlled using your computer's volume settings.",
"gemCap": "Gem Cap",
@@ -256,7 +256,7 @@
"APITokenTitle": "API Token",
"userNameSuccess": "Username successfully changed",
"addWebhook": "Add Webhook",
"changeEmailDisclaimer": "This is the email address that you use to log in to Habitica, as well as receive notifications.",
"changeEmailDisclaimer": "This is the e-mail address that you use to log in to Habitica, as well as receive notifications.",
"transaction_subscription_bonus": "<b>Subscription</b> bonus",
"privacySettingsOverview": "Habitica uses cookies to analyse performance, handle support requests, and provide you with the best possible gamified experience. To do that, we need to request the following permissions. You can change these at any time from your account settings.",
"privacyOverview": "In today's world, it feels like every company is looking to profit from your data. This can make it difficult to find the right app to improve your habits. Habitica uses cookies that store data only to analyse performance, handle support requests, and provide you with the best possible gamified experience. You can change this at any time from your account settings.",
+1 -1
View File
@@ -49,7 +49,7 @@
"spellSpecialSandText": "Sand",
"spellSpecialSandNotes": "Reverse the spell that made you a sea star.",
"partyNotFound": "Party not found",
"targetIdUUID": "\"targetId\" must be a valid User ID.",
"targetIdUUID": "targetId must be a valid User ID.",
"challengeTasksNoCast": "Casting a skill on challenge tasks is not allowed.",
"groupTasksNoCast": "Casting a skill on group tasks is not allowed.",
"spellNotOwned": "You don't own this skill.",
+27 -7
View File
@@ -14,14 +14,14 @@
"nowSubscribed": "You are now subscribed to Habitica!",
"cancelSub": "Cancel Subscription",
"cancelSubInfoGroupPlan": "Because you have a free subscription from a Group Plan, you cannot cancel it. It will end when you are no longer a member of the Group Plan. If you are the Group leader and want to cancel the Group Plan, you can do that from the Group Plans “Group Billing” tab.",
"cancelingSubscription": "Canceling the subscription",
"cancelingSubscription": "Cancelling the subscription",
"contactUs": "Contact Us",
"checkout": "Checkout",
"subGemPop": "Because you subscribe to Habitica, you can purchase a number of Gems each month using Gold.",
"subGemName": "Subscriber Gems",
"maxBuyGems": "You have bought all the Gems you can this month. More become available within the first three days of each month. Thanks for subscribing!",
"timeTravelers": "Time Travellers",
"timeTravelersPopoverNoSubMobile": "Subscribers receive a rare Mystic Hourglass every month to use in the Time Travellers Shop!",
"timeTravelers": "Time Travellers",
"timeTravelersPopoverNoSubMobile": "Subscribers receive a rare Mystic Hourglass every month to use in the Time Travellers Shop!",
"timeTravelersPopover": "Your Mystic Hourglass has opened our time portal! Choose what youd like us to fetch from the past or future.",
"mysterySetNotFound": "Mystery set not found, or set already owned.",
"mysteryItemIsEmpty": "Mystery items are empty",
@@ -120,11 +120,11 @@
"gemBenefit2": "Backgrounds to immerse your avatar in the world of Habitica!",
"gemBenefit3": "Exciting Quest chains that drop pet eggs.",
"gemBenefit4": "Reset your avatar's Stat Points and change its Class.",
"subscriptionBenefit1": "Get up to 50 Gold-purchasable Gems in the Market to buy Quests, Customizations, Pets, and more!",
"subscriptionBenefit1": "Get up to 50 Gold-purchasable Gems in the Market to buy Quests, Customisations, Pets, and more!",
"subscriptionBenefit3": "Find double the Eggs, Hatching Potions, and Food each day to grow your Pet collection!",
"subscriptionBenefit4": "Stay decked out in the latest exclusive gear. Subscribe now to get <%= month %>s <%= currentMysterySetName %>!",
"subscriptionBenefit5": "Get the exclusive Royal Purple Jackalope when you subscribe today!",
"subscriptionBenefit6": "Never miss an item with 1 Mystic Hourglass a month to use in the Time Travellers Shop!",
"subscriptionBenefit6": "Never miss an item with 1 Mystic Hourglass a month to use in the Time Travellers Shop!",
"purchaseAll": "Purchase Set",
"gemsRemaining": "remaining",
"notEnoughGemsToBuy": "No more Gems available for purchase this month. More will become available within the first 3 days of each month.",
@@ -154,7 +154,7 @@
"subMonths": "Months Subscribed",
"subscriptionStats": "Subscription Stats",
"subscriptionInactiveDate": "Your subscription benefits will become inactive on <br><strong><%= date %></strong>",
"subscriptionCanceled": "Your subscription is canceled",
"subscriptionCanceled": "Your subscription is cancelled",
"youAreSubscribed": "You are subscribed to Habitica",
"doubleDropCap": "Double the Drops",
"monthlyMysteryItems": "Limited Monthly Gear Sets",
@@ -257,5 +257,25 @@
"giftSubscriptionLeadText": "Choose the subscription you'd like to gift below! This purchase will not automatically renew.",
"oneMonthGift": "For 1 month",
"subscribe": "Subscribe",
"resubscribeToPickUp": "Resubscribe to pick up where you left off!"
"resubscribeToPickUp": "Resubscribe to pick up where you left off!",
"mysterySet202508": "Brilliant Blade Set",
"mysterySet202507": "Spirited Skater Set",
"unlockNGemsGift": "They'll unlock <strong><%= count %> Gems</strong> per month in the Market",
"maxGemCapGift": "They'll have the max <strong>Gem Cap</strong>",
"subscriptionBillingFYIShort": "Subscriptions automatically renew unless you cancel at least 24 hours before the end of the current period. Your account will be charged within 24 hours of your renewal date, at the same price you initially paid.",
"maxGemCap": "Instantly start at the max <strong>Gem Cap</strong>",
"subscriptionChangeAnnouncement": "<strong>Subscription benefits and the way they are sent out will be changing on 19 November.</strong> <%= linkStart %>Click here</a> to read more about this change.",
"recurringNMonthly": "Recurring every <%= length %> months",
"unlockNGems": "Unlock <strong><%= count %> Gems</strong> per month in the Market",
"earn2Gems": "Earn <strong>+2 Gems</strong> every month you're subscribed",
"nMonthsGift": "For <%= months %> months",
"earn2GemsGift": "They'll earn <strong>+2 Gems</strong> every month they're subscribed",
"mysterySet202603": "Wisteria Wizard Set",
"mysterySet202604": "Audacious Astronaut Set",
"mysterySet202605": "Nightfall Nimbus Set",
"mysterySet202512": "Biscuit Champion Set",
"mysterySet202601": "Winter's Aegis Set",
"mysterySet202602": "Sakura Fox Set",
"immediate12Hourglasses": "Get <strong>12 Mystic Hourglasses</strong> immediately after your first 12-month subscription!",
"subscriptionBillingFYI": "Subscriptions automatically renew unless you cancel at least 24 hours before the end of the current period. You can manage your subscription from the Subscription tab in the settings. Your account will be charged within 24 hours of your renewal date, at the same price you initially paid."
}
+12 -10
View File
@@ -2,7 +2,7 @@
"clearCompleted": "Delete Completed",
"clearCompletedDescription": "Completed To Do's are deleted after 30 days for non-subscribers and 90 days for subscribers.",
"clearCompletedConfirm": "Are you sure you want to delete your completed To Do's?",
"addMultipleTip": "<strong>Tip:</strong> To add multiple <%= taskType %>, separate each one using a line break (Shift + Enter) and then press \"Enter.\"",
"addMultipleTip": "<strong>Tip:</strong> To add multiple <%= taskType %>, separate each one using a line break (Shift + Enter) and then press Enter.",
"addATask": "Add a <%= type %>",
"editATask": "Edit <%= type %>",
"createTask": "Create <%= type %>",
@@ -66,7 +66,7 @@
"price": "Price",
"tags": "Tags",
"newTag": "New Tag",
"toRequired": "You must supply a \"to\" property",
"toRequired": "You must supply a “to” property",
"startDate": "Start Date",
"streaks": "Streak Achievements",
"streakName": "<%= count %> Streak Achievements",
@@ -85,16 +85,16 @@
"taskToTop": "To top",
"taskToBottom": "To bottom",
"taskAliasAlreadyUsed": "Task alias already used on another task.",
"invalidTaskType": "Task type must be one of \"habit\", \"daily\", \"todo\", \"reward\".",
"invalidTasksType": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\".",
"invalidTasksTypeExtra": "Task type must be one of \"habits\", \"dailys\", \"todos\", \"rewards\", \"completedTodos\".",
"invalidTaskType": "Task type must be one of habit”, “daily”, “todo”, “reward.",
"invalidTasksType": "Task type must be one of habits”, “dailys”, “todos”, “rewards.",
"invalidTasksTypeExtra": "Task type must be one of habits”, “dailys”, “todos”, “rewards”, “completedTodos.",
"cantDeleteChallengeTasks": "A task belonging to a challenge can't be deleted.",
"checklistOnlyDailyTodo": "Checklists are supported only on Dailies and To Do's",
"checklistItemNotFound": "No checklist item was found with given id.",
"itemIdRequired": "\"itemId\" must be a valid UUID.",
"itemIdRequired": "itemId must be a valid UUID.",
"tagNotFound": "No tag item was found with given ID.",
"tagIdRequired": "\"tagId\" must be a valid UUID corresponding to a tag belonging to the user.",
"positionRequired": "\"position\" is required and must be a number.",
"tagIdRequired": "tagId must be a valid UUID corresponding to a tag belonging to the user.",
"positionRequired": "position is required and must be a number.",
"cantMoveCompletedTodo": "Can't move a completed to-do.",
"alreadyTagged": "The task is already tagged with given tag.",
"taskRequiresApproval": "This task must be approved before you can complete it. Approval has already been requested",
@@ -126,7 +126,7 @@
"deleteTaskType": "Delete this <%= type %>",
"pressEnterToAddTag": "Press Enter to add tag: '<%= tagName %>'",
"enterTag": "Enter a tag",
"addTags": "Add tags...",
"addTags": "Add tags",
"tomorrow": "Tomorrow",
"counter": "Counter",
"taskSummary": "<%= type %> Summary",
@@ -141,5 +141,7 @@
"deleteType": "Delete <%= type %>",
"deleteTask": "Delete Task",
"deleteXTasks": "Delete <%= count %> Tasks",
"sureDeleteType": "Are you sure you want to delete this task?"
"sureDeleteType": "Are you sure you want to delete this task?",
"brokenChallengeTaskCount": "This is one of <%= count %> tasks that are part of a Challenge that no longer exists.",
"confirmDeleteTasks": "Would you like to delete the tasks?"
}
+2 -1
View File
@@ -410,5 +410,6 @@
"questEggPlatypusMountText": "Marsupial",
"hatchingPotionOpal": "Ópalo",
"questEggPlatypusText": "Ornitorrinco",
"questEggPlatypusAdjective": "meticulosa"
"questEggPlatypusAdjective": "meticulosa",
"hatchingPotionAlien": "extraterrestre"
}
+4 -2
View File
@@ -110,7 +110,7 @@
"missingPassword": "Falta la contraseña.",
"missingNewPassword": "Falta una nueva contraseña.",
"invalidEmailDomain": "No puedes registrar con emails con los siguientes dominios: <%= domains %>",
"wrongPassword": "Contraseña errónea. Si has olvidado tu contraseña, haz clic en \"He olvidado la contraseña\".",
"wrongPassword": "Contraseña incorrecta. Si has olvidado tu contraseña, haz clic en \"He olvidado mi contraseña.\"",
"incorrectDeletePhrase": "Por favor, teclea <%= magicWord %> en mayúsculas para eliminar tu cuenta.",
"notAnEmail": "La dirección de correo electrónico no es válida.",
"emailTaken": "Ya existe una cuenta con esa dirección de correo electrónico.",
@@ -187,5 +187,7 @@
"minPasswordLengthLogin": "Tu contraseña contiene al menos 8 caracteres.",
"enterValidEmail": "Por favor introduce una dirección de correo electrónico válida.",
"whatToCallYou": "¿Cómo debemos llamarte?",
"acceptPrivacyTOS": "Confirmas que tienes al menos 18 años y que has leído y estás de acuerdo con nuestros <a href='/static/terms' target='_blank'>Terminos de Servicio</a>y<a href='/static/privacy' target='_blank'>Política de Privacidad</a>"
"acceptPrivacyTOS": "Confirmas que tienes al menos 18 años y que has leído y estás de acuerdo con nuestros <a href='/static/terms' target='_blank'>Terminos de Servicio</a>y<a href='/static/privacy' target='_blank'>Política de Privacidad</a>",
"emailAddress": "Dirección de correo electrónico",
"emailRequiredForSupport": "Necesitamos una dirección de correo electrónico para brindarte asistencia. Por favor, introduce una dirección de correo electrónico para continuar creando tu cuenta."
}
+3 -1
View File
@@ -243,5 +243,7 @@
"targetUserNotExist": "Usuario objetivo: '<%= userName %>' no existe.",
"rememberToBeKind": "Por favor recuerda ser bondadoso, respetuoso y seguir las <a href='/static/community-guidelines' target='_blank'>Normas de la Comunidad</a>.",
"gem": "Gema",
"confirmPurchase": "Confirmar Compra"
"confirmPurchase": "Confirmar Compra",
"avoidSPI": "Evita IPS",
"avoidSPIDetails": "Por tu privacidad, evita incluir <%= firstLink %>información personal sensible<%= linkClose %> (IPS) cuando uses Habitica. La información de tu cuenta, incluyendo tareas, es almacenada en nuestros servidores para que puedas acceder a ella desde cualquier dispositivo.<br><br>Para saber más, revisa nuestra <%= secondLink %>Política de Privacidad<%= linkClose %>."
}
+16 -7
View File
@@ -83,7 +83,7 @@
"allocatePerPop": "Añadir un punto a Percepción",
"allocateInt": "Puntos asignados a Inteligencia:",
"allocateIntPop": "Añadir un punto a Inteligencia",
"noMoreAllocate": "Ahora que has alcanzado el nivel 100, ya no ganarás más Puntos de Atributo. Puedes seguir subiendo de nivel, o empezar una nueva aventura desde el nivel 1 utilizando la <a href='/shops/market'>Esfera de Renacimiento</a>!",
"noMoreAllocate": "Ahora que has alcanzado el nivel 100, ya no ganarás más Puntos de Atributo. Puedes seguir subiendo de nivel, o empezar una nueva aventura desde el nivel 1 utilizando el <a href='/shops/market'>Orbe del Renacimiento</a>.",
"stats": "Atributos",
"strength": "Fuerza",
"strText": "La Fuerza aumenta la probabilidad de conseguir \"golpes críticos\" aleatorios y el Oro, la Experiencia y la probabilidad de conseguir botín al asestarlos. También ayuda a hacer daño a los monstruos jefe.",
@@ -115,11 +115,11 @@
"autoAllocation": "Asignación Automática",
"autoAllocationPop": "Asigna Puntos a los Atributos de acuerdo a tus preferencias cuando subes de nivel.",
"evenAllocation": "Distribuir los Puntos de Atributo equitativamente",
"evenAllocationPop": "Asigna el mismo número de Puntos a cada Atributo.",
"evenAllocationPop": "Asigna el mismo número de Puntos a cada Atributo",
"classAllocation": "Distribuir Puntos de acuerdo a tu Clase",
"classAllocationPop": "Asignar más Puntos a los Atributos importantes para tu Clase.",
"taskAllocation": "Distribuir Puntos según la actividad de tus tareas",
"taskAllocationPop": "Asigna Puntos basándose en las categorías de Fuerza, Inteligencia, Constitución y Percepción asociadas a las tareas que completas.",
"classAllocationPop": "Asigna más Puntos a los Atributos importantes para tu Clase",
"taskAllocation": "Distribuye los Puntos según la actividad de tus tareas",
"taskAllocationPop": "Asigna Puntos basándose en las categorías de Fuerza, Inteligencia, Constitución y Percepción asociadas a las tareas que completas",
"distributePoints": "Distribuir Puntos no Asignados",
"distributePointsPop": "Asigna todos los Puntos no Asignados según el esquema de asignación seleccionado.",
"warriorText": "Los Guerreros consiguen más y mejores \"golpes críticos\", los cuales otorgan bonus de Oro, Experiencia y probabilidad de botín al completar una tarea. También hacen graves daños a los monstruos jefes. ¡Juega como un Guerrero si te motivan las recompensas impredecibles como al ganar la lotería o si deseas ser la fuente de daño en las Misiones!",
@@ -178,7 +178,7 @@
"mainHand": "Mano Principal",
"offHand": "Mano Secundaria",
"statPoints": "Puntos de estadisticas",
"pts": "puntos",
"pts": "PTOS",
"purchaseForGold": "¿Comprar por <%= cost %> monedas de oro?",
"chatCastSpellUser": "<%= username %> lanza <%= spell %> sobre <%= target %>.",
"chatCastSpellParty": "<%= username %> lanza <%= spell %> para el grupo.",
@@ -191,5 +191,14 @@
"titleHairbase": "Estilos de Pelo",
"customizations": "Personalizaciónes",
"nextReward": "Próxima Recompensa de Log in",
"skins": "Pieles"
"skins": "Pieles",
"perTaskText": "Aumenta las probabilidades de obtener botín, el límite de botín diario, los bonus por racha de tareas completadas, y Oro obtenido cuando completas tareas.",
"autoAllocate": "Auto Asignar",
"pointsAvailable": "Puntos Disponibles",
"allocationMethod": "Método de Asignación",
"statAllocationInfo": "Cada nivel te proporciona un Punto de Atributo para asignar a la Estadística de tu elección. Puedes asignarlo manualmente, o dejar que el juego decida por ti mediante la opción de Asignación Automática.",
"assignedStat": "Estadística Asignada",
"strTaskText": "Aumenta las probabilidades de golpe y daño crítico cuando completas tareas. También aumenta el daño aplicado a Jefes de Misión.",
"conTaskText": "Reduce el daño recibido por Tareas Diarias no completadas y Hábitos negativos. No reduce el daño recibido de Jefes de Misión.",
"intTaskText": "Aumenta la Experiencia obtenida por completar tareas. También aumenta tu límite de Maná y la velocidad con la que lo recuperas."
}

Some files were not shown because too many files have changed in this diff Show More