[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-env\",\n    \"@babel/preset-react\",\n    [\n      \"@babel/preset-typescript\",\n      {\n        \"optimizeConstEnums\": true\n      }\n    ]\n  ],\n  \"plugins\": [\"babel-plugin-styled-components\"]\n}\n"
  },
  {
    "path": ".dockerignore",
    "content": "node_modules\n"
  },
  {
    "path": ".eslintignore",
    "content": "public/\ndeclarations/\ndist/\nplugins/\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# production\n/dist\n\n# misc\n.DS_Store\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# firebase\n.firebase\n\n# local image files for testing\npublic/img/artifacts\n\ndeclarations\ntsconfig.ref.tsbuildinfo\n\n# Local Netlify folder\n.netlify\n\n# Environment variables\n.env\n"
  },
  {
    "path": ".nvmrc",
    "content": "16\n"
  },
  {
    "path": ".prettierignore",
    "content": "dist/\ndeclarations/\nplugins/dist/\nembedded_plugins/dist/\npublic/\nsrc/styles/font/\nsrc/styles/icomoon/\nembedded_plugins/Remote-Explorer.ts\nembedded_plugins/Renderer-Showcase.ts\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# Dark Forest Client\n\n## Development Guide\n\n### Installing Core Dependencies\n\n- Node (v14.x OR v16.x)\n- Yarn (Javascript Package Manager)\n\n#### Installing The Correct Node Version Using NVM\n\nDark Forest is built and tested using Node.js v14/v16 and might not run properly on other Node.js versions. We recommend using NVM to switch between multiple Node.js version on your machine.\n\n```sh\ncurl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.3/install.sh | bash\nnvm install\n```\n\nAfter the installation is finished, you can run `node --version` to verify that you are running v14 or v16\n\n#### Installing Yarn\n\nRefer to [Yarn's official documentation](https://classic.yarnpkg.com/en/docs/install) for the installation guide.\n\nAfter you have Yarn installed, run `yarn` to install the dependencies:\n\n### Running the client\n\nTo connecting to the mainnet client, simply run `yarn start:prod`. When asked you can use your whitelist key or import your mainnet burner secret and home coordinates.\n\n### Plugin development\n\nYou can develop plugins for Dark Forest either inside this game client repository, or externally using something like https://github.com/Bind/my-first-plugin. In either case, you'll want to use the [`df-plugin-dev-server`](https://github.com/projectsophon/df-plugin-dev-server).\n\nYou can install it as a global command, using:\n\n```sh\nnpm install -g @projectsophon/df-plugin-dev-server\n```\n\nOnce it is installed, you can run it inside this project repository, using:\n\n```sh\ndf-plugin-dev-server\n```\n\nYou can then add or modify any plugins inside the [`plugins/`](./plugins) directory and they will be automatically bundled and served as plugins you can import inside the game!\n\nAnd then load your plugin in the game client, like so:\n\n```js\n// Replace PluginTemplate.js with the name of your Plugin\n// And `.ts` extensions become `.js`\nexport { default } from 'http://127.0.0.1:2222/PluginTemplate.js?dev';\n```\n\n### Embedded plugins\n\nThe Dark Forest client ships with some game \"plugins\" embedded in the game client. The source code for these plugins exists at [`embedded_plugins/`](./embedded_plugins). You are able to edit them inside the game and the changes will persist. If you change the source code directly, you must delete the plugin in-game and reload your browser to import the new code.\n"
  },
  {
    "path": "docs/README.md",
    "content": "# client\n\n## Table of contents\n\n### Modules\n\n- [Backend/GameLogic/ArrivalUtils](modules/Backend_GameLogic_ArrivalUtils.md)\n- [Backend/GameLogic/CaptureZoneGenerator](modules/Backend_GameLogic_CaptureZoneGenerator.md)\n- [Backend/GameLogic/ContractsAPI](modules/Backend_GameLogic_ContractsAPI.md)\n- [Backend/GameLogic/GameManager](modules/Backend_GameLogic_GameManager.md)\n- [Backend/GameLogic/GameObjects](modules/Backend_GameLogic_GameObjects.md)\n- [Backend/GameLogic/GameUIManager](modules/Backend_GameLogic_GameUIManager.md)\n- [Backend/GameLogic/InitialGameStateDownloader](modules/Backend_GameLogic_InitialGameStateDownloader.md)\n- [Backend/GameLogic/LayeredMap](modules/Backend_GameLogic_LayeredMap.md)\n- [Backend/GameLogic/PluginManager](modules/Backend_GameLogic_PluginManager.md)\n- [Backend/GameLogic/TutorialManager](modules/Backend_GameLogic_TutorialManager.md)\n- [Backend/GameLogic/ViewportEntities](modules/Backend_GameLogic_ViewportEntities.md)\n- [Backend/Miner/ChunkUtils](modules/Backend_Miner_ChunkUtils.md)\n- [Backend/Miner/MinerManager](modules/Backend_Miner_MinerManager.md)\n- [Backend/Miner/MiningPatterns](modules/Backend_Miner_MiningPatterns.md)\n- [Backend/Miner/permutation](modules/Backend_Miner_permutation.md)\n- [Backend/Network/AccountManager](modules/Backend_Network_AccountManager.md)\n- [Backend/Network/Blockchain](modules/Backend_Network_Blockchain.md)\n- [Backend/Network/EventLogger](modules/Backend_Network_EventLogger.md)\n- [Backend/Network/LeaderboardApi](modules/Backend_Network_LeaderboardApi.md)\n- [Backend/Network/MessageAPI](modules/Backend_Network_MessageAPI.md)\n- [Backend/Network/NetworkHealthApi](modules/Backend_Network_NetworkHealthApi.md)\n- [Backend/Network/UtilityServerAPI](modules/Backend_Network_UtilityServerAPI.md)\n- [Backend/Plugins/EmbeddedPluginLoader](modules/Backend_Plugins_EmbeddedPluginLoader.md)\n- [Backend/Plugins/PluginProcess](modules/Backend_Plugins_PluginProcess.md)\n- [Backend/Plugins/PluginTemplate](modules/Backend_Plugins_PluginTemplate.md)\n- [Backend/Plugins/SerializedPlugin](modules/Backend_Plugins_SerializedPlugin.md)\n- [Backend/Storage/PersistentChunkStore](modules/Backend_Storage_PersistentChunkStore.md)\n- [Backend/Storage/ReaderDataStore](modules/Backend_Storage_ReaderDataStore.md)\n- [Backend/Utils/Animation](modules/Backend_Utils_Animation.md)\n- [Backend/Utils/Coordinates](modules/Backend_Utils_Coordinates.md)\n- [Backend/Utils/SnarkArgsHelper](modules/Backend_Utils_SnarkArgsHelper.md)\n- [Backend/Utils/Utils](modules/Backend_Utils_Utils.md)\n- [Backend/Utils/WhitelistSnarkArgsHelper](modules/Backend_Utils_WhitelistSnarkArgsHelper.md)\n- [Backend/Utils/Wrapper](modules/Backend_Utils_Wrapper.md)\n- [Frontend/Components/AncientLabel](modules/Frontend_Components_AncientLabel.md)\n- [Frontend/Components/ArtifactImage](modules/Frontend_Components_ArtifactImage.md)\n- [Frontend/Components/BiomeAnims](modules/Frontend_Components_BiomeAnims.md)\n- [Frontend/Components/Btn](modules/Frontend_Components_Btn.md)\n- [Frontend/Components/Button](modules/Frontend_Components_Button.md)\n- [Frontend/Components/CapturePlanetButton](modules/Frontend_Components_CapturePlanetButton.md)\n- [Frontend/Components/CoreUI](modules/Frontend_Components_CoreUI.md)\n- [Frontend/Components/Corner](modules/Frontend_Components_Corner.md)\n- [Frontend/Components/DisplayGasPrices](modules/Frontend_Components_DisplayGasPrices.md)\n- [Frontend/Components/Email](modules/Frontend_Components_Email.md)\n- [Frontend/Components/GameLandingPageComponents](modules/Frontend_Components_GameLandingPageComponents.md)\n- [Frontend/Components/GameWindowComponents](modules/Frontend_Components_GameWindowComponents.md)\n- [Frontend/Components/Icons](modules/Frontend_Components_Icons.md)\n- [Frontend/Components/Input](modules/Frontend_Components_Input.md)\n- [Frontend/Components/Labels/ArtifactLabels](modules/Frontend_Components_Labels_ArtifactLabels.md)\n- [Frontend/Components/Labels/BiomeLabels](modules/Frontend_Components_Labels_BiomeLabels.md)\n- [Frontend/Components/Labels/KeywordLabels](modules/Frontend_Components_Labels_KeywordLabels.md)\n- [Frontend/Components/Labels/Labels](modules/Frontend_Components_Labels_Labels.md)\n- [Frontend/Components/Labels/LavaLabel](modules/Frontend_Components_Labels_LavaLabel.md)\n- [Frontend/Components/Labels/LegendaryLabel](modules/Frontend_Components_Labels_LegendaryLabel.md)\n- [Frontend/Components/Labels/MythicLabel](modules/Frontend_Components_Labels_MythicLabel.md)\n- [Frontend/Components/Labels/PlanetLabels](modules/Frontend_Components_Labels_PlanetLabels.md)\n- [Frontend/Components/Labels/SpacetimeRipLabel](modules/Frontend_Components_Labels_SpacetimeRipLabel.md)\n- [Frontend/Components/Labels/WastelandLabel](modules/Frontend_Components_Labels_WastelandLabel.md)\n- [Frontend/Components/LoadingSpinner](modules/Frontend_Components_LoadingSpinner.md)\n- [Frontend/Components/MaybeShortcutButton](modules/Frontend_Components_MaybeShortcutButton.md)\n- [Frontend/Components/MineArtifactButton](modules/Frontend_Components_MineArtifactButton.md)\n- [Frontend/Components/Modal](modules/Frontend_Components_Modal.md)\n- [Frontend/Components/OpenPaneButtons](modules/Frontend_Components_OpenPaneButtons.md)\n- [Frontend/Components/PluginModal](modules/Frontend_Components_PluginModal.md)\n- [Frontend/Components/ReadMore](modules/Frontend_Components_ReadMore.md)\n- [Frontend/Components/RemoteModal](modules/Frontend_Components_RemoteModal.md)\n- [Frontend/Components/Row](modules/Frontend_Components_Row.md)\n- [Frontend/Components/Slider](modules/Frontend_Components_Slider.md)\n- [Frontend/Components/Text](modules/Frontend_Components_Text.md)\n- [Frontend/Components/TextLoadingBar](modules/Frontend_Components_TextLoadingBar.md)\n- [Frontend/Components/TextPreview](modules/Frontend_Components_TextPreview.md)\n- [Frontend/Components/Theme](modules/Frontend_Components_Theme.md)\n- [Frontend/Components/TimeUntil](modules/Frontend_Components_TimeUntil.md)\n- [Frontend/Game/ControllableCanvas](modules/Frontend_Game_ControllableCanvas.md)\n- [Frontend/Game/ModalManager](modules/Frontend_Game_ModalManager.md)\n- [Frontend/Game/NotificationManager](modules/Frontend_Game_NotificationManager.md)\n- [Frontend/Game/Popups](modules/Frontend_Game_Popups.md)\n- [Frontend/Game/Viewport](modules/Frontend_Game_Viewport.md)\n- [Frontend/Pages/App](modules/Frontend_Pages_App.md)\n- [Frontend/Pages/CreateLobby](modules/Frontend_Pages_CreateLobby.md)\n- [Frontend/Pages/EventsPage](modules/Frontend_Pages_EventsPage.md)\n- [Frontend/Pages/GameLandingPage](modules/Frontend_Pages_GameLandingPage.md)\n- [Frontend/Pages/GifMaker](modules/Frontend_Pages_GifMaker.md)\n- [Frontend/Pages/LandingPage](modules/Frontend_Pages_LandingPage.md)\n- [Frontend/Pages/LoadingPage](modules/Frontend_Pages_LoadingPage.md)\n- [Frontend/Pages/LobbyLandingPage](modules/Frontend_Pages_LobbyLandingPage.md)\n- [Frontend/Pages/NotFoundPage](modules/Frontend_Pages_NotFoundPage.md)\n- [Frontend/Pages/ShareArtifact](modules/Frontend_Pages_ShareArtifact.md)\n- [Frontend/Pages/SharePlanet](modules/Frontend_Pages_SharePlanet.md)\n- [Frontend/Pages/TestArtifactImages](modules/Frontend_Pages_TestArtifactImages.md)\n- [Frontend/Pages/TxConfirmPopup](modules/Frontend_Pages_TxConfirmPopup.md)\n- [Frontend/Pages/UnsubscribePage](modules/Frontend_Pages_UnsubscribePage.md)\n- [Frontend/Pages/ValhallaPage](modules/Frontend_Pages_ValhallaPage.md)\n- [Frontend/Panes/ArtifactCard](modules/Frontend_Panes_ArtifactCard.md)\n- [Frontend/Panes/ArtifactDetailsPane](modules/Frontend_Panes_ArtifactDetailsPane.md)\n- [Frontend/Panes/ArtifactHoverPane](modules/Frontend_Panes_ArtifactHoverPane.md)\n- [Frontend/Panes/ArtifactsList](modules/Frontend_Panes_ArtifactsList.md)\n- [Frontend/Panes/BroadcastPane](modules/Frontend_Panes_BroadcastPane.md)\n- [Frontend/Panes/CoordsPane](modules/Frontend_Panes_CoordsPane.md)\n- [Frontend/Panes/DiagnosticsPane](modules/Frontend_Panes_DiagnosticsPane.md)\n- [Frontend/Panes/ExplorePane](modules/Frontend_Panes_ExplorePane.md)\n- [Frontend/Panes/HatPane](modules/Frontend_Panes_HatPane.md)\n- [Frontend/Panes/HelpPane](modules/Frontend_Panes_HelpPane.md)\n- [Frontend/Panes/HoverPane](modules/Frontend_Panes_HoverPane.md)\n- [Frontend/Panes/HoverPlanetPane](modules/Frontend_Panes_HoverPlanetPane.md)\n- [Frontend/Panes/Lobbies/AdminPermissionsPane](modules/Frontend_Panes_Lobbies_AdminPermissionsPane.md)\n- [Frontend/Panes/Lobbies/ArtifactSettingsPane](modules/Frontend_Panes_Lobbies_ArtifactSettingsPane.md)\n- [Frontend/Panes/Lobbies/CaptureZonesPane](modules/Frontend_Panes_Lobbies_CaptureZonesPane.md)\n- [Frontend/Panes/Lobbies/ConfigurationPane](modules/Frontend_Panes_Lobbies_ConfigurationPane.md)\n- [Frontend/Panes/Lobbies/GameSettingsPane](modules/Frontend_Panes_Lobbies_GameSettingsPane.md)\n- [Frontend/Panes/Lobbies/LobbiesUtils](modules/Frontend_Panes_Lobbies_LobbiesUtils.md)\n- [Frontend/Panes/Lobbies/MinimapPane](modules/Frontend_Panes_Lobbies_MinimapPane.md)\n- [Frontend/Panes/Lobbies/MinimapUtils](modules/Frontend_Panes_Lobbies_MinimapUtils.md)\n- [Frontend/Panes/Lobbies/PlanetPane](modules/Frontend_Panes_Lobbies_PlanetPane.md)\n- [Frontend/Panes/Lobbies/PlayerSpawnPane](modules/Frontend_Panes_Lobbies_PlayerSpawnPane.md)\n- [Frontend/Panes/Lobbies/Reducer](modules/Frontend_Panes_Lobbies_Reducer.md)\n- [Frontend/Panes/Lobbies/SnarkPane](modules/Frontend_Panes_Lobbies_SnarkPane.md)\n- [Frontend/Panes/Lobbies/SpaceJunkPane](modules/Frontend_Panes_Lobbies_SpaceJunkPane.md)\n- [Frontend/Panes/Lobbies/SpaceTypeBiomePane](modules/Frontend_Panes_Lobbies_SpaceTypeBiomePane.md)\n- [Frontend/Panes/Lobbies/WorldSizePane](modules/Frontend_Panes_Lobbies_WorldSizePane.md)\n- [Frontend/Panes/ManagePlanetArtifacts/ArtifactActions](modules/Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md)\n- [Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts](modules/Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md)\n- [Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane](modules/Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md)\n- [Frontend/Panes/ManagePlanetArtifacts/SortBy](modules/Frontend_Panes_ManagePlanetArtifacts_SortBy.md)\n- [Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView](modules/Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md)\n- [Frontend/Panes/OnboardingPane](modules/Frontend_Panes_OnboardingPane.md)\n- [Frontend/Panes/PlanetContextPane](modules/Frontend_Panes_PlanetContextPane.md)\n- [Frontend/Panes/PlanetDexPane](modules/Frontend_Panes_PlanetDexPane.md)\n- [Frontend/Panes/PlanetInfoPane](modules/Frontend_Panes_PlanetInfoPane.md)\n- [Frontend/Panes/PlayerArtifactsPane](modules/Frontend_Panes_PlayerArtifactsPane.md)\n- [Frontend/Panes/PluginEditorPane](modules/Frontend_Panes_PluginEditorPane.md)\n- [Frontend/Panes/PluginLibraryPane](modules/Frontend_Panes_PluginLibraryPane.md)\n- [Frontend/Panes/PrivatePane](modules/Frontend_Panes_PrivatePane.md)\n- [Frontend/Panes/SettingsPane](modules/Frontend_Panes_SettingsPane.md)\n- [Frontend/Panes/Tooltip](modules/Frontend_Panes_Tooltip.md)\n- [Frontend/Panes/TooltipPanes](modules/Frontend_Panes_TooltipPanes.md)\n- [Frontend/Panes/TransactionLogPane](modules/Frontend_Panes_TransactionLogPane.md)\n- [Frontend/Panes/TutorialPane](modules/Frontend_Panes_TutorialPane.md)\n- [Frontend/Panes/TwitterVerifyPane](modules/Frontend_Panes_TwitterVerifyPane.md)\n- [Frontend/Panes/UpgradeDetailsPane](modules/Frontend_Panes_UpgradeDetailsPane.md)\n- [Frontend/Panes/WikiPane](modules/Frontend_Panes_WikiPane.md)\n- [Frontend/Panes/ZoomPane](modules/Frontend_Panes_ZoomPane.md)\n- [Frontend/Renderers/Artifacts/ArtifactRenderer](modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md)\n- [Frontend/Renderers/GifRenderer](modules/Frontend_Renderers_GifRenderer.md)\n- [Frontend/Renderers/LandingPageCanvas](modules/Frontend_Renderers_LandingPageCanvas.md)\n- [Frontend/Renderers/PlanetscapeRenderer/PlanetIcons](modules/Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md)\n- [Frontend/Styles/Colors](modules/Frontend_Styles_Colors.md)\n- [Frontend/Styles/Mixins](modules/Frontend_Styles_Mixins.md)\n- [Frontend/Styles/dfstyles](modules/Frontend_Styles_dfstyles.md)\n- [Frontend/Utils/AppHooks](modules/Frontend_Utils_AppHooks.md)\n- [Frontend/Utils/BrowserChecks](modules/Frontend_Utils_BrowserChecks.md)\n- [Frontend/Utils/EmitterHooks](modules/Frontend_Utils_EmitterHooks.md)\n- [Frontend/Utils/EmitterUtils](modules/Frontend_Utils_EmitterUtils.md)\n- [Frontend/Utils/Hooks](modules/Frontend_Utils_Hooks.md)\n- [Frontend/Utils/KeyEmitters](modules/Frontend_Utils_KeyEmitters.md)\n- [Frontend/Utils/SettingsHooks](modules/Frontend_Utils_SettingsHooks.md)\n- [Frontend/Utils/ShortcutConstants](modules/Frontend_Utils_ShortcutConstants.md)\n- [Frontend/Utils/TerminalTypes](modules/Frontend_Utils_TerminalTypes.md)\n- [Frontend/Utils/TimeUtils](modules/Frontend_Utils_TimeUtils.md)\n- [Frontend/Utils/UIEmitter](modules/Frontend_Utils_UIEmitter.md)\n- [Frontend/Utils/constants](modules/Frontend_Utils_constants.md)\n- [Frontend/Utils/createDefinedContext](modules/Frontend_Utils_createDefinedContext.md)\n- [Frontend/Views/ArtifactLink](modules/Frontend_Views_ArtifactLink.md)\n- [Frontend/Views/ArtifactRow](modules/Frontend_Views_ArtifactRow.md)\n- [Frontend/Views/CadetWormhole](modules/Frontend_Views_CadetWormhole.md)\n- [Frontend/Views/DFErrorBoundary](modules/Frontend_Views_DFErrorBoundary.md)\n- [Frontend/Views/DarkForestTips](modules/Frontend_Views_DarkForestTips.md)\n- [Frontend/Views/EmojiPicker](modules/Frontend_Views_EmojiPicker.md)\n- [Frontend/Views/EmojiPlanetNotification](modules/Frontend_Views_EmojiPlanetNotification.md)\n- [Frontend/Views/GameWindowLayout](modules/Frontend_Views_GameWindowLayout.md)\n- [Frontend/Views/GenericErrorBoundary](modules/Frontend_Views_GenericErrorBoundary.md)\n- [Frontend/Views/LandingPageRoundArt](modules/Frontend_Views_LandingPageRoundArt.md)\n- [Frontend/Views/Leaderboard](modules/Frontend_Views_Leaderboard.md)\n- [Frontend/Views/ModalIcon](modules/Frontend_Views_ModalIcon.md)\n- [Frontend/Views/ModalPane](modules/Frontend_Views_ModalPane.md)\n- [Frontend/Views/NetworkHealth](modules/Frontend_Views_NetworkHealth.md)\n- [Frontend/Views/Notifications](modules/Frontend_Views_Notifications.md)\n- [Frontend/Views/Paused](modules/Frontend_Views_Paused.md)\n- [Frontend/Views/PlanetCard](modules/Frontend_Views_PlanetCard.md)\n- [Frontend/Views/PlanetCardComponents](modules/Frontend_Views_PlanetCardComponents.md)\n- [Frontend/Views/PlanetLink](modules/Frontend_Views_PlanetLink.md)\n- [Frontend/Views/PlanetNotifications](modules/Frontend_Views_PlanetNotifications.md)\n- [Frontend/Views/SendResources](modules/Frontend_Views_SendResources.md)\n- [Frontend/Views/Share](modules/Frontend_Views_Share.md)\n- [Frontend/Views/SidebarPane](modules/Frontend_Views_SidebarPane.md)\n- [Frontend/Views/SortableTable](modules/Frontend_Views_SortableTable.md)\n- [Frontend/Views/TabbedView](modules/Frontend_Views_TabbedView.md)\n- [Frontend/Views/Table](modules/Frontend_Views_Table.md)\n- [Frontend/Views/Terminal](modules/Frontend_Views_Terminal.md)\n- [Frontend/Views/TopBar](modules/Frontend_Views_TopBar.md)\n- [Frontend/Views/UpgradePreview](modules/Frontend_Views_UpgradePreview.md)\n- [Frontend/Views/WithdrawSilver](modules/Frontend_Views_WithdrawSilver.md)\n- [\\_types/darkforest/api/ChunkStoreTypes](modules/types_darkforest_api_ChunkStoreTypes.md)\n- [\\_types/darkforest/api/ContractsAPITypes](modules/types_darkforest_api_ContractsAPITypes.md)\n- [\\_types/darkforest/api/UtilityServerAPITypes](modules/types_darkforest_api_UtilityServerAPITypes.md)\n- [\\_types/file-loader/FileWorkerTypes](modules/types_file_loader_FileWorkerTypes.md)\n- [\\_types/global/GlobalTypes](modules/types_global_GlobalTypes.md)\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md",
    "content": "# Class: CaptureZoneGenerator\n\n[Backend/GameLogic/CaptureZoneGenerator](../modules/Backend_GameLogic_CaptureZoneGenerator.md).CaptureZoneGenerator\n\nGiven a game start block and a zone change block interval, decide when to generate new Capture Zones.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#constructor)\n\n### Properties\n\n- [capturablePlanets](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#capturableplanets)\n- [changeInterval](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#changeinterval)\n- [gameManager](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#gamemanager)\n- [generated$](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#generated$)\n- [lastChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#lastchangeblock)\n- [nextChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#nextchangeblock)\n- [zones](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#zones)\n\n### Accessors\n\n- [gameObjects](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#gameobjects)\n\n### Methods\n\n- [\\_generate](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#_generate)\n- [generate](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#generate)\n- [getNextChangeBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#getnextchangeblock)\n- [getZones](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#getzones)\n- [isInZone](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#isinzone)\n- [onNewChunk](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#onnewchunk)\n- [setNextGenerationBlock](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#setnextgenerationblock)\n- [updateCapturablePlanets](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md#updatecapturableplanets)\n\n## Constructors\n\n### constructor\n\n• **new CaptureZoneGenerator**(`gameManager`, `gameStartBlock`, `changeInterval`)\n\n#### Parameters\n\n| Name             | Type                                                  |\n| :--------------- | :---------------------------------------------------- |\n| `gameManager`    | [`default`](Backend_GameLogic_GameManager.default.md) |\n| `gameStartBlock` | `number`                                              |\n| `changeInterval` | `number`                                              |\n\n## Properties\n\n### capturablePlanets\n\n• `Private` **capturablePlanets**: `Set`<`LocationId`\\>\n\n---\n\n### changeInterval\n\n• `Private` **changeInterval**: `number`\n\n---\n\n### gameManager\n\n• `Private` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### generated$\n\n• `Readonly` **generated$**: `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\\>\n\n---\n\n### lastChangeBlock\n\n• `Private` **lastChangeBlock**: `number`\n\n---\n\n### nextChangeBlock\n\n• `Private` **nextChangeBlock**: `number`\n\n---\n\n### zones\n\n• `Private` **zones**: `Set`<`CaptureZone`\\>\n\n## Accessors\n\n### gameObjects\n\n• `Private` `get` **gameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\n#### Returns\n\n[`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\n## Methods\n\n### \\_generate\n\n▸ `Private` **\\_generate**(`blockNumber`): `Promise`<`Set`<`CaptureZone`\\>\\>\n\n#### Parameters\n\n| Name          | Type     |\n| :------------ | :------- |\n| `blockNumber` | `number` |\n\n#### Returns\n\n`Promise`<`Set`<`CaptureZone`\\>\\>\n\n---\n\n### generate\n\n▸ **generate**(`blockNumber`): `Promise`<`void`\\>\n\nCall when a new block is received to check if generation is needed.\n\n#### Parameters\n\n| Name          | Type     | Description           |\n| :------------ | :------- | :-------------------- |\n| `blockNumber` | `number` | Current block number. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### getNextChangeBlock\n\n▸ **getNextChangeBlock**(): `number`\n\nThe next block that will trigger a Capture Zone generation.\n\n#### Returns\n\n`number`\n\n---\n\n### getZones\n\n▸ **getZones**(): `Set`<`CaptureZone`\\>\n\n#### Returns\n\n`Set`<`CaptureZone`\\>\n\n---\n\n### isInZone\n\n▸ **isInZone**(`locationId`): `boolean`\n\nIs the given planet inside of a Capture Zone.\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### onNewChunk\n\n▸ `Private` **onNewChunk**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### setNextGenerationBlock\n\n▸ `Private` **setNextGenerationBlock**(`blockNumber`): `void`\n\n#### Parameters\n\n| Name          | Type     |\n| :------------ | :------- |\n| `blockNumber` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### updateCapturablePlanets\n\n▸ `Private` **updateCapturablePlanets**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md",
    "content": "# Class: ContractsAPI\n\n[Backend/GameLogic/ContractsAPI](../modules/Backend_GameLogic_ContractsAPI.md).ContractsAPI\n\nRoughly contains methods that map 1:1 with functions that live in the contract. Responsible for\nreading and writing to and from the blockchain.\n\n**`todo`** don't inherit from {@link EventEmitter}. instead use {@link Monomitter}\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`ContractsAPI`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_ContractsAPI.ContractsAPI.md#constructor)\n\n### Properties\n\n- [contractAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contractaddress)\n- [contractCaller](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contractcaller)\n- [ethConnection](Backend_GameLogic_ContractsAPI.ContractsAPI.md#ethconnection)\n- [txExecutor](Backend_GameLogic_ContractsAPI.ContractsAPI.md#txexecutor)\n- [MIN_BALANCE](Backend_GameLogic_ContractsAPI.ContractsAPI.md#min_balance)\n\n### Accessors\n\n- [contract](Backend_GameLogic_ContractsAPI.ContractsAPI.md#contract)\n\n### Methods\n\n- [afterTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#aftertransaction)\n- [beforeQueued](Backend_GameLogic_ContractsAPI.ContractsAPI.md#beforequeued)\n- [beforeTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#beforetransaction)\n- [bulkGetArtifacts](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetartifacts)\n- [bulkGetArtifactsOnPlanets](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetartifactsonplanets)\n- [bulkGetPlanets](Backend_GameLogic_ContractsAPI.ContractsAPI.md#bulkgetplanets)\n- [cancelTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#canceltransaction)\n- [destroy](Backend_GameLogic_ContractsAPI.ContractsAPI.md#destroy)\n- [emitTransactionEvents](Backend_GameLogic_ContractsAPI.ContractsAPI.md#emittransactionevents)\n- [getAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getaddress)\n- [getAllArrivals](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getallarrivals)\n- [getArrival](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getarrival)\n- [getArrivalsForPlanet](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getarrivalsforplanet)\n- [getArtifactById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getartifactbyid)\n- [getConstants](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getconstants)\n- [getContractAddress](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getcontractaddress)\n- [getGasFeeForTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getgasfeefortransaction)\n- [getIsPaused](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getispaused)\n- [getPlanetById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplanetbyid)\n- [getPlayerArtifacts](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayerartifacts)\n- [getPlayerById](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayerbyid)\n- [getPlayers](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getplayers)\n- [getRevealedCoordsByIdIfExists](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getrevealedcoordsbyidifexists)\n- [getRevealedPlanetsCoords](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getrevealedplanetscoords)\n- [getTokenMintEndTimestamp](Backend_GameLogic_ContractsAPI.ContractsAPI.md#gettokenmintendtimestamp)\n- [getTouchedPlanetIds](Backend_GameLogic_ContractsAPI.ContractsAPI.md#gettouchedplanetids)\n- [getWorldRadius](Backend_GameLogic_ContractsAPI.ContractsAPI.md#getworldradius)\n- [makeCall](Backend_GameLogic_ContractsAPI.ContractsAPI.md#makecall)\n- [prioritizeTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#prioritizetransaction)\n- [removeEventListeners](Backend_GameLogic_ContractsAPI.ContractsAPI.md#removeeventlisteners)\n- [setDiagnosticUpdater](Backend_GameLogic_ContractsAPI.ContractsAPI.md#setdiagnosticupdater)\n- [setupEventListeners](Backend_GameLogic_ContractsAPI.ContractsAPI.md#setupeventlisteners)\n- [submitTransaction](Backend_GameLogic_ContractsAPI.ContractsAPI.md#submittransaction)\n\n## Constructors\n\n### constructor\n\n• **new ContractsAPI**(`__namedParameters`)\n\n#### Parameters\n\n| Name                | Type                 |\n| :------------------ | :------------------- |\n| `__namedParameters` | `ContractsApiConfig` |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### contractAddress\n\n• `Private` **contractAddress**: `EthAddress`\n\nThe contract address is saved on the object upon construction\n\n---\n\n### contractCaller\n\n• `Private` `Readonly` **contractCaller**: `ContractCaller`\n\nInstrumented {@link ThrottledConcurrentQueue} for blockchain reads.\n\n---\n\n### ethConnection\n\n• `Readonly` **ethConnection**: `EthConnection`\n\nOur connection to the blockchain. In charge of low level networking, and also of the burner\nwallet.\n\n---\n\n### txExecutor\n\n• `Readonly` **txExecutor**: `TxExecutor`\n\nInstrumented {@link ThrottledConcurrentQueue} for blockchain writes.\n\n---\n\n### MIN_BALANCE\n\n▪ `Static` `Private` `Readonly` **MIN_BALANCE**: `BigNumber`\n\nDon't allow users to submit txs if balance falls below this amount/\n\n## Accessors\n\n### contract\n\n• `get` **contract**(): `DarkForest`\n\n#### Returns\n\n`DarkForest`\n\n## Methods\n\n### afterTransaction\n\n▸ `Private` **afterTransaction**(`_txRequest`, `txDiagnosticInfo`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name               | Type                       |\n| :----------------- | :------------------------- |\n| `_txRequest`       | `Transaction`<`TxIntent`\\> |\n| `txDiagnosticInfo` | `unknown`                  |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### beforeQueued\n\n▸ `Private` **beforeQueued**(`id`, `intent`, `overrides?`): `Promise`<`void`\\>\n\nThis function is called by {@link TxExecutor} before a transaction is queued.\nIt gives the client an opportunity to prevent a transaction from being queued based\non business logic or user interaction.\n\nReject the promise to prevent the queued transaction from being queued.\n\n#### Parameters\n\n| Name         | Type                 |\n| :----------- | :------------------- |\n| `id`         | `number`             |\n| `intent`     | `TxIntent`           |\n| `overrides?` | `TransactionRequest` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### beforeTransaction\n\n▸ `Private` **beforeTransaction**(`tx`): `Promise`<`void`\\>\n\nThis function is called by {@link TxExecutor} before each transaction. It gives the client an\nopportunity to prevent a transaction from going through based on business logic or user\ninteraction. To prevent the queued transaction from being submitted, throw an Error.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### bulkGetArtifacts\n\n▸ **bulkGetArtifacts**(`artifactIds`, `onProgress?`): `Promise`<`Artifact`[]\\>\n\n#### Parameters\n\n| Name          | Type                                      |\n| :------------ | :---------------------------------------- |\n| `artifactIds` | `ArtifactId`[]                            |\n| `onProgress?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`Artifact`[]\\>\n\n---\n\n### bulkGetArtifactsOnPlanets\n\n▸ **bulkGetArtifactsOnPlanets**(`locationIds`, `onProgress?`): `Promise`<`Artifact`[][]\\>\n\n#### Parameters\n\n| Name          | Type                                      |\n| :------------ | :---------------------------------------- |\n| `locationIds` | `LocationId`[]                            |\n| `onProgress?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`Artifact`[][]\\>\n\n---\n\n### bulkGetPlanets\n\n▸ **bulkGetPlanets**(`toLoadPlanets`, `onProgressPlanet?`, `onProgressMetadata?`): `Promise`<`Map`<`LocationId`, `Planet`\\>\\>\n\n#### Parameters\n\n| Name                  | Type                                      |\n| :-------------------- | :---------------------------------------- |\n| `toLoadPlanets`       | `LocationId`[]                            |\n| `onProgressPlanet?`   | (`fractionCompleted`: `number`) => `void` |\n| `onProgressMetadata?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`Map`<`LocationId`, `Planet`\\>\\>\n\n---\n\n### cancelTransaction\n\n▸ **cancelTransaction**(`tx`): `void`\n\nRemove a transaction from the queue.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### emitTransactionEvents\n\n▸ **emitTransactionEvents**(`tx`): `void`\n\nThis is a strange interface between the transaction queue system and the rest of the game. The\nstrange thing about it is that introduces another way by which transactions are pushed into the\ngame - these {@code ContractsAPIEvent} events.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### getAddress\n\n▸ **getAddress**(): `undefined` \\| `EthAddress`\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### getAllArrivals\n\n▸ **getAllArrivals**(`planetsToLoad`, `onProgress?`): `Promise`<`QueuedArrival`[]\\>\n\n#### Parameters\n\n| Name            | Type                                      |\n| :-------------- | :---------------------------------------- |\n| `planetsToLoad` | `LocationId`[]                            |\n| `onProgress?`   | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`QueuedArrival`[]\\>\n\n---\n\n### getArrival\n\n▸ **getArrival**(`arrivalId`): `Promise`<`undefined` \\| `QueuedArrival`\\>\n\n#### Parameters\n\n| Name        | Type     |\n| :---------- | :------- |\n| `arrivalId` | `number` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `QueuedArrival`\\>\n\n---\n\n### getArrivalsForPlanet\n\n▸ **getArrivalsForPlanet**(`planetId`): `Promise`<`QueuedArrival`[]\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`QueuedArrival`[]\\>\n\n---\n\n### getArtifactById\n\n▸ **getArtifactById**(`artifactId`): `Promise`<`undefined` \\| `Artifact`\\>\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `Artifact`\\>\n\n---\n\n### getConstants\n\n▸ **getConstants**(): `Promise`<[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\\>\n\n#### Returns\n\n`Promise`<[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\\>\n\n---\n\n### getContractAddress\n\n▸ **getContractAddress**(): `EthAddress`\n\n#### Returns\n\n`EthAddress`\n\n---\n\n### getGasFeeForTransaction\n\n▸ `Private` **getGasFeeForTransaction**(`tx`): `string` \\| `AutoGasSetting`\n\nWe pass this function into {@link TxExecutor} to calculate what gas fee we should use for the\ngiven transaction. The result is either a number, measured in gwei, represented as a string, or\na string representing that we want to use an auto gas setting.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`string` \\| `AutoGasSetting`\n\n---\n\n### getIsPaused\n\n▸ **getIsPaused**(): `Promise`<`boolean`\\>\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### getPlanetById\n\n▸ **getPlanetById**(`planetId`): `Promise`<`undefined` \\| `Planet`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `Planet`\\>\n\n---\n\n### getPlayerArtifacts\n\n▸ **getPlayerArtifacts**(`playerId?`, `onProgress?`): `Promise`<`Artifact`[]\\>\n\n#### Parameters\n\n| Name          | Type                            |\n| :------------ | :------------------------------ |\n| `playerId?`   | `EthAddress`                    |\n| `onProgress?` | (`percent`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`Artifact`[]\\>\n\n---\n\n### getPlayerById\n\n▸ **getPlayerById**(`playerId`): `Promise`<`undefined` \\| `Player`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `playerId` | `EthAddress` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `Player`\\>\n\n---\n\n### getPlayers\n\n▸ **getPlayers**(`onProgress?`): `Promise`<`Map`<`string`, `Player`\\>\\>\n\n#### Parameters\n\n| Name          | Type                                      |\n| :------------ | :---------------------------------------- |\n| `onProgress?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`Map`<`string`, `Player`\\>\\>\n\n---\n\n### getRevealedCoordsByIdIfExists\n\n▸ **getRevealedCoordsByIdIfExists**(`planetId`): `Promise`<`undefined` \\| `RevealedCoords`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `RevealedCoords`\\>\n\n---\n\n### getRevealedPlanetsCoords\n\n▸ **getRevealedPlanetsCoords**(`startingAt`, `onProgressIds?`, `onProgressCoords?`): `Promise`<`RevealedCoords`[]\\>\n\n#### Parameters\n\n| Name                | Type                                      |\n| :------------------ | :---------------------------------------- |\n| `startingAt`        | `number`                                  |\n| `onProgressIds?`    | (`fractionCompleted`: `number`) => `void` |\n| `onProgressCoords?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`RevealedCoords`[]\\>\n\n---\n\n### getTokenMintEndTimestamp\n\n▸ **getTokenMintEndTimestamp**(): `Promise`<`number`\\>\n\n#### Returns\n\n`Promise`<`number`\\>\n\n---\n\n### getTouchedPlanetIds\n\n▸ **getTouchedPlanetIds**(`startingAt`, `onProgress?`): `Promise`<`LocationId`[]\\>\n\n#### Parameters\n\n| Name          | Type                                      |\n| :------------ | :---------------------------------------- |\n| `startingAt`  | `number`                                  |\n| `onProgress?` | (`fractionCompleted`: `number`) => `void` |\n\n#### Returns\n\n`Promise`<`LocationId`[]\\>\n\n---\n\n### getWorldRadius\n\n▸ **getWorldRadius**(): `Promise`<`number`\\>\n\n#### Returns\n\n`Promise`<`number`\\>\n\n---\n\n### makeCall\n\n▸ `Private` **makeCall**<`T`\\>(`contractViewFunction`, `args?`): `Promise`<`T`\\>\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name                   | Type                     | Default value |\n| :--------------------- | :----------------------- | :------------ |\n| `contractViewFunction` | `ContractFunction`<`T`\\> | `undefined`   |\n| `args`                 | `unknown`[]              | `[]`          |\n\n#### Returns\n\n`Promise`<`T`\\>\n\n---\n\n### prioritizeTransaction\n\n▸ **prioritizeTransaction**(`tx`): `void`\n\nMake sure this transaction is the next to be executed.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### removeEventListeners\n\n▸ **removeEventListeners**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### setDiagnosticUpdater\n\n▸ **setDiagnosticUpdater**(`diagnosticUpdater?`): `void`\n\n#### Parameters\n\n| Name                 | Type                |\n| :------------------- | :------------------ |\n| `diagnosticUpdater?` | `DiagnosticUpdater` |\n\n#### Returns\n\n`void`\n\n---\n\n### setupEventListeners\n\n▸ **setupEventListeners**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### submitTransaction\n\n▸ **submitTransaction**<`T`\\>(`txIntent`, `overrides?`): `Promise`<`Transaction`<`T`\\>\\>\n\n#### Type parameters\n\n| Name | Type               |\n| :--- | :----------------- |\n| `T`  | extends `TxIntent` |\n\n#### Parameters\n\n| Name         | Type                 |\n| :----------- | :------------------- |\n| `txIntent`   | `T`                  |\n| `overrides?` | `TransactionRequest` |\n\n#### Returns\n\n`Promise`<`Transaction`<`T`\\>\\>\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_GameManager.default.md",
    "content": "# Class: default\n\n[Backend/GameLogic/GameManager](../modules/Backend_GameLogic_GameManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_GameManager.default.md#constructor)\n\n### Properties\n\n- [account](Backend_GameLogic_GameManager.default.md#account)\n- [captureZoneGenerator](Backend_GameLogic_GameManager.default.md#capturezonegenerator)\n- [contractConstants](Backend_GameLogic_GameManager.default.md#contractconstants)\n- [contractsAPI](Backend_GameLogic_GameManager.default.md#contractsapi)\n- [diagnostics](Backend_GameLogic_GameManager.default.md#diagnostics)\n- [diagnosticsInterval](Backend_GameLogic_GameManager.default.md#diagnosticsinterval)\n- [endTimeSeconds](Backend_GameLogic_GameManager.default.md#endtimeseconds)\n- [entityStore](Backend_GameLogic_GameManager.default.md#entitystore)\n- [ethConnection](Backend_GameLogic_GameManager.default.md#ethconnection)\n- [hashConfig](Backend_GameLogic_GameManager.default.md#hashconfig)\n- [hashRate](Backend_GameLogic_GameManager.default.md#hashrate)\n- [homeLocation](Backend_GameLogic_GameManager.default.md#homelocation)\n- [minerManager](Backend_GameLogic_GameManager.default.md#minermanager)\n- [networkHealth$](Backend_GameLogic_GameManager.default.md#networkhealth$)\n- [networkHealthInterval](Backend_GameLogic_GameManager.default.md#networkhealthinterval)\n- [paused](Backend_GameLogic_GameManager.default.md#paused)\n- [paused$](Backend_GameLogic_GameManager.default.md#paused$)\n- [persistentChunkStore](Backend_GameLogic_GameManager.default.md#persistentchunkstore)\n- [planetHashMimc](Backend_GameLogic_GameManager.default.md#planethashmimc)\n- [playerInterval](Backend_GameLogic_GameManager.default.md#playerinterval)\n- [players](Backend_GameLogic_GameManager.default.md#players)\n- [playersUpdated$](Backend_GameLogic_GameManager.default.md#playersupdated$)\n- [safeMode](Backend_GameLogic_GameManager.default.md#safemode)\n- [scoreboardInterval](Backend_GameLogic_GameManager.default.md#scoreboardinterval)\n- [settingsSubscription](Backend_GameLogic_GameManager.default.md#settingssubscription)\n- [snarkHelper](Backend_GameLogic_GameManager.default.md#snarkhelper)\n- [terminal](Backend_GameLogic_GameManager.default.md#terminal)\n- [useMockHash](Backend_GameLogic_GameManager.default.md#usemockhash)\n- [worldRadius](Backend_GameLogic_GameManager.default.md#worldradius)\n\n### Accessors\n\n- [captureZoneGeneratedEmitter](Backend_GameLogic_GameManager.default.md#capturezonegeneratedemitter)\n- [planetRarity](Backend_GameLogic_GameManager.default.md#planetrarity)\n\n### Methods\n\n- [activateArtifact](Backend_GameLogic_GameManager.default.md#activateartifact)\n- [addAccount](Backend_GameLogic_GameManager.default.md#addaccount)\n- [addNewChunk](Backend_GameLogic_GameManager.default.md#addnewchunk)\n- [biomebasePerlin](Backend_GameLogic_GameManager.default.md#biomebaseperlin)\n- [bulkAddNewChunks](Backend_GameLogic_GameManager.default.md#bulkaddnewchunks)\n- [bulkHardRefreshPlanets](Backend_GameLogic_GameManager.default.md#bulkhardrefreshplanets)\n- [buyHat](Backend_GameLogic_GameManager.default.md#buyhat)\n- [capturePlanet](Backend_GameLogic_GameManager.default.md#captureplanet)\n- [checkGameHasEnded](Backend_GameLogic_GameManager.default.md#checkgamehasended)\n- [clearEmoji](Backend_GameLogic_GameManager.default.md#clearemoji)\n- [deactivateArtifact](Backend_GameLogic_GameManager.default.md#deactivateartifact)\n- [depositArtifact](Backend_GameLogic_GameManager.default.md#depositartifact)\n- [destroy](Backend_GameLogic_GameManager.default.md#destroy)\n- [findArtifact](Backend_GameLogic_GameManager.default.md#findartifact)\n- [findRandomHomePlanet](Backend_GameLogic_GameManager.default.md#findrandomhomeplanet)\n- [forceTick](Backend_GameLogic_GameManager.default.md#forcetick)\n- [getAccount](Backend_GameLogic_GameManager.default.md#getaccount)\n- [getActiveArtifact](Backend_GameLogic_GameManager.default.md#getactiveartifact)\n- [getAddress](Backend_GameLogic_GameManager.default.md#getaddress)\n- [getAllOwnedPlanets](Backend_GameLogic_GameManager.default.md#getallownedplanets)\n- [getAllPlanets](Backend_GameLogic_GameManager.default.md#getallplanets)\n- [getAllPlayers](Backend_GameLogic_GameManager.default.md#getallplayers)\n- [getAllVoyages](Backend_GameLogic_GameManager.default.md#getallvoyages)\n- [getArtifactMap](Backend_GameLogic_GameManager.default.md#getartifactmap)\n- [getArtifactUpdated$](Backend_GameLogic_GameManager.default.md#getartifactupdated$)\n- [getArtifactWithId](Backend_GameLogic_GameManager.default.md#getartifactwithid)\n- [getArtifactsWithIds](Backend_GameLogic_GameManager.default.md#getartifactswithids)\n- [getCaptureZoneGenerator](Backend_GameLogic_GameManager.default.md#getcapturezonegenerator)\n- [getCaptureZones](Backend_GameLogic_GameManager.default.md#getcapturezones)\n- [getChunk](Backend_GameLogic_GameManager.default.md#getchunk)\n- [getChunkStore](Backend_GameLogic_GameManager.default.md#getchunkstore)\n- [getClaimedLocations](Backend_GameLogic_GameManager.default.md#getclaimedlocations)\n- [getConstructors](Backend_GameLogic_GameManager.default.md#getconstructors)\n- [getContract](Backend_GameLogic_GameManager.default.md#getcontract)\n- [getContractAPI](Backend_GameLogic_GameManager.default.md#getcontractapi)\n- [getContractAddress](Backend_GameLogic_GameManager.default.md#getcontractaddress)\n- [getContractConstants](Backend_GameLogic_GameManager.default.md#getcontractconstants)\n- [getCurrentlyExploringChunk](Backend_GameLogic_GameManager.default.md#getcurrentlyexploringchunk)\n- [getDefaultSpaceJunkForPlanetLevel](Backend_GameLogic_GameManager.default.md#getdefaultspacejunkforplanetlevel)\n- [getDiagnostics](Backend_GameLogic_GameManager.default.md#getdiagnostics)\n- [getDist](Backend_GameLogic_GameManager.default.md#getdist)\n- [getDistCoords](Backend_GameLogic_GameManager.default.md#getdistcoords)\n- [getEndTimeSeconds](Backend_GameLogic_GameManager.default.md#getendtimeseconds)\n- [getEnergyArrivingForMove](Backend_GameLogic_GameManager.default.md#getenergyarrivingformove)\n- [getEnergyCurveAtPercent](Backend_GameLogic_GameManager.default.md#getenergycurveatpercent)\n- [getEnergyNeededForMove](Backend_GameLogic_GameManager.default.md#getenergyneededformove)\n- [getEnergyOfPlayer](Backend_GameLogic_GameManager.default.md#getenergyofplayer)\n- [getEthConnection](Backend_GameLogic_GameManager.default.md#getethconnection)\n- [getExploredChunks](Backend_GameLogic_GameManager.default.md#getexploredchunks)\n- [getGameObjects](Backend_GameLogic_GameManager.default.md#getgameobjects)\n- [getHashConfig](Backend_GameLogic_GameManager.default.md#gethashconfig)\n- [getHashesPerSec](Backend_GameLogic_GameManager.default.md#gethashespersec)\n- [getHomeCoords](Backend_GameLogic_GameManager.default.md#gethomecoords)\n- [getHomeHash](Backend_GameLogic_GameManager.default.md#gethomehash)\n- [getLocationOfPlanet](Backend_GameLogic_GameManager.default.md#getlocationofplanet)\n- [getMaxMoveDist](Backend_GameLogic_GameManager.default.md#getmaxmovedist)\n- [getMiningPattern](Backend_GameLogic_GameManager.default.md#getminingpattern)\n- [getMyArtifactMap](Backend_GameLogic_GameManager.default.md#getmyartifactmap)\n- [getMyArtifacts](Backend_GameLogic_GameManager.default.md#getmyartifacts)\n- [getMyArtifactsUpdated$](Backend_GameLogic_GameManager.default.md#getmyartifactsupdated$)\n- [getMyBalance](Backend_GameLogic_GameManager.default.md#getmybalance)\n- [getMyBalance$](Backend_GameLogic_GameManager.default.md#getmybalance$)\n- [getMyBalanceEth](Backend_GameLogic_GameManager.default.md#getmybalanceeth)\n- [getMyPlanetMap](Backend_GameLogic_GameManager.default.md#getmyplanetmap)\n- [getMyPlanets](Backend_GameLogic_GameManager.default.md#getmyplanets)\n- [getMyPlanetsUpdated$](Backend_GameLogic_GameManager.default.md#getmyplanetsupdated$)\n- [getMyScore](Backend_GameLogic_GameManager.default.md#getmyscore)\n- [getNextBroadcastAvailableTimestamp](Backend_GameLogic_GameManager.default.md#getnextbroadcastavailabletimestamp)\n- [getNextClaimAvailableTimestamp](Backend_GameLogic_GameManager.default.md#getnextclaimavailabletimestamp)\n- [getNextRevealCountdownInfo](Backend_GameLogic_GameManager.default.md#getnextrevealcountdowninfo)\n- [getNotificationsManager](Backend_GameLogic_GameManager.default.md#getnotificationsmanager)\n- [getPaused](Backend_GameLogic_GameManager.default.md#getpaused)\n- [getPaused$](Backend_GameLogic_GameManager.default.md#getpaused$)\n- [getPerlinThresholds](Backend_GameLogic_GameManager.default.md#getperlinthresholds)\n- [getPlanetLevel](Backend_GameLogic_GameManager.default.md#getplanetlevel)\n- [getPlanetMap](Backend_GameLogic_GameManager.default.md#getplanetmap)\n- [getPlanetRarity](Backend_GameLogic_GameManager.default.md#getplanetrarity)\n- [getPlanetUpdated$](Backend_GameLogic_GameManager.default.md#getplanetupdated$)\n- [getPlanetWithCoords](Backend_GameLogic_GameManager.default.md#getplanetwithcoords)\n- [getPlanetWithId](Backend_GameLogic_GameManager.default.md#getplanetwithid)\n- [getPlanetsInRange](Backend_GameLogic_GameManager.default.md#getplanetsinrange)\n- [getPlanetsInWorldRectangle](Backend_GameLogic_GameManager.default.md#getplanetsinworldrectangle)\n- [getPlanetsWithIds](Backend_GameLogic_GameManager.default.md#getplanetswithids)\n- [getPlayer](Backend_GameLogic_GameManager.default.md#getplayer)\n- [getPlayerScore](Backend_GameLogic_GameManager.default.md#getplayerscore)\n- [getPlayerSpaceJunk](Backend_GameLogic_GameManager.default.md#getplayerspacejunk)\n- [getPlayerSpaceJunkLimit](Backend_GameLogic_GameManager.default.md#getplayerspacejunklimit)\n- [getPrivateKey](Backend_GameLogic_GameManager.default.md#getprivatekey)\n- [getRangeBuff](Backend_GameLogic_GameManager.default.md#getrangebuff)\n- [getRevealedLocations](Backend_GameLogic_GameManager.default.md#getrevealedlocations)\n- [getSafeMode](Backend_GameLogic_GameManager.default.md#getsafemode)\n- [getSignedTwitter](Backend_GameLogic_GameManager.default.md#getsignedtwitter)\n- [getSilverCurveAtPercent](Backend_GameLogic_GameManager.default.md#getsilvercurveatpercent)\n- [getSilverOfPlayer](Backend_GameLogic_GameManager.default.md#getsilverofplayer)\n- [getSnarkHelper](Backend_GameLogic_GameManager.default.md#getsnarkhelper)\n- [getSpaceships](Backend_GameLogic_GameManager.default.md#getspaceships)\n- [getSpeedBuff](Backend_GameLogic_GameManager.default.md#getspeedbuff)\n- [getStalePlanetWithId](Backend_GameLogic_GameManager.default.md#getstaleplanetwithid)\n- [getTemperature](Backend_GameLogic_GameManager.default.md#gettemperature)\n- [getTimeForMove](Backend_GameLogic_GameManager.default.md#gettimeformove)\n- [getTokenMintEndTimeSeconds](Backend_GameLogic_GameManager.default.md#gettokenmintendtimeseconds)\n- [getTwitter](Backend_GameLogic_GameManager.default.md#gettwitter)\n- [getUIEventEmitter](Backend_GameLogic_GameManager.default.md#getuieventemitter)\n- [getUnconfirmedMoves](Backend_GameLogic_GameManager.default.md#getunconfirmedmoves)\n- [getUnconfirmedUpgrades](Backend_GameLogic_GameManager.default.md#getunconfirmedupgrades)\n- [getUnconfirmedWormholeActivations](Backend_GameLogic_GameManager.default.md#getunconfirmedwormholeactivations)\n- [getUniverseTotalEnergy](Backend_GameLogic_GameManager.default.md#getuniversetotalenergy)\n- [getUpgrade](Backend_GameLogic_GameManager.default.md#getupgrade)\n- [getWorldRadius](Backend_GameLogic_GameManager.default.md#getworldradius)\n- [getWorldSilver](Backend_GameLogic_GameManager.default.md#getworldsilver)\n- [getWormholeFactors](Backend_GameLogic_GameManager.default.md#getwormholefactors)\n- [getWormholes](Backend_GameLogic_GameManager.default.md#getwormholes)\n- [hardRefreshArtifact](Backend_GameLogic_GameManager.default.md#hardrefreshartifact)\n- [hardRefreshPlanet](Backend_GameLogic_GameManager.default.md#hardrefreshplanet)\n- [hardRefreshPlayer](Backend_GameLogic_GameManager.default.md#hardrefreshplayer)\n- [hasJoinedGame](Backend_GameLogic_GameManager.default.md#hasjoinedgame)\n- [hasMinedChunk](Backend_GameLogic_GameManager.default.md#hasminedchunk)\n- [initMiningManager](Backend_GameLogic_GameManager.default.md#initminingmanager)\n- [invadePlanet](Backend_GameLogic_GameManager.default.md#invadeplanet)\n- [isAdmin](Backend_GameLogic_GameManager.default.md#isadmin)\n- [isMining](Backend_GameLogic_GameManager.default.md#ismining)\n- [isPlanetMineable](Backend_GameLogic_GameManager.default.md#isplanetmineable)\n- [isRoundOver](Backend_GameLogic_GameManager.default.md#isroundover)\n- [joinGame](Backend_GameLogic_GameManager.default.md#joingame)\n- [listenForNewBlock](Backend_GameLogic_GameManager.default.md#listenfornewblock)\n- [loadContract](Backend_GameLogic_GameManager.default.md#loadcontract)\n- [loadPlugins](Backend_GameLogic_GameManager.default.md#loadplugins)\n- [locationBigIntFromCoords](Backend_GameLogic_GameManager.default.md#locationbigintfromcoords)\n- [locationFromCoords](Backend_GameLogic_GameManager.default.md#locationfromcoords)\n- [move](Backend_GameLogic_GameManager.default.md#move)\n- [onTxCancelled](Backend_GameLogic_GameManager.default.md#ontxcancelled)\n- [onTxConfirmed](Backend_GameLogic_GameManager.default.md#ontxconfirmed)\n- [onTxReverted](Backend_GameLogic_GameManager.default.md#ontxreverted)\n- [onTxSubmit](Backend_GameLogic_GameManager.default.md#ontxsubmit)\n- [prospectPlanet](Backend_GameLogic_GameManager.default.md#prospectplanet)\n- [refreshNetworkHealth](Backend_GameLogic_GameManager.default.md#refreshnetworkhealth)\n- [refreshScoreboard](Backend_GameLogic_GameManager.default.md#refreshscoreboard)\n- [refreshServerPlanetStates](Backend_GameLogic_GameManager.default.md#refreshserverplanetstates)\n- [refreshTwitters](Backend_GameLogic_GameManager.default.md#refreshtwitters)\n- [revealLocation](Backend_GameLogic_GameManager.default.md#reveallocation)\n- [savePlugins](Backend_GameLogic_GameManager.default.md#saveplugins)\n- [setMinerCores](Backend_GameLogic_GameManager.default.md#setminercores)\n- [setMiningPattern](Backend_GameLogic_GameManager.default.md#setminingpattern)\n- [setPlanetEmoji](Backend_GameLogic_GameManager.default.md#setplanetemoji)\n- [setPlayerTwitters](Backend_GameLogic_GameManager.default.md#setplayertwitters)\n- [setRadius](Backend_GameLogic_GameManager.default.md#setradius)\n- [setSafeMode](Backend_GameLogic_GameManager.default.md#setsafemode)\n- [setSnarkCacheSize](Backend_GameLogic_GameManager.default.md#setsnarkcachesize)\n- [softRefreshPlanet](Backend_GameLogic_GameManager.default.md#softrefreshplanet)\n- [spaceTypeFromPerlin](Backend_GameLogic_GameManager.default.md#spacetypefromperlin)\n- [spaceTypePerlin](Backend_GameLogic_GameManager.default.md#spacetypeperlin)\n- [startExplore](Backend_GameLogic_GameManager.default.md#startexplore)\n- [stopExplore](Backend_GameLogic_GameManager.default.md#stopexplore)\n- [submitDisconnectTwitter](Backend_GameLogic_GameManager.default.md#submitdisconnecttwitter)\n- [submitPlanetMessage](Backend_GameLogic_GameManager.default.md#submitplanetmessage)\n- [submitTransaction](Backend_GameLogic_GameManager.default.md#submittransaction)\n- [submitVerifyTwitter](Backend_GameLogic_GameManager.default.md#submitverifytwitter)\n- [testNotification](Backend_GameLogic_GameManager.default.md#testnotification)\n- [timeUntilNextBroadcastAvailable](Backend_GameLogic_GameManager.default.md#timeuntilnextbroadcastavailable)\n- [transferOwnership](Backend_GameLogic_GameManager.default.md#transferownership)\n- [updateDiagnostics](Backend_GameLogic_GameManager.default.md#updatediagnostics)\n- [upgrade](Backend_GameLogic_GameManager.default.md#upgrade)\n- [uploadDiagnostics](Backend_GameLogic_GameManager.default.md#uploaddiagnostics)\n- [verifyMessage](Backend_GameLogic_GameManager.default.md#verifymessage)\n- [waitForPlanet](Backend_GameLogic_GameManager.default.md#waitforplanet)\n- [withdrawArtifact](Backend_GameLogic_GameManager.default.md#withdrawartifact)\n- [withdrawSilver](Backend_GameLogic_GameManager.default.md#withdrawsilver)\n- [create](Backend_GameLogic_GameManager.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`terminal`, `account`, `players`, `touchedPlanets`, `allTouchedPlanetIds`, `revealedCoords`, `claimedCoords`, `worldRadius`, `unprocessedArrivals`, `unprocessedPlanetArrivalIds`, `contractsAPI`, `contractConstants`, `persistentChunkStore`, `snarkHelper`, `homeLocation`, `useMockHash`, `artifacts`, `ethConnection`, `paused`)\n\n#### Parameters\n\n| Name                          | Type                                                                                                            |\n| :---------------------------- | :-------------------------------------------------------------------------------------------------------------- |\n| `terminal`                    | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n| `account`                     | `undefined` \\| `EthAddress`                                                                                     |\n| `players`                     | `Map`<`string`, `Player`\\>                                                                                      |\n| `touchedPlanets`              | `Map`<`LocationId`, `Planet`\\>                                                                                  |\n| `allTouchedPlanetIds`         | `Set`<`LocationId`\\>                                                                                            |\n| `revealedCoords`              | `Map`<`LocationId`, `RevealedCoords`\\>                                                                          |\n| `claimedCoords`               | `Map`<`LocationId`, `ClaimedCoords`\\>                                                                           |\n| `worldRadius`                 | `number`                                                                                                        |\n| `unprocessedArrivals`         | `Map`<`VoyageId`, `QueuedArrival`\\>                                                                             |\n| `unprocessedPlanetArrivalIds` | `Map`<`LocationId`, `VoyageId`[]\\>                                                                              |\n| `contractsAPI`                | [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md)                                                |\n| `contractConstants`           | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)                |\n| `persistentChunkStore`        | [`default`](Backend_Storage_PersistentChunkStore.default.md)                                                    |\n| `snarkHelper`                 | [`default`](Backend_Utils_SnarkArgsHelper.default.md)                                                           |\n| `homeLocation`                | `undefined` \\| `WorldLocation`                                                                                  |\n| `useMockHash`                 | `boolean`                                                                                                       |\n| `artifacts`                   | `Map`<`ArtifactId`, `Artifact`\\>                                                                                |\n| `ethConnection`               | `EthConnection`                                                                                                 |\n| `paused`                      | `boolean`                                                                                                       |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### account\n\n• `Private` `Readonly` **account**: `undefined` \\| `EthAddress`\n\nThe ethereum address of the player who is currently logged in. We support 'no account',\nrepresented by `undefined` in the case when you want to simply load the game state from the\ncontract and view it without be able to make any moves.\n\n---\n\n### captureZoneGenerator\n\n• `Private` **captureZoneGenerator**: `undefined` \\| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\nGenerates capture zones.\n\n---\n\n### contractConstants\n\n• `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\nGame parameters set by the contract. Stuff like perlin keys, which are important for mining the\ncorrect universe, or the time multiplier, which allows us to tune how quickly voyages go.\n\n**`todo`** move this into a separate `GameConfiguration` class.\n\n---\n\n### contractsAPI\n\n• `Private` `Readonly` **contractsAPI**: [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md)\n\nAllows us to make contract calls, and execute transactions. Be careful about how you use this\nguy. You don't want to cause your client to send an excessive amount of traffic to whatever\nnode you're connected to.\n\nInteracting with the blockchain isn't free, and we need to be mindful about about the way our\napplication interacts with the blockchain. The current rate limiting strategy consists of three\npoints:\n\n- data that needs to be fetched often should be fetched in bulk.\n- rate limit smart contract calls (reads from the blockchain), implemented by\n  {@link ContractCaller} and transactions (writes to the blockchain on behalf of the player),\n  implemented by {@link TxExecutor} via two separately tuned {@link ThrottledConcurrentQueue}s.\n\n---\n\n### diagnostics\n\n• `Private` **diagnostics**: `Diagnostics`\n\nDiagnostic information about the game.\n\n---\n\n### diagnosticsInterval\n\n• `Private` **diagnosticsInterval**: `Timer`\n\nHandle to an interval that periodically uploads diagnostic information from this client.\n\n---\n\n### endTimeSeconds\n\n• `Private` `Readonly` **endTimeSeconds**: `number` = `1948939200`\n\n**`todo`** change this to the correct timestamp each round.\n\n---\n\n### entityStore\n\n• `Private` `Readonly` **entityStore**: [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\nThis variable contains the internal state of objects that live in the game world.\n\n---\n\n### ethConnection\n\n• `Private` `Readonly` **ethConnection**: `EthConnection`\n\nAn interface to the blockchain that is a little bit lower-level than [ContractsAPI](Backend_GameLogic_ContractsAPI.ContractsAPI.md). It\nallows us to do basic operations such as wait for a transaction to complete, check the player's\naddress and balance, etc.\n\n---\n\n### hashConfig\n\n• `Private` `Readonly` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\nEach round we change the hash configuration of the game. The hash configuration is download\nfrom the blockchain, and essentially acts as a salt, permuting the universe into a unique\nconfiguration for each new round.\n\n**`todo`** deduplicate this and `useMockHash` somehow.\n\n---\n\n### hashRate\n\n• `Private` **hashRate**: `number`\n\nContinuously updated value representing the total hashes per second that the game is currently\nmining the universe at.\n\n**`todo`** keep this in {@link MinerManager}\n\n---\n\n### homeLocation\n\n• `Private` **homeLocation**: `undefined` \\| `WorldLocation`\n\nThe spawn location of the current player.\n\n**`todo,`** make this smarter somehow. It's really annoying to have to import world coordinates, and\nget them wrong or something. Maybe we need to mark a planet, once it's been initialized\ncontract-side, as the homeworld of the user who initialized on it. That way, when you import a\nnew account into the game, and you import map data that contains your home planet, the client\nwould be able to automatically detect which planet is the player's home planet.\n\n**`todo`** move this into a new `PlayerState` class.\n\n---\n\n### minerManager\n\n• `Private` `Optional` **minerManager**: [`default`](Backend_Miner_MinerManager.default.md)\n\nManages the process of mining new space territory.\n\n---\n\n### networkHealth$\n\n• **networkHealth$**: `Monomitter`<`NetworkHealthSummary`\\>\n\nEmits whenever we load the network health summary from the webserver, which is derived from\ndiagnostics that the client sends up to the webserver as well.\n\n---\n\n### networkHealthInterval\n\n• `Private` **networkHealthInterval**: `Timer`\n\nHandle to an interval that periodically refreshes the network's health from our webserver.\n\n---\n\n### paused\n\n• `Private` **paused**: `boolean`\n\n---\n\n### paused$\n\n• **paused$**: `Monomitter`<`boolean`\\>\n\n---\n\n### persistentChunkStore\n\n• `Private` `Readonly` **persistentChunkStore**: [`default`](Backend_Storage_PersistentChunkStore.default.md)\n\nAn object that syncs any newly added or deleted chunks to the player's IndexedDB.\n\n**`todo`** it also persists other game data to IndexedDB. This class needs to be renamed `GameSaver`\nor something like that.\n\n---\n\n### planetHashMimc\n\n• `Private` `Readonly` **planetHashMimc**: (...`inputs`: `number`[]) => `BigInteger`\n\n#### Type declaration\n\n▸ (...`inputs`): `BigInteger`\n\nThe aforementioned hash function. In debug mode where `DISABLE_ZK_CHECKS` is on, we use a\nfaster hash function. Othewise, in production mode, use MiMC hash (https://byt3bit.github.io/primesym/).\n\n##### Parameters\n\n| Name        | Type       |\n| :---------- | :--------- |\n| `...inputs` | `number`[] |\n\n##### Returns\n\n`BigInteger`\n\n---\n\n### playerInterval\n\n• `Private` **playerInterval**: `Timer`\n\nHandle to an interval that periodically refreshes some information about the player from the\nblockchain.\n\n**`todo`** move this into a new `PlayerState` class.\n\n---\n\n### players\n\n• `Private` `Readonly` **players**: `Map`<`string`, `Player`\\>\n\nMap from ethereum addresses to player objects. This isn't stored in [GameObjects](Backend_GameLogic_GameObjects.GameObjects.md),\nbecause it's not techincally an entity that exists in the world. A player just controls planets\nand artifacts that do exist in the world.\n\n**`todo`** move this into a new `Players` class.\n\n---\n\n### playersUpdated$\n\n• `Readonly` **playersUpdated$**: `Monomitter`<`void`\\>\n\nWhenever we refresh the players twitter accounts or scores, we publish an event here.\n\n---\n\n### safeMode\n\n• `Private` **safeMode**: `boolean`\n\nSetting to allow players to start game without plugins that were running during the previous\nrun of the game client. By default, the game launches plugins that were running that were\nrunning when the game was last closed.\n\n---\n\n### scoreboardInterval\n\n• `Private` **scoreboardInterval**: `Timer`\n\nHandle to an interval that periodically refreshes the scoreboard from our webserver.\n\n---\n\n### settingsSubscription\n\n• `Private` **settingsSubscription**: `undefined` \\| `Subscription`\n\nSubscription to act on setting changes\n\n---\n\n### snarkHelper\n\n• `Private` `Readonly` **snarkHelper**: [`default`](Backend_Utils_SnarkArgsHelper.default.md)\n\nResponsible for generating snark proofs.\n\n---\n\n### terminal\n\n• `Private` `Readonly` **terminal**: `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\>\n\nKind of hacky, but we store a reference to the terminal that the player sees when the initially\nload into the game. This is the same exact terminal that appears inside the collapsable right\nbar of the game.\n\n---\n\n### useMockHash\n\n• `Private` `Readonly` **useMockHash**: `boolean`\n\nIn debug builds of the game, we can connect to a set of contracts deployed to a local\nblockchain, which are tweaked to not verify planet hashes, meaning we can use a faster hash\nfunction with similar properties to mimc. This allows us to mine the map faster in debug mode.\n\n**`todo`** move this into a separate `GameConfiguration` class.\n\n---\n\n### worldRadius\n\n• `Private` **worldRadius**: `number`\n\nSometimes the universe gets bigger... Sometimes it doesn't.\n\n**`todo`** move this into a new `GameConfiguration` class.\n\n## Accessors\n\n### captureZoneGeneratedEmitter\n\n• `get` **captureZoneGeneratedEmitter**(): `undefined` \\| `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\\>\n\nEmits when new capture zones are generated.\n\n#### Returns\n\n`undefined` \\| `Monomitter`<[`CaptureZonesGeneratedEvent`](../modules/Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\\>\n\n---\n\n### planetRarity\n\n• `get` **planetRarity**(): `number`\n\n#### Returns\n\n`number`\n\n## Methods\n\n### activateArtifact\n\n▸ **activateArtifact**(`locationId`, `artifactId`, `wormholeTo`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedActivateArtifact`\\>\\>\n\n#### Parameters\n\n| Name           | Type                        | Default value |\n| :------------- | :-------------------------- | :------------ |\n| `locationId`   | `LocationId`                | `undefined`   |\n| `artifactId`   | `ArtifactId`                | `undefined`   |\n| `wormholeTo`   | `undefined` \\| `LocationId` | `undefined`   |\n| `bypassChecks` | `boolean`                   | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedActivateArtifact`\\>\\>\n\n---\n\n### addAccount\n\n▸ **addAccount**(`coords`): `Promise`<`boolean`\\>\n\nInitializes a new player's game to start at the given home planet. Must have already\ninitialized the player on the contract.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### addNewChunk\n\n▸ **addNewChunk**(`chunk`): [`default`](Backend_GameLogic_GameManager.default.md)\n\nMakes this game manager aware of a new chunk - which includes its location, size,\nas well as all of the planets contained in that chunk. Causes the client to load\nall of the information about those planets from the blockchain.\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n[`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### biomebasePerlin\n\n▸ **biomebasePerlin**(`coords`, `floor`): `number`\n\nGets the biome perlin valie at the given location in the world.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n| `floor`  | `boolean`     |\n\n#### Returns\n\n`number`\n\n---\n\n### bulkAddNewChunks\n\n▸ **bulkAddNewChunks**(`chunks`): `Promise`<`void`\\>\n\nTo add multiple chunks at once, use this function rather than `addNewChunk`, in order\nto load all of the associated planet data in an efficient manner.\n\n#### Parameters\n\n| Name     | Type      |\n| :------- | :-------- |\n| `chunks` | `Chunk`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### bulkHardRefreshPlanets\n\n▸ `Private` **bulkHardRefreshPlanets**(`planetIds`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name        | Type           |\n| :---------- | :------------- |\n| `planetIds` | `LocationId`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### buyHat\n\n▸ **buyHat**(`planetId`, `_bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedBuyHat`\\>\\>\n\nSubmits a transaction to the blockchain to buy a hat for the given planet. You must own the\nplanet. Warning costs real xdai. Hats are permanently locked to a planet. They are purely\ncosmetic and a great way to BM your opponents or just look your best. Just like in the real\nworld, more money means more hat.\n\n#### Parameters\n\n| Name            | Type         | Default value |\n| :-------------- | :----------- | :------------ |\n| `planetId`      | `LocationId` | `undefined`   |\n| `_bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedBuyHat`\\>\\>\n\n---\n\n### capturePlanet\n\n▸ **capturePlanet**(`locationId`): `Promise`<`Transaction`<`UnconfirmedCapturePlanet`\\>\\>\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedCapturePlanet`\\>\\>\n\n---\n\n### checkGameHasEnded\n\n▸ `Private` **checkGameHasEnded**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### clearEmoji\n\n▸ **clearEmoji**(`locationId`): `Promise`<`void`\\>\n\nIf you are the owner of this planet, you can delete the emoji that is hovering above the\nplanet.\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### deactivateArtifact\n\n▸ **deactivateArtifact**(`locationId`, `artifactId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedDeactivateArtifact`\\>\\>\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `locationId`   | `LocationId` | `undefined`   |\n| `artifactId`   | `ArtifactId` | `undefined`   |\n| `bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedDeactivateArtifact`\\>\\>\n\n---\n\n### depositArtifact\n\n▸ **depositArtifact**(`locationId`, `artifactId`): `Promise`<`Transaction`<`UnconfirmedDepositArtifact`\\>\\>\n\nSubmits a transaction to the blockchain to deposit an artifact on a given planet.\nYou must own the planet and you must own the artifact directly (can't be locked in contract)\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedDepositArtifact`\\>\\>\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### findArtifact\n\n▸ **findArtifact**(`planetId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedFindArtifact`\\>\\>\n\nCalls the contract to find an artifact on the given planet.\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `planetId`     | `LocationId` | `undefined`   |\n| `bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedFindArtifact`\\>\\>\n\n---\n\n### findRandomHomePlanet\n\n▸ `Private` **findRandomHomePlanet**(): `Promise`<`LocatablePlanet`\\>\n\n#### Returns\n\n`Promise`<`LocatablePlanet`\\>\n\n---\n\n### forceTick\n\n▸ **forceTick**(`locationId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### getAccount\n\n▸ **getAccount**(): `undefined` \\| `EthAddress`\n\nGets the address of the player logged into this game manager.\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### getActiveArtifact\n\n▸ **getActiveArtifact**(`planet`): `undefined` \\| `Artifact`\n\nGets the active artifact on this planet, if one exists.\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`undefined` \\| `Artifact`\n\n---\n\n### getAddress\n\n▸ **getAddress**(): `undefined` \\| `EthAddress`\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### getAllOwnedPlanets\n\n▸ **getAllOwnedPlanets**(): `Planet`[]\n\nGets a list of planets that have an owner.\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getAllPlanets\n\n▸ **getAllPlanets**(): `Iterable`<`Planet`\\>\n\nGets all planets. This means all planets that are in the contract, and also all\nplanets that have been mined locally. Does not update planets if they are stale.\nNOT PERFORMANT - for scripting only.\n\n#### Returns\n\n`Iterable`<`Planet`\\>\n\n---\n\n### getAllPlayers\n\n▸ **getAllPlayers**(): `Player`[]\n\nGets a list of all the players in the game (not just the ones you've\nencounterd)\n\n#### Returns\n\n`Player`[]\n\n---\n\n### getAllVoyages\n\n▸ **getAllVoyages**(): `QueuedArrival`[]\n\nGets all voyages that have not completed.\n\n#### Returns\n\n`QueuedArrival`[]\n\n---\n\n### getArtifactMap\n\n▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\nReturn a reference to the artifact map\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getArtifactUpdated$\n\n▸ **getArtifactUpdated$**(): `Monomitter`<`ArtifactId`\\>\n\n#### Returns\n\n`Monomitter`<`ArtifactId`\\>\n\n---\n\n### getArtifactWithId\n\n▸ **getArtifactWithId**(`artifactId?`): `undefined` \\| `Artifact`\n\nGets the artifact with the given id. Null if no artifact with id exists.\n\n#### Parameters\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `artifactId?` | `ArtifactId` |\n\n#### Returns\n\n`undefined` \\| `Artifact`\n\n---\n\n### getArtifactsWithIds\n\n▸ **getArtifactsWithIds**(`artifactIds?`): (`undefined` \\| `Artifact`)[]\n\nGets the artifacts with the given ids, including ones we know exist but haven't been loaded,\nrepresented by `undefined`.\n\n#### Parameters\n\n| Name          | Type           | Default value |\n| :------------ | :------------- | :------------ |\n| `artifactIds` | `ArtifactId`[] | `[]`          |\n\n#### Returns\n\n(`undefined` \\| `Artifact`)[]\n\n---\n\n### getCaptureZoneGenerator\n\n▸ **getCaptureZoneGenerator**(): `undefined` \\| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\n#### Returns\n\n`undefined` \\| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\n---\n\n### getCaptureZones\n\n▸ **getCaptureZones**(): `Set`<`CaptureZone`\\>\n\n#### Returns\n\n`Set`<`CaptureZone`\\>\n\n---\n\n### getChunk\n\n▸ **getChunk**(`chunkFootprint`): `undefined` \\| `Chunk`\n\n#### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkFootprint` | `Rectangle` |\n\n#### Returns\n\n`undefined` \\| `Chunk`\n\n---\n\n### getChunkStore\n\n▸ **getChunkStore**(): [`default`](Backend_Storage_PersistentChunkStore.default.md)\n\n#### Returns\n\n[`default`](Backend_Storage_PersistentChunkStore.default.md)\n\n---\n\n### getClaimedLocations\n\n▸ **getClaimedLocations**(): `Map`<`LocationId`, `ClaimedLocation`\\>\n\nGets a map of all location IDs which have been claimed.\n\n#### Returns\n\n`Map`<`LocationId`, `ClaimedLocation`\\>\n\n---\n\n### getConstructors\n\n▸ **getConstructors**(): `Object`\n\nReturns constructors of classes that may be useful for developing plugins.\n\n#### Returns\n\n`Object`\n\n| Name                     | Type                                                                                      |\n| :----------------------- | :---------------------------------------------------------------------------------------- |\n| `MinerManager`           | typeof [`default`](Backend_Miner_MinerManager.default.md)                                 |\n| `SpiralPattern`          | typeof [`SpiralPattern`](Backend_Miner_MiningPatterns.SpiralPattern.md)                   |\n| `SwissCheesePattern`     | typeof [`SwissCheesePattern`](Backend_Miner_MiningPatterns.SwissCheesePattern.md)         |\n| `TowardsCenterPattern`   | typeof [`TowardsCenterPattern`](Backend_Miner_MiningPatterns.TowardsCenterPattern.md)     |\n| `TowardsCenterPatternV2` | typeof [`TowardsCenterPatternV2`](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md) |\n\n---\n\n### getContract\n\n▸ **getContract**(): `DarkForest`\n\n#### Returns\n\n`DarkForest`\n\n---\n\n### getContractAPI\n\n▸ **getContractAPI**(): [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md)\n\nGet the thing that handles contract interaction.\n\n#### Returns\n\n[`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md)\n\n---\n\n### getContractAddress\n\n▸ **getContractAddress**(): `EthAddress`\n\nGets the address of the `DarkForest` contract, which is the 'backend' of the game.\n\n#### Returns\n\n`EthAddress`\n\n---\n\n### getContractConstants\n\n▸ **getContractConstants**(): [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n#### Returns\n\n[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n---\n\n### getCurrentlyExploringChunk\n\n▸ **getCurrentlyExploringChunk**(): `undefined` \\| `Rectangle`\n\nGets the rectangle bounding the chunk that the miner is currently in the process\nof hashing.\n\n#### Returns\n\n`undefined` \\| `Rectangle`\n\n---\n\n### getDefaultSpaceJunkForPlanetLevel\n\n▸ **getDefaultSpaceJunkForPlanetLevel**(`level`): `number`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `level` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getDiagnostics\n\n▸ **getDiagnostics**(): `Diagnostics`\n\nGets some diagnostic information about the game. Returns a copy, you can't modify it.\n\n#### Returns\n\n`Diagnostics`\n\n---\n\n### getDist\n\n▸ **getDist**(`fromId`, `toId`): `number`\n\nGets the distance between two planets. Throws an exception if you don't\nknow the location of either planet. Takes into account wormholes.\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `fromId` | `LocationId` |\n| `toId`   | `LocationId` |\n\n#### Returns\n\n`number`\n\n---\n\n### getDistCoords\n\n▸ **getDistCoords**(`fromCoords`, `toCoords`): `number`\n\nGets the distance between two coordinates in space.\n\n#### Parameters\n\n| Name         | Type          |\n| :----------- | :------------ |\n| `fromCoords` | `WorldCoords` |\n| `toCoords`   | `WorldCoords` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEndTimeSeconds\n\n▸ **getEndTimeSeconds**(): `number`\n\nThe game ends at a particular time in the future - get this time measured\nin seconds from the epoch.\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyArrivingForMove\n\n▸ **getEnergyArrivingForMove**(`fromId`, `toId`, `distance`, `sentEnergy`, `abandoning`): `number`\n\nGets the amount of energy that would arrive if a voyage with the given parameters\nwas to occur. The toPlanet is optional, in case you want an estimate that doesn't include\nwormhole speedups.\n\n#### Parameters\n\n| Name         | Type                        |\n| :----------- | :-------------------------- |\n| `fromId`     | `LocationId`                |\n| `toId`       | `undefined` \\| `LocationId` |\n| `distance`   | `undefined` \\| `number`     |\n| `sentEnergy` | `number`                    |\n| `abandoning` | `boolean`                   |\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyCurveAtPercent\n\n▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number`\n\nreturns timestamp (seconds) that planet will reach percent% of energycap\ntime may be in the past\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyNeededForMove\n\n▸ **getEnergyNeededForMove**(`fromId`, `toId`, `arrivingEnergy`, `abandoning?`): `number`\n\nGets the amount of energy needed in order for a voyage from the given to the given\nplanet to arrive with your desired amount of energy.\n\n#### Parameters\n\n| Name             | Type         | Default value |\n| :--------------- | :----------- | :------------ |\n| `fromId`         | `LocationId` | `undefined`   |\n| `toId`           | `LocationId` | `undefined`   |\n| `arrivingEnergy` | `number`     | `undefined`   |\n| `abandoning`     | `boolean`    | `false`       |\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyOfPlayer\n\n▸ **getEnergyOfPlayer**(`player`): `number`\n\nGets the total amount of energy that lives on planets that the given player owns.\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEthConnection\n\n▸ **getEthConnection**(): `EthConnection`\n\n#### Returns\n\n`EthConnection`\n\n---\n\n### getExploredChunks\n\n▸ **getExploredChunks**(): `Iterable`<`Chunk`\\>\n\nGets all the map chunks that this client is aware of. Chunks may have come from\nmining, or from importing map data.\n\n#### Returns\n\n`Iterable`<`Chunk`\\>\n\n---\n\n### getGameObjects\n\n▸ **getGameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\nGets a reference to the game's internal representation of the world state. This includes\nvoyages, planets, artifacts, and active wormholes,\n\n#### Returns\n\n[`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\n---\n\n### getHashConfig\n\n▸ **getHashConfig**(): [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\nGets the HASH CONFIG\n\n#### Returns\n\n[`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\n---\n\n### getHashesPerSec\n\n▸ **getHashesPerSec**(): `number`\n\nGets the amount of hashes per second that the miner manager is calculating.\n\n#### Returns\n\n`number`\n\n---\n\n### getHomeCoords\n\n▸ **getHomeCoords**(): `undefined` \\| `WorldCoords`\n\nGets the location of your home planet.\n\n#### Returns\n\n`undefined` \\| `WorldCoords`\n\n---\n\n### getHomeHash\n\n▸ **getHomeHash**(): `undefined` \\| `LocationId`\n\nGets the hash of the location of your home planet.\n\n#### Returns\n\n`undefined` \\| `LocationId`\n\n---\n\n### getLocationOfPlanet\n\n▸ **getLocationOfPlanet**(`planetId`): `undefined` \\| `WorldLocation`\n\nGets the location of the given planet. Returns undefined if the planet does not exist, or if\nwe do not know the location of this planet NOT update the planet if the planet is stale,\nwhich means this function is fast.\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `WorldLocation`\n\n---\n\n### getMaxMoveDist\n\n▸ **getMaxMoveDist**(`planetId`, `sendingPercent`, `abandoning`): `number`\n\nGets the maximuim distance that you can send your energy from the given planet,\nusing the given percentage of that planet's current silver.\n\n#### Parameters\n\n| Name             | Type         |\n| :--------------- | :----------- |\n| `planetId`       | `LocationId` |\n| `sendingPercent` | `number`     |\n| `abandoning`     | `boolean`    |\n\n#### Returns\n\n`number`\n\n---\n\n### getMiningPattern\n\n▸ **getMiningPattern**(): `undefined` \\| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\nGets the mining pattern that the miner is currently using.\n\n#### Returns\n\n`undefined` \\| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n---\n\n### getMyArtifactMap\n\n▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\nReturn a reference to the map of my artifacts\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getMyArtifacts\n\n▸ **getMyArtifacts**(): `Artifact`[]\n\ngets both deposited artifacts that are on planets i own as well as artifacts i own\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getMyArtifactsUpdated$\n\n▸ **getMyArtifactsUpdated$**(): `Monomitter`<`Map`<`ArtifactId`, `Artifact`\\>\\>\n\n#### Returns\n\n`Monomitter`<`Map`<`ArtifactId`, `Artifact`\\>\\>\n\n---\n\n### getMyBalance\n\n▸ **getMyBalance**(): `BigNumber`\n\nGets the balance of the account\n\n#### Returns\n\n`BigNumber`\n\n---\n\n### getMyBalance$\n\n▸ **getMyBalance$**(): `Monomitter`<`BigNumber`\\>\n\nReturns the monomitter which publishes events whenever the player's balance changes.\n\n#### Returns\n\n`Monomitter`<`BigNumber`\\>\n\n---\n\n### getMyBalanceEth\n\n▸ **getMyBalanceEth**(): `number`\n\nGets the balance of the account measured in Eth (i.e. in full units of the chain).\n\n#### Returns\n\n`number`\n\n---\n\n### getMyPlanetMap\n\n▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\nReturn a reference to the map of my planets\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getMyPlanets\n\n▸ **getMyPlanets**(): `Planet`[]\n\nGets a list of the planets that the player logged into this `GameManager` owns.\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getMyPlanetsUpdated$\n\n▸ **getMyPlanetsUpdated$**(): `Monomitter`<`Map`<`LocationId`, `Planet`\\>\\>\n\n#### Returns\n\n`Monomitter`<`Map`<`LocationId`, `Planet`\\>\\>\n\n---\n\n### getMyScore\n\n▸ **getMyScore**(): `undefined` \\| `number`\n\nGet the score of the currently logged-in account.\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getNextBroadcastAvailableTimestamp\n\n▸ **getNextBroadcastAvailableTimestamp**(): `number`\n\nGets the timestamp (ms) of the next time that we can broadcast the coordinates of a planet.\n\n#### Returns\n\n`number`\n\n---\n\n### getNextClaimAvailableTimestamp\n\n▸ **getNextClaimAvailableTimestamp**(): `number`\n\nGets the timestamp (ms) of the next time that we can claim a planet.\n\n#### Returns\n\n`number`\n\n---\n\n### getNextRevealCountdownInfo\n\n▸ **getNextRevealCountdownInfo**(): [`RevealCountdownInfo`](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md)\n\nReturns info about the next time you can broadcast coordinates\n\n#### Returns\n\n[`RevealCountdownInfo`](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md)\n\n---\n\n### getNotificationsManager\n\n▸ **getNotificationsManager**(): [`default`](Frontend_Game_NotificationManager.default.md)\n\n#### Returns\n\n[`default`](Frontend_Game_NotificationManager.default.md)\n\n---\n\n### getPaused\n\n▸ **getPaused**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getPaused$\n\n▸ **getPaused$**(): `Monomitter`<`boolean`\\>\n\n#### Returns\n\n`Monomitter`<`boolean`\\>\n\n---\n\n### getPerlinThresholds\n\n▸ **getPerlinThresholds**(): [`number`, `number`, `number`]\n\nThe perlin value at each coordinate determines the space type. There are four space\ntypes, which means there are four ranges on the number line that correspond to\neach space type. This function returns the boundary values between each of these\nfour ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`.\n\n#### Returns\n\n[`number`, `number`, `number`]\n\n---\n\n### getPlanetLevel\n\n▸ **getPlanetLevel**(`planetId`): `undefined` \\| `PlanetLevel`\n\nGets the level of the given planet. Returns undefined if the planet does not exist. Does\nNOT update the planet if the planet is stale, which means this function is fast.\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `PlanetLevel`\n\n---\n\n### getPlanetMap\n\n▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\nReturn a reference to the planet map\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getPlanetRarity\n\n▸ **getPlanetRarity**(): `number`\n\nGets the rarity of planets in the universe\n\n#### Returns\n\n`number`\n\n---\n\n### getPlanetUpdated$\n\n▸ **getPlanetUpdated$**(): `Monomitter`<`LocationId`\\>\n\n#### Returns\n\n`Monomitter`<`LocationId`\\>\n\n---\n\n### getPlanetWithCoords\n\n▸ **getPlanetWithCoords**(`coords`): `undefined` \\| `LocatablePlanet`\n\nGets the planet that is located at the given coordinates. Returns undefined if not a valid\nlocation or if no planet exists at location. If the planet needs to be updated (because\nsome time has passed since we last updated the planet), then updates that planet first.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`undefined` \\| `LocatablePlanet`\n\n---\n\n### getPlanetWithId\n\n▸ **getPlanetWithId**(`planetId`): `undefined` \\| `Planet`\n\nGets the planet with the given hash. Returns undefined if the planet is neither in the contract\nnor has been discovered locally. If the planet needs to be updated (because some time has\npassed since we last updated the planet), then updates that planet first.\n\n#### Parameters\n\n| Name       | Type                        |\n| :--------- | :-------------------------- |\n| `planetId` | `undefined` \\| `LocationId` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPlanetsInRange\n\n▸ **getPlanetsInRange**(`planetId`, `sendingPercent`, `abandoning`): `Planet`[]\n\nGets all the planets that you can reach with at least 1 energy from\nthe given planet. Does not take into account wormholes.\n\n#### Parameters\n\n| Name             | Type         |\n| :--------------- | :----------- |\n| `planetId`       | `LocationId` |\n| `sendingPercent` | `number`     |\n| `abandoning`     | `boolean`    |\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getPlanetsInWorldRectangle\n\n▸ **getPlanetsInWorldRectangle**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `levels`, `planetLevelToRadii`, `updateIfStale?`): `LocatablePlanet`[]\n\nGets the ids of all the planets that are both within the given bounding box (defined by its bottom\nleft coordinate, width, and height) in the world and of a level that was passed in via the\n`planetLevels` parameter.\n\n#### Parameters\n\n| Name                 | Type                      | Default value |\n| :------------------- | :------------------------ | :------------ |\n| `worldX`             | `number`                  | `undefined`   |\n| `worldY`             | `number`                  | `undefined`   |\n| `worldWidth`         | `number`                  | `undefined`   |\n| `worldHeight`        | `number`                  | `undefined`   |\n| `levels`             | `number`[]                | `undefined`   |\n| `planetLevelToRadii` | `Map`<`number`, `Radii`\\> | `undefined`   |\n| `updateIfStale`      | `boolean`                 | `true`        |\n\n#### Returns\n\n`LocatablePlanet`[]\n\n---\n\n### getPlanetsWithIds\n\n▸ **getPlanetsWithIds**(`planetId`): `Planet`[]\n\nGets a list of planets in the client's memory with the given ids. If a planet with the given id\ndoesn't exist, no entry for that planet will be returned in the result.\n\n#### Parameters\n\n| Name       | Type           |\n| :--------- | :------------- |\n| `planetId` | `LocationId`[] |\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getPlayer\n\n▸ **getPlayer**(`address?`): `undefined` \\| `Player`\n\nGets either the given player, or if no address was provided, gets the player that is logged\nthis client.\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `address?` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `Player`\n\n---\n\n### getPlayerScore\n\n▸ **getPlayerScore**(`addr`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name   | Type         |\n| :----- | :----------- |\n| `addr` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getPlayerSpaceJunk\n\n▸ **getPlayerSpaceJunk**(`addr`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name   | Type         |\n| :----- | :----------- |\n| `addr` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getPlayerSpaceJunkLimit\n\n▸ **getPlayerSpaceJunkLimit**(`addr`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name   | Type         |\n| :----- | :----------- |\n| `addr` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getPrivateKey\n\n▸ **getPrivateKey**(): `undefined` \\| `string`\n\nGets the private key of the burner wallet used by this account.\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getRangeBuff\n\n▸ **getRangeBuff**(`abandoning`): `number`\n\n#### Parameters\n\n| Name         | Type      |\n| :----------- | :-------- |\n| `abandoning` | `boolean` |\n\n#### Returns\n\n`number`\n\n---\n\n### getRevealedLocations\n\n▸ **getRevealedLocations**(): `Map`<`LocationId`, `RevealedLocation`\\>\n\nGets a map of all location IDs whose coords have been publically revealed\n\n#### Returns\n\n`Map`<`LocationId`, `RevealedLocation`\\>\n\n---\n\n### getSafeMode\n\n▸ **getSafeMode**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getSignedTwitter\n\n▸ **getSignedTwitter**(`twitter`): `Promise`<`string`\\>\n\nSigns the given twitter handle with the private key of the current user. Used to\nverify that the person who owns the Dark Forest account was the one that attempted\nto link a twitter to their account.\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`string`\\>\n\n---\n\n### getSilverCurveAtPercent\n\n▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \\| `number`\n\nreturns timestamp (seconds) that planet will reach percent% of silcap if\ndoesn't produce silver, returns undefined if already over percent% of silcap,\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getSilverOfPlayer\n\n▸ **getSilverOfPlayer**(`player`): `number`\n\nGets the total amount of silver that lives on planets that the given player owns.\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`number`\n\n---\n\n### getSnarkHelper\n\n▸ **getSnarkHelper**(): [`default`](Backend_Utils_SnarkArgsHelper.default.md)\n\n#### Returns\n\n[`default`](Backend_Utils_SnarkArgsHelper.default.md)\n\n---\n\n### getSpaceships\n\n▸ `Private` **getSpaceships**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### getSpeedBuff\n\n▸ **getSpeedBuff**(`abandoning`): `number`\n\nRight now the only buffs supported in this way are\nspeed/range buffs from Abandoning a planet.\n\nThe abandoning argument is used when interacting with\nthis function programmatically.\n\n#### Parameters\n\n| Name         | Type      |\n| :----------- | :-------- |\n| `abandoning` | `boolean` |\n\n#### Returns\n\n`number`\n\n---\n\n### getStalePlanetWithId\n\n▸ **getStalePlanetWithId**(`planetId`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getTemperature\n\n▸ **getTemperature**(`coords`): `number`\n\nGets the temperature of a given location.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`number`\n\n---\n\n### getTimeForMove\n\n▸ **getTimeForMove**(`fromId`, `toId`, `abandoning?`): `number`\n\nGets the amount of time, in seconds that a voyage between from the first to the\nsecond planet would take.\n\n#### Parameters\n\n| Name         | Type         | Default value |\n| :----------- | :----------- | :------------ |\n| `fromId`     | `LocationId` | `undefined`   |\n| `toId`       | `LocationId` | `undefined`   |\n| `abandoning` | `boolean`    | `false`       |\n\n#### Returns\n\n`number`\n\n---\n\n### getTokenMintEndTimeSeconds\n\n▸ **getTokenMintEndTimeSeconds**(): `number`\n\nDark Forest tokens can only be minted up to a certain time - get this time measured in seconds from epoch.\n\n#### Returns\n\n`number`\n\n---\n\n### getTwitter\n\n▸ **getTwitter**(`address`): `undefined` \\| `string`\n\nGets the twitter handle of the given ethereum account which is associated\nwith Dark Forest.\n\n#### Parameters\n\n| Name      | Type                        |\n| :-------- | :-------------------------- |\n| `address` | `undefined` \\| `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getUIEventEmitter\n\n▸ **getUIEventEmitter**(): [`default`](Frontend_Utils_UIEmitter.default.md)\n\nHelpful for listening to user input events.\n\n#### Returns\n\n[`default`](Frontend_Utils_UIEmitter.default.md)\n\n---\n\n### getUnconfirmedMoves\n\n▸ **getUnconfirmedMoves**(): `Transaction`<`UnconfirmedMove`\\>[]\n\nGets all moves that this client has queued to be uploaded to the contract, but\nhave not been successfully confirmed yet.\n\n#### Returns\n\n`Transaction`<`UnconfirmedMove`\\>[]\n\n---\n\n### getUnconfirmedUpgrades\n\n▸ **getUnconfirmedUpgrades**(): `Transaction`<`UnconfirmedUpgrade`\\>[]\n\nGets all upgrades that this client has queued to be uploaded to the contract, but\nhave not been successfully confirmed yet.\n\n#### Returns\n\n`Transaction`<`UnconfirmedUpgrade`\\>[]\n\n---\n\n### getUnconfirmedWormholeActivations\n\n▸ **getUnconfirmedWormholeActivations**(): `Transaction`<`UnconfirmedActivateArtifact`\\>[]\n\n#### Returns\n\n`Transaction`<`UnconfirmedActivateArtifact`\\>[]\n\n---\n\n### getUniverseTotalEnergy\n\n▸ **getUniverseTotalEnergy**(): `number`\n\nGets the total amount of energy that lives on a planet that somebody owns.\n\n#### Returns\n\n`number`\n\n---\n\n### getUpgrade\n\n▸ **getUpgrade**(`branch`, `level`): `Upgrade`\n\nReturns the upgrade that would be applied to a planet given a particular\nupgrade branch (defense, range, speed) and level of upgrade.\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `branch` | `number` |\n| `level`  | `number` |\n\n#### Returns\n\n`Upgrade`\n\n---\n\n### getWorldRadius\n\n▸ **getWorldRadius**(): `number`\n\nGets the radius of the playable area of the universe.\n\n#### Returns\n\n`number`\n\n---\n\n### getWorldSilver\n\n▸ **getWorldSilver**(): `number`\n\nGets the total amount of silver that lives on a planet that somebody owns.\n\n#### Returns\n\n`number`\n\n---\n\n### getWormholeFactors\n\n▸ **getWormholeFactors**(`fromPlanet`, `toPlanet`): `undefined` \\| { `distanceFactor`: `number` ; `speedFactor`: `number` }\n\nIf there's an active artifact on either of these planets which happens to be a wormhole which\nis active and targetting the other planet, return the wormhole boost which is greater. Values\nrepresent a multiplier.\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `fromPlanet` | `Planet` |\n| `toPlanet`   | `Planet` |\n\n#### Returns\n\n`undefined` \\| { `distanceFactor`: `number` ; `speedFactor`: `number` }\n\n---\n\n### getWormholes\n\n▸ **getWormholes**(): `Iterable`<`Wormhole`\\>\n\n#### Returns\n\n`Iterable`<`Wormhole`\\>\n\n---\n\n### hardRefreshArtifact\n\n▸ **hardRefreshArtifact**(`artifactId`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### hardRefreshPlanet\n\n▸ **hardRefreshPlanet**(`planetId`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### hardRefreshPlayer\n\n▸ `Private` **hardRefreshPlayer**(`address?`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `address?` | `EthAddress` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### hasJoinedGame\n\n▸ **hasJoinedGame**(): `boolean`\n\nWhether or not this client has successfully found and landed on a home planet.\n\n#### Returns\n\n`boolean`\n\n---\n\n### hasMinedChunk\n\n▸ **hasMinedChunk**(`chunkLocation`): `boolean`\n\nWhether or not the given rectangle has been mined.\n\n#### Parameters\n\n| Name            | Type        |\n| :-------------- | :---------- |\n| `chunkLocation` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### initMiningManager\n\n▸ `Private` **initMiningManager**(`homeCoords`, `cores?`): `void`\n\n#### Parameters\n\n| Name         | Type          |\n| :----------- | :------------ |\n| `homeCoords` | `WorldCoords` |\n| `cores?`     | `number`      |\n\n#### Returns\n\n`void`\n\n---\n\n### invadePlanet\n\n▸ **invadePlanet**(`locationId`): `Promise`<`Transaction`<`UnconfirmedInvadePlanet`\\>\\>\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedInvadePlanet`\\>\\>\n\n---\n\n### isAdmin\n\n▸ **isAdmin**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isMining\n\n▸ **isMining**(): `boolean`\n\nWhether or not the miner is currently exploring space.\n\n#### Returns\n\n`boolean`\n\n---\n\n### isPlanetMineable\n\n▸ **isPlanetMineable**(`p`): `boolean`\n\nWhether or not the given planet is capable of minting an artifact.\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `p`  | `Planet` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isRoundOver\n\n▸ **isRoundOver**(): `boolean`\n\nReturns whether or not the current round has ended.\n\n#### Returns\n\n`boolean`\n\n---\n\n### joinGame\n\n▸ **joinGame**(`beforeRetry`): `Promise`<`void`\\>\n\nAttempts to join the game. Should not be called once you've already joined.\n\n#### Parameters\n\n| Name          | Type                                    |\n| :------------ | :-------------------------------------- |\n| `beforeRetry` | (`e`: `Error`) => `Promise`<`boolean`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### listenForNewBlock\n\n▸ **listenForNewBlock**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### loadContract\n\n▸ **loadContract**<`T`\\>(`contractAddress`, `contractABI`): `Promise`<`T`\\>\n\nReturns an instance of a `Contract` from the ethersjs library. This is the library we use to\nconnect to the blockchain. For documentation about how `Contract` works, see:\nhttps://docs.ethers.io/v5/api/contract/contract/\n\nAlso, registers your contract in the system to make calls against it and to reload it when\nnecessary (such as the RPC endpoint changing).\n\n#### Type parameters\n\n| Name | Type                     |\n| :--- | :----------------------- |\n| `T`  | extends `Contract`<`T`\\> |\n\n#### Parameters\n\n| Name              | Type                |\n| :---------------- | :------------------ |\n| `contractAddress` | `string`            |\n| `contractABI`     | `ContractInterface` |\n\n#### Returns\n\n`Promise`<`T`\\>\n\n---\n\n### loadPlugins\n\n▸ **loadPlugins**(): `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\\>\n\nLoad the serialized versions of all the plugins that this player has.\n\n#### Returns\n\n`Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\\>\n\n---\n\n### locationBigIntFromCoords\n\n▸ **locationBigIntFromCoords**(`coords`): `BigInteger`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`BigInteger`\n\n---\n\n### locationFromCoords\n\n▸ `Private` **locationFromCoords**(`coords`): `WorldLocation`\n\ncomputes the WorldLocation object corresponding to a set of coordinates\nvery slow since it actually calculates the hash; do not use in render loop\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`WorldLocation`\n\n---\n\n### move\n\n▸ **move**(`from`, `to`, `forces`, `silver`, `artifactMoved?`, `abandoning?`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedMove`\\>\\>\n\nSubmits a transaction to the blockchain to move the given amount of resources from\nthe given planet to the given planet.\n\n#### Parameters\n\n| Name             | Type         | Default value |\n| :--------------- | :----------- | :------------ |\n| `from`           | `LocationId` | `undefined`   |\n| `to`             | `LocationId` | `undefined`   |\n| `forces`         | `number`     | `undefined`   |\n| `silver`         | `number`     | `undefined`   |\n| `artifactMoved?` | `ArtifactId` | `undefined`   |\n| `abandoning`     | `boolean`    | `false`       |\n| `bypassChecks`   | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedMove`\\>\\>\n\n---\n\n### onTxCancelled\n\n▸ `Private` **onTxCancelled**(`tx`): `void`\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### onTxConfirmed\n\n▸ `Private` **onTxConfirmed**(`tx`): `void`\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### onTxReverted\n\n▸ `Private` **onTxReverted**(`tx`): `void`\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### onTxSubmit\n\n▸ `Private` **onTxSubmit**(`tx`): `void`\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### prospectPlanet\n\n▸ **prospectPlanet**(`planetId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedProspectPlanet`\\>\\>\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `planetId`     | `LocationId` | `undefined`   |\n| `bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedProspectPlanet`\\>\\>\n\n---\n\n### refreshNetworkHealth\n\n▸ `Private` **refreshNetworkHealth**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### refreshScoreboard\n\n▸ `Private` **refreshScoreboard**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### refreshServerPlanetStates\n\n▸ **refreshServerPlanetStates**(`planetIds`): `Promise`<`void`\\>\n\nWe have two locations which planet state can live: on the server, and on the blockchain. We use\nthe blockchain for the 'physics' of the universe, and the webserver for optional 'add-on'\nfeatures, which are cryptographically secure, but live off-chain.\n\nThis function loads the planet states which live on the server. Plays nicely with our\nnotifications system and sets the appropriate loading state values on the planet.\n\n#### Parameters\n\n| Name        | Type           |\n| :---------- | :------------- |\n| `planetIds` | `LocationId`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### refreshTwitters\n\n▸ `Private` **refreshTwitters**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### revealLocation\n\n▸ **revealLocation**(`planetId`): `Promise`<`Transaction`<`UnconfirmedReveal`\\>\\>\n\nReveals a planet's location on-chain.\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedReveal`\\>\\>\n\n---\n\n### savePlugins\n\n▸ **savePlugins**(`savedPlugins`): `Promise`<`void`\\>\n\nOverwrites all the saved plugins to equal the given array of plugins.\n\n#### Parameters\n\n| Name           | Type                                                                                       |\n| :------------- | :----------------------------------------------------------------------------------------- |\n| `savedPlugins` | [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### setMinerCores\n\n▸ **setMinerCores**(`nCores`): `void`\n\nSet the amount of cores to mine the universe with. More cores equals faster!\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `nCores` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setMiningPattern\n\n▸ **setMiningPattern**(`pattern`): `void`\n\nSets the mining pattern of the miner. This kills the old miner and starts this one.\n\n#### Parameters\n\n| Name      | Type                                                                           |\n| :-------- | :----------------------------------------------------------------------------- |\n| `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### setPlanetEmoji\n\n▸ **setPlanetEmoji**(`locationId`, `emojiStr`): `Promise`<`void`\\>\n\nIf you are the owner of this planet, you can set an 'emoji' to hover above the planet.\n`emojiStr` must be a string that contains a single emoji, otherwise this function will throw an\nerror.\n\nThe emoji is stored off-chain in a postgres database. We verify planet ownership via a contract\ncall from the webserver, and by verifying that the request to add (or remove) an emoji from a\nplanet was signed by the owner.\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `emojiStr`   | `string`     |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### setPlayerTwitters\n\n▸ `Private` **setPlayerTwitters**(`twitters`): `void`\n\n#### Parameters\n\n| Name       | Type                                                                                              |\n| :--------- | :------------------------------------------------------------------------------------------------ |\n| `twitters` | [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap) |\n\n#### Returns\n\n`void`\n\n---\n\n### setRadius\n\n▸ `Private` **setRadius**(`worldRadius`): `void`\n\n#### Parameters\n\n| Name          | Type     |\n| :------------ | :------- |\n| `worldRadius` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSafeMode\n\n▸ **setSafeMode**(`safeMode`): `void`\n\n#### Parameters\n\n| Name       | Type      |\n| :--------- | :-------- |\n| `safeMode` | `boolean` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSnarkCacheSize\n\n▸ **setSnarkCacheSize**(`size`): `void`\n\nChanges the amount of move snark proofs that are cached.\n\n#### Parameters\n\n| Name   | Type     |\n| :----- | :------- |\n| `size` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### softRefreshPlanet\n\n▸ `Private` **softRefreshPlanet**(`planetId`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### spaceTypeFromPerlin\n\n▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType`\n\nEach coordinate lives in a particular type of space, determined by a smooth random\nfunction called 'perlin noise.\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `perlin` | `number` |\n\n#### Returns\n\n`SpaceType`\n\n---\n\n### spaceTypePerlin\n\n▸ **spaceTypePerlin**(`coords`, `floor`): `number`\n\nGets the perlin value at the given location in the world. SpaceType is based\non this value.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n| `floor`  | `boolean`     |\n\n#### Returns\n\n`number`\n\n---\n\n### startExplore\n\n▸ **startExplore**(): `void`\n\nStarts the miner.\n\n#### Returns\n\n`void`\n\n---\n\n### stopExplore\n\n▸ **stopExplore**(): `void`\n\nStops the miner.\n\n#### Returns\n\n`void`\n\n---\n\n### submitDisconnectTwitter\n\n▸ **submitDisconnectTwitter**(`twitter`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### submitPlanetMessage\n\n▸ `Private` **submitPlanetMessage**(`locationId`, `type`, `body`): `Promise`<`void`\\>\n\nThe planet emoji feature is built on top of a more general 'Planet Message' system, which\nallows players to upload pieces of data called 'Message's to planets that they own. Emojis are\njust one type of message. Their implementation leaves the door open to more off-chain data.\n\n#### Parameters\n\n| Name         | Type                |\n| :----------- | :------------------ |\n| `locationId` | `LocationId`        |\n| `type`       | `PlanetMessageType` |\n| `body`       | `unknown`           |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### submitTransaction\n\n▸ **submitTransaction**<`T`\\>(`txIntent`, `overrides?`): `Promise`<`Transaction`<`T`\\>\\>\n\n#### Type parameters\n\n| Name | Type               |\n| :--- | :----------------- |\n| `T`  | extends `TxIntent` |\n\n#### Parameters\n\n| Name         | Type                 |\n| :----------- | :------------------- |\n| `txIntent`   | `T`                  |\n| `overrides?` | `TransactionRequest` |\n\n#### Returns\n\n`Promise`<`Transaction`<`T`\\>\\>\n\n---\n\n### submitVerifyTwitter\n\n▸ **submitVerifyTwitter**(`twitter`): `Promise`<`boolean`\\>\n\nOnce you have posted the verification tweet - complete the twitter-account-linking\nprocess by telling the Dark Forest webserver to look at that tweet.\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### testNotification\n\n▸ **testNotification**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### timeUntilNextBroadcastAvailable\n\n▸ **timeUntilNextBroadcastAvailable**(): `number`\n\nGets the amount of time (ms) until the next time the current player can broadcast a planet.\n\n#### Returns\n\n`number`\n\n---\n\n### transferOwnership\n\n▸ **transferOwnership**(`planetId`, `newOwner`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedPlanetTransfer`\\>\\>\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `planetId`     | `LocationId` | `undefined`   |\n| `newOwner`     | `EthAddress` | `undefined`   |\n| `bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedPlanetTransfer`\\>\\>\n\n---\n\n### updateDiagnostics\n\n▸ **updateDiagnostics**(`updateFn`): `void`\n\nUpdates the diagnostic info of the game using the supplied function. Ideally, each spot in the\ncodebase that would like to record a metric is able to update its specific metric in a\nconvenient manner.\n\n#### Parameters\n\n| Name       | Type                           |\n| :--------- | :----------------------------- |\n| `updateFn` | (`d`: `Diagnostics`) => `void` |\n\n#### Returns\n\n`void`\n\n---\n\n### upgrade\n\n▸ **upgrade**(`planetId`, `branch`, `_bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedUpgrade`\\>\\>\n\nSubmits a transaction to the blockchain to upgrade the given planet with the given\nupgrade branch. You must own the planet, and have enough silver on it to complete\nthe upgrade.\n\n#### Parameters\n\n| Name            | Type         | Default value |\n| :-------------- | :----------- | :------------ |\n| `planetId`      | `LocationId` | `undefined`   |\n| `branch`        | `number`     | `undefined`   |\n| `_bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedUpgrade`\\>\\>\n\n---\n\n### uploadDiagnostics\n\n▸ `Private` **uploadDiagnostics**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### verifyMessage\n\n▸ `Private` **verifyMessage**(`message`): `Promise`<`boolean`\\>\n\nChecks that a message signed by {@link GameManager#signMessage} was signed by the address that\nit claims it was signed by.\n\n#### Parameters\n\n| Name      | Type                        |\n| :-------- | :-------------------------- |\n| `message` | `SignedMessage`<`unknown`\\> |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### waitForPlanet\n\n▸ **waitForPlanet**<`T`\\>(`locationId`, `predicate`): `Promise`<`T`\\>\n\nListen for changes to a planet take action,\neg.\nwaitForPlanet(\"yourAsteroidId\", ({current}) => current.silverCap / current.silver > 90)\n.then(() => {\n// Send Silver to nearby planet\n})\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name         | Type                                                                                                                | Description                                                                                                 |\n| :----------- | :------------------------------------------------------------------------------------------------------------------ | :---------------------------------------------------------------------------------------------------------- |\n| `locationId` | `LocationId`                                                                                                        | A locationId to watch for updates                                                                           |\n| `predicate`  | (`__namedParameters`: [`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Planet`\\>) => `undefined` \\| `T` | a function that accepts a Diff and should return a truth-y value, value will be passed to promise.resolve() |\n\n#### Returns\n\n`Promise`<`T`\\>\n\na promise that will resolve with results returned from the predicate function\n\n---\n\n### withdrawArtifact\n\n▸ **withdrawArtifact**(`locationId`, `artifactId`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedWithdrawArtifact`\\>\\>\n\nWithdraws the artifact that is locked up on the given planet.\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `locationId`   | `LocationId` | `undefined`   |\n| `artifactId`   | `ArtifactId` | `undefined`   |\n| `bypassChecks` | `boolean`    | `true`        |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedWithdrawArtifact`\\>\\>\n\n---\n\n### withdrawSilver\n\n▸ **withdrawSilver**(`locationId`, `amount`, `bypassChecks?`): `Promise`<`Transaction`<`UnconfirmedWithdrawSilver`\\>\\>\n\n#### Parameters\n\n| Name           | Type         | Default value |\n| :------------- | :----------- | :------------ |\n| `locationId`   | `LocationId` | `undefined`   |\n| `amount`       | `number`     | `undefined`   |\n| `bypassChecks` | `boolean`    | `false`       |\n\n#### Returns\n\n`Promise`<`Transaction`<`UnconfirmedWithdrawSilver`\\>\\>\n\n---\n\n### create\n\n▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_GameLogic_GameManager.default.md)\\>\n\n#### Parameters\n\n| Name                                | Type                                                                                                            |\n| :---------------------------------- | :-------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                                                                        |\n| `__namedParameters.connection`      | `EthConnection`                                                                                                 |\n| `__namedParameters.contractAddress` | `EthAddress`                                                                                                    |\n| `__namedParameters.terminal`        | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n\n#### Returns\n\n`Promise`<[`default`](Backend_GameLogic_GameManager.default.md)\\>\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_GameObjects.GameObjects.md",
    "content": "# Class: GameObjects\n\n[Backend/GameLogic/GameObjects](../modules/Backend_GameLogic_GameObjects.md).GameObjects\n\nRepresentation of the objects which exist in the world.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_GameObjects.GameObjects.md#constructor)\n\n### Properties\n\n- [address](Backend_GameLogic_GameObjects.GameObjects.md#address)\n- [arrivals](Backend_GameLogic_GameObjects.GameObjects.md#arrivals)\n- [artifactUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#artifactupdated$)\n- [artifacts](Backend_GameLogic_GameObjects.GameObjects.md#artifacts)\n- [claimedLocations](Backend_GameLogic_GameObjects.GameObjects.md#claimedlocations)\n- [contractConstants](Backend_GameLogic_GameObjects.GameObjects.md#contractconstants)\n- [coordsToLocation](Backend_GameLogic_GameObjects.GameObjects.md#coordstolocation)\n- [layeredMap](Backend_GameLogic_GameObjects.GameObjects.md#layeredmap)\n- [myArtifacts](Backend_GameLogic_GameObjects.GameObjects.md#myartifacts)\n- [myArtifactsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myartifactsupdated$)\n- [myPlanets](Backend_GameLogic_GameObjects.GameObjects.md#myplanets)\n- [myPlanetsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myplanetsupdated$)\n- [planetArrivalIds](Backend_GameLogic_GameObjects.GameObjects.md#planetarrivalids)\n- [planetLocationMap](Backend_GameLogic_GameObjects.GameObjects.md#planetlocationmap)\n- [planetUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#planetupdated$)\n- [planets](Backend_GameLogic_GameObjects.GameObjects.md#planets)\n- [revealedLocations](Backend_GameLogic_GameObjects.GameObjects.md#revealedlocations)\n- [touchedPlanetIds](Backend_GameLogic_GameObjects.GameObjects.md#touchedplanetids)\n- [transactions](Backend_GameLogic_GameObjects.GameObjects.md#transactions)\n- [wormholes](Backend_GameLogic_GameObjects.GameObjects.md#wormholes)\n\n### Methods\n\n- [addPlanetLocation](Backend_GameLogic_GameObjects.GameObjects.md#addplanetlocation)\n- [clearOldArrivals](Backend_GameLogic_GameObjects.GameObjects.md#clearoldarrivals)\n- [clearUnconfirmedTxIntent](Backend_GameLogic_GameObjects.GameObjects.md#clearunconfirmedtxintent)\n- [defaultPlanetFromLocation](Backend_GameLogic_GameObjects.GameObjects.md#defaultplanetfromlocation)\n- [emitArrivalNotifications](Backend_GameLogic_GameObjects.GameObjects.md#emitarrivalnotifications)\n- [forceTick](Backend_GameLogic_GameObjects.GameObjects.md#forcetick)\n- [getAllOwnedPlanets](Backend_GameLogic_GameObjects.GameObjects.md#getallownedplanets)\n- [getAllPlanets](Backend_GameLogic_GameObjects.GameObjects.md#getallplanets)\n- [getAllPlanetsMap](Backend_GameLogic_GameObjects.GameObjects.md#getallplanetsmap)\n- [getAllVoyages](Backend_GameLogic_GameObjects.GameObjects.md#getallvoyages)\n- [getArrivalIdsForLocation](Backend_GameLogic_GameObjects.GameObjects.md#getarrivalidsforlocation)\n- [getArtifactById](Backend_GameLogic_GameObjects.GameObjects.md#getartifactbyid)\n- [getArtifactController](Backend_GameLogic_GameObjects.GameObjects.md#getartifactcontroller)\n- [getArtifactMap](Backend_GameLogic_GameObjects.GameObjects.md#getartifactmap)\n- [getArtifactsOnPlanetsOwnedBy](Backend_GameLogic_GameObjects.GameObjects.md#getartifactsonplanetsownedby)\n- [getArtifactsOwnedBy](Backend_GameLogic_GameObjects.GameObjects.md#getartifactsownedby)\n- [getBiome](Backend_GameLogic_GameObjects.GameObjects.md#getbiome)\n- [getClaimedLocations](Backend_GameLogic_GameObjects.GameObjects.md#getclaimedlocations)\n- [getEnergyCurveAtPercent](Backend_GameLogic_GameObjects.GameObjects.md#getenergycurveatpercent)\n- [getLocationOfPlanet](Backend_GameLogic_GameObjects.GameObjects.md#getlocationofplanet)\n- [getMyArtifactMap](Backend_GameLogic_GameObjects.GameObjects.md#getmyartifactmap)\n- [getMyPlanetMap](Backend_GameLogic_GameObjects.GameObjects.md#getmyplanetmap)\n- [getPlanetArtifacts](Backend_GameLogic_GameObjects.GameObjects.md#getplanetartifacts)\n- [getPlanetDetailLevel](Backend_GameLogic_GameObjects.GameObjects.md#getplanetdetaillevel)\n- [getPlanetLevel](Backend_GameLogic_GameObjects.GameObjects.md#getplanetlevel)\n- [getPlanetMap](Backend_GameLogic_GameObjects.GameObjects.md#getplanetmap)\n- [getPlanetWithCoords](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithcoords)\n- [getPlanetWithId](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithid)\n- [getPlanetWithLocation](Backend_GameLogic_GameObjects.GameObjects.md#getplanetwithlocation)\n- [getPlanetsInWorldCircle](Backend_GameLogic_GameObjects.GameObjects.md#getplanetsinworldcircle)\n- [getPlanetsInWorldRectangle](Backend_GameLogic_GameObjects.GameObjects.md#getplanetsinworldrectangle)\n- [getPlanetsWithIds](Backend_GameLogic_GameObjects.GameObjects.md#getplanetswithids)\n- [getRevealedLocations](Backend_GameLogic_GameObjects.GameObjects.md#getrevealedlocations)\n- [getSilverCurveAtPercent](Backend_GameLogic_GameObjects.GameObjects.md#getsilvercurveatpercent)\n- [getWormholes](Backend_GameLogic_GameObjects.GameObjects.md#getwormholes)\n- [isGettingSpaceships](Backend_GameLogic_GameObjects.GameObjects.md#isgettingspaceships)\n- [isPlanetInContract](Backend_GameLogic_GameObjects.GameObjects.md#isplanetincontract)\n- [markLocationRevealed](Backend_GameLogic_GameObjects.GameObjects.md#marklocationrevealed)\n- [onTxIntent](Backend_GameLogic_GameObjects.GameObjects.md#ontxintent)\n- [planetLevelFromHexPerlin](Backend_GameLogic_GameObjects.GameObjects.md#planetlevelfromhexperlin)\n- [planetTypeFromHexPerlin](Backend_GameLogic_GameObjects.GameObjects.md#planettypefromhexperlin)\n- [processArrivalsForPlanet](Backend_GameLogic_GameObjects.GameObjects.md#processarrivalsforplanet)\n- [removeArrival](Backend_GameLogic_GameObjects.GameObjects.md#removearrival)\n- [replaceArtifactFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceartifactfromcontractdata)\n- [replaceArtifactsFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceartifactsfromcontractdata)\n- [replacePlanetFromContractData](Backend_GameLogic_GameObjects.GameObjects.md#replaceplanetfromcontractdata)\n- [setArtifact](Backend_GameLogic_GameObjects.GameObjects.md#setartifact)\n- [setClaimedLocation](Backend_GameLogic_GameObjects.GameObjects.md#setclaimedlocation)\n- [setPlanet](Backend_GameLogic_GameObjects.GameObjects.md#setplanet)\n- [spaceTypeFromPerlin](Backend_GameLogic_GameObjects.GameObjects.md#spacetypefromperlin)\n- [updateArtifact](Backend_GameLogic_GameObjects.GameObjects.md#updateartifact)\n- [updatePlanet](Backend_GameLogic_GameObjects.GameObjects.md#updateplanet)\n- [updatePlanetIfStale](Backend_GameLogic_GameObjects.GameObjects.md#updateplanetifstale)\n- [getSilverNeeded](Backend_GameLogic_GameObjects.GameObjects.md#getsilverneeded)\n- [planetCanUpgrade](Backend_GameLogic_GameObjects.GameObjects.md#planetcanupgrade)\n\n## Constructors\n\n### constructor\n\n• **new GameObjects**(`address`, `touchedPlanets`, `allTouchedPlanetIds`, `revealedLocations`, `claimedLocations`, `artifacts`, `allChunks`, `unprocessedArrivals`, `unprocessedPlanetArrivalIds`, `contractConstants`, `worldRadius`)\n\n#### Parameters\n\n| Name                          | Type                                                                                             |\n| :---------------------------- | :----------------------------------------------------------------------------------------------- |\n| `address`                     | `undefined` \\| `EthAddress`                                                                      |\n| `touchedPlanets`              | `Map`<`LocationId`, `Planet`\\>                                                                   |\n| `allTouchedPlanetIds`         | `Set`<`LocationId`\\>                                                                             |\n| `revealedLocations`           | `Map`<`LocationId`, `RevealedLocation`\\>                                                         |\n| `claimedLocations`            | `Map`<`LocationId`, `ClaimedLocation`\\>                                                          |\n| `artifacts`                   | `Map`<`ArtifactId`, `Artifact`\\>                                                                 |\n| `allChunks`                   | `Iterable`<`Chunk`\\>                                                                             |\n| `unprocessedArrivals`         | `Map`<`VoyageId`, `QueuedArrival`\\>                                                              |\n| `unprocessedPlanetArrivalIds` | `Map`<`LocationId`, `VoyageId`[]\\>                                                               |\n| `contractConstants`           | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) |\n| `worldRadius`                 | `number`                                                                                         |\n\n## Properties\n\n### address\n\n• `Private` `Readonly` **address**: `undefined` \\| `EthAddress`\n\nThis address of the player that is currently logged in.\n\n**`todo`** move this, along with all other objects relating to the currently logged-on player into a\nnew field: {@code player: PlayerInfo}\n\n---\n\n### arrivals\n\n• `Private` `Readonly` **arrivals**: `Map`<`VoyageId`, `ArrivalWithTimer`\\>\n\nMap of arrivals to timers that fire when an arrival arrives, in case that handler needs to be\ncancelled for whatever reason.\n\n---\n\n### artifactUpdated$\n\n• `Readonly` **artifactUpdated$**: `Monomitter`<`ArtifactId`\\>\n\nEvent emitter which publishes whenever an artifact has been updated.\n\n---\n\n### artifacts\n\n• `Private` `Readonly` **artifacts**: `Map`<`ArtifactId`, `Artifact`\\>\n\nCached index of all known artifact data.\n\n**`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets)\n\n---\n\n### claimedLocations\n\n• `Private` `Readonly` **claimedLocations**: `Map`<`LocationId`, `ClaimedLocation`\\>\n\nMap from location ids to, if that location id has been claimed on-chain, the world coordinates\nof that location id, as well as some extra information regarding the circumstances of the\nrevealing of this planet.\n\n---\n\n### contractConstants\n\n• `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\nSome of the game's parameters are downloaded from the blockchain. This allows the client to be\nflexible, and connect to any compatible set of Dark Forest contracts, download the parameters,\nand join the game, taking into account the unique configuration of those specific Dark Forest\ncontracts.\n\n---\n\n### coordsToLocation\n\n• `Private` `Readonly` **coordsToLocation**: `Map`<`CoordsString`, `WorldLocation`\\>\n\nMap from a stringified representation of an x-y coordinate to an object that contains some more\ninformation about the world at that location.\n\n---\n\n### layeredMap\n\n• `Private` `Readonly` **layeredMap**: [`LayeredMap`](Backend_GameLogic_LayeredMap.LayeredMap.md)\n\nThis is a data structure that allows us to efficiently calculate which planets are visible on\nthe player's screen given the viewport's position and size.\n\n---\n\n### myArtifacts\n\n• `Private` `Readonly` **myArtifacts**: `Map`<`ArtifactId`, `Artifact`\\>\n\nCached index of artifacts owned by the player.\n\n**`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets)\n\n---\n\n### myArtifactsUpdated$\n\n• `Readonly` **myArtifactsUpdated$**: `Monomitter`<`Map`<`ArtifactId`, `Artifact`\\>\\>\n\nWhenever one of the player's artifacts are updated, this event emitter publishes. See\n[GameObjects.myPlanetsUpdated$](Backend_GameLogic_GameObjects.GameObjects.md#myplanetsupdated$) for more info.\n\n---\n\n### myPlanets\n\n• `Private` `Readonly` **myPlanets**: `Map`<`LocationId`, `Planet`\\>\n\nCached index of planets owned by the player.\n\n**`see`** The same warning applys as the one on [GameObjects.planets](Backend_GameLogic_GameObjects.GameObjects.md#planets)\n\n---\n\n### myPlanetsUpdated$\n\n• `Readonly` **myPlanetsUpdated$**: `Monomitter`<`Map`<`LocationId`, `Planet`\\>\\>\n\nWhenever a planet is updated, we publish to this event with a reference to a map from location\nid to planet. We need to rethink this event emitter because it currently publishes every time\nthat any planet is updated, and if a lot of them are updated at once (which i think is the case\nonce every two minutes) then this event emitter will publish a shitton of events.\nTODO: rethink this\n\n---\n\n### planetArrivalIds\n\n• `Private` `Readonly` **planetArrivalIds**: `Map`<`LocationId`, `VoyageId`[]\\>\n\nMap from a location id (think of it as the unique id of each planet) to all the ids of the\nvoyages that are arriving on that planet. These include both the player's own voyages, and also\nany potential invader's voyages.\n\n---\n\n### planetLocationMap\n\n• `Private` `Readonly` **planetLocationMap**: `Map`<`LocationId`, `WorldLocation`\\>\n\nMap from location id (unique id of each planet) to some information about the location at which\nthis planet is located, if this client happens to know the coordinates of this planet.\n\n---\n\n### planetUpdated$\n\n• `Readonly` **planetUpdated$**: `Monomitter`<`LocationId`\\>\n\nEvent emitter which publishes whenever a planet is updated.\n\n---\n\n### planets\n\n• `Private` `Readonly` **planets**: `Map`<`LocationId`, `Planet`\\>\n\nCached index of all known planet data.\n\nWarning!\n\nThis should NEVER be set to directly! Any time you want to update a planet, you must call the\n{@link GameObjects#setPlanet()} function. Following this rule enables us to reliably notify\nother parts of the client when a particular object has been updated. TODO: what is the best way\nto do this?\n\n**`todo`** extract the pattern we're using for the field tuples\n\n- {planets, myPlanets, myPlanetsUpdated, planetUpdated$}\n- {artifacts, myArtifacts, myArtifactsUpdated, artifactUpdated$}\n\ninto some sort of class.\n\n---\n\n### revealedLocations\n\n• `Private` `Readonly` **revealedLocations**: `Map`<`LocationId`, `RevealedLocation`\\>\n\nMap from location ids to, if that location id has been revealed on-chain, the world coordinates\nof that location id, as well as some extra information regarding the circumstances of the\nrevealing of this planet.\n\n---\n\n### touchedPlanetIds\n\n• `Private` `Readonly` **touchedPlanetIds**: `Set`<`LocationId`\\>\n\nSet of all planet ids that we know have been interacted-with on-chain.\n\n---\n\n### transactions\n\n• `Readonly` **transactions**: `TransactionCollection`\n\nTransactions that are currently in flight.\n\n---\n\n### wormholes\n\n• `Private` `Readonly` **wormholes**: `Map`<`ArtifactId`, `Wormhole`\\>\n\nMap from artifact ids to wormholes.\n\n## Methods\n\n### addPlanetLocation\n\n▸ **addPlanetLocation**(`planetLocation`): `void`\n\nCalled when we load chunk data into memory (on startup), when we're loading all revealed locations (on startup),\nwhen miner has mined a new chunk while exploring, and when a planet's location is revealed onchain during the course of play\nAdds a WorldLocation to the planetLocationMap, making it known to the player locally\nSets an unsynced default planet in the PlanetMap this.planets\nIMPORTANT: This is the only way a LocatablePlanet gets constructed\nIMPORTANT: Idempotent\n\n#### Parameters\n\n| Name             | Type            |\n| :--------------- | :-------------- |\n| `planetLocation` | `WorldLocation` |\n\n#### Returns\n\n`void`\n\n---\n\n### clearOldArrivals\n\n▸ `Private` **clearOldArrivals**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### clearUnconfirmedTxIntent\n\n▸ **clearUnconfirmedTxIntent**(`tx`): `void`\n\nWhenever a transaction that the user initiated either succeeds or fails, we need to clear the\nfact that it was in progress from the event's corresponding entities. For example, whenever a\ntransaction that sends a voyage from one planet to another either succeeds or fails, we need to\nremove the dashed line that connected them.\n\nMaking sure that we never miss something here is very tedious.\n\n**`todo`** Make this less tedious.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### defaultPlanetFromLocation\n\n▸ `Private` **defaultPlanetFromLocation**(`location`): `LocatablePlanet`\n\nreturns the data for an unowned, untouched planet at location\nmost planets in the game are untouched and not stored in the contract,\nso we need to generate their data optimistically in the client\n\n#### Parameters\n\n| Name       | Type            |\n| :--------- | :-------------- |\n| `location` | `WorldLocation` |\n\n#### Returns\n\n`LocatablePlanet`\n\n---\n\n### emitArrivalNotifications\n\n▸ `Private` **emitArrivalNotifications**(`__namedParameters`): `void`\n\nEmit notifications based on a planet's state change\n\n#### Parameters\n\n| Name                | Type                                                                       |\n| :------------------ | :------------------------------------------------------------------------- |\n| `__namedParameters` | [`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### forceTick\n\n▸ **forceTick**(`locationId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### getAllOwnedPlanets\n\n▸ **getAllOwnedPlanets**(): `Planet`[]\n\nReturns all the planets in the game which this client is aware of that have an owner, as a map\nfrom their id to the planet\n\n**`tutorial`** For plugin developers!\n\n**`see`** Warning in {@link GameObjects.getAllPlanets()}\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getAllPlanets\n\n▸ **getAllPlanets**(): `Iterable`<`Planet`\\>\n\nReturns all planets in the game.\n\nWarning! Simply iterating over this is not performant, and is meant for scripting.\n\n**`tutorial`** For plugin developers!\n\n#### Returns\n\n`Iterable`<`Planet`\\>\n\n---\n\n### getAllPlanetsMap\n\n▸ **getAllPlanetsMap**(): `Map`<`LocationId`, `Planet`\\>\n\nReturns all planets in the game, as a map from their location id to the planet.\n\n**`tutorial`** For plugin developers!\n\n**`see`** Warning in {@link GameObjects.getAllPlanets()}\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getAllVoyages\n\n▸ **getAllVoyages**(): `QueuedArrival`[]\n\nReturns all voyages that are scheduled to arrive at some point in the future.\n\n**`tutorial`** For plugin developers!\n\n**`see`** Warning in {@link GameObjects.getAllPlanets()}\n\n#### Returns\n\n`QueuedArrival`[]\n\n---\n\n### getArrivalIdsForLocation\n\n▸ **getArrivalIdsForLocation**(`location`): `undefined` \\| `VoyageId`[]\n\nGet all of the incoming voyages for a given location.\n\n#### Parameters\n\n| Name       | Type                        |\n| :--------- | :-------------------------- |\n| `location` | `undefined` \\| `LocationId` |\n\n#### Returns\n\n`undefined` \\| `VoyageId`[]\n\n---\n\n### getArtifactById\n\n▸ **getArtifactById**(`artifactId?`): `undefined` \\| `Artifact`\n\n#### Parameters\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `artifactId?` | `ArtifactId` |\n\n#### Returns\n\n`undefined` \\| `Artifact`\n\n---\n\n### getArtifactController\n\n▸ **getArtifactController**(`artifactId`): `undefined` \\| `EthAddress`\n\nReturns the EthAddress of the player who can control the owner:\nif the artifact is on a planet, this is the owner of the planet\nif the artifact is on a voyage, this is the initiator of the voyage\nif the artifact is not on either, then it is the owner of the artifact NFT\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### getArtifactMap\n\n▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getArtifactsOnPlanetsOwnedBy\n\n▸ **getArtifactsOnPlanetsOwnedBy**(`addr`): `Artifact`[]\n\n#### Parameters\n\n| Name   | Type         |\n| :----- | :----------- |\n| `addr` | `EthAddress` |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getArtifactsOwnedBy\n\n▸ **getArtifactsOwnedBy**(`addr`): `Artifact`[]\n\n#### Parameters\n\n| Name   | Type         |\n| :----- | :----------- |\n| `addr` | `EthAddress` |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getBiome\n\n▸ `Private` **getBiome**(`loc`): `Biome`\n\n#### Parameters\n\n| Name  | Type            |\n| :---- | :-------------- |\n| `loc` | `WorldLocation` |\n\n#### Returns\n\n`Biome`\n\n---\n\n### getClaimedLocations\n\n▸ **getClaimedLocations**(): `Map`<`LocationId`, `ClaimedLocation`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `ClaimedLocation`\\>\n\n---\n\n### getEnergyCurveAtPercent\n\n▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number`\n\nreturns timestamp (seconds) that planet will reach percent% of energycap\ntime may be in the past\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getLocationOfPlanet\n\n▸ **getLocationOfPlanet**(`planetId`): `undefined` \\| `WorldLocation`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `WorldLocation`\n\n---\n\n### getMyArtifactMap\n\n▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getMyPlanetMap\n\n▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getPlanetArtifacts\n\n▸ **getPlanetArtifacts**(`planetId`): `Artifact`[]\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getPlanetDetailLevel\n\n▸ **getPlanetDetailLevel**(`planetId`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getPlanetLevel\n\n▸ **getPlanetLevel**(`planetId`): `undefined` \\| `PlanetLevel`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `PlanetLevel`\n\n---\n\n### getPlanetMap\n\n▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getPlanetWithCoords\n\n▸ **getPlanetWithCoords**(`coords`): `undefined` \\| `LocatablePlanet`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`undefined` \\| `LocatablePlanet`\n\n---\n\n### getPlanetWithId\n\n▸ **getPlanetWithId**(`planetId`, `updateIfStale?`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name            | Type         | Default value |\n| :-------------- | :----------- | :------------ |\n| `planetId`      | `LocationId` | `undefined`   |\n| `updateIfStale` | `boolean`    | `true`        |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPlanetWithLocation\n\n▸ **getPlanetWithLocation**(`location`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name       | Type                           |\n| :--------- | :----------------------------- |\n| `location` | `undefined` \\| `WorldLocation` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPlanetsInWorldCircle\n\n▸ **getPlanetsInWorldCircle**(`coords`, `radius`): `LocatablePlanet`[]\n\nGets all the planets that are within {@code radius} world units from the given coordinate. Fast\nbecause it uses [LayeredMap](Backend_GameLogic_LayeredMap.LayeredMap.md).\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n| `radius` | `number`      |\n\n#### Returns\n\n`LocatablePlanet`[]\n\n---\n\n### getPlanetsInWorldRectangle\n\n▸ **getPlanetsInWorldRectangle**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `levels`, `planetLevelToRadii`, `updateIfStale?`): `LocatablePlanet`[]\n\nGets the ids of all the planets that are both within the given bounding box (defined by its\nbottom left coordinate, width, and height) in the world and of a level that was passed in via\nthe `planetLevels` parameter. Fast because it uses [LayeredMap](Backend_GameLogic_LayeredMap.LayeredMap.md).\n\n#### Parameters\n\n| Name                 | Type                      | Default value |\n| :------------------- | :------------------------ | :------------ |\n| `worldX`             | `number`                  | `undefined`   |\n| `worldY`             | `number`                  | `undefined`   |\n| `worldWidth`         | `number`                  | `undefined`   |\n| `worldHeight`        | `number`                  | `undefined`   |\n| `levels`             | `number`[]                | `undefined`   |\n| `planetLevelToRadii` | `Map`<`number`, `Radii`\\> | `undefined`   |\n| `updateIfStale`      | `boolean`                 | `true`        |\n\n#### Returns\n\n`LocatablePlanet`[]\n\n---\n\n### getPlanetsWithIds\n\n▸ **getPlanetsWithIds**(`locationIds`, `updateIfStale?`): `Planet`[]\n\nGets all the planets with the given ids, giltering out the ones that we don't have.\n\n#### Parameters\n\n| Name            | Type           | Default value |\n| :-------------- | :------------- | :------------ |\n| `locationIds`   | `LocationId`[] | `undefined`   |\n| `updateIfStale` | `boolean`      | `true`        |\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getRevealedLocations\n\n▸ **getRevealedLocations**(): `Map`<`LocationId`, `RevealedLocation`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `RevealedLocation`\\>\n\n---\n\n### getSilverCurveAtPercent\n\n▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \\| `number`\n\nreturns timestamp (seconds) that planet will reach percent% of silcap if\ndoesn't produce silver, returns undefined if already over percent% of silcap,\nreturns undefined\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getWormholes\n\n▸ **getWormholes**(): `Iterable`<`Wormhole`\\>\n\n#### Returns\n\n`Iterable`<`Wormhole`\\>\n\n---\n\n### isGettingSpaceships\n\n▸ **isGettingSpaceships**(): `boolean`\n\nWhether or not we're already asking the game to give us spaceships.\n\n#### Returns\n\n`boolean`\n\n---\n\n### isPlanetInContract\n\n▸ **isPlanetInContract**(`planetId`): `boolean`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### markLocationRevealed\n\n▸ **markLocationRevealed**(`revealedLocation`): `void`\n\n#### Parameters\n\n| Name               | Type               |\n| :----------------- | :----------------- |\n| `revealedLocation` | `RevealedLocation` |\n\n#### Returns\n\n`void`\n\n---\n\n### onTxIntent\n\n▸ **onTxIntent**(`tx`): `void`\n\nWe call this function whenever the user requests that we send a transaction to the blockchain\nwith their localstorage wallet. You can think of it as one of the hubs which connects\n`GameObjects` to the rest of the world.\n\nInside this function, we update the relevant internal game objects to reflect that the user has\nrequested a particular action. Additionally, we publish the appropriate events to the relevant\n{@link Monomitter} instances that are stored in this class.\n\nIn the case of something like prospecting for an artifact, this allows us to display a spinner\ntext which says \"Prospecting...\"\n\nIn the case of the user sending energy from one planet to another planet, this allows us to\ndisplay a dashed line between the two planets in their new voyage.\n\nWhenever we update an entity, we must do it via that entity's type's corresponding\n`set<EntityType>` function, in order for us to publish these events.\n\n**`todo:`** this entire function could be automated by implementing a new interface called\n{@code TxFilter}.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### planetLevelFromHexPerlin\n\n▸ **planetLevelFromHexPerlin**(`hex`, `perlin`): `PlanetLevel`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `hex`    | `LocationId` |\n| `perlin` | `number`     |\n\n#### Returns\n\n`PlanetLevel`\n\n---\n\n### planetTypeFromHexPerlin\n\n▸ **planetTypeFromHexPerlin**(`hex`, `perlin`): `PlanetType`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `hex`    | `LocationId` |\n| `perlin` | `number`     |\n\n#### Returns\n\n`PlanetType`\n\n---\n\n### processArrivalsForPlanet\n\n▸ `Private` **processArrivalsForPlanet**(`planetId`, `arrivals`): `ArrivalWithTimer`[]\n\n#### Parameters\n\n| Name       | Type              |\n| :--------- | :---------------- |\n| `planetId` | `LocationId`      |\n| `arrivals` | `QueuedArrival`[] |\n\n#### Returns\n\n`ArrivalWithTimer`[]\n\n---\n\n### removeArrival\n\n▸ `Private` **removeArrival**(`planetId`, `arrivalId`): `void`\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId`  | `LocationId` |\n| `arrivalId` | `VoyageId`   |\n\n#### Returns\n\n`void`\n\n---\n\n### replaceArtifactFromContractData\n\n▸ **replaceArtifactFromContractData**(`artifact`): `void`\n\nreceived some artifact data from the contract. update our stores\n\n#### Parameters\n\n| Name       | Type       |\n| :--------- | :--------- |\n| `artifact` | `Artifact` |\n\n#### Returns\n\n`void`\n\n---\n\n### replaceArtifactsFromContractData\n\n▸ **replaceArtifactsFromContractData**(`artifacts`): `void`\n\n#### Parameters\n\n| Name        | Type                    |\n| :---------- | :---------------------- |\n| `artifacts` | `Iterable`<`Artifact`\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### replacePlanetFromContractData\n\n▸ **replacePlanetFromContractData**(`planet`, `updatedArrivals?`, `updatedArtifactsOnPlanet?`, `revealedLocation?`, `claimerEthAddress?`): `void`\n\nreceived some planet data from the contract. update our stores\n\n#### Parameters\n\n| Name                        | Type               |\n| :-------------------------- | :----------------- |\n| `planet`                    | `Planet`           |\n| `updatedArrivals?`          | `QueuedArrival`[]  |\n| `updatedArtifactsOnPlanet?` | `ArtifactId`[]     |\n| `revealedLocation?`         | `RevealedLocation` |\n| `claimerEthAddress?`        | `EthAddress`       |\n\n#### Returns\n\n`void`\n\n---\n\n### setArtifact\n\n▸ `Private` **setArtifact**(`artifact`): `void`\n\nSet an artifact into our cached store. Should ALWAYS call this when setting an artifact.\n`this.artifacts` and `this.myArtifacts` should NEVER be accessed directly!\nThis function also handles managing artifact update messages and indexing the map of owned artifacts.\n\n#### Parameters\n\n| Name       | Type       | Description         |\n| :--------- | :--------- | :------------------ |\n| `artifact` | `Artifact` | the artifact to set |\n\n#### Returns\n\n`void`\n\n---\n\n### setClaimedLocation\n\n▸ **setClaimedLocation**(`claimedLocation`): `void`\n\n#### Parameters\n\n| Name              | Type              |\n| :---------------- | :---------------- |\n| `claimedLocation` | `ClaimedLocation` |\n\n#### Returns\n\n`void`\n\n---\n\n### setPlanet\n\n▸ `Private` **setPlanet**(`planet`): `void`\n\nSet a planet into our cached store. Should ALWAYS call this when setting a planet.\n`this.planets` and `this.myPlanets` should NEVER be accessed directly!\nThis function also handles managing planet update messages and indexing the map of owned planets.\n\n#### Parameters\n\n| Name     | Type     | Description       |\n| :------- | :------- | :---------------- |\n| `planet` | `Planet` | the planet to set |\n\n#### Returns\n\n`void`\n\n---\n\n### spaceTypeFromPerlin\n\n▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `perlin` | `number` |\n\n#### Returns\n\n`SpaceType`\n\n---\n\n### updateArtifact\n\n▸ **updateArtifact**(`id`, `updateFn`): `void`\n\nGiven a planet id, update the state of the given planet by calling the given update function.\nIf the planet was updated, then also publish the appropriate event.\n\n#### Parameters\n\n| Name       | Type                        |\n| :--------- | :-------------------------- |\n| `id`       | `undefined` \\| `ArtifactId` |\n| `updateFn` | (`p`: `Artifact`) => `void` |\n\n#### Returns\n\n`void`\n\n---\n\n### updatePlanet\n\n▸ **updatePlanet**(`id`, `updateFn`): `void`\n\nGiven a planet id, update the state of the given planet by calling the given update function.\nIf the planet was updated, then also publish the appropriate event.\n\n#### Parameters\n\n| Name       | Type                      |\n| :--------- | :------------------------ |\n| `id`       | `LocationId`              |\n| `updateFn` | (`p`: `Planet`) => `void` |\n\n#### Returns\n\n`void`\n\n---\n\n### updatePlanetIfStale\n\n▸ `Private` **updatePlanetIfStale**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### getSilverNeeded\n\n▸ `Static` **getSilverNeeded**(`planet`): `number`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`number`\n\n---\n\n### planetCanUpgrade\n\n▸ `Static` **planetCanUpgrade**(`planet`): `boolean`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`boolean`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_GameUIManager.default.md",
    "content": "# Class: default\n\n[Backend/GameLogic/GameUIManager](../modules/Backend_GameLogic_GameUIManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_GameUIManager.default.md#constructor)\n\n### Properties\n\n- [abandoning](Backend_GameLogic_GameUIManager.default.md#abandoning)\n- [artifactSending](Backend_GameLogic_GameUIManager.default.md#artifactsending)\n- [extraMinerLocations](Backend_GameLogic_GameUIManager.default.md#extraminerlocations)\n- [forcesSending](Backend_GameLogic_GameUIManager.default.md#forcessending)\n- [gameManager](Backend_GameLogic_GameUIManager.default.md#gamemanager)\n- [hoverArtifact$](Backend_GameLogic_GameUIManager.default.md#hoverartifact$)\n- [hoverArtifactId$](Backend_GameLogic_GameUIManager.default.md#hoverartifactid$)\n- [hoverPlanet$](Backend_GameLogic_GameUIManager.default.md#hoverplanet$)\n- [hoverPlanetId$](Backend_GameLogic_GameUIManager.default.md#hoverplanetid$)\n- [isAbandoning$](Backend_GameLogic_GameUIManager.default.md#isabandoning$)\n- [isChoosingTargetPlanet](Backend_GameLogic_GameUIManager.default.md#ischoosingtargetplanet)\n- [isSending](Backend_GameLogic_GameUIManager.default.md#issending)\n- [isSending$](Backend_GameLogic_GameUIManager.default.md#issending$)\n- [minerLocation](Backend_GameLogic_GameUIManager.default.md#minerlocation)\n- [modalManager](Backend_GameLogic_GameUIManager.default.md#modalmanager)\n- [mouseDownOverCoords](Backend_GameLogic_GameUIManager.default.md#mousedownovercoords)\n- [mouseDownOverPlanet](Backend_GameLogic_GameUIManager.default.md#mousedownoverplanet)\n- [mouseHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#mousehoveringovercoords)\n- [mouseHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#mousehoveringoverplanet)\n- [myArtifacts$](Backend_GameLogic_GameUIManager.default.md#myartifacts$)\n- [onChooseTargetPlanet](Backend_GameLogic_GameUIManager.default.md#onchoosetargetplanet)\n- [overlayContainer](Backend_GameLogic_GameUIManager.default.md#overlaycontainer)\n- [planetHoveringInRenderer](Backend_GameLogic_GameUIManager.default.md#planethoveringinrenderer)\n- [plugins](Backend_GameLogic_GameUIManager.default.md#plugins)\n- [previousSelectedPlanetId](Backend_GameLogic_GameUIManager.default.md#previousselectedplanetid)\n- [radiusMap](Backend_GameLogic_GameUIManager.default.md#radiusmap)\n- [selectedCoords](Backend_GameLogic_GameUIManager.default.md#selectedcoords)\n- [selectedPlanetId](Backend_GameLogic_GameUIManager.default.md#selectedplanetid)\n- [selectedPlanetId$](Backend_GameLogic_GameUIManager.default.md#selectedplanetid$)\n- [sendingCoords](Backend_GameLogic_GameUIManager.default.md#sendingcoords)\n- [sendingPlanet](Backend_GameLogic_GameUIManager.default.md#sendingplanet)\n- [silverSending](Backend_GameLogic_GameUIManager.default.md#silversending)\n- [terminal](Backend_GameLogic_GameUIManager.default.md#terminal)\n- [viewportEntities](Backend_GameLogic_GameUIManager.default.md#viewportentities)\n\n### Accessors\n\n- [captureZonesEnabled](Backend_GameLogic_GameUIManager.default.md#capturezonesenabled)\n- [contractConstants](Backend_GameLogic_GameUIManager.default.md#contractconstants)\n\n### Methods\n\n- [activateArtifact](Backend_GameLogic_GameUIManager.default.md#activateartifact)\n- [addAccount](Backend_GameLogic_GameUIManager.default.md#addaccount)\n- [addNewChunk](Backend_GameLogic_GameUIManager.default.md#addnewchunk)\n- [bulkAddNewChunks](Backend_GameLogic_GameUIManager.default.md#bulkaddnewchunks)\n- [buyHat](Backend_GameLogic_GameUIManager.default.md#buyhat)\n- [centerCoords](Backend_GameLogic_GameUIManager.default.md#centercoords)\n- [centerLocationId](Backend_GameLogic_GameUIManager.default.md#centerlocationid)\n- [centerPlanet](Backend_GameLogic_GameUIManager.default.md#centerplanet)\n- [deactivateArtifact](Backend_GameLogic_GameUIManager.default.md#deactivateartifact)\n- [depositArtifact](Backend_GameLogic_GameUIManager.default.md#depositartifact)\n- [destroy](Backend_GameLogic_GameUIManager.default.md#destroy)\n- [disableCustomRenderer](Backend_GameLogic_GameUIManager.default.md#disablecustomrenderer)\n- [disconnectTwitter](Backend_GameLogic_GameUIManager.default.md#disconnecttwitter)\n- [discoverBiome](Backend_GameLogic_GameUIManager.default.md#discoverbiome)\n- [drawAllRunningPlugins](Backend_GameLogic_GameUIManager.default.md#drawallrunningplugins)\n- [findArtifact](Backend_GameLogic_GameUIManager.default.md#findartifact)\n- [generateVerificationTweet](Backend_GameLogic_GameUIManager.default.md#generateverificationtweet)\n- [get2dRenderer](Backend_GameLogic_GameUIManager.default.md#get2drenderer)\n- [getAbandonRangeChangePercent](Backend_GameLogic_GameUIManager.default.md#getabandonrangechangepercent)\n- [getAbandonSpeedChangePercent](Backend_GameLogic_GameUIManager.default.md#getabandonspeedchangepercent)\n- [getAccount](Backend_GameLogic_GameUIManager.default.md#getaccount)\n- [getAllMinerLocations](Backend_GameLogic_GameUIManager.default.md#getallminerlocations)\n- [getAllOwnedPlanets](Backend_GameLogic_GameUIManager.default.md#getallownedplanets)\n- [getAllPlayers](Backend_GameLogic_GameUIManager.default.md#getallplayers)\n- [getAllVoyages](Backend_GameLogic_GameUIManager.default.md#getallvoyages)\n- [getArtifactMap](Backend_GameLogic_GameUIManager.default.md#getartifactmap)\n- [getArtifactPlanet](Backend_GameLogic_GameUIManager.default.md#getartifactplanet)\n- [getArtifactPointValues](Backend_GameLogic_GameUIManager.default.md#getartifactpointvalues)\n- [getArtifactSending](Backend_GameLogic_GameUIManager.default.md#getartifactsending)\n- [getArtifactUpdated$](Backend_GameLogic_GameUIManager.default.md#getartifactupdated$)\n- [getArtifactWithId](Backend_GameLogic_GameUIManager.default.md#getartifactwithid)\n- [getArtifactsWithIds](Backend_GameLogic_GameUIManager.default.md#getartifactswithids)\n- [getBiomeKey](Backend_GameLogic_GameUIManager.default.md#getbiomekey)\n- [getBiomePerlin](Backend_GameLogic_GameUIManager.default.md#getbiomeperlin)\n- [getBooleanSetting](Backend_GameLogic_GameUIManager.default.md#getbooleansetting)\n- [getCaptureZoneGenerator](Backend_GameLogic_GameUIManager.default.md#getcapturezonegenerator)\n- [getCaptureZonePointValues](Backend_GameLogic_GameUIManager.default.md#getcapturezonepointvalues)\n- [getCaptureZones](Backend_GameLogic_GameUIManager.default.md#getcapturezones)\n- [getChunk](Backend_GameLogic_GameUIManager.default.md#getchunk)\n- [getContractAddress](Backend_GameLogic_GameUIManager.default.md#getcontractaddress)\n- [getDefaultSpaceJunkForPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getdefaultspacejunkforplanetlevel)\n- [getDiagnostics](Backend_GameLogic_GameUIManager.default.md#getdiagnostics)\n- [getDiscoverBiomeName](Backend_GameLogic_GameUIManager.default.md#getdiscoverbiomename)\n- [getDistCoords](Backend_GameLogic_GameUIManager.default.md#getdistcoords)\n- [getEndTimeSeconds](Backend_GameLogic_GameUIManager.default.md#getendtimeseconds)\n- [getEnergyArrivingForMove](Backend_GameLogic_GameUIManager.default.md#getenergyarrivingformove)\n- [getEnergyCurveAtPercent](Backend_GameLogic_GameUIManager.default.md#getenergycurveatpercent)\n- [getEnergyOfPlayer](Backend_GameLogic_GameUIManager.default.md#getenergyofplayer)\n- [getEthConnection](Backend_GameLogic_GameUIManager.default.md#getethconnection)\n- [getExploredChunks](Backend_GameLogic_GameUIManager.default.md#getexploredchunks)\n- [getForcesSending](Backend_GameLogic_GameUIManager.default.md#getforcessending)\n- [getGameManager](Backend_GameLogic_GameUIManager.default.md#getgamemanager)\n- [getGameObjects](Backend_GameLogic_GameUIManager.default.md#getgameobjects)\n- [getGlManager](Backend_GameLogic_GameUIManager.default.md#getglmanager)\n- [getHashConfig](Backend_GameLogic_GameUIManager.default.md#gethashconfig)\n- [getHashesPerSec](Backend_GameLogic_GameUIManager.default.md#gethashespersec)\n- [getHomeCoords](Backend_GameLogic_GameUIManager.default.md#gethomecoords)\n- [getHomeHash](Backend_GameLogic_GameUIManager.default.md#gethomehash)\n- [getHomePlanet](Backend_GameLogic_GameUIManager.default.md#gethomeplanet)\n- [getHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#gethoveringovercoords)\n- [getHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#gethoveringoverplanet)\n- [getIsChoosingTargetPlanet](Backend_GameLogic_GameUIManager.default.md#getischoosingtargetplanet)\n- [getIsHighPerfMode](Backend_GameLogic_GameUIManager.default.md#getishighperfmode)\n- [getLocationOfPlanet](Backend_GameLogic_GameUIManager.default.md#getlocationofplanet)\n- [getLocationsAndChunks](Backend_GameLogic_GameUIManager.default.md#getlocationsandchunks)\n- [getMinerLocation](Backend_GameLogic_GameUIManager.default.md#getminerlocation)\n- [getMiningPattern](Backend_GameLogic_GameUIManager.default.md#getminingpattern)\n- [getModalManager](Backend_GameLogic_GameUIManager.default.md#getmodalmanager)\n- [getMouseDownCoords](Backend_GameLogic_GameUIManager.default.md#getmousedowncoords)\n- [getMouseDownPlanet](Backend_GameLogic_GameUIManager.default.md#getmousedownplanet)\n- [getMyArtifactMap](Backend_GameLogic_GameUIManager.default.md#getmyartifactmap)\n- [getMyArtifacts](Backend_GameLogic_GameUIManager.default.md#getmyartifacts)\n- [getMyArtifactsNotOnPlanet](Backend_GameLogic_GameUIManager.default.md#getmyartifactsnotonplanet)\n- [getMyBalance](Backend_GameLogic_GameUIManager.default.md#getmybalance)\n- [getMyBalance$](Backend_GameLogic_GameUIManager.default.md#getmybalance$)\n- [getMyBalanceBn](Backend_GameLogic_GameUIManager.default.md#getmybalancebn)\n- [getMyPlanetMap](Backend_GameLogic_GameUIManager.default.md#getmyplanetmap)\n- [getMyScore](Backend_GameLogic_GameUIManager.default.md#getmyscore)\n- [getNextBroadcastAvailableTimestamp](Backend_GameLogic_GameUIManager.default.md#getnextbroadcastavailabletimestamp)\n- [getOverlayContainer](Backend_GameLogic_GameUIManager.default.md#getoverlaycontainer)\n- [getPaused](Backend_GameLogic_GameUIManager.default.md#getpaused)\n- [getPaused$](Backend_GameLogic_GameUIManager.default.md#getpaused$)\n- [getPerlinConfig](Backend_GameLogic_GameUIManager.default.md#getperlinconfig)\n- [getPerlinThresholds](Backend_GameLogic_GameUIManager.default.md#getperlinthresholds)\n- [getPlanetHoveringInRenderer](Backend_GameLogic_GameUIManager.default.md#getplanethoveringinrenderer)\n- [getPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getplanetlevel)\n- [getPlanetMap](Backend_GameLogic_GameUIManager.default.md#getplanetmap)\n- [getPlanetWithCoords](Backend_GameLogic_GameUIManager.default.md#getplanetwithcoords)\n- [getPlanetWithId](Backend_GameLogic_GameUIManager.default.md#getplanetwithid)\n- [getPlanetsInViewport](Backend_GameLogic_GameUIManager.default.md#getplanetsinviewport)\n- [getPlayer](Backend_GameLogic_GameUIManager.default.md#getplayer)\n- [getPlayerScore](Backend_GameLogic_GameUIManager.default.md#getplayerscore)\n- [getPluginManager](Backend_GameLogic_GameUIManager.default.md#getpluginmanager)\n- [getPreviousSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#getpreviousselectedplanet)\n- [getPrivateKey](Backend_GameLogic_GameUIManager.default.md#getprivatekey)\n- [getRadiusOfPlanetLevel](Backend_GameLogic_GameUIManager.default.md#getradiusofplanetlevel)\n- [getRangeBuff](Backend_GameLogic_GameUIManager.default.md#getrangebuff)\n- [getRenderer](Backend_GameLogic_GameUIManager.default.md#getrenderer)\n- [getSelectedCoords](Backend_GameLogic_GameUIManager.default.md#getselectedcoords)\n- [getSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#getselectedplanet)\n- [getSilverCurveAtPercent](Backend_GameLogic_GameUIManager.default.md#getsilvercurveatpercent)\n- [getSilverOfPlayer](Backend_GameLogic_GameUIManager.default.md#getsilverofplayer)\n- [getSilverScoreValue](Backend_GameLogic_GameUIManager.default.md#getsilverscorevalue)\n- [getSilverSending](Backend_GameLogic_GameUIManager.default.md#getsilversending)\n- [getSpaceJunkEnabled](Backend_GameLogic_GameUIManager.default.md#getspacejunkenabled)\n- [getSpaceTypePerlin](Backend_GameLogic_GameUIManager.default.md#getspacetypeperlin)\n- [getSpeedBuff](Backend_GameLogic_GameUIManager.default.md#getspeedbuff)\n- [getStringSetting](Backend_GameLogic_GameUIManager.default.md#getstringsetting)\n- [getTerminal](Backend_GameLogic_GameUIManager.default.md#getterminal)\n- [getTwitter](Backend_GameLogic_GameUIManager.default.md#gettwitter)\n- [getUIEmitter](Backend_GameLogic_GameUIManager.default.md#getuiemitter)\n- [getUnconfirmedMoves](Backend_GameLogic_GameUIManager.default.md#getunconfirmedmoves)\n- [getUnconfirmedUpgrades](Backend_GameLogic_GameUIManager.default.md#getunconfirmedupgrades)\n- [getUnconfirmedWormholeActivations](Backend_GameLogic_GameUIManager.default.md#getunconfirmedwormholeactivations)\n- [getUniverseTotalEnergy](Backend_GameLogic_GameUIManager.default.md#getuniversetotalenergy)\n- [getUpgrade](Backend_GameLogic_GameUIManager.default.md#getupgrade)\n- [getViewport](Backend_GameLogic_GameUIManager.default.md#getviewport)\n- [getWorldRadius](Backend_GameLogic_GameUIManager.default.md#getworldradius)\n- [getWorldSilver](Backend_GameLogic_GameUIManager.default.md#getworldsilver)\n- [getWormholes](Backend_GameLogic_GameUIManager.default.md#getwormholes)\n- [hasMinedChunk](Backend_GameLogic_GameUIManager.default.md#hasminedchunk)\n- [isAbandoning](Backend_GameLogic_GameUIManager.default.md#isabandoning)\n- [isAdmin](Backend_GameLogic_GameUIManager.default.md#isadmin)\n- [isCurrentlyRevealing](Backend_GameLogic_GameUIManager.default.md#iscurrentlyrevealing)\n- [isMining](Backend_GameLogic_GameUIManager.default.md#ismining)\n- [isOverOwnPlanet](Backend_GameLogic_GameUIManager.default.md#isoverownplanet)\n- [isOwnedByMe](Backend_GameLogic_GameUIManager.default.md#isownedbyme)\n- [isRoundOver](Backend_GameLogic_GameUIManager.default.md#isroundover)\n- [isSendingForces](Backend_GameLogic_GameUIManager.default.md#issendingforces)\n- [isSendingShip](Backend_GameLogic_GameUIManager.default.md#issendingship)\n- [joinGame](Backend_GameLogic_GameUIManager.default.md#joingame)\n- [onDiscoveredChunk](Backend_GameLogic_GameUIManager.default.md#ondiscoveredchunk)\n- [onEmitInitializedPlayer](Backend_GameLogic_GameUIManager.default.md#onemitinitializedplayer)\n- [onEmitInitializedPlayerError](Backend_GameLogic_GameUIManager.default.md#onemitinitializedplayererror)\n- [onMouseClick](Backend_GameLogic_GameUIManager.default.md#onmouseclick)\n- [onMouseDown](Backend_GameLogic_GameUIManager.default.md#onmousedown)\n- [onMouseMove](Backend_GameLogic_GameUIManager.default.md#onmousemove)\n- [onMouseOut](Backend_GameLogic_GameUIManager.default.md#onmouseout)\n- [onMouseUp](Backend_GameLogic_GameUIManager.default.md#onmouseup)\n- [onSendCancel](Backend_GameLogic_GameUIManager.default.md#onsendcancel)\n- [onSendComplete](Backend_GameLogic_GameUIManager.default.md#onsendcomplete)\n- [onSendInit](Backend_GameLogic_GameUIManager.default.md#onsendinit)\n- [potentialCaptureScore](Backend_GameLogic_GameUIManager.default.md#potentialcapturescore)\n- [prospectPlanet](Backend_GameLogic_GameUIManager.default.md#prospectplanet)\n- [removeExtraMinerLocation](Backend_GameLogic_GameUIManager.default.md#removeextraminerlocation)\n- [revealLocation](Backend_GameLogic_GameUIManager.default.md#reveallocation)\n- [setAbandoning](Backend_GameLogic_GameUIManager.default.md#setabandoning)\n- [setArtifactSending](Backend_GameLogic_GameUIManager.default.md#setartifactsending)\n- [setCustomRenderer](Backend_GameLogic_GameUIManager.default.md#setcustomrenderer)\n- [setExtraMinerLocation](Backend_GameLogic_GameUIManager.default.md#setextraminerlocation)\n- [setForcesSending](Backend_GameLogic_GameUIManager.default.md#setforcessending)\n- [setHoveringOverArtifact](Backend_GameLogic_GameUIManager.default.md#sethoveringoverartifact)\n- [setHoveringOverPlanet](Backend_GameLogic_GameUIManager.default.md#sethoveringoverplanet)\n- [setMiningPattern](Backend_GameLogic_GameUIManager.default.md#setminingpattern)\n- [setModalManager](Backend_GameLogic_GameUIManager.default.md#setmodalmanager)\n- [setOverlayContainer](Backend_GameLogic_GameUIManager.default.md#setoverlaycontainer)\n- [setSelectedId](Backend_GameLogic_GameUIManager.default.md#setselectedid)\n- [setSelectedPlanet](Backend_GameLogic_GameUIManager.default.md#setselectedplanet)\n- [setSending](Backend_GameLogic_GameUIManager.default.md#setsending)\n- [setSilverSending](Backend_GameLogic_GameUIManager.default.md#setsilversending)\n- [spaceTypeFromPerlin](Backend_GameLogic_GameUIManager.default.md#spacetypefromperlin)\n- [startExplore](Backend_GameLogic_GameUIManager.default.md#startexplore)\n- [startWormholeFrom](Backend_GameLogic_GameUIManager.default.md#startwormholefrom)\n- [stopExplore](Backend_GameLogic_GameUIManager.default.md#stopexplore)\n- [timeUntilNextBroadcastAvailable](Backend_GameLogic_GameUIManager.default.md#timeuntilnextbroadcastavailable)\n- [toggleExplore](Backend_GameLogic_GameUIManager.default.md#toggleexplore)\n- [toggleTargettingExplorer](Backend_GameLogic_GameUIManager.default.md#toggletargettingexplorer)\n- [updateDiagnostics](Backend_GameLogic_GameUIManager.default.md#updatediagnostics)\n- [updateMouseHoveringOverCoords](Backend_GameLogic_GameUIManager.default.md#updatemousehoveringovercoords)\n- [updatePlanets](Backend_GameLogic_GameUIManager.default.md#updateplanets)\n- [upgrade](Backend_GameLogic_GameUIManager.default.md#upgrade)\n- [verifyTwitter](Backend_GameLogic_GameUIManager.default.md#verifytwitter)\n- [withdrawArtifact](Backend_GameLogic_GameUIManager.default.md#withdrawartifact)\n- [withdrawSilver](Backend_GameLogic_GameUIManager.default.md#withdrawsilver)\n- [create](Backend_GameLogic_GameUIManager.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`gameManager`, `terminalHandle`)\n\n#### Parameters\n\n| Name             | Type                                                                                                            |\n| :--------------- | :-------------------------------------------------------------------------------------------------------------- |\n| `gameManager`    | [`default`](Backend_GameLogic_GameManager.default.md)                                                           |\n| `terminalHandle` | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### abandoning\n\n• `Private` **abandoning**: `boolean` = `false`\n\n---\n\n### artifactSending\n\n• `Private` **artifactSending**: `Object` = `{}`\n\n#### Index signature\n\n▪ [key: `string`]: `Artifact` \\| `undefined`\n\n---\n\n### extraMinerLocations\n\n• `Private` **extraMinerLocations**: `WorldCoords`[] = `[]`\n\n---\n\n### forcesSending\n\n• `Private` **forcesSending**: `Object` = `{}`\n\n#### Index signature\n\n▪ [key: `string`]: `number`\n\n---\n\n### gameManager\n\n• `Private` `Readonly` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### hoverArtifact$\n\n• `Readonly` **hoverArtifact$**: `Monomitter`<`undefined` \\| `Artifact`\\>\n\n---\n\n### hoverArtifactId$\n\n• `Readonly` **hoverArtifactId$**: `Monomitter`<`undefined` \\| `ArtifactId`\\>\n\n---\n\n### hoverPlanet$\n\n• `Readonly` **hoverPlanet$**: `Monomitter`<`undefined` \\| `Planet`\\>\n\n---\n\n### hoverPlanetId$\n\n• `Readonly` **hoverPlanetId$**: `Monomitter`<`undefined` \\| `LocationId`\\>\n\n---\n\n### isAbandoning$\n\n• `Readonly` **isAbandoning$**: `Monomitter`<`boolean`\\>\n\n---\n\n### isChoosingTargetPlanet\n\n• `Private` **isChoosingTargetPlanet**: `boolean` = `false`\n\nThe Wormhole artifact requires you to choose a target planet. This value\nindicates whether or not the player is currently selecting a target planet.\n\n---\n\n### isSending\n\n• `Private` **isSending**: `boolean` = `false`\n\n---\n\n### isSending$\n\n• `Readonly` **isSending$**: `Monomitter`<`boolean`\\>\n\n---\n\n### minerLocation\n\n• `Private` **minerLocation**: `undefined` \\| `WorldCoords`\n\n---\n\n### modalManager\n\n• `Private` **modalManager**: [`default`](Frontend_Game_ModalManager.default.md)\n\n---\n\n### mouseDownOverCoords\n\n• `Private` **mouseDownOverCoords**: `undefined` \\| `WorldCoords`\n\n---\n\n### mouseDownOverPlanet\n\n• `Private` **mouseDownOverPlanet**: `undefined` \\| `LocatablePlanet`\n\n---\n\n### mouseHoveringOverCoords\n\n• `Private` **mouseHoveringOverCoords**: `undefined` \\| `WorldCoords`\n\n---\n\n### mouseHoveringOverPlanet\n\n• `Private` **mouseHoveringOverPlanet**: `undefined` \\| `LocatablePlanet`\n\n---\n\n### myArtifacts$\n\n• `Readonly` **myArtifacts$**: `Monomitter`<`Map`<`ArtifactId`, `Artifact`\\>\\>\n\n---\n\n### onChooseTargetPlanet\n\n• `Private` `Optional` **onChooseTargetPlanet**: (`planet`: `undefined` \\| `LocatablePlanet`) => `void`\n\n#### Type declaration\n\n▸ (`planet`): `void`\n\n##### Parameters\n\n| Name     | Type                             |\n| :------- | :------------------------------- |\n| `planet` | `undefined` \\| `LocatablePlanet` |\n\n##### Returns\n\n`void`\n\n---\n\n### overlayContainer\n\n• `Private` `Optional` **overlayContainer**: `HTMLDivElement`\n\nIn order to render React on top of the game, we need to insert React nodes into an overlay\ncontainer. We keep a reference to this container, so that our React components can optionally\nchoose to render themselves into this overlay container using React Portals.\n\n---\n\n### planetHoveringInRenderer\n\n• `Private` **planetHoveringInRenderer**: `boolean` = `false`\n\n---\n\n### plugins\n\n• `Private` **plugins**: [`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md)\n\n---\n\n### previousSelectedPlanetId\n\n• `Private` **previousSelectedPlanetId**: `undefined` \\| `LocationId`\n\n---\n\n### radiusMap\n\n• `Private` `Readonly` **radiusMap**: `Object`\n\n#### Index signature\n\n▪ [PlanetLevel: `number`]: `number`\n\n---\n\n### selectedCoords\n\n• `Private` **selectedCoords**: `undefined` \\| `WorldCoords`\n\n---\n\n### selectedPlanetId\n\n• `Private` **selectedPlanetId**: `undefined` \\| `LocationId`\n\n---\n\n### selectedPlanetId$\n\n• `Readonly` **selectedPlanetId$**: `Monomitter`<`undefined` \\| `LocationId`\\>\n\n---\n\n### sendingCoords\n\n• `Private` **sendingCoords**: `undefined` \\| `WorldCoords`\n\n---\n\n### sendingPlanet\n\n• `Private` **sendingPlanet**: `undefined` \\| `LocatablePlanet`\n\n---\n\n### silverSending\n\n• `Private` **silverSending**: `Object` = `{}`\n\n#### Index signature\n\n▪ [key: `string`]: `number`\n\n---\n\n### terminal\n\n• `Private` **terminal**: `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\>\n\n---\n\n### viewportEntities\n\n• `Private` **viewportEntities**: [`ViewportEntities`](Backend_GameLogic_ViewportEntities.ViewportEntities.md)\n\n## Accessors\n\n### captureZonesEnabled\n\n• `get` **captureZonesEnabled**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### contractConstants\n\n• `get` **contractConstants**(): [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n#### Returns\n\n[`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n## Methods\n\n### activateArtifact\n\n▸ **activateArtifact**(`locationId`, `id`, `wormholeTo?`): `void`\n\n#### Parameters\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `locationId`  | `LocationId` |\n| `id`          | `ArtifactId` |\n| `wormholeTo?` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### addAccount\n\n▸ **addAccount**(`coords`): `Promise`<`boolean`\\>\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### addNewChunk\n\n▸ **addNewChunk**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### bulkAddNewChunks\n\n▸ **bulkAddNewChunks**(`chunks`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name     | Type      |\n| :------- | :-------- |\n| `chunks` | `Chunk`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### buyHat\n\n▸ **buyHat**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### centerCoords\n\n▸ **centerCoords**(`coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### centerLocationId\n\n▸ **centerLocationId**(`planetId`): `void`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### centerPlanet\n\n▸ **centerPlanet**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type                             |\n| :------- | :------------------------------- |\n| `planet` | `undefined` \\| `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### deactivateArtifact\n\n▸ **deactivateArtifact**(`locationId`, `artifactId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`void`\n\n---\n\n### depositArtifact\n\n▸ **depositArtifact**(`locationId`, `artifactId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`void`\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### disableCustomRenderer\n\n▸ **disableCustomRenderer**(`customRenderer`): `void`\n\nThis function will remove the passed in renderer from the renderering stack. If the\nrenderer is on top of the renderering stack the next renderer will be the one bellow it.\n\n#### Parameters\n\n| Name             | Type           | Description                                              |\n| :--------------- | :------------- | :------------------------------------------------------- |\n| `customRenderer` | `BaseRenderer` | a Renderer that follows one of the 23 renderer tempaltes |\n\n#### Returns\n\n`void`\n\n---\n\n### disconnectTwitter\n\n▸ **disconnectTwitter**(`twitter`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### discoverBiome\n\n▸ **discoverBiome**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### drawAllRunningPlugins\n\n▸ **drawAllRunningPlugins**(`ctx`): `void`\n\n#### Parameters\n\n| Name  | Type                       |\n| :---- | :------------------------- |\n| `ctx` | `CanvasRenderingContext2D` |\n\n#### Returns\n\n`void`\n\n---\n\n### findArtifact\n\n▸ **findArtifact**(`planetId`): `void`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### generateVerificationTweet\n\n▸ **generateVerificationTweet**(`twitter`): `Promise`<`string`\\>\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`string`\\>\n\n---\n\n### get2dRenderer\n\n▸ **get2dRenderer**(): `null` \\| `CanvasRenderingContext2D`\n\n#### Returns\n\n`null` \\| `CanvasRenderingContext2D`\n\nthe CanvasRenderingContext2D for the game canvas.\n\n---\n\n### getAbandonRangeChangePercent\n\n▸ **getAbandonRangeChangePercent**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getAbandonSpeedChangePercent\n\n▸ **getAbandonSpeedChangePercent**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getAccount\n\n▸ **getAccount**(): `undefined` \\| `EthAddress`\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### getAllMinerLocations\n\n▸ **getAllMinerLocations**(): `WorldCoords`[]\n\n#### Returns\n\n`WorldCoords`[]\n\n---\n\n### getAllOwnedPlanets\n\n▸ **getAllOwnedPlanets**(): `Planet`[]\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getAllPlayers\n\n▸ **getAllPlayers**(): `Player`[]\n\n#### Returns\n\n`Player`[]\n\n---\n\n### getAllVoyages\n\n▸ **getAllVoyages**(): `QueuedArrival`[]\n\n#### Returns\n\n`QueuedArrival`[]\n\n---\n\n### getArtifactMap\n\n▸ **getArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getArtifactPlanet\n\n▸ **getArtifactPlanet**(`artifact`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name       | Type       |\n| :--------- | :--------- |\n| `artifact` | `Artifact` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getArtifactPointValues\n\n▸ **getArtifactPointValues**(): `ArtifactPointValues`\n\n#### Returns\n\n`ArtifactPointValues`\n\n---\n\n### getArtifactSending\n\n▸ **getArtifactSending**(`planetId?`): `undefined` \\| `Artifact`\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId?` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `Artifact`\n\n---\n\n### getArtifactUpdated$\n\n▸ **getArtifactUpdated$**(): `Monomitter`<`ArtifactId`\\>\n\n#### Returns\n\n`Monomitter`<`ArtifactId`\\>\n\n---\n\n### getArtifactWithId\n\n▸ **getArtifactWithId**(`artifactId`): `undefined` \\| `Artifact`\n\n#### Parameters\n\n| Name         | Type                        |\n| :----------- | :-------------------------- |\n| `artifactId` | `undefined` \\| `ArtifactId` |\n\n#### Returns\n\n`undefined` \\| `Artifact`\n\n---\n\n### getArtifactsWithIds\n\n▸ **getArtifactsWithIds**(`artifactIds?`): (`undefined` \\| `Artifact`)[]\n\n#### Parameters\n\n| Name           | Type           |\n| :------------- | :------------- |\n| `artifactIds?` | `ArtifactId`[] |\n\n#### Returns\n\n(`undefined` \\| `Artifact`)[]\n\n---\n\n### getBiomeKey\n\n▸ `Private` **getBiomeKey**(`biome`): `string`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `biome` | `Biome` |\n\n#### Returns\n\n`string`\n\n---\n\n### getBiomePerlin\n\n▸ **getBiomePerlin**(`coords`, `floor`): `number`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n| `floor`  | `boolean`     |\n\n#### Returns\n\n`number`\n\n---\n\n### getBooleanSetting\n\n▸ **getBooleanSetting**(`setting`): `boolean`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `setting` | `Setting` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### getCaptureZoneGenerator\n\n▸ **getCaptureZoneGenerator**(): `undefined` \\| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\n#### Returns\n\n`undefined` \\| [`CaptureZoneGenerator`](Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\n---\n\n### getCaptureZonePointValues\n\n▸ **getCaptureZonePointValues**(): [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`]\n\n#### Returns\n\n[`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`]\n\n---\n\n### getCaptureZones\n\n▸ **getCaptureZones**(): `Set`<`CaptureZone`\\>\n\n#### Returns\n\n`Set`<`CaptureZone`\\>\n\n---\n\n### getChunk\n\n▸ **getChunk**(`chunkFootprint`): `undefined` \\| `Chunk`\n\n#### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkFootprint` | `Rectangle` |\n\n#### Returns\n\n`undefined` \\| `Chunk`\n\n---\n\n### getContractAddress\n\n▸ **getContractAddress**(): `EthAddress`\n\n#### Returns\n\n`EthAddress`\n\n---\n\n### getDefaultSpaceJunkForPlanetLevel\n\n▸ **getDefaultSpaceJunkForPlanetLevel**(`level`): `number`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `level` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getDiagnostics\n\n▸ **getDiagnostics**(): `Diagnostics`\n\n#### Returns\n\n`Diagnostics`\n\n---\n\n### getDiscoverBiomeName\n\n▸ **getDiscoverBiomeName**(`biome`): `string`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `biome` | `Biome` |\n\n#### Returns\n\n`string`\n\n---\n\n### getDistCoords\n\n▸ **getDistCoords**(`from`, `to`): `number`\n\n#### Parameters\n\n| Name   | Type          |\n| :----- | :------------ |\n| `from` | `WorldCoords` |\n| `to`   | `WorldCoords` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEndTimeSeconds\n\n▸ **getEndTimeSeconds**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyArrivingForMove\n\n▸ **getEnergyArrivingForMove**(`from`, `to`, `dist`, `energy`): `number`\n\n#### Parameters\n\n| Name     | Type                        |\n| :------- | :-------------------------- |\n| `from`   | `LocationId`                |\n| `to`     | `undefined` \\| `LocationId` |\n| `dist`   | `undefined` \\| `number`     |\n| `energy` | `number`                    |\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyCurveAtPercent\n\n▸ **getEnergyCurveAtPercent**(`planet`, `percent`): `number`\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEnergyOfPlayer\n\n▸ **getEnergyOfPlayer**(`player`): `number`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEthConnection\n\n▸ **getEthConnection**(): `EthConnection`\n\n#### Returns\n\n`EthConnection`\n\n---\n\n### getExploredChunks\n\n▸ **getExploredChunks**(): `Iterable`<`Chunk`\\>\n\n#### Returns\n\n`Iterable`<`Chunk`\\>\n\n---\n\n### getForcesSending\n\n▸ **getForcesSending**(`planetId?`): `number`\n\nPercent from 0 to 100.\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId?` | `LocationId` |\n\n#### Returns\n\n`number`\n\n---\n\n### getGameManager\n\n▸ **getGameManager**(): [`default`](Backend_GameLogic_GameManager.default.md)\n\n#### Returns\n\n[`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### getGameObjects\n\n▸ **getGameObjects**(): [`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\nGets a reference to the game's internal representation of the world state. Beware! Use this for\nreading only, otherwise you might mess up the state of the game. You can try modifying the game\nstate in some way\n\n#### Returns\n\n[`GameObjects`](Backend_GameLogic_GameObjects.GameObjects.md)\n\n---\n\n### getGlManager\n\n▸ **getGlManager**(): `null` \\| `GameGLManager`\n\n#### Returns\n\n`null` \\| `GameGLManager`\n\n- A wrapper class for the WebGL2RenderingContext.\n\n---\n\n### getHashConfig\n\n▸ **getHashConfig**(): [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\n#### Returns\n\n[`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\n---\n\n### getHashesPerSec\n\n▸ **getHashesPerSec**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getHomeCoords\n\n▸ **getHomeCoords**(): `WorldCoords`\n\n#### Returns\n\n`WorldCoords`\n\n---\n\n### getHomeHash\n\n▸ **getHomeHash**(): `undefined` \\| `LocationId`\n\n#### Returns\n\n`undefined` \\| `LocationId`\n\n---\n\n### getHomePlanet\n\n▸ **getHomePlanet**(): `undefined` \\| `Planet`\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getHoveringOverCoords\n\n▸ **getHoveringOverCoords**(): `undefined` \\| `WorldCoords`\n\n#### Returns\n\n`undefined` \\| `WorldCoords`\n\n---\n\n### getHoveringOverPlanet\n\n▸ **getHoveringOverPlanet**(): `undefined` \\| `Planet`\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getIsChoosingTargetPlanet\n\n▸ **getIsChoosingTargetPlanet**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getIsHighPerfMode\n\n▸ **getIsHighPerfMode**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getLocationOfPlanet\n\n▸ **getLocationOfPlanet**(`planetId`): `undefined` \\| `WorldLocation`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `WorldLocation`\n\n---\n\n### getLocationsAndChunks\n\n▸ **getLocationsAndChunks**(): `Object`\n\n#### Returns\n\n`Object`\n\n| Name            | Type                                     |\n| :-------------- | :--------------------------------------- |\n| `cachedPlanets` | `Map`<`LocationId`, `PlanetRenderInfo`\\> |\n| `chunks`        | `Set`<`Chunk`\\>                          |\n\n---\n\n### getMinerLocation\n\n▸ **getMinerLocation**(): `undefined` \\| `WorldCoords`\n\n#### Returns\n\n`undefined` \\| `WorldCoords`\n\n---\n\n### getMiningPattern\n\n▸ **getMiningPattern**(): `undefined` \\| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n#### Returns\n\n`undefined` \\| [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n---\n\n### getModalManager\n\n▸ **getModalManager**(): [`default`](Frontend_Game_ModalManager.default.md)\n\n#### Returns\n\n[`default`](Frontend_Game_ModalManager.default.md)\n\n---\n\n### getMouseDownCoords\n\n▸ **getMouseDownCoords**(): `undefined` \\| `WorldCoords`\n\n#### Returns\n\n`undefined` \\| `WorldCoords`\n\n---\n\n### getMouseDownPlanet\n\n▸ **getMouseDownPlanet**(): `undefined` \\| `LocatablePlanet`\n\n#### Returns\n\n`undefined` \\| `LocatablePlanet`\n\n---\n\n### getMyArtifactMap\n\n▸ **getMyArtifactMap**(): `Map`<`ArtifactId`, `Artifact`\\>\n\n#### Returns\n\n`Map`<`ArtifactId`, `Artifact`\\>\n\n---\n\n### getMyArtifacts\n\n▸ **getMyArtifacts**(): `Artifact`[]\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getMyArtifactsNotOnPlanet\n\n▸ **getMyArtifactsNotOnPlanet**(): `Artifact`[]\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### getMyBalance\n\n▸ **getMyBalance**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getMyBalance$\n\n▸ **getMyBalance$**(): `Monomitter`<`BigNumber`\\>\n\n#### Returns\n\n`Monomitter`<`BigNumber`\\>\n\n---\n\n### getMyBalanceBn\n\n▸ **getMyBalanceBn**(): `BigNumber`\n\n#### Returns\n\n`BigNumber`\n\n---\n\n### getMyPlanetMap\n\n▸ **getMyPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getMyScore\n\n▸ **getMyScore**(): `undefined` \\| `number`\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getNextBroadcastAvailableTimestamp\n\n▸ **getNextBroadcastAvailableTimestamp**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getOverlayContainer\n\n▸ **getOverlayContainer**(): `undefined` \\| `HTMLDivElement`\n\nGets the overlay container. See {@link GameUIManger.overlayContainer} for more information\nabout what the overlay container is.\n\n#### Returns\n\n`undefined` \\| `HTMLDivElement`\n\n---\n\n### getPaused\n\n▸ **getPaused**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getPaused$\n\n▸ **getPaused$**(): `Monomitter`<`boolean`\\>\n\n#### Returns\n\n`Monomitter`<`boolean`\\>\n\n---\n\n### getPerlinConfig\n\n▸ **getPerlinConfig**(`isBiome?`): `PerlinConfig`\n\n#### Parameters\n\n| Name      | Type      | Default value |\n| :-------- | :-------- | :------------ |\n| `isBiome` | `boolean` | `false`       |\n\n#### Returns\n\n`PerlinConfig`\n\n---\n\n### getPerlinThresholds\n\n▸ **getPerlinThresholds**(): [`number`, `number`, `number`]\n\n#### Returns\n\n[`number`, `number`, `number`]\n\n---\n\n### getPlanetHoveringInRenderer\n\n▸ **getPlanetHoveringInRenderer**(): `boolean`\n\nIf there is a planet being hovered over, returns whether or not it's being hovered\nover in the renderer.\n\n#### Returns\n\n`boolean`\n\n---\n\n### getPlanetLevel\n\n▸ **getPlanetLevel**(`planetId`): `undefined` \\| `PlanetLevel`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`undefined` \\| `PlanetLevel`\n\n---\n\n### getPlanetMap\n\n▸ **getPlanetMap**(): `Map`<`LocationId`, `Planet`\\>\n\n#### Returns\n\n`Map`<`LocationId`, `Planet`\\>\n\n---\n\n### getPlanetWithCoords\n\n▸ **getPlanetWithCoords**(`coords`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name     | Type                         |\n| :------- | :--------------------------- |\n| `coords` | `undefined` \\| `WorldCoords` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPlanetWithId\n\n▸ **getPlanetWithId**(`planetId`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name       | Type                        |\n| :--------- | :-------------------------- |\n| `planetId` | `undefined` \\| `LocationId` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPlanetsInViewport\n\n▸ **getPlanetsInViewport**(): `Planet`[]\n\n#### Returns\n\n`Planet`[]\n\n---\n\n### getPlayer\n\n▸ **getPlayer**(`address?`): `undefined` \\| `Player`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `address?` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `Player`\n\n---\n\n### getPlayerScore\n\n▸ **getPlayerScore**(`player`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getPluginManager\n\n▸ **getPluginManager**(): [`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md)\n\n#### Returns\n\n[`PluginManager`](Backend_GameLogic_PluginManager.PluginManager.md)\n\n---\n\n### getPreviousSelectedPlanet\n\n▸ **getPreviousSelectedPlanet**(): `undefined` \\| `Planet`\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### getPrivateKey\n\n▸ **getPrivateKey**(): `undefined` \\| `string`\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getRadiusOfPlanetLevel\n\n▸ **getRadiusOfPlanetLevel**(`planetRarity`): `number`\n\n#### Parameters\n\n| Name           | Type          |\n| :------------- | :------------ |\n| `planetRarity` | `PlanetLevel` |\n\n#### Returns\n\n`number`\n\n---\n\n### getRangeBuff\n\n▸ **getRangeBuff**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getRenderer\n\n▸ **getRenderer**(): `null` \\| `Renderer`\n\n#### Returns\n\n`null` \\| `Renderer`\n\n---\n\n### getSelectedCoords\n\n▸ **getSelectedCoords**(): `undefined` \\| `WorldCoords`\n\n#### Returns\n\n`undefined` \\| `WorldCoords`\n\n---\n\n### getSelectedPlanet\n\n▸ **getSelectedPlanet**(): `undefined` \\| `LocatablePlanet`\n\n#### Returns\n\n`undefined` \\| `LocatablePlanet`\n\n---\n\n### getSilverCurveAtPercent\n\n▸ **getSilverCurveAtPercent**(`planet`, `percent`): `undefined` \\| `number`\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet`  | `Planet` |\n| `percent` | `number` |\n\n#### Returns\n\n`undefined` \\| `number`\n\n---\n\n### getSilverOfPlayer\n\n▸ **getSilverOfPlayer**(`player`): `number`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`number`\n\n---\n\n### getSilverScoreValue\n\n▸ **getSilverScoreValue**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getSilverSending\n\n▸ **getSilverSending**(`planetId?`): `number`\n\nPercent from 0 to 100.\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId?` | `LocationId` |\n\n#### Returns\n\n`number`\n\n---\n\n### getSpaceJunkEnabled\n\n▸ **getSpaceJunkEnabled**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### getSpaceTypePerlin\n\n▸ **getSpaceTypePerlin**(`coords`, `floor`): `number`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n| `floor`  | `boolean`     |\n\n#### Returns\n\n`number`\n\n---\n\n### getSpeedBuff\n\n▸ **getSpeedBuff**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getStringSetting\n\n▸ **getStringSetting**(`setting`): `undefined` \\| `string`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `setting` | `Setting` |\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getTerminal\n\n▸ **getTerminal**(): `undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\n\n#### Returns\n\n`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\n\n---\n\n### getTwitter\n\n▸ **getTwitter**(`address`): `undefined` \\| `string`\n\n#### Parameters\n\n| Name      | Type                        |\n| :-------- | :-------------------------- |\n| `address` | `undefined` \\| `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getUIEmitter\n\n▸ **getUIEmitter**(): [`default`](Frontend_Utils_UIEmitter.default.md)\n\n#### Returns\n\n[`default`](Frontend_Utils_UIEmitter.default.md)\n\n---\n\n### getUnconfirmedMoves\n\n▸ **getUnconfirmedMoves**(): `Transaction`<`UnconfirmedMove`\\>[]\n\n**`todo`** delete this. now that [GameObjects](Backend_GameLogic_GameObjects.GameObjects.md) is publically accessible, we shouldn't need to\ndrill fields like this anymore.\n\n**`tutorial`** Plugin developers, please access fields like this with something like {@code df.getGameObjects().}\n\n**`deprecated`**\n\n#### Returns\n\n`Transaction`<`UnconfirmedMove`\\>[]\n\n---\n\n### getUnconfirmedUpgrades\n\n▸ **getUnconfirmedUpgrades**(): `Transaction`<`UnconfirmedUpgrade`\\>[]\n\n#### Returns\n\n`Transaction`<`UnconfirmedUpgrade`\\>[]\n\n---\n\n### getUnconfirmedWormholeActivations\n\n▸ **getUnconfirmedWormholeActivations**(): `Transaction`<`UnconfirmedActivateArtifact`\\>[]\n\n#### Returns\n\n`Transaction`<`UnconfirmedActivateArtifact`\\>[]\n\n---\n\n### getUniverseTotalEnergy\n\n▸ **getUniverseTotalEnergy**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getUpgrade\n\n▸ **getUpgrade**(`branch`, `level`): `Upgrade`\n\n#### Parameters\n\n| Name     | Type                |\n| :------- | :------------------ |\n| `branch` | `UpgradeBranchName` |\n| `level`  | `number`            |\n\n#### Returns\n\n`Upgrade`\n\n---\n\n### getViewport\n\n▸ **getViewport**(): [`default`](Frontend_Game_Viewport.default.md)\n\n#### Returns\n\n[`default`](Frontend_Game_Viewport.default.md)\n\n---\n\n### getWorldRadius\n\n▸ **getWorldRadius**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getWorldSilver\n\n▸ **getWorldSilver**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getWormholes\n\n▸ **getWormholes**(): `Iterable`<`Wormhole`\\>\n\n#### Returns\n\n`Iterable`<`Wormhole`\\>\n\n---\n\n### hasMinedChunk\n\n▸ **hasMinedChunk**(`chunkLocation`): `boolean`\n\n#### Parameters\n\n| Name            | Type        |\n| :-------------- | :---------- |\n| `chunkLocation` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isAbandoning\n\n▸ **isAbandoning**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isAdmin\n\n▸ **isAdmin**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isCurrentlyRevealing\n\n▸ **isCurrentlyRevealing**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isMining\n\n▸ **isMining**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isOverOwnPlanet\n\n▸ **isOverOwnPlanet**(`coords`): `undefined` \\| `Planet`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`undefined` \\| `Planet`\n\n---\n\n### isOwnedByMe\n\n▸ **isOwnedByMe**(`planet`): `boolean`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isRoundOver\n\n▸ **isRoundOver**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isSendingForces\n\n▸ **isSendingForces**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isSendingShip\n\n▸ **isSendingShip**(`planetId?`): `boolean`\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId?` | `LocationId` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### joinGame\n\n▸ **joinGame**(`beforeRetry`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name          | Type                                    |\n| :------------ | :-------------------------------------- |\n| `beforeRetry` | (`e`: `Error`) => `Promise`<`boolean`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### onDiscoveredChunk\n\n▸ **onDiscoveredChunk**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### onEmitInitializedPlayer\n\n▸ `Private` **onEmitInitializedPlayer**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onEmitInitializedPlayerError\n\n▸ `Private` **onEmitInitializedPlayerError**(`err`): `void`\n\n#### Parameters\n\n| Name  | Type        |\n| :---- | :---------- |\n| `err` | `ReactNode` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseClick\n\n▸ **onMouseClick**(`_coords`): `void`\n\n#### Parameters\n\n| Name      | Type          |\n| :-------- | :------------ |\n| `_coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseDown\n\n▸ **onMouseDown**(`coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseMove\n\n▸ **onMouseMove**(`coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseOut\n\n▸ **onMouseOut**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseUp\n\n▸ **onMouseUp**(`coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onSendCancel\n\n▸ **onSendCancel**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onSendComplete\n\n▸ **onSendComplete**(`locationId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### onSendInit\n\n▸ **onSendInit**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type                             |\n| :------- | :------------------------------- |\n| `planet` | `undefined` \\| `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### potentialCaptureScore\n\n▸ **potentialCaptureScore**(`planetLevel`): `number`\n\n#### Parameters\n\n| Name          | Type     |\n| :------------ | :------- |\n| `planetLevel` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### prospectPlanet\n\n▸ **prospectPlanet**(`planetId`): `void`\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### removeExtraMinerLocation\n\n▸ **removeExtraMinerLocation**(`idx`): `void`\n\n#### Parameters\n\n| Name  | Type     |\n| :---- | :------- |\n| `idx` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### revealLocation\n\n▸ **revealLocation**(`locationId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### setAbandoning\n\n▸ **setAbandoning**(`abandoning`): `void`\n\n#### Parameters\n\n| Name         | Type      |\n| :----------- | :-------- |\n| `abandoning` | `boolean` |\n\n#### Returns\n\n`void`\n\n---\n\n### setArtifactSending\n\n▸ **setArtifactSending**(`planetId`, `artifact?`): `void`\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `planetId`  | `LocationId` |\n| `artifact?` | `Artifact`   |\n\n#### Returns\n\n`void`\n\n---\n\n### setCustomRenderer\n\n▸ **setCustomRenderer**(`customRenderer`): `void`\n\nReplaces the current renderer with the passed in custom renderer and adds the renderer\nto the rendering stack. The function will automatically determine which renderer it is\nby the rendererType and the methods in the renderer.\n\n#### Parameters\n\n| Name             | Type           | Description                                              |\n| :--------------- | :------------- | :------------------------------------------------------- |\n| `customRenderer` | `BaseRenderer` | a Renderer that follows one of the 23 renderer tempaltes |\n\n#### Returns\n\n`void`\n\n---\n\n### setExtraMinerLocation\n\n▸ **setExtraMinerLocation**(`idx`, `coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `idx`    | `number`      |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### setForcesSending\n\n▸ **setForcesSending**(`planetId`, `percentage`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `planetId`   | `LocationId` |\n| `percentage` | `number`     |\n\n#### Returns\n\n`void`\n\n---\n\n### setHoveringOverArtifact\n\n▸ **setHoveringOverArtifact**(`artifactId?`): `void`\n\n#### Parameters\n\n| Name          | Type         |\n| :------------ | :----------- |\n| `artifactId?` | `ArtifactId` |\n\n#### Returns\n\n`void`\n\n---\n\n### setHoveringOverPlanet\n\n▸ **setHoveringOverPlanet**(`planet`, `inRenderer`): `void`\n\n#### Parameters\n\n| Name         | Type                             |\n| :----------- | :------------------------------- |\n| `planet`     | `undefined` \\| `LocatablePlanet` |\n| `inRenderer` | `boolean`                        |\n\n#### Returns\n\n`void`\n\n---\n\n### setMiningPattern\n\n▸ **setMiningPattern**(`pattern`): `void`\n\n#### Parameters\n\n| Name      | Type                                                                           |\n| :-------- | :----------------------------------------------------------------------------- |\n| `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### setModalManager\n\n▸ `Private` **setModalManager**(`modalManager`): `void`\n\n#### Parameters\n\n| Name           | Type                                               |\n| :------------- | :------------------------------------------------- |\n| `modalManager` | [`default`](Frontend_Game_ModalManager.default.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### setOverlayContainer\n\n▸ **setOverlayContainer**(`randomContainer?`): `void`\n\nSets the overlay container. See {@link GameUIManger.overlayContainer} for more information\nabout what the overlay container is.\n\n#### Parameters\n\n| Name               | Type             |\n| :----------------- | :--------------- |\n| `randomContainer?` | `HTMLDivElement` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSelectedId\n\n▸ **setSelectedId**(`id`): `void`\n\n#### Parameters\n\n| Name | Type         |\n| :--- | :----------- |\n| `id` | `LocationId` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSelectedPlanet\n\n▸ **setSelectedPlanet**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type                             |\n| :------- | :------------------------------- |\n| `planet` | `undefined` \\| `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSending\n\n▸ **setSending**(`sending`): `void`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `sending` | `boolean` |\n\n#### Returns\n\n`void`\n\n---\n\n### setSilverSending\n\n▸ **setSilverSending**(`planetId`, `percentage`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `planetId`   | `LocationId` |\n| `percentage` | `number`     |\n\n#### Returns\n\n`void`\n\n---\n\n### spaceTypeFromPerlin\n\n▸ **spaceTypeFromPerlin**(`perlin`): `SpaceType`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `perlin` | `number` |\n\n#### Returns\n\n`SpaceType`\n\n---\n\n### startExplore\n\n▸ **startExplore**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### startWormholeFrom\n\n▸ **startWormholeFrom**(`planet`): `Promise`<`undefined` \\| `LocatablePlanet`\\>\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `LocatablePlanet`\\>\n\n---\n\n### stopExplore\n\n▸ **stopExplore**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### timeUntilNextBroadcastAvailable\n\n▸ **timeUntilNextBroadcastAvailable**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### toggleExplore\n\n▸ **toggleExplore**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### toggleTargettingExplorer\n\n▸ **toggleTargettingExplorer**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### updateDiagnostics\n\n▸ **updateDiagnostics**(`updateFn`): `void`\n\n#### Parameters\n\n| Name       | Type                           |\n| :--------- | :----------------------------- |\n| `updateFn` | (`d`: `Diagnostics`) => `void` |\n\n#### Returns\n\n`void`\n\n---\n\n### updateMouseHoveringOverCoords\n\n▸ `Private` **updateMouseHoveringOverCoords**(`coords`): `WorldCoords`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`WorldCoords`\n\n---\n\n### updatePlanets\n\n▸ `Private` **updatePlanets**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### upgrade\n\n▸ **upgrade**(`planet`, `branch`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n| `branch` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### verifyTwitter\n\n▸ **verifyTwitter**(`twitter`): `Promise`<`boolean`\\>\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `twitter` | `string` |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### withdrawArtifact\n\n▸ **withdrawArtifact**(`locationId`, `artifactId`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`void`\n\n---\n\n### withdrawSilver\n\n▸ **withdrawSilver**(`locationId`, `amount`): `void`\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `locationId` | `LocationId` |\n| `amount`     | `number`     |\n\n#### Returns\n\n`void`\n\n---\n\n### create\n\n▸ `Static` **create**(`gameManager`, `terminalHandle`): `Promise`<[`default`](Backend_GameLogic_GameUIManager.default.md)\\>\n\n#### Parameters\n\n| Name             | Type                                                                                                            |\n| :--------------- | :-------------------------------------------------------------------------------------------------------------- |\n| `gameManager`    | [`default`](Backend_GameLogic_GameManager.default.md)                                                           |\n| `terminalHandle` | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n\n#### Returns\n\n`Promise`<[`default`](Backend_GameLogic_GameUIManager.default.md)\\>\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md",
    "content": "# Class: InitialGameStateDownloader\n\n[Backend/GameLogic/InitialGameStateDownloader](../modules/Backend_GameLogic_InitialGameStateDownloader.md).InitialGameStateDownloader\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#constructor)\n\n### Properties\n\n- [terminal](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#terminal)\n\n### Methods\n\n- [download](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#download)\n- [makeProgressListener](Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md#makeprogresslistener)\n\n## Constructors\n\n### constructor\n\n• **new InitialGameStateDownloader**(`terminal`)\n\n#### Parameters\n\n| Name       | Type                                                                        |\n| :--------- | :-------------------------------------------------------------------------- |\n| `terminal` | [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md) |\n\n## Properties\n\n### terminal\n\n• `Private` **terminal**: [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\n\n## Methods\n\n### download\n\n▸ **download**(`contractsAPI`, `persistentChunkStore`): `Promise`<[`InitialGameState`](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md)\\>\n\n#### Parameters\n\n| Name                   | Type                                                             |\n| :--------------------- | :--------------------------------------------------------------- |\n| `contractsAPI`         | [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md) |\n| `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md)     |\n\n#### Returns\n\n`Promise`<[`InitialGameState`](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md)\\>\n\n---\n\n### makeProgressListener\n\n▸ `Private` **makeProgressListener**(`prettyEntityName`): (`percent`: `number`) => `void`\n\n#### Parameters\n\n| Name               | Type     |\n| :----------------- | :------- |\n| `prettyEntityName` | `string` |\n\n#### Returns\n\n`fn`\n\n▸ (`percent`): `void`\n\n##### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `percent` | `number` |\n\n##### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_LayeredMap.LayeredMap.md",
    "content": "# Class: LayeredMap\n\n[Backend/GameLogic/LayeredMap](../modules/Backend_GameLogic_LayeredMap.md).LayeredMap\n\nData structure which allows us to efficiently query for \"which planets between level X and X + n\n(for positive n) are present in the given world rectangle\", as well as, in the future, \"which\nchunks are visible in the vieport\".\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_LayeredMap.LayeredMap.md#constructor)\n\n### Properties\n\n- [insertedLocations](Backend_GameLogic_LayeredMap.LayeredMap.md#insertedlocations)\n- [perLevelPlanetQuadtrees](Backend_GameLogic_LayeredMap.LayeredMap.md#perlevelplanetquadtrees)\n\n### Methods\n\n- [getPlanets](Backend_GameLogic_LayeredMap.LayeredMap.md#getplanets)\n- [getPlanetsInCircle](Backend_GameLogic_LayeredMap.LayeredMap.md#getplanetsincircle)\n- [getPointLocationId](Backend_GameLogic_LayeredMap.LayeredMap.md#getpointlocationid)\n- [insertPlanet](Backend_GameLogic_LayeredMap.LayeredMap.md#insertplanet)\n\n## Constructors\n\n### constructor\n\n• **new LayeredMap**(`worldRadius`)\n\n#### Parameters\n\n| Name          | Type     |\n| :------------ | :------- |\n| `worldRadius` | `number` |\n\n## Properties\n\n### insertedLocations\n\n• `Private` **insertedLocations**: `Set`<`LocationId`\\>\n\n---\n\n### perLevelPlanetQuadtrees\n\n• `Private` **perLevelPlanetQuadtrees**: `Map`<`number`, `QuadTree`\\>\n\n## Methods\n\n### getPlanets\n\n▸ **getPlanets**(`worldX`, `worldY`, `worldWidth`, `worldHeight`, `planetLevels`, `planetLevelToRadii`): `LocationId`[]\n\nGets the ids of all the planets that are both within the given bounding box (defined by its bottom\nleft coordinate, width, and height) in the world and of a level that was passed in via the\n`planetLevels` parameter.\n\n#### Parameters\n\n| Name                 | Type                      |\n| :------------------- | :------------------------ |\n| `worldX`             | `number`                  |\n| `worldY`             | `number`                  |\n| `worldWidth`         | `number`                  |\n| `worldHeight`        | `number`                  |\n| `planetLevels`       | `number`[]                |\n| `planetLevelToRadii` | `Map`<`number`, `Radii`\\> |\n\n#### Returns\n\n`LocationId`[]\n\n---\n\n### getPlanetsInCircle\n\n▸ **getPlanetsInCircle**(`coords`, `worldRadius`): `LocationId`[]\n\nGets all the planets within the given world radius of a world location.\n\n#### Parameters\n\n| Name          | Type          |\n| :------------ | :------------ |\n| `coords`      | `WorldCoords` |\n| `worldRadius` | `number`      |\n\n#### Returns\n\n`LocationId`[]\n\n---\n\n### getPointLocationId\n\n▸ `Private` **getPointLocationId**(`point`): `LocationId`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `point` | `Point` |\n\n#### Returns\n\n`LocationId`\n\n---\n\n### insertPlanet\n\n▸ **insertPlanet**(`location`, `planetLevel`): `void`\n\nRecords the fact that there is a planet at the given world location.\n\n#### Parameters\n\n| Name          | Type            |\n| :------------ | :-------------- |\n| `location`    | `WorldLocation` |\n| `planetLevel` | `number`        |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_PluginManager.PluginManager.md",
    "content": "# Class: PluginManager\n\n[Backend/GameLogic/PluginManager](../modules/Backend_GameLogic_PluginManager.md).PluginManager\n\nThis class keeps track of all the plugins that this player has loaded\ninto their game. Acts as a task manager, supports all CRUD operations\nfor plugins, as well as instantiating and destroying running plugins.\nAll library operations are also persisted to IndexDB.\n\nImportant! Does not run plugins until the user clicks 'run' somewhere in\nthis UI. This is important, because if someone develops a buggy plugin,\nit would suck if that bricked their game.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_PluginManager.PluginManager.md#constructor)\n\n### Properties\n\n- [gameManager](Backend_GameLogic_PluginManager.PluginManager.md#gamemanager)\n- [pluginLibrary](Backend_GameLogic_PluginManager.PluginManager.md#pluginlibrary)\n- [pluginProcessInfos](Backend_GameLogic_PluginManager.PluginManager.md#pluginprocessinfos)\n- [pluginProcesses](Backend_GameLogic_PluginManager.PluginManager.md#pluginprocesses)\n- [plugins$](Backend_GameLogic_PluginManager.PluginManager.md#plugins$)\n\n### Methods\n\n- [addPluginToLibrary](Backend_GameLogic_PluginManager.PluginManager.md#addplugintolibrary)\n- [deletePlugin](Backend_GameLogic_PluginManager.PluginManager.md#deleteplugin)\n- [destroy](Backend_GameLogic_PluginManager.PluginManager.md#destroy)\n- [drawAllRunningPlugins](Backend_GameLogic_PluginManager.PluginManager.md#drawallrunningplugins)\n- [getAllProcessInfos](Backend_GameLogic_PluginManager.PluginManager.md#getallprocessinfos)\n- [getLibrary](Backend_GameLogic_PluginManager.PluginManager.md#getlibrary)\n- [getPluginFromLibrary](Backend_GameLogic_PluginManager.PluginManager.md#getpluginfromlibrary)\n- [getProcessInfo](Backend_GameLogic_PluginManager.PluginManager.md#getprocessinfo)\n- [hasPlugin](Backend_GameLogic_PluginManager.PluginManager.md#hasplugin)\n- [load](Backend_GameLogic_PluginManager.PluginManager.md#load)\n- [notifyPluginLibraryUpdated](Backend_GameLogic_PluginManager.PluginManager.md#notifypluginlibraryupdated)\n- [onNewEmbeddedPlugins](Backend_GameLogic_PluginManager.PluginManager.md#onnewembeddedplugins)\n- [overwritePlugin](Backend_GameLogic_PluginManager.PluginManager.md#overwriteplugin)\n- [render](Backend_GameLogic_PluginManager.PluginManager.md#render)\n- [reorderPlugins](Backend_GameLogic_PluginManager.PluginManager.md#reorderplugins)\n- [spawn](Backend_GameLogic_PluginManager.PluginManager.md#spawn)\n- [copy](Backend_GameLogic_PluginManager.PluginManager.md#copy)\n\n## Constructors\n\n### constructor\n\n• **new PluginManager**(`gameManager`)\n\n#### Parameters\n\n| Name          | Type                                                  |\n| :------------ | :---------------------------------------------------- |\n| `gameManager` | [`default`](Backend_GameLogic_GameManager.default.md) |\n\n## Properties\n\n### gameManager\n\n• `Private` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### pluginLibrary\n\n• `Private` **pluginLibrary**: [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\n\nAll the plugins in the player's library. Not all of the player's plugins\nare running, and therefore not all exist in `pluginInstances`.\n`PluginsManager` keeps this field in sync with the plugins the user has\nsaved in the IndexDB via {@link PersistentChunkStore}\n\n---\n\n### pluginProcessInfos\n\n• `Private` **pluginProcessInfos**: `Record`<`string`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\\>\n\nparallel to pluginProcesses\n\n---\n\n### pluginProcesses\n\n• `Private` **pluginProcesses**: `Record`<`string`, [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\\>\n\nPlugins that are currently loaded into the game, and are rendering into a modal.\n`PluginsManager` makes sure that when a plugin starts executing, it is added into\n`pluginInstances`, and that once a plugin is unloaded, its `.destroy()` method is called, and\nthat the plugin is removed from `pluginInstances`.\n\n---\n\n### plugins$\n\n• **plugins$**: `Monomitter`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\\>\n\nEvent emitter that publishes whenever the set of plugins changes.\n\n## Methods\n\n### addPluginToLibrary\n\n▸ **addPluginToLibrary**(`id`, `name`, `code`): [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)\n\nadds a new plugin into the plugin library.\n\n#### Parameters\n\n| Name   | Type       |\n| :----- | :--------- |\n| `id`   | `PluginId` |\n| `name` | `string`   |\n| `code` | `string`   |\n\n#### Returns\n\n[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)\n\n---\n\n### deletePlugin\n\n▸ **deletePlugin**(`pluginId`): `Promise`<`void`\\>\n\nRemove the given plugin both from the player's library, and kills\nthe plugin if it is running.\n\n#### Parameters\n\n| Name       | Type       |\n| :--------- | :--------- |\n| `pluginId` | `PluginId` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### destroy\n\n▸ **destroy**(`id`): `void`\n\nIf a plugin with the given id is running, call its `.destroy()` method,\nand remove it from `pluginInstances`. Stop listening for new local plugins.\n\n#### Parameters\n\n| Name | Type       |\n| :--- | :--------- |\n| `id` | `PluginId` |\n\n#### Returns\n\n`void`\n\n---\n\n### drawAllRunningPlugins\n\n▸ **drawAllRunningPlugins**(`ctx`): `void`\n\nFor each currently running plugin, if the plugin has a 'draw'\nfunction, then draw that plugin to the screen.\n\n#### Parameters\n\n| Name  | Type                       |\n| :---- | :------------------------- |\n| `ctx` | `CanvasRenderingContext2D` |\n\n#### Returns\n\n`void`\n\n---\n\n### getAllProcessInfos\n\n▸ **getAllProcessInfos**(): `Map`<`PluginId`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\\>\n\nGets a map of all the currently running processes\n\n#### Returns\n\n`Map`<`PluginId`, [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\\>\n\n---\n\n### getLibrary\n\n▸ **getLibrary**(): [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\n\nGets all the plugins in this player's library.\n\n#### Returns\n\n[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\n\n---\n\n### getPluginFromLibrary\n\n▸ **getPluginFromLibrary**(`id?`): `undefined` \\| [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)\n\nGets the serialized plugin with the given id from the player's plugin\nlibrary. `undefined` if no plugin exists.\n\n#### Parameters\n\n| Name  | Type       |\n| :---- | :--------- |\n| `id?` | `PluginId` |\n\n#### Returns\n\n`undefined` \\| [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)\n\n---\n\n### getProcessInfo\n\n▸ **getProcessInfo**(`id`): [`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\n\nIf this process has been started, gets its info\n\n#### Parameters\n\n| Name | Type       |\n| :--- | :--------- |\n| `id` | `PluginId` |\n\n#### Returns\n\n[`ProcessInfo`](Backend_GameLogic_PluginManager.ProcessInfo.md)\n\n---\n\n### hasPlugin\n\n▸ `Private` **hasPlugin**(`plugin`): `boolean`\n\n#### Parameters\n\n| Name     | Type                                                                                     |\n| :------- | :--------------------------------------------------------------------------------------- |\n| `plugin` | [`EmbeddedPlugin`](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md) |\n\n#### Returns\n\n`boolean`\n\n---\n\n### load\n\n▸ **load**(`isAdmin`, `overwriteEmbeddedPlugins`): `Promise`<`void`\\>\n\nLoad all plugins from this disk into `pluginLibrary`. Insert the default\nplugins into the player's library if the default plugins have never been\nadded before. Effectively idempotent after the first time you call it.\n\n#### Parameters\n\n| Name                       | Type      | Description                                                                               |\n| :------------------------- | :-------- | :---------------------------------------------------------------------------------------- |\n| `isAdmin`                  | `boolean` | Is an admin loading the plugins.                                                          |\n| `overwriteEmbeddedPlugins` | `boolean` | Reload all embedded plugins even if a local copy is found. Useful for plugin development. |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### notifyPluginLibraryUpdated\n\n▸ `Private` **notifyPluginLibraryUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onNewEmbeddedPlugins\n\n▸ `Private` **onNewEmbeddedPlugins**(`newPlugins`, `overwriteEmbeddedPlugins`): `void`\n\n#### Parameters\n\n| Name                       | Type                                                                                       |\n| :------------------------- | :----------------------------------------------------------------------------------------- |\n| `newPlugins`               | [`EmbeddedPlugin`](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md)[] |\n| `overwriteEmbeddedPlugins` | `boolean`                                                                                  |\n\n#### Returns\n\n`void`\n\n---\n\n### overwritePlugin\n\n▸ **overwritePlugin**(`newName`, `pluginCode`, `id`): `void`\n\n1. kills the plugin if it's running\n2. edits the plugin-library version of this plugin\n3. if a plugin was edited, save the plugin library to disk\n\n#### Parameters\n\n| Name         | Type       |\n| :----------- | :--------- |\n| `newName`    | `string`   |\n| `pluginCode` | `string`   |\n| `id`         | `PluginId` |\n\n#### Returns\n\n`void`\n\n---\n\n### render\n\n▸ **render**(`id`, `element`): `Promise`<`void`\\>\n\nIf this plugin's `render` method has not been called yet, then\ncall it! Remembers that this plugin has been rendered.\n\n#### Parameters\n\n| Name      | Type             |\n| :-------- | :--------------- |\n| `id`      | `PluginId`       |\n| `element` | `HTMLDivElement` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### reorderPlugins\n\n▸ **reorderPlugins**(`newPluginIdOrder`): `void`\n\nReorders the current plugins. plugin ids in `newPluginIdOrder` must correspond\n1:1 to plugins in the plugin library.\n\n#### Parameters\n\n| Name               | Type       |\n| :----------------- | :--------- |\n| `newPluginIdOrder` | `string`[] |\n\n#### Returns\n\n`void`\n\n---\n\n### spawn\n\n▸ **spawn**(`id`): `Promise`<`undefined` \\| [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\\>\n\nEither spawns the given plugin by evaluating its `pluginCode`, or\nreturns the already running plugin instance. If starting a plugin\nthrows an error then returns `undefined`.\n\n#### Parameters\n\n| Name | Type       |\n| :--- | :--------- |\n| `id` | `PluginId` |\n\n#### Returns\n\n`Promise`<`undefined` \\| [`PluginProcess`](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\\>\n\n---\n\n### copy\n\n▸ `Static` `Private` **copy**<`T`\\>(`plugin`): `T`\n\nTo prevent users of this class from modifying our plugins library,\nwe return clones of the plugins. This should probably be a function\nin a Utils file somewhere, but I thought I should leave a good comment\nabout why we return copies of the plugins from the library.\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name     | Type |\n| :------- | :--- |\n| `plugin` | `T`  |\n\n#### Returns\n\n`T`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_PluginManager.ProcessInfo.md",
    "content": "# Class: ProcessInfo\n\n[Backend/GameLogic/PluginManager](../modules/Backend_GameLogic_PluginManager.md).ProcessInfo\n\nRepresents book-keeping information about a running process. We keep it\nseparate from the process code, so that the plugin doesn't accidentally\noverwrite this information.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_PluginManager.ProcessInfo.md#constructor)\n\n### Properties\n\n- [hasError](Backend_GameLogic_PluginManager.ProcessInfo.md#haserror)\n- [rendered](Backend_GameLogic_PluginManager.ProcessInfo.md#rendered)\n\n## Constructors\n\n### constructor\n\n• **new ProcessInfo**()\n\n## Properties\n\n### hasError\n\n• **hasError**: `boolean` = `false`\n\n---\n\n### rendered\n\n• **rendered**: `boolean` = `false`\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_TutorialManager.default.md",
    "content": "# Class: default\n\n[Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_TutorialManager.default.md#constructor)\n\n### Properties\n\n- [tutorialState](Backend_GameLogic_TutorialManager.default.md#tutorialstate)\n- [uiManager](Backend_GameLogic_TutorialManager.default.md#uimanager)\n- [instance](Backend_GameLogic_TutorialManager.default.md#instance)\n\n### Methods\n\n- [acceptInput](Backend_GameLogic_TutorialManager.default.md#acceptinput)\n- [advance](Backend_GameLogic_TutorialManager.default.md#advance)\n- [complete](Backend_GameLogic_TutorialManager.default.md#complete)\n- [reset](Backend_GameLogic_TutorialManager.default.md#reset)\n- [setTutorialState](Backend_GameLogic_TutorialManager.default.md#settutorialstate)\n- [shouldSkipState](Backend_GameLogic_TutorialManager.default.md#shouldskipstate)\n- [getInstance](Backend_GameLogic_TutorialManager.default.md#getinstance)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`uiManager`)\n\n#### Parameters\n\n| Name        | Type                                                    |\n| :---------- | :------------------------------------------------------ |\n| `uiManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### tutorialState\n\n• `Private` **tutorialState**: [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) = `TutorialState.None`\n\n---\n\n### uiManager\n\n• `Private` **uiManager**: [`default`](Backend_GameLogic_GameUIManager.default.md)\n\n---\n\n### instance\n\n▪ `Static` **instance**: [`default`](Backend_GameLogic_TutorialManager.default.md)\n\n## Methods\n\n### acceptInput\n\n▸ **acceptInput**(`state`): `void`\n\n#### Parameters\n\n| Name    | Type                                                                           |\n| :------ | :----------------------------------------------------------------------------- |\n| `state` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### advance\n\n▸ `Private` **advance**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### complete\n\n▸ **complete**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### reset\n\n▸ **reset**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### setTutorialState\n\n▸ `Private` **setTutorialState**(`newState`): `void`\n\n#### Parameters\n\n| Name       | Type                                                                           |\n| :--------- | :----------------------------------------------------------------------------- |\n| `newState` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### shouldSkipState\n\n▸ `Private` **shouldSkipState**(`state`): `boolean`\n\n#### Parameters\n\n| Name    | Type                                                                           |\n| :------ | :----------------------------------------------------------------------------- |\n| `state` | [`TutorialState`](../enums/Backend_GameLogic_TutorialManager.TutorialState.md) |\n\n#### Returns\n\n`boolean`\n\n---\n\n### getInstance\n\n▸ `Static` **getInstance**(`uiManager`): [`default`](Backend_GameLogic_TutorialManager.default.md)\n\n#### Parameters\n\n| Name        | Type                                                    |\n| :---------- | :------------------------------------------------------ |\n| `uiManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n[`default`](Backend_GameLogic_TutorialManager.default.md)\n"
  },
  {
    "path": "docs/classes/Backend_GameLogic_ViewportEntities.ViewportEntities.md",
    "content": "# Class: ViewportEntities\n\n[Backend/GameLogic/ViewportEntities](../modules/Backend_GameLogic_ViewportEntities.md).ViewportEntities\n\nEfficiently calculates which planets are in the viewport, and allows you to find the nearest\nvisible planet to the mouse.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_GameLogic_ViewportEntities.ViewportEntities.md#constructor)\n\n### Properties\n\n- [cachedExploredChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#cachedexploredchunks)\n- [cachedPlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#cachedplanets)\n- [gameManager](Backend_GameLogic_ViewportEntities.ViewportEntities.md#gamemanager)\n- [uiManager](Backend_GameLogic_ViewportEntities.ViewportEntities.md#uimanager)\n\n### Methods\n\n- [getNearestVisiblePlanet](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getnearestvisibleplanet)\n- [getPlanetRadii](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getplanetradii)\n- [getPlanetsAndChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getplanetsandchunks)\n- [getVisiblePlanetLevels](Backend_GameLogic_ViewportEntities.ViewportEntities.md#getvisibleplanetlevels)\n- [loadPlanetMessages](Backend_GameLogic_ViewportEntities.ViewportEntities.md#loadplanetmessages)\n- [recalculateViewportChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#recalculateviewportchunks)\n- [recalculateViewportPlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#recalculateviewportplanets)\n- [replacePlanets](Backend_GameLogic_ViewportEntities.ViewportEntities.md#replaceplanets)\n- [startRefreshing](Backend_GameLogic_ViewportEntities.ViewportEntities.md#startrefreshing)\n- [updateLocationsAndChunks](Backend_GameLogic_ViewportEntities.ViewportEntities.md#updatelocationsandchunks)\n\n## Constructors\n\n### constructor\n\n• **new ViewportEntities**(`gameManager`, `gameUIManager`)\n\n#### Parameters\n\n| Name            | Type                                                    |\n| :-------------- | :------------------------------------------------------ |\n| `gameManager`   | [`default`](Backend_GameLogic_GameManager.default.md)   |\n| `gameUIManager` | [`default`](Backend_GameLogic_GameUIManager.default.md) |\n\n## Properties\n\n### cachedExploredChunks\n\n• `Private` **cachedExploredChunks**: `Set`<`Chunk`\\>\n\n---\n\n### cachedPlanets\n\n• `Private` **cachedPlanets**: `Map`<`LocationId`, `PlanetRenderInfo`\\>\n\n---\n\n### gameManager\n\n• `Private` `Readonly` **gameManager**: [`default`](Backend_GameLogic_GameManager.default.md)\n\n---\n\n### uiManager\n\n• `Private` `Readonly` **uiManager**: [`default`](Backend_GameLogic_GameUIManager.default.md)\n\n## Methods\n\n### getNearestVisiblePlanet\n\n▸ **getNearestVisiblePlanet**(`coords`): `undefined` \\| `LocatablePlanet`\n\nGets the planet that is closest to the given coordinates. Filters out irrelevant planets\nusing the `radiusMap` parameter, which specifies how close a planet must be in order to\nbe returned from this function, given that planet's level. Smaller planets have a smaller\nradius, and larger planets have a larger radius.\n\nIf a smaller and a larger planet are both within respective radii of coords, the smaller\nplanet is returned.\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`undefined` \\| `LocatablePlanet`\n\n---\n\n### getPlanetRadii\n\n▸ `Private` **getPlanetRadii**(`viewport`): `Map`<`PlanetLevel`, `Radii`\\>\n\nOne entry per planet level - radius in screen pixels of that planet level given the current\nviewport configuration, as well as the world radius.\n\n#### Parameters\n\n| Name       | Type                                           |\n| :--------- | :--------------------------------------------- |\n| `viewport` | [`default`](Frontend_Game_Viewport.default.md) |\n\n#### Returns\n\n`Map`<`PlanetLevel`, `Radii`\\>\n\n---\n\n### getPlanetsAndChunks\n\n▸ **getPlanetsAndChunks**(): `Object`\n\n#### Returns\n\n`Object`\n\n| Name            | Type                                     |\n| :-------------- | :--------------------------------------- |\n| `cachedPlanets` | `Map`<`LocationId`, `PlanetRenderInfo`\\> |\n| `chunks`        | `Set`<`Chunk`\\>                          |\n\n---\n\n### getVisiblePlanetLevels\n\n▸ `Private` **getVisiblePlanetLevels**(`viewport`): `number`[]\n\nReturns a list of planet levels which, when rendered, would result in a planet that has a size\nlarger than one pixel.\n\n#### Parameters\n\n| Name       | Type                                           |\n| :--------- | :--------------------------------------------- |\n| `viewport` | [`default`](Frontend_Game_Viewport.default.md) |\n\n#### Returns\n\n`number`[]\n\n---\n\n### loadPlanetMessages\n\n▸ `Private` **loadPlanetMessages**(): `Promise`<`void`\\>\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### recalculateViewportChunks\n\n▸ `Private` **recalculateViewportChunks**(`viewport`): `void`\n\n#### Parameters\n\n| Name       | Type                                           |\n| :--------- | :--------------------------------------------- |\n| `viewport` | [`default`](Frontend_Game_Viewport.default.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### recalculateViewportPlanets\n\n▸ `Private` **recalculateViewportPlanets**(`viewport`): `void`\n\n#### Parameters\n\n| Name       | Type                                           |\n| :--------- | :--------------------------------------------- |\n| `viewport` | [`default`](Frontend_Game_Viewport.default.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### replacePlanets\n\n▸ `Private` **replacePlanets**(`newPlanetsInViewport`): `void`\n\n#### Parameters\n\n| Name                   | Type                |\n| :--------------------- | :------------------ |\n| `newPlanetsInViewport` | `LocatablePlanet`[] |\n\n#### Returns\n\n`void`\n\n---\n\n### startRefreshing\n\n▸ **startRefreshing**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### updateLocationsAndChunks\n\n▸ `Private` **updateLocationsAndChunks**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md",
    "content": "# Class: HomePlanetMinerChunkStore\n\n[Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).HomePlanetMinerChunkStore\n\n## Implements\n\n- [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#constructor)\n\n### Properties\n\n- [initPerlinMax](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#initperlinmax)\n- [initPerlinMin](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#initperlinmin)\n- [minedChunkKeys](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#minedchunkkeys)\n- [perlinOptions](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#perlinoptions)\n\n### Methods\n\n- [addChunk](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#addchunk)\n- [hasMinedChunk](Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md#hasminedchunk)\n\n## Constructors\n\n### constructor\n\n• **new HomePlanetMinerChunkStore**(`initPerlinMin`, `initPerlinMax`, `hashConfig`)\n\n#### Parameters\n\n| Name            | Type                                                              |\n| :-------------- | :---------------------------------------------------------------- |\n| `initPerlinMin` | `number`                                                          |\n| `initPerlinMax` | `number`                                                          |\n| `hashConfig`    | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig) |\n\n## Properties\n\n### initPerlinMax\n\n• `Private` **initPerlinMax**: `number`\n\n---\n\n### initPerlinMin\n\n• `Private` **initPerlinMin**: `number`\n\n---\n\n### minedChunkKeys\n\n• `Private` **minedChunkKeys**: `Set`<`string`\\>\n\n---\n\n### perlinOptions\n\n• `Private` **perlinOptions**: `PerlinConfig`\n\n## Methods\n\n### addChunk\n\n▸ **addChunk**(`exploredChunk`): `void`\n\n#### Parameters\n\n| Name            | Type    |\n| :-------------- | :------ |\n| `exploredChunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### hasMinedChunk\n\n▸ **hasMinedChunk**(`chunkFootprint`): `boolean`\n\n#### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkFootprint` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n\n#### Implementation of\n\n[ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md).[hasMinedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk)\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MinerManager.default.md",
    "content": "# Class: default\n\n[Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MinerManager.default.md#constructor)\n\n### Properties\n\n- [cores](Backend_Miner_MinerManager.default.md#cores)\n- [currentJobId](Backend_Miner_MinerManager.default.md#currentjobid)\n- [exploringChunk](Backend_Miner_MinerManager.default.md#exploringchunk)\n- [exploringChunkStart](Backend_Miner_MinerManager.default.md#exploringchunkstart)\n- [hashConfig](Backend_Miner_MinerManager.default.md#hashconfig)\n- [isExploring](Backend_Miner_MinerManager.default.md#isexploring)\n- [minedChunksStore](Backend_Miner_MinerManager.default.md#minedchunksstore)\n- [minersComplete](Backend_Miner_MinerManager.default.md#minerscomplete)\n- [miningPattern](Backend_Miner_MinerManager.default.md#miningpattern)\n- [perlinOptions](Backend_Miner_MinerManager.default.md#perlinoptions)\n- [planetRarity](Backend_Miner_MinerManager.default.md#planetrarity)\n- [useMockHash](Backend_Miner_MinerManager.default.md#usemockhash)\n- [workerFactory](Backend_Miner_MinerManager.default.md#workerfactory)\n- [workers](Backend_Miner_MinerManager.default.md#workers)\n- [worldRadius](Backend_Miner_MinerManager.default.md#worldradius)\n\n### Methods\n\n- [chunkKeyToLocation](Backend_Miner_MinerManager.default.md#chunkkeytolocation)\n- [chunkLocationToKey](Backend_Miner_MinerManager.default.md#chunklocationtokey)\n- [destroy](Backend_Miner_MinerManager.default.md#destroy)\n- [exploreNext](Backend_Miner_MinerManager.default.md#explorenext)\n- [getCurrentlyExploringChunk](Backend_Miner_MinerManager.default.md#getcurrentlyexploringchunk)\n- [getMiningPattern](Backend_Miner_MinerManager.default.md#getminingpattern)\n- [initWorker](Backend_Miner_MinerManager.default.md#initworker)\n- [isMining](Backend_Miner_MinerManager.default.md#ismining)\n- [isValidExploreTarget](Backend_Miner_MinerManager.default.md#isvalidexploretarget)\n- [nextValidExploreTarget](Backend_Miner_MinerManager.default.md#nextvalidexploretarget)\n- [onDiscovered](Backend_Miner_MinerManager.default.md#ondiscovered)\n- [sendMessageToWorkers](Backend_Miner_MinerManager.default.md#sendmessagetoworkers)\n- [setCores](Backend_Miner_MinerManager.default.md#setcores)\n- [setMiningPattern](Backend_Miner_MinerManager.default.md#setminingpattern)\n- [setRadius](Backend_Miner_MinerManager.default.md#setradius)\n- [startExplore](Backend_Miner_MinerManager.default.md#startexplore)\n- [stopExplore](Backend_Miner_MinerManager.default.md#stopexplore)\n- [create](Backend_Miner_MinerManager.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`minedChunksStore`, `miningPattern`, `worldRadius`, `planetRarity`, `hashConfig`, `useMockHash`, `workerFactory`)\n\n#### Parameters\n\n| Name               | Type                                                                             |\n| :----------------- | :------------------------------------------------------------------------------- |\n| `minedChunksStore` | [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) |\n| `miningPattern`    | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)   |\n| `worldRadius`      | `number`                                                                         |\n| `planetRarity`     | `number`                                                                         |\n| `hashConfig`       | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)                |\n| `useMockHash`      | `boolean`                                                                        |\n| `workerFactory`    | [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory)        |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### cores\n\n• `Private` **cores**: `number` = `1`\n\n---\n\n### currentJobId\n\n• `Private` **currentJobId**: `number` = `0`\n\n---\n\n### exploringChunk\n\n• `Private` **exploringChunk**: `Object` = `{}`\n\n#### Index signature\n\n▪ [chunkKey: `string`]: `Chunk`\n\n---\n\n### exploringChunkStart\n\n• `Private` **exploringChunkStart**: `Object` = `{}`\n\n#### Index signature\n\n▪ [chunkKey: `string`]: `number`\n\n---\n\n### hashConfig\n\n• `Private` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\n---\n\n### isExploring\n\n• `Private` **isExploring**: `boolean` = `false`\n\n---\n\n### minedChunksStore\n\n• `Private` `Readonly` **minedChunksStore**: [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md)\n\n---\n\n### minersComplete\n\n• `Private` **minersComplete**: `Object` = `{}`\n\n#### Index signature\n\n▪ [chunkKey: `string`]: `number`\n\n---\n\n### miningPattern\n\n• `Private` **miningPattern**: [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n---\n\n### perlinOptions\n\n• `Private` **perlinOptions**: `PerlinConfig`\n\n---\n\n### planetRarity\n\n• `Private` `Readonly` **planetRarity**: `number`\n\n---\n\n### useMockHash\n\n• `Private` **useMockHash**: `boolean`\n\n---\n\n### workerFactory\n\n• `Private` **workerFactory**: [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory)\n\n---\n\n### workers\n\n• `Private` **workers**: `Worker`[]\n\n---\n\n### worldRadius\n\n• `Private` **worldRadius**: `number`\n\n## Methods\n\n### chunkKeyToLocation\n\n▸ `Private` **chunkKeyToLocation**(`chunkKey`): `undefined` \\| [`Rectangle`, `number`]\n\n#### Parameters\n\n| Name       | Type     |\n| :--------- | :------- |\n| `chunkKey` | `string` |\n\n#### Returns\n\n`undefined` \\| [`Rectangle`, `number`]\n\n---\n\n### chunkLocationToKey\n\n▸ `Private` **chunkLocationToKey**(`chunkLocation`, `jobId`): `string`\n\n#### Parameters\n\n| Name            | Type        |\n| :-------------- | :---------- |\n| `chunkLocation` | `Rectangle` |\n| `jobId`         | `number`    |\n\n#### Returns\n\n`string`\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### exploreNext\n\n▸ `Private` **exploreNext**(`fromChunk`, `jobId`): `void`\n\n#### Parameters\n\n| Name        | Type        |\n| :---------- | :---------- |\n| `fromChunk` | `Rectangle` |\n| `jobId`     | `number`    |\n\n#### Returns\n\n`void`\n\n---\n\n### getCurrentlyExploringChunk\n\n▸ **getCurrentlyExploringChunk**(): `undefined` \\| `Rectangle`\n\n#### Returns\n\n`undefined` \\| `Rectangle`\n\n---\n\n### getMiningPattern\n\n▸ **getMiningPattern**(): [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n#### Returns\n\n[`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n---\n\n### initWorker\n\n▸ `Private` **initWorker**(`index`): `void`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `index` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### isMining\n\n▸ **isMining**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isValidExploreTarget\n\n▸ `Private` **isValidExploreTarget**(`chunkLocation`): `boolean`\n\n#### Parameters\n\n| Name            | Type        |\n| :-------------- | :---------- |\n| `chunkLocation` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### nextValidExploreTarget\n\n▸ `Private` **nextValidExploreTarget**(`chunkLocation`, `jobId`): `Promise`<`undefined` \\| `Rectangle`\\>\n\n#### Parameters\n\n| Name            | Type        |\n| :-------------- | :---------- |\n| `chunkLocation` | `Rectangle` |\n| `jobId`         | `number`    |\n\n#### Returns\n\n`Promise`<`undefined` \\| `Rectangle`\\>\n\n---\n\n### onDiscovered\n\n▸ `Private` **onDiscovered**(`exploredChunk`, `jobId`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name            | Type     |\n| :-------------- | :------- |\n| `exploredChunk` | `Chunk`  |\n| `jobId`         | `number` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### sendMessageToWorkers\n\n▸ `Private` **sendMessageToWorkers**(`chunkToExplore`, `jobId`): `void`\n\n#### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkToExplore` | `Rectangle` |\n| `jobId`          | `number`    |\n\n#### Returns\n\n`void`\n\n---\n\n### setCores\n\n▸ **setCores**(`nCores`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `nCores` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setMiningPattern\n\n▸ **setMiningPattern**(`pattern`): `void`\n\n#### Parameters\n\n| Name      | Type                                                                           |\n| :-------- | :----------------------------------------------------------------------------- |\n| `pattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### setRadius\n\n▸ **setRadius**(`radius`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `radius` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### startExplore\n\n▸ **startExplore**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### stopExplore\n\n▸ **stopExplore**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### create\n\n▸ `Static` **create**(`chunkStore`, `miningPattern`, `worldRadius`, `planetRarity`, `hashConfig`, `useMockHash?`, `workerFactory?`): [`default`](Backend_Miner_MinerManager.default.md)\n\n#### Parameters\n\n| Name            | Type                                                                             | Default value   |\n| :-------------- | :------------------------------------------------------------------------------- | :-------------- |\n| `chunkStore`    | [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md) | `undefined`     |\n| `miningPattern` | [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)   | `undefined`     |\n| `worldRadius`   | `number`                                                                         | `undefined`     |\n| `planetRarity`  | `number`                                                                         | `undefined`     |\n| `hashConfig`    | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)                | `undefined`     |\n| `useMockHash`   | `boolean`                                                                        | `false`         |\n| `workerFactory` | [`workerFactory`](../modules/Backend_Miner_MinerManager.md#workerfactory)        | `defaultWorker` |\n\n#### Returns\n\n[`default`](Backend_Miner_MinerManager.default.md)\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MiningPatterns.SpiralPattern.md",
    "content": "# Class: SpiralPattern\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).SpiralPattern\n\n## Implements\n\n- [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MiningPatterns.SpiralPattern.md#constructor)\n\n### Properties\n\n- [chunkSideLength](Backend_Miner_MiningPatterns.SpiralPattern.md#chunksidelength)\n- [fromChunk](Backend_Miner_MiningPatterns.SpiralPattern.md#fromchunk)\n- [type](Backend_Miner_MiningPatterns.SpiralPattern.md#type)\n\n### Methods\n\n- [nextChunk](Backend_Miner_MiningPatterns.SpiralPattern.md#nextchunk)\n\n## Constructors\n\n### constructor\n\n• **new SpiralPattern**(`center`, `chunkSize`)\n\n#### Parameters\n\n| Name        | Type          |\n| :---------- | :------------ |\n| `center`    | `WorldCoords` |\n| `chunkSize` | `number`      |\n\n## Properties\n\n### chunkSideLength\n\n• **chunkSideLength**: `number`\n\n---\n\n### fromChunk\n\n• **fromChunk**: `Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk)\n\n---\n\n### type\n\n• **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.Spiral`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type)\n\n## Methods\n\n### nextChunk\n\n▸ **nextChunk**(`chunk`): `Rectangle`\n\n#### Parameters\n\n| Name    | Type        |\n| :------ | :---------- |\n| `chunk` | `Rectangle` |\n\n#### Returns\n\n`Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk)\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md",
    "content": "# Class: SwissCheesePattern\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).SwissCheesePattern\n\n## Implements\n\n- [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MiningPatterns.SwissCheesePattern.md#constructor)\n\n### Properties\n\n- [chunkSideLength](Backend_Miner_MiningPatterns.SwissCheesePattern.md#chunksidelength)\n- [fromChunk](Backend_Miner_MiningPatterns.SwissCheesePattern.md#fromchunk)\n- [type](Backend_Miner_MiningPatterns.SwissCheesePattern.md#type)\n\n### Methods\n\n- [nextChunk](Backend_Miner_MiningPatterns.SwissCheesePattern.md#nextchunk)\n\n## Constructors\n\n### constructor\n\n• **new SwissCheesePattern**(`center`, `chunkSize`)\n\n#### Parameters\n\n| Name        | Type          |\n| :---------- | :------------ |\n| `center`    | `WorldCoords` |\n| `chunkSize` | `number`      |\n\n## Properties\n\n### chunkSideLength\n\n• **chunkSideLength**: `number`\n\n---\n\n### fromChunk\n\n• **fromChunk**: `Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk)\n\n---\n\n### type\n\n• **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.SwissCheese`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type)\n\n## Methods\n\n### nextChunk\n\n▸ **nextChunk**(`chunk`): `Rectangle`\n\n#### Parameters\n\n| Name    | Type        |\n| :------ | :---------- |\n| `chunk` | `Rectangle` |\n\n#### Returns\n\n`Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk)\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md",
    "content": "# Class: TowardsCenterPattern\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).TowardsCenterPattern\n\n## Implements\n\n- [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#constructor)\n\n### Properties\n\n- [chunkSideLength](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#chunksidelength)\n- [fromChunk](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#fromchunk)\n- [maxWidth](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#maxwidth)\n- [tipX](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#tipx)\n- [tipY](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#tipy)\n- [type](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#type)\n\n### Methods\n\n- [nextChunk](Backend_Miner_MiningPatterns.TowardsCenterPattern.md#nextchunk)\n\n## Constructors\n\n### constructor\n\n• **new TowardsCenterPattern**(`center`, `chunkSize`)\n\n#### Parameters\n\n| Name        | Type          |\n| :---------- | :------------ |\n| `center`    | `WorldCoords` |\n| `chunkSize` | `number`      |\n\n## Properties\n\n### chunkSideLength\n\n• **chunkSideLength**: `number`\n\n---\n\n### fromChunk\n\n• **fromChunk**: `Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk)\n\n---\n\n### maxWidth\n\n• `Private` **maxWidth**: `number` = `1600`\n\n---\n\n### tipX\n\n• `Private` **tipX**: `number`\n\n---\n\n### tipY\n\n• `Private` **tipY**: `number`\n\n---\n\n### type\n\n• **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.TowardsCenter`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type)\n\n## Methods\n\n### nextChunk\n\n▸ **nextChunk**(`chunk`): `Rectangle`\n\n#### Parameters\n\n| Name    | Type        |\n| :------ | :---------- |\n| `chunk` | `Rectangle` |\n\n#### Returns\n\n`Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk)\n"
  },
  {
    "path": "docs/classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md",
    "content": "# Class: TowardsCenterPatternV2\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).TowardsCenterPatternV2\n\n## Implements\n\n- [`MiningPattern`](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#constructor)\n\n### Properties\n\n- [chunkSideLength](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#chunksidelength)\n- [fromChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#fromchunk)\n- [rowRadius](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#rowradius)\n- [slopeToCenter](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#slopetocenter)\n- [type](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#type)\n- [yDominant](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#ydominant)\n\n### Methods\n\n- [nextChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#nextchunk)\n- [toChunk](Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md#tochunk)\n\n## Constructors\n\n### constructor\n\n• **new TowardsCenterPatternV2**(`center`, `chunkSize`)\n\n#### Parameters\n\n| Name        | Type          |\n| :---------- | :------------ |\n| `center`    | `WorldCoords` |\n| `chunkSize` | `number`      |\n\n## Properties\n\n### chunkSideLength\n\n• **chunkSideLength**: `number`\n\n---\n\n### fromChunk\n\n• **fromChunk**: `Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[fromChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk)\n\n---\n\n### rowRadius\n\n• `Private` **rowRadius**: `number`\n\n---\n\n### slopeToCenter\n\n• `Private` **slopeToCenter**: `number`\n\n---\n\n### type\n\n• **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md) = `MiningPatternType.TowardsCenterV2`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[type](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#type)\n\n---\n\n### yDominant\n\n• `Private` **yDominant**: `boolean`\n\n## Methods\n\n### nextChunk\n\n▸ **nextChunk**(`chunk`): `Rectangle`\n\n#### Parameters\n\n| Name    | Type        |\n| :------ | :---------- |\n| `chunk` | `Rectangle` |\n\n#### Returns\n\n`Rectangle`\n\n#### Implementation of\n\n[MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md).[nextChunk](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk)\n\n---\n\n### toChunk\n\n▸ **toChunk**(`coord`): `number`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `coord` | `number` |\n\n#### Returns\n\n`number`\n"
  },
  {
    "path": "docs/classes/Backend_Network_EventLogger.EventLogger.md",
    "content": "# Class: EventLogger\n\n[Backend/Network/EventLogger](../modules/Backend_Network_EventLogger.md).EventLogger\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Network_EventLogger.EventLogger.md#constructor)\n\n### Methods\n\n- [logEvent](Backend_Network_EventLogger.EventLogger.md#logevent)\n- [augmentEvent](Backend_Network_EventLogger.EventLogger.md#augmentevent)\n\n## Constructors\n\n### constructor\n\n• **new EventLogger**()\n\n## Methods\n\n### logEvent\n\n▸ **logEvent**(`eventType`, `event`): `void`\n\n#### Parameters\n\n| Name        | Type                                                             |\n| :---------- | :--------------------------------------------------------------- |\n| `eventType` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) |\n| `event`     | `unknown`                                                        |\n\n#### Returns\n\n`void`\n\n---\n\n### augmentEvent\n\n▸ `Static` `Private` **augmentEvent**(`event`, `eventType`): `Object`\n\n#### Parameters\n\n| Name        | Type                                                             |\n| :---------- | :--------------------------------------------------------------- |\n| `event`     | `unknown`                                                        |\n| `eventType` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) |\n\n#### Returns\n\n`Object`\n\n| Name            | Type                                                             |\n| :-------------- | :--------------------------------------------------------------- |\n| `df_event_type` | [`EventType`](../enums/Backend_Network_EventLogger.EventType.md) |\n"
  },
  {
    "path": "docs/classes/Backend_Storage_PersistentChunkStore.default.md",
    "content": "# Class: default\n\n[Backend/Storage/PersistentChunkStore](../modules/Backend_Storage_PersistentChunkStore.md).default\n\n## Implements\n\n- [`ChunkStore`](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Storage_PersistentChunkStore.default.md#constructor)\n\n### Properties\n\n- [account](Backend_Storage_PersistentChunkStore.default.md#account)\n- [chunkMap](Backend_Storage_PersistentChunkStore.default.md#chunkmap)\n- [confirmedTxHashes](Backend_Storage_PersistentChunkStore.default.md#confirmedtxhashes)\n- [contractAddress](Backend_Storage_PersistentChunkStore.default.md#contractaddress)\n- [db](Backend_Storage_PersistentChunkStore.default.md#db)\n- [diagnosticUpdater](Backend_Storage_PersistentChunkStore.default.md#diagnosticupdater)\n- [nUpdatesLastTwoMins](Backend_Storage_PersistentChunkStore.default.md#nupdateslasttwomins)\n- [queuedChunkWrites](Backend_Storage_PersistentChunkStore.default.md#queuedchunkwrites)\n- [throttledSaveChunkCacheToDisk](Backend_Storage_PersistentChunkStore.default.md#throttledsavechunkcachetodisk)\n\n### Methods\n\n- [addChunk](Backend_Storage_PersistentChunkStore.default.md#addchunk)\n- [addHomeLocation](Backend_Storage_PersistentChunkStore.default.md#addhomelocation)\n- [allChunks](Backend_Storage_PersistentChunkStore.default.md#allchunks)\n- [bulkSetKeyInCollection](Backend_Storage_PersistentChunkStore.default.md#bulksetkeyincollection)\n- [confirmHomeLocation](Backend_Storage_PersistentChunkStore.default.md#confirmhomelocation)\n- [destroy](Backend_Storage_PersistentChunkStore.default.md#destroy)\n- [getChunkByFootprint](Backend_Storage_PersistentChunkStore.default.md#getchunkbyfootprint)\n- [getChunkById](Backend_Storage_PersistentChunkStore.default.md#getchunkbyid)\n- [getHomeLocations](Backend_Storage_PersistentChunkStore.default.md#gethomelocations)\n- [getKey](Backend_Storage_PersistentChunkStore.default.md#getkey)\n- [getMinedSubChunks](Backend_Storage_PersistentChunkStore.default.md#getminedsubchunks)\n- [getSavedClaimedCoords](Backend_Storage_PersistentChunkStore.default.md#getsavedclaimedcoords)\n- [getSavedRevealedCoords](Backend_Storage_PersistentChunkStore.default.md#getsavedrevealedcoords)\n- [getSavedTouchedPlanetIds](Backend_Storage_PersistentChunkStore.default.md#getsavedtouchedplanetids)\n- [getUnconfirmedSubmittedEthTxs](Backend_Storage_PersistentChunkStore.default.md#getunconfirmedsubmittedethtxs)\n- [hasMinedChunk](Backend_Storage_PersistentChunkStore.default.md#hasminedchunk)\n- [loadChunks](Backend_Storage_PersistentChunkStore.default.md#loadchunks)\n- [loadModalPositions](Backend_Storage_PersistentChunkStore.default.md#loadmodalpositions)\n- [loadPlugins](Backend_Storage_PersistentChunkStore.default.md#loadplugins)\n- [onEthTxComplete](Backend_Storage_PersistentChunkStore.default.md#onethtxcomplete)\n- [onEthTxSubmit](Backend_Storage_PersistentChunkStore.default.md#onethtxsubmit)\n- [persistQueuedChunks](Backend_Storage_PersistentChunkStore.default.md#persistqueuedchunks)\n- [recomputeSaveThrottleAfterUpdate](Backend_Storage_PersistentChunkStore.default.md#recomputesavethrottleafterupdate)\n- [removeKey](Backend_Storage_PersistentChunkStore.default.md#removekey)\n- [saveClaimedCoords](Backend_Storage_PersistentChunkStore.default.md#saveclaimedcoords)\n- [saveModalPositions](Backend_Storage_PersistentChunkStore.default.md#savemodalpositions)\n- [savePlugins](Backend_Storage_PersistentChunkStore.default.md#saveplugins)\n- [saveRevealedCoords](Backend_Storage_PersistentChunkStore.default.md#saverevealedcoords)\n- [saveTouchedPlanetIds](Backend_Storage_PersistentChunkStore.default.md#savetouchedplanetids)\n- [setDiagnosticUpdater](Backend_Storage_PersistentChunkStore.default.md#setdiagnosticupdater)\n- [setKey](Backend_Storage_PersistentChunkStore.default.md#setkey)\n- [create](Backend_Storage_PersistentChunkStore.default.md#create)\n\n## Constructors\n\n### constructor\n\n• **new default**(`__namedParameters`)\n\n#### Parameters\n\n| Name                | Type                         |\n| :------------------ | :--------------------------- |\n| `__namedParameters` | `PersistentChunkStoreConfig` |\n\n## Properties\n\n### account\n\n• `Private` **account**: `EthAddress`\n\n---\n\n### chunkMap\n\n• `Private` **chunkMap**: `Map`<[`ChunkId`](../modules/types_darkforest_api_ChunkStoreTypes.md#chunkid), `Chunk`\\>\n\n---\n\n### confirmedTxHashes\n\n• `Private` **confirmedTxHashes**: `Set`<`string`\\>\n\n---\n\n### contractAddress\n\n• `Private` **contractAddress**: `EthAddress`\n\n---\n\n### db\n\n• `Private` **db**: `IDBPDatabase`<`unknown`\\>\n\n---\n\n### diagnosticUpdater\n\n• `Private` `Optional` **diagnosticUpdater**: `DiagnosticUpdater`\n\n---\n\n### nUpdatesLastTwoMins\n\n• `Private` **nUpdatesLastTwoMins**: `number` = `0`\n\n---\n\n### queuedChunkWrites\n\n• `Private` **queuedChunkWrites**: `DBTx`[]\n\n---\n\n### throttledSaveChunkCacheToDisk\n\n• `Private` **throttledSaveChunkCacheToDisk**: `DebouncedFunc`<() => `Promise`<`void`\\>\\>\n\n## Methods\n\n### addChunk\n\n▸ **addChunk**(`chunk`, `persistChunk?`): `void`\n\nWhen a chunk is mined, or a chunk is imported via map import, or a chunk is loaded from\npersistent storage for the first time, we need to add this chunk to the game. This function\nallows you to add a new chunk to the game, and optionally persist that chunk. The reason you\nmight not want to persist the chunk is if you are sure that you got it from persistent storage.\ni.e. it already exists in persistent storage.\n\n#### Parameters\n\n| Name           | Type      | Default value |\n| :------------- | :-------- | :------------ |\n| `chunk`        | `Chunk`   | `undefined`   |\n| `persistChunk` | `boolean` | `true`        |\n\n#### Returns\n\n`void`\n\n---\n\n### addHomeLocation\n\n▸ **addHomeLocation**(`location`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type            |\n| :--------- | :-------------- |\n| `location` | `WorldLocation` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### allChunks\n\n▸ **allChunks**(): `Iterable`<`Chunk`\\>\n\n#### Returns\n\n`Iterable`<`Chunk`\\>\n\n---\n\n### bulkSetKeyInCollection\n\n▸ `Private` **bulkSetKeyInCollection**(`updateChunkTxs`, `collection`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name             | Type          |\n| :--------------- | :------------ |\n| `updateChunkTxs` | `DBTx`[]      |\n| `collection`     | `ObjectStore` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### confirmHomeLocation\n\n▸ **confirmHomeLocation**(`location`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type            |\n| :--------- | :-------------- |\n| `location` | `WorldLocation` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getChunkByFootprint\n\n▸ **getChunkByFootprint**(`chunkLoc`): `undefined` \\| `Chunk`\n\nReturns the explored chunk data for the given rectangle if that chunk has been mined. If this\nchunk is entirely contained within another bigger chunk that has been mined, return that chunk.\n`chunkLoc` is an aligned square, as defined in ChunkUtils.ts in the `getSiblingLocations`\nfunction.\n\n#### Parameters\n\n| Name       | Type        |\n| :--------- | :---------- |\n| `chunkLoc` | `Rectangle` |\n\n#### Returns\n\n`undefined` \\| `Chunk`\n\n---\n\n### getChunkById\n\n▸ `Private` **getChunkById**(`chunkId`): `undefined` \\| `Chunk`\n\n#### Parameters\n\n| Name      | Type                                                                    |\n| :-------- | :---------------------------------------------------------------------- |\n| `chunkId` | [`ChunkId`](../modules/types_darkforest_api_ChunkStoreTypes.md#chunkid) |\n\n#### Returns\n\n`undefined` \\| `Chunk`\n\n---\n\n### getHomeLocations\n\n▸ **getHomeLocations**(): `Promise`<`WorldLocation`[]\\>\n\nwe keep a list rather than a single location, since client/contract can\noften go out of sync on initialization - if client thinks that init\nfailed but is wrong, it will prompt user to initialize with new home coords,\nwhich bricks the user's account.\n\n#### Returns\n\n`Promise`<`WorldLocation`[]\\>\n\n---\n\n### getKey\n\n▸ `Private` **getKey**(`key`, `objStore?`): `Promise`<`undefined` \\| `string`\\>\n\nImportant! This sets the key in indexed db per account and per contract. This means the same\nclient can connect to multiple different dark forest contracts, with multiple different\naccounts, and the persistent storage will not overwrite data that is not relevant for the\ncurrent configuration of the client.\n\n#### Parameters\n\n| Name       | Type          | Default value         |\n| :--------- | :------------ | :-------------------- |\n| `key`      | `string`      | `undefined`           |\n| `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` |\n\n#### Returns\n\n`Promise`<`undefined` \\| `string`\\>\n\n---\n\n### getMinedSubChunks\n\n▸ `Private` **getMinedSubChunks**(`chunk`): `Chunk`[]\n\nReturns all the mined chunks with smaller sidelength strictly contained in the chunk.\n\nTODO: move this into ChunkUtils, and also make use of it, the way that it is currently used, in\nthe function named `addToChunkMap`.\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`Chunk`[]\n\n---\n\n### getSavedClaimedCoords\n\n▸ **getSavedClaimedCoords**(): `Promise`<`ClaimedCoords`[]\\>\n\n#### Returns\n\n`Promise`<`ClaimedCoords`[]\\>\n\n---\n\n### getSavedRevealedCoords\n\n▸ **getSavedRevealedCoords**(): `Promise`<`RevealedCoords`[]\\>\n\n#### Returns\n\n`Promise`<`RevealedCoords`[]\\>\n\n---\n\n### getSavedTouchedPlanetIds\n\n▸ **getSavedTouchedPlanetIds**(): `Promise`<`LocationId`[]\\>\n\n#### Returns\n\n`Promise`<`LocationId`[]\\>\n\n---\n\n### getUnconfirmedSubmittedEthTxs\n\n▸ **getUnconfirmedSubmittedEthTxs**(): `Promise`<`PersistedTransaction`<`TxIntent`\\>[]\\>\n\n#### Returns\n\n`Promise`<`PersistedTransaction`<`TxIntent`\\>[]\\>\n\n---\n\n### hasMinedChunk\n\n▸ **hasMinedChunk**(`chunkLoc`): `boolean`\n\n#### Parameters\n\n| Name       | Type        |\n| :--------- | :---------- |\n| `chunkLoc` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n\n#### Implementation of\n\n[ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md).[hasMinedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk)\n\n---\n\n### loadChunks\n\n▸ `Private` **loadChunks**(): `Promise`<`void`\\>\n\nThis function loads all chunks persisted in the user's storage into the game.\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### loadModalPositions\n\n▸ **loadModalPositions**(): `Promise`<`Map`<`ModalId`, `ModalPosition`\\>\\>\n\n#### Returns\n\n`Promise`<`Map`<`ModalId`, `ModalPosition`\\>\\>\n\n---\n\n### loadPlugins\n\n▸ **loadPlugins**(): `Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\\>\n\n#### Returns\n\n`Promise`<[`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[]\\>\n\n---\n\n### onEthTxComplete\n\n▸ **onEthTxComplete**(`txHash`): `Promise`<`void`\\>\n\nPartner function to {@link PersistentChunkStore#onEthTxSubmit}\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `txHash` | `string` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### onEthTxSubmit\n\n▸ **onEthTxSubmit**(`tx`): `Promise`<`void`\\>\n\nWhenever a transaction is submitted, it is persisted. When the transaction either fails or\nsucceeds, it is un-persisted. The reason we persist submitted transactions is to be able to\nwait for them upon a fresh start of the game if you close the game before a transaction\nconfirms.\n\n#### Parameters\n\n| Name | Type                       |\n| :--- | :------------------------- |\n| `tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### persistQueuedChunks\n\n▸ `Private` **persistQueuedChunks**(): `Promise`<`void`\\>\n\nRather than saving a chunk immediately after it's mined, we queue up new chunks, and\nperiodically save them. This function gets all of the queued new chunks, and persists them to\nindexed db.\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### recomputeSaveThrottleAfterUpdate\n\n▸ `Private` **recomputeSaveThrottleAfterUpdate**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### removeKey\n\n▸ `Private` **removeKey**(`key`, `objStore?`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name       | Type          | Default value         |\n| :--------- | :------------ | :-------------------- |\n| `key`      | `string`      | `undefined`           |\n| `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### saveClaimedCoords\n\n▸ **saveClaimedCoords**(`claimedCoordTupps`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name                | Type              |\n| :------------------ | :---------------- |\n| `claimedCoordTupps` | `ClaimedCoords`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### saveModalPositions\n\n▸ **saveModalPositions**(`modalPositions`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name             | Type                               |\n| :--------------- | :--------------------------------- |\n| `modalPositions` | `Map`<`ModalId`, `ModalPosition`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### savePlugins\n\n▸ **savePlugins**(`plugins`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type                                                                                       |\n| :-------- | :----------------------------------------------------------------------------------------- |\n| `plugins` | [`SerializedPlugin`](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### saveRevealedCoords\n\n▸ **saveRevealedCoords**(`revealedCoordTups`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name                | Type               |\n| :------------------ | :----------------- |\n| `revealedCoordTups` | `RevealedCoords`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### saveTouchedPlanetIds\n\n▸ **saveTouchedPlanetIds**(`ids`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name  | Type           |\n| :---- | :------------- |\n| `ids` | `LocationId`[] |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### setDiagnosticUpdater\n\n▸ **setDiagnosticUpdater**(`diagnosticUpdater?`): `void`\n\n#### Parameters\n\n| Name                 | Type                |\n| :------------------- | :------------------ |\n| `diagnosticUpdater?` | `DiagnosticUpdater` |\n\n#### Returns\n\n`void`\n\n---\n\n### setKey\n\n▸ `Private` **setKey**(`key`, `value`, `objStore?`): `Promise`<`void`\\>\n\nImportant! This sets the key in indexed db per account and per contract. This means the same\nclient can connect to multiple different dark forest contracts, with multiple different\naccounts, and the persistent storage will not overwrite data that is not relevant for the\ncurrent configuration of the client.\n\n#### Parameters\n\n| Name       | Type          | Default value         |\n| :--------- | :------------ | :-------------------- |\n| `key`      | `string`      | `undefined`           |\n| `value`    | `string`      | `undefined`           |\n| `objStore` | `ObjectStore` | `ObjectStore.DEFAULT` |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### create\n\n▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_Storage_PersistentChunkStore.default.md)\\>\n\nNOTE! if you're creating a new object store, it will not be _added_ to existing dark forest\naccounts. This creation code runs once per account. Therefore, if you're adding a new object\nstore, and need to test it out, you must either clear the indexed db databse for this account,\nor create a brand new account.\n\n#### Parameters\n\n| Name                | Type                                          |\n| :------------------ | :-------------------------------------------- |\n| `__namedParameters` | `Omit`<`PersistentChunkStoreConfig`, `\"db\"`\\> |\n\n#### Returns\n\n`Promise`<[`default`](Backend_Storage_PersistentChunkStore.default.md)\\>\n"
  },
  {
    "path": "docs/classes/Backend_Storage_ReaderDataStore.default.md",
    "content": "# Class: default\n\n[Backend/Storage/ReaderDataStore](../modules/Backend_Storage_ReaderDataStore.md).default\n\nA data store that allows you to retrieve data from the contract,\nand combine it with data that is stored in this browser about a\nparticular user.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Storage_ReaderDataStore.default.md#constructor)\n\n### Properties\n\n- [addressTwitterMap](Backend_Storage_ReaderDataStore.default.md#addresstwittermap)\n- [contractConstants](Backend_Storage_ReaderDataStore.default.md#contractconstants)\n- [contractsAPI](Backend_Storage_ReaderDataStore.default.md#contractsapi)\n- [persistentChunkStore](Backend_Storage_ReaderDataStore.default.md#persistentchunkstore)\n- [viewer](Backend_Storage_ReaderDataStore.default.md#viewer)\n\n### Methods\n\n- [destroy](Backend_Storage_ReaderDataStore.default.md#destroy)\n- [getBiome](Backend_Storage_ReaderDataStore.default.md#getbiome)\n- [getTwitter](Backend_Storage_ReaderDataStore.default.md#gettwitter)\n- [getViewer](Backend_Storage_ReaderDataStore.default.md#getviewer)\n- [loadArtifactFromContract](Backend_Storage_ReaderDataStore.default.md#loadartifactfromcontract)\n- [loadPlanetFromContract](Backend_Storage_ReaderDataStore.default.md#loadplanetfromcontract)\n- [setPlanetLocationIfKnown](Backend_Storage_ReaderDataStore.default.md#setplanetlocationifknown)\n- [spaceTypeFromPerlin](Backend_Storage_ReaderDataStore.default.md#spacetypefromperlin)\n- [create](Backend_Storage_ReaderDataStore.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`__namedParameters`)\n\n#### Parameters\n\n| Name                | Type                    |\n| :------------------ | :---------------------- |\n| `__namedParameters` | `ReaderDataStoreConfig` |\n\n## Properties\n\n### addressTwitterMap\n\n• `Private` `Readonly` **addressTwitterMap**: [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\n\n---\n\n### contractConstants\n\n• `Private` `Readonly` **contractConstants**: [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n---\n\n### contractsAPI\n\n• `Private` `Readonly` **contractsAPI**: [`ContractsAPI`](Backend_GameLogic_ContractsAPI.ContractsAPI.md)\n\n---\n\n### persistentChunkStore\n\n• `Private` `Readonly` **persistentChunkStore**: `undefined` \\| [`default`](Backend_Storage_PersistentChunkStore.default.md)\n\n---\n\n### viewer\n\n• `Private` `Readonly` **viewer**: `undefined` \\| `EthAddress`\n\n## Methods\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getBiome\n\n▸ `Private` **getBiome**(`loc`): `Biome`\n\n#### Parameters\n\n| Name  | Type            |\n| :---- | :-------------- |\n| `loc` | `WorldLocation` |\n\n#### Returns\n\n`Biome`\n\n---\n\n### getTwitter\n\n▸ **getTwitter**(`owner`): `undefined` \\| `string`\n\n#### Parameters\n\n| Name    | Type                        |\n| :------ | :-------------------------- |\n| `owner` | `undefined` \\| `EthAddress` |\n\n#### Returns\n\n`undefined` \\| `string`\n\n---\n\n### getViewer\n\n▸ **getViewer**(): `undefined` \\| `EthAddress`\n\n#### Returns\n\n`undefined` \\| `EthAddress`\n\n---\n\n### loadArtifactFromContract\n\n▸ **loadArtifactFromContract**(`artifactId`): `Promise`<`Artifact`\\>\n\n#### Parameters\n\n| Name         | Type         |\n| :----------- | :----------- |\n| `artifactId` | `ArtifactId` |\n\n#### Returns\n\n`Promise`<`Artifact`\\>\n\n---\n\n### loadPlanetFromContract\n\n▸ **loadPlanetFromContract**(`planetId`): `Promise`<`Planet` \\| `LocatablePlanet`\\>\n\n#### Parameters\n\n| Name       | Type         |\n| :--------- | :----------- |\n| `planetId` | `LocationId` |\n\n#### Returns\n\n`Promise`<`Planet` \\| `LocatablePlanet`\\>\n\n---\n\n### setPlanetLocationIfKnown\n\n▸ `Private` **setPlanetLocationIfKnown**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### spaceTypeFromPerlin\n\n▸ `Private` **spaceTypeFromPerlin**(`perlin`): `SpaceType`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `perlin` | `number` |\n\n#### Returns\n\n`SpaceType`\n\n---\n\n### create\n\n▸ `Static` **create**(`__namedParameters`): `Promise`<[`default`](Backend_Storage_ReaderDataStore.default.md)\\>\n\n#### Parameters\n\n| Name                                | Type                        |\n| :---------------------------------- | :-------------------------- |\n| `__namedParameters`                 | `Object`                    |\n| `__namedParameters.connection`      | `EthConnection`             |\n| `__namedParameters.contractAddress` | `EthAddress`                |\n| `__namedParameters.viewer`          | `undefined` \\| `EthAddress` |\n\n#### Returns\n\n`Promise`<[`default`](Backend_Storage_ReaderDataStore.default.md)\\>\n"
  },
  {
    "path": "docs/classes/Backend_Utils_SnarkArgsHelper.default.md",
    "content": "# Class: default\n\n[Backend/Utils/SnarkArgsHelper](../modules/Backend_Utils_SnarkArgsHelper.md).default\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Utils_SnarkArgsHelper.default.md#constructor)\n\n### Properties\n\n- [biomebasePerlinOpts](Backend_Utils_SnarkArgsHelper.default.md#biomebaseperlinopts)\n- [hashConfig](Backend_Utils_SnarkArgsHelper.default.md#hashconfig)\n- [moveSnarkCache](Backend_Utils_SnarkArgsHelper.default.md#movesnarkcache)\n- [planetHashMimc](Backend_Utils_SnarkArgsHelper.default.md#planethashmimc)\n- [snarkProverQueue](Backend_Utils_SnarkArgsHelper.default.md#snarkproverqueue)\n- [spaceTypePerlinOpts](Backend_Utils_SnarkArgsHelper.default.md#spacetypeperlinopts)\n- [terminal](Backend_Utils_SnarkArgsHelper.default.md#terminal)\n- [useMockHash](Backend_Utils_SnarkArgsHelper.default.md#usemockhash)\n- [DEFAULT_SNARK_CACHE_SIZE](Backend_Utils_SnarkArgsHelper.default.md#default_snark_cache_size)\n\n### Methods\n\n- [fakeBiomebaseProof](Backend_Utils_SnarkArgsHelper.default.md#fakebiomebaseproof)\n- [fakeInitProof](Backend_Utils_SnarkArgsHelper.default.md#fakeinitproof)\n- [fakeMoveProof](Backend_Utils_SnarkArgsHelper.default.md#fakemoveproof)\n- [fakeRevealProof](Backend_Utils_SnarkArgsHelper.default.md#fakerevealproof)\n- [getFindArtifactArgs](Backend_Utils_SnarkArgsHelper.default.md#getfindartifactargs)\n- [getInitArgs](Backend_Utils_SnarkArgsHelper.default.md#getinitargs)\n- [getMoveArgs](Backend_Utils_SnarkArgsHelper.default.md#getmoveargs)\n- [getRevealArgs](Backend_Utils_SnarkArgsHelper.default.md#getrevealargs)\n- [setSnarkCacheSize](Backend_Utils_SnarkArgsHelper.default.md#setsnarkcachesize)\n- [create](Backend_Utils_SnarkArgsHelper.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`hashConfig`, `terminal`, `useMockHash`)\n\n#### Parameters\n\n| Name          | Type                                                                                                            |\n| :------------ | :-------------------------------------------------------------------------------------------------------------- |\n| `hashConfig`  | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)                                               |\n| `terminal`    | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n| `useMockHash` | `boolean`                                                                                                       |\n\n## Properties\n\n### biomebasePerlinOpts\n\n• `Private` `Readonly` **biomebasePerlinOpts**: `PerlinConfig`\n\n---\n\n### hashConfig\n\n• `Private` `Readonly` **hashConfig**: [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)\n\n---\n\n### moveSnarkCache\n\n• `Private` **moveSnarkCache**: `default`<`string`, `MoveSnarkContractCallArgs`\\>\n\n---\n\n### planetHashMimc\n\n• `Private` `Readonly` **planetHashMimc**: (...`inputs`: `number`[]) => `BigInteger`\n\n#### Type declaration\n\n▸ (...`inputs`): `BigInteger`\n\n##### Parameters\n\n| Name        | Type       |\n| :---------- | :--------- |\n| `...inputs` | `number`[] |\n\n##### Returns\n\n`BigInteger`\n\n---\n\n### snarkProverQueue\n\n• `Private` `Readonly` **snarkProverQueue**: `SnarkProverQueue`\n\n---\n\n### spaceTypePerlinOpts\n\n• `Private` `Readonly` **spaceTypePerlinOpts**: `PerlinConfig`\n\n---\n\n### terminal\n\n• `Private` `Readonly` **terminal**: `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\>\n\n---\n\n### useMockHash\n\n• `Private` `Readonly` **useMockHash**: `boolean`\n\n---\n\n### DEFAULT_SNARK_CACHE_SIZE\n\n▪ `Static` `Private` `Readonly` **DEFAULT_SNARK_CACHE_SIZE**: `20`\n\nHow many snark results to keep in an LRU cache.\n\n## Methods\n\n### fakeBiomebaseProof\n\n▸ `Private` **fakeBiomebaseProof**(`x`, `y`): `SnarkJSProofAndSignals`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n#### Returns\n\n`SnarkJSProofAndSignals`\n\n---\n\n### fakeInitProof\n\n▸ `Private` **fakeInitProof**(`x`, `y`, `r`): `SnarkJSProofAndSignals`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n| `r`  | `number` |\n\n#### Returns\n\n`SnarkJSProofAndSignals`\n\n---\n\n### fakeMoveProof\n\n▸ `Private` **fakeMoveProof**(`x1`, `y1`, `x2`, `y2`, `r`, `distMax`): `SnarkJSProofAndSignals`\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `x1`      | `number` |\n| `y1`      | `number` |\n| `x2`      | `number` |\n| `y2`      | `number` |\n| `r`       | `number` |\n| `distMax` | `number` |\n\n#### Returns\n\n`SnarkJSProofAndSignals`\n\n---\n\n### fakeRevealProof\n\n▸ `Private` **fakeRevealProof**(`x`, `y`): `SnarkJSProofAndSignals`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n#### Returns\n\n`SnarkJSProofAndSignals`\n\n---\n\n### getFindArtifactArgs\n\n▸ **getFindArtifactArgs**(`x`, `y`): `Promise`<`BiomebaseSnarkContractCallArgs`\\>\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n#### Returns\n\n`Promise`<`BiomebaseSnarkContractCallArgs`\\>\n\n---\n\n### getInitArgs\n\n▸ **getInitArgs**(`x`, `y`, `r`): `Promise`<`InitSnarkContractCallArgs`\\>\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n| `r`  | `number` |\n\n#### Returns\n\n`Promise`<`InitSnarkContractCallArgs`\\>\n\n---\n\n### getMoveArgs\n\n▸ **getMoveArgs**(`x1`, `y1`, `x2`, `y2`, `r`, `distMax`): `Promise`<`MoveSnarkContractCallArgs`\\>\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `x1`      | `number` |\n| `y1`      | `number` |\n| `x2`      | `number` |\n| `y2`      | `number` |\n| `r`       | `number` |\n| `distMax` | `number` |\n\n#### Returns\n\n`Promise`<`MoveSnarkContractCallArgs`\\>\n\n---\n\n### getRevealArgs\n\n▸ **getRevealArgs**(`x`, `y`): `Promise`<`RevealSnarkContractCallArgs`\\>\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n#### Returns\n\n`Promise`<`RevealSnarkContractCallArgs`\\>\n\n---\n\n### setSnarkCacheSize\n\n▸ **setSnarkCacheSize**(`size`): `void`\n\n#### Parameters\n\n| Name   | Type     |\n| :----- | :------- |\n| `size` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### create\n\n▸ `Static` **create**(`hashConfig`, `terminal`, `fakeHash?`): [`default`](Backend_Utils_SnarkArgsHelper.default.md)\n\n#### Parameters\n\n| Name         | Type                                                                                                            | Default value |\n| :----------- | :-------------------------------------------------------------------------------------------------------------- | :------------ |\n| `hashConfig` | [`HashConfig`](../modules/types_global_GlobalTypes.md#hashconfig)                                               | `undefined`   |\n| `terminal`   | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> | `undefined`   |\n| `fakeHash`   | `boolean`                                                                                                       | `false`       |\n\n#### Returns\n\n[`default`](Backend_Utils_SnarkArgsHelper.default.md)\n"
  },
  {
    "path": "docs/classes/Backend_Utils_Wrapper.Wrapper.md",
    "content": "# Class: Wrapper<T\\>\n\n[Backend/Utils/Wrapper](../modules/Backend_Utils_Wrapper.md).Wrapper\n\nReact uses referential identity to detect changes, and rerender. Rather\nthan copying an object into a new object, to force a rerender, we can\njust wrap it in a new {@code Wrapper}, which will force a rerender.\n\n## Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Utils_Wrapper.Wrapper.md#constructor)\n\n### Properties\n\n- [value](Backend_Utils_Wrapper.Wrapper.md#value)\n\n## Constructors\n\n### constructor\n\n• **new Wrapper**<`T`\\>(`value`)\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name    | Type |\n| :------ | :--- |\n| `value` | `T`  |\n\n## Properties\n\n### value\n\n• `Readonly` **value**: `T`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Btn.DarkForestButton.md",
    "content": "# Class: DarkForestButton\n\n[Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).DarkForestButton\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestButton`**\n\n  ↳↳ [`DarkForestShortcutButton`](Frontend_Components_Btn.DarkForestShortcutButton.md)\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Btn.DarkForestButton.md#constructor)\n\n### Properties\n\n- [active](Frontend_Components_Btn.DarkForestButton.md#active)\n- [disabled](Frontend_Components_Btn.DarkForestButton.md#disabled)\n- [size](Frontend_Components_Btn.DarkForestButton.md#size)\n- [variant](Frontend_Components_Btn.DarkForestButton.md#variant)\n- [properties](Frontend_Components_Btn.DarkForestButton.md#properties)\n- [styles](Frontend_Components_Btn.DarkForestButton.md#styles)\n- [tagName](Frontend_Components_Btn.DarkForestButton.md#tagname)\n\n### Methods\n\n- [\\_handleClick](Frontend_Components_Btn.DarkForestButton.md#_handleclick)\n- [render](Frontend_Components_Btn.DarkForestButton.md#render)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestButton**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### active\n\n• **active**: `boolean`\n\n---\n\n### disabled\n\n• **disabled**: `boolean`\n\n---\n\n### size\n\n• **size**: `\"small\"` \\| `\"stretch\"` \\| `\"medium\"` \\| `\"large\"`\n\n---\n\n### variant\n\n• **variant**: `\"normal\"` \\| `\"danger\"`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name            | Type                             |\n| :-------------- | :------------------------------- |\n| `active`        | { `type`: `BooleanConstructor` } |\n| `active.type`   | `BooleanConstructor`             |\n| `disabled`      | { `type`: `BooleanConstructor` } |\n| `disabled.type` | `BooleanConstructor`             |\n| `size`          | { `type`: `StringConstructor` }  |\n| `size.type`     | `StringConstructor`              |\n| `variant`       | { `type`: `StringConstructor` }  |\n| `variant.type`  | `StringConstructor`              |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`[]\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### \\_handleClick\n\n▸ `Protected` **\\_handleClick**(`evt`): `void`\n\n#### Parameters\n\n| Name  | Type         |\n| :---- | :----------- |\n| `evt` | `MouseEvent` |\n\n#### Returns\n\n`void`\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Btn.DarkForestShortcutButton.md",
    "content": "# Class: DarkForestShortcutButton\n\n[Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).DarkForestShortcutButton\n\n## Hierarchy\n\n- [`DarkForestButton`](Frontend_Components_Btn.DarkForestButton.md)\n\n  ↳ **`DarkForestShortcutButton`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Btn.DarkForestShortcutButton.md#constructor)\n\n### Properties\n\n- [\\_getKeyFromEvent](Frontend_Components_Btn.DarkForestShortcutButton.md#_getkeyfromevent)\n- [\\_handleKeyDown](Frontend_Components_Btn.DarkForestShortcutButton.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Btn.DarkForestShortcutButton.md#_handlekeyup)\n- [\\_renderKbd](Frontend_Components_Btn.DarkForestShortcutButton.md#_renderkbd)\n- [\\_shortcutPressed](Frontend_Components_Btn.DarkForestShortcutButton.md#_shortcutpressed)\n- [active](Frontend_Components_Btn.DarkForestShortcutButton.md#active)\n- [disabled](Frontend_Components_Btn.DarkForestShortcutButton.md#disabled)\n- [shortcutKey](Frontend_Components_Btn.DarkForestShortcutButton.md#shortcutkey)\n- [shortcutText](Frontend_Components_Btn.DarkForestShortcutButton.md#shortcuttext)\n- [size](Frontend_Components_Btn.DarkForestShortcutButton.md#size)\n- [variant](Frontend_Components_Btn.DarkForestShortcutButton.md#variant)\n- [properties](Frontend_Components_Btn.DarkForestShortcutButton.md#properties)\n- [styles](Frontend_Components_Btn.DarkForestShortcutButton.md#styles)\n- [tagName](Frontend_Components_Btn.DarkForestShortcutButton.md#tagname)\n\n### Methods\n\n- [\\_handleClick](Frontend_Components_Btn.DarkForestShortcutButton.md#_handleclick)\n- [connectedCallback](Frontend_Components_Btn.DarkForestShortcutButton.md#connectedcallback)\n- [disconnectedCallback](Frontend_Components_Btn.DarkForestShortcutButton.md#disconnectedcallback)\n- [render](Frontend_Components_Btn.DarkForestShortcutButton.md#render)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestShortcutButton**()\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[constructor](Frontend_Components_Btn.DarkForestButton.md#constructor)\n\n## Properties\n\n### \\_getKeyFromEvent\n\n• `Private` **\\_getKeyFromEvent**: `any`\n\n---\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### \\_renderKbd\n\n• `Private` **\\_renderKbd**: `any`\n\n---\n\n### \\_shortcutPressed\n\n• `Private` **\\_shortcutPressed**: `any`\n\n---\n\n### active\n\n• **active**: `boolean`\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[active](Frontend_Components_Btn.DarkForestButton.md#active)\n\n---\n\n### disabled\n\n• **disabled**: `boolean`\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[disabled](Frontend_Components_Btn.DarkForestButton.md#disabled)\n\n---\n\n### shortcutKey\n\n• `Optional` **shortcutKey**: `string`\n\nThe `shortcutKey` indicates which key this component listens for while it is mounted\n\n---\n\n### shortcutText\n\n• `Optional` **shortcutText**: `string`\n\nThe `shortcutText` indicates the key should be displayed and with what text\n\n---\n\n### size\n\n• **size**: `\"small\"` \\| `\"stretch\"` \\| `\"medium\"` \\| `\"large\"`\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[size](Frontend_Components_Btn.DarkForestButton.md#size)\n\n---\n\n### variant\n\n• **variant**: `\"normal\"` \\| `\"danger\"`\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[variant](Frontend_Components_Btn.DarkForestButton.md#variant)\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name                     | Type                             |\n| :----------------------- | :------------------------------- |\n| `_shortcutPressed`       | { `state`: `boolean` }           |\n| `_shortcutPressed.state` | `boolean`                        |\n| `active`                 | { `type`: `BooleanConstructor` } |\n| `active.type`            | `BooleanConstructor`             |\n| `disabled`               | { `type`: `BooleanConstructor` } |\n| `disabled.type`          | `BooleanConstructor`             |\n| `shortcutKey`            | { `type`: `StringConstructor` }  |\n| `shortcutKey.type`       | `StringConstructor`              |\n| `shortcutText`           | { `type`: `StringConstructor` }  |\n| `shortcutText.type`      | `StringConstructor`              |\n| `size`                   | { `type`: `StringConstructor` }  |\n| `size.type`              | `StringConstructor`              |\n| `variant`                | { `type`: `StringConstructor` }  |\n| `variant.type`           | `StringConstructor`              |\n\n#### Overrides\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[properties](Frontend_Components_Btn.DarkForestButton.md#properties)\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`[]\n\n#### Overrides\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[styles](Frontend_Components_Btn.DarkForestButton.md#styles)\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n#### Overrides\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[tagName](Frontend_Components_Btn.DarkForestButton.md#tagname)\n\n## Methods\n\n### \\_handleClick\n\n▸ `Protected` **\\_handleClick**(`evt`): `void`\n\n#### Parameters\n\n| Name  | Type         |\n| :---- | :----------- |\n| `evt` | `MouseEvent` |\n\n#### Returns\n\n`void`\n\n#### Inherited from\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[\\_handleClick](Frontend_Components_Btn.DarkForestButton.md#_handleclick)\n\n---\n\n### connectedCallback\n\n▸ **connectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nDarkForestButton.connectedCallback\n\n---\n\n### disconnectedCallback\n\n▸ **disconnectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nDarkForestButton.disconnectedCallback\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\n[DarkForestButton](Frontend_Components_Btn.DarkForestButton.md).[render](Frontend_Components_Btn.DarkForestButton.md#render)\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Btn.ShortcutPressedEvent.md",
    "content": "# Class: ShortcutPressedEvent\n\n[Frontend/Components/Btn](../modules/Frontend_Components_Btn.md).ShortcutPressedEvent\n\n## Hierarchy\n\n- `Event`\n\n  ↳ **`ShortcutPressedEvent`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Btn.ShortcutPressedEvent.md#constructor)\n\n## Constructors\n\n### constructor\n\n• **new ShortcutPressedEvent**()\n\n#### Overrides\n\nEvent.constructor\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Input.DarkForestCheckbox.md",
    "content": "# Class: DarkForestCheckbox\n\n[Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestCheckbox\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestCheckbox`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Input.DarkForestCheckbox.md#constructor)\n\n### Properties\n\n- [\\_handleInput](Frontend_Components_Input.DarkForestCheckbox.md#_handleinput)\n- [\\_handleKeyDown](Frontend_Components_Input.DarkForestCheckbox.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Input.DarkForestCheckbox.md#_handlekeyup)\n- [\\_inputRef](Frontend_Components_Input.DarkForestCheckbox.md#_inputref)\n- [checked](Frontend_Components_Input.DarkForestCheckbox.md#checked)\n- [disabled](Frontend_Components_Input.DarkForestCheckbox.md#disabled)\n- [label](Frontend_Components_Input.DarkForestCheckbox.md#label)\n- [selected](Frontend_Components_Input.DarkForestCheckbox.md#selected)\n- [properties](Frontend_Components_Input.DarkForestCheckbox.md#properties)\n- [styles](Frontend_Components_Input.DarkForestCheckbox.md#styles)\n- [tagName](Frontend_Components_Input.DarkForestCheckbox.md#tagname)\n\n### Methods\n\n- [firstUpdated](Frontend_Components_Input.DarkForestCheckbox.md#firstupdated)\n- [focus](Frontend_Components_Input.DarkForestCheckbox.md#focus)\n- [render](Frontend_Components_Input.DarkForestCheckbox.md#render)\n- [select](Frontend_Components_Input.DarkForestCheckbox.md#select)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestCheckbox**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### \\_handleInput\n\n• `Private` **\\_handleInput**: `any`\n\n---\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### \\_inputRef\n\n• `Private` **\\_inputRef**: `any`\n\n---\n\n### checked\n\n• **checked**: `boolean`\n\n---\n\n### disabled\n\n• `Optional` **disabled**: `boolean`\n\n---\n\n### label\n\n• `Optional` **label**: `string`\n\n---\n\n### selected\n\n• **selected**: `boolean`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name            | Type                             |\n| :-------------- | :------------------------------- |\n| `checked`       | { `type`: `BooleanConstructor` } |\n| `checked.type`  | `BooleanConstructor`             |\n| `disabled`      | { `type`: `BooleanConstructor` } |\n| `disabled.type` | `BooleanConstructor`             |\n| `label`         | { `type`: `StringConstructor` }  |\n| `label.type`    | `StringConstructor`              |\n| `selected`      | { `type`: `BooleanConstructor` } |\n| `selected.type` | `BooleanConstructor`             |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### firstUpdated\n\n▸ **firstUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.firstUpdated\n\n---\n\n### focus\n\n▸ **focus**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.focus\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n\n---\n\n### select\n\n▸ **select**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Input.DarkForestColorInput.md",
    "content": "# Class: DarkForestColorInput\n\n[Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestColorInput\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestColorInput`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Input.DarkForestColorInput.md#constructor)\n\n### Properties\n\n- [\\_handleInput](Frontend_Components_Input.DarkForestColorInput.md#_handleinput)\n- [\\_handleKeyDown](Frontend_Components_Input.DarkForestColorInput.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Input.DarkForestColorInput.md#_handlekeyup)\n- [\\_inputRef](Frontend_Components_Input.DarkForestColorInput.md#_inputref)\n- [disabled](Frontend_Components_Input.DarkForestColorInput.md#disabled)\n- [readonly](Frontend_Components_Input.DarkForestColorInput.md#readonly)\n- [selected](Frontend_Components_Input.DarkForestColorInput.md#selected)\n- [value](Frontend_Components_Input.DarkForestColorInput.md#value)\n- [properties](Frontend_Components_Input.DarkForestColorInput.md#properties)\n- [styles](Frontend_Components_Input.DarkForestColorInput.md#styles)\n- [tagName](Frontend_Components_Input.DarkForestColorInput.md#tagname)\n\n### Methods\n\n- [firstUpdated](Frontend_Components_Input.DarkForestColorInput.md#firstupdated)\n- [focus](Frontend_Components_Input.DarkForestColorInput.md#focus)\n- [render](Frontend_Components_Input.DarkForestColorInput.md#render)\n- [select](Frontend_Components_Input.DarkForestColorInput.md#select)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestColorInput**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### \\_handleInput\n\n• `Private` **\\_handleInput**: `any`\n\n---\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### \\_inputRef\n\n• `Private` **\\_inputRef**: `any`\n\n---\n\n### disabled\n\n• `Optional` **disabled**: `boolean`\n\n---\n\n### readonly\n\n• **readonly**: `boolean`\n\n---\n\n### selected\n\n• **selected**: `boolean`\n\n---\n\n### value\n\n• **value**: `string`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name            | Type                             |\n| :-------------- | :------------------------------- |\n| `disabled`      | { `type`: `BooleanConstructor` } |\n| `disabled.type` | `BooleanConstructor`             |\n| `readonly`      | { `type`: `BooleanConstructor` } |\n| `readonly.type` | `BooleanConstructor`             |\n| `selected`      | { `type`: `BooleanConstructor` } |\n| `selected.type` | `BooleanConstructor`             |\n| `value`         | { `type`: `StringConstructor` }  |\n| `value.type`    | `StringConstructor`              |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### firstUpdated\n\n▸ **firstUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.firstUpdated\n\n---\n\n### focus\n\n▸ **focus**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.focus\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n\n---\n\n### select\n\n▸ **select**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Input.DarkForestNumberInput.md",
    "content": "# Class: DarkForestNumberInput\n\n[Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestNumberInput\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestNumberInput`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Input.DarkForestNumberInput.md#constructor)\n\n### Properties\n\n- [\\_handleInput](Frontend_Components_Input.DarkForestNumberInput.md#_handleinput)\n- [\\_handleKeyDown](Frontend_Components_Input.DarkForestNumberInput.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Input.DarkForestNumberInput.md#_handlekeyup)\n- [\\_handleWheel](Frontend_Components_Input.DarkForestNumberInput.md#_handlewheel)\n- [\\_inputRef](Frontend_Components_Input.DarkForestNumberInput.md#_inputref)\n- [\\_value](Frontend_Components_Input.DarkForestNumberInput.md#_value)\n- [disabled](Frontend_Components_Input.DarkForestNumberInput.md#disabled)\n- [format](Frontend_Components_Input.DarkForestNumberInput.md#format)\n- [readonly](Frontend_Components_Input.DarkForestNumberInput.md#readonly)\n- [selected](Frontend_Components_Input.DarkForestNumberInput.md#selected)\n- [properties](Frontend_Components_Input.DarkForestNumberInput.md#properties)\n- [styles](Frontend_Components_Input.DarkForestNumberInput.md#styles)\n- [tagName](Frontend_Components_Input.DarkForestNumberInput.md#tagname)\n\n### Accessors\n\n- [value](Frontend_Components_Input.DarkForestNumberInput.md#value)\n\n### Methods\n\n- [firstUpdated](Frontend_Components_Input.DarkForestNumberInput.md#firstupdated)\n- [focus](Frontend_Components_Input.DarkForestNumberInput.md#focus)\n- [render](Frontend_Components_Input.DarkForestNumberInput.md#render)\n- [select](Frontend_Components_Input.DarkForestNumberInput.md#select)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestNumberInput**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### \\_handleInput\n\n• `Private` **\\_handleInput**: `any`\n\n---\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### \\_handleWheel\n\n• `Private` **\\_handleWheel**: `any`\n\n---\n\n### \\_inputRef\n\n• `Private` **\\_inputRef**: `any`\n\n---\n\n### \\_value\n\n• `Private` **\\_value**: `any`\n\n---\n\n### disabled\n\n• `Optional` **disabled**: `boolean`\n\n---\n\n### format\n\n• **format**: `\"integer\"` \\| `\"float\"`\n\n---\n\n### readonly\n\n• **readonly**: `boolean`\n\n---\n\n### selected\n\n• **selected**: `boolean`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name            | Type                             |\n| :-------------- | :------------------------------- |\n| `_value`        | { `state`: `boolean` }           |\n| `_value.state`  | `boolean`                        |\n| `disabled`      | { `type`: `BooleanConstructor` } |\n| `disabled.type` | `BooleanConstructor`             |\n| `format`        | { `type`: `StringConstructor` }  |\n| `format.type`   | `StringConstructor`              |\n| `readonly`      | { `type`: `BooleanConstructor` } |\n| `readonly.type` | `BooleanConstructor`             |\n| `selected`      | { `type`: `BooleanConstructor` } |\n| `selected.type` | `BooleanConstructor`             |\n| `value`         | { `type`: `NumberConstructor` }  |\n| `value.type`    | `NumberConstructor`              |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Accessors\n\n### value\n\n• `get` **value**(): `undefined` \\| `number`\n\n#### Returns\n\n`undefined` \\| `number`\n\n• `set` **value**(`newValue`): `void`\n\n#### Parameters\n\n| Name       | Type                    |\n| :--------- | :---------------------- |\n| `newValue` | `undefined` \\| `number` |\n\n#### Returns\n\n`void`\n\n## Methods\n\n### firstUpdated\n\n▸ **firstUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.firstUpdated\n\n---\n\n### focus\n\n▸ **focus**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.focus\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n\n---\n\n### select\n\n▸ **select**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Input.DarkForestTextInput.md",
    "content": "# Class: DarkForestTextInput\n\n[Frontend/Components/Input](../modules/Frontend_Components_Input.md).DarkForestTextInput\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestTextInput`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Input.DarkForestTextInput.md#constructor)\n\n### Properties\n\n- [\\_handleInput](Frontend_Components_Input.DarkForestTextInput.md#_handleinput)\n- [\\_handleKeyDown](Frontend_Components_Input.DarkForestTextInput.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Input.DarkForestTextInput.md#_handlekeyup)\n- [\\_inputRef](Frontend_Components_Input.DarkForestTextInput.md#_inputref)\n- [disabled](Frontend_Components_Input.DarkForestTextInput.md#disabled)\n- [placeholder](Frontend_Components_Input.DarkForestTextInput.md#placeholder)\n- [readonly](Frontend_Components_Input.DarkForestTextInput.md#readonly)\n- [selected](Frontend_Components_Input.DarkForestTextInput.md#selected)\n- [value](Frontend_Components_Input.DarkForestTextInput.md#value)\n- [properties](Frontend_Components_Input.DarkForestTextInput.md#properties)\n- [styles](Frontend_Components_Input.DarkForestTextInput.md#styles)\n- [tagName](Frontend_Components_Input.DarkForestTextInput.md#tagname)\n\n### Methods\n\n- [firstUpdated](Frontend_Components_Input.DarkForestTextInput.md#firstupdated)\n- [focus](Frontend_Components_Input.DarkForestTextInput.md#focus)\n- [render](Frontend_Components_Input.DarkForestTextInput.md#render)\n- [select](Frontend_Components_Input.DarkForestTextInput.md#select)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestTextInput**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### \\_handleInput\n\n• `Private` **\\_handleInput**: `any`\n\n---\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### \\_inputRef\n\n• `Private` **\\_inputRef**: `any`\n\n---\n\n### disabled\n\n• `Optional` **disabled**: `boolean`\n\n---\n\n### placeholder\n\n• **placeholder**: `string`\n\n---\n\n### readonly\n\n• **readonly**: `boolean`\n\n---\n\n### selected\n\n• **selected**: `boolean`\n\n---\n\n### value\n\n• **value**: `string`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name               | Type                             |\n| :----------------- | :------------------------------- |\n| `disabled`         | { `type`: `BooleanConstructor` } |\n| `disabled.type`    | `BooleanConstructor`             |\n| `placeholder`      | { `type`: `StringConstructor` }  |\n| `placeholder.type` | `StringConstructor`              |\n| `readonly`         | { `type`: `BooleanConstructor` } |\n| `readonly.type`    | `BooleanConstructor`             |\n| `selected`         | { `type`: `BooleanConstructor` } |\n| `selected.type`    | `BooleanConstructor`             |\n| `value`            | { `type`: `StringConstructor` }  |\n| `value.type`       | `StringConstructor`              |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### firstUpdated\n\n▸ **firstUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.firstUpdated\n\n---\n\n### focus\n\n▸ **focus**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.focus\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n\n---\n\n### select\n\n▸ **select**(): `void`\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Modal.DarkForestModal.md",
    "content": "# Class: DarkForestModal\n\n[Frontend/Components/Modal](../modules/Frontend_Components_Modal.md).DarkForestModal\n\n## Hierarchy\n\n- `LitElement`\n\n  ↳ **`DarkForestModal`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Modal.DarkForestModal.md#constructor)\n\n### Properties\n\n- [\\_coords](Frontend_Components_Modal.DarkForestModal.md#_coords)\n- [\\_delCoords](Frontend_Components_Modal.DarkForestModal.md#_delcoords)\n- [\\_dragging](Frontend_Components_Modal.DarkForestModal.md#_dragging)\n- [\\_handleMouseMove](Frontend_Components_Modal.DarkForestModal.md#_handlemousemove)\n- [\\_handleMoveEnd](Frontend_Components_Modal.DarkForestModal.md#_handlemoveend)\n- [\\_handleResize](Frontend_Components_Modal.DarkForestModal.md#_handleresize)\n- [\\_mousedownCoords](Frontend_Components_Modal.DarkForestModal.md#_mousedowncoords)\n- [\\_setDragging](Frontend_Components_Modal.DarkForestModal.md#_setdragging)\n- [\\_unsetDragging](Frontend_Components_Modal.DarkForestModal.md#_unsetdragging)\n- [contain](Frontend_Components_Modal.DarkForestModal.md#contain)\n- [index](Frontend_Components_Modal.DarkForestModal.md#index)\n- [initialX](Frontend_Components_Modal.DarkForestModal.md#initialx)\n- [initialY](Frontend_Components_Modal.DarkForestModal.md#initialy)\n- [minimized](Frontend_Components_Modal.DarkForestModal.md#minimized)\n- [renderContent](Frontend_Components_Modal.DarkForestModal.md#rendercontent)\n- [renderTitleBar](Frontend_Components_Modal.DarkForestModal.md#rendertitlebar)\n- [width](Frontend_Components_Modal.DarkForestModal.md#width)\n- [properties](Frontend_Components_Modal.DarkForestModal.md#properties)\n- [styles](Frontend_Components_Modal.DarkForestModal.md#styles)\n- [tagName](Frontend_Components_Modal.DarkForestModal.md#tagname)\n\n### Methods\n\n- [connectedCallback](Frontend_Components_Modal.DarkForestModal.md#connectedcallback)\n- [disconnectedCallback](Frontend_Components_Modal.DarkForestModal.md#disconnectedcallback)\n- [firstUpdated](Frontend_Components_Modal.DarkForestModal.md#firstupdated)\n- [render](Frontend_Components_Modal.DarkForestModal.md#render)\n- [updated](Frontend_Components_Modal.DarkForestModal.md#updated)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestModal**()\n\n#### Inherited from\n\nLitElement.constructor\n\n## Properties\n\n### \\_coords\n\n• `Private` `Optional` **\\_coords**: `any`\n\n---\n\n### \\_delCoords\n\n• `Private` `Optional` **\\_delCoords**: `any`\n\n---\n\n### \\_dragging\n\n• `Private` **\\_dragging**: `any`\n\n---\n\n### \\_handleMouseMove\n\n• `Private` **\\_handleMouseMove**: `any`\n\n---\n\n### \\_handleMoveEnd\n\n• `Private` **\\_handleMoveEnd**: `any`\n\n---\n\n### \\_handleResize\n\n• `Private` **\\_handleResize**: `any`\n\n---\n\n### \\_mousedownCoords\n\n• `Private` `Optional` **\\_mousedownCoords**: `any`\n\n---\n\n### \\_setDragging\n\n• `Private` **\\_setDragging**: `any`\n\n---\n\n### \\_unsetDragging\n\n• `Private` **\\_unsetDragging**: `any`\n\n---\n\n### contain\n\n• **contain**: `Contain`[]\n\n---\n\n### index\n\n• `Optional` **index**: `number`\n\n---\n\n### initialX\n\n• `Optional` **initialX**: `number`\n\n---\n\n### initialY\n\n• `Optional` **initialY**: `number`\n\n---\n\n### minimized\n\n• **minimized**: `boolean`\n\n---\n\n### renderContent\n\n• `Private` **renderContent**: `any`\n\n---\n\n### renderTitleBar\n\n• `Private` **renderTitleBar**: `any`\n\n---\n\n### width\n\n• `Optional` **width**: `string`\n\n---\n\n### properties\n\n▪ `Static` **properties**: `Object`\n\n#### Type declaration\n\n| Name                     | Type                             |\n| :----------------------- | :------------------------------- |\n| `_delCoords`             | { `state`: `boolean` }           |\n| `_delCoords.state`       | `boolean`                        |\n| `_dragging`              | { `state`: `boolean` }           |\n| `_dragging.state`        | `boolean`                        |\n| `_mousedownCoords`       | { `state`: `boolean` }           |\n| `_mousedownCoords.state` | `boolean`                        |\n| `contain`                | { `type`: `ArrayConstructor` }   |\n| `contain.type`           | `ArrayConstructor`               |\n| `index`                  | { `type`: `NumberConstructor` }  |\n| `index.type`             | `NumberConstructor`              |\n| `initialX`               | { `type`: `NumberConstructor` }  |\n| `initialX.type`          | `NumberConstructor`              |\n| `initialY`               | { `type`: `NumberConstructor` }  |\n| `initialY.type`          | `NumberConstructor`              |\n| `minimized`              | { `type`: `BooleanConstructor` } |\n| `minimized.type`         | `BooleanConstructor`             |\n| `width`                  | { `type`: `StringConstructor` }  |\n| `width.type`             | `StringConstructor`              |\n\n#### Overrides\n\nLitElement.properties\n\n---\n\n### styles\n\n▪ `Static` **styles**: `CSSResult`\n\n#### Overrides\n\nLitElement.styles\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### connectedCallback\n\n▸ **connectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.connectedCallback\n\n---\n\n### disconnectedCallback\n\n▸ **disconnectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.disconnectedCallback\n\n---\n\n### firstUpdated\n\n▸ **firstUpdated**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.firstUpdated\n\n---\n\n### render\n\n▸ **render**(): `TemplateResult`<`1`\\>\n\n#### Returns\n\n`TemplateResult`<`1`\\>\n\n#### Overrides\n\nLitElement.render\n\n---\n\n### updated\n\n▸ **updated**(`changedProperties`): `void`\n\n#### Parameters\n\n| Name                | Type                                                |\n| :------------------ | :-------------------------------------------------- |\n| `changedProperties` | `Map`<`string` \\| `number` \\| `symbol`, `unknown`\\> |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nLitElement.updated\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Modal.PositionChangedEvent.md",
    "content": "# Class: PositionChangedEvent\n\n[Frontend/Components/Modal](../modules/Frontend_Components_Modal.md).PositionChangedEvent\n\n## Hierarchy\n\n- `Event`\n\n  ↳ **`PositionChangedEvent`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Modal.PositionChangedEvent.md#constructor)\n\n### Properties\n\n- [coords](Frontend_Components_Modal.PositionChangedEvent.md#coords)\n\n## Constructors\n\n### constructor\n\n• **new PositionChangedEvent**(`x`, `y`)\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n#### Overrides\n\nEvent.constructor\n\n## Properties\n\n### coords\n\n• **coords**: `Coords`\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Slider.DarkForestSlider.md",
    "content": "# Class: DarkForestSlider\n\n[Frontend/Components/Slider](../modules/Frontend_Components_Slider.md).DarkForestSlider\n\n## Hierarchy\n\n- `Slider`\n\n  ↳ **`DarkForestSlider`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Slider.DarkForestSlider.md#constructor)\n\n### Properties\n\n- [\\_handleKeyDown](Frontend_Components_Slider.DarkForestSlider.md#_handlekeydown)\n- [\\_handleKeyUp](Frontend_Components_Slider.DarkForestSlider.md#_handlekeyup)\n- [tagName](Frontend_Components_Slider.DarkForestSlider.md#tagname)\n\n### Accessors\n\n- [styles](Frontend_Components_Slider.DarkForestSlider.md#styles)\n\n### Methods\n\n- [connectedCallback](Frontend_Components_Slider.DarkForestSlider.md#connectedcallback)\n- [disconnectedCallback](Frontend_Components_Slider.DarkForestSlider.md#disconnectedcallback)\n- [handlePointerdown](Frontend_Components_Slider.DarkForestSlider.md#handlepointerdown)\n- [handlePointermove](Frontend_Components_Slider.DarkForestSlider.md#handlepointermove)\n- [handlePointerup](Frontend_Components_Slider.DarkForestSlider.md#handlepointerup)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestSlider**()\n\n#### Inherited from\n\nSlider.constructor\n\n## Properties\n\n### \\_handleKeyDown\n\n• `Private` **\\_handleKeyDown**: `any`\n\n---\n\n### \\_handleKeyUp\n\n• `Private` **\\_handleKeyUp**: `any`\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Accessors\n\n### styles\n\n• `Static` `get` **styles**(): `CSSResultArray`\n\n#### Returns\n\n`CSSResultArray`\n\n#### Overrides\n\nSlider.styles\n\n## Methods\n\n### connectedCallback\n\n▸ **connectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSlider.connectedCallback\n\n---\n\n### disconnectedCallback\n\n▸ **disconnectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSlider.disconnectedCallback\n\n---\n\n### handlePointerdown\n\n▸ **handlePointerdown**(`event`): `void`\n\n#### Parameters\n\n| Name    | Type           |\n| :------ | :------------- |\n| `event` | `PointerEvent` |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSlider.handlePointerdown\n\n---\n\n### handlePointermove\n\n▸ **handlePointermove**(`event`): `void`\n\n#### Parameters\n\n| Name    | Type           |\n| :------ | :------------- |\n| `event` | `PointerEvent` |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSlider.handlePointermove\n\n---\n\n### handlePointerup\n\n▸ **handlePointerup**(`event`): `void`\n\n#### Parameters\n\n| Name    | Type           |\n| :------ | :------------- |\n| `event` | `PointerEvent` |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSlider.handlePointerup\n"
  },
  {
    "path": "docs/classes/Frontend_Components_Slider.DarkForestSliderHandle.md",
    "content": "# Class: DarkForestSliderHandle\n\n[Frontend/Components/Slider](../modules/Frontend_Components_Slider.md).DarkForestSliderHandle\n\n## Hierarchy\n\n- `SliderHandle`\n\n  ↳ **`DarkForestSliderHandle`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Components_Slider.DarkForestSliderHandle.md#constructor)\n\n### Properties\n\n- [\\_handleChange](Frontend_Components_Slider.DarkForestSliderHandle.md#_handlechange)\n- [tagName](Frontend_Components_Slider.DarkForestSliderHandle.md#tagname)\n\n### Methods\n\n- [connectedCallback](Frontend_Components_Slider.DarkForestSliderHandle.md#connectedcallback)\n- [disconnectedCallback](Frontend_Components_Slider.DarkForestSliderHandle.md#disconnectedcallback)\n\n## Constructors\n\n### constructor\n\n• **new DarkForestSliderHandle**()\n\n#### Inherited from\n\nSliderHandle.constructor\n\n## Properties\n\n### \\_handleChange\n\n• `Private` **\\_handleChange**: `any`\n\n---\n\n### tagName\n\n▪ `Static` **tagName**: `string`\n\n## Methods\n\n### connectedCallback\n\n▸ **connectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSliderHandle.connectedCallback\n\n---\n\n### disconnectedCallback\n\n▸ **disconnectedCallback**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nSliderHandle.disconnectedCallback\n"
  },
  {
    "path": "docs/classes/Frontend_Game_ModalManager.default.md",
    "content": "# Class: default\n\n[Frontend/Game/ModalManager](../modules/Frontend_Game_ModalManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Game_ModalManager.default.md#constructor)\n\n### Properties\n\n- [activeModalId$](Frontend_Game_ModalManager.default.md#activemodalid$)\n- [cursorState](Frontend_Game_ModalManager.default.md#cursorstate)\n- [lastIndex](Frontend_Game_ModalManager.default.md#lastindex)\n- [modalPositionChanged$](Frontend_Game_ModalManager.default.md#modalpositionchanged$)\n- [modalPositions](Frontend_Game_ModalManager.default.md#modalpositions)\n- [modalPositions$](Frontend_Game_ModalManager.default.md#modalpositions$)\n- [persistentChunkStore](Frontend_Game_ModalManager.default.md#persistentchunkstore)\n- [instance](Frontend_Game_ModalManager.default.md#instance)\n\n### Methods\n\n- [acceptInputForTarget](Frontend_Game_ModalManager.default.md#acceptinputfortarget)\n- [clearModalPosition](Frontend_Game_ModalManager.default.md#clearmodalposition)\n- [getCursorState](Frontend_Game_ModalManager.default.md#getcursorstate)\n- [getIndex](Frontend_Game_ModalManager.default.md#getindex)\n- [getModalPosition](Frontend_Game_ModalManager.default.md#getmodalposition)\n- [getModalPositions](Frontend_Game_ModalManager.default.md#getmodalpositions)\n- [setCursorState](Frontend_Game_ModalManager.default.md#setcursorstate)\n- [setModalPosition](Frontend_Game_ModalManager.default.md#setmodalposition)\n- [setModalState](Frontend_Game_ModalManager.default.md#setmodalstate)\n- [create](Frontend_Game_ModalManager.default.md#create)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`persistentChunkStore`, `modalPositions`)\n\n#### Parameters\n\n| Name                   | Type                                                         |\n| :--------------------- | :----------------------------------------------------------- |\n| `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) |\n| `modalPositions`       | `Map`<`ModalId`, `ModalPosition`\\>                           |\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### activeModalId$\n\n• `Readonly` **activeModalId$**: `Monomitter`<`string`\\>\n\n---\n\n### cursorState\n\n• `Private` **cursorState**: `CursorState`\n\n---\n\n### lastIndex\n\n• `Private` **lastIndex**: `number`\n\n---\n\n### modalPositionChanged$\n\n• `Readonly` **modalPositionChanged$**: `Monomitter`<`ModalId`\\>\n\n---\n\n### modalPositions\n\n• `Private` **modalPositions**: `Map`<`ModalId`, `ModalPosition`\\>\n\n---\n\n### modalPositions$\n\n• **modalPositions$**: `Monomitter`<`Map`<`ModalId`, `ModalPosition`\\>\\>\n\n---\n\n### persistentChunkStore\n\n• `Private` **persistentChunkStore**: [`default`](Backend_Storage_PersistentChunkStore.default.md)\n\n---\n\n### instance\n\n▪ `Static` **instance**: [`default`](Frontend_Game_ModalManager.default.md)\n\n## Methods\n\n### acceptInputForTarget\n\n▸ **acceptInputForTarget**(`input`): `void`\n\n#### Parameters\n\n| Name    | Type          |\n| :------ | :------------ |\n| `input` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### clearModalPosition\n\n▸ **clearModalPosition**(`modalId`): `void`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `modalId` | `ModalId` |\n\n#### Returns\n\n`void`\n\n---\n\n### getCursorState\n\n▸ **getCursorState**(): `CursorState`\n\n#### Returns\n\n`CursorState`\n\n---\n\n### getIndex\n\n▸ **getIndex**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getModalPosition\n\n▸ **getModalPosition**(`modalId`): `undefined` \\| `ModalPosition`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `modalId` | `ModalId` |\n\n#### Returns\n\n`undefined` \\| `ModalPosition`\n\n---\n\n### getModalPositions\n\n▸ **getModalPositions**(`modalIds?`): `Map`<`ModalId`, `ModalPosition`\\>\n\n#### Parameters\n\n| Name       | Type        | Default value |\n| :--------- | :---------- | :------------ |\n| `modalIds` | `ModalId`[] | `[]`          |\n\n#### Returns\n\n`Map`<`ModalId`, `ModalPosition`\\>\n\n---\n\n### setCursorState\n\n▸ **setCursorState**(`newstate`): `void`\n\n#### Parameters\n\n| Name       | Type          |\n| :--------- | :------------ |\n| `newstate` | `CursorState` |\n\n#### Returns\n\n`void`\n\n---\n\n### setModalPosition\n\n▸ **setModalPosition**(`modalId`, `pos`): `void`\n\n#### Parameters\n\n| Name      | Type            |\n| :-------- | :-------------- |\n| `modalId` | `ModalId`       |\n| `pos`     | `ModalPosition` |\n\n#### Returns\n\n`void`\n\n---\n\n### setModalState\n\n▸ **setModalState**(`modalId`, `state`): `void`\n\n#### Parameters\n\n| Name      | Type                                    |\n| :-------- | :-------------------------------------- |\n| `modalId` | `ModalId`                               |\n| `state`   | `\"open\"` \\| `\"minimized\"` \\| `\"closed\"` |\n\n#### Returns\n\n`void`\n\n---\n\n### create\n\n▸ `Static` **create**(`persistentChunkStore`): `Promise`<[`default`](Frontend_Game_ModalManager.default.md)\\>\n\n#### Parameters\n\n| Name                   | Type                                                         |\n| :--------------------- | :----------------------------------------------------------- |\n| `persistentChunkStore` | [`default`](Backend_Storage_PersistentChunkStore.default.md) |\n\n#### Returns\n\n`Promise`<[`default`](Frontend_Game_ModalManager.default.md)\\>\n"
  },
  {
    "path": "docs/classes/Frontend_Game_NotificationManager.default.md",
    "content": "# Class: default\n\n[Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Game_NotificationManager.default.md#constructor)\n\n### Properties\n\n- [instance](Frontend_Game_NotificationManager.default.md#instance)\n\n### Methods\n\n- [artifactFound](Frontend_Game_NotificationManager.default.md#artifactfound)\n- [artifactProspected](Frontend_Game_NotificationManager.default.md#artifactprospected)\n- [balanceEmpty](Frontend_Game_NotificationManager.default.md#balanceempty)\n- [clearNotification](Frontend_Game_NotificationManager.default.md#clearnotification)\n- [foundBiome](Frontend_Game_NotificationManager.default.md#foundbiome)\n- [foundComet](Frontend_Game_NotificationManager.default.md#foundcomet)\n- [foundDeadSpace](Frontend_Game_NotificationManager.default.md#founddeadspace)\n- [foundDeepSpace](Frontend_Game_NotificationManager.default.md#founddeepspace)\n- [foundFoundry](Frontend_Game_NotificationManager.default.md#foundfoundry)\n- [foundPirates](Frontend_Game_NotificationManager.default.md#foundpirates)\n- [foundSilver](Frontend_Game_NotificationManager.default.md#foundsilver)\n- [foundSilverBank](Frontend_Game_NotificationManager.default.md#foundsilverbank)\n- [foundSpace](Frontend_Game_NotificationManager.default.md#foundspace)\n- [foundTradingPost](Frontend_Game_NotificationManager.default.md#foundtradingpost)\n- [getIcon](Frontend_Game_NotificationManager.default.md#geticon)\n- [notify](Frontend_Game_NotificationManager.default.md#notify)\n- [planetAttacked](Frontend_Game_NotificationManager.default.md#planetattacked)\n- [planetCanUpgrade](Frontend_Game_NotificationManager.default.md#planetcanupgrade)\n- [planetConquered](Frontend_Game_NotificationManager.default.md#planetconquered)\n- [planetLost](Frontend_Game_NotificationManager.default.md#planetlost)\n- [reallyLongNotification](Frontend_Game_NotificationManager.default.md#reallylongnotification)\n- [receivedPlanet](Frontend_Game_NotificationManager.default.md#receivedplanet)\n- [txInitError](Frontend_Game_NotificationManager.default.md#txiniterror)\n- [welcomePlayer](Frontend_Game_NotificationManager.default.md#welcomeplayer)\n- [getInstance](Frontend_Game_NotificationManager.default.md#getinstance)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**()\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### instance\n\n▪ `Static` **instance**: [`default`](Frontend_Game_NotificationManager.default.md)\n\n## Methods\n\n### artifactFound\n\n▸ **artifactFound**(`planet`, `artifact`): `void`\n\n#### Parameters\n\n| Name       | Type              |\n| :--------- | :---------------- |\n| `planet`   | `LocatablePlanet` |\n| `artifact` | `Artifact`        |\n\n#### Returns\n\n`void`\n\n---\n\n### artifactProspected\n\n▸ **artifactProspected**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### balanceEmpty\n\n▸ **balanceEmpty**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### clearNotification\n\n▸ **clearNotification**(`id`): `void`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `id` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundBiome\n\n▸ **foundBiome**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundComet\n\n▸ **foundComet**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundDeadSpace\n\n▸ **foundDeadSpace**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundDeepSpace\n\n▸ **foundDeepSpace**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundFoundry\n\n▸ **foundFoundry**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundPirates\n\n▸ **foundPirates**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundSilver\n\n▸ **foundSilver**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundSilverBank\n\n▸ **foundSilverBank**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundSpace\n\n▸ **foundSpace**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### foundTradingPost\n\n▸ **foundTradingPost**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### getIcon\n\n▸ `Private` **getIcon**(`type`): `Element`\n\n#### Parameters\n\n| Name   | Type                                                                                 |\n| :----- | :----------------------------------------------------------------------------------- |\n| `type` | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### notify\n\n▸ **notify**(`type`, `message`): `void`\n\n#### Parameters\n\n| Name      | Type                                                                                 |\n| :-------- | :----------------------------------------------------------------------------------- |\n| `type`    | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) |\n| `message` | `ReactNode`                                                                          |\n\n#### Returns\n\n`void`\n\n---\n\n### planetAttacked\n\n▸ **planetAttacked**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### planetCanUpgrade\n\n▸ **planetCanUpgrade**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### planetConquered\n\n▸ **planetConquered**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### planetLost\n\n▸ **planetLost**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type              |\n| :------- | :---------------- |\n| `planet` | `LocatablePlanet` |\n\n#### Returns\n\n`void`\n\n---\n\n### reallyLongNotification\n\n▸ **reallyLongNotification**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### receivedPlanet\n\n▸ **receivedPlanet**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### txInitError\n\n▸ **txInitError**(`methodName`, `failureReason`): `void`\n\n#### Parameters\n\n| Name            | Type                 |\n| :-------------- | :------------------- |\n| `methodName`    | `ContractMethodName` |\n| `failureReason` | `string`             |\n\n#### Returns\n\n`void`\n\n---\n\n### welcomePlayer\n\n▸ **welcomePlayer**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getInstance\n\n▸ `Static` **getInstance**(): [`default`](Frontend_Game_NotificationManager.default.md)\n\n#### Returns\n\n[`default`](Frontend_Game_NotificationManager.default.md)\n"
  },
  {
    "path": "docs/classes/Frontend_Game_Viewport.default.md",
    "content": "# Class: default\n\n[Frontend/Game/Viewport](../modules/Frontend_Game_Viewport.md).default\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Game_Viewport.default.md#constructor)\n\n### Properties\n\n- [canvas](Frontend_Game_Viewport.default.md#canvas)\n- [centerWorldCoords](Frontend_Game_Viewport.default.md#centerworldcoords)\n- [diagnosticUpdater](Frontend_Game_Viewport.default.md#diagnosticupdater)\n- [frameRequestId](Frontend_Game_Viewport.default.md#framerequestid)\n- [gameUIManager](Frontend_Game_Viewport.default.md#gameuimanager)\n- [heightInWorldUnits](Frontend_Game_Viewport.default.md#heightinworldunits)\n- [intervalId](Frontend_Game_Viewport.default.md#intervalid)\n- [isFirefox](Frontend_Game_Viewport.default.md#isfirefox)\n- [isPanning](Frontend_Game_Viewport.default.md#ispanning)\n- [isSending](Frontend_Game_Viewport.default.md#issending)\n- [momentum](Frontend_Game_Viewport.default.md#momentum)\n- [mouseLastCoords](Frontend_Game_Viewport.default.md#mouselastcoords)\n- [mouseSensitivity](Frontend_Game_Viewport.default.md#mousesensitivity)\n- [mousedownCoords](Frontend_Game_Viewport.default.md#mousedowncoords)\n- [scale](Frontend_Game_Viewport.default.md#scale)\n- [velocity](Frontend_Game_Viewport.default.md#velocity)\n- [viewportHeight](Frontend_Game_Viewport.default.md#viewportheight)\n- [viewportWidth](Frontend_Game_Viewport.default.md#viewportwidth)\n- [widthInWorldUnits](Frontend_Game_Viewport.default.md#widthinworldunits)\n- [instance](Frontend_Game_Viewport.default.md#instance)\n\n### Accessors\n\n- [maxWorldWidth](Frontend_Game_Viewport.default.md#maxworldwidth)\n- [minWorldWidth](Frontend_Game_Viewport.default.md#minworldwidth)\n\n### Methods\n\n- [canvasToWorldCoords](Frontend_Game_Viewport.default.md#canvastoworldcoords)\n- [canvasToWorldDist](Frontend_Game_Viewport.default.md#canvastoworlddist)\n- [canvasToWorldX](Frontend_Game_Viewport.default.md#canvastoworldx)\n- [canvasToWorldY](Frontend_Game_Viewport.default.md#canvastoworldy)\n- [centerChunk](Frontend_Game_Viewport.default.md#centerchunk)\n- [centerCoords](Frontend_Game_Viewport.default.md#centercoords)\n- [centerPlanet](Frontend_Game_Viewport.default.md#centerplanet)\n- [getBottomBound](Frontend_Game_Viewport.default.md#getbottombound)\n- [getLeftBound](Frontend_Game_Viewport.default.md#getleftbound)\n- [getRightBound](Frontend_Game_Viewport.default.md#getrightbound)\n- [getStorage](Frontend_Game_Viewport.default.md#getstorage)\n- [getStorageKey](Frontend_Game_Viewport.default.md#getstoragekey)\n- [getTopBound](Frontend_Game_Viewport.default.md#gettopbound)\n- [getViewportPosition](Frontend_Game_Viewport.default.md#getviewportposition)\n- [getViewportWorldHeight](Frontend_Game_Viewport.default.md#getviewportworldheight)\n- [getViewportWorldWidth](Frontend_Game_Viewport.default.md#getviewportworldwidth)\n- [intersectsViewport](Frontend_Game_Viewport.default.md#intersectsviewport)\n- [isInOrAroundViewport](Frontend_Game_Viewport.default.md#isinoraroundviewport)\n- [isInViewport](Frontend_Game_Viewport.default.md#isinviewport)\n- [isValidWorldWidth](Frontend_Game_Viewport.default.md#isvalidworldwidth)\n- [onMouseDown](Frontend_Game_Viewport.default.md#onmousedown)\n- [onMouseMove](Frontend_Game_Viewport.default.md#onmousemove)\n- [onMouseOut](Frontend_Game_Viewport.default.md#onmouseout)\n- [onMouseUp](Frontend_Game_Viewport.default.md#onmouseup)\n- [onResize](Frontend_Game_Viewport.default.md#onresize)\n- [onScroll](Frontend_Game_Viewport.default.md#onscroll)\n- [onSendComplete](Frontend_Game_Viewport.default.md#onsendcomplete)\n- [onSendInit](Frontend_Game_Viewport.default.md#onsendinit)\n- [onWindowResize](Frontend_Game_Viewport.default.md#onwindowresize)\n- [setData](Frontend_Game_Viewport.default.md#setdata)\n- [setDiagnosticUpdater](Frontend_Game_Viewport.default.md#setdiagnosticupdater)\n- [setMouseSensitivty](Frontend_Game_Viewport.default.md#setmousesensitivty)\n- [setStorage](Frontend_Game_Viewport.default.md#setstorage)\n- [setWorldHeight](Frontend_Game_Viewport.default.md#setworldheight)\n- [setWorldWidth](Frontend_Game_Viewport.default.md#setworldwidth)\n- [updateDiagnostics](Frontend_Game_Viewport.default.md#updatediagnostics)\n- [worldToCanvasCoords](Frontend_Game_Viewport.default.md#worldtocanvascoords)\n- [worldToCanvasDist](Frontend_Game_Viewport.default.md#worldtocanvasdist)\n- [worldToCanvasX](Frontend_Game_Viewport.default.md#worldtocanvasx)\n- [worldToCanvasY](Frontend_Game_Viewport.default.md#worldtocanvasy)\n- [zoomIn](Frontend_Game_Viewport.default.md#zoomin)\n- [zoomOut](Frontend_Game_Viewport.default.md#zoomout)\n- [zoomPlanet](Frontend_Game_Viewport.default.md#zoomplanet)\n- [destroyInstance](Frontend_Game_Viewport.default.md#destroyinstance)\n- [getInstance](Frontend_Game_Viewport.default.md#getinstance)\n- [initialize](Frontend_Game_Viewport.default.md#initialize)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**(`gameUIManager`, `centerWorldCoords`, `widthInWorldUnits`, `viewportWidth`, `viewportHeight`, `canvas`)\n\n#### Parameters\n\n| Name                | Type                                                    |\n| :------------------ | :------------------------------------------------------ |\n| `gameUIManager`     | [`default`](Backend_GameLogic_GameUIManager.default.md) |\n| `centerWorldCoords` | `WorldCoords`                                           |\n| `widthInWorldUnits` | `number`                                                |\n| `viewportWidth`     | `number`                                                |\n| `viewportHeight`    | `number`                                                |\n| `canvas`            | `HTMLCanvasElement`                                     |\n\n## Properties\n\n### canvas\n\n• **canvas**: `HTMLCanvasElement`\n\n---\n\n### centerWorldCoords\n\n• **centerWorldCoords**: `WorldCoords`\n\n---\n\n### diagnosticUpdater\n\n• `Optional` **diagnosticUpdater**: `DiagnosticUpdater`\n\n---\n\n### frameRequestId\n\n• **frameRequestId**: `number`\n\n---\n\n### gameUIManager\n\n• **gameUIManager**: [`default`](Backend_GameLogic_GameUIManager.default.md)\n\n---\n\n### heightInWorldUnits\n\n• **heightInWorldUnits**: `number`\n\n---\n\n### intervalId\n\n• **intervalId**: `Timeout`\n\n---\n\n### isFirefox\n\n• **isFirefox**: `boolean`\n\n---\n\n### isPanning\n\n• **isPanning**: `boolean` = `false`\n\n---\n\n### isSending\n\n• `Private` **isSending**: `boolean` = `false`\n\n---\n\n### momentum\n\n• **momentum**: `boolean` = `false`\n\n---\n\n### mouseLastCoords\n\n• **mouseLastCoords**: `undefined` \\| `CanvasCoords`\n\n---\n\n### mouseSensitivity\n\n• **mouseSensitivity**: `number`\n\n---\n\n### mousedownCoords\n\n• **mousedownCoords**: `undefined` \\| `CanvasCoords` = `undefined`\n\n---\n\n### scale\n\n• **scale**: `number`\n\n---\n\n### velocity\n\n• **velocity**: `undefined` \\| `WorldCoords` = `undefined`\n\n---\n\n### viewportHeight\n\n• **viewportHeight**: `number`\n\n---\n\n### viewportWidth\n\n• **viewportWidth**: `number`\n\n---\n\n### widthInWorldUnits\n\n• **widthInWorldUnits**: `number`\n\n---\n\n### instance\n\n▪ `Static` **instance**: `undefined` \\| [`default`](Frontend_Game_Viewport.default.md)\n\n## Accessors\n\n### maxWorldWidth\n\n• `get` **maxWorldWidth**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### minWorldWidth\n\n• `get` **minWorldWidth**(): `number`\n\n#### Returns\n\n`number`\n\n## Methods\n\n### canvasToWorldCoords\n\n▸ **canvasToWorldCoords**(`canvasCoords`): `WorldCoords`\n\n#### Parameters\n\n| Name           | Type           |\n| :------------- | :------------- |\n| `canvasCoords` | `CanvasCoords` |\n\n#### Returns\n\n`WorldCoords`\n\n---\n\n### canvasToWorldDist\n\n▸ **canvasToWorldDist**(`d`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `d`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### canvasToWorldX\n\n▸ `Private` **canvasToWorldX**(`x`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### canvasToWorldY\n\n▸ `Private` **canvasToWorldY**(`y`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `y`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### centerChunk\n\n▸ **centerChunk**(`chunk`): `void`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`void`\n\n---\n\n### centerCoords\n\n▸ **centerCoords**(`coords`): `void`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### centerPlanet\n\n▸ **centerPlanet**(`planet`): `void`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`void`\n\n---\n\n### getBottomBound\n\n▸ **getBottomBound**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getLeftBound\n\n▸ **getLeftBound**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getRightBound\n\n▸ **getRightBound**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getStorage\n\n▸ **getStorage**(): `undefined` \\| `ViewportData`\n\n#### Returns\n\n`undefined` \\| `ViewportData`\n\n---\n\n### getStorageKey\n\n▸ `Private` **getStorageKey**(): `string`\n\n#### Returns\n\n`string`\n\n---\n\n### getTopBound\n\n▸ **getTopBound**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getViewportPosition\n\n▸ **getViewportPosition**(): `Object`\n\n#### Returns\n\n`Object`\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n---\n\n### getViewportWorldHeight\n\n▸ **getViewportWorldHeight**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### getViewportWorldWidth\n\n▸ **getViewportWorldWidth**(): `number`\n\n#### Returns\n\n`number`\n\n---\n\n### intersectsViewport\n\n▸ **intersectsViewport**(`chunk`): `boolean`\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isInOrAroundViewport\n\n▸ **isInOrAroundViewport**(`coords`): `boolean`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isInViewport\n\n▸ **isInViewport**(`coords`): `boolean`\n\n#### Parameters\n\n| Name     | Type          |\n| :------- | :------------ |\n| `coords` | `WorldCoords` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isValidWorldWidth\n\n▸ `Private` **isValidWorldWidth**(`width`): `boolean`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `width` | `number` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### onMouseDown\n\n▸ **onMouseDown**(`canvasCoords`): `void`\n\n#### Parameters\n\n| Name           | Type           |\n| :------------- | :------------- |\n| `canvasCoords` | `CanvasCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseMove\n\n▸ **onMouseMove**(`canvasCoords`): `void`\n\n#### Parameters\n\n| Name           | Type           |\n| :------------- | :------------- |\n| `canvasCoords` | `CanvasCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseOut\n\n▸ **onMouseOut**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onMouseUp\n\n▸ **onMouseUp**(`canvasCoords`): `void`\n\n#### Parameters\n\n| Name           | Type           |\n| :------------- | :------------- |\n| `canvasCoords` | `CanvasCoords` |\n\n#### Returns\n\n`void`\n\n---\n\n### onResize\n\n▸ **onResize**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onScroll\n\n▸ **onScroll**(`deltaY`, `forceZoom?`): `void`\n\n#### Parameters\n\n| Name        | Type      | Default value |\n| :---------- | :-------- | :------------ |\n| `deltaY`    | `number`  | `undefined`   |\n| `forceZoom` | `boolean` | `false`       |\n\n#### Returns\n\n`void`\n\n---\n\n### onSendComplete\n\n▸ **onSendComplete**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onSendInit\n\n▸ **onSendInit**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### onWindowResize\n\n▸ **onWindowResize**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### setData\n\n▸ **setData**(`data`): `void`\n\n#### Parameters\n\n| Name   | Type           |\n| :----- | :------------- |\n| `data` | `ViewportData` |\n\n#### Returns\n\n`void`\n\n---\n\n### setDiagnosticUpdater\n\n▸ **setDiagnosticUpdater**(`diagnosticUpdater`): `void`\n\n#### Parameters\n\n| Name                | Type                |\n| :------------------ | :------------------ |\n| `diagnosticUpdater` | `DiagnosticUpdater` |\n\n#### Returns\n\n`void`\n\n---\n\n### setMouseSensitivty\n\n▸ **setMouseSensitivty**(`mouseSensitivity`): `void`\n\n#### Parameters\n\n| Name               | Type     |\n| :----------------- | :------- |\n| `mouseSensitivity` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setStorage\n\n▸ **setStorage**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### setWorldHeight\n\n▸ **setWorldHeight**(`height`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `height` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setWorldWidth\n\n▸ `Private` **setWorldWidth**(`width`): `void`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `width` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### updateDiagnostics\n\n▸ `Private` **updateDiagnostics**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### worldToCanvasCoords\n\n▸ **worldToCanvasCoords**(`worldCoords`): `CanvasCoords`\n\n#### Parameters\n\n| Name          | Type          |\n| :------------ | :------------ |\n| `worldCoords` | `WorldCoords` |\n\n#### Returns\n\n`CanvasCoords`\n\n---\n\n### worldToCanvasDist\n\n▸ **worldToCanvasDist**(`d`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `d`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### worldToCanvasX\n\n▸ `Private` **worldToCanvasX**(`x`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### worldToCanvasY\n\n▸ `Private` **worldToCanvasY**(`y`): `number`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `y`  | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### zoomIn\n\n▸ **zoomIn**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### zoomOut\n\n▸ **zoomOut**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### zoomPlanet\n\n▸ **zoomPlanet**(`planet?`, `radii?`): `void`\n\n#### Parameters\n\n| Name      | Type     |\n| :-------- | :------- |\n| `planet?` | `Planet` |\n| `radii?`  | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### destroyInstance\n\n▸ `Static` **destroyInstance**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getInstance\n\n▸ `Static` **getInstance**(): [`default`](Frontend_Game_Viewport.default.md)\n\n#### Returns\n\n[`default`](Frontend_Game_Viewport.default.md)\n\n---\n\n### initialize\n\n▸ `Static` **initialize**(`gameUIManager`, `widthInWorldUnits`, `canvas`): [`default`](Frontend_Game_Viewport.default.md)\n\n#### Parameters\n\n| Name                | Type                                                    |\n| :------------------ | :------------------------------------------------------ |\n| `gameUIManager`     | [`default`](Backend_GameLogic_GameUIManager.default.md) |\n| `widthInWorldUnits` | `number`                                                |\n| `canvas`            | `HTMLCanvasElement`                                     |\n\n#### Returns\n\n[`default`](Frontend_Game_Viewport.default.md)\n"
  },
  {
    "path": "docs/classes/Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md",
    "content": "# Class: InvalidConfigError\n\n[Frontend/Panes/Lobbies/Reducer](../modules/Frontend_Panes_Lobbies_Reducer.md).InvalidConfigError\n\n## Hierarchy\n\n- `Error`\n\n  ↳ **`InvalidConfigError`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#constructor)\n\n### Properties\n\n- [key](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#key)\n- [value](Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md#value)\n\n## Constructors\n\n### constructor\n\n• **new InvalidConfigError**(`msg`, `key`, `value`)\n\n#### Parameters\n\n| Name    | Type      |\n| :------ | :-------- |\n| `msg`   | `string`  |\n| `key`   | `string`  |\n| `value` | `unknown` |\n\n#### Overrides\n\nError.constructor\n\n## Properties\n\n### key\n\n• **key**: `string`\n\n---\n\n### value\n\n• **value**: `unknown`\n"
  },
  {
    "path": "docs/classes/Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md",
    "content": "# Class: ArtifactRenderer\n\n[Frontend/Renderers/Artifacts/ArtifactRenderer](../modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md).ArtifactRenderer\n\n## Hierarchy\n\n- `WebGLManager`\n\n  ↳ **`ArtifactRenderer`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#constructor)\n\n### Properties\n\n- [artifacts](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#artifacts)\n- [canvas](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#canvas)\n- [frameRequestId](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#framerequestid)\n- [gl](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#gl)\n- [isDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#isdex)\n- [projectionMatrix](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#projectionmatrix)\n- [scroll](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#scroll)\n- [spriteRenderer](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#spriterenderer)\n- [visible](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#visible)\n\n### Methods\n\n- [clear](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#clear)\n- [containsArtifact](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#containsartifact)\n- [destroy](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#destroy)\n- [draw](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#draw)\n- [drawDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#drawdex)\n- [drawList](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#drawlist)\n- [getTexIdx](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#gettexidx)\n- [loop](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#loop)\n- [queueArtifactColumn](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#queueartifactcolumn)\n- [queueRarityColumn](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#queueraritycolumn)\n- [setArtifacts](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setartifacts)\n- [setIsDex](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setisdex)\n- [setProjectionMatrix](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setprojectionmatrix)\n- [setScroll](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setscroll)\n- [setVisible](Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md#setvisible)\n\n## Constructors\n\n### constructor\n\n• **new ArtifactRenderer**(`canvas`, `isDex?`)\n\n#### Parameters\n\n| Name     | Type                | Default value |\n| :------- | :------------------ | :------------ |\n| `canvas` | `HTMLCanvasElement` | `undefined`   |\n| `isDex`  | `boolean`           | `true`        |\n\n#### Overrides\n\nWebGLManager.constructor\n\n## Properties\n\n### artifacts\n\n• `Private` **artifacts**: `Artifact`[]\n\n---\n\n### canvas\n\n• **canvas**: `HTMLCanvasElement`\n\n#### Inherited from\n\nWebGLManager.canvas\n\n---\n\n### frameRequestId\n\n• `Private` **frameRequestId**: `number`\n\n---\n\n### gl\n\n• **gl**: `WebGL2RenderingContext`\n\n#### Inherited from\n\nWebGLManager.gl\n\n---\n\n### isDex\n\n• `Private` **isDex**: `boolean`\n\n---\n\n### projectionMatrix\n\n• **projectionMatrix**: `mat4`\n\n#### Inherited from\n\nWebGLManager.projectionMatrix\n\n---\n\n### scroll\n\n• `Private` **scroll**: `number` = `0`\n\n---\n\n### spriteRenderer\n\n• `Private` **spriteRenderer**: `SpriteRenderer`\n\n---\n\n### visible\n\n• `Private` **visible**: `boolean` = `false`\n\n## Methods\n\n### clear\n\n▸ **clear**(`bits?`, `color?`): `void`\n\n#### Parameters\n\n| Name     | Type      |\n| :------- | :-------- |\n| `bits?`  | `number`  |\n| `color?` | `RGBAVec` |\n\n#### Returns\n\n`void`\n\n#### Inherited from\n\nWebGLManager.clear\n\n---\n\n### containsArtifact\n\n▸ `Private` **containsArtifact**(`biome`, `rarity`, `type`): `boolean`\n\n#### Parameters\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `biome`  | `Biome`          |\n| `rarity` | `ArtifactRarity` |\n| `type`   | `ArtifactType`   |\n\n#### Returns\n\n`boolean`\n\n---\n\n### destroy\n\n▸ **destroy**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### draw\n\n▸ `Private` **draw**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### drawDex\n\n▸ `Private` **drawDex**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### drawList\n\n▸ `Private` **drawList**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getTexIdx\n\n▸ **getTexIdx**(): `number`\n\n#### Returns\n\n`number`\n\n#### Inherited from\n\nWebGLManager.getTexIdx\n\n---\n\n### loop\n\n▸ `Private` **loop**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### queueArtifactColumn\n\n▸ `Private` **queueArtifactColumn**(`type`, `rarity`, `startX`): `void`\n\n#### Parameters\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `type`   | `ArtifactType`   |\n| `rarity` | `ArtifactRarity` |\n| `startX` | `number`         |\n\n#### Returns\n\n`void`\n\n---\n\n### queueRarityColumn\n\n▸ `Private` **queueRarityColumn**(`rarity`, `startX`): `void`\n\n#### Parameters\n\n| Name     | Type             |\n| :------- | :--------------- |\n| `rarity` | `ArtifactRarity` |\n| `startX` | `number`         |\n\n#### Returns\n\n`void`\n\n---\n\n### setArtifacts\n\n▸ **setArtifacts**(`artifacts`): `void`\n\n#### Parameters\n\n| Name        | Type         |\n| :---------- | :----------- |\n| `artifacts` | `Artifact`[] |\n\n#### Returns\n\n`void`\n\n---\n\n### setIsDex\n\n▸ **setIsDex**(`isDex`): `void`\n\n#### Parameters\n\n| Name    | Type      |\n| :------ | :-------- |\n| `isDex` | `boolean` |\n\n#### Returns\n\n`void`\n\n---\n\n### setProjectionMatrix\n\n▸ **setProjectionMatrix**(): `void`\n\n#### Returns\n\n`void`\n\n#### Inherited from\n\nWebGLManager.setProjectionMatrix\n\n---\n\n### setScroll\n\n▸ **setScroll**(`scroll`): `void`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `scroll` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setVisible\n\n▸ **setVisible**(`visible`): `void`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `visible` | `boolean` |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/classes/Frontend_Renderers_GifRenderer.GifRenderer.md",
    "content": "# Class: GifRenderer\n\n[Frontend/Renderers/GifRenderer](../modules/Frontend_Renderers_GifRenderer.md).GifRenderer\n\n## Hierarchy\n\n- `WebGLManager`\n\n  ↳ **`GifRenderer`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Renderers_GifRenderer.GifRenderer.md#constructor)\n\n### Properties\n\n- [artifactDim](Frontend_Renderers_GifRenderer.GifRenderer.md#artifactdim)\n- [canvas](Frontend_Renderers_GifRenderer.GifRenderer.md#canvas)\n- [canvasDim](Frontend_Renderers_GifRenderer.GifRenderer.md#canvasdim)\n- [gl](Frontend_Renderers_GifRenderer.GifRenderer.md#gl)\n- [margin](Frontend_Renderers_GifRenderer.GifRenderer.md#margin)\n- [projectionMatrix](Frontend_Renderers_GifRenderer.GifRenderer.md#projectionmatrix)\n- [resolution](Frontend_Renderers_GifRenderer.GifRenderer.md#resolution)\n- [spriteRenderer](Frontend_Renderers_GifRenderer.GifRenderer.md#spriterenderer)\n- [thumb](Frontend_Renderers_GifRenderer.GifRenderer.md#thumb)\n\n### Methods\n\n- [addAncient](Frontend_Renderers_GifRenderer.GifRenderer.md#addancient)\n- [addBiomes](Frontend_Renderers_GifRenderer.GifRenderer.md#addbiomes)\n- [addSprite](Frontend_Renderers_GifRenderer.GifRenderer.md#addsprite)\n- [addVideo](Frontend_Renderers_GifRenderer.GifRenderer.md#addvideo)\n- [clear](Frontend_Renderers_GifRenderer.GifRenderer.md#clear)\n- [drawSprite](Frontend_Renderers_GifRenderer.GifRenderer.md#drawsprite)\n- [getAll](Frontend_Renderers_GifRenderer.GifRenderer.md#getall)\n- [getAllSprites](Frontend_Renderers_GifRenderer.GifRenderer.md#getallsprites)\n- [getAllVideos](Frontend_Renderers_GifRenderer.GifRenderer.md#getallvideos)\n- [getBase64](Frontend_Renderers_GifRenderer.GifRenderer.md#getbase64)\n- [getFileName](Frontend_Renderers_GifRenderer.GifRenderer.md#getfilename)\n- [getTexIdx](Frontend_Renderers_GifRenderer.GifRenderer.md#gettexidx)\n- [setDim](Frontend_Renderers_GifRenderer.GifRenderer.md#setdim)\n- [setProjectionMatrix](Frontend_Renderers_GifRenderer.GifRenderer.md#setprojectionmatrix)\n\n## Constructors\n\n### constructor\n\n• **new GifRenderer**(`canvas`, `dim`, `isThumb`)\n\n#### Parameters\n\n| Name      | Type                |\n| :-------- | :------------------ |\n| `canvas`  | `HTMLCanvasElement` |\n| `dim`     | `number`            |\n| `isThumb` | `boolean`           |\n\n#### Overrides\n\nWebGLManager.constructor\n\n## Properties\n\n### artifactDim\n\n• `Private` **artifactDim**: `number`\n\n---\n\n### canvas\n\n• **canvas**: `HTMLCanvasElement`\n\n#### Inherited from\n\nWebGLManager.canvas\n\n---\n\n### canvasDim\n\n• `Private` **canvasDim**: `number`\n\n---\n\n### gl\n\n• **gl**: `WebGL2RenderingContext`\n\n#### Inherited from\n\nWebGLManager.gl\n\n---\n\n### margin\n\n• `Private` **margin**: `number`\n\n---\n\n### projectionMatrix\n\n• **projectionMatrix**: `mat4`\n\n#### Overrides\n\nWebGLManager.projectionMatrix\n\n---\n\n### resolution\n\n• `Private` **resolution**: `number`\n\n---\n\n### spriteRenderer\n\n• `Private` **spriteRenderer**: `SpriteRenderer`\n\n---\n\n### thumb\n\n• `Private` **thumb**: `boolean`\n\n## Methods\n\n### addAncient\n\n▸ `Private` **addAncient**(`videoMode`, `dir`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name        | Type      |\n| :---------- | :-------- |\n| `videoMode` | `boolean` |\n| `dir`       | `JSZip`   |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### addBiomes\n\n▸ `Private` **addBiomes**(`videoMode`, `dir`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name        | Type      |\n| :---------- | :-------- |\n| `videoMode` | `boolean` |\n| `dir`       | `JSZip`   |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### addSprite\n\n▸ `Private` **addSprite**(`dir`, `type`, `biome`, `rarity`, `ancient?`): `void`\n\n#### Parameters\n\n| Name      | Type             | Default value |\n| :-------- | :--------------- | :------------ |\n| `dir`     | `JSZip`          | `undefined`   |\n| `type`    | `ArtifactType`   | `undefined`   |\n| `biome`   | `Biome`          | `undefined`   |\n| `rarity`  | `ArtifactRarity` | `undefined`   |\n| `ancient` | `boolean`        | `false`       |\n\n#### Returns\n\n`void`\n\n---\n\n### addVideo\n\n▸ `Private` **addVideo**(`dir`, `type`, `biome`, `rarity`, `ancient?`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type             | Default value |\n| :-------- | :--------------- | :------------ |\n| `dir`     | `JSZip`          | `undefined`   |\n| `type`    | `ArtifactType`   | `undefined`   |\n| `biome`   | `Biome`          | `undefined`   |\n| `rarity`  | `ArtifactRarity` | `undefined`   |\n| `ancient` | `boolean`        | `false`       |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### clear\n\n▸ **clear**(): `void`\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nWebGLManager.clear\n\n---\n\n### drawSprite\n\n▸ `Private` **drawSprite**(`artifact`, `atFrame?`): `void`\n\n#### Parameters\n\n| Name       | Type                    | Default value |\n| :--------- | :---------------------- | :------------ |\n| `artifact` | `Artifact`              | `undefined`   |\n| `atFrame`  | `undefined` \\| `number` | `undefined`   |\n\n#### Returns\n\n`void`\n\n---\n\n### getAll\n\n▸ `Private` **getAll**(`videoMode?`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name        | Type      | Default value |\n| :---------- | :-------- | :------------ |\n| `videoMode` | `boolean` | `false`       |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### getAllSprites\n\n▸ **getAllSprites**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getAllVideos\n\n▸ **getAllVideos**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getBase64\n\n▸ `Private` **getBase64**(): `string`\n\n#### Returns\n\n`string`\n\n---\n\n### getFileName\n\n▸ `Private` **getFileName**(`video`, `type`, `biome`, `rarity`, `ancient`): `string`\n\n#### Parameters\n\n| Name      | Type             |\n| :-------- | :--------------- |\n| `video`   | `boolean`        |\n| `type`    | `ArtifactType`   |\n| `biome`   | `Biome`          |\n| `rarity`  | `ArtifactRarity` |\n| `ancient` | `boolean`        |\n\n#### Returns\n\n`string`\n\n---\n\n### getTexIdx\n\n▸ **getTexIdx**(): `number`\n\n#### Returns\n\n`number`\n\n#### Inherited from\n\nWebGLManager.getTexIdx\n\n---\n\n### setDim\n\n▸ `Private` **setDim**(`dim`): `void`\n\n#### Parameters\n\n| Name  | Type     |\n| :---- | :------- |\n| `dim` | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setProjectionMatrix\n\n▸ **setProjectionMatrix**(): `void`\n\n#### Returns\n\n`void`\n\n#### Inherited from\n\nWebGLManager.setProjectionMatrix\n"
  },
  {
    "path": "docs/classes/Frontend_Utils_UIEmitter.default.md",
    "content": "# Class: default\n\n[Frontend/Utils/UIEmitter](../modules/Frontend_Utils_UIEmitter.md).default\n\n## Hierarchy\n\n- `EventEmitter`\n\n  ↳ **`default`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Utils_UIEmitter.default.md#constructor)\n\n### Properties\n\n- [instance](Frontend_Utils_UIEmitter.default.md#instance)\n\n### Methods\n\n- [getInstance](Frontend_Utils_UIEmitter.default.md#getinstance)\n- [initialize](Frontend_Utils_UIEmitter.default.md#initialize)\n\n## Constructors\n\n### constructor\n\n• `Private` **new default**()\n\n#### Overrides\n\nEventEmitter.constructor\n\n## Properties\n\n### instance\n\n▪ `Static` **instance**: [`default`](Frontend_Utils_UIEmitter.default.md)\n\n## Methods\n\n### getInstance\n\n▸ `Static` **getInstance**(): [`default`](Frontend_Utils_UIEmitter.default.md)\n\n#### Returns\n\n[`default`](Frontend_Utils_UIEmitter.default.md)\n\n---\n\n### initialize\n\n▸ `Static` **initialize**(): [`default`](Frontend_Utils_UIEmitter.default.md)\n\n#### Returns\n\n[`default`](Frontend_Utils_UIEmitter.default.md)\n"
  },
  {
    "path": "docs/classes/Frontend_Views_DFErrorBoundary.DFErrorBoundary.md",
    "content": "# Class: DFErrorBoundary\n\n[Frontend/Views/DFErrorBoundary](../modules/Frontend_Views_DFErrorBoundary.md).DFErrorBoundary\n\n## Hierarchy\n\n- `Component`<`unknown`, { `hasError`: `boolean` }\\>\n\n  ↳ **`DFErrorBoundary`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#constructor)\n\n### Methods\n\n- [componentDidCatch](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#componentdidcatch)\n- [render](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#render)\n- [getDerivedStateFromError](Frontend_Views_DFErrorBoundary.DFErrorBoundary.md#getderivedstatefromerror)\n\n## Constructors\n\n### constructor\n\n• **new DFErrorBoundary**(`props`)\n\n#### Parameters\n\n| Name    | Type      |\n| :------ | :-------- |\n| `props` | `unknown` |\n\n#### Overrides\n\nReact.Component&lt;unknown, { hasError: boolean }\\&gt;.constructor\n\n## Methods\n\n### componentDidCatch\n\n▸ **componentDidCatch**(`error`, `_errorInfo`): `void`\n\n#### Parameters\n\n| Name         | Type        |\n| :----------- | :---------- |\n| `error`      | `Error`     |\n| `_errorInfo` | `ErrorInfo` |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nReact.Component.componentDidCatch\n\n---\n\n### render\n\n▸ **render**(): `ReactNode`\n\n#### Returns\n\n`ReactNode`\n\n#### Overrides\n\nReact.Component.render\n\n---\n\n### getDerivedStateFromError\n\n▸ `Static` **getDerivedStateFromError**(`_error`): `Object`\n\n#### Parameters\n\n| Name     | Type    |\n| :------- | :------ |\n| `_error` | `Error` |\n\n#### Returns\n\n`Object`\n\n| Name       | Type      |\n| :--------- | :-------- |\n| `hasError` | `boolean` |\n"
  },
  {
    "path": "docs/classes/Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md",
    "content": "# Class: GenericErrorBoundary\n\n[Frontend/Views/GenericErrorBoundary](../modules/Frontend_Views_GenericErrorBoundary.md).GenericErrorBoundary\n\n## Hierarchy\n\n- `Component`<`GenericErrorBoundaryProps`, { `hasError`: `boolean` }\\>\n\n  ↳ **`GenericErrorBoundary`**\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#constructor)\n\n### Methods\n\n- [componentDidCatch](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#componentdidcatch)\n- [render](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#render)\n- [getDerivedStateFromError](Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md#getderivedstatefromerror)\n\n## Constructors\n\n### constructor\n\n• **new GenericErrorBoundary**(`props`)\n\n#### Parameters\n\n| Name    | Type                        |\n| :------ | :-------------------------- |\n| `props` | `GenericErrorBoundaryProps` |\n\n#### Overrides\n\nReact.Component&lt;\nGenericErrorBoundaryProps,\n{ hasError: boolean }\n\\&gt;.constructor\n\n## Methods\n\n### componentDidCatch\n\n▸ **componentDidCatch**(`error`, `_errorInfo`): `void`\n\n#### Parameters\n\n| Name         | Type        |\n| :----------- | :---------- |\n| `error`      | `Error`     |\n| `_errorInfo` | `ErrorInfo` |\n\n#### Returns\n\n`void`\n\n#### Overrides\n\nReact.Component.componentDidCatch\n\n---\n\n### render\n\n▸ **render**(): `ReactNode`\n\n#### Returns\n\n`ReactNode`\n\n#### Overrides\n\nReact.Component.render\n\n---\n\n### getDerivedStateFromError\n\n▸ `Static` **getDerivedStateFromError**(`_error`): `Object`\n\n#### Parameters\n\n| Name     | Type    |\n| :------- | :------ |\n| `_error` | `Error` |\n\n#### Returns\n\n`Object`\n\n| Name       | Type      |\n| :--------- | :-------- |\n| `hasError` | `boolean` |\n"
  },
  {
    "path": "docs/enums/Backend_GameLogic_GameManager.GameManagerEvent.md",
    "content": "# Enumeration: GameManagerEvent\n\n[Backend/GameLogic/GameManager](../modules/Backend_GameLogic_GameManager.md).GameManagerEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [ArtifactUpdate](Backend_GameLogic_GameManager.GameManagerEvent.md#artifactupdate)\n- [DiscoveredNewChunk](Backend_GameLogic_GameManager.GameManagerEvent.md#discoverednewchunk)\n- [InitializedPlayer](Backend_GameLogic_GameManager.GameManagerEvent.md#initializedplayer)\n- [InitializedPlayerError](Backend_GameLogic_GameManager.GameManagerEvent.md#initializedplayererror)\n- [Moved](Backend_GameLogic_GameManager.GameManagerEvent.md#moved)\n- [PlanetUpdate](Backend_GameLogic_GameManager.GameManagerEvent.md#planetupdate)\n\n## Enumeration members\n\n### ArtifactUpdate\n\n• **ArtifactUpdate** = `\"ArtifactUpdate\"`\n\n---\n\n### DiscoveredNewChunk\n\n• **DiscoveredNewChunk** = `\"DiscoveredNewChunk\"`\n\n---\n\n### InitializedPlayer\n\n• **InitializedPlayer** = `\"InitializedPlayer\"`\n\n---\n\n### InitializedPlayerError\n\n• **InitializedPlayerError** = `\"InitializedPlayerError\"`\n\n---\n\n### Moved\n\n• **Moved** = `\"Moved\"`\n\n---\n\n### PlanetUpdate\n\n• **PlanetUpdate** = `\"PlanetUpdate\"`\n"
  },
  {
    "path": "docs/enums/Backend_GameLogic_GameUIManager.GameUIManagerEvent.md",
    "content": "# Enumeration: GameUIManagerEvent\n\n[Backend/GameLogic/GameUIManager](../modules/Backend_GameLogic_GameUIManager.md).GameUIManagerEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [InitializedPlayer](Backend_GameLogic_GameUIManager.GameUIManagerEvent.md#initializedplayer)\n- [InitializedPlayerError](Backend_GameLogic_GameUIManager.GameUIManagerEvent.md#initializedplayererror)\n\n## Enumeration members\n\n### InitializedPlayer\n\n• **InitializedPlayer** = `\"InitializedPlayer\"`\n\n---\n\n### InitializedPlayerError\n\n• **InitializedPlayerError** = `\"InitializedPlayerError\"`\n"
  },
  {
    "path": "docs/enums/Backend_GameLogic_TutorialManager.TutorialManagerEvent.md",
    "content": "# Enumeration: TutorialManagerEvent\n\n[Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).TutorialManagerEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [StateChanged](Backend_GameLogic_TutorialManager.TutorialManagerEvent.md#statechanged)\n\n## Enumeration members\n\n### StateChanged\n\n• **StateChanged** = `\"StateChanged\"`\n"
  },
  {
    "path": "docs/enums/Backend_GameLogic_TutorialManager.TutorialState.md",
    "content": "# Enumeration: TutorialState\n\n[Backend/GameLogic/TutorialManager](../modules/Backend_GameLogic_TutorialManager.md).TutorialState\n\n## Table of contents\n\n### Enumeration members\n\n- [AlmostCompleted](Backend_GameLogic_TutorialManager.TutorialState.md#almostcompleted)\n- [Completed](Backend_GameLogic_TutorialManager.TutorialState.md#completed)\n- [Deselect](Backend_GameLogic_TutorialManager.TutorialState.md#deselect)\n- [HomePlanet](Backend_GameLogic_TutorialManager.TutorialState.md#homeplanet)\n- [HowToGetScore](Backend_GameLogic_TutorialManager.TutorialState.md#howtogetscore)\n- [MinerMove](Backend_GameLogic_TutorialManager.TutorialState.md#minermove)\n- [MinerPause](Backend_GameLogic_TutorialManager.TutorialState.md#minerpause)\n- [None](Backend_GameLogic_TutorialManager.TutorialState.md#none)\n- [ScoringDetails](Backend_GameLogic_TutorialManager.TutorialState.md#scoringdetails)\n- [SendFleet](Backend_GameLogic_TutorialManager.TutorialState.md#sendfleet)\n- [SpaceJunk](Backend_GameLogic_TutorialManager.TutorialState.md#spacejunk)\n- [Spaceship](Backend_GameLogic_TutorialManager.TutorialState.md#spaceship)\n- [Terminal](Backend_GameLogic_TutorialManager.TutorialState.md#terminal)\n- [Valhalla](Backend_GameLogic_TutorialManager.TutorialState.md#valhalla)\n- [ZoomOut](Backend_GameLogic_TutorialManager.TutorialState.md#zoomout)\n\n## Enumeration members\n\n### AlmostCompleted\n\n• **AlmostCompleted** = `13`\n\n---\n\n### Completed\n\n• **Completed** = `14`\n\n---\n\n### Deselect\n\n• **Deselect** = `5`\n\n---\n\n### HomePlanet\n\n• **HomePlanet** = `1`\n\n---\n\n### HowToGetScore\n\n• **HowToGetScore** = `10`\n\n---\n\n### MinerMove\n\n• **MinerMove** = `7`\n\n---\n\n### MinerPause\n\n• **MinerPause** = `8`\n\n---\n\n### None\n\n• **None** = `0`\n\n---\n\n### ScoringDetails\n\n• **ScoringDetails** = `11`\n\n---\n\n### SendFleet\n\n• **SendFleet** = `2`\n\n---\n\n### SpaceJunk\n\n• **SpaceJunk** = `3`\n\n---\n\n### Spaceship\n\n• **Spaceship** = `4`\n\n---\n\n### Terminal\n\n• **Terminal** = `9`\n\n---\n\n### Valhalla\n\n• **Valhalla** = `12`\n\n---\n\n### ZoomOut\n\n• **ZoomOut** = `6`\n"
  },
  {
    "path": "docs/enums/Backend_Miner_MinerManager.MinerManagerEvent.md",
    "content": "# Enumeration: MinerManagerEvent\n\n[Backend/Miner/MinerManager](../modules/Backend_Miner_MinerManager.md).MinerManagerEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [DiscoveredNewChunk](Backend_Miner_MinerManager.MinerManagerEvent.md#discoverednewchunk)\n\n## Enumeration members\n\n### DiscoveredNewChunk\n\n• **DiscoveredNewChunk** = `\"DiscoveredNewChunk\"`\n"
  },
  {
    "path": "docs/enums/Backend_Miner_MiningPatterns.MiningPatternType.md",
    "content": "# Enumeration: MiningPatternType\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).MiningPatternType\n\n## Table of contents\n\n### Enumeration members\n\n- [Cone](Backend_Miner_MiningPatterns.MiningPatternType.md#cone)\n- [ETH](Backend_Miner_MiningPatterns.MiningPatternType.md#eth)\n- [Grid](Backend_Miner_MiningPatterns.MiningPatternType.md#grid)\n- [Home](Backend_Miner_MiningPatterns.MiningPatternType.md#home)\n- [Spiral](Backend_Miner_MiningPatterns.MiningPatternType.md#spiral)\n- [SwissCheese](Backend_Miner_MiningPatterns.MiningPatternType.md#swisscheese)\n- [Target](Backend_Miner_MiningPatterns.MiningPatternType.md#target)\n- [TowardsCenter](Backend_Miner_MiningPatterns.MiningPatternType.md#towardscenter)\n- [TowardsCenterV2](Backend_Miner_MiningPatterns.MiningPatternType.md#towardscenterv2)\n\n## Enumeration members\n\n### Cone\n\n• **Cone** = `3`\n\n---\n\n### ETH\n\n• **ETH** = `5`\n\n---\n\n### Grid\n\n• **Grid** = `4`\n\n---\n\n### Home\n\n• **Home** = `0`\n\n---\n\n### Spiral\n\n• **Spiral** = `2`\n\n---\n\n### SwissCheese\n\n• **SwissCheese** = `6`\n\n---\n\n### Target\n\n• **Target** = `1`\n\n---\n\n### TowardsCenter\n\n• **TowardsCenter** = `7`\n\n---\n\n### TowardsCenterV2\n\n• **TowardsCenterV2** = `8`\n"
  },
  {
    "path": "docs/enums/Backend_Network_EventLogger.EventType.md",
    "content": "# Enumeration: EventType\n\n[Backend/Network/EventLogger](../modules/Backend_Network_EventLogger.md).EventType\n\n## Table of contents\n\n### Enumeration members\n\n- [Diagnostics](Backend_Network_EventLogger.EventType.md#diagnostics)\n- [Transaction](Backend_Network_EventLogger.EventType.md#transaction)\n\n## Enumeration members\n\n### Diagnostics\n\n• **Diagnostics** = `\"diagnostics\"`\n\n---\n\n### Transaction\n\n• **Transaction** = `\"transaction\"`\n"
  },
  {
    "path": "docs/enums/Backend_Network_UtilityServerAPI.EmailResponse.md",
    "content": "# Enumeration: EmailResponse\n\n[Backend/Network/UtilityServerAPI](../modules/Backend_Network_UtilityServerAPI.md).EmailResponse\n\n## Table of contents\n\n### Enumeration members\n\n- [Invalid](Backend_Network_UtilityServerAPI.EmailResponse.md#invalid)\n- [ServerError](Backend_Network_UtilityServerAPI.EmailResponse.md#servererror)\n- [Success](Backend_Network_UtilityServerAPI.EmailResponse.md#success)\n\n## Enumeration members\n\n### Invalid\n\n• **Invalid** = `1`\n\n---\n\n### ServerError\n\n• **ServerError** = `2`\n\n---\n\n### Success\n\n• **Success** = `0`\n"
  },
  {
    "path": "docs/enums/Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md",
    "content": "# Enumeration: SinglePlanetDataStoreEvent\n\n[Backend/Storage/ReaderDataStore](../modules/Backend_Storage_ReaderDataStore.md).SinglePlanetDataStoreEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [REFRESHED_ARTIFACT](Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md#refreshed_artifact)\n- [REFRESHED_PLANET](Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md#refreshed_planet)\n\n## Enumeration members\n\n### REFRESHED_ARTIFACT\n\n• **REFRESHED_ARTIFACT** = `\"REFRESHED_ARTIFACT\"`\n\n---\n\n### REFRESHED_PLANET\n\n• **REFRESHED_PLANET** = `\"REFRESHED_PLANET\"`\n"
  },
  {
    "path": "docs/enums/Frontend_Components_Email.EmailCTAMode.md",
    "content": "# Enumeration: EmailCTAMode\n\n[Frontend/Components/Email](../modules/Frontend_Components_Email.md).EmailCTAMode\n\n## Table of contents\n\n### Enumeration members\n\n- [SUBSCRIBE](Frontend_Components_Email.EmailCTAMode.md#subscribe)\n- [UNSUBSCRIBE](Frontend_Components_Email.EmailCTAMode.md#unsubscribe)\n\n## Enumeration members\n\n### SUBSCRIBE\n\n• **SUBSCRIBE** = `0`\n\n---\n\n### UNSUBSCRIBE\n\n• **UNSUBSCRIBE** = `1`\n"
  },
  {
    "path": "docs/enums/Frontend_Components_GameLandingPageComponents.InitRenderState.md",
    "content": "# Enumeration: InitRenderState\n\n[Frontend/Components/GameLandingPageComponents](../modules/Frontend_Components_GameLandingPageComponents.md).InitRenderState\n\n## Table of contents\n\n### Enumeration members\n\n- [COMPLETE](Frontend_Components_GameLandingPageComponents.InitRenderState.md#complete)\n- [LOADING](Frontend_Components_GameLandingPageComponents.InitRenderState.md#loading)\n- [NONE](Frontend_Components_GameLandingPageComponents.InitRenderState.md#none)\n\n## Enumeration members\n\n### COMPLETE\n\n• **COMPLETE** = `2`\n\n---\n\n### LOADING\n\n• **LOADING** = `1`\n\n---\n\n### NONE\n\n• **NONE** = `0`\n"
  },
  {
    "path": "docs/enums/Frontend_Game_NotificationManager.NotificationManagerEvent.md",
    "content": "# Enumeration: NotificationManagerEvent\n\n[Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).NotificationManagerEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [ClearNotification](Frontend_Game_NotificationManager.NotificationManagerEvent.md#clearnotification)\n- [Notify](Frontend_Game_NotificationManager.NotificationManagerEvent.md#notify)\n\n## Enumeration members\n\n### ClearNotification\n\n• **ClearNotification** = `\"ClearNotification\"`\n\n---\n\n### Notify\n\n• **Notify** = `\"Notify\"`\n"
  },
  {
    "path": "docs/enums/Frontend_Game_NotificationManager.NotificationType.md",
    "content": "# Enumeration: NotificationType\n\n[Frontend/Game/NotificationManager](../modules/Frontend_Game_NotificationManager.md).NotificationType\n\n## Table of contents\n\n### Enumeration members\n\n- [ArtifactFound](Frontend_Game_NotificationManager.NotificationType.md#artifactfound)\n- [ArtifactProspected](Frontend_Game_NotificationManager.NotificationType.md#artifactprospected)\n- [BalanceEmpty](Frontend_Game_NotificationManager.NotificationType.md#balanceempty)\n- [CanUpgrade](Frontend_Game_NotificationManager.NotificationType.md#canupgrade)\n- [FoundBiome](Frontend_Game_NotificationManager.NotificationType.md#foundbiome)\n- [FoundBiomeCorrupted](Frontend_Game_NotificationManager.NotificationType.md#foundbiomecorrupted)\n- [FoundBiomeDesert](Frontend_Game_NotificationManager.NotificationType.md#foundbiomedesert)\n- [FoundBiomeForest](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeforest)\n- [FoundBiomeGrassland](Frontend_Game_NotificationManager.NotificationType.md#foundbiomegrassland)\n- [FoundBiomeIce](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeice)\n- [FoundBiomeLava](Frontend_Game_NotificationManager.NotificationType.md#foundbiomelava)\n- [FoundBiomeOcean](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeocean)\n- [FoundBiomeSwamp](Frontend_Game_NotificationManager.NotificationType.md#foundbiomeswamp)\n- [FoundBiomeTundra](Frontend_Game_NotificationManager.NotificationType.md#foundbiometundra)\n- [FoundBiomeWasteland](Frontend_Game_NotificationManager.NotificationType.md#foundbiomewasteland)\n- [FoundComet](Frontend_Game_NotificationManager.NotificationType.md#foundcomet)\n- [FoundDeadSpace](Frontend_Game_NotificationManager.NotificationType.md#founddeadspace)\n- [FoundDeepSpace](Frontend_Game_NotificationManager.NotificationType.md#founddeepspace)\n- [FoundFoundry](Frontend_Game_NotificationManager.NotificationType.md#foundfoundry)\n- [FoundPirates](Frontend_Game_NotificationManager.NotificationType.md#foundpirates)\n- [FoundSilver](Frontend_Game_NotificationManager.NotificationType.md#foundsilver)\n- [FoundSilverBank](Frontend_Game_NotificationManager.NotificationType.md#foundsilverbank)\n- [FoundSpace](Frontend_Game_NotificationManager.NotificationType.md#foundspace)\n- [FoundTradingPost](Frontend_Game_NotificationManager.NotificationType.md#foundtradingpost)\n- [Generic](Frontend_Game_NotificationManager.NotificationType.md#generic)\n- [PlanetAttacked](Frontend_Game_NotificationManager.NotificationType.md#planetattacked)\n- [PlanetLost](Frontend_Game_NotificationManager.NotificationType.md#planetlost)\n- [PlanetWon](Frontend_Game_NotificationManager.NotificationType.md#planetwon)\n- [ReceivedPlanet](Frontend_Game_NotificationManager.NotificationType.md#receivedplanet)\n- [Tx](Frontend_Game_NotificationManager.NotificationType.md#tx)\n- [TxInitError](Frontend_Game_NotificationManager.NotificationType.md#txiniterror)\n- [WelcomePlayer](Frontend_Game_NotificationManager.NotificationType.md#welcomeplayer)\n\n## Enumeration members\n\n### ArtifactFound\n\n• **ArtifactFound** = `28`\n\n---\n\n### ArtifactProspected\n\n• **ArtifactProspected** = `27`\n\n---\n\n### BalanceEmpty\n\n• **BalanceEmpty** = `2`\n\n---\n\n### CanUpgrade\n\n• **CanUpgrade** = `1`\n\n---\n\n### FoundBiome\n\n• **FoundBiome** = `13`\n\n---\n\n### FoundBiomeCorrupted\n\n• **FoundBiomeCorrupted** = `23`\n\n---\n\n### FoundBiomeDesert\n\n• **FoundBiomeDesert** = `19`\n\n---\n\n### FoundBiomeForest\n\n• **FoundBiomeForest** = `15`\n\n---\n\n### FoundBiomeGrassland\n\n• **FoundBiomeGrassland** = `16`\n\n---\n\n### FoundBiomeIce\n\n• **FoundBiomeIce** = `20`\n\n---\n\n### FoundBiomeLava\n\n• **FoundBiomeLava** = `22`\n\n---\n\n### FoundBiomeOcean\n\n• **FoundBiomeOcean** = `14`\n\n---\n\n### FoundBiomeSwamp\n\n• **FoundBiomeSwamp** = `18`\n\n---\n\n### FoundBiomeTundra\n\n• **FoundBiomeTundra** = `17`\n\n---\n\n### FoundBiomeWasteland\n\n• **FoundBiomeWasteland** = `21`\n\n---\n\n### FoundComet\n\n• **FoundComet** = `11`\n\n---\n\n### FoundDeadSpace\n\n• **FoundDeadSpace** = `6`\n\n---\n\n### FoundDeepSpace\n\n• **FoundDeepSpace** = `5`\n\n---\n\n### FoundFoundry\n\n• **FoundFoundry** = `12`\n\n---\n\n### FoundPirates\n\n• **FoundPirates** = `7`\n\n---\n\n### FoundSilver\n\n• **FoundSilver** = `8`\n\n---\n\n### FoundSilverBank\n\n• **FoundSilverBank** = `9`\n\n---\n\n### FoundSpace\n\n• **FoundSpace** = `4`\n\n---\n\n### FoundTradingPost\n\n• **FoundTradingPost** = `10`\n\n---\n\n### Generic\n\n• **Generic** = `30`\n\n---\n\n### PlanetAttacked\n\n• **PlanetAttacked** = `26`\n\n---\n\n### PlanetLost\n\n• **PlanetLost** = `24`\n\n---\n\n### PlanetWon\n\n• **PlanetWon** = `25`\n\n---\n\n### ReceivedPlanet\n\n• **ReceivedPlanet** = `29`\n\n---\n\n### Tx\n\n• **Tx** = `0`\n\n---\n\n### TxInitError\n\n• **TxInitError** = `31`\n\n---\n\n### WelcomePlayer\n\n• **WelcomePlayer** = `3`\n"
  },
  {
    "path": "docs/enums/Frontend_Pages_LandingPage.LandingPageZIndex.md",
    "content": "# Enumeration: LandingPageZIndex\n\n[Frontend/Pages/LandingPage](../modules/Frontend_Pages_LandingPage.md).LandingPageZIndex\n\n## Table of contents\n\n### Enumeration members\n\n- [Background](Frontend_Pages_LandingPage.LandingPageZIndex.md#background)\n- [BasePage](Frontend_Pages_LandingPage.LandingPageZIndex.md#basepage)\n- [Canvas](Frontend_Pages_LandingPage.LandingPageZIndex.md#canvas)\n\n## Enumeration members\n\n### Background\n\n• **Background** = `0`\n\n---\n\n### BasePage\n\n• **BasePage** = `2`\n\n---\n\n### Canvas\n\n• **Canvas** = `1`\n"
  },
  {
    "path": "docs/enums/Frontend_Pages_UnsubscribePage.LandingPageZIndex.md",
    "content": "# Enumeration: LandingPageZIndex\n\n[Frontend/Pages/UnsubscribePage](../modules/Frontend_Pages_UnsubscribePage.md).LandingPageZIndex\n\n## Table of contents\n\n### Enumeration members\n\n- [Background](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#background)\n- [BasePage](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#basepage)\n- [Canvas](Frontend_Pages_UnsubscribePage.LandingPageZIndex.md#canvas)\n\n## Enumeration members\n\n### Background\n\n• **Background** = `0`\n\n---\n\n### BasePage\n\n• **BasePage** = `2`\n\n---\n\n### Canvas\n\n• **Canvas** = `1`\n"
  },
  {
    "path": "docs/enums/Frontend_Utils_BrowserChecks.Incompatibility.md",
    "content": "# Enumeration: Incompatibility\n\n[Frontend/Utils/BrowserChecks](../modules/Frontend_Utils_BrowserChecks.md).Incompatibility\n\n## Table of contents\n\n### Enumeration members\n\n- [MobileOrTablet](Frontend_Utils_BrowserChecks.Incompatibility.md#mobileortablet)\n- [NoIDB](Frontend_Utils_BrowserChecks.Incompatibility.md#noidb)\n- [NotLoggedInOrEnabled](Frontend_Utils_BrowserChecks.Incompatibility.md#notloggedinorenabled)\n- [NotRopsten](Frontend_Utils_BrowserChecks.Incompatibility.md#notropsten)\n- [UnexpectedError](Frontend_Utils_BrowserChecks.Incompatibility.md#unexpectederror)\n- [UnsupportedBrowser](Frontend_Utils_BrowserChecks.Incompatibility.md#unsupportedbrowser)\n\n## Enumeration members\n\n### MobileOrTablet\n\n• **MobileOrTablet** = `\"mobile_or_tablet\"`\n\n---\n\n### NoIDB\n\n• **NoIDB** = `\"no_idb\"`\n\n---\n\n### NotLoggedInOrEnabled\n\n• **NotLoggedInOrEnabled** = `\"not_logged_in_or_enabled\"`\n\n---\n\n### NotRopsten\n\n• **NotRopsten** = `\"not_ropsten\"`\n\n---\n\n### UnexpectedError\n\n• **UnexpectedError** = `\"unexpected_error\"`\n\n---\n\n### UnsupportedBrowser\n\n• **UnsupportedBrowser** = `\"unsupported_browser\"`\n"
  },
  {
    "path": "docs/enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md",
    "content": "# Enumeration: TerminalTextStyle\n\n[Frontend/Utils/TerminalTypes](../modules/Frontend_Utils_TerminalTypes.md).TerminalTextStyle\n\n## Table of contents\n\n### Enumeration members\n\n- [Blue](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#blue)\n- [Green](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#green)\n- [Invisible](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#invisible)\n- [Mythic](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#mythic)\n- [Red](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#red)\n- [Sub](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#sub)\n- [Subber](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#subber)\n- [Text](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#text)\n- [Underline](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#underline)\n- [White](Frontend_Utils_TerminalTypes.TerminalTextStyle.md#white)\n\n## Enumeration members\n\n### Blue\n\n• **Blue** = `6`\n\n---\n\n### Green\n\n• **Green** = `0`\n\n---\n\n### Invisible\n\n• **Invisible** = `7`\n\n---\n\n### Mythic\n\n• **Mythic** = `9`\n\n---\n\n### Red\n\n• **Red** = `5`\n\n---\n\n### Sub\n\n• **Sub** = `1`\n\n---\n\n### Subber\n\n• **Subber** = `2`\n\n---\n\n### Text\n\n• **Text** = `3`\n\n---\n\n### Underline\n\n• **Underline** = `8`\n\n---\n\n### White\n\n• **White** = `4`\n"
  },
  {
    "path": "docs/enums/Frontend_Utils_UIEmitter.UIEmitterEvent.md",
    "content": "# Enumeration: UIEmitterEvent\n\n[Frontend/Utils/UIEmitter](../modules/Frontend_Utils_UIEmitter.md).UIEmitterEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [CanvasMouseDown](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmousedown)\n- [CanvasMouseMove](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmousemove)\n- [CanvasMouseOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmouseout)\n- [CanvasMouseUp](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasmouseup)\n- [CanvasScroll](Frontend_Utils_UIEmitter.UIEmitterEvent.md#canvasscroll)\n- [CenterPlanet](Frontend_Utils_UIEmitter.UIEmitterEvent.md#centerplanet)\n- [DepositArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#depositartifact)\n- [DepositToPlanet](Frontend_Utils_UIEmitter.UIEmitterEvent.md#deposittoplanet)\n- [GamePlanetSelected](Frontend_Utils_UIEmitter.UIEmitterEvent.md#gameplanetselected)\n- [SelectArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#selectartifact)\n- [SendCancelled](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendcancelled)\n- [SendCompleted](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendcompleted)\n- [SendInitiated](Frontend_Utils_UIEmitter.UIEmitterEvent.md#sendinitiated)\n- [ShowArtifact](Frontend_Utils_UIEmitter.UIEmitterEvent.md#showartifact)\n- [UIChange](Frontend_Utils_UIEmitter.UIEmitterEvent.md#uichange)\n- [WindowResize](Frontend_Utils_UIEmitter.UIEmitterEvent.md#windowresize)\n- [WorldMouseClick](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseclick)\n- [WorldMouseDown](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmousedown)\n- [WorldMouseMove](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmousemove)\n- [WorldMouseOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseout)\n- [WorldMouseUp](Frontend_Utils_UIEmitter.UIEmitterEvent.md#worldmouseup)\n- [ZoomIn](Frontend_Utils_UIEmitter.UIEmitterEvent.md#zoomin)\n- [ZoomOut](Frontend_Utils_UIEmitter.UIEmitterEvent.md#zoomout)\n\n## Enumeration members\n\n### CanvasMouseDown\n\n• **CanvasMouseDown** = `\"CanvasMouseDown\"`\n\n---\n\n### CanvasMouseMove\n\n• **CanvasMouseMove** = `\"CanvasMouseMove\"`\n\n---\n\n### CanvasMouseOut\n\n• **CanvasMouseOut** = `\"CanvasMouseOut\"`\n\n---\n\n### CanvasMouseUp\n\n• **CanvasMouseUp** = `\"CanvasMouseUp\"`\n\n---\n\n### CanvasScroll\n\n• **CanvasScroll** = `\"CanvasScroll\"`\n\n---\n\n### CenterPlanet\n\n• **CenterPlanet** = `\"CenterPlanet\"`\n\n---\n\n### DepositArtifact\n\n• **DepositArtifact** = `\"DepositArtifact\"`\n\n---\n\n### DepositToPlanet\n\n• **DepositToPlanet** = `\"DepositToPlanet\"`\n\n---\n\n### GamePlanetSelected\n\n• **GamePlanetSelected** = `\"GamePlanetSelected\"`\n\n---\n\n### SelectArtifact\n\n• **SelectArtifact** = `\"SelectArtifact\"`\n\n---\n\n### SendCancelled\n\n• **SendCancelled** = `\"SendCancelled\"`\n\n---\n\n### SendCompleted\n\n• **SendCompleted** = `\"SendCompleted\"`\n\n---\n\n### SendInitiated\n\n• **SendInitiated** = `\"SendInitiated\"`\n\n---\n\n### ShowArtifact\n\n• **ShowArtifact** = `\"ShowArtifact\"`\n\n---\n\n### UIChange\n\n• **UIChange** = `\"UIChange\"`\n\n---\n\n### WindowResize\n\n• **WindowResize** = `\"WindowResize\"`\n\n---\n\n### WorldMouseClick\n\n• **WorldMouseClick** = `\"WorldMouseClick\"`\n\n---\n\n### WorldMouseDown\n\n• **WorldMouseDown** = `\"WorldMouseDown\"`\n\n---\n\n### WorldMouseMove\n\n• **WorldMouseMove** = `\"WorldMouseMove\"`\n\n---\n\n### WorldMouseOut\n\n• **WorldMouseOut** = `\"WorldMouseOut\"`\n\n---\n\n### WorldMouseUp\n\n• **WorldMouseUp** = `\"WorldMouseUp\"`\n\n---\n\n### ZoomIn\n\n• **ZoomIn** = `\"ZoomIn\"`\n\n---\n\n### ZoomOut\n\n• **ZoomOut** = `\"ZoomOut\"`\n"
  },
  {
    "path": "docs/enums/Frontend_Utils_constants.DFZIndex.md",
    "content": "# Enumeration: DFZIndex\n\n[Frontend/Utils/constants](../modules/Frontend_Utils_constants.md).DFZIndex\n\n## Table of contents\n\n### Enumeration members\n\n- [HoverPlanet](Frontend_Utils_constants.DFZIndex.md#hoverplanet)\n- [MenuBar](Frontend_Utils_constants.DFZIndex.md#menubar)\n- [Modal](Frontend_Utils_constants.DFZIndex.md#modal)\n- [Notification](Frontend_Utils_constants.DFZIndex.md#notification)\n- [Tooltip](Frontend_Utils_constants.DFZIndex.md#tooltip)\n\n## Enumeration members\n\n### HoverPlanet\n\n• **HoverPlanet** = `1001`\n\n---\n\n### MenuBar\n\n• **MenuBar** = `4`\n\n---\n\n### Modal\n\n• **Modal** = `1001`\n\n---\n\n### Notification\n\n• **Notification** = `1000`\n\n---\n\n### Tooltip\n\n• **Tooltip** = `16000000`\n"
  },
  {
    "path": "docs/enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md",
    "content": "# Enumeration: PlanetNotifType\n\n[Frontend/Views/PlanetNotifications](../modules/Frontend_Views_PlanetNotifications.md).PlanetNotifType\n\n## Table of contents\n\n### Enumeration members\n\n- [CanAddEmoji](Frontend_Views_PlanetNotifications.PlanetNotifType.md#canaddemoji)\n- [Claimed](Frontend_Views_PlanetNotifications.PlanetNotifType.md#claimed)\n- [DistanceFromCenter](Frontend_Views_PlanetNotifications.PlanetNotifType.md#distancefromcenter)\n- [PlanetCanUpgrade](Frontend_Views_PlanetNotifications.PlanetNotifType.md#planetcanupgrade)\n\n## Enumeration members\n\n### CanAddEmoji\n\n• **CanAddEmoji** = `3`\n\n---\n\n### Claimed\n\n• **Claimed** = `1`\n\n---\n\n### DistanceFromCenter\n\n• **DistanceFromCenter** = `2`\n\n---\n\n### PlanetCanUpgrade\n\n• **PlanetCanUpgrade** = `0`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.ContractEvent.md",
    "content": "# Enumeration: ContractEvent\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [AdminGiveSpaceship](types_darkforest_api_ContractsAPITypes.ContractEvent.md#admingivespaceship)\n- [AdminOwnershipChanged](types_darkforest_api_ContractsAPITypes.ContractEvent.md#adminownershipchanged)\n- [ArrivalQueued](types_darkforest_api_ContractsAPITypes.ContractEvent.md#arrivalqueued)\n- [ArtifactActivated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactactivated)\n- [ArtifactDeactivated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactdeactivated)\n- [ArtifactDeposited](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactdeposited)\n- [ArtifactFound](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactfound)\n- [ArtifactWithdrawn](types_darkforest_api_ContractsAPITypes.ContractEvent.md#artifactwithdrawn)\n- [LobbyCreated](types_darkforest_api_ContractsAPITypes.ContractEvent.md#lobbycreated)\n- [LocationRevealed](types_darkforest_api_ContractsAPITypes.ContractEvent.md#locationrevealed)\n- [PauseStateChanged](types_darkforest_api_ContractsAPITypes.ContractEvent.md#pausestatechanged)\n- [PlanetCaptured](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetcaptured)\n- [PlanetHatBought](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planethatbought)\n- [PlanetInvaded](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetinvaded)\n- [PlanetSilverWithdrawn](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetsilverwithdrawn)\n- [PlanetTransferred](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planettransferred)\n- [PlanetUpgraded](types_darkforest_api_ContractsAPITypes.ContractEvent.md#planetupgraded)\n- [PlayerInitialized](types_darkforest_api_ContractsAPITypes.ContractEvent.md#playerinitialized)\n\n## Enumeration members\n\n### AdminGiveSpaceship\n\n• **AdminGiveSpaceship** = `\"AdminGiveSpaceship\"`\n\n---\n\n### AdminOwnershipChanged\n\n• **AdminOwnershipChanged** = `\"AdminOwnershipChanged\"`\n\n---\n\n### ArrivalQueued\n\n• **ArrivalQueued** = `\"ArrivalQueued\"`\n\n---\n\n### ArtifactActivated\n\n• **ArtifactActivated** = `\"ArtifactActivated\"`\n\n---\n\n### ArtifactDeactivated\n\n• **ArtifactDeactivated** = `\"ArtifactDeactivated\"`\n\n---\n\n### ArtifactDeposited\n\n• **ArtifactDeposited** = `\"ArtifactDeposited\"`\n\n---\n\n### ArtifactFound\n\n• **ArtifactFound** = `\"ArtifactFound\"`\n\n---\n\n### ArtifactWithdrawn\n\n• **ArtifactWithdrawn** = `\"ArtifactWithdrawn\"`\n\n---\n\n### LobbyCreated\n\n• **LobbyCreated** = `\"LobbyCreated\"`\n\n---\n\n### LocationRevealed\n\n• **LocationRevealed** = `\"LocationRevealed\"`\n\n---\n\n### PauseStateChanged\n\n• **PauseStateChanged** = `\"PauseStateChanged\"`\n\n---\n\n### PlanetCaptured\n\n• **PlanetCaptured** = `\"PlanetCaptured\"`\n\n---\n\n### PlanetHatBought\n\n• **PlanetHatBought** = `\"PlanetHatBought\"`\n\n---\n\n### PlanetInvaded\n\n• **PlanetInvaded** = `\"PlanetInvaded\"`\n\n---\n\n### PlanetSilverWithdrawn\n\n• **PlanetSilverWithdrawn** = `\"PlanetSilverWithdrawn\"`\n\n---\n\n### PlanetTransferred\n\n• **PlanetTransferred** = `\"PlanetTransferred\"`\n\n---\n\n### PlanetUpgraded\n\n• **PlanetUpgraded** = `\"PlanetUpgraded\"`\n\n---\n\n### PlayerInitialized\n\n• **PlayerInitialized** = `\"PlayerInitialized\"`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md",
    "content": "# Enumeration: ContractsAPIEvent\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractsAPIEvent\n\n## Table of contents\n\n### Enumeration members\n\n- [ArrivalQueued](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#arrivalqueued)\n- [ArtifactUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#artifactupdate)\n- [LobbyCreated](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#lobbycreated)\n- [LocationRevealed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#locationrevealed)\n- [PauseStateChanged](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#pausestatechanged)\n- [PlanetClaimed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planetclaimed)\n- [PlanetTransferred](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planettransferred)\n- [PlanetUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#planetupdate)\n- [PlayerUpdate](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#playerupdate)\n- [RadiusUpdated](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#radiusupdated)\n- [TxCancelled](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txcancelled)\n- [TxConfirmed](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txconfirmed)\n- [TxErrored](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txerrored)\n- [TxPrioritized](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txprioritized)\n- [TxProcessing](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txprocessing)\n- [TxQueued](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txqueued)\n- [TxSubmitted](types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md#txsubmitted)\n\n## Enumeration members\n\n### ArrivalQueued\n\n• **ArrivalQueued** = `\"ArrivalQueued\"`\n\n---\n\n### ArtifactUpdate\n\n• **ArtifactUpdate** = `\"ArtifactUpdate\"`\n\n---\n\n### LobbyCreated\n\n• **LobbyCreated** = `\"LobbyCreated\"`\n\n---\n\n### LocationRevealed\n\n• **LocationRevealed** = `\"LocationRevealed\"`\n\n---\n\n### PauseStateChanged\n\n• **PauseStateChanged** = `\"PauseStateChanged\"`\n\n---\n\n### PlanetClaimed\n\n• **PlanetClaimed** = `\"PlanetClaimed\"`\n\n---\n\n### PlanetTransferred\n\n• **PlanetTransferred** = `\"PlanetTransferred\"`\n\n---\n\n### PlanetUpdate\n\n• **PlanetUpdate** = `\"PlanetUpdate\"`\n\n---\n\n### PlayerUpdate\n\n• **PlayerUpdate** = `\"PlayerUpdate\"`\n\n---\n\n### RadiusUpdated\n\n• **RadiusUpdated** = `\"RadiusUpdated\"`\n\n---\n\n### TxCancelled\n\n• **TxCancelled** = `\"TxCancelled\"`\n\nThe transaction was cancelled before it left the queue.\n\n---\n\n### TxConfirmed\n\n• **TxConfirmed** = `\"TxConfirmed\"`\n\nThe transaction has been confirmed.\n\n---\n\n### TxErrored\n\n• **TxErrored** = `\"TxErrored\"`\n\nThe transaction has failed for some reason. This\ncould either be a revert or a purely client side\nerror. In the case of a revert, the transaction hash\nwill be included in the transaction object.\n\n---\n\n### TxPrioritized\n\n• **TxPrioritized** = `\"TxPrioritized\"`\n\nThe transaction is queued, but is prioritized for execution\nabove other queued transactions.\n\n---\n\n### TxProcessing\n\n• **TxProcessing** = `\"TxProcessing\"`\n\nThe transaction has been removed from the queue and is\ncalculating arguments in preparation for submission.\n\n---\n\n### TxQueued\n\n• **TxQueued** = `\"TxQueued\"`\n\nThe transaction has been queued for future execution.\n\n---\n\n### TxSubmitted\n\n• **TxSubmitted** = `\"TxSubmitted\"`\n\nThe transaction has been submitted and we are awaiting\nconfirmation.\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.InitArgIdxs.md",
    "content": "# Enumeration: InitArgIdxs\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).InitArgIdxs\n\n## Table of contents\n\n### Enumeration members\n\n- [LOCATION_ID](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#location_id)\n- [PERLIN](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin)\n- [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_length_scale)\n- [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_mirror_x)\n- [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#perlin_mirror_y)\n- [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#planethash_key)\n- [RADIUS](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#radius)\n- [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.InitArgIdxs.md#spacetype_key)\n\n## Enumeration members\n\n### LOCATION_ID\n\n• **LOCATION_ID** = `0`\n\n---\n\n### PERLIN\n\n• **PERLIN** = `1`\n\n---\n\n### PERLIN_LENGTH_SCALE\n\n• **PERLIN_LENGTH_SCALE** = `5`\n\n---\n\n### PERLIN_MIRROR_X\n\n• **PERLIN_MIRROR_X** = `6`\n\n---\n\n### PERLIN_MIRROR_Y\n\n• **PERLIN_MIRROR_Y** = `7`\n\n---\n\n### PLANETHASH_KEY\n\n• **PLANETHASH_KEY** = `3`\n\n---\n\n### RADIUS\n\n• **RADIUS** = `2`\n\n---\n\n### SPACETYPE_KEY\n\n• **SPACETYPE_KEY** = `4`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md",
    "content": "# Enumeration: MoveArgIdxs\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).MoveArgIdxs\n\n## Table of contents\n\n### Enumeration members\n\n- [ARTIFACT_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#artifact_sent)\n- [DIST_MAX](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#dist_max)\n- [FROM_ID](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#from_id)\n- [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_length_scale)\n- [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_mirror_x)\n- [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#perlin_mirror_y)\n- [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#planethash_key)\n- [SHIPS_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#ships_sent)\n- [SILVER_SENT](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#silver_sent)\n- [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#spacetype_key)\n- [TO_ID](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_id)\n- [TO_PERLIN](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_perlin)\n- [TO_RADIUS](types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md#to_radius)\n\n## Enumeration members\n\n### ARTIFACT_SENT\n\n• **ARTIFACT_SENT** = `12`\n\n---\n\n### DIST_MAX\n\n• **DIST_MAX** = `4`\n\n---\n\n### FROM_ID\n\n• **FROM_ID** = `0`\n\n---\n\n### PERLIN_LENGTH_SCALE\n\n• **PERLIN_LENGTH_SCALE** = `7`\n\n---\n\n### PERLIN_MIRROR_X\n\n• **PERLIN_MIRROR_X** = `8`\n\n---\n\n### PERLIN_MIRROR_Y\n\n• **PERLIN_MIRROR_Y** = `9`\n\n---\n\n### PLANETHASH_KEY\n\n• **PLANETHASH_KEY** = `5`\n\n---\n\n### SHIPS_SENT\n\n• **SHIPS_SENT** = `10`\n\n---\n\n### SILVER_SENT\n\n• **SILVER_SENT** = `11`\n\n---\n\n### SPACETYPE_KEY\n\n• **SPACETYPE_KEY** = `6`\n\n---\n\n### TO_ID\n\n• **TO_ID** = `1`\n\n---\n\n### TO_PERLIN\n\n• **TO_PERLIN** = `2`\n\n---\n\n### TO_RADIUS\n\n• **TO_RADIUS** = `3`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.PlanetEventType.md",
    "content": "# Enumeration: PlanetEventType\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).PlanetEventType\n\n## Table of contents\n\n### Enumeration members\n\n- [ARRIVAL](types_darkforest_api_ContractsAPITypes.PlanetEventType.md#arrival)\n\n## Enumeration members\n\n### ARRIVAL\n\n• **ARRIVAL** = `0`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md",
    "content": "# Enumeration: UpgradeArgIdxs\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).UpgradeArgIdxs\n\n## Table of contents\n\n### Enumeration members\n\n- [LOCATION_ID](types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md#location_id)\n- [UPGRADE_BRANCH](types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md#upgrade_branch)\n\n## Enumeration members\n\n### LOCATION_ID\n\n• **LOCATION_ID** = `0`\n\n---\n\n### UPGRADE_BRANCH\n\n• **UPGRADE_BRANCH** = `1`\n"
  },
  {
    "path": "docs/enums/types_darkforest_api_ContractsAPITypes.ZKArgIdx.md",
    "content": "# Enumeration: ZKArgIdx\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ZKArgIdx\n\n## Table of contents\n\n### Enumeration members\n\n- [DATA](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#data)\n- [PROOF_A](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_a)\n- [PROOF_B](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_b)\n- [PROOF_C](types_darkforest_api_ContractsAPITypes.ZKArgIdx.md#proof_c)\n\n## Enumeration members\n\n### DATA\n\n• **DATA** = `3`\n\n---\n\n### PROOF_A\n\n• **PROOF_A** = `0`\n\n---\n\n### PROOF_B\n\n• **PROOF_B** = `1`\n\n---\n\n### PROOF_C\n\n• **PROOF_C** = `2`\n"
  },
  {
    "path": "docs/enums/types_global_GlobalTypes.StatIdx.md",
    "content": "# Enumeration: StatIdx\n\n[\\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).StatIdx\n\n## Table of contents\n\n### Enumeration members\n\n- [Defense](types_global_GlobalTypes.StatIdx.md#defense)\n- [EnergyCap](types_global_GlobalTypes.StatIdx.md#energycap)\n- [EnergyGro](types_global_GlobalTypes.StatIdx.md#energygro)\n- [Range](types_global_GlobalTypes.StatIdx.md#range)\n- [SpaceJunk](types_global_GlobalTypes.StatIdx.md#spacejunk)\n- [Speed](types_global_GlobalTypes.StatIdx.md#speed)\n\n## Enumeration members\n\n### Defense\n\n• **Defense** = `4`\n\n---\n\n### EnergyCap\n\n• **EnergyCap** = `0`\n\n---\n\n### EnergyGro\n\n• **EnergyGro** = `1`\n\n---\n\n### Range\n\n• **Range** = `2`\n\n---\n\n### SpaceJunk\n\n• **SpaceJunk** = `5`\n\n---\n\n### Speed\n\n• **Speed** = `3`\n"
  },
  {
    "path": "docs/interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md",
    "content": "# Interface: PlanetDiff\n\n[Backend/GameLogic/ArrivalUtils](../modules/Backend_GameLogic_ArrivalUtils.md).PlanetDiff\n\n**`param`** The previously calculated state of a planet\n\n**`param`** The current calculated state of the planet\n\n**`param`** The Arrival that caused the state change\n\n## Table of contents\n\n### Properties\n\n- [arrival](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#arrival)\n- [current](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#current)\n- [previous](Backend_GameLogic_ArrivalUtils.PlanetDiff.md#previous)\n\n## Properties\n\n### arrival\n\n• **arrival**: `QueuedArrival`\n\n---\n\n### current\n\n• **current**: `Planet`\n\n---\n\n### previous\n\n• **previous**: `Planet`\n"
  },
  {
    "path": "docs/interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md",
    "content": "# Interface: InitialGameState\n\n[Backend/GameLogic/InitialGameStateDownloader](../modules/Backend_GameLogic_InitialGameStateDownloader.md).InitialGameState\n\n## Table of contents\n\n### Properties\n\n- [allClaimedCoords](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#allclaimedcoords)\n- [allRevealedCoords](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#allrevealedcoords)\n- [allTouchedPlanetIds](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#alltouchedplanetids)\n- [arrivals](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#arrivals)\n- [artifactsOnVoyages](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#artifactsonvoyages)\n- [claimedCoordsMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#claimedcoordsmap)\n- [contractConstants](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#contractconstants)\n- [heldArtifacts](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#heldartifacts)\n- [loadedPlanets](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#loadedplanets)\n- [myArtifacts](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#myartifacts)\n- [paused](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#paused)\n- [pendingMoves](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#pendingmoves)\n- [planetVoyageIdMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#planetvoyageidmap)\n- [players](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#players)\n- [revealedCoordsMap](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#revealedcoordsmap)\n- [touchedAndLocatedPlanets](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#touchedandlocatedplanets)\n- [twitters](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#twitters)\n- [worldRadius](Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md#worldradius)\n\n## Properties\n\n### allClaimedCoords\n\n• `Optional` **allClaimedCoords**: `ClaimedCoords`[]\n\n---\n\n### allRevealedCoords\n\n• **allRevealedCoords**: `RevealedCoords`[]\n\n---\n\n### allTouchedPlanetIds\n\n• **allTouchedPlanetIds**: `LocationId`[]\n\n---\n\n### arrivals\n\n• **arrivals**: `Map`<`VoyageId`, `QueuedArrival`\\>\n\n---\n\n### artifactsOnVoyages\n\n• **artifactsOnVoyages**: `Artifact`[]\n\n---\n\n### claimedCoordsMap\n\n• `Optional` **claimedCoordsMap**: `Map`<`LocationId`, `ClaimedCoords`\\>\n\n---\n\n### contractConstants\n\n• **contractConstants**: [`ContractConstants`](types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n---\n\n### heldArtifacts\n\n• **heldArtifacts**: `Artifact`[][]\n\n---\n\n### loadedPlanets\n\n• **loadedPlanets**: `LocationId`[]\n\n---\n\n### myArtifacts\n\n• **myArtifacts**: `Artifact`[]\n\n---\n\n### paused\n\n• **paused**: `boolean`\n\n---\n\n### pendingMoves\n\n• **pendingMoves**: `QueuedArrival`[]\n\n---\n\n### planetVoyageIdMap\n\n• **planetVoyageIdMap**: `Map`<`LocationId`, `VoyageId`[]\\>\n\n---\n\n### players\n\n• **players**: `Map`<`string`, `Player`\\>\n\n---\n\n### revealedCoordsMap\n\n• **revealedCoordsMap**: `Map`<`LocationId`, `RevealedCoords`\\>\n\n---\n\n### touchedAndLocatedPlanets\n\n• **touchedAndLocatedPlanets**: `Map`<`LocationId`, `Planet`\\>\n\n---\n\n### twitters\n\n• **twitters**: [`AddressTwitterMap`](../modules/types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\n\n---\n\n### worldRadius\n\n• **worldRadius**: `number`\n"
  },
  {
    "path": "docs/interfaces/Backend_Miner_MiningPatterns.MiningPattern.md",
    "content": "# Interface: MiningPattern\n\n[Backend/Miner/MiningPatterns](../modules/Backend_Miner_MiningPatterns.md).MiningPattern\n\n## Implemented by\n\n- [`SpiralPattern`](../classes/Backend_Miner_MiningPatterns.SpiralPattern.md)\n- [`SwissCheesePattern`](../classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md)\n- [`TowardsCenterPattern`](../classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md)\n- [`TowardsCenterPatternV2`](../classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md)\n\n## Table of contents\n\n### Properties\n\n- [fromChunk](Backend_Miner_MiningPatterns.MiningPattern.md#fromchunk)\n- [type](Backend_Miner_MiningPatterns.MiningPattern.md#type)\n\n### Methods\n\n- [nextChunk](Backend_Miner_MiningPatterns.MiningPattern.md#nextchunk)\n\n## Properties\n\n### fromChunk\n\n• **fromChunk**: `Rectangle`\n\n---\n\n### type\n\n• **type**: [`MiningPatternType`](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md)\n\n## Methods\n\n### nextChunk\n\n▸ **nextChunk**(`prevLoc`): `Rectangle`\n\n#### Parameters\n\n| Name      | Type        |\n| :-------- | :---------- |\n| `prevLoc` | `Rectangle` |\n\n#### Returns\n\n`Rectangle`\n"
  },
  {
    "path": "docs/interfaces/Backend_Network_AccountManager.Account.md",
    "content": "# Interface: Account\n\n[Backend/Network/AccountManager](../modules/Backend_Network_AccountManager.md).Account\n\nRepresents an account with which the user plays the game.\n\n## Table of contents\n\n### Properties\n\n- [address](Backend_Network_AccountManager.Account.md#address)\n- [privateKey](Backend_Network_AccountManager.Account.md#privatekey)\n\n## Properties\n\n### address\n\n• **address**: `EthAddress`\n\n---\n\n### privateKey\n\n• **privateKey**: `string`\n"
  },
  {
    "path": "docs/interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md",
    "content": "# Interface: EmbeddedPlugin\n\n[Backend/Plugins/EmbeddedPluginLoader](../modules/Backend_Plugins_EmbeddedPluginLoader.md).EmbeddedPlugin\n\nThis interface represents an embedded plugin, which is stored in `embedded_plugins/`.\n\n## Table of contents\n\n### Properties\n\n- [code](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#code)\n- [id](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#id)\n- [name](Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md#name)\n\n## Properties\n\n### code\n\n• **code**: `string`\n\n---\n\n### id\n\n• **id**: `PluginId`\n\n---\n\n### name\n\n• **name**: `string`\n"
  },
  {
    "path": "docs/interfaces/Backend_Plugins_PluginProcess.PluginProcess.md",
    "content": "# Interface: PluginProcess\n\n[Backend/Plugins/PluginProcess](../modules/Backend_Plugins_PluginProcess.md).PluginProcess\n\nAll plugins must conform to this interface. Provides facilities for\ndisplaying an interactive UI, as well as references to game state,\nwhich are set externally.\n\n## Table of contents\n\n### Constructors\n\n- [constructor](Backend_Plugins_PluginProcess.PluginProcess.md#constructor)\n\n### Methods\n\n- [destroy](Backend_Plugins_PluginProcess.PluginProcess.md#destroy)\n- [draw](Backend_Plugins_PluginProcess.PluginProcess.md#draw)\n- [render](Backend_Plugins_PluginProcess.PluginProcess.md#render)\n\n## Constructors\n\n### constructor\n\n• **new PluginProcess**()\n\n## Methods\n\n### destroy\n\n▸ `Optional` **destroy**(): `void`\n\nCalled when the plugin is unloaded. Plugins unload whenever the\nplugin is edited (modified and saved, or deleted).\n\n#### Returns\n\n`void`\n\n---\n\n### draw\n\n▸ `Optional` **draw**(`ctx`): `void`\n\nIf present, called at the same framerate the the game is running at,\nand allows you to draw on top of the game UI.\n\n#### Parameters\n\n| Name  | Type                       |\n| :---- | :------------------------- |\n| `ctx` | `CanvasRenderingContext2D` |\n\n#### Returns\n\n`void`\n\n---\n\n### render\n\n▸ `Optional` **render**(`into`): `void`\n\nIf present, called once when the user clicks 'run' in the plugin\nmanager modal.\n\n#### Parameters\n\n| Name   | Type             |\n| :----- | :--------------- |\n| `into` | `HTMLDivElement` |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md",
    "content": "# Interface: SerializedPlugin\n\n[Backend/Plugins/SerializedPlugin](../modules/Backend_Plugins_SerializedPlugin.md).SerializedPlugin\n\nRepresents a plugin that the user has added to their game. Used\ninternally for storing plugins. Not used for evaluating plugins!\n\n## Table of contents\n\n### Properties\n\n- [code](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#code)\n- [id](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#id)\n- [lastEdited](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#lastedited)\n- [name](Backend_Plugins_SerializedPlugin.SerializedPlugin.md#name)\n\n## Properties\n\n### code\n\n• **code**: `string`\n\nThis code is a javascript object that complies with the\n[PluginProcess](Backend_Plugins_PluginProcess.PluginProcess.md) interface.\n\n---\n\n### id\n\n• **id**: `PluginId`\n\nUnique ID, assigned at the time the plugin is first saved.\n\n---\n\n### lastEdited\n\n• **lastEdited**: `number`\n\n{@code new Date.getTime()} at the point that this plugin was saved\n\n---\n\n### name\n\n• **name**: `string`\n\nShown in the list of plugins.\n"
  },
  {
    "path": "docs/interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md",
    "content": "# Interface: LoadingBarHandle\n\n[Frontend/Components/TextLoadingBar](../modules/Frontend_Components_TextLoadingBar.md).LoadingBarHandle\n\n## Table of contents\n\n### Methods\n\n- [setFractionCompleted](Frontend_Components_TextLoadingBar.LoadingBarHandle.md#setfractioncompleted)\n\n## Methods\n\n### setFractionCompleted\n\n▸ **setFractionCompleted**(`fractionCompleted`): `void`\n\n#### Parameters\n\n| Name                | Type     |\n| :------------------ | :------- |\n| `fractionCompleted` | `number` |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md",
    "content": "# Interface: LobbiesPaneProps\n\n[Frontend/Panes/Lobbies/LobbiesUtils](../modules/Frontend_Panes_Lobbies_LobbiesUtils.md).LobbiesPaneProps\n\n## Table of contents\n\n### Properties\n\n- [config](Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md#config)\n\n### Methods\n\n- [onUpdate](Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md#onupdate)\n\n## Properties\n\n### config\n\n• **config**: [`LobbyConfigState`](../modules/Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n\n## Methods\n\n### onUpdate\n\n▸ **onUpdate**(`change`): `void`\n\n#### Parameters\n\n| Name     | Type                                                                                  |\n| :------- | :------------------------------------------------------------------------------------ |\n| `change` | [`LobbyConfigAction`](../modules/Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Panes_Tooltip.TooltipProps.md",
    "content": "# Interface: TooltipProps\n\n[Frontend/Panes/Tooltip](../modules/Frontend_Panes_Tooltip.md).TooltipProps\n\n## Hierarchy\n\n- [`TooltipTriggerProps`](Frontend_Panes_Tooltip.TooltipTriggerProps.md)\n\n  ↳ **`TooltipProps`**\n\n## Table of contents\n\n### Properties\n\n- [children](Frontend_Panes_Tooltip.TooltipProps.md#children)\n- [extraContent](Frontend_Panes_Tooltip.TooltipProps.md#extracontent)\n- [left](Frontend_Panes_Tooltip.TooltipProps.md#left)\n- [name](Frontend_Panes_Tooltip.TooltipProps.md#name)\n- [style](Frontend_Panes_Tooltip.TooltipProps.md#style)\n- [top](Frontend_Panes_Tooltip.TooltipProps.md#top)\n\n## Properties\n\n### children\n\n• **children**: `ReactNode`\n\nA [TooltipTrigger](../modules/Frontend_Panes_Tooltip.md#tooltiptrigger) wraps this child, and causes a tooltip to appear when the user hovers\nover it.\n\n#### Inherited from\n\n[TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[children](Frontend_Panes_Tooltip.TooltipTriggerProps.md#children)\n\n---\n\n### extraContent\n\n• `Optional` **extraContent**: `ReactNode`\n\nYou can append some dynamic content to the given tooltip by setting this field to a React node.\n\n#### Inherited from\n\n[TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[extraContent](Frontend_Panes_Tooltip.TooltipTriggerProps.md#extracontent)\n\n---\n\n### left\n\n• **left**: `number`\n\n---\n\n### name\n\n• **name**: `undefined` \\| `TooltipName`\n\nThe name of the tooltip element to display. You can see all the concrete tooltip contents in\nthe file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip.\n\n#### Inherited from\n\n[TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[name](Frontend_Panes_Tooltip.TooltipTriggerProps.md#name)\n\n---\n\n### style\n\n• `Optional` **style**: `CSSProperties`\n\nYou can optionally style the tooltip trigger element, not the tooltip itself.\n\n#### Inherited from\n\n[TooltipTriggerProps](Frontend_Panes_Tooltip.TooltipTriggerProps.md).[style](Frontend_Panes_Tooltip.TooltipTriggerProps.md#style)\n\n---\n\n### top\n\n• **top**: `number`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md",
    "content": "# Interface: TooltipTriggerProps\n\n[Frontend/Panes/Tooltip](../modules/Frontend_Panes_Tooltip.md).TooltipTriggerProps\n\nEach {@link TooltipName} has a corresponding tooltip element.\n\n## Hierarchy\n\n- **`TooltipTriggerProps`**\n\n  ↳ [`TooltipProps`](Frontend_Panes_Tooltip.TooltipProps.md)\n\n## Table of contents\n\n### Properties\n\n- [children](Frontend_Panes_Tooltip.TooltipTriggerProps.md#children)\n- [extraContent](Frontend_Panes_Tooltip.TooltipTriggerProps.md#extracontent)\n- [name](Frontend_Panes_Tooltip.TooltipTriggerProps.md#name)\n- [style](Frontend_Panes_Tooltip.TooltipTriggerProps.md#style)\n\n## Properties\n\n### children\n\n• **children**: `ReactNode`\n\nA [TooltipTrigger](../modules/Frontend_Panes_Tooltip.md#tooltiptrigger) wraps this child, and causes a tooltip to appear when the user hovers\nover it.\n\n---\n\n### extraContent\n\n• `Optional` **extraContent**: `ReactNode`\n\nYou can append some dynamic content to the given tooltip by setting this field to a React node.\n\n---\n\n### name\n\n• **name**: `undefined` \\| `TooltipName`\n\nThe name of the tooltip element to display. You can see all the concrete tooltip contents in\nthe file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip.\n\n---\n\n### style\n\n• `Optional` **style**: `CSSProperties`\n\nYou can optionally style the tooltip trigger element, not the tooltip itself.\n"
  },
  {
    "path": "docs/interfaces/Frontend_Utils_EmitterUtils.Diff.md",
    "content": "# Interface: Diff<Type\\>\n\n[Frontend/Utils/EmitterUtils](../modules/Frontend_Utils_EmitterUtils.md).Diff\n\n**`param`** The previously emitted state of an object\n\n**`param`** The current emitted state of an object\n\n## Type parameters\n\n| Name   |\n| :----- |\n| `Type` |\n\n## Table of contents\n\n### Properties\n\n- [current](Frontend_Utils_EmitterUtils.Diff.md#current)\n- [previous](Frontend_Utils_EmitterUtils.Diff.md#previous)\n\n## Properties\n\n### current\n\n• **current**: `Type`\n\n---\n\n### previous\n\n• **previous**: `Type`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Views_ModalPane.ModalFrame.md",
    "content": "# Interface: ModalFrame\n\n[Frontend/Views/ModalPane](../modules/Frontend_Views_ModalPane.md).ModalFrame\n\nA modal has a {@code content}, and also optionally many {@link ModalFrames} pushed on top of it.\n\n## Table of contents\n\n### Properties\n\n- [helpContent](Frontend_Views_ModalPane.ModalFrame.md#helpcontent)\n- [title](Frontend_Views_ModalPane.ModalFrame.md#title)\n\n### Methods\n\n- [element](Frontend_Views_ModalPane.ModalFrame.md#element)\n\n## Properties\n\n### helpContent\n\n• `Optional` **helpContent**: `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>\n\n---\n\n### title\n\n• **title**: `string`\n\n## Methods\n\n### element\n\n▸ **element**(): `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>\n\n#### Returns\n\n`ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>\n"
  },
  {
    "path": "docs/interfaces/Frontend_Views_ModalPane.ModalHandle.md",
    "content": "# Interface: ModalHandle\n\n[Frontend/Views/ModalPane](../modules/Frontend_Views_ModalPane.md).ModalHandle\n\n**`todo`** Add things like open, close, set position, etc.\n\n## Table of contents\n\n### Properties\n\n- [id](Frontend_Views_ModalPane.ModalHandle.md#id)\n- [isActive](Frontend_Views_ModalPane.ModalHandle.md#isactive)\n\n### Methods\n\n- [pop](Frontend_Views_ModalPane.ModalHandle.md#pop)\n- [popAll](Frontend_Views_ModalPane.ModalHandle.md#popall)\n- [push](Frontend_Views_ModalPane.ModalHandle.md#push)\n\n## Properties\n\n### id\n\n• **id**: `string`\n\n---\n\n### isActive\n\n• **isActive**: `boolean`\n\n## Methods\n\n### pop\n\n▸ **pop**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### popAll\n\n▸ **popAll**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### push\n\n▸ **push**(`frame`): `void`\n\n#### Parameters\n\n| Name    | Type                                                   |\n| :------ | :----------------------------------------------------- |\n| `frame` | [`ModalFrame`](Frontend_Views_ModalPane.ModalFrame.md) |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Views_Share.ShareProps.md",
    "content": "# Interface: ShareProps<T\\>\n\n[Frontend/Views/Share](../modules/Frontend_Views_Share.md).ShareProps\n\n## Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n## Table of contents\n\n### Methods\n\n- [children](Frontend_Views_Share.ShareProps.md#children)\n- [load](Frontend_Views_Share.ShareProps.md#load)\n\n## Methods\n\n### children\n\n▸ **children**(`state`, `loading`, `error`): `ReactNode`\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `state`   | `undefined` \\| `T`     |\n| `loading` | `boolean`              |\n| `error`   | `undefined` \\| `Error` |\n\n#### Returns\n\n`ReactNode`\n\n---\n\n### load\n\n▸ **load**(`store`): `Promise`<`T`\\>\n\n#### Parameters\n\n| Name    | Type                                                               |\n| :------ | :----------------------------------------------------------------- |\n| `store` | [`default`](../classes/Backend_Storage_ReaderDataStore.default.md) |\n\n#### Returns\n\n`Promise`<`T`\\>\n"
  },
  {
    "path": "docs/interfaces/Frontend_Views_Terminal.TerminalHandle.md",
    "content": "# Interface: TerminalHandle\n\n[Frontend/Views/Terminal](../modules/Frontend_Views_Terminal.md).TerminalHandle\n\n## Table of contents\n\n### Methods\n\n- [clear](Frontend_Views_Terminal.TerminalHandle.md#clear)\n- [focus](Frontend_Views_Terminal.TerminalHandle.md#focus)\n- [getInput](Frontend_Views_Terminal.TerminalHandle.md#getinput)\n- [newline](Frontend_Views_Terminal.TerminalHandle.md#newline)\n- [print](Frontend_Views_Terminal.TerminalHandle.md#print)\n- [printElement](Frontend_Views_Terminal.TerminalHandle.md#printelement)\n- [printLink](Frontend_Views_Terminal.TerminalHandle.md#printlink)\n- [printLoadingBar](Frontend_Views_Terminal.TerminalHandle.md#printloadingbar)\n- [printLoadingSpinner](Frontend_Views_Terminal.TerminalHandle.md#printloadingspinner)\n- [printShellLn](Frontend_Views_Terminal.TerminalHandle.md#printshellln)\n- [println](Frontend_Views_Terminal.TerminalHandle.md#println)\n- [removeLast](Frontend_Views_Terminal.TerminalHandle.md#removelast)\n- [setInput](Frontend_Views_Terminal.TerminalHandle.md#setinput)\n- [setUserInputEnabled](Frontend_Views_Terminal.TerminalHandle.md#setuserinputenabled)\n\n## Methods\n\n### clear\n\n▸ **clear**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### focus\n\n▸ **focus**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### getInput\n\n▸ **getInput**(): `Promise`<`string`\\>\n\n#### Returns\n\n`Promise`<`string`\\>\n\n---\n\n### newline\n\n▸ **newline**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### print\n\n▸ **print**(`str`, `style?`): `void`\n\n#### Parameters\n\n| Name     | Type                                                                              |\n| :------- | :-------------------------------------------------------------------------------- |\n| `str`    | `string`                                                                          |\n| `style?` | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### printElement\n\n▸ **printElement**(`element`): `void`\n\n#### Parameters\n\n| Name      | Type                                                                |\n| :-------- | :------------------------------------------------------------------ |\n| `element` | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### printLink\n\n▸ **printLink**(`str`, `onClick`, `style`): `void`\n\n#### Parameters\n\n| Name      | Type                                                                              |\n| :-------- | :-------------------------------------------------------------------------------- |\n| `str`     | `string`                                                                          |\n| `onClick` | () => `void`                                                                      |\n| `style`   | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### printLoadingBar\n\n▸ **printLoadingBar**(`prettyEntityName`, `ref`): `void`\n\n#### Parameters\n\n| Name               | Type                                                                                       |\n| :----------------- | :----------------------------------------------------------------------------------------- |\n| `prettyEntityName` | `string`                                                                                   |\n| `ref`              | `RefObject`<[`LoadingBarHandle`](Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\\> |\n\n#### Returns\n\n`void`\n\n---\n\n### printLoadingSpinner\n\n▸ **printLoadingSpinner**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### printShellLn\n\n▸ **printShellLn**(`str`): `void`\n\n#### Parameters\n\n| Name  | Type     |\n| :---- | :------- |\n| `str` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### println\n\n▸ **println**(`str`, `style?`): `void`\n\n#### Parameters\n\n| Name     | Type                                                                              |\n| :------- | :-------------------------------------------------------------------------------- |\n| `str`    | `string`                                                                          |\n| `style?` | [`TerminalTextStyle`](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md) |\n\n#### Returns\n\n`void`\n\n---\n\n### removeLast\n\n▸ **removeLast**(`n`): `void`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `n`  | `number` |\n\n#### Returns\n\n`void`\n\n---\n\n### setInput\n\n▸ **setInput**(`input`): `void`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `input` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### setUserInputEnabled\n\n▸ **setUserInputEnabled**(`enabled`): `void`\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `enabled` | `boolean` |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/interfaces/Frontend_Views_Terminal.TerminalProps.md",
    "content": "# Interface: TerminalProps\n\n[Frontend/Views/Terminal](../modules/Frontend_Views_Terminal.md).TerminalProps\n\n## Table of contents\n\n### Properties\n\n- [promptCharacter](Frontend_Views_Terminal.TerminalProps.md#promptcharacter)\n\n## Properties\n\n### promptCharacter\n\n• **promptCharacter**: `string`\n"
  },
  {
    "path": "docs/interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md",
    "content": "# Interface: ChunkStore\n\n[\\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).ChunkStore\n\nAbstract interface shared between different types of chunk stores. Currently we have one that\nwrites to IndexedDB, and one that simply throws away the data.\n\n## Implemented by\n\n- [`HomePlanetMinerChunkStore`](../classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md)\n- [`default`](../classes/Backend_Storage_PersistentChunkStore.default.md)\n\n## Table of contents\n\n### Methods\n\n- [hasMinedChunk](types_darkforest_api_ChunkStoreTypes.ChunkStore.md#hasminedchunk)\n\n## Methods\n\n### hasMinedChunk\n\n▸ **hasMinedChunk**(`chunkFootprint`): `boolean`\n\n#### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkFootprint` | `Rectangle` |\n\n#### Returns\n\n`boolean`\n"
  },
  {
    "path": "docs/interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md",
    "content": "# Interface: PersistedChunk\n\n[\\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).PersistedChunk\n\nChunks represent map data in some rectangle. This type represents a chunk when it is at rest in\nIndexedDB. The reason for this type's existence is that we want to reduce the amount of data we\nstore on the user's computer. Shorter names hopefully means less data.\n\n## Table of contents\n\n### Properties\n\n- [l](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#l)\n- [p](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#p)\n- [s](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#s)\n- [x](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#x)\n- [y](types_darkforest_api_ChunkStoreTypes.PersistedChunk.md#y)\n\n## Properties\n\n### l\n\n• **l**: [`PersistedLocation`](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md)[]\n\n---\n\n### p\n\n• **p**: `number`\n\n---\n\n### s\n\n• **s**: `number`\n\n---\n\n### x\n\n• **x**: `number`\n\n---\n\n### y\n\n• **y**: `number`\n"
  },
  {
    "path": "docs/interfaces/types_darkforest_api_ChunkStoreTypes.PersistedLocation.md",
    "content": "# Interface: PersistedLocation\n\n[\\_types/darkforest/api/ChunkStoreTypes](../modules/types_darkforest_api_ChunkStoreTypes.md).PersistedLocation\n\nA location is a point sample of the universe. This type represents that point sample at rest when\nit is stored in IndexedDB.\n\n## Table of contents\n\n### Properties\n\n- [b](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#b)\n- [h](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#h)\n- [p](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#p)\n- [x](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#x)\n- [y](types_darkforest_api_ChunkStoreTypes.PersistedLocation.md#y)\n\n## Properties\n\n### b\n\n• **b**: `number`\n\n---\n\n### h\n\n• **h**: `LocationId`\n\n---\n\n### p\n\n• **p**: `number`\n\n---\n\n### x\n\n• **x**: `number`\n\n---\n\n### y\n\n• **y**: `number`\n"
  },
  {
    "path": "docs/interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md",
    "content": "# Interface: ContractConstants\n\n[\\_types/darkforest/api/ContractsAPITypes](../modules/types_darkforest_api_ContractsAPITypes.md).ContractConstants\n\n## Table of contents\n\n### Properties\n\n- [ABANDON_RANGE_CHANGE_PERCENT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#abandon_range_change_percent)\n- [ABANDON_SPEED_CHANGE_PERCENT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#abandon_speed_change_percent)\n- [ADMIN_CAN_ADD_PLANETS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#admin_can_add_planets)\n- [ARTIFACT_POINT_VALUES](types_darkforest_api_ContractsAPITypes.ContractConstants.md#artifact_point_values)\n- [BIOMEBASE_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biomebase_key)\n- [BIOME_THRESHOLD_1](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biome_threshold_1)\n- [BIOME_THRESHOLD_2](types_darkforest_api_ContractsAPITypes.ContractConstants.md#biome_threshold_2)\n- [CAPTURE_ZONES_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zones_enabled)\n- [CAPTURE_ZONES_PER_5000_WORLD_RADIUS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zones_per_5000_world_radius)\n- [CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_change_block_interval)\n- [CAPTURE_ZONE_COUNT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_count)\n- [CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_hold_blocks_required)\n- [CAPTURE_ZONE_PLANET_LEVEL_SCORE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_planet_level_score)\n- [CAPTURE_ZONE_RADIUS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#capture_zone_radius)\n- [CLAIM_PLANET_COOLDOWN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#claim_planet_cooldown)\n- [DISABLE_ZK_CHECKS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#disable_zk_checks)\n- [GAME_START_BLOCK](types_darkforest_api_ContractsAPITypes.ContractConstants.md#game_start_block)\n- [INIT_PERLIN_MAX](types_darkforest_api_ContractsAPITypes.ContractConstants.md#init_perlin_max)\n- [INIT_PERLIN_MIN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#init_perlin_min)\n- [LOCATION_REVEAL_COOLDOWN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#location_reveal_cooldown)\n- [MAX_NATURAL_PLANET_LEVEL](types_darkforest_api_ContractsAPITypes.ContractConstants.md#max_natural_planet_level)\n- [PERLIN_LENGTH_SCALE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_length_scale)\n- [PERLIN_MIRROR_X](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_mirror_x)\n- [PERLIN_MIRROR_Y](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_mirror_y)\n- [PERLIN_THRESHOLD_1](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_1)\n- [PERLIN_THRESHOLD_2](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_2)\n- [PERLIN_THRESHOLD_3](types_darkforest_api_ContractsAPITypes.ContractConstants.md#perlin_threshold_3)\n- [PHOTOID_ACTIVATION_DELAY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#photoid_activation_delay)\n- [PLANETHASH_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planethash_key)\n- [PLANET_LEVEL_JUNK](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_level_junk)\n- [PLANET_LEVEL_THRESHOLDS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_level_thresholds)\n- [PLANET_RARITY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_rarity)\n- [PLANET_TRANSFER_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_transfer_enabled)\n- [PLANET_TYPE_WEIGHTS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planet_type_weights)\n- [SILVER_SCORE_VALUE](types_darkforest_api_ContractsAPITypes.ContractConstants.md#silver_score_value)\n- [SPACETYPE_KEY](types_darkforest_api_ContractsAPITypes.ContractConstants.md#spacetype_key)\n- [SPACE_JUNK_ENABLED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#space_junk_enabled)\n- [SPACE_JUNK_LIMIT](types_darkforest_api_ContractsAPITypes.ContractConstants.md#space_junk_limit)\n- [SPAWN_RIM_AREA](types_darkforest_api_ContractsAPITypes.ContractConstants.md#spawn_rim_area)\n- [TIME_FACTOR_HUNDREDTHS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#time_factor_hundredths)\n- [TOKEN_MINT_END_SECONDS](types_darkforest_api_ContractsAPITypes.ContractConstants.md#token_mint_end_seconds)\n- [WORLD_RADIUS_LOCKED](types_darkforest_api_ContractsAPITypes.ContractConstants.md#world_radius_locked)\n- [WORLD_RADIUS_MIN](types_darkforest_api_ContractsAPITypes.ContractConstants.md#world_radius_min)\n- [adminAddress](types_darkforest_api_ContractsAPITypes.ContractConstants.md#adminaddress)\n- [defaultBarbarianPercentage](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultbarbarianpercentage)\n- [defaultDefense](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultdefense)\n- [defaultPopulationCap](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultpopulationcap)\n- [defaultPopulationGrowth](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultpopulationgrowth)\n- [defaultRange](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultrange)\n- [defaultSilverCap](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultsilvercap)\n- [defaultSilverGrowth](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultsilvergrowth)\n- [defaultSpeed](types_darkforest_api_ContractsAPITypes.ContractConstants.md#defaultspeed)\n- [planetCumulativeRarities](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planetcumulativerarities)\n- [planetLevelThresholds](types_darkforest_api_ContractsAPITypes.ContractConstants.md#planetlevelthresholds)\n- [upgrades](types_darkforest_api_ContractsAPITypes.ContractConstants.md#upgrades)\n\n## Properties\n\n### ABANDON_RANGE_CHANGE_PERCENT\n\n• **ABANDON_RANGE_CHANGE_PERCENT**: `number`\n\nThe range boost a movement receives when abandoning\na planet.\n\n---\n\n### ABANDON_SPEED_CHANGE_PERCENT\n\n• **ABANDON_SPEED_CHANGE_PERCENT**: `number`\n\nThe speed boost a movement receives when abandoning\na planet.\n\n---\n\n### ADMIN_CAN_ADD_PLANETS\n\n• **ADMIN_CAN_ADD_PLANETS**: `boolean`\n\n---\n\n### ARTIFACT_POINT_VALUES\n\n• **ARTIFACT_POINT_VALUES**: `ArtifactPointValues`\n\n---\n\n### BIOMEBASE_KEY\n\n• **BIOMEBASE_KEY**: `number`\n\n---\n\n### BIOME_THRESHOLD_1\n\n• **BIOME_THRESHOLD_1**: `number`\n\n---\n\n### BIOME_THRESHOLD_2\n\n• **BIOME_THRESHOLD_2**: `number`\n\n---\n\n### CAPTURE_ZONES_ENABLED\n\n• **CAPTURE_ZONES_ENABLED**: `boolean`\n\n---\n\n### CAPTURE_ZONES_PER_5000_WORLD_RADIUS\n\n• **CAPTURE_ZONES_PER_5000_WORLD_RADIUS**: `number`\n\n---\n\n### CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\n\n• **CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL**: `number`\n\n---\n\n### CAPTURE_ZONE_COUNT\n\n• **CAPTURE_ZONE_COUNT**: `number`\n\n---\n\n### CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\n\n• **CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED**: `number`\n\n---\n\n### CAPTURE_ZONE_PLANET_LEVEL_SCORE\n\n• **CAPTURE_ZONE_PLANET_LEVEL_SCORE**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`]\n\n---\n\n### CAPTURE_ZONE_RADIUS\n\n• **CAPTURE_ZONE_RADIUS**: `number`\n\n---\n\n### CLAIM_PLANET_COOLDOWN\n\n• `Optional` **CLAIM_PLANET_COOLDOWN**: `number`\n\n---\n\n### DISABLE_ZK_CHECKS\n\n• **DISABLE_ZK_CHECKS**: `boolean`\n\n---\n\n### GAME_START_BLOCK\n\n• **GAME_START_BLOCK**: `number`\n\n---\n\n### INIT_PERLIN_MAX\n\n• **INIT_PERLIN_MAX**: `number`\n\n---\n\n### INIT_PERLIN_MIN\n\n• **INIT_PERLIN_MIN**: `number`\n\n---\n\n### LOCATION_REVEAL_COOLDOWN\n\n• **LOCATION_REVEAL_COOLDOWN**: `number`\n\n---\n\n### MAX_NATURAL_PLANET_LEVEL\n\n• **MAX_NATURAL_PLANET_LEVEL**: `number`\n\n---\n\n### PERLIN_LENGTH_SCALE\n\n• **PERLIN_LENGTH_SCALE**: `number`\n\n---\n\n### PERLIN_MIRROR_X\n\n• **PERLIN_MIRROR_X**: `boolean`\n\n---\n\n### PERLIN_MIRROR_Y\n\n• **PERLIN_MIRROR_Y**: `boolean`\n\n---\n\n### PERLIN_THRESHOLD_1\n\n• **PERLIN_THRESHOLD_1**: `number`\n\nThe perlin value at each coordinate determines the space type. There are four space\ntypes, which means there are four ranges on the number line that correspond to\neach space type. This function returns the boundary values between each of these\nfour ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`.\n\n---\n\n### PERLIN_THRESHOLD_2\n\n• **PERLIN_THRESHOLD_2**: `number`\n\n---\n\n### PERLIN_THRESHOLD_3\n\n• **PERLIN_THRESHOLD_3**: `number`\n\n---\n\n### PHOTOID_ACTIVATION_DELAY\n\n• **PHOTOID_ACTIVATION_DELAY**: `number`\n\n---\n\n### PLANETHASH_KEY\n\n• **PLANETHASH_KEY**: `number`\n\n---\n\n### PLANET_LEVEL_JUNK\n\n• **PLANET_LEVEL_JUNK**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`]\n\nThe amount of junk that each level of planet\ngives the player when moving to it for the\nfirst time.\n\n---\n\n### PLANET_LEVEL_THRESHOLDS\n\n• **PLANET_LEVEL_THRESHOLDS**: [`number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`, `number`]\n\nThe chance for a planet to be a specific level.\nEach index corresponds to a planet level (index 5 is level 5 planet).\nThe lower the number the lower the chance.\nNote: This does not control if a planet spawns or not, just the level\nwhen it spawns.\n\n---\n\n### PLANET_RARITY\n\n• **PLANET_RARITY**: `number`\n\n---\n\n### PLANET_TRANSFER_ENABLED\n\n• **PLANET_TRANSFER_ENABLED**: `boolean`\n\n---\n\n### PLANET_TYPE_WEIGHTS\n\n• **PLANET_TYPE_WEIGHTS**: [`PlanetTypeWeightsBySpaceType`](../modules/types_darkforest_api_ContractsAPITypes.md#planettypeweightsbyspacetype)\n\n---\n\n### SILVER_SCORE_VALUE\n\n• **SILVER_SCORE_VALUE**: `number`\n\nHow much score silver gives when withdrawing.\nExpressed as a percentage integer.\n(100 is 100%)\n\n---\n\n### SPACETYPE_KEY\n\n• **SPACETYPE_KEY**: `number`\n\n---\n\n### SPACE_JUNK_ENABLED\n\n• **SPACE_JUNK_ENABLED**: `boolean`\n\n---\n\n### SPACE_JUNK_LIMIT\n\n• **SPACE_JUNK_LIMIT**: `number`\n\nTotal amount of space junk a player can take on.\nThis can be overridden at runtime by updating\nthis value for a specific player in storage.\n\n---\n\n### SPAWN_RIM_AREA\n\n• **SPAWN_RIM_AREA**: `number`\n\n---\n\n### TIME_FACTOR_HUNDREDTHS\n\n• **TIME_FACTOR_HUNDREDTHS**: `number`\n\n---\n\n### TOKEN_MINT_END_SECONDS\n\n• **TOKEN_MINT_END_SECONDS**: `number`\n\n---\n\n### WORLD_RADIUS_LOCKED\n\n• **WORLD_RADIUS_LOCKED**: `boolean`\n\n---\n\n### WORLD_RADIUS_MIN\n\n• **WORLD_RADIUS_MIN**: `number`\n\n---\n\n### adminAddress\n\n• **adminAddress**: `EthAddress`\n\n---\n\n### defaultBarbarianPercentage\n\n• **defaultBarbarianPercentage**: `number`[]\n\n---\n\n### defaultDefense\n\n• **defaultDefense**: `number`[]\n\n---\n\n### defaultPopulationCap\n\n• **defaultPopulationCap**: `number`[]\n\n---\n\n### defaultPopulationGrowth\n\n• **defaultPopulationGrowth**: `number`[]\n\n---\n\n### defaultRange\n\n• **defaultRange**: `number`[]\n\n---\n\n### defaultSilverCap\n\n• **defaultSilverCap**: `number`[]\n\n---\n\n### defaultSilverGrowth\n\n• **defaultSilverGrowth**: `number`[]\n\n---\n\n### defaultSpeed\n\n• **defaultSpeed**: `number`[]\n\n---\n\n### planetCumulativeRarities\n\n• **planetCumulativeRarities**: `number`[]\n\n---\n\n### planetLevelThresholds\n\n• **planetLevelThresholds**: `number`[]\n\n---\n\n### upgrades\n\n• **upgrades**: `UpgradeBranches`\n"
  },
  {
    "path": "docs/interfaces/types_global_GlobalTypes.ClaimCountdownInfo.md",
    "content": "# Interface: ClaimCountdownInfo\n\n[\\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).ClaimCountdownInfo\n\n## Table of contents\n\n### Properties\n\n- [claimCooldownTime](types_global_GlobalTypes.ClaimCountdownInfo.md#claimcooldowntime)\n- [currentlyClaiming](types_global_GlobalTypes.ClaimCountdownInfo.md#currentlyclaiming)\n- [myLastClaimTimestamp](types_global_GlobalTypes.ClaimCountdownInfo.md#mylastclaimtimestamp)\n\n## Properties\n\n### claimCooldownTime\n\n• **claimCooldownTime**: `number`\n\n---\n\n### currentlyClaiming\n\n• **currentlyClaiming**: `boolean`\n\n---\n\n### myLastClaimTimestamp\n\n• `Optional` **myLastClaimTimestamp**: `number`\n"
  },
  {
    "path": "docs/interfaces/types_global_GlobalTypes.MinerWorkerMessage.md",
    "content": "# Interface: MinerWorkerMessage\n\n[\\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).MinerWorkerMessage\n\n## Table of contents\n\n### Properties\n\n- [biomebaseKey](types_global_GlobalTypes.MinerWorkerMessage.md#biomebasekey)\n- [chunkFootprint](types_global_GlobalTypes.MinerWorkerMessage.md#chunkfootprint)\n- [jobId](types_global_GlobalTypes.MinerWorkerMessage.md#jobid)\n- [perlinLengthScale](types_global_GlobalTypes.MinerWorkerMessage.md#perlinlengthscale)\n- [perlinMirrorX](types_global_GlobalTypes.MinerWorkerMessage.md#perlinmirrorx)\n- [perlinMirrorY](types_global_GlobalTypes.MinerWorkerMessage.md#perlinmirrory)\n- [planetHashKey](types_global_GlobalTypes.MinerWorkerMessage.md#planethashkey)\n- [planetRarity](types_global_GlobalTypes.MinerWorkerMessage.md#planetrarity)\n- [spaceTypeKey](types_global_GlobalTypes.MinerWorkerMessage.md#spacetypekey)\n- [totalWorkers](types_global_GlobalTypes.MinerWorkerMessage.md#totalworkers)\n- [useMockHash](types_global_GlobalTypes.MinerWorkerMessage.md#usemockhash)\n- [workerIndex](types_global_GlobalTypes.MinerWorkerMessage.md#workerindex)\n\n## Properties\n\n### biomebaseKey\n\n• **biomebaseKey**: `number`\n\n---\n\n### chunkFootprint\n\n• **chunkFootprint**: `Rectangle`\n\n---\n\n### jobId\n\n• **jobId**: `number`\n\n---\n\n### perlinLengthScale\n\n• **perlinLengthScale**: `number`\n\n---\n\n### perlinMirrorX\n\n• **perlinMirrorX**: `boolean`\n\n---\n\n### perlinMirrorY\n\n• **perlinMirrorY**: `boolean`\n\n---\n\n### planetHashKey\n\n• **planetHashKey**: `number`\n\n---\n\n### planetRarity\n\n• **planetRarity**: `number`\n\n---\n\n### spaceTypeKey\n\n• **spaceTypeKey**: `number`\n\n---\n\n### totalWorkers\n\n• **totalWorkers**: `number`\n\n---\n\n### useMockHash\n\n• **useMockHash**: `boolean`\n\n---\n\n### workerIndex\n\n• **workerIndex**: `number`\n"
  },
  {
    "path": "docs/interfaces/types_global_GlobalTypes.RevealCountdownInfo.md",
    "content": "# Interface: RevealCountdownInfo\n\n[\\_types/global/GlobalTypes](../modules/types_global_GlobalTypes.md).RevealCountdownInfo\n\n## Table of contents\n\n### Properties\n\n- [currentlyRevealing](types_global_GlobalTypes.RevealCountdownInfo.md#currentlyrevealing)\n- [myLastRevealTimestamp](types_global_GlobalTypes.RevealCountdownInfo.md#mylastrevealtimestamp)\n- [revealCooldownTime](types_global_GlobalTypes.RevealCountdownInfo.md#revealcooldowntime)\n\n## Properties\n\n### currentlyRevealing\n\n• **currentlyRevealing**: `boolean`\n\n---\n\n### myLastRevealTimestamp\n\n• `Optional` **myLastRevealTimestamp**: `number`\n\n---\n\n### revealCooldownTime\n\n• **revealCooldownTime**: `number`\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_ArrivalUtils.md",
    "content": "# Module: Backend/GameLogic/ArrivalUtils\n\n## Table of contents\n\n### Interfaces\n\n- [PlanetDiff](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md)\n\n### Functions\n\n- [applyUpgrade](Backend_GameLogic_ArrivalUtils.md#applyupgrade)\n- [arrive](Backend_GameLogic_ArrivalUtils.md#arrive)\n- [blocksLeftToProspectExpiration](Backend_GameLogic_ArrivalUtils.md#blockslefttoprospectexpiration)\n- [getEmojiMessage](Backend_GameLogic_ArrivalUtils.md#getemojimessage)\n- [isFindable](Backend_GameLogic_ArrivalUtils.md#isfindable)\n- [isProspectable](Backend_GameLogic_ArrivalUtils.md#isprospectable)\n- [prospectExpired](Backend_GameLogic_ArrivalUtils.md#prospectexpired)\n- [updatePlanetToTime](Backend_GameLogic_ArrivalUtils.md#updateplanettotime)\n\n## Functions\n\n### applyUpgrade\n\n▸ **applyUpgrade**(`planet`, `upgrade`, `unApply?`): `void`\n\n#### Parameters\n\n| Name      | Type      | Default value |\n| :-------- | :-------- | :------------ |\n| `planet`  | `Planet`  | `undefined`   |\n| `upgrade` | `Upgrade` | `undefined`   |\n| `unApply` | `boolean` | `false`       |\n\n#### Returns\n\n`void`\n\n---\n\n### arrive\n\n▸ **arrive**(`toPlanet`, `artifactsOnPlanet`, `arrival`, `arrivingArtifact`, `contractConstants`): [`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md)\n\n#### Parameters\n\n| Name                | Type                                                                                             |\n| :------------------ | :----------------------------------------------------------------------------------------------- |\n| `toPlanet`          | `Planet`                                                                                         |\n| `artifactsOnPlanet` | `Artifact`[]                                                                                     |\n| `arrival`           | `QueuedArrival`                                                                                  |\n| `arrivingArtifact`  | `undefined` \\| `Artifact`                                                                        |\n| `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) |\n\n#### Returns\n\n[`PlanetDiff`](../interfaces/Backend_GameLogic_ArrivalUtils.PlanetDiff.md)\n\n---\n\n### blocksLeftToProspectExpiration\n\n▸ **blocksLeftToProspectExpiration**(`currentBlockNumber`, `prospectedBlockNumber?`): `number`\n\n#### Parameters\n\n| Name                     | Type     |\n| :----------------------- | :------- |\n| `currentBlockNumber`     | `number` |\n| `prospectedBlockNumber?` | `number` |\n\n#### Returns\n\n`number`\n\n---\n\n### getEmojiMessage\n\n▸ **getEmojiMessage**(`planet`): `PlanetMessage`<`EmojiFlagBody`\\> \\| `undefined`\n\n**`todo`** ArrivalUtils has become a dumping ground for functions that should just live inside of a\n`Planet` class.\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`PlanetMessage`<`EmojiFlagBody`\\> \\| `undefined`\n\n---\n\n### isFindable\n\n▸ **isFindable**(`planet`, `currentBlockNumber?`): `boolean`\n\n#### Parameters\n\n| Name                  | Type     |\n| :-------------------- | :------- |\n| `planet`              | `Planet` |\n| `currentBlockNumber?` | `number` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### isProspectable\n\n▸ **isProspectable**(`planet`): `boolean`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### prospectExpired\n\n▸ **prospectExpired**(`currentBlockNumber`, `prospectedBlockNumber`): `boolean`\n\n#### Parameters\n\n| Name                    | Type     |\n| :---------------------- | :------- |\n| `currentBlockNumber`    | `number` |\n| `prospectedBlockNumber` | `number` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### updatePlanetToTime\n\n▸ **updatePlanetToTime**(`planet`, `planetArtifacts`, `atTimeMillis`, `contractConstants`, `setPlanet?`): `void`\n\n#### Parameters\n\n| Name                | Type                                                                                             |\n| :------------------ | :----------------------------------------------------------------------------------------------- |\n| `planet`            | `Planet`                                                                                         |\n| `planetArtifacts`   | `Artifact`[]                                                                                     |\n| `atTimeMillis`      | `number`                                                                                         |\n| `contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) |\n| `setPlanet`         | (`p`: `Planet`) => `void`                                                                        |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_CaptureZoneGenerator.md",
    "content": "# Module: Backend/GameLogic/CaptureZoneGenerator\n\n## Table of contents\n\n### Classes\n\n- [CaptureZoneGenerator](../classes/Backend_GameLogic_CaptureZoneGenerator.CaptureZoneGenerator.md)\n\n### Type aliases\n\n- [CaptureZonesGeneratedEvent](Backend_GameLogic_CaptureZoneGenerator.md#capturezonesgeneratedevent)\n\n## Type aliases\n\n### CaptureZonesGeneratedEvent\n\nƬ **CaptureZonesGeneratedEvent**: `Object`\n\n#### Type declaration\n\n| Name              | Type            |\n| :---------------- | :-------------- |\n| `changeBlock`     | `number`        |\n| `nextChangeBlock` | `number`        |\n| `zones`           | `CaptureZone`[] |\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_ContractsAPI.md",
    "content": "# Module: Backend/GameLogic/ContractsAPI\n\n## Table of contents\n\n### Classes\n\n- [ContractsAPI](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md)\n\n### Functions\n\n- [makeContractsAPI](Backend_GameLogic_ContractsAPI.md#makecontractsapi)\n\n## Functions\n\n### makeContractsAPI\n\n▸ **makeContractsAPI**(`__namedParameters`): `Promise`<[`ContractsAPI`](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md)\\>\n\n#### Parameters\n\n| Name                | Type                 |\n| :------------------ | :------------------- |\n| `__namedParameters` | `ContractsApiConfig` |\n\n#### Returns\n\n`Promise`<[`ContractsAPI`](../classes/Backend_GameLogic_ContractsAPI.ContractsAPI.md)\\>\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_GameManager.md",
    "content": "# Module: Backend/GameLogic/GameManager\n\n## Table of contents\n\n### Enumerations\n\n- [GameManagerEvent](../enums/Backend_GameLogic_GameManager.GameManagerEvent.md)\n\n### Classes\n\n- [default](../classes/Backend_GameLogic_GameManager.default.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_GameObjects.md",
    "content": "# Module: Backend/GameLogic/GameObjects\n\n## Table of contents\n\n### Classes\n\n- [GameObjects](../classes/Backend_GameLogic_GameObjects.GameObjects.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_GameUIManager.md",
    "content": "# Module: Backend/GameLogic/GameUIManager\n\n## Table of contents\n\n### Enumerations\n\n- [GameUIManagerEvent](../enums/Backend_GameLogic_GameUIManager.GameUIManagerEvent.md)\n\n### Classes\n\n- [default](../classes/Backend_GameLogic_GameUIManager.default.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_InitialGameStateDownloader.md",
    "content": "# Module: Backend/GameLogic/InitialGameStateDownloader\n\n## Table of contents\n\n### Classes\n\n- [InitialGameStateDownloader](../classes/Backend_GameLogic_InitialGameStateDownloader.InitialGameStateDownloader.md)\n\n### Interfaces\n\n- [InitialGameState](../interfaces/Backend_GameLogic_InitialGameStateDownloader.InitialGameState.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_LayeredMap.md",
    "content": "# Module: Backend/GameLogic/LayeredMap\n\n## Table of contents\n\n### Classes\n\n- [LayeredMap](../classes/Backend_GameLogic_LayeredMap.LayeredMap.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_PluginManager.md",
    "content": "# Module: Backend/GameLogic/PluginManager\n\n## Table of contents\n\n### Classes\n\n- [PluginManager](../classes/Backend_GameLogic_PluginManager.PluginManager.md)\n- [ProcessInfo](../classes/Backend_GameLogic_PluginManager.ProcessInfo.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_TutorialManager.md",
    "content": "# Module: Backend/GameLogic/TutorialManager\n\n## Table of contents\n\n### Enumerations\n\n- [TutorialManagerEvent](../enums/Backend_GameLogic_TutorialManager.TutorialManagerEvent.md)\n- [TutorialState](../enums/Backend_GameLogic_TutorialManager.TutorialState.md)\n\n### Classes\n\n- [default](../classes/Backend_GameLogic_TutorialManager.default.md)\n"
  },
  {
    "path": "docs/modules/Backend_GameLogic_ViewportEntities.md",
    "content": "# Module: Backend/GameLogic/ViewportEntities\n\n## Table of contents\n\n### Classes\n\n- [ViewportEntities](../classes/Backend_GameLogic_ViewportEntities.ViewportEntities.md)\n"
  },
  {
    "path": "docs/modules/Backend_Miner_ChunkUtils.md",
    "content": "# Module: Backend/Miner/ChunkUtils\n\n## Table of contents\n\n### Functions\n\n- [addToChunkMap](Backend_Miner_ChunkUtils.md#addtochunkmap)\n- [getBucket](Backend_Miner_ChunkUtils.md#getbucket)\n- [getChunkKey](Backend_Miner_ChunkUtils.md#getchunkkey)\n- [getChunkOfSideLengthContainingPoint](Backend_Miner_ChunkUtils.md#getchunkofsidelengthcontainingpoint)\n- [getSiblingLocations](Backend_Miner_ChunkUtils.md#getsiblinglocations)\n- [toExploredChunk](Backend_Miner_ChunkUtils.md#toexploredchunk)\n- [toPersistedChunk](Backend_Miner_ChunkUtils.md#topersistedchunk)\n\n## Functions\n\n### addToChunkMap\n\n▸ **addToChunkMap**(`existingChunks`, `newChunk`, `onAdd?`, `onRemove?`, `maxChunkSize?`): `void`\n\nAt a high level, call this function to update an efficient quadtree-like store containing all of\nthe chunks that a player has either mined or imported in their client.\n\nMore speecifically, adds the given new chunk to the given map of chunks. If the map of chunks\ncontains all of the \"sibling\" chunks to this new chunk, then instead of adding it, we merge the 4\nsibling chunks, and add the merged chunk to the map and remove the existing sibling chunks. This\nfunction is recursive, which means that if the newly created merged chunk can also be merged with\nits siblings, then we merge it, add the new larger chunk, and also remove the previously existing\nsibling chunks.\n\nThe maximum chunk size is represented by the `maxChunkSize` parameter (which has to be a power of\ntwo). If no `maxChunkSize` parameter is provided, then there is no maxmimum chunk size, meaning\nthat chunks will be merged until no further merging is possible.\n\n`onAdd` and `onRemove` are called for each of the chunks that we add and remove to/from the\n`existingChunks` map. `onAdd` will be called exactly once, whereas `onRemove` only ever be called\nfor sibling chunks that existed prior to this function being called.\n\n#### Parameters\n\n| Name             | Type                                                                          |\n| :--------------- | :---------------------------------------------------------------------------- |\n| `existingChunks` | `Map`<[`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid), `Chunk`\\> |\n| `newChunk`       | `Chunk`                                                                       |\n| `onAdd?`         | (`arg`: `Chunk`) => `void`                                                    |\n| `onRemove?`      | (`arg`: `Chunk`) => `void`                                                    |\n| `maxChunkSize?`  | `number`                                                                      |\n\n#### Returns\n\n`void`\n\n---\n\n### getBucket\n\n▸ **getBucket**(`chunk`): [`BucketId`](types_darkforest_api_ChunkStoreTypes.md#bucketid)\n\nDeterministically assigns a bucket ID to a rectangle, based on its position and size in the\nuniverse. This is kind of like a shitty hash function. Its purpose is to distribute chunks\nroughly evenly between the buckets.\n\n#### Parameters\n\n| Name    | Type        |\n| :------ | :---------- |\n| `chunk` | `Rectangle` |\n\n#### Returns\n\n[`BucketId`](types_darkforest_api_ChunkStoreTypes.md#bucketid)\n\n---\n\n### getChunkKey\n\n▸ **getChunkKey**(`chunkLoc`): [`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid)\n\nA unique ID generated for each chunk based on its rectangle, as well as its bucket. It's the\nprimary key by which chunks are identified.\n\n#### Parameters\n\n| Name       | Type        |\n| :--------- | :---------- |\n| `chunkLoc` | `Rectangle` |\n\n#### Returns\n\n[`ChunkId`](types_darkforest_api_ChunkStoreTypes.md#chunkid)\n\n---\n\n### getChunkOfSideLengthContainingPoint\n\n▸ **getChunkOfSideLengthContainingPoint**(`coords`, `sideLength`): `Rectangle`\n\nReturns the unique aligned chunk (for definition of \"aligned\" see comment on\n`getSiblingLocations`) with the given side length that contains the given point. A chunk contains\nall of the points strictly inside of its bounds, as well as the bottom and left edges. This means\nit does not contain points which are on its right or top edges.\n\n#### Parameters\n\n| Name         | Type          |\n| :----------- | :------------ |\n| `coords`     | `WorldCoords` |\n| `sideLength` | `number`      |\n\n#### Returns\n\n`Rectangle`\n\n---\n\n### getSiblingLocations\n\n▸ **getSiblingLocations**(`chunkLoc`): [`Rectangle`, `Rectangle`, `Rectangle`]\n\nAn aligned chunk is one whose corner's coordinates are multiples of its side length, and its side\nlength is a power of two between [MIN_CHUNK_SIZE](Frontend_Utils_constants.md#min_chunk_size) and [MAX_CHUNK_SIZE](Frontend_Utils_constants.md#max_chunk_size) inclusive.\n\n\"Aligned\" chunks is that they can be merged into other aligned chunks. Non-aligned chunks cannot\nalways be merged into squares. The reason we care about merging is that merging chunks allows us\nto represent more world-space using fewer chunks. This saves memory at both runtime and\nstorage-time. Therefore, we only store aligned chunks.\n\nAs an example, chunks with any corner at (0, 0) are always aligned. A chunk with side length 4 is\naligned if it's on (4, 4), (8, 12), but not (4, 6).\n\nThis function returns the other three chunks with the same side length of the given chunk, such\nthat the four chunks, if merged, would result in an \"aligned\" chunk whose side length is double\nthe given chunk.\n\n#### Parameters\n\n| Name       | Type        |\n| :--------- | :---------- |\n| `chunkLoc` | `Rectangle` |\n\n#### Returns\n\n[`Rectangle`, `Rectangle`, `Rectangle`]\n\n---\n\n### toExploredChunk\n\n▸ **toExploredChunk**(`chunk`): `Chunk`\n\nConverts from the persisted representation of a chunk to the in-game representation of a chunk.\n\n#### Parameters\n\n| Name    | Type                                                                                     |\n| :------ | :--------------------------------------------------------------------------------------- |\n| `chunk` | [`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md) |\n\n#### Returns\n\n`Chunk`\n\n---\n\n### toPersistedChunk\n\n▸ **toPersistedChunk**(`chunk`): [`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md)\n\nConverts from the in-game representation of a chunk to its persisted representation.\n\n#### Parameters\n\n| Name    | Type    |\n| :------ | :------ |\n| `chunk` | `Chunk` |\n\n#### Returns\n\n[`PersistedChunk`](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md)\n"
  },
  {
    "path": "docs/modules/Backend_Miner_MinerManager.md",
    "content": "# Module: Backend/Miner/MinerManager\n\n## Table of contents\n\n### Enumerations\n\n- [MinerManagerEvent](../enums/Backend_Miner_MinerManager.MinerManagerEvent.md)\n\n### Classes\n\n- [HomePlanetMinerChunkStore](../classes/Backend_Miner_MinerManager.HomePlanetMinerChunkStore.md)\n- [default](../classes/Backend_Miner_MinerManager.default.md)\n\n### Type aliases\n\n- [workerFactory](Backend_Miner_MinerManager.md#workerfactory)\n\n## Type aliases\n\n### workerFactory\n\nƬ **workerFactory**: () => `Worker`\n\n#### Type declaration\n\n▸ (): `Worker`\n\n##### Returns\n\n`Worker`\n"
  },
  {
    "path": "docs/modules/Backend_Miner_MiningPatterns.md",
    "content": "# Module: Backend/Miner/MiningPatterns\n\n## Table of contents\n\n### Enumerations\n\n- [MiningPatternType](../enums/Backend_Miner_MiningPatterns.MiningPatternType.md)\n\n### Classes\n\n- [SpiralPattern](../classes/Backend_Miner_MiningPatterns.SpiralPattern.md)\n- [SwissCheesePattern](../classes/Backend_Miner_MiningPatterns.SwissCheesePattern.md)\n- [TowardsCenterPattern](../classes/Backend_Miner_MiningPatterns.TowardsCenterPattern.md)\n- [TowardsCenterPatternV2](../classes/Backend_Miner_MiningPatterns.TowardsCenterPatternV2.md)\n\n### Interfaces\n\n- [MiningPattern](../interfaces/Backend_Miner_MiningPatterns.MiningPattern.md)\n"
  },
  {
    "path": "docs/modules/Backend_Miner_permutation.md",
    "content": "# Module: Backend/Miner/permutation\n\n## Table of contents\n\n### Functions\n\n- [getPlanetLocations](Backend_Miner_permutation.md#getplanetlocations)\n\n## Functions\n\n### getPlanetLocations\n\n▸ **getPlanetLocations**(`spaceTypeKey`, `biomebaseKey`, `perlinLengthScale`, `perlinMirrorX`, `perlinMirrorY`): (`chunkFootprint`: `Rectangle`, `planetRarity`: `number`) => `WorldLocation`[]\n\n#### Parameters\n\n| Name                | Type      |\n| :------------------ | :-------- |\n| `spaceTypeKey`      | `number`  |\n| `biomebaseKey`      | `number`  |\n| `perlinLengthScale` | `number`  |\n| `perlinMirrorX`     | `boolean` |\n| `perlinMirrorY`     | `boolean` |\n\n#### Returns\n\n`fn`\n\n▸ (`chunkFootprint`, `planetRarity`): `WorldLocation`[]\n\n##### Parameters\n\n| Name             | Type        |\n| :--------------- | :---------- |\n| `chunkFootprint` | `Rectangle` |\n| `planetRarity`   | `number`    |\n\n##### Returns\n\n`WorldLocation`[]\n"
  },
  {
    "path": "docs/modules/Backend_Network_AccountManager.md",
    "content": "# Module: Backend/Network/AccountManager\n\n## Table of contents\n\n### Interfaces\n\n- [Account](../interfaces/Backend_Network_AccountManager.Account.md)\n\n### Functions\n\n- [addAccount](Backend_Network_AccountManager.md#addaccount)\n- [getAccounts](Backend_Network_AccountManager.md#getaccounts)\n\n## Functions\n\n### addAccount\n\n▸ **addAccount**(`privateKey`): `void`\n\nAdds the given account, and saves it to localstorage.\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `privateKey` | `string` |\n\n#### Returns\n\n`void`\n\n---\n\n### getAccounts\n\n▸ **getAccounts**(): [`Account`](../interfaces/Backend_Network_AccountManager.Account.md)[]\n\nReturns the list of accounts that are logged into the game.\n\n#### Returns\n\n[`Account`](../interfaces/Backend_Network_AccountManager.Account.md)[]\n"
  },
  {
    "path": "docs/modules/Backend_Network_Blockchain.md",
    "content": "# Module: Backend/Network/Blockchain\n\n## Table of contents\n\n### Functions\n\n- [getEthConnection](Backend_Network_Blockchain.md#getethconnection)\n- [loadDiamondContract](Backend_Network_Blockchain.md#loaddiamondcontract)\n\n## Functions\n\n### getEthConnection\n\n▸ **getEthConnection**(): `Promise`<`EthConnection`\\>\n\n#### Returns\n\n`Promise`<`EthConnection`\\>\n\n---\n\n### loadDiamondContract\n\n▸ **loadDiamondContract**<`T`\\>(`address`, `provider`, `signer?`): `Promise`<`T`\\>\n\nLoads the game contract, which is responsible for updating the state of the game.\n\n#### Type parameters\n\n| Name | Type                     |\n| :--- | :----------------------- |\n| `T`  | extends `Contract`<`T`\\> |\n\n#### Parameters\n\n| Name       | Type              |\n| :--------- | :---------------- |\n| `address`  | `string`          |\n| `provider` | `JsonRpcProvider` |\n| `signer?`  | `Wallet`          |\n\n#### Returns\n\n`Promise`<`T`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Network_EventLogger.md",
    "content": "# Module: Backend/Network/EventLogger\n\n## Table of contents\n\n### Enumerations\n\n- [EventType](../enums/Backend_Network_EventLogger.EventType.md)\n\n### Classes\n\n- [EventLogger](../classes/Backend_Network_EventLogger.EventLogger.md)\n\n### Variables\n\n- [eventLogger](Backend_Network_EventLogger.md#eventlogger)\n\n## Variables\n\n### eventLogger\n\n• `Const` **eventLogger**: [`EventLogger`](../classes/Backend_Network_EventLogger.EventLogger.md)\n"
  },
  {
    "path": "docs/modules/Backend_Network_LeaderboardApi.md",
    "content": "# Module: Backend/Network/LeaderboardApi\n\n## Table of contents\n\n### Functions\n\n- [loadLeaderboard](Backend_Network_LeaderboardApi.md#loadleaderboard)\n\n## Functions\n\n### loadLeaderboard\n\n▸ **loadLeaderboard**(): `Promise`<`Leaderboard`\\>\n\n#### Returns\n\n`Promise`<`Leaderboard`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Network_MessageAPI.md",
    "content": "# Module: Backend/Network/MessageAPI\n\n## Table of contents\n\n### Functions\n\n- [addMessage](Backend_Network_MessageAPI.md#addmessage)\n- [deleteMessages](Backend_Network_MessageAPI.md#deletemessages)\n- [getMessagesOnPlanets](Backend_Network_MessageAPI.md#getmessagesonplanets)\n\n## Functions\n\n### addMessage\n\n▸ **addMessage**(`request`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type                                               |\n| :-------- | :------------------------------------------------- |\n| `request` | `SignedMessage`<`PostMessageRequest`<`unknown`\\>\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### deleteMessages\n\n▸ **deleteMessages**(`request`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name      | Type                                      |\n| :-------- | :---------------------------------------- |\n| `request` | `SignedMessage`<`DeleteMessagesRequest`\\> |\n\n#### Returns\n\n`Promise`<`void`\\>\n\n---\n\n### getMessagesOnPlanets\n\n▸ **getMessagesOnPlanets**(`request`): `Promise`<`PlanetMessageResponse`\\>\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `request` | `PlanetMessageRequest` |\n\n#### Returns\n\n`Promise`<`PlanetMessageResponse`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Network_NetworkHealthApi.md",
    "content": "# Module: Backend/Network/NetworkHealthApi\n\n## Table of contents\n\n### Functions\n\n- [loadNetworkHealth](Backend_Network_NetworkHealthApi.md#loadnetworkhealth)\n\n## Functions\n\n### loadNetworkHealth\n\n▸ **loadNetworkHealth**(): `Promise`<`NetworkHealthSummary`\\>\n\nThe Dark Forest webserver keeps track of network health, this function loads that information\nfrom the webserver.\n\n#### Returns\n\n`Promise`<`NetworkHealthSummary`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Network_UtilityServerAPI.md",
    "content": "# Module: Backend/Network/UtilityServerAPI\n\n## Table of contents\n\n### Enumerations\n\n- [EmailResponse](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\n\n### Type aliases\n\n- [RegisterConfirmationResponse](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse)\n\n### Functions\n\n- [callRegisterAndWaitForConfirmation](Backend_Network_UtilityServerAPI.md#callregisterandwaitforconfirmation)\n- [disconnectTwitter](Backend_Network_UtilityServerAPI.md#disconnecttwitter)\n- [getAllTwitters](Backend_Network_UtilityServerAPI.md#getalltwitters)\n- [requestDevFaucet](Backend_Network_UtilityServerAPI.md#requestdevfaucet)\n- [submitInterestedEmail](Backend_Network_UtilityServerAPI.md#submitinterestedemail)\n- [submitPlayerEmail](Backend_Network_UtilityServerAPI.md#submitplayeremail)\n- [submitUnsubscribeEmail](Backend_Network_UtilityServerAPI.md#submitunsubscribeemail)\n- [submitWhitelistKey](Backend_Network_UtilityServerAPI.md#submitwhitelistkey)\n- [tryGetAllTwitters](Backend_Network_UtilityServerAPI.md#trygetalltwitters)\n- [verifyTwitterHandle](Backend_Network_UtilityServerAPI.md#verifytwitterhandle)\n- [whitelistStatus](Backend_Network_UtilityServerAPI.md#whiteliststatus)\n\n## Type aliases\n\n### RegisterConfirmationResponse\n\nƬ **RegisterConfirmationResponse**: `Object`\n\n#### Type declaration\n\n| Name            | Type      | Description                                                                                              |\n| :-------------- | :-------- | :------------------------------------------------------------------------------------------------------- |\n| `canRetry?`     | `boolean` | If the whitelist registration is unsuccessful, this is true if the client is able to retry registration. |\n| `errorMessage?` | `string`  | If the whitelist registration is unsuccessful, this is populated with the error message explaining why.  |\n| `txHash?`       | `string`  | If the whitelist registration is successful, this is populated with the hash of the transaction.         |\n\n## Functions\n\n### callRegisterAndWaitForConfirmation\n\n▸ **callRegisterAndWaitForConfirmation**(`key`, `address`, `terminal`): `Promise`<[`RegisterConfirmationResponse`](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse)\\>\n\nStarts the registration process for the user then\npolls for success.\n\n#### Parameters\n\n| Name       | Type                                                                                                            |\n| :--------- | :-------------------------------------------------------------------------------------------------------------- |\n| `key`      | `string`                                                                                                        |\n| `address`  | `EthAddress`                                                                                                    |\n| `terminal` | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n\n#### Returns\n\n`Promise`<[`RegisterConfirmationResponse`](Backend_Network_UtilityServerAPI.md#registerconfirmationresponse)\\>\n\n---\n\n### disconnectTwitter\n\n▸ **disconnectTwitter**(`disconnectMessage`): `Promise`<`boolean`\\>\n\n#### Parameters\n\n| Name                | Type                                      |\n| :------------------ | :---------------------------------------- |\n| `disconnectMessage` | `SignedMessage`<{ `twitter`: `string` }\\> |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### getAllTwitters\n\n▸ **getAllTwitters**(): `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\\>\n\n#### Returns\n\n`Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\\>\n\n---\n\n### requestDevFaucet\n\n▸ **requestDevFaucet**(`address`): `Promise`<`boolean`\\>\n\n#### Parameters\n\n| Name      | Type         |\n| :-------- | :----------- |\n| `address` | `EthAddress` |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### submitInterestedEmail\n\n▸ **submitInterestedEmail**(`email`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `email` | `string` |\n\n#### Returns\n\n`Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n---\n\n### submitPlayerEmail\n\n▸ **submitPlayerEmail**(`request?`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n#### Parameters\n\n| Name       | Type                                    |\n| :--------- | :-------------------------------------- |\n| `request?` | `SignedMessage`<{ `email`: `string` }\\> |\n\n#### Returns\n\n`Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n---\n\n### submitUnsubscribeEmail\n\n▸ **submitUnsubscribeEmail**(`email`): `Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `email` | `string` |\n\n#### Returns\n\n`Promise`<[`EmailResponse`](../enums/Backend_Network_UtilityServerAPI.EmailResponse.md)\\>\n\n---\n\n### submitWhitelistKey\n\n▸ **submitWhitelistKey**(`key`, `address`): `Promise`<`null` \\| `RegisterResponse`\\>\n\nSubmits a whitelist key to register the given player to the game. Returns null if there was an\nerror.\n\n#### Parameters\n\n| Name      | Type         |\n| :-------- | :----------- |\n| `key`     | `string`     |\n| `address` | `EthAddress` |\n\n#### Returns\n\n`Promise`<`null` \\| `RegisterResponse`\\>\n\n---\n\n### tryGetAllTwitters\n\n▸ **tryGetAllTwitters**(): `Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\\>\n\nSwallows all errors. Either loads the address to twitter map from the webserver in 5 seconds, or\nreturan empty map.\n\n#### Returns\n\n`Promise`<[`AddressTwitterMap`](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\\>\n\n---\n\n### verifyTwitterHandle\n\n▸ **verifyTwitterHandle**(`verifyMessage`): `Promise`<`boolean`\\>\n\n#### Parameters\n\n| Name            | Type                                      |\n| :-------------- | :---------------------------------------- |\n| `verifyMessage` | `SignedMessage`<{ `twitter`: `string` }\\> |\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### whitelistStatus\n\n▸ **whitelistStatus**(`address`): `Promise`<`null` \\| `WhitelistStatusResponse`\\>\n\n#### Parameters\n\n| Name      | Type         |\n| :-------- | :----------- |\n| `address` | `EthAddress` |\n\n#### Returns\n\n`Promise`<`null` \\| `WhitelistStatusResponse`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Plugins_EmbeddedPluginLoader.md",
    "content": "# Module: Backend/Plugins/EmbeddedPluginLoader\n\n## Table of contents\n\n### Interfaces\n\n- [EmbeddedPlugin](../interfaces/Backend_Plugins_EmbeddedPluginLoader.EmbeddedPlugin.md)\n\n### Functions\n\n- [getEmbeddedPlugins](Backend_Plugins_EmbeddedPluginLoader.md#getembeddedplugins)\n\n## Functions\n\n### getEmbeddedPlugins\n\n▸ **getEmbeddedPlugins**(`isAdmin`): { `code`: `string` ; `id`: `PluginId` ; `name`: `string` }[]\n\n#### Parameters\n\n| Name      | Type      |\n| :-------- | :-------- |\n| `isAdmin` | `boolean` |\n\n#### Returns\n\n{ `code`: `string` ; `id`: `PluginId` ; `name`: `string` }[]\n"
  },
  {
    "path": "docs/modules/Backend_Plugins_PluginProcess.md",
    "content": "# Module: Backend/Plugins/PluginProcess\n\n## Table of contents\n\n### Interfaces\n\n- [PluginProcess](../interfaces/Backend_Plugins_PluginProcess.PluginProcess.md)\n"
  },
  {
    "path": "docs/modules/Backend_Plugins_PluginTemplate.md",
    "content": "# Module: Backend/Plugins/PluginTemplate\n\n## Table of contents\n\n### Variables\n\n- [PLUGIN_TEMPLATE](Backend_Plugins_PluginTemplate.md#plugin_template)\n\n## Variables\n\n### PLUGIN_TEMPLATE\n\n• `Const` **PLUGIN_TEMPLATE**: `string`\n"
  },
  {
    "path": "docs/modules/Backend_Plugins_SerializedPlugin.md",
    "content": "# Module: Backend/Plugins/SerializedPlugin\n\n## Table of contents\n\n### Interfaces\n\n- [SerializedPlugin](../interfaces/Backend_Plugins_SerializedPlugin.SerializedPlugin.md)\n"
  },
  {
    "path": "docs/modules/Backend_Storage_PersistentChunkStore.md",
    "content": "# Module: Backend/Storage/PersistentChunkStore\n\n## Table of contents\n\n### Classes\n\n- [default](../classes/Backend_Storage_PersistentChunkStore.default.md)\n\n### Variables\n\n- [MODAL_POSITIONS_KEY](Backend_Storage_PersistentChunkStore.md#modal_positions_key)\n\n## Variables\n\n### MODAL_POSITIONS_KEY\n\n• `Const` **MODAL_POSITIONS_KEY**: `\"modal_positions\"`\n"
  },
  {
    "path": "docs/modules/Backend_Storage_ReaderDataStore.md",
    "content": "# Module: Backend/Storage/ReaderDataStore\n\n## Table of contents\n\n### Enumerations\n\n- [SinglePlanetDataStoreEvent](../enums/Backend_Storage_ReaderDataStore.SinglePlanetDataStoreEvent.md)\n\n### Classes\n\n- [default](../classes/Backend_Storage_ReaderDataStore.default.md)\n"
  },
  {
    "path": "docs/modules/Backend_Utils_Animation.md",
    "content": "# Module: Backend/Utils/Animation\n\n## Table of contents\n\n### Functions\n\n- [constantAnimation](Backend_Utils_Animation.md#constantanimation)\n- [easeInAnimation](Backend_Utils_Animation.md#easeinanimation)\n- [emojiEaseOutAnimation](Backend_Utils_Animation.md#emojieaseoutanimation)\n- [planetLevelToAnimationSpeed](Backend_Utils_Animation.md#planetleveltoanimationspeed)\n- [sinusoidalAnimation](Backend_Utils_Animation.md#sinusoidalanimation)\n\n## Functions\n\n### constantAnimation\n\n▸ **constantAnimation**(`constant`): `DFAnimation`\n\n#### Parameters\n\n| Name       | Type     |\n| :--------- | :------- |\n| `constant` | `number` |\n\n#### Returns\n\n`DFAnimation`\n\n---\n\n### easeInAnimation\n\n▸ **easeInAnimation**(`durationMs`, `delayMs?`): `DFAnimation`\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `durationMs` | `number` |\n| `delayMs?`   | `number` |\n\n#### Returns\n\n`DFAnimation`\n\n---\n\n### emojiEaseOutAnimation\n\n▸ **emojiEaseOutAnimation**(`durationMs`, `emoji`): `DFStatefulAnimation`<`string`\\>\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `durationMs` | `number` |\n| `emoji`      | `string` |\n\n#### Returns\n\n`DFStatefulAnimation`<`string`\\>\n\n---\n\n### planetLevelToAnimationSpeed\n\n▸ **planetLevelToAnimationSpeed**(`level`): `number`\n\n#### Parameters\n\n| Name    | Type          |\n| :------ | :------------ |\n| `level` | `PlanetLevel` |\n\n#### Returns\n\n`number`\n\n---\n\n### sinusoidalAnimation\n\n▸ **sinusoidalAnimation**(`rps`): `DFAnimation`\n\n#### Parameters\n\n| Name  | Type     |\n| :---- | :------- |\n| `rps` | `number` |\n\n#### Returns\n\n`DFAnimation`\n"
  },
  {
    "path": "docs/modules/Backend_Utils_Coordinates.md",
    "content": "# Module: Backend/Utils/Coordinates\n\n## Table of contents\n\n### Functions\n\n- [coordsEqual](Backend_Utils_Coordinates.md#coordsequal)\n- [distL2](Backend_Utils_Coordinates.md#distl2)\n- [normalizeVector](Backend_Utils_Coordinates.md#normalizevector)\n- [scaleVector](Backend_Utils_Coordinates.md#scalevector)\n- [vectorLength](Backend_Utils_Coordinates.md#vectorlength)\n\n## Functions\n\n### coordsEqual\n\n▸ **coordsEqual**(`a`, `b`): `boolean`\n\n#### Parameters\n\n| Name | Type          |\n| :--- | :------------ |\n| `a`  | `WorldCoords` |\n| `b`  | `WorldCoords` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### distL2\n\n▸ **distL2**(`a`, `b`): `number`\n\n#### Parameters\n\n| Name | Type                            |\n| :--- | :------------------------------ |\n| `a`  | `WorldCoords` \\| `CanvasCoords` |\n| `b`  | `WorldCoords` \\| `CanvasCoords` |\n\n#### Returns\n\n`number`\n\n---\n\n### normalizeVector\n\n▸ **normalizeVector**(`a`): `WorldCoords`\n\n#### Parameters\n\n| Name | Type          |\n| :--- | :------------ |\n| `a`  | `WorldCoords` |\n\n#### Returns\n\n`WorldCoords`\n\n---\n\n### scaleVector\n\n▸ **scaleVector**(`a`, `k`): `Object`\n\n#### Parameters\n\n| Name | Type          |\n| :--- | :------------ |\n| `a`  | `WorldCoords` |\n| `k`  | `number`      |\n\n#### Returns\n\n`Object`\n\n| Name | Type     |\n| :--- | :------- |\n| `x`  | `number` |\n| `y`  | `number` |\n\n---\n\n### vectorLength\n\n▸ **vectorLength**(`a`): `number`\n\n#### Parameters\n\n| Name | Type                            |\n| :--- | :------------------------------ |\n| `a`  | `WorldCoords` \\| `CanvasCoords` |\n\n#### Returns\n\n`number`\n"
  },
  {
    "path": "docs/modules/Backend_Utils_SnarkArgsHelper.md",
    "content": "# Module: Backend/Utils/SnarkArgsHelper\n\n## Table of contents\n\n### Classes\n\n- [default](../classes/Backend_Utils_SnarkArgsHelper.default.md)\n"
  },
  {
    "path": "docs/modules/Backend_Utils_Utils.md",
    "content": "# Module: Backend/Utils/Utils\n\n## Table of contents\n\n### Variables\n\n- [ONE_DAY](Backend_Utils_Utils.md#one_day)\n\n### Functions\n\n- [getFormatProp](Backend_Utils_Utils.md#getformatprop)\n- [getOwnerColor](Backend_Utils_Utils.md#getownercolor)\n- [getPlanetMaxRank](Backend_Utils_Utils.md#getplanetmaxrank)\n- [getPlanetRank](Backend_Utils_Utils.md#getplanetrank)\n- [getPlanetShortHash](Backend_Utils_Utils.md#getplanetshorthash)\n- [getPlayerColor](Backend_Utils_Utils.md#getplayercolor)\n- [getPlayerShortHash](Backend_Utils_Utils.md#getplayershorthash)\n- [getRandomActionId](Backend_Utils_Utils.md#getrandomactionid)\n- [getUpgradeStat](Backend_Utils_Utils.md#getupgradestat)\n- [hexifyBigIntNestedArray](Backend_Utils_Utils.md#hexifybigintnestedarray)\n- [hslStr](Backend_Utils_Utils.md#hslstr)\n- [isFullRank](Backend_Utils_Utils.md#isfullrank)\n- [titleCase](Backend_Utils_Utils.md#titlecase)\n- [upgradeName](Backend_Utils_Utils.md#upgradename)\n\n## Variables\n\n### ONE_DAY\n\n• `Const` **ONE_DAY**: `number`\n\n## Functions\n\n### getFormatProp\n\n▸ **getFormatProp**(`planet`, `prop`): `string`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n| `prop`   | `string`                |\n\n#### Returns\n\n`string`\n\n---\n\n### getOwnerColor\n\n▸ **getOwnerColor**(`planet`): `string`\n\n#### Parameters\n\n| Name     | Type     |\n| :------- | :------- |\n| `planet` | `Planet` |\n\n#### Returns\n\n`string`\n\n---\n\n### getPlanetMaxRank\n\n▸ **getPlanetMaxRank**(`planet`): `number`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`number`\n\n---\n\n### getPlanetRank\n\n▸ **getPlanetRank**(`planet`): `number`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`number`\n\n---\n\n### getPlanetShortHash\n\n▸ **getPlanetShortHash**(`planet`): `string`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`string`\n\n---\n\n### getPlayerColor\n\n▸ **getPlayerColor**(`player`): `string`\n\n#### Parameters\n\n| Name     | Type         |\n| :------- | :----------- |\n| `player` | `EthAddress` |\n\n#### Returns\n\n`string`\n\n---\n\n### getPlayerShortHash\n\n▸ **getPlayerShortHash**(`address`): `string`\n\n#### Parameters\n\n| Name      | Type         |\n| :-------- | :----------- |\n| `address` | `EthAddress` |\n\n#### Returns\n\n`string`\n\n---\n\n### getRandomActionId\n\n▸ **getRandomActionId**(): `string`\n\n#### Returns\n\n`string`\n\n---\n\n### getUpgradeStat\n\n▸ **getUpgradeStat**(`upgrade`, `stat`): `number`\n\n#### Parameters\n\n| Name      | Type                                                      |\n| :-------- | :-------------------------------------------------------- |\n| `upgrade` | `Upgrade`                                                 |\n| `stat`    | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) |\n\n#### Returns\n\n`number`\n\n---\n\n### hexifyBigIntNestedArray\n\n▸ **hexifyBigIntNestedArray**(`arr`): `NestedStringArray`\n\n#### Parameters\n\n| Name  | Type                |\n| :---- | :------------------ |\n| `arr` | `NestedBigIntArray` |\n\n#### Returns\n\n`NestedStringArray`\n\n---\n\n### hslStr\n\n▸ **hslStr**(`h`, `s`, `l`): `string`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `h`  | `number` |\n| `s`  | `number` |\n| `l`  | `number` |\n\n#### Returns\n\n`string`\n\n---\n\n### isFullRank\n\n▸ **isFullRank**(`planet`): `boolean`\n\n#### Parameters\n\n| Name     | Type                    |\n| :------- | :---------------------- |\n| `planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### titleCase\n\n▸ **titleCase**(`title`): `string`\n\n#### Parameters\n\n| Name    | Type     |\n| :------ | :------- |\n| `title` | `string` |\n\n#### Returns\n\n`string`\n\n---\n\n### upgradeName\n\n▸ **upgradeName**(`branchName`): `string`\n\n#### Parameters\n\n| Name         | Type                |\n| :----------- | :------------------ |\n| `branchName` | `UpgradeBranchName` |\n\n#### Returns\n\n`string`\n"
  },
  {
    "path": "docs/modules/Backend_Utils_WhitelistSnarkArgsHelper.md",
    "content": "# Module: Backend/Utils/WhitelistSnarkArgsHelper\n\n## Table of contents\n\n### Functions\n\n- [getWhitelistArgs](Backend_Utils_WhitelistSnarkArgsHelper.md#getwhitelistargs)\n\n## Functions\n\n### getWhitelistArgs\n\n▸ **getWhitelistArgs**(`key`, `recipient`, `terminal?`): `Promise`<`WhitelistSnarkContractCallArgs`\\>\n\nHelper method for generating whitelist SNARKS.\nThis is separate from the existing {@link SnarkArgsHelper}\nbecause whitelist txs require far less setup compared\nto SNARKS that are sent in context of the game.\n\n#### Parameters\n\n| Name        | Type                                                                                                            |\n| :---------- | :-------------------------------------------------------------------------------------------------------------- |\n| `key`       | `BigInteger`                                                                                                    |\n| `recipient` | `EthAddress`                                                                                                    |\n| `terminal?` | `MutableRefObject`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\> |\n\n#### Returns\n\n`Promise`<`WhitelistSnarkContractCallArgs`\\>\n"
  },
  {
    "path": "docs/modules/Backend_Utils_Wrapper.md",
    "content": "# Module: Backend/Utils/Wrapper\n\n## Table of contents\n\n### Classes\n\n- [Wrapper](../classes/Backend_Utils_Wrapper.Wrapper.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Components_AncientLabel.md",
    "content": "# Module: Frontend/Components/AncientLabel\n\n## Table of contents\n\n### Variables\n\n- [ancientAnim](Frontend_Components_AncientLabel.md#ancientanim)\n\n### Functions\n\n- [AncientLabel](Frontend_Components_AncientLabel.md#ancientlabel)\n- [AncientLabelAnim](Frontend_Components_AncientLabel.md#ancientlabelanim)\n\n## Variables\n\n### ancientAnim\n\n• `Const` **ancientAnim**: `FlattenSimpleInterpolation`\n\n## Functions\n\n### AncientLabel\n\n▸ **AncientLabel**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### AncientLabelAnim\n\n▸ **AncientLabelAnim**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_ArtifactImage.md",
    "content": "# Module: Frontend/Components/ArtifactImage\n\n## Table of contents\n\n### Variables\n\n- [ARTIFACT_URL](Frontend_Components_ArtifactImage.md#artifact_url)\n\n### Functions\n\n- [ArtifactImage](Frontend_Components_ArtifactImage.md#artifactimage)\n\n## Variables\n\n### ARTIFACT_URL\n\n• `Const` **ARTIFACT_URL**: `\"https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/\"`\n\n## Functions\n\n### ArtifactImage\n\n▸ **ArtifactImage**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                |\n| :--------------------------- | :------------------ |\n| `__namedParameters`          | `Object`            |\n| `__namedParameters.artifact` | `Artifact`          |\n| `__namedParameters.bgColor?` | `ArtifactFileColor` |\n| `__namedParameters.size`     | `number`            |\n| `__namedParameters.thumb?`   | `boolean`           |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_BiomeAnims.md",
    "content": "# Module: Frontend/Components/BiomeAnims\n\n## Table of contents\n\n### Variables\n\n- [burnAnim](Frontend_Components_BiomeAnims.md#burnanim)\n- [icyAnim](Frontend_Components_BiomeAnims.md#icyanim)\n- [shakeAnim](Frontend_Components_BiomeAnims.md#shakeanim)\n- [wiggle](Frontend_Components_BiomeAnims.md#wiggle)\n\n## Variables\n\n### burnAnim\n\n• `Const` **burnAnim**: `FlattenSimpleInterpolation`\n\n---\n\n### icyAnim\n\n• `Const` **icyAnim**: `FlattenSimpleInterpolation`\n\n---\n\n### shakeAnim\n\n• `Const` **shakeAnim**: `FlattenSimpleInterpolation`\n\n---\n\n### wiggle\n\n• `Const` **wiggle**: `Keyframes`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Btn.md",
    "content": "# Module: Frontend/Components/Btn\n\n## Table of contents\n\n### Classes\n\n- [DarkForestButton](../classes/Frontend_Components_Btn.DarkForestButton.md)\n- [DarkForestShortcutButton](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md)\n- [ShortcutPressedEvent](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)\n\n### Variables\n\n- [Btn](Frontend_Components_Btn.md#btn)\n- [ShortcutBtn](Frontend_Components_Btn.md#shortcutbtn)\n\n## Variables\n\n### Btn\n\n• `Const` **Btn**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### ShortcutBtn\n\n• `Const` **ShortcutBtn**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Button.md",
    "content": "# Module: Frontend/Components/Button\n\n## Table of contents\n\n### Functions\n\n- [default](Frontend_Components_Button.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type          |\n| :------------------ | :------------ |\n| `__namedParameters` | `ButtonProps` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_CapturePlanetButton.md",
    "content": "# Module: Frontend/Components/CapturePlanetButton\n\n## Table of contents\n\n### Functions\n\n- [CapturePlanetButton](Frontend_Components_CapturePlanetButton.md#captureplanetbutton)\n\n## Functions\n\n### CapturePlanetButton\n\n▸ **CapturePlanetButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                              | Type                                                                               |\n| :-------------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`               | `Object`                                                                           |\n| `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_CoreUI.md",
    "content": "# Module: Frontend/Components/CoreUI\n\n## Table of contents\n\n### Variables\n\n- [AlignCenterHorizontally](Frontend_Components_CoreUI.md#aligncenterhorizontally)\n- [AlignCenterVertically](Frontend_Components_CoreUI.md#aligncentervertically)\n- [BorderlessPane](Frontend_Components_CoreUI.md#borderlesspane)\n- [Bottom](Frontend_Components_CoreUI.md#bottom)\n- [CenterBackgroundSubtext](Frontend_Components_CoreUI.md#centerbackgroundsubtext)\n- [CenterRow](Frontend_Components_CoreUI.md#centerrow)\n- [Display](Frontend_Components_CoreUI.md#display)\n- [DontShrink](Frontend_Components_CoreUI.md#dontshrink)\n- [EmSpacer](Frontend_Components_CoreUI.md#emspacer)\n- [Emphasized](Frontend_Components_CoreUI.md#emphasized)\n- [Expand](Frontend_Components_CoreUI.md#expand)\n- [FloatRight](Frontend_Components_CoreUI.md#floatright)\n- [FullHeight](Frontend_Components_CoreUI.md#fullheight)\n- [HeaderText](Frontend_Components_CoreUI.md#headertext)\n- [Hidden](Frontend_Components_CoreUI.md#hidden)\n- [InlineBlock](Frontend_Components_CoreUI.md#inlineblock)\n- [MaxWidth](Frontend_Components_CoreUI.md#maxwidth)\n- [Padded](Frontend_Components_CoreUI.md#padded)\n- [PluginElements](Frontend_Components_CoreUI.md#pluginelements)\n- [Section](Frontend_Components_CoreUI.md#section)\n- [SectionHeader](Frontend_Components_CoreUI.md#sectionheader)\n- [Select](Frontend_Components_CoreUI.md#select)\n- [Separator](Frontend_Components_CoreUI.md#separator)\n- [Spacer](Frontend_Components_CoreUI.md#spacer)\n- [Spread](Frontend_Components_CoreUI.md#spread)\n- [SpreadApart](Frontend_Components_CoreUI.md#spreadapart)\n- [TextButton](Frontend_Components_CoreUI.md#textbutton)\n- [Title](Frontend_Components_CoreUI.md#title)\n- [Truncate](Frontend_Components_CoreUI.md#truncate)\n- [Underline](Frontend_Components_CoreUI.md#underline)\n\n### Functions\n\n- [Link](Frontend_Components_CoreUI.md#link)\n- [SelectFrom](Frontend_Components_CoreUI.md#selectfrom)\n- [VerticalSplit](Frontend_Components_CoreUI.md#verticalsplit)\n\n## Variables\n\n### AlignCenterHorizontally\n\n• `Const` **AlignCenterHorizontally**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nFills parent width, aligns children horizontally in the center.\n\n---\n\n### AlignCenterVertically\n\n• `Const` **AlignCenterVertically**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### BorderlessPane\n\n• `Const` **BorderlessPane**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### Bottom\n\n• `Const` **Bottom**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### CenterBackgroundSubtext\n\n• `Const` **CenterBackgroundSubtext**: `StyledComponent`<`\"div\"`, `any`, { `height`: `string` ; `width`: `string` }, `never`\\>\n\nA box which centers some darkened text. Useful for displaying\n_somthing_ instead of empty space, if there isn't something to\nbe displayed. Think of it as a placeholder.\n\n---\n\n### CenterRow\n\n• `Const` **CenterRow**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### Display\n\n• `Const` **Display**: `StyledComponent`<`\"div\"`, `any`, { `visible?`: `boolean` }, `never`\\>\n\n---\n\n### DontShrink\n\n• `Const` **DontShrink**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nDon't shrink in a flexbox.\n\n---\n\n### EmSpacer\n\n• `Const` **EmSpacer**: `StyledComponent`<`\"div\"`, `any`, { `height?`: `number` ; `width?`: `number` }, `never`\\>\n\nInline block rectangle, measured in ems, default 1em by 1em.\n\n---\n\n### Emphasized\n\n• `Const` **Emphasized**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Expand\n\n• `Const` **Expand**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nExpands to fill space in a flexbox.\n\n---\n\n### FloatRight\n\n• `Const` **FloatRight**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### FullHeight\n\n• `Const` **FullHeight**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### HeaderText\n\n• `Const` **HeaderText**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### Hidden\n\n• `Const` **Hidden**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### InlineBlock\n\n• `Const` **InlineBlock**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### MaxWidth\n\n• `Const` **MaxWidth**: `StyledComponent`<`\"div\"`, `any`, { `width`: `string` }, `never`\\>\n\n---\n\n### Padded\n\n• `Const` **Padded**: `StyledComponent`<`\"div\"`, `any`, { `bottom?`: `string` ; `left?`: `string` ; `right?`: `string` ; `top?`: `string` }, `never`\\>\n\n---\n\n### PluginElements\n\n• `Const` **PluginElements**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nThe container element into which a plugin renders its html elements.\nContains styles for child elements so that plugins can use UI\nthat is consistent with the rest of Dark Forest's UI. Keeping this up\nto date will be an ongoing challange, but there's probably some better\nway to do this.\n\n---\n\n### Section\n\n• `Const` **Section**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### SectionHeader\n\n• `Const` **SectionHeader**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### Select\n\n• `Const` **Select**: `StyledComponent`<`\"select\"`, `any`, { `wide?`: `boolean` }, `never`\\>\n\n---\n\n### Separator\n\n• `Const` **Separator**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### Spacer\n\n• `Const` **Spacer**: `StyledComponent`<`\"div\"`, `any`, { `height?`: `number` ; `width?`: `number` }, `never`\\>\n\n---\n\n### Spread\n\n• `Const` **Spread**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nExpands to fit the width of container. Is itself a flex box that spreads out its children\nhorizontally.\n\n---\n\n### SpreadApart\n\n• `Const` **SpreadApart**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\nExpands to fit the width of container. Is itself a flex box that spreads out its children\nhorizontally.\n\n---\n\n### TextButton\n\n• `Const` **TextButton**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Title\n\n• `Const` **Title**: `StyledComponent`<`\"div\"`, `any`, { `maxWidth?`: `string` }, `never`\\>\n\n---\n\n### Truncate\n\n• `Const` **Truncate**: `StyledComponent`<`\"div\"`, `any`, { `maxWidth?`: `string` }, `never`\\>\n\n---\n\n### Underline\n\n• `Const` **Underline**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n## Functions\n\n### Link\n\n▸ **Link**(`props`): `Element`\n\nThis is the link that all core ui in Dark Forest should use. Please!\n\n#### Parameters\n\n| Name    | Type                                                                                                                                          |\n| :------ | :-------------------------------------------------------------------------------------------------------------------------------------------- |\n| `props` | { `children`: `ReactNode` ; `color?`: `string` ; `openInThisTab?`: `boolean` ; `to?`: `string` } & `HtmlHTMLAttributes`<`HTMLAnchorElement`\\> |\n\n#### Returns\n\n`Element`\n\n---\n\n### SelectFrom\n\n▸ **SelectFrom**(`__namedParameters`): `Element`\n\nControllable input that allows the user to select from one of the\ngiven string values.\n\n#### Parameters\n\n| Name                         | Type                          |\n| :--------------------------- | :---------------------------- |\n| `__namedParameters`          | `Object`                      |\n| `__namedParameters.labels`   | `string`[]                    |\n| `__namedParameters.style?`   | `CSSProperties`               |\n| `__namedParameters.value`    | `string`                      |\n| `__namedParameters.values`   | `string`[]                    |\n| `__namedParameters.wide?`    | `boolean`                     |\n| `__namedParameters.setValue` | (`value`: `string`) => `void` |\n\n#### Returns\n\n`Element`\n\n---\n\n### VerticalSplit\n\n▸ **VerticalSplit**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                       |\n| :--------------------------- | :------------------------- |\n| `__namedParameters`          | `Object`                   |\n| `__namedParameters.children` | [`ReactNode`, `ReactNode`] |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Corner.md",
    "content": "# Module: Frontend/Components/Corner\n\n## Table of contents\n\n### Functions\n\n- [Corner](Frontend_Components_Corner.md#corner)\n\n## Functions\n\n### Corner\n\n▸ **Corner**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type          |\n| :------------------ | :------------ |\n| `__namedParameters` | `CornerProps` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_DisplayGasPrices.md",
    "content": "# Module: Frontend/Components/DisplayGasPrices\n\n## Table of contents\n\n### Functions\n\n- [DisplayGasPrices](Frontend_Components_DisplayGasPrices.md#displaygasprices)\n\n## Functions\n\n### DisplayGasPrices\n\n▸ **DisplayGasPrices**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                           | Type        |\n| :----------------------------- | :---------- |\n| `__namedParameters`            | `Object`    |\n| `__namedParameters.gasPrices?` | `GasPrices` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Email.md",
    "content": "# Module: Frontend/Components/Email\n\n## Table of contents\n\n### Enumerations\n\n- [EmailCTAMode](../enums/Frontend_Components_Email.EmailCTAMode.md)\n\n### Functions\n\n- [EmailCTA](Frontend_Components_Email.md#emailcta)\n\n## Functions\n\n### EmailCTA\n\n▸ **EmailCTA**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                     | Type                                                                 |\n| :----------------------- | :------------------------------------------------------------------- |\n| `__namedParameters`      | `Object`                                                             |\n| `__namedParameters.mode` | [`EmailCTAMode`](../enums/Frontend_Components_Email.EmailCTAMode.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_GameLandingPageComponents.md",
    "content": "# Module: Frontend/Components/GameLandingPageComponents\n\n## Table of contents\n\n### Enumerations\n\n- [InitRenderState](../enums/Frontend_Components_GameLandingPageComponents.InitRenderState.md)\n\n### Variables\n\n- [Hidden](Frontend_Components_GameLandingPageComponents.md#hidden)\n\n### Functions\n\n- [GameWindowWrapper](Frontend_Components_GameLandingPageComponents.md#gamewindowwrapper)\n- [TerminalToggler](Frontend_Components_GameLandingPageComponents.md#terminaltoggler)\n- [TerminalWrapper](Frontend_Components_GameLandingPageComponents.md#terminalwrapper)\n- [Wrapper](Frontend_Components_GameLandingPageComponents.md#wrapper)\n\n## Variables\n\n### Hidden\n\n• `Const` **Hidden**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n## Functions\n\n### GameWindowWrapper\n\n▸ **GameWindowWrapper**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                  |\n| :------------------ | :-------------------- |\n| `__namedParameters` | `LandingWrapperProps` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TerminalToggler\n\n▸ **TerminalToggler**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                   | Type                                      |\n| :------------------------------------- | :---------------------------------------- |\n| `__namedParameters`                    | `Object`                                  |\n| `__namedParameters.setTerminalEnabled` | `Dispatch`<`SetStateAction`<`boolean`\\>\\> |\n| `__namedParameters.terminalEnabled`    | `boolean`                                 |\n\n#### Returns\n\n`Element`\n\n---\n\n### TerminalWrapper\n\n▸ **TerminalWrapper**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                  |\n| :------------------ | :-------------------- |\n| `__namedParameters` | `LandingWrapperProps` |\n\n#### Returns\n\n`Element`\n\n---\n\n### Wrapper\n\n▸ **Wrapper**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                  |\n| :------------------ | :-------------------- |\n| `__namedParameters` | `LandingWrapperProps` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_GameWindowComponents.md",
    "content": "# Module: Frontend/Components/GameWindowComponents\n\n## Table of contents\n\n### Type aliases\n\n- [PaneProps](Frontend_Components_GameWindowComponents.md#paneprops)\n\n### Variables\n\n- [CanvasContainer](Frontend_Components_GameWindowComponents.md#canvascontainer)\n- [CanvasWrapper](Frontend_Components_GameWindowComponents.md#canvaswrapper)\n- [MainWindow](Frontend_Components_GameWindowComponents.md#mainwindow)\n- [StyledPane](Frontend_Components_GameWindowComponents.md#styledpane)\n- [UpperLeft](Frontend_Components_GameWindowComponents.md#upperleft)\n- [WindowWrapper](Frontend_Components_GameWindowComponents.md#windowwrapper)\n\n## Type aliases\n\n### PaneProps\n\nƬ **PaneProps**: `Object`\n\n#### Type declaration\n\n| Name           | Type                                                  |\n| :------------- | :---------------------------------------------------- |\n| `children`     | `React.ReactNode`                                     |\n| `headerItems?` | `React.ReactNode`                                     |\n| `title`        | `string` \\| (`small`: `boolean`) => `React.ReactNode` |\n\n## Variables\n\n### CanvasContainer\n\n• `Const` **CanvasContainer**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### CanvasWrapper\n\n• `Const` **CanvasWrapper**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### MainWindow\n\n• `Const` **MainWindow**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### StyledPane\n\n• `Const` **StyledPane**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### UpperLeft\n\n• `Const` **UpperLeft**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n---\n\n### WindowWrapper\n\n• `Const` **WindowWrapper**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Icons.md",
    "content": "# Module: Frontend/Components/Icons\n\n## Table of contents\n\n### Type aliases\n\n- [IconType](Frontend_Components_Icons.md#icontype)\n\n### Variables\n\n- [Icon](Frontend_Components_Icons.md#icon)\n- [IconType](Frontend_Components_Icons.md#icontype)\n\n### Functions\n\n- [ArtifactFound](Frontend_Components_Icons.md#artifactfound)\n- [ArtifactProspected](Frontend_Components_Icons.md#artifactprospected)\n- [BranchIcon](Frontend_Components_Icons.md#branchicon)\n- [FoundCorrupted](Frontend_Components_Icons.md#foundcorrupted)\n- [FoundDeadSpace](Frontend_Components_Icons.md#founddeadspace)\n- [FoundDeepSpace](Frontend_Components_Icons.md#founddeepspace)\n- [FoundDesert](Frontend_Components_Icons.md#founddesert)\n- [FoundForest](Frontend_Components_Icons.md#foundforest)\n- [FoundGrassland](Frontend_Components_Icons.md#foundgrassland)\n- [FoundIce](Frontend_Components_Icons.md#foundice)\n- [FoundLava](Frontend_Components_Icons.md#foundlava)\n- [FoundOcean](Frontend_Components_Icons.md#foundocean)\n- [FoundPirates](Frontend_Components_Icons.md#foundpirates)\n- [FoundRuins](Frontend_Components_Icons.md#foundruins)\n- [FoundSilver](Frontend_Components_Icons.md#foundsilver)\n- [FoundSpace](Frontend_Components_Icons.md#foundspace)\n- [FoundSpacetimeRip](Frontend_Components_Icons.md#foundspacetimerip)\n- [FoundSwamp](Frontend_Components_Icons.md#foundswamp)\n- [FoundTradingPost](Frontend_Components_Icons.md#foundtradingpost)\n- [FoundTundra](Frontend_Components_Icons.md#foundtundra)\n- [FoundWasteland](Frontend_Components_Icons.md#foundwasteland)\n- [Generic](Frontend_Components_Icons.md#generic)\n- [MetPlayer](Frontend_Components_Icons.md#metplayer)\n- [PlanetAttacked](Frontend_Components_Icons.md#planetattacked)\n- [PlanetConquered](Frontend_Components_Icons.md#planetconquered)\n- [PlanetLost](Frontend_Components_Icons.md#planetlost)\n- [Quasar](Frontend_Components_Icons.md#quasar)\n- [RankIcon](Frontend_Components_Icons.md#rankicon)\n- [StatIcon](Frontend_Components_Icons.md#staticon)\n- [TxAccepted](Frontend_Components_Icons.md#txaccepted)\n- [TxConfirmed](Frontend_Components_Icons.md#txconfirmed)\n- [TxDeclined](Frontend_Components_Icons.md#txdeclined)\n- [TxInitialized](Frontend_Components_Icons.md#txinitialized)\n\n## Type aliases\n\n### IconType\n\nƬ **IconType**: `Abstract`<`string`, `\"IconType\"`\\>\n\n## Variables\n\n### Icon\n\n• `Const` **Icon**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestIcon`, `\"children\"`\\>\\> & `Events`<`unknown`\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### IconType\n\n• **IconType**: `Object`\n\n#### Type declaration\n\n| Name           | Type                                                |\n| :------------- | :-------------------------------------------------- |\n| `Activate`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Artifact`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Broadcast`    | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Capturable`   | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Check`        | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Deactivate`   | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Defense`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Deposit`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Destroyed`    | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `DoubleArrows` | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Energy`       | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `EnergyGrowth` | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `FastForward`  | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Hat`          | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Help`         | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Invadable`    | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Leaderboard`  | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Lock`         | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `MaxLevel`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Pause`        | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Pirates`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Planet`       | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `PlanetDex`    | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Play`         | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Plugin`       | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Range`        | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RankFour`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RankMax`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RankOne`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RankThree`    | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RankTwo`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Refresh`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `RightArrow`   | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Settings`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Share`        | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Silver`       | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `SilverGrowth` | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `SilverProd`   | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Sparkles`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Speed`        | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Target`       | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `TrashCan`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Twitter`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Upgrade`      | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `Withdraw`     | [`IconType`](Frontend_Components_Icons.md#icontype) |\n| `X`            | [`IconType`](Frontend_Components_Icons.md#icontype) |\n\n## Functions\n\n### ArtifactFound\n\n▸ **ArtifactFound**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactProspected\n\n▸ **ArtifactProspected**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### BranchIcon\n\n▸ **BranchIcon**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type     |\n| :------------------------- | :------- |\n| `__namedParameters`        | `Object` |\n| `__namedParameters.branch` | `number` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundCorrupted\n\n▸ **FoundCorrupted**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundDeadSpace\n\n▸ **FoundDeadSpace**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundDeepSpace\n\n▸ **FoundDeepSpace**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundDesert\n\n▸ **FoundDesert**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundForest\n\n▸ **FoundForest**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundGrassland\n\n▸ **FoundGrassland**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundIce\n\n▸ **FoundIce**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundLava\n\n▸ **FoundLava**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundOcean\n\n▸ **FoundOcean**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundPirates\n\n▸ **FoundPirates**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundRuins\n\n▸ **FoundRuins**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundSilver\n\n▸ **FoundSilver**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundSpace\n\n▸ **FoundSpace**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundSpacetimeRip\n\n▸ **FoundSpacetimeRip**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundSwamp\n\n▸ **FoundSwamp**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundTradingPost\n\n▸ **FoundTradingPost**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundTundra\n\n▸ **FoundTundra**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FoundWasteland\n\n▸ **FoundWasteland**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### Generic\n\n▸ **Generic**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### MetPlayer\n\n▸ **MetPlayer**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetAttacked\n\n▸ **PlanetAttacked**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetConquered\n\n▸ **PlanetConquered**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetLost\n\n▸ **PlanetLost**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### Quasar\n\n▸ **Quasar**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### RankIcon\n\n▸ **RankIcon**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### StatIcon\n\n▸ **StatIcon**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                     | Type                                                      |\n| :----------------------- | :-------------------------------------------------------- |\n| `__namedParameters`      | `Object`                                                  |\n| `__namedParameters.stat` | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### TxAccepted\n\n▸ **TxAccepted**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TxConfirmed\n\n▸ **TxConfirmed**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TxDeclined\n\n▸ **TxDeclined**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TxInitialized\n\n▸ **TxInitialized**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type        |\n| :------------------ | :---------- |\n| `__namedParameters` | `AlertIcon` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Input.md",
    "content": "# Module: Frontend/Components/Input\n\n## Table of contents\n\n### Classes\n\n- [DarkForestCheckbox](../classes/Frontend_Components_Input.DarkForestCheckbox.md)\n- [DarkForestColorInput](../classes/Frontend_Components_Input.DarkForestColorInput.md)\n- [DarkForestNumberInput](../classes/Frontend_Components_Input.DarkForestNumberInput.md)\n- [DarkForestTextInput](../classes/Frontend_Components_Input.DarkForestTextInput.md)\n\n### Variables\n\n- [Checkbox](Frontend_Components_Input.md#checkbox)\n- [ColorInput](Frontend_Components_Input.md#colorinput)\n- [NumberInput](Frontend_Components_Input.md#numberinput)\n- [TextInput](Frontend_Components_Input.md#textinput)\n\n## Variables\n\n### Checkbox\n\n• `Const` **Checkbox**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestCheckbox`](../classes/Frontend_Components_Input.DarkForestCheckbox.md), `\"children\"`\\>\\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestCheckbox`](../classes/Frontend_Components_Input.DarkForestCheckbox.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### ColorInput\n\n• `Const` **ColorInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestColorInput`](../classes/Frontend_Components_Input.DarkForestColorInput.md), `\"children\"`\\>\\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestColorInput`](../classes/Frontend_Components_Input.DarkForestColorInput.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### NumberInput\n\n• `Const` **NumberInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestNumberInput`](../classes/Frontend_Components_Input.DarkForestNumberInput.md), `\"children\"`\\>\\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestNumberInput`](../classes/Frontend_Components_Input.DarkForestNumberInput.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### TextInput\n\n• `Const` **TextInput**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestTextInput`](../classes/Frontend_Components_Input.DarkForestTextInput.md), `\"children\"`\\>\\> & `Events`<{ `onBlur`: (`e`: `Event`) => `void` ; `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestTextInput`](../classes/Frontend_Components_Input.DarkForestTextInput.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_ArtifactLabels.md",
    "content": "# Module: Frontend/Components/Labels/ArtifactLabels\n\n## Table of contents\n\n### Variables\n\n- [StyledArtifactRarityLabel](Frontend_Components_Labels_ArtifactLabels.md#styledartifactraritylabel)\n\n### Functions\n\n- [ArtifactBiomeText](Frontend_Components_Labels_ArtifactLabels.md#artifactbiometext)\n- [ArtifactRarityBiomeTypeText](Frontend_Components_Labels_ArtifactLabels.md#artifactraritybiometypetext)\n- [ArtifactRarityLabel](Frontend_Components_Labels_ArtifactLabels.md#artifactraritylabel)\n- [ArtifactRarityLabelAnim](Frontend_Components_Labels_ArtifactLabels.md#artifactraritylabelanim)\n- [ArtifactRarityText](Frontend_Components_Labels_ArtifactLabels.md#artifactraritytext)\n- [ArtifactTypeText](Frontend_Components_Labels_ArtifactLabels.md#artifacttypetext)\n\n## Variables\n\n### StyledArtifactRarityLabel\n\n• `Const` **StyledArtifactRarityLabel**: `StyledComponent`<`\"span\"`, `any`, { `rarity`: `ArtifactRarity` }, `never`\\>\n\n## Functions\n\n### ArtifactBiomeText\n\n▸ **ArtifactBiomeText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type       |\n| :--------------------------- | :--------- |\n| `__namedParameters`          | `Object`   |\n| `__namedParameters.artifact` | `Artifact` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactRarityBiomeTypeText\n\n▸ **ArtifactRarityBiomeTypeText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type       |\n| :--------------------------- | :--------- |\n| `__namedParameters`          | `Object`   |\n| `__namedParameters.artifact` | `Artifact` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactRarityLabel\n\n▸ **ArtifactRarityLabel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type             |\n| :------------------------- | :--------------- |\n| `__namedParameters`        | `Object`         |\n| `__namedParameters.rarity` | `ArtifactRarity` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactRarityLabelAnim\n\n▸ **ArtifactRarityLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type             |\n| :------------------------- | :--------------- |\n| `__namedParameters`        | `Object`         |\n| `__namedParameters.rarity` | `ArtifactRarity` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactRarityText\n\n▸ **ArtifactRarityText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type             |\n| :------------------------- | :--------------- |\n| `__namedParameters`        | `Object`         |\n| `__namedParameters.rarity` | `ArtifactRarity` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactTypeText\n\n▸ **ArtifactTypeText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type       |\n| :--------------------------- | :--------- |\n| `__namedParameters`          | `Object`   |\n| `__namedParameters.artifact` | `Artifact` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_BiomeLabels.md",
    "content": "# Module: Frontend/Components/Labels/BiomeLabels\n\n## Table of contents\n\n### Variables\n\n- [BiomeLabel](Frontend_Components_Labels_BiomeLabels.md#biomelabel)\n\n### Functions\n\n- [ArtifactBiomeLabel](Frontend_Components_Labels_BiomeLabels.md#artifactbiomelabel)\n- [ArtifactBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#artifactbiomelabelanim)\n- [BiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#biomelabelanim)\n- [OptionalPlanetBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#optionalplanetbiomelabelanim)\n- [PlanetBiomeLabelAnim](Frontend_Components_Labels_BiomeLabels.md#planetbiomelabelanim)\n\n## Variables\n\n### BiomeLabel\n\n• `Const` **BiomeLabel**: `StyledComponent`<`\"span\"`, `any`, { `biome`: `Biome` }, `never`\\>\n\nRenders colored text corresponding to a biome\n\n## Functions\n\n### ArtifactBiomeLabel\n\n▸ **ArtifactBiomeLabel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type       |\n| :--------------------------- | :--------- |\n| `__namedParameters`          | `Object`   |\n| `__namedParameters.artifact` | `Artifact` |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactBiomeLabelAnim\n\n▸ **ArtifactBiomeLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type       |\n| :--------------------------- | :--------- |\n| `__namedParameters`          | `Object`   |\n| `__namedParameters.artifact` | `Artifact` |\n\n#### Returns\n\n`Element`\n\n---\n\n### BiomeLabelAnim\n\n▸ **BiomeLabelAnim**(`__namedParameters`): `Element`\n\nRenders animated colored text corresponding to a biome\n\n#### Parameters\n\n| Name                      | Type     |\n| :------------------------ | :------- |\n| `__namedParameters`       | `Object` |\n| `__namedParameters.biome` | `Biome`  |\n\n#### Returns\n\n`Element`\n\n---\n\n### OptionalPlanetBiomeLabelAnim\n\n▸ **OptionalPlanetBiomeLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetBiomeLabelAnim\n\n▸ **PlanetBiomeLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type              |\n| :------------------------- | :---------------- |\n| `__namedParameters`        | `Object`          |\n| `__namedParameters.planet` | `LocatablePlanet` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_KeywordLabels.md",
    "content": "# Module: Frontend/Components/Labels/KeywordLabels\n\n## Table of contents\n\n### Functions\n\n- [ScoreLabel](Frontend_Components_Labels_KeywordLabels.md#scorelabel)\n- [ScoreLabelTip](Frontend_Components_Labels_KeywordLabels.md#scorelabeltip)\n- [SilverLabel](Frontend_Components_Labels_KeywordLabels.md#silverlabel)\n- [SilverLabelTip](Frontend_Components_Labels_KeywordLabels.md#silverlabeltip)\n\n## Functions\n\n### ScoreLabel\n\n▸ **ScoreLabel**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ScoreLabelTip\n\n▸ **ScoreLabelTip**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverLabel\n\n▸ **SilverLabel**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverLabelTip\n\n▸ **SilverLabelTip**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_Labels.md",
    "content": "# Module: Frontend/Components/Labels/Labels\n\n## Table of contents\n\n### Functions\n\n- [AccountLabel](Frontend_Components_Labels_Labels.md#accountlabel)\n- [TwitterLink](Frontend_Components_Labels_Labels.md#twitterlink)\n\n## Functions\n\n### AccountLabel\n\n▸ **AccountLabel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                            | Type            |\n| :---------------------------------------------- | :-------------- |\n| `__namedParameters`                             | `Object`        |\n| `__namedParameters.ethAddress?`                 | `EthAddress`    |\n| `__namedParameters.includeAddressIfHasTwitter?` | `boolean`       |\n| `__namedParameters.style?`                      | `CSSProperties` |\n| `__namedParameters.width?`                      | `string`        |\n\n#### Returns\n\n`Element`\n\n---\n\n### TwitterLink\n\n▸ **TwitterLink**(`__namedParameters`): `Element`\n\nLink to a twitter account.\n\n#### Parameters\n\n| Name                        | Type     |\n| :-------------------------- | :------- |\n| `__namedParameters`         | `Object` |\n| `__namedParameters.color?`  | `string` |\n| `__namedParameters.twitter` | `string` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_LavaLabel.md",
    "content": "# Module: Frontend/Components/Labels/LavaLabel\n\n## Table of contents\n\n### Variables\n\n- [LavaLabel](Frontend_Components_Labels_LavaLabel.md#lavalabel)\n\n### Functions\n\n- [LavaLabelRaw](Frontend_Components_Labels_LavaLabel.md#lavalabelraw)\n\n## Variables\n\n### LavaLabel\n\n• `Const` **LavaLabel**: `MemoExoticComponent`<() => `Element`\\>\n\n## Functions\n\n### LavaLabelRaw\n\n▸ **LavaLabelRaw**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_LegendaryLabel.md",
    "content": "# Module: Frontend/Components/Labels/LegendaryLabel\n\n## Table of contents\n\n### Variables\n\n- [LegendaryLabel](Frontend_Components_Labels_LegendaryLabel.md#legendarylabel)\n\n## Variables\n\n### LegendaryLabel\n\n• `Const` **LegendaryLabel**: `MemoExoticComponent`<() => `Element`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_MythicLabel.md",
    "content": "# Module: Frontend/Components/Labels/MythicLabel\n\n## Table of contents\n\n### Variables\n\n- [MythicLabel](Frontend_Components_Labels_MythicLabel.md#mythiclabel)\n\n### Functions\n\n- [MythicLabelText](Frontend_Components_Labels_MythicLabel.md#mythiclabeltext)\n\n## Variables\n\n### MythicLabel\n\n• `Const` **MythicLabel**: `MemoExoticComponent`<() => `Element`\\>\n\n## Functions\n\n### MythicLabelText\n\n▸ **MythicLabelText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type            |\n| :------------------------- | :-------------- |\n| `__namedParameters`        | `Object`        |\n| `__namedParameters.style?` | `CSSProperties` |\n| `__namedParameters.text`   | `string`        |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_PlanetLabels.md",
    "content": "# Module: Frontend/Components/Labels/PlanetLabels\n\n## Table of contents\n\n### Functions\n\n- [DefenseText](Frontend_Components_Labels_PlanetLabels.md#defensetext)\n- [EnergyCapText](Frontend_Components_Labels_PlanetLabels.md#energycaptext)\n- [EnergyGrowthText](Frontend_Components_Labels_PlanetLabels.md#energygrowthtext)\n- [EnergyText](Frontend_Components_Labels_PlanetLabels.md#energytext)\n- [GrowthText](Frontend_Components_Labels_PlanetLabels.md#growthtext)\n- [JunkText](Frontend_Components_Labels_PlanetLabels.md#junktext)\n- [LevelRankText](Frontend_Components_Labels_PlanetLabels.md#levelranktext)\n- [LevelRankTextEm](Frontend_Components_Labels_PlanetLabels.md#levelranktextem)\n- [PlanetBiomeTypeLabelAnim](Frontend_Components_Labels_PlanetLabels.md#planetbiometypelabelanim)\n- [PlanetEnergyLabel](Frontend_Components_Labels_PlanetLabels.md#planetenergylabel)\n- [PlanetLevel](Frontend_Components_Labels_PlanetLabels.md#planetlevel)\n- [PlanetLevelText](Frontend_Components_Labels_PlanetLabels.md#planetleveltext)\n- [PlanetOwnerLabel](Frontend_Components_Labels_PlanetLabels.md#planetownerlabel)\n- [PlanetRank](Frontend_Components_Labels_PlanetLabels.md#planetrank)\n- [PlanetRankText](Frontend_Components_Labels_PlanetLabels.md#planetranktext)\n- [PlanetSilverLabel](Frontend_Components_Labels_PlanetLabels.md#planetsilverlabel)\n- [PlanetTypeLabelAnim](Frontend_Components_Labels_PlanetLabels.md#planettypelabelanim)\n- [RangeText](Frontend_Components_Labels_PlanetLabels.md#rangetext)\n- [SilverCapText](Frontend_Components_Labels_PlanetLabels.md#silvercaptext)\n- [SilverGrowthText](Frontend_Components_Labels_PlanetLabels.md#silvergrowthtext)\n- [SilverText](Frontend_Components_Labels_PlanetLabels.md#silvertext)\n- [SpeedText](Frontend_Components_Labels_PlanetLabels.md#speedtext)\n- [StatText](Frontend_Components_Labels_PlanetLabels.md#stattext)\n\n## Functions\n\n### DefenseText\n\n▸ **DefenseText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyCapText\n\n▸ **EnergyCapText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyGrowthText\n\n▸ **EnergyGrowthText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyText\n\n▸ **EnergyText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### GrowthText\n\n▸ **GrowthText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                        |\n| :-------------------------- | :-------------------------- |\n| `__namedParameters`         | `Object`                    |\n| `__namedParameters.planet`  | `undefined` \\| `Planet`     |\n| `__namedParameters.style?`  | `CSSProperties`             |\n| `__namedParameters.getStat` | (`p`: `Planet`) => `number` |\n\n#### Returns\n\n`Element`\n\n---\n\n### JunkText\n\n▸ **JunkText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### LevelRankText\n\n▸ **LevelRankText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.delim?` | `string`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### LevelRankTextEm\n\n▸ **LevelRankTextEm**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.delim?` | `string`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetBiomeTypeLabelAnim\n\n▸ **PlanetBiomeTypeLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetEnergyLabel\n\n▸ **PlanetEnergyLabel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetLevel\n\n▸ **PlanetLevel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetLevelText\n\n▸ **PlanetLevelText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetOwnerLabel\n\n▸ **PlanetOwnerLabel**(`__namedParameters`): `Element`\n\nEither 'yours' in green text,\n\n#### Parameters\n\n| Name                                      | Type                    |\n| :---------------------------------------- | :---------------------- |\n| `__namedParameters`                       | `Object`                |\n| `__namedParameters.abbreviateOwnAddress?` | `boolean`               |\n| `__namedParameters.colorWithOwnerColor?`  | `boolean`               |\n| `__namedParameters.planet`                | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetRank\n\n▸ **PlanetRank**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetRankText\n\n▸ **PlanetRankText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetSilverLabel\n\n▸ **PlanetSilverLabel**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetTypeLabelAnim\n\n▸ **PlanetTypeLabelAnim**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### RangeText\n\n▸ **RangeText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.buff?`  | `number`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverCapText\n\n▸ **SilverCapText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverGrowthText\n\n▸ **SilverGrowthText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverText\n\n▸ **SilverText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SpeedText\n\n▸ **SpeedText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.buff?`  | `number`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### StatText\n\n▸ **StatText**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                        |\n| :-------------------------- | :-------------------------- |\n| `__namedParameters`         | `Object`                    |\n| `__namedParameters.buff?`   | `number`                    |\n| `__namedParameters.planet`  | `undefined` \\| `Planet`     |\n| `__namedParameters.style?`  | `CSSProperties`             |\n| `__namedParameters.getStat` | (`p`: `Planet`) => `number` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_SpacetimeRipLabel.md",
    "content": "# Module: Frontend/Components/Labels/SpacetimeRipLabel\n\n## Table of contents\n\n### Variables\n\n- [SpacetimeRipLabel](Frontend_Components_Labels_SpacetimeRipLabel.md#spacetimeriplabel)\n\n## Variables\n\n### SpacetimeRipLabel\n\n• `Const` **SpacetimeRipLabel**: `MemoExoticComponent`<() => `Element`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Labels_WastelandLabel.md",
    "content": "# Module: Frontend/Components/Labels/WastelandLabel\n\n## Table of contents\n\n### Variables\n\n- [WastelandLabel](Frontend_Components_Labels_WastelandLabel.md#wastelandlabel)\n\n### Functions\n\n- [WastelandLabelRaw](Frontend_Components_Labels_WastelandLabel.md#wastelandlabelraw)\n\n## Variables\n\n### WastelandLabel\n\n• `Const` **WastelandLabel**: `MemoExoticComponent`<() => `Element`\\>\n\n## Functions\n\n### WastelandLabelRaw\n\n▸ **WastelandLabelRaw**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_LoadingSpinner.md",
    "content": "# Module: Frontend/Components/LoadingSpinner\n\n## Table of contents\n\n### Functions\n\n- [LoadingSpinner](Frontend_Components_LoadingSpinner.md#loadingspinner)\n\n## Functions\n\n### LoadingSpinner\n\n▸ **LoadingSpinner**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                             | Type     |\n| :------------------------------- | :------- |\n| `__namedParameters`              | `Object` |\n| `__namedParameters.initialText?` | `string` |\n| `__namedParameters.rate?`        | `number` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_MaybeShortcutButton.md",
    "content": "# Module: Frontend/Components/MaybeShortcutButton\n\n## Table of contents\n\n### Functions\n\n- [MaybeShortcutButton](Frontend_Components_MaybeShortcutButton.md#maybeshortcutbutton)\n\n## Functions\n\n### MaybeShortcutButton\n\n▸ **MaybeShortcutButton**(`props`): `Element`\n\nA button that will show shortcuts if enabled globally in the game, otherwise it will display a normal button\n\nMust ONLY be used when a GameUIManager is available.\n\n#### Parameters\n\n| Name    | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| :------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `props` | `Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\> \\| `Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_MineArtifactButton.md",
    "content": "# Module: Frontend/Components/MineArtifactButton\n\n## Table of contents\n\n### Functions\n\n- [MineArtifactButton](Frontend_Components_MineArtifactButton.md#mineartifactbutton)\n\n## Functions\n\n### MineArtifactButton\n\n▸ **MineArtifactButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                              | Type                                                                               |\n| :-------------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`               | `Object`                                                                           |\n| `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Modal.md",
    "content": "# Module: Frontend/Components/Modal\n\n## Table of contents\n\n### Classes\n\n- [DarkForestModal](../classes/Frontend_Components_Modal.DarkForestModal.md)\n- [PositionChangedEvent](../classes/Frontend_Components_Modal.PositionChangedEvent.md)\n\n### Variables\n\n- [Modal](Frontend_Components_Modal.md#modal)\n\n## Variables\n\n### Modal\n\n• `Const` **Modal**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestModal`](../classes/Frontend_Components_Modal.DarkForestModal.md), `\"children\"`\\>\\> & `Events`<{ `onMouseDown`: (`evt`: `Event` & `MouseEvent`<[`DarkForestModal`](../classes/Frontend_Components_Modal.DarkForestModal.md), `MouseEvent`\\>) => `void` ; `onPositionChanged`: (`evt`: [`PositionChangedEvent`](../classes/Frontend_Components_Modal.PositionChangedEvent.md)) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_OpenPaneButtons.md",
    "content": "# Module: Frontend/Components/OpenPaneButtons\n\n## Table of contents\n\n### Functions\n\n- [OpenBroadcastPaneButton](Frontend_Components_OpenPaneButtons.md#openbroadcastpanebutton)\n- [OpenHatPaneButton](Frontend_Components_OpenPaneButtons.md#openhatpanebutton)\n- [OpenManagePlanetArtifactsButton](Frontend_Components_OpenPaneButtons.md#openmanageplanetartifactsbutton)\n- [OpenPaneButton](Frontend_Components_OpenPaneButtons.md#openpanebutton)\n- [OpenPlanetInfoButton](Frontend_Components_OpenPaneButtons.md#openplanetinfobutton)\n- [OpenUpgradeDetailsPaneButton](Frontend_Components_OpenPaneButtons.md#openupgradedetailspanebutton)\n\n## Functions\n\n### OpenBroadcastPaneButton\n\n▸ **OpenBroadcastPaneButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                   |\n| :--------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                               |\n| `__namedParameters.modal`    | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planetId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`Element`\n\n---\n\n### OpenHatPaneButton\n\n▸ **OpenHatPaneButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                   |\n| :--------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                               |\n| `__namedParameters.modal`    | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planetId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`Element`\n\n---\n\n### OpenManagePlanetArtifactsButton\n\n▸ **OpenManagePlanetArtifactsButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                   |\n| :--------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                               |\n| `__namedParameters.modal`    | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planetId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`Element`\n\n---\n\n### OpenPaneButton\n\n▸ **OpenPaneButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                             | Type                                                                      |\n| :------------------------------- | :------------------------------------------------------------------------ |\n| `__namedParameters`              | `Object`                                                                  |\n| `__namedParameters.helpContent?` | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>       |\n| `__namedParameters.modal`        | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md)    |\n| `__namedParameters.shortcut?`    | `string`                                                                  |\n| `__namedParameters.title`        | `string`                                                                  |\n| `__namedParameters.element`      | () => `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\> |\n\n#### Returns\n\n`Element`\n\n---\n\n### OpenPlanetInfoButton\n\n▸ **OpenPlanetInfoButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                   |\n| :--------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                               |\n| `__namedParameters.modal`    | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planetId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`Element`\n\n---\n\n### OpenUpgradeDetailsPaneButton\n\n▸ **OpenUpgradeDetailsPaneButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                   |\n| :--------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                               |\n| `__namedParameters.modal`    | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planetId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_PluginModal.md",
    "content": "# Module: Frontend/Components/PluginModal\n\n## Table of contents\n\n### Functions\n\n- [PluginModal](Frontend_Components_PluginModal.md#pluginmodal)\n\n## Functions\n\n### PluginModal\n\n▸ **PluginModal**(`__namedParameters`): `ReactPortal`\n\n#### Parameters\n\n| Name                          | Type                               |\n| :---------------------------- | :--------------------------------- |\n| `__namedParameters`           | `Object`                           |\n| `__namedParameters.container` | `Element`                          |\n| `__namedParameters.id`        | `ModalId`                          |\n| `__namedParameters.title`     | `string`                           |\n| `__namedParameters.width?`    | `string`                           |\n| `__namedParameters.onClose`   | () => `void`                       |\n| `__namedParameters.onRender`  | (`el`: `HTMLDivElement`) => `void` |\n\n#### Returns\n\n`ReactPortal`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_ReadMore.md",
    "content": "# Module: Frontend/Components/ReadMore\n\n## Table of contents\n\n### Functions\n\n- [ReadMore](Frontend_Components_ReadMore.md#readmore)\n\n## Functions\n\n### ReadMore\n\n▸ **ReadMore**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                    | Type                         |\n| :-------------------------------------- | :--------------------------- |\n| `__namedParameters`                     | `Object`                     |\n| `__namedParameters.children`            | `ReactNode` \\| `ReactNode`[] |\n| `__namedParameters.height?`             | `string`                     |\n| `__namedParameters.text?`               | `string`                     |\n| `__namedParameters.toggleButtonMargin?` | `string`                     |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_RemoteModal.md",
    "content": "# Module: Frontend/Components/RemoteModal\n\n## Table of contents\n\n### Functions\n\n- [RemoteModal](Frontend_Components_RemoteModal.md#remotemodal)\n\n## Functions\n\n### RemoteModal\n\n▸ **RemoteModal**(`__namedParameters`): `ReactPortal`\n\nAllows you to instantiate a modal, and render it into the desired element.\nUseful for loading temporary modals from ANYWHERE in the UI, not just\n[GameWindowLayout](Frontend_Views_GameWindowLayout.md#gamewindowlayout)\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                         |\n| :------------------ | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | `PropsWithChildren`<{ `container`: `Element` ; `id`: `ModalId` ; `title`: `string` ; `visible`: `boolean` ; `width?`: `string` ; `onClose`: () => `void` }\\> |\n\n#### Returns\n\n`ReactPortal`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Row.md",
    "content": "# Module: Frontend/Components/Row\n\n## Table of contents\n\n### Variables\n\n- [Row](Frontend_Components_Row.md#row)\n\n## Variables\n\n### Row\n\n• `Const` **Row**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestRow`, `\"children\"`\\>\\> & `Events`<`unknown`\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Slider.md",
    "content": "# Module: Frontend/Components/Slider\n\n## Table of contents\n\n### Classes\n\n- [DarkForestSlider](../classes/Frontend_Components_Slider.DarkForestSlider.md)\n- [DarkForestSliderHandle](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md)\n\n### Variables\n\n- [Slider](Frontend_Components_Slider.md#slider)\n- [SliderHandle](Frontend_Components_Slider.md#sliderhandle)\n\n## Variables\n\n### Slider\n\n• `Const` **Slider**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestSlider`](../classes/Frontend_Components_Slider.DarkForestSlider.md), `\"children\"`\\>\\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestSlider`](../classes/Frontend_Components_Slider.DarkForestSlider.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n\n---\n\n### SliderHandle\n\n• `Const` **SliderHandle**: `ForwardRefExoticComponent`<`Partial`<`Omit`<[`DarkForestSliderHandle`](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md), `\"children\"`\\>\\> & `Events`<{ `onChange`: (`e`: `Event` & `ChangeEvent`<[`DarkForestSliderHandle`](../classes/Frontend_Components_Slider.DarkForestSliderHandle.md)\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Text.md",
    "content": "# Module: Frontend/Components/Text\n\n## Table of contents\n\n### Variables\n\n- [Blue](Frontend_Components_Text.md#blue)\n- [Colored](Frontend_Components_Text.md#colored)\n- [Gold](Frontend_Components_Text.md#gold)\n- [Green](Frontend_Components_Text.md#green)\n- [HideSmall](Frontend_Components_Text.md#hidesmall)\n- [Invisible](Frontend_Components_Text.md#invisible)\n- [Red](Frontend_Components_Text.md#red)\n- [Smaller](Frontend_Components_Text.md#smaller)\n- [Sub](Frontend_Components_Text.md#sub)\n- [Subber](Frontend_Components_Text.md#subber)\n- [Text](Frontend_Components_Text.md#text)\n- [White](Frontend_Components_Text.md#white)\n\n### Functions\n\n- [ArtifactNameLink](Frontend_Components_Text.md#artifactnamelink)\n- [BlinkCursor](Frontend_Components_Text.md#blinkcursor)\n- [CenterChunkLink](Frontend_Components_Text.md#centerchunklink)\n- [CenterPlanetLink](Frontend_Components_Text.md#centerplanetlink)\n- [Coords](Frontend_Components_Text.md#coords)\n- [FAQ04Link](Frontend_Components_Text.md#faq04link)\n- [LongDash](Frontend_Components_Text.md#longdash)\n- [PlanetNameLink](Frontend_Components_Text.md#planetnamelink)\n- [TxLink](Frontend_Components_Text.md#txlink)\n\n## Variables\n\n### Blue\n\n• `Const` **Blue**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Colored\n\n• `Const` **Colored**: `StyledComponent`<`\"span\"`, `any`, { `color`: `string` }, `never`\\>\n\n---\n\n### Gold\n\n• `Const` **Gold**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Green\n\n• `Const` **Green**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### HideSmall\n\n• `Const` **HideSmall**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Invisible\n\n• `Const` **Invisible**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Red\n\n• `Const` **Red**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Smaller\n\n• `Const` **Smaller**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Sub\n\n• `Const` **Sub**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Subber\n\n• `Const` **Subber**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### Text\n\n• `Const` **Text**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n---\n\n### White\n\n• `Const` **White**: `StyledComponent`<`\"span\"`, `any`, {}, `never`\\>\n\n## Functions\n\n### ArtifactNameLink\n\n▸ **ArtifactNameLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                   | Type         |\n| :--------------------- | :----------- |\n| `__namedParameters`    | `Object`     |\n| `__namedParameters.id` | `ArtifactId` |\n\n#### Returns\n\n`Element`\n\n---\n\n### BlinkCursor\n\n▸ **BlinkCursor**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### CenterChunkLink\n\n▸ **CenterChunkLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type        |\n| :--------------------------- | :---------- |\n| `__namedParameters`          | `Object`    |\n| `__namedParameters.children` | `ReactNode` |\n| `__namedParameters.chunk`    | `Chunk`     |\n\n#### Returns\n\n`Element`\n\n---\n\n### CenterPlanetLink\n\n▸ **CenterPlanetLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type        |\n| :--------------------------- | :---------- |\n| `__namedParameters`          | `Object`    |\n| `__namedParameters.children` | `ReactNode` |\n| `__namedParameters.planet`   | `Planet`    |\n\n#### Returns\n\n`Element`\n\n---\n\n### Coords\n\n▸ **Coords**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type          |\n| :------------------------- | :------------ |\n| `__namedParameters`        | `Object`      |\n| `__namedParameters.coords` | `WorldCoords` |\n\n#### Returns\n\n`Element`\n\n---\n\n### FAQ04Link\n\n▸ **FAQ04Link**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type        |\n| :--------------------------- | :---------- |\n| `__namedParameters`          | `Object`    |\n| `__namedParameters.children` | `ReactNode` |\n\n#### Returns\n\n`Element`\n\n---\n\n### LongDash\n\n▸ **LongDash**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetNameLink\n\n▸ **PlanetNameLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type     |\n| :------------------------- | :------- |\n| `__namedParameters`        | `Object` |\n| `__namedParameters.planet` | `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TxLink\n\n▸ **TxLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                   | Type                       |\n| :--------------------- | :------------------------- |\n| `__namedParameters`    | `Object`                   |\n| `__namedParameters.tx` | `Transaction`<`TxIntent`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_TextLoadingBar.md",
    "content": "# Module: Frontend/Components/TextLoadingBar\n\n## Table of contents\n\n### Interfaces\n\n- [LoadingBarHandle](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\n\n### Variables\n\n- [TextLoadingBar](Frontend_Components_TextLoadingBar.md#textloadingbar)\n\n### Functions\n\n- [TextLoadingBarImpl](Frontend_Components_TextLoadingBar.md#textloadingbarimpl)\n\n## Variables\n\n### TextLoadingBar\n\n• `Const` **TextLoadingBar**: `ForwardRefExoticComponent`<`LoadingBarProps` & `RefAttributes`<`undefined` \\| [`LoadingBarHandle`](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\\>\\>\n\n## Functions\n\n### TextLoadingBarImpl\n\n▸ **TextLoadingBarImpl**(`__namedParameters`, `ref`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                               |\n| :------------------ | :------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | `LoadingBarProps`                                                                                  |\n| `ref`               | `Ref`<[`LoadingBarHandle`](../interfaces/Frontend_Components_TextLoadingBar.LoadingBarHandle.md)\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_TextPreview.md",
    "content": "# Module: Frontend/Components/TextPreview\n\n## Table of contents\n\n### Functions\n\n- [TextPreview](Frontend_Components_TextPreview.md#textpreview)\n\n## Functions\n\n### TextPreview\n\n▸ **TextPreview**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                | Type            |\n| :---------------------------------- | :-------------- |\n| `__namedParameters`                 | `Object`        |\n| `__namedParameters.focusedWidth?`   | `string`        |\n| `__namedParameters.maxLength?`      | `number`        |\n| `__namedParameters.style?`          | `CSSProperties` |\n| `__namedParameters.text?`           | `string`        |\n| `__namedParameters.unFocusedWidth?` | `string`        |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Components_Theme.md",
    "content": "# Module: Frontend/Components/Theme\n\n## Table of contents\n\n### Variables\n\n- [Theme](Frontend_Components_Theme.md#theme)\n\n## Variables\n\n### Theme\n\n• `Const` **Theme**: `ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestTheme`, `\"children\"`\\>\\> & `Events`<`unknown`\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Components_TimeUntil.md",
    "content": "# Module: Frontend/Components/TimeUntil\n\n## Table of contents\n\n### Functions\n\n- [TimeUntil](Frontend_Components_TimeUntil.md#timeuntil)\n- [formatDuration](Frontend_Components_TimeUntil.md#formatduration)\n\n## Functions\n\n### TimeUntil\n\n▸ **TimeUntil**(`__namedParameters`): `Element`\n\nGiven a timestamp, displays the amount of time until the timestamp from now in hh:mm:ss format.\nIf the timestamp is in the past, displays the given hardcoded value.\n\n#### Parameters\n\n| Name                          | Type     |\n| :---------------------------- | :------- |\n| `__namedParameters`           | `Object` |\n| `__namedParameters.ifPassed`  | `string` |\n| `__namedParameters.timestamp` | `number` |\n\n#### Returns\n\n`Element`\n\n---\n\n### formatDuration\n\n▸ **formatDuration**(`msDuration`): `string`\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `msDuration` | `number` |\n\n#### Returns\n\n`string`\n"
  },
  {
    "path": "docs/modules/Frontend_Game_ControllableCanvas.md",
    "content": "# Module: Frontend/Game/ControllableCanvas\n\n## Table of contents\n\n### Functions\n\n- [default](Frontend_Game_ControllableCanvas.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Game_ModalManager.md",
    "content": "# Module: Frontend/Game/ModalManager\n\n## Table of contents\n\n### Classes\n\n- [default](../classes/Frontend_Game_ModalManager.default.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Game_NotificationManager.md",
    "content": "# Module: Frontend/Game/NotificationManager\n\n## Table of contents\n\n### Enumerations\n\n- [NotificationManagerEvent](../enums/Frontend_Game_NotificationManager.NotificationManagerEvent.md)\n- [NotificationType](../enums/Frontend_Game_NotificationManager.NotificationType.md)\n\n### Classes\n\n- [default](../classes/Frontend_Game_NotificationManager.default.md)\n\n### Type aliases\n\n- [NotificationInfo](Frontend_Game_NotificationManager.md#notificationinfo)\n\n## Type aliases\n\n### NotificationInfo\n\nƬ **NotificationInfo**: `Object`\n\n#### Type declaration\n\n| Name        | Type                                                                                 |\n| :---------- | :----------------------------------------------------------------------------------- |\n| `color?`    | `string`                                                                             |\n| `icon`      | `React.ReactNode`                                                                    |\n| `id`        | `string`                                                                             |\n| `message`   | `React.ReactNode`                                                                    |\n| `txData?`   | `TxIntent`                                                                           |\n| `txStatus?` | `EthTxStatus`                                                                        |\n| `type`      | [`NotificationType`](../enums/Frontend_Game_NotificationManager.NotificationType.md) |\n"
  },
  {
    "path": "docs/modules/Frontend_Game_Popups.md",
    "content": "# Module: Frontend/Game/Popups\n\n## Table of contents\n\n### Functions\n\n- [openConfirmationWindowForTransaction](Frontend_Game_Popups.md#openconfirmationwindowfortransaction)\n\n## Functions\n\n### openConfirmationWindowForTransaction\n\n▸ **openConfirmationWindowForTransaction**(`__namedParameters`): `Promise`<`void`\\>\n\n#### Parameters\n\n| Name                | Type                     |\n| :------------------ | :----------------------- |\n| `__namedParameters` | `OpenConfirmationConfig` |\n\n#### Returns\n\n`Promise`<`void`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Game_Viewport.md",
    "content": "# Module: Frontend/Game/Viewport\n\n## Table of contents\n\n### Classes\n\n- [default](../classes/Frontend_Game_Viewport.default.md)\n\n### Functions\n\n- [getDefaultScroll](Frontend_Game_Viewport.md#getdefaultscroll)\n\n## Functions\n\n### getDefaultScroll\n\n▸ **getDefaultScroll**(): `number`\n\n#### Returns\n\n`number`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_App.md",
    "content": "# Module: Frontend/Pages/App\n\n## Table of contents\n\n### Functions\n\n- [default](Frontend_Pages_App.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_CreateLobby.md",
    "content": "# Module: Frontend/Pages/CreateLobby\n\n## Table of contents\n\n### Functions\n\n- [CreateLobby](Frontend_Pages_CreateLobby.md#createlobby)\n\n## Functions\n\n### CreateLobby\n\n▸ **CreateLobby**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                         |\n| :------------------ | :--------------------------------------------------------------------------- |\n| `__namedParameters` | `RouteComponentProps`<{ `contract`: `string` }, `StaticContext`, `unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_EventsPage.md",
    "content": "# Module: Frontend/Pages/EventsPage\n\n## Table of contents\n\n### Functions\n\n- [EventsPage](Frontend_Pages_EventsPage.md#eventspage)\n\n## Functions\n\n### EventsPage\n\n▸ **EventsPage**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_GameLandingPage.md",
    "content": "# Module: Frontend/Pages/GameLandingPage\n\n## Table of contents\n\n### Functions\n\n- [GameLandingPage](Frontend_Pages_GameLandingPage.md#gamelandingpage)\n\n## Functions\n\n### GameLandingPage\n\n▸ **GameLandingPage**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                         |\n| :------------------ | :--------------------------------------------------------------------------- |\n| `__namedParameters` | `RouteComponentProps`<{ `contract`: `string` }, `StaticContext`, `unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_GifMaker.md",
    "content": "# Module: Frontend/Pages/GifMaker\n\n## Table of contents\n\n### Variables\n\n- [GIF_ARTIFACT_COLOR](Frontend_Pages_GifMaker.md#gif_artifact_color)\n\n### Functions\n\n- [GifMaker](Frontend_Pages_GifMaker.md#gifmaker)\n\n## Variables\n\n### GIF_ARTIFACT_COLOR\n\n• `Const` **GIF_ARTIFACT_COLOR**: `ArtifactFileColor` = `ArtifactFileColor.APP_BACKGROUND`\n\n## Functions\n\n### GifMaker\n\n▸ **GifMaker**(): `Element`\n\nEntrypoint for gif and sprite generation, accessed via `yarn run gifs`.\nWait a second or so for the textures to get loaded, then click the buttons to download files as a zip.\ngifs are saved as 60fps webm, and can take a while - open the console to see progress (logged verbosely)\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_LandingPage.md",
    "content": "# Module: Frontend/Pages/LandingPage\n\n## Table of contents\n\n### Enumerations\n\n- [LandingPageZIndex](../enums/Frontend_Pages_LandingPage.LandingPageZIndex.md)\n\n### Variables\n\n- [LinkContainer](Frontend_Pages_LandingPage.md#linkcontainer)\n\n### Functions\n\n- [default](Frontend_Pages_LandingPage.md#default)\n\n## Variables\n\n### LinkContainer\n\n• `Const` **LinkContainer**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n## Functions\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_LoadingPage.md",
    "content": "# Module: Frontend/Pages/LoadingPage\n\n## Table of contents\n\n### Functions\n\n- [default](Frontend_Pages_LoadingPage.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_LobbyLandingPage.md",
    "content": "# Module: Frontend/Pages/LobbyLandingPage\n\n## Table of contents\n\n### Functions\n\n- [LobbyLandingPage](Frontend_Pages_LobbyLandingPage.md#lobbylandingpage)\n\n## Functions\n\n### LobbyLandingPage\n\n▸ **LobbyLandingPage**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                                      |\n| :-------------------------- | :---------------------------------------- |\n| `__namedParameters`         | `Object`                                  |\n| `__namedParameters.onReady` | (`connection`: `EthConnection`) => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_NotFoundPage.md",
    "content": "# Module: Frontend/Pages/NotFoundPage\n\n## Table of contents\n\n### Functions\n\n- [NotFoundPage](Frontend_Pages_NotFoundPage.md#notfoundpage)\n\n## Functions\n\n### NotFoundPage\n\n▸ **NotFoundPage**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_ShareArtifact.md",
    "content": "# Module: Frontend/Pages/ShareArtifact\n\n## Table of contents\n\n### Functions\n\n- [ShareArtifact](Frontend_Pages_ShareArtifact.md#shareartifact)\n\n## Functions\n\n### ShareArtifact\n\n▸ **ShareArtifact**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                               |\n| :------------------ | :--------------------------------------------------------------------------------- |\n| `__namedParameters` | `RouteComponentProps`<{ `artifactId`: `ArtifactId` }, `StaticContext`, `unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_SharePlanet.md",
    "content": "# Module: Frontend/Pages/SharePlanet\n\n## Table of contents\n\n### Functions\n\n- [SharePlanet](Frontend_Pages_SharePlanet.md#shareplanet)\n\n## Functions\n\n### SharePlanet\n\n▸ **SharePlanet**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                               |\n| :------------------ | :--------------------------------------------------------------------------------- |\n| `__namedParameters` | `RouteComponentProps`<{ `locationId`: `LocationId` }, `StaticContext`, `unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_TestArtifactImages.md",
    "content": "# Module: Frontend/Pages/TestArtifactImages\n\n## Table of contents\n\n### Functions\n\n- [TestArtifactImages](Frontend_Pages_TestArtifactImages.md#testartifactimages)\n\n## Functions\n\n### TestArtifactImages\n\n▸ **TestArtifactImages**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_TxConfirmPopup.md",
    "content": "# Module: Frontend/Pages/TxConfirmPopup\n\n## Table of contents\n\n### Functions\n\n- [TxConfirmPopup](Frontend_Pages_TxConfirmPopup.md#txconfirmpopup)\n\n## Functions\n\n### TxConfirmPopup\n\n▸ **TxConfirmPopup**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                              |\n| :------------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | `RouteComponentProps`<{ `actionId`: `string` ; `addr`: `string` ; `balance`: `string` ; `contract`: `string` ; `method`: `string` }, `StaticContext`, `unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_UnsubscribePage.md",
    "content": "# Module: Frontend/Pages/UnsubscribePage\n\n## Table of contents\n\n### Enumerations\n\n- [LandingPageZIndex](../enums/Frontend_Pages_UnsubscribePage.LandingPageZIndex.md)\n\n### Functions\n\n- [default](Frontend_Pages_UnsubscribePage.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Pages_ValhallaPage.md",
    "content": "# Module: Frontend/Pages/ValhallaPage\n\n## Table of contents\n\n### Functions\n\n- [ValhallaPage](Frontend_Pages_ValhallaPage.md#valhallapage)\n\n## Functions\n\n### ValhallaPage\n\n▸ **ValhallaPage**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ArtifactCard.md",
    "content": "# Module: Frontend/Panes/ArtifactCard\n\n## Table of contents\n\n### Functions\n\n- [ArtifactCard](Frontend_Panes_ArtifactCard.md#artifactcard)\n\n## Functions\n\n### ArtifactCard\n\n▸ **ArtifactCard**(`__namedParameters`): `null` \\| `Element`\n\n#### Parameters\n\n| Name                            | Type         |\n| :------------------------------ | :----------- |\n| `__namedParameters`             | `Object`     |\n| `__namedParameters.artifactId?` | `ArtifactId` |\n\n#### Returns\n\n`null` \\| `Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ArtifactDetailsPane.md",
    "content": "# Module: Frontend/Panes/ArtifactDetailsPane\n\n## Table of contents\n\n### Functions\n\n- [ArtifactDetailsBody](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailsbody)\n- [ArtifactDetailsHelpContent](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailshelpcontent)\n- [ArtifactDetailsPane](Frontend_Panes_ArtifactDetailsPane.md#artifactdetailspane)\n- [UpgradeStatInfo](Frontend_Panes_ArtifactDetailsPane.md#upgradestatinfo)\n\n## Functions\n\n### ArtifactDetailsBody\n\n▸ **ArtifactDetailsBody**(`__namedParameters`): `null` \\| `Element`\n\n#### Parameters\n\n| Name                                  | Type                                                                                             |\n| :------------------------------------ | :----------------------------------------------------------------------------------------------- |\n| `__namedParameters`                   | `Object`                                                                                         |\n| `__namedParameters.artifactId`        | `ArtifactId`                                                                                     |\n| `__namedParameters.contractConstants` | [`ContractConstants`](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md) |\n| `__namedParameters.depositOn?`        | `LocationId`                                                                                     |\n| `__namedParameters.modal?`            | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md)                           |\n| `__namedParameters.noActions?`        | `boolean`                                                                                        |\n\n#### Returns\n\n`null` \\| `Element`\n\n---\n\n### ArtifactDetailsHelpContent\n\n▸ **ArtifactDetailsHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactDetailsPane\n\n▸ **ArtifactDetailsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                           | Type                                                                   |\n| :----------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`            | `Object`                                                               |\n| `__namedParameters.artifactId` | `ArtifactId`                                                           |\n| `__namedParameters.depositOn?` | `LocationId`                                                           |\n| `__namedParameters.modal`      | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### UpgradeStatInfo\n\n▸ **UpgradeStatInfo**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                      |\n| :--------------------------- | :-------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                  |\n| `__namedParameters.stat`     | [`StatIdx`](../enums/types_global_GlobalTypes.StatIdx.md) |\n| `__namedParameters.upgrades` | (`undefined` \\| `Upgrade`)[]                              |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ArtifactHoverPane.md",
    "content": "# Module: Frontend/Panes/ArtifactHoverPane\n\n## Table of contents\n\n### Functions\n\n- [ArtifactHoverPane](Frontend_Panes_ArtifactHoverPane.md#artifacthoverpane)\n\n## Functions\n\n### ArtifactHoverPane\n\n▸ **ArtifactHoverPane**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ArtifactsList.md",
    "content": "# Module: Frontend/Panes/ArtifactsList\n\n## Table of contents\n\n### Functions\n\n- [AllArtifacts](Frontend_Panes_ArtifactsList.md#allartifacts)\n- [ArtifactsList](Frontend_Panes_ArtifactsList.md#artifactslist)\n- [ShipList](Frontend_Panes_ArtifactsList.md#shiplist)\n\n## Functions\n\n### AllArtifacts\n\n▸ **AllArtifacts**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                    | Type                                                                   |\n| :-------------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                     | `Object`                                                               |\n| `__namedParameters.artifacts`           | `Artifact`[]                                                           |\n| `__namedParameters.depositOn?`          | `LocationId`                                                           |\n| `__namedParameters.maxRarity?`          | `number`                                                               |\n| `__namedParameters.modal`               | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.noArtifactsMessage?` | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>    |\n| `__namedParameters.noShipsMessage?`     | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>    |\n\n#### Returns\n\n`Element`\n\n---\n\n### ArtifactsList\n\n▸ **ArtifactsList**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                    | Type                                                                   |\n| :-------------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                     | `Object`                                                               |\n| `__namedParameters.artifacts`           | `Artifact`[]                                                           |\n| `__namedParameters.depositOn?`          | `LocationId`                                                           |\n| `__namedParameters.maxRarity?`          | `number`                                                               |\n| `__namedParameters.modal`               | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.noArtifactsMessage?` | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>    |\n\n#### Returns\n\n`Element`\n\n---\n\n### ShipList\n\n▸ **ShipList**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                | Type                                                                   |\n| :---------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                               |\n| `__namedParameters.artifacts`       | `Artifact`[]                                                           |\n| `__namedParameters.depositOn?`      | `LocationId`                                                           |\n| `__namedParameters.modal`           | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.noShipsMessage?` | `ReactElement`<`any`, `string` \\| `JSXElementConstructor`<`any`\\>\\>    |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_BroadcastPane.md",
    "content": "# Module: Frontend/Panes/BroadcastPane\n\n## Table of contents\n\n### Functions\n\n- [BroadcastPane](Frontend_Panes_BroadcastPane.md#broadcastpane)\n- [BroadcastPaneHelpContent](Frontend_Panes_BroadcastPane.md#broadcastpanehelpcontent)\n\n## Functions\n\n### BroadcastPane\n\n▸ **BroadcastPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                | Type                                                                   |\n| :---------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                               |\n| `__namedParameters.initialPlanetId` | `undefined` \\| `LocationId`                                            |\n| `__namedParameters.modal`           | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### BroadcastPaneHelpContent\n\n▸ **BroadcastPaneHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_CoordsPane.md",
    "content": "# Module: Frontend/Panes/CoordsPane\n\n## Table of contents\n\n### Functions\n\n- [CoordsPane](Frontend_Panes_CoordsPane.md#coordspane)\n\n## Functions\n\n### CoordsPane\n\n▸ **CoordsPane**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_DiagnosticsPane.md",
    "content": "# Module: Frontend/Panes/DiagnosticsPane\n\n## Table of contents\n\n### Functions\n\n- [DiagnosticsPane](Frontend_Panes_DiagnosticsPane.md#diagnosticspane)\n\n## Functions\n\n### DiagnosticsPane\n\n▸ **DiagnosticsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ExplorePane.md",
    "content": "# Module: Frontend/Panes/ExplorePane\n\n## Table of contents\n\n### Functions\n\n- [ExplorePane](Frontend_Panes_ExplorePane.md#explorepane)\n\n## Functions\n\n### ExplorePane\n\n▸ **ExplorePane**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_HatPane.md",
    "content": "# Module: Frontend/Panes/HatPane\n\n## Table of contents\n\n### Functions\n\n- [HatPane](Frontend_Panes_HatPane.md#hatpane)\n\n## Functions\n\n### HatPane\n\n▸ **HatPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                 | Type                                                                   |\n| :----------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                  | `Object`                                                               |\n| `__namedParameters.initialPlanetId?` | `LocationId`                                                           |\n| `__namedParameters.modal`            | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_HelpPane.md",
    "content": "# Module: Frontend/Panes/HelpPane\n\n## Table of contents\n\n### Functions\n\n- [HelpPane](Frontend_Panes_HelpPane.md#helppane)\n\n## Functions\n\n### HelpPane\n\n▸ **HelpPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_HoverPane.md",
    "content": "# Module: Frontend/Panes/HoverPane\n\n## Table of contents\n\n### Functions\n\n- [HoverPane](Frontend_Panes_HoverPane.md#hoverpane)\n\n## Functions\n\n### HoverPane\n\n▸ **HoverPane**(`__namedParameters`): `Element`\n\nThis is the pane that is rendered when you hover over a planet.\n\n#### Parameters\n\n| Name                        | Type            |\n| :-------------------------- | :-------------- |\n| `__namedParameters`         | `Object`        |\n| `__namedParameters.element` | `ReactChild`    |\n| `__namedParameters.style?`  | `CSSProperties` |\n| `__namedParameters.visible` | `boolean`       |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_HoverPlanetPane.md",
    "content": "# Module: Frontend/Panes/HoverPlanetPane\n\n## Table of contents\n\n### Functions\n\n- [HoverPlanetPane](Frontend_Panes_HoverPlanetPane.md#hoverplanetpane)\n\n## Functions\n\n### HoverPlanetPane\n\n▸ **HoverPlanetPane**(): `Element`\n\nThis is the pane that is rendered when you hover over a planet.\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_AdminPermissionsPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/AdminPermissionsPane\n\n## Table of contents\n\n### Functions\n\n- [AdminPermissionsPane](Frontend_Panes_Lobbies_AdminPermissionsPane.md#adminpermissionspane)\n\n## Functions\n\n### AdminPermissionsPane\n\n▸ **AdminPermissionsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_ArtifactSettingsPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/ArtifactSettingsPane\n\n## Table of contents\n\n### Functions\n\n- [ArtifactSettingsPane](Frontend_Panes_Lobbies_ArtifactSettingsPane.md#artifactsettingspane)\n\n## Functions\n\n### ArtifactSettingsPane\n\n▸ **ArtifactSettingsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_CaptureZonesPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/CaptureZonesPane\n\n## Table of contents\n\n### Functions\n\n- [CaptureZonesPane](Frontend_Panes_Lobbies_CaptureZonesPane.md#capturezonespane)\n\n## Functions\n\n### CaptureZonesPane\n\n▸ **CaptureZonesPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_ConfigurationPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/ConfigurationPane\n\n## Table of contents\n\n### Functions\n\n- [ConfigurationPane](Frontend_Panes_Lobbies_ConfigurationPane.md#configurationpane)\n\n## Functions\n\n### ConfigurationPane\n\n▸ **ConfigurationPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                               | Type                                                                                                         |\n| :--------------------------------- | :----------------------------------------------------------------------------------------------------------- |\n| `__namedParameters`                | `Object`                                                                                                     |\n| `__namedParameters.lobbyAddress`   | `undefined` \\| `EthAddress`                                                                                  |\n| `__namedParameters.modalIndex`     | `number`                                                                                                     |\n| `__namedParameters.startingConfig` | [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)                                   |\n| `__namedParameters.onCreate`       | (`config`: [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)) => `Promise`<`void`\\> |\n| `__namedParameters.onMapChange`    | (`props`: [`MinimapConfig`](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig)) => `void`                 |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_GameSettingsPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/GameSettingsPane\n\n## Table of contents\n\n### Functions\n\n- [GameSettingsPane](Frontend_Panes_Lobbies_GameSettingsPane.md#gamesettingspane)\n\n## Functions\n\n### GameSettingsPane\n\n▸ **GameSettingsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_LobbiesUtils.md",
    "content": "# Module: Frontend/Panes/Lobbies/LobbiesUtils\n\n## Table of contents\n\n### Interfaces\n\n- [LobbiesPaneProps](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md)\n\n### Variables\n\n- [ButtonRow](Frontend_Panes_Lobbies_LobbiesUtils.md#buttonrow)\n\n### Functions\n\n- [ConfigDownload](Frontend_Panes_Lobbies_LobbiesUtils.md#configdownload)\n- [ConfigUpload](Frontend_Panes_Lobbies_LobbiesUtils.md#configupload)\n- [LinkButton](Frontend_Panes_Lobbies_LobbiesUtils.md#linkbutton)\n- [NavigationTitle](Frontend_Panes_Lobbies_LobbiesUtils.md#navigationtitle)\n- [Warning](Frontend_Panes_Lobbies_LobbiesUtils.md#warning)\n\n## Variables\n\n### ButtonRow\n\n• `Const` **ButtonRow**: `StyledComponent`<`ForwardRefExoticComponent`<`Partial`<`Omit`<`DarkForestRow`, `\"children\"`\\>\\> & `Events`<`unknown`\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\>\\>, `any`, {}, `never`\\>\n\n## Functions\n\n### ConfigDownload\n\n▸ **ConfigDownload**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                                                                     |\n| :-------------------------- | :----------------------------------------------------------------------- |\n| `__namedParameters`         | `Object`                                                                 |\n| `__namedParameters.address` | `undefined` \\| `EthAddress`                                              |\n| `__namedParameters.config`  | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n| `__namedParameters.onError` | (`msg`: `string`) => `void`                                              |\n\n#### Returns\n\n`Element`\n\n---\n\n### ConfigUpload\n\n▸ **ConfigUpload**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |\n| :--------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters`          | `Object`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |\n| `__namedParameters.onError`  | (`msg`: `string`) => `void`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| `__namedParameters.onUpload` | (`initializers`: { `ABANDON_RANGE_CHANGE_PERCENT`: `number` ; `ABANDON_SPEED_CHANGE_PERCENT`: `number` ; `ADMIN_CAN_ADD_PLANETS`: `boolean` ; `ARTIFACT_POINT_VALUES`: `Tuple6`<`number`\\> ; `BIOMEBASE_KEY`: `number` ; `BIOME_THRESHOLD_1`: `number` ; `BIOME_THRESHOLD_2`: `number` ; `CAPTURE_ZONES_ENABLED`: `boolean` ; `CAPTURE_ZONES_PER_5000_WORLD_RADIUS`: `number` ; `CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL`: `number` ; `CAPTURE_ZONE_COUNT`: `number` ; `CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED`: `number` ; `CAPTURE_ZONE_PLANET_LEVEL_SCORE`: `ExactArray10`<`number`\\> ; `CAPTURE_ZONE_RADIUS`: `number` ; `CLAIM_PLANET_COOLDOWN`: `number` ; `DISABLE_ZK_CHECKS`: `boolean` ; `INIT_PERLIN_MAX`: `number` ; `INIT_PERLIN_MIN`: `number` ; `LOCATION_REVEAL_COOLDOWN`: `number` ; `MAX_NATURAL_PLANET_LEVEL`: `number` ; `PERLIN_LENGTH_SCALE`: `number` ; `PERLIN_MIRROR_X`: `boolean` ; `PERLIN_MIRROR_Y`: `boolean` ; `PERLIN_THRESHOLD_1`: `number` ; `PERLIN_THRESHOLD_2`: `number` ; `PERLIN_THRESHOLD_3`: `number` ; `PHOTOID_ACTIVATION_DELAY`: `number` ; `PLANETHASH_KEY`: `number` ; `PLANET_LEVEL_JUNK`: `ExactArray10`<`number`\\> ; `PLANET_LEVEL_THRESHOLDS`: `ExactArray10`<`number`\\> ; `PLANET_RARITY`: `number` ; `PLANET_TRANSFER_ENABLED`: `boolean` ; `PLANET_TYPE_WEIGHTS`: `PlanetTypeWeights` ; `SILVER_SCORE_VALUE`: `number` ; `SPACETYPE_KEY`: `number` ; `SPACE_JUNK_ENABLED`: `boolean` ; `SPACE_JUNK_LIMIT`: `number` ; `SPAWN_RIM_AREA`: `number` ; `START_PAUSED`: `boolean` ; `TIME_FACTOR_HUNDREDTHS`: `number` ; `TOKEN_MINT_END_TIMESTAMP`: `number` ; `WORLD_RADIUS_LOCKED`: `boolean` ; `WORLD_RADIUS_MIN`: `number` }) => `void` |\n\n#### Returns\n\n`Element`\n\n---\n\n### LinkButton\n\n▸ **LinkButton**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                             |\n| :------------------ | :--------------------------------------------------------------- |\n| `__namedParameters` | `PropsWithChildren`<{ `shortcut?`: `string` ; `to`: `string` }\\> |\n\n#### Returns\n\n`Element`\n\n---\n\n### NavigationTitle\n\n▸ **NavigationTitle**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type     |\n| :------------------ | :------- |\n| `__namedParameters` | `Object` |\n\n#### Returns\n\n`Element`\n\n---\n\n### Warning\n\n▸ **Warning**(`__namedParameters`): `null` \\| `Element`\n\n#### Parameters\n\n| Name                | Type     |\n| :------------------ | :------- |\n| `__namedParameters` | `Object` |\n\n#### Returns\n\n`null` \\| `Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_MinimapPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/MinimapPane\n\n## Table of contents\n\n### Functions\n\n- [Minimap](Frontend_Panes_Lobbies_MinimapPane.md#minimap)\n\n## Functions\n\n### Minimap\n\n▸ **Minimap**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                           | Type                                                                                   |\n| :----------------------------- | :------------------------------------------------------------------------------------- |\n| `__namedParameters`            | `Object`                                                                               |\n| `__namedParameters.config`     | `undefined` \\| [`MinimapConfig`](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig) |\n| `__namedParameters.modalIndex` | `number`                                                                               |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_MinimapUtils.md",
    "content": "# Module: Frontend/Panes/Lobbies/MinimapUtils\n\n## Table of contents\n\n### Type aliases\n\n- [DrawMessage](Frontend_Panes_Lobbies_MinimapUtils.md#drawmessage)\n- [MinimapConfig](Frontend_Panes_Lobbies_MinimapUtils.md#minimapconfig)\n\n## Type aliases\n\n### DrawMessage\n\nƬ **DrawMessage**: `Object`\n\n#### Type declaration\n\n| Name     | Type                                                      |\n| :------- | :-------------------------------------------------------- |\n| `data`   | { `type`: `SpaceType` ; `x`: `number` ; `y`: `number` }[] |\n| `radius` | `number`                                                  |\n\n---\n\n### MinimapConfig\n\nƬ **MinimapConfig**: `Object`\n\n#### Type declaration\n\n| Name               | Type      |\n| :----------------- | :-------- |\n| `key`              | `number`  |\n| `mirrorX`          | `boolean` |\n| `mirrorY`          | `boolean` |\n| `perlinThreshold1` | `number`  |\n| `perlinThreshold2` | `number`  |\n| `perlinThreshold3` | `number`  |\n| `scale`            | `number`  |\n| `worldRadius`      | `number`  |\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_PlanetPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/PlanetPane\n\n## Table of contents\n\n### Functions\n\n- [PlanetPane](Frontend_Panes_Lobbies_PlanetPane.md#planetpane)\n\n## Functions\n\n### PlanetPane\n\n▸ **PlanetPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_PlayerSpawnPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/PlayerSpawnPane\n\n## Table of contents\n\n### Functions\n\n- [PlayerSpawnPane](Frontend_Panes_Lobbies_PlayerSpawnPane.md#playerspawnpane)\n\n## Functions\n\n### PlayerSpawnPane\n\n▸ **PlayerSpawnPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_Reducer.md",
    "content": "# Module: Frontend/Panes/Lobbies/Reducer\n\n## Table of contents\n\n### Classes\n\n- [InvalidConfigError](../classes/Frontend_Panes_Lobbies_Reducer.InvalidConfigError.md)\n\n### Type aliases\n\n- [LobbyAction](Frontend_Panes_Lobbies_Reducer.md#lobbyaction)\n- [LobbyConfigAction](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction)\n- [LobbyConfigState](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n- [LobbyInitializers](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)\n\n### Variables\n\n- [SAFE_UPPER_BOUNDS](Frontend_Panes_Lobbies_Reducer.md#safe_upper_bounds)\n\n### Functions\n\n- [lobbyConfigInit](Frontend_Panes_Lobbies_Reducer.md#lobbyconfiginit)\n- [lobbyConfigReducer](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigreducer)\n- [ofAny](Frontend_Panes_Lobbies_Reducer.md#ofany)\n- [ofArtifactPointValues](Frontend_Panes_Lobbies_Reducer.md#ofartifactpointvalues)\n- [ofBoolean](Frontend_Panes_Lobbies_Reducer.md#ofboolean)\n- [ofCaptureZoneChangeBlockInterval](Frontend_Panes_Lobbies_Reducer.md#ofcapturezonechangeblockinterval)\n- [ofCaptureZonePlanetLevelScore](Frontend_Panes_Lobbies_Reducer.md#ofcapturezoneplanetlevelscore)\n- [ofCaptureZoneRadius](Frontend_Panes_Lobbies_Reducer.md#ofcapturezoneradius)\n- [ofCaptureZonesPer5000WorldRadius](Frontend_Panes_Lobbies_Reducer.md#ofcapturezonesper5000worldradius)\n- [ofMaxNaturalPlanetLevel](Frontend_Panes_Lobbies_Reducer.md#ofmaxnaturalplanetlevel)\n- [ofNoop](Frontend_Panes_Lobbies_Reducer.md#ofnoop)\n- [ofPerlinLengthScale](Frontend_Panes_Lobbies_Reducer.md#ofperlinlengthscale)\n- [ofPlanetLevelJunk](Frontend_Panes_Lobbies_Reducer.md#ofplanetleveljunk)\n- [ofPlanetLevelThresholds](Frontend_Panes_Lobbies_Reducer.md#ofplanetlevelthresholds)\n- [ofPlanetRarity](Frontend_Panes_Lobbies_Reducer.md#ofplanetrarity)\n- [ofPositiveFloat](Frontend_Panes_Lobbies_Reducer.md#ofpositivefloat)\n- [ofPositiveInteger](Frontend_Panes_Lobbies_Reducer.md#ofpositiveinteger)\n- [ofPositivePercent](Frontend_Panes_Lobbies_Reducer.md#ofpositivepercent)\n- [ofSpaceJunkLimit](Frontend_Panes_Lobbies_Reducer.md#ofspacejunklimit)\n- [ofSpawnRimArea](Frontend_Panes_Lobbies_Reducer.md#ofspawnrimarea)\n- [ofTimeFactorHundredths](Frontend_Panes_Lobbies_Reducer.md#oftimefactorhundredths)\n- [ofWorldRadiusMin](Frontend_Panes_Lobbies_Reducer.md#ofworldradiusmin)\n- [toInitializers](Frontend_Panes_Lobbies_Reducer.md#toinitializers)\n\n## Type aliases\n\n### LobbyAction\n\nƬ **LobbyAction**: { `type`: `\"RESET\"` ; `value`: [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) } \\| [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction)\n\n---\n\n### LobbyConfigAction\n\nƬ **LobbyConfigAction**: { `type`: `\"START_PAUSED\"` ; `value`: `Initializers`[``\"START_PAUSED\"``] \\| `undefined` } \\| { `type`: `\"ADMIN_CAN_ADD_PLANETS\"` ; `value`: `Initializers`[``\"ADMIN_CAN_ADD_PLANETS\"``] \\| `undefined` } \\| { `type`: `\"TOKEN_MINT_END_TIMESTAMP\"` ; `value`: `Initializers`[``\"TOKEN_MINT_END_TIMESTAMP\"``] \\| `undefined` } \\| { `type`: `\"WORLD_RADIUS_LOCKED\"` ; `value`: `Initializers`[``\"WORLD_RADIUS_LOCKED\"``] \\| `undefined` } \\| { `type`: `\"WORLD_RADIUS_MIN\"` ; `value`: `Initializers`[``\"WORLD_RADIUS_MIN\"``] \\| `undefined` } \\| { `type`: `\"DISABLE_ZK_CHECKS\"` ; `value`: `Initializers`[``\"DISABLE_ZK_CHECKS\"``] \\| `undefined` } \\| { `type`: `\"PLANETHASH_KEY\"` ; `value`: `Initializers`[``\"PLANETHASH_KEY\"``] \\| `undefined` } \\| { `type`: `\"SPACETYPE_KEY\"` ; `value`: `Initializers`[``\"SPACETYPE_KEY\"``] \\| `undefined` } \\| { `type`: `\"BIOMEBASE_KEY\"` ; `value`: `Initializers`[``\"BIOMEBASE_KEY\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_MIRROR_X\"` ; `value`: `Initializers`[``\"PERLIN_MIRROR_X\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_MIRROR_Y\"` ; `value`: `Initializers`[``\"PERLIN_MIRROR_Y\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_LENGTH_SCALE\"` ; `value`: `Initializers`[``\"PERLIN_LENGTH_SCALE\"``] \\| `undefined` } \\| { `type`: `\"MAX_NATURAL_PLANET_LEVEL\"` ; `value`: `Initializers`[``\"MAX_NATURAL_PLANET_LEVEL\"``] \\| `undefined` } \\| { `type`: `\"TIME_FACTOR_HUNDREDTHS\"` ; `value`: `Initializers`[``\"TIME_FACTOR_HUNDREDTHS\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_THRESHOLD_1\"` ; `value`: `Initializers`[``\"PERLIN_THRESHOLD_1\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_THRESHOLD_2\"` ; `value`: `Initializers`[``\"PERLIN_THRESHOLD_2\"``] \\| `undefined` } \\| { `type`: `\"PERLIN_THRESHOLD_3\"` ; `value`: `Initializers`[``\"PERLIN_THRESHOLD_3\"``] \\| `undefined` } \\| { `type`: `\"INIT_PERLIN_MIN\"` ; `value`: `Initializers`[``\"INIT_PERLIN_MIN\"``] \\| `undefined` } \\| { `type`: `\"INIT_PERLIN_MAX\"` ; `value`: `Initializers`[``\"INIT_PERLIN_MAX\"``] \\| `undefined` } \\| { `type`: `\"BIOME_THRESHOLD_1\"` ; `value`: `Initializers`[``\"BIOME_THRESHOLD_1\"``] \\| `undefined` } \\| { `type`: `\"BIOME_THRESHOLD_2\"` ; `value`: `Initializers`[``\"BIOME_THRESHOLD_2\"``] \\| `undefined` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_THRESHOLDS\"` ; `value`: `number` \\| `undefined` } \\| { `type`: `\"PLANET_RARITY\"` ; `value`: `Initializers`[``\"PLANET_RARITY\"``] \\| `undefined` } \\| { `type`: `\"PLANET_TRANSFER_ENABLED\"` ; `value`: `Initializers`[``\"PLANET_TRANSFER_ENABLED\"``] \\| `undefined` } \\| { `type`: `\"PHOTOID_ACTIVATION_DELAY\"` ; `value`: `Initializers`[``\"PHOTOID_ACTIVATION_DELAY\"``] \\| `undefined` } \\| { `type`: `\"SPAWN_RIM_AREA\"` ; `value`: `Initializers`[``\"SPAWN_RIM_AREA\"``] \\| `undefined` } \\| { `type`: `\"LOCATION_REVEAL_COOLDOWN\"` ; `value`: `Initializers`[``\"LOCATION_REVEAL_COOLDOWN\"``] \\| `undefined` } \\| { `type`: `\"PLANET_TYPE_WEIGHTS\"` ; `value`: `Initializers`[``\"PLANET_TYPE_WEIGHTS\"``] \\| `undefined` } \\| { `type`: `\"SILVER_SCORE_VALUE\"` ; `value`: `Initializers`[``\"SILVER_SCORE_VALUE\"``] \\| `undefined` } \\| { `index`: `number` ; `type`: `\"ARTIFACT_POINT_VALUES\"` ; `value`: `number` \\| `undefined` } \\| { `type`: `\"SPACE_JUNK_ENABLED\"` ; `value`: `Initializers`[``\"SPACE_JUNK_ENABLED\"``] \\| `undefined` } \\| { `type`: `\"SPACE_JUNK_LIMIT\"` ; `value`: `Initializers`[``\"SPACE_JUNK_LIMIT\"``] \\| `undefined` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_JUNK\"` ; `value`: `number` \\| `undefined` } \\| { `type`: `\"ABANDON_SPEED_CHANGE_PERCENT\"` ; `value`: `Initializers`[``\"ABANDON_SPEED_CHANGE_PERCENT\"``] \\| `undefined` } \\| { `type`: `\"ABANDON_RANGE_CHANGE_PERCENT\"` ; `value`: `Initializers`[``\"ABANDON_RANGE_CHANGE_PERCENT\"``] \\| `undefined` } \\| { `type`: `\"CAPTURE_ZONES_ENABLED\"` ; `value`: `Initializers`[``\"CAPTURE_ZONES_ENABLED\"``] \\| `undefined` } \\| { `type`: `\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"` ; `value`: `Initializers`[``\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"``] \\| `undefined` } \\| { `type`: `\"CAPTURE_ZONE_RADIUS\"` ; `value`: `Initializers`[``\"CAPTURE_ZONE_RADIUS\"``] \\| `undefined` } \\| { `index`: `number` ; `type`: `\"CAPTURE_ZONE_PLANET_LEVEL_SCORE\"` ; `value`: `number` \\| `undefined` } \\| { `type`: `\"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\"` ; `value`: `Initializers`[``\"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\"``] \\| `undefined` } \\| { `type`: `\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"` ; `value`: `Initializers`[``\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"``] \\| `undefined` } \\| { `type`: `\"WHITELIST_ENABLED\"` ; `value`: `boolean` \\| `undefined` }\n\n---\n\n### LobbyConfigState\n\nƬ **LobbyConfigState**: { [key in keyof LobbyInitializers]: Object }\n\n---\n\n### LobbyInitializers\n\nƬ **LobbyInitializers**: `Initializers` & { `WHITELIST_ENABLED`: `boolean` \\| `undefined` }\n\n## Variables\n\n### SAFE_UPPER_BOUNDS\n\n• `Const` **SAFE_UPPER_BOUNDS**: `number`\n\n## Functions\n\n### lobbyConfigInit\n\n▸ **lobbyConfigInit**(`startingConfig`): [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n\n#### Parameters\n\n| Name             | Type                                                                       |\n| :--------------- | :------------------------------------------------------------------------- |\n| `startingConfig` | [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers) |\n\n#### Returns\n\n[`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n\n---\n\n### lobbyConfigReducer\n\n▸ **lobbyConfigReducer**(`state`, `action`): [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n\n#### Parameters\n\n| Name     | Type                                                                     |\n| :------- | :----------------------------------------------------------------------- |\n| `state`  | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n| `action` | [`LobbyAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyaction)           |\n\n#### Returns\n\n[`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)\n\n---\n\n### ofAny\n\n▸ **ofAny**(`__namedParameters`, `state`): { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                | Type                                                                       |\n| :------------------ | :------------------------------------------------------------------------- |\n| `__namedParameters` | [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)   |\n\n#### Returns\n\n{ `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `undefined` \\| `number` \\| `boolean` \\| `PlanetTypeWeights` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofArtifactPointValues\n\n▸ **ofArtifactPointValues**(`__namedParameters`, `state`): { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.index` | `number`                                                                 |\n| `__namedParameters.type`  | `\"ARTIFACT_POINT_VALUES\"`                                                |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n---\n\n### ofBoolean\n\n▸ **ofBoolean**(`__namedParameters`, `state`): { `currentValue`: `undefined` \\| `boolean` ; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `boolean` = value; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `boolean` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |\n| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | { `type`: `\"START_PAUSED\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"ADMIN_CAN_ADD_PLANETS\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"WORLD_RADIUS_LOCKED\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"DISABLE_ZK_CHECKS\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"PERLIN_MIRROR_X\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"PERLIN_MIRROR_Y\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"PLANET_TRANSFER_ENABLED\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"SPACE_JUNK_ENABLED\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"CAPTURE_ZONES_ENABLED\"` ; `value`: `undefined` \\| `boolean` } \\| { `type`: `\"WHITELIST_ENABLED\"` ; `value`: `undefined` \\| `boolean` } |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n\n#### Returns\n\n{ `currentValue`: `undefined` \\| `boolean` ; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `boolean` = value; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `boolean` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofCaptureZoneChangeBlockInterval\n\n▸ **ofCaptureZoneChangeBlockInterval**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"`                                   |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofCaptureZonePlanetLevelScore\n\n▸ **ofCaptureZonePlanetLevelScore**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.index` | `number`                                                                 |\n| `__namedParameters.type`  | `\"CAPTURE_ZONE_PLANET_LEVEL_SCORE\"`                                      |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n---\n\n### ofCaptureZoneRadius\n\n▸ **ofCaptureZoneRadius**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"CAPTURE_ZONE_RADIUS\"`                                                  |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofCaptureZonesPer5000WorldRadius\n\n▸ **ofCaptureZonesPer5000WorldRadius**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"`                                  |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofMaxNaturalPlanetLevel\n\n▸ **ofMaxNaturalPlanetLevel**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"MAX_NATURAL_PLANET_LEVEL\"`                                             |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofNoop\n\n▸ **ofNoop**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `PlanetTypeWeights` ; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \\| [ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `undefined` \\| `boolean` ; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `Partial`<`undefined` \\| `boolean`\\> ; `warning`: `undefined` \\| `string` }\n\n#### Parameters\n\n| Name                | Type                                                                       |\n| :------------------ | :------------------------------------------------------------------------- |\n| `__namedParameters` | [`LobbyConfigAction`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigaction) |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)   |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `PlanetTypeWeights` ; `defaultValue`: `PlanetTypeWeights` ; `displayValue`: `undefined` \\| [ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?, ExactArray10<ExactArray5<number\\>\\>?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `boolean` ; `defaultValue`: `boolean` ; `displayValue`: `undefined` \\| `Partial`<`boolean`\\> ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` \\| `number` ; `warning`: `undefined` \\| `string` } \\| { `currentValue`: `undefined` \\| `boolean` ; `defaultValue`: `undefined` \\| `boolean` ; `displayValue`: `Partial`<`undefined` \\| `boolean`\\> ; `warning`: `undefined` \\| `string` }\n\n---\n\n### ofPerlinLengthScale\n\n▸ **ofPerlinLengthScale**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `never` = value; `warning`: `string` = 'Value must be a number' } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"PERLIN_LENGTH_SCALE\"`                                                  |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `never` = value; `warning`: `string` = 'Value must be a number' } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofPlanetLevelJunk\n\n▸ **ofPlanetLevelJunk**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.index` | `number`                                                                 |\n| `__namedParameters.type`  | `\"PLANET_LEVEL_JUNK\"`                                                    |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined }\n\n---\n\n### ofPlanetLevelThresholds\n\n▸ **ofPlanetLevelThresholds**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.index` | `number`                                                                 |\n| `__namedParameters.type`  | `\"PLANET_LEVEL_THRESHOLDS\"`                                              |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `string` } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` \\| [number?, number?, number?, number?, number?, number?, number?, number?, number?, number?] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `number`[] ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` } \\| { `currentValue`: `number`[] ; `displayValue`: (`undefined` \\| `number`)[] ; `warning`: `string` }\n\n---\n\n### ofPlanetRarity\n\n▸ **ofPlanetRarity**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"PLANET_RARITY\"`                                                        |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofPositiveFloat\n\n▸ **ofPositiveFloat**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | { `type`: `\"TOKEN_MINT_END_TIMESTAMP\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"WORLD_RADIUS_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANETHASH_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACETYPE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOMEBASE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_LENGTH_SCALE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"MAX_NATURAL_PLANET_LEVEL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"TIME_FACTOR_HUNDREDTHS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_3\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MAX\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_THRESHOLDS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANET_RARITY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PHOTOID_ACTIVATION_DELAY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPAWN_RIM_AREA\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"LOCATION_REVEAL_COOLDOWN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SILVER_SCORE_VALUE\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"ARTIFACT_POINT_VALUES\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACE_JUNK_LIMIT\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_JUNK\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_SPEED_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_RANGE_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_RADIUS\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"CAPTURE_ZONE_PLANET_LEVEL_SCORE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"` ; `value`: `undefined` \\| `number` } |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofPositiveInteger\n\n▸ **ofPositiveInteger**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | { `type`: `\"TOKEN_MINT_END_TIMESTAMP\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"WORLD_RADIUS_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANETHASH_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACETYPE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOMEBASE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_LENGTH_SCALE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"MAX_NATURAL_PLANET_LEVEL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"TIME_FACTOR_HUNDREDTHS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_3\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MAX\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_THRESHOLDS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANET_RARITY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PHOTOID_ACTIVATION_DELAY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPAWN_RIM_AREA\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"LOCATION_REVEAL_COOLDOWN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SILVER_SCORE_VALUE\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"ARTIFACT_POINT_VALUES\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACE_JUNK_LIMIT\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_JUNK\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_SPEED_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_RANGE_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_RADIUS\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"CAPTURE_ZONE_PLANET_LEVEL_SCORE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"` ; `value`: `undefined` \\| `number` } |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofPositivePercent\n\n▸ **ofPositivePercent**(`__namedParameters`, `state`): { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |\n| :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | { `type`: `\"TOKEN_MINT_END_TIMESTAMP\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"WORLD_RADIUS_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANETHASH_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACETYPE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOMEBASE_KEY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_LENGTH_SCALE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"MAX_NATURAL_PLANET_LEVEL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"TIME_FACTOR_HUNDREDTHS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PERLIN_THRESHOLD_3\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MIN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"INIT_PERLIN_MAX\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_1\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"BIOME_THRESHOLD_2\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_THRESHOLDS\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PLANET_RARITY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"PHOTOID_ACTIVATION_DELAY\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPAWN_RIM_AREA\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"LOCATION_REVEAL_COOLDOWN\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SILVER_SCORE_VALUE\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"ARTIFACT_POINT_VALUES\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"SPACE_JUNK_LIMIT\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"PLANET_LEVEL_JUNK\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_SPEED_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"ABANDON_RANGE_CHANGE_PERCENT\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_RADIUS\"` ; `value`: `undefined` \\| `number` } \\| { `index`: `number` ; `type`: `\"CAPTURE_ZONE_PLANET_LEVEL_SCORE\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\"` ; `value`: `undefined` \\| `number` } \\| { `type`: `\"CAPTURE_ZONES_PER_5000_WORLD_RADIUS\"` ; `value`: `undefined` \\| `number` } |\n| `state`             | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate)                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n\n#### Returns\n\n{ `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `ExactArray10`<`number`\\> ; `defaultValue`: `ExactArray10`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `Tuple6`<`number`\\> ; `defaultValue`: `Tuple6`<`number`\\> ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofSpaceJunkLimit\n\n▸ **ofSpaceJunkLimit**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"SPACE_JUNK_LIMIT\"`                                                     |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofSpawnRimArea\n\n▸ **ofSpawnRimArea**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` = 'Spawnable area must be larger' }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"SPAWN_RIM_AREA\"`                                                       |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` = 'Spawnable area must be larger' }\n\n---\n\n### ofTimeFactorHundredths\n\n▸ **ofTimeFactorHundredths**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"TIME_FACTOR_HUNDREDTHS\"`                                               |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### ofWorldRadiusMin\n\n▸ **ofWorldRadiusMin**(`__namedParameters`, `state`): { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n#### Parameters\n\n| Name                      | Type                                                                     |\n| :------------------------ | :----------------------------------------------------------------------- |\n| `__namedParameters`       | `Object`                                                                 |\n| `__namedParameters.type`  | `\"WORLD_RADIUS_MIN\"`                                                     |\n| `__namedParameters.value` | `undefined` \\| `number`                                                  |\n| `state`                   | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n{ `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `undefined` = value; `warning`: `undefined` = undefined } \\| { `currentValue`: `number` ; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `string` } \\| { `currentValue`: `number` = value; `defaultValue`: `number` ; `displayValue`: `number` = value; `warning`: `undefined` = undefined }\n\n---\n\n### toInitializers\n\n▸ **toInitializers**(`obj`): [`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)\n\n#### Parameters\n\n| Name  | Type                                                                     |\n| :---- | :----------------------------------------------------------------------- |\n| `obj` | [`LobbyConfigState`](Frontend_Panes_Lobbies_Reducer.md#lobbyconfigstate) |\n\n#### Returns\n\n[`LobbyInitializers`](Frontend_Panes_Lobbies_Reducer.md#lobbyinitializers)\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_SnarkPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/SnarkPane\n\n## Table of contents\n\n### Functions\n\n- [SnarkPane](Frontend_Panes_Lobbies_SnarkPane.md#snarkpane)\n\n## Functions\n\n### SnarkPane\n\n▸ **SnarkPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_SpaceJunkPane.md",
    "content": "# Module: Frontend/Panes/Lobbies/SpaceJunkPane\n\n## Table of contents\n\n### Functions\n\n- [SpaceJunkPane](Frontend_Panes_Lobbies_SpaceJunkPane.md#spacejunkpane)\n\n## Functions\n\n### SpaceJunkPane\n\n▸ **SpaceJunkPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_SpaceTypeBiomePane.md",
    "content": "# Module: Frontend/Panes/Lobbies/SpaceTypeBiomePane\n\n## Table of contents\n\n### Functions\n\n- [SpaceTypeBiomePane](Frontend_Panes_Lobbies_SpaceTypeBiomePane.md#spacetypebiomepane)\n\n## Functions\n\n### SpaceTypeBiomePane\n\n▸ **SpaceTypeBiomePane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Lobbies_WorldSizePane.md",
    "content": "# Module: Frontend/Panes/Lobbies/WorldSizePane\n\n## Table of contents\n\n### Functions\n\n- [WorldSizePane](Frontend_Panes_Lobbies_WorldSizePane.md#worldsizepane)\n\n## Functions\n\n### WorldSizePane\n\n▸ **WorldSizePane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                | Type                                                                                        |\n| :------------------ | :------------------------------------------------------------------------------------------ |\n| `__namedParameters` | [`LobbiesPaneProps`](../interfaces/Frontend_Panes_Lobbies_LobbiesUtils.LobbiesPaneProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md",
    "content": "# Module: Frontend/Panes/ManagePlanetArtifacts/ArtifactActions\n\n## Table of contents\n\n### Functions\n\n- [ArtifactActions](Frontend_Panes_ManagePlanetArtifacts_ArtifactActions.md#artifactactions)\n\n## Functions\n\n### ArtifactActions\n\n▸ **ArtifactActions**(`__namedParameters`): `null` \\| `Element`\n\n#### Parameters\n\n| Name                           | Type         |\n| :----------------------------- | :----------- |\n| `__namedParameters`            | `Object`     |\n| `__namedParameters.artifactId` | `ArtifactId` |\n| `__namedParameters.depositOn?` | `LocationId` |\n\n#### Returns\n\n`null` \\| `Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md",
    "content": "# Module: Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts\n\n## Table of contents\n\n### Functions\n\n- [ManageArtifactsPane](Frontend_Panes_ManagePlanetArtifacts_ManageArtifacts.md#manageartifactspane)\n\n## Functions\n\n### ManageArtifactsPane\n\n▸ **ManageArtifactsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                  | Type                                                                   |\n| :------------------------------------ | :--------------------------------------------------------------------- |\n| `__namedParameters`                   | `Object`                                                               |\n| `__namedParameters.artifactsInWallet` | `Artifact`[]                                                           |\n| `__namedParameters.artifactsOnPlanet` | (`undefined` \\| `Artifact`)[]                                          |\n| `__namedParameters.modal`             | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `__namedParameters.planet`            | `LocatablePlanet`                                                      |\n| `__namedParameters.playerAddress`     | `string`                                                               |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md",
    "content": "# Module: Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane\n\n## Table of contents\n\n### Functions\n\n- [ManagePlanetArtifactsHelpContent](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#manageplanetartifactshelpcontent)\n- [ManagePlanetArtifactsPane](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#manageplanetartifactspane)\n- [PlanetInfoHelpContent](Frontend_Panes_ManagePlanetArtifacts_ManagePlanetArtifactsPane.md#planetinfohelpcontent)\n\n## Functions\n\n### ManagePlanetArtifactsHelpContent\n\n▸ **ManagePlanetArtifactsHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ManagePlanetArtifactsPane\n\n▸ **ManagePlanetArtifactsPane**(`__namedParameters`): `Element`\n\nThis is the place where a user can manage all of their artifacts on a\nparticular planet. This includes prospecting, withdrawing, depositing,\nactivating, and deactivating artifacts.\n\n#### Parameters\n\n| Name                                | Type                                                                   |\n| :---------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                               |\n| `__namedParameters.initialPlanetId` | `undefined` \\| `LocationId`                                            |\n| `__namedParameters.modal`           | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetInfoHelpContent\n\n▸ **PlanetInfoHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ManagePlanetArtifacts_SortBy.md",
    "content": "# Module: Frontend/Panes/ManagePlanetArtifacts/SortBy\n\n## Table of contents\n\n### Functions\n\n- [SortBy](Frontend_Panes_ManagePlanetArtifacts_SortBy.md#sortby)\n\n## Functions\n\n### SortBy\n\n▸ **SortBy**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                          | Type                                            |\n| :---------------------------- | :---------------------------------------------- |\n| `__namedParameters`           | `Object`                                        |\n| `__namedParameters.sortBy`    | `undefined` \\| keyof `Upgrade`                  |\n| `__namedParameters.setSortBy` | (`k`: `undefined` \\| keyof `Upgrade`) => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md",
    "content": "# Module: Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView\n\n## Table of contents\n\n### Functions\n\n- [UpgradeStatsView](Frontend_Panes_ManagePlanetArtifacts_UpgradeStatsView.md#upgradestatsview)\n\n## Functions\n\n### UpgradeStatsView\n\n▸ **UpgradeStatsView**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                             | Type           |\n| :------------------------------- | :------------- |\n| `__namedParameters`              | `Object`       |\n| `__namedParameters.artifactType` | `ArtifactType` |\n| `__namedParameters.isActive`     | `boolean`      |\n| `__namedParameters.upgrade`      | `Upgrade`      |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_OnboardingPane.md",
    "content": "# Module: Frontend/Panes/OnboardingPane\n\n## Table of contents\n\n### Functions\n\n- [default](Frontend_Panes_OnboardingPane.md#default)\n\n## Functions\n\n### default\n\n▸ **default**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PlanetContextPane.md",
    "content": "# Module: Frontend/Panes/PlanetContextPane\n\n## Table of contents\n\n### Functions\n\n- [PlanetContextPane](Frontend_Panes_PlanetContextPane.md#planetcontextpane)\n- [SelectedPlanetHelpContent](Frontend_Panes_PlanetContextPane.md#selectedplanethelpcontent)\n\n## Functions\n\n### PlanetContextPane\n\n▸ **PlanetContextPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SelectedPlanetHelpContent\n\n▸ **SelectedPlanetHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PlanetDexPane.md",
    "content": "# Module: Frontend/Panes/PlanetDexPane\n\n## Table of contents\n\n### Functions\n\n- [PlanetDexPane](Frontend_Panes_PlanetDexPane.md#planetdexpane)\n- [PlanetThumb](Frontend_Panes_PlanetDexPane.md#planetthumb)\n\n## Functions\n\n### PlanetDexPane\n\n▸ **PlanetDexPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetThumb\n\n▸ **PlanetThumb**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type     |\n| :------------------------- | :------- |\n| `__namedParameters`        | `Object` |\n| `__namedParameters.planet` | `Planet` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PlanetInfoPane.md",
    "content": "# Module: Frontend/Panes/PlanetInfoPane\n\n## Table of contents\n\n### Functions\n\n- [PlanetInfoPane](Frontend_Panes_PlanetInfoPane.md#planetinfopane)\n\n## Functions\n\n### PlanetInfoPane\n\n▸ **PlanetInfoPane**(`__namedParameters`): `Element`\n\nThis pane contains misc info about the planet, which does not have a place in the main Planet Context Pane.\n\n#### Parameters\n\n| Name                                | Type                        |\n| :---------------------------------- | :-------------------------- |\n| `__namedParameters`                 | `Object`                    |\n| `__namedParameters.initialPlanetId` | `undefined` \\| `LocationId` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PlayerArtifactsPane.md",
    "content": "# Module: Frontend/Panes/PlayerArtifactsPane\n\n## Table of contents\n\n### Functions\n\n- [PlayerArtifactsPane](Frontend_Panes_PlayerArtifactsPane.md#playerartifactspane)\n\n## Functions\n\n### PlayerArtifactsPane\n\n▸ **PlayerArtifactsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PluginEditorPane.md",
    "content": "# Module: Frontend/Panes/PluginEditorPane\n\n## Table of contents\n\n### Functions\n\n- [PluginEditorPane](Frontend_Panes_PluginEditorPane.md#plugineditorpane)\n\n## Functions\n\n### PluginEditorPane\n\n▸ **PluginEditorPane**(`__namedParameters`): `Element`\n\nComponent for editing plugins. Saving causes its containing modal\nto be closed, and the `overwrite` to be called, indicating that the\ngiven plugin's source should be overwritten and reloaded. If no\nplugin id is provided, assumes we're editing a new plugin.\n\n#### Parameters\n\n| Name                            | Type                                                                                      |\n| :------------------------------ | :---------------------------------------------------------------------------------------- |\n| `__namedParameters`             | `Object`                                                                                  |\n| `__namedParameters.pluginHost?` | `null` \\| [`PluginManager`](../classes/Backend_GameLogic_PluginManager.PluginManager.md)  |\n| `__namedParameters.pluginId?`   | `PluginId`                                                                                |\n| `__namedParameters.overwrite`   | (`newPluginName`: `string`, `newPluginCode`: `string`, `pluginId?`: `PluginId`) => `void` |\n| `__namedParameters.setIsOpen`   | (`open`: `boolean`) => `void`                                                             |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PluginLibraryPane.md",
    "content": "# Module: Frontend/Panes/PluginLibraryPane\n\n## Table of contents\n\n### Functions\n\n- [PluginLibraryPane](Frontend_Panes_PluginLibraryPane.md#pluginlibrarypane)\n\n## Functions\n\n### PluginLibraryPane\n\n▸ **PluginLibraryPane**(`__namedParameters`): `Element`\n\nThis modal presents an overview of all of the player's plugins. Has a button to add a new plugin,\nand lists out all the existing plugins, allowing the user to view their titles, as well as either\nedit, delete, or open their modal.\n\nYou can think of this as the plugin process list, the Activity Monitor of Dark forest.\n\n#### Parameters\n\n| Name                                | Type                                                               |\n| :---------------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                           |\n| `__namedParameters.gameUIManager`   | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `__namedParameters.modalsContainer` | `Element`                                                          |\n| `__namedParameters.visible`         | `boolean`                                                          |\n| `__namedParameters.onClose`         | () => `void`                                                       |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_PrivatePane.md",
    "content": "# Module: Frontend/Panes/PrivatePane\n\n## Table of contents\n\n### Functions\n\n- [PrivatePane](Frontend_Panes_PrivatePane.md#privatepane)\n\n## Functions\n\n### PrivatePane\n\n▸ **PrivatePane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_SettingsPane.md",
    "content": "# Module: Frontend/Panes/SettingsPane\n\n## Table of contents\n\n### Functions\n\n- [SettingsPane](Frontend_Panes_SettingsPane.md#settingspane)\n\n## Functions\n\n### SettingsPane\n\n▸ **SettingsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                              | Type            |\n| :-------------------------------- | :-------------- |\n| `__namedParameters`               | `Object`        |\n| `__namedParameters.ethConnection` | `EthConnection` |\n| `__namedParameters.visible`       | `boolean`       |\n| `__namedParameters.onClose`       | () => `void`    |\n| `__namedParameters.onOpenPrivate` | () => `void`    |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_Tooltip.md",
    "content": "# Module: Frontend/Panes/Tooltip\n\n## Table of contents\n\n### Interfaces\n\n- [TooltipProps](../interfaces/Frontend_Panes_Tooltip.TooltipProps.md)\n- [TooltipTriggerProps](../interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md)\n\n### Functions\n\n- [Tooltip](Frontend_Panes_Tooltip.md#tooltip)\n- [TooltipTrigger](Frontend_Panes_Tooltip.md#tooltiptrigger)\n\n## Functions\n\n### Tooltip\n\n▸ **Tooltip**(`props`): `null` \\| `Element`\n\nAt any given moment, there can only be one tooltip visible in the game. This is true because a\nplayer only has one mouse cursor on the screen, and therefore can only be hovering over a single\n[TooltipTrigger](Frontend_Panes_Tooltip.md#tooltiptrigger) element at any given time. This component is responsible for keeping track\nof which tooltip has been hovered over, and rendering the corresponding content.\n\n#### Parameters\n\n| Name    | Type                                                                   |\n| :------ | :--------------------------------------------------------------------- |\n| `props` | [`TooltipProps`](../interfaces/Frontend_Panes_Tooltip.TooltipProps.md) |\n\n#### Returns\n\n`null` \\| `Element`\n\n---\n\n### TooltipTrigger\n\n▸ **TooltipTrigger**(`props`): `Element`\n\nWhen the player hovers over this element, triggers the tooltip with the given name to be\ndisplayed on top of everything.\n\n#### Parameters\n\n| Name    | Type                                                                                 |\n| :------ | :----------------------------------------------------------------------------------- |\n| `props` | [`TooltipTriggerProps`](../interfaces/Frontend_Panes_Tooltip.TooltipTriggerProps.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_TooltipPanes.md",
    "content": "# Module: Frontend/Panes/TooltipPanes\n\n## Table of contents\n\n### Functions\n\n- [ActivateArtifactPane](Frontend_Panes_TooltipPanes.md#activateartifactpane)\n- [BonusDefenseTooltipPane](Frontend_Panes_TooltipPanes.md#bonusdefensetooltippane)\n- [BonusEnergyCapTooltipPane](Frontend_Panes_TooltipPanes.md#bonusenergycaptooltippane)\n- [BonusEnergyGroTooltipPane](Frontend_Panes_TooltipPanes.md#bonusenergygrotooltippane)\n- [BonusRangeTooltipPane](Frontend_Panes_TooltipPanes.md#bonusrangetooltippane)\n- [BonusSpaceJunkTooltipPane](Frontend_Panes_TooltipPanes.md#bonusspacejunktooltippane)\n- [BonusSpeedTooltipPane](Frontend_Panes_TooltipPanes.md#bonusspeedtooltippane)\n- [BonusTooltipPane](Frontend_Panes_TooltipPanes.md#bonustooltippane)\n- [ClowntownTooltipPane](Frontend_Panes_TooltipPanes.md#clowntowntooltippane)\n- [CurrentMiningTooltipPane](Frontend_Panes_TooltipPanes.md#currentminingtooltippane)\n- [DeactivateArtifactPane](Frontend_Panes_TooltipPanes.md#deactivateartifactpane)\n- [DefenseMultiplierPane](Frontend_Panes_TooltipPanes.md#defensemultiplierpane)\n- [DepositArtifactPane](Frontend_Panes_TooltipPanes.md#depositartifactpane)\n- [EnergyCapMultiplierPane](Frontend_Panes_TooltipPanes.md#energycapmultiplierpane)\n- [EnergyGrowthMultiplierPane](Frontend_Panes_TooltipPanes.md#energygrowthmultiplierpane)\n- [EnergyGrowthTooltipPane](Frontend_Panes_TooltipPanes.md#energygrowthtooltippane)\n- [EnergyTooltipPane](Frontend_Panes_TooltipPanes.md#energytooltippane)\n- [MaxLevelTooltipPane](Frontend_Panes_TooltipPanes.md#maxleveltooltippane)\n- [MinEnergyTooltipPane](Frontend_Panes_TooltipPanes.md#minenergytooltippane)\n- [MiningPauseTooltipPane](Frontend_Panes_TooltipPanes.md#miningpausetooltippane)\n- [MiningTargetTooltipPane](Frontend_Panes_TooltipPanes.md#miningtargettooltippane)\n- [ModalBroadcastTooltipPane](Frontend_Panes_TooltipPanes.md#modalbroadcasttooltippane)\n- [ModalHelpTooltipPane](Frontend_Panes_TooltipPanes.md#modalhelptooltippane)\n- [ModalLeaderboardTooltipPane](Frontend_Panes_TooltipPanes.md#modalleaderboardtooltippane)\n- [ModalPlanetDetailsTooltipPane](Frontend_Panes_TooltipPanes.md#modalplanetdetailstooltippane)\n- [ModalPlanetDexTooltipPane](Frontend_Panes_TooltipPanes.md#modalplanetdextooltippane)\n- [ModalTwitterVerificationTooltipPane](Frontend_Panes_TooltipPanes.md#modaltwitterverificationtooltippane)\n- [ModalUpgradeDetailsTooltipPane](Frontend_Panes_TooltipPanes.md#modalupgradedetailstooltippane)\n- [NetworkHealthPane](Frontend_Panes_TooltipPanes.md#networkhealthpane)\n- [PiratesTooltipPane](Frontend_Panes_TooltipPanes.md#piratestooltippane)\n- [PlanetRankTooltipPane](Frontend_Panes_TooltipPanes.md#planetranktooltippane)\n- [RangeMultiplierPane](Frontend_Panes_TooltipPanes.md#rangemultiplierpane)\n- [RangeTooltipPane](Frontend_Panes_TooltipPanes.md#rangetooltippane)\n- [RankTooltipPane](Frontend_Panes_TooltipPanes.md#ranktooltippane)\n- [ScoreTooltipPane](Frontend_Panes_TooltipPanes.md#scoretooltippane)\n- [SelectedSilverTooltipPane](Frontend_Panes_TooltipPanes.md#selectedsilvertooltippane)\n- [SilverCapTooltipPane](Frontend_Panes_TooltipPanes.md#silvercaptooltippane)\n- [SilverGrowthTooltipPane](Frontend_Panes_TooltipPanes.md#silvergrowthtooltippane)\n- [SilverProdTooltipPane](Frontend_Panes_TooltipPanes.md#silverprodtooltippane)\n- [SilverTooltipPane](Frontend_Panes_TooltipPanes.md#silvertooltippane)\n- [SpeedMultiplierPane](Frontend_Panes_TooltipPanes.md#speedmultiplierpane)\n- [Time50TooltipPane](Frontend_Panes_TooltipPanes.md#time50tooltippane)\n- [Time90TooltipPane](Frontend_Panes_TooltipPanes.md#time90tooltippane)\n- [TimeUntilActivationPossiblePane](Frontend_Panes_TooltipPanes.md#timeuntilactivationpossiblepane)\n- [TooltipContent](Frontend_Panes_TooltipPanes.md#tooltipcontent)\n- [TwitterHandleTooltipPane](Frontend_Panes_TooltipPanes.md#twitterhandletooltippane)\n- [UpgradesTooltipPane](Frontend_Panes_TooltipPanes.md#upgradestooltippane)\n- [WithdrawArtifactPane](Frontend_Panes_TooltipPanes.md#withdrawartifactpane)\n- [WithdrawSilverButton](Frontend_Panes_TooltipPanes.md#withdrawsilverbutton)\n\n## Functions\n\n### ActivateArtifactPane\n\n▸ **ActivateArtifactPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusDefenseTooltipPane\n\n▸ **BonusDefenseTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusEnergyCapTooltipPane\n\n▸ **BonusEnergyCapTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusEnergyGroTooltipPane\n\n▸ **BonusEnergyGroTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusRangeTooltipPane\n\n▸ **BonusRangeTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusSpaceJunkTooltipPane\n\n▸ **BonusSpaceJunkTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusSpeedTooltipPane\n\n▸ **BonusSpeedTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### BonusTooltipPane\n\n▸ **BonusTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ClowntownTooltipPane\n\n▸ **ClowntownTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### CurrentMiningTooltipPane\n\n▸ **CurrentMiningTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### DeactivateArtifactPane\n\n▸ **DeactivateArtifactPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### DefenseMultiplierPane\n\n▸ **DefenseMultiplierPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### DepositArtifactPane\n\n▸ **DepositArtifactPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyCapMultiplierPane\n\n▸ **EnergyCapMultiplierPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyGrowthMultiplierPane\n\n▸ **EnergyGrowthMultiplierPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyGrowthTooltipPane\n\n▸ **EnergyGrowthTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### EnergyTooltipPane\n\n▸ **EnergyTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### MaxLevelTooltipPane\n\n▸ **MaxLevelTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### MinEnergyTooltipPane\n\n▸ **MinEnergyTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### MiningPauseTooltipPane\n\n▸ **MiningPauseTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### MiningTargetTooltipPane\n\n▸ **MiningTargetTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalBroadcastTooltipPane\n\n▸ **ModalBroadcastTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalHelpTooltipPane\n\n▸ **ModalHelpTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalLeaderboardTooltipPane\n\n▸ **ModalLeaderboardTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalPlanetDetailsTooltipPane\n\n▸ **ModalPlanetDetailsTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalPlanetDexTooltipPane\n\n▸ **ModalPlanetDexTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalTwitterVerificationTooltipPane\n\n▸ **ModalTwitterVerificationTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ModalUpgradeDetailsTooltipPane\n\n▸ **ModalUpgradeDetailsTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### NetworkHealthPane\n\n▸ **NetworkHealthPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### PiratesTooltipPane\n\n▸ **PiratesTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetRankTooltipPane\n\n▸ **PlanetRankTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### RangeMultiplierPane\n\n▸ **RangeMultiplierPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### RangeTooltipPane\n\n▸ **RangeTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### RankTooltipPane\n\n▸ **RankTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### ScoreTooltipPane\n\n▸ **ScoreTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SelectedSilverTooltipPane\n\n▸ **SelectedSilverTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverCapTooltipPane\n\n▸ **SilverCapTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverGrowthTooltipPane\n\n▸ **SilverGrowthTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverProdTooltipPane\n\n▸ **SilverProdTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SilverTooltipPane\n\n▸ **SilverTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### SpeedMultiplierPane\n\n▸ **SpeedMultiplierPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### Time50TooltipPane\n\n▸ **Time50TooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### Time90TooltipPane\n\n▸ **Time90TooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### TimeUntilActivationPossiblePane\n\n▸ **TimeUntilActivationPossiblePane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### TooltipContent\n\n▸ **TooltipContent**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                     | Type                         |\n| :----------------------- | :--------------------------- |\n| `__namedParameters`      | `Object`                     |\n| `__namedParameters.name` | `undefined` \\| `TooltipName` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TwitterHandleTooltipPane\n\n▸ **TwitterHandleTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### UpgradesTooltipPane\n\n▸ **UpgradesTooltipPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### WithdrawArtifactPane\n\n▸ **WithdrawArtifactPane**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### WithdrawSilverButton\n\n▸ **WithdrawSilverButton**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_TransactionLogPane.md",
    "content": "# Module: Frontend/Panes/TransactionLogPane\n\n## Table of contents\n\n### Functions\n\n- [TransactionLogPane](Frontend_Panes_TransactionLogPane.md#transactionlogpane)\n\n## Functions\n\n### TransactionLogPane\n\n▸ **TransactionLogPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_TutorialPane.md",
    "content": "# Module: Frontend/Panes/TutorialPane\n\n## Table of contents\n\n### Functions\n\n- [TutorialPane](Frontend_Panes_TutorialPane.md#tutorialpane)\n\n## Functions\n\n### TutorialPane\n\n▸ **TutorialPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                             | Type                                                   |\n| :------------------------------- | :----------------------------------------------------- |\n| `__namedParameters`              | `Object`                                               |\n| `__namedParameters.tutorialHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_TwitterVerifyPane.md",
    "content": "# Module: Frontend/Panes/TwitterVerifyPane\n\n## Table of contents\n\n### Functions\n\n- [TwitterVerifyPane](Frontend_Panes_TwitterVerifyPane.md#twitterverifypane)\n\n## Functions\n\n### TwitterVerifyPane\n\n▸ **TwitterVerifyPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type         |\n| :-------------------------- | :----------- |\n| `__namedParameters`         | `Object`     |\n| `__namedParameters.visible` | `boolean`    |\n| `__namedParameters.onClose` | () => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_UpgradeDetailsPane.md",
    "content": "# Module: Frontend/Panes/UpgradeDetailsPane\n\n## Table of contents\n\n### Functions\n\n- [UpgradeDetailsPane](Frontend_Panes_UpgradeDetailsPane.md#upgradedetailspane)\n- [UpgradeDetailsPaneHelpContent](Frontend_Panes_UpgradeDetailsPane.md#upgradedetailspanehelpcontent)\n\n## Functions\n\n### UpgradeDetailsPane\n\n▸ **UpgradeDetailsPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                | Type                                                                   |\n| :---------------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`                 | `Object`                                                               |\n| `__namedParameters.initialPlanetId` | `undefined` \\| `LocationId`                                            |\n| `__namedParameters.modal`           | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### UpgradeDetailsPaneHelpContent\n\n▸ **UpgradeDetailsPaneHelpContent**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_WikiPane.md",
    "content": "# Module: Frontend/Panes/WikiPane\n\n## Table of contents\n\n### Functions\n\n- [WikiPane](Frontend_Panes_WikiPane.md#wikipane)\n\n## Functions\n\n### WikiPane\n\n▸ **WikiPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type        |\n| :--------------------------- | :---------- |\n| `__namedParameters`          | `Object`    |\n| `__namedParameters.children` | `ReactNode` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Panes_ZoomPane.md",
    "content": "# Module: Frontend/Panes/ZoomPane\n\n## Table of contents\n\n### Functions\n\n- [ZoomPane](Frontend_Panes_ZoomPane.md#zoompane)\n\n## Functions\n\n### ZoomPane\n\n▸ **ZoomPane**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Renderers_Artifacts_ArtifactRenderer.md",
    "content": "# Module: Frontend/Renderers/Artifacts/ArtifactRenderer\n\n## Table of contents\n\n### Classes\n\n- [ArtifactRenderer](../classes/Frontend_Renderers_Artifacts_ArtifactRenderer.ArtifactRenderer.md)\n\n### Variables\n\n- [aDexCanvasH](Frontend_Renderers_Artifacts_ArtifactRenderer.md#adexcanvash)\n- [aDexCanvasW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#adexcanvasw)\n- [aListCanvasH](Frontend_Renderers_Artifacts_ArtifactRenderer.md#alistcanvash)\n- [aListCanvasW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#alistcanvasw)\n- [artifactColM](Frontend_Renderers_Artifacts_ArtifactRenderer.md#artifactcolm)\n- [artifactColW](Frontend_Renderers_Artifacts_ArtifactRenderer.md#artifactcolw)\n\n## Variables\n\n### aDexCanvasH\n\n• `Const` **aDexCanvasH**: `number`\n\n---\n\n### aDexCanvasW\n\n• `Const` **aDexCanvasW**: `number`\n\n---\n\n### aListCanvasH\n\n• `Const` **aListCanvasH**: `400`\n\n---\n\n### aListCanvasW\n\n• `Const` **aListCanvasW**: `48`\n\n---\n\n### artifactColM\n\n• `Const` **artifactColM**: `32`\n\n---\n\n### artifactColW\n\n• `Const` **artifactColW**: `number`\n"
  },
  {
    "path": "docs/modules/Frontend_Renderers_GifRenderer.md",
    "content": "# Module: Frontend/Renderers/GifRenderer\n\n## Table of contents\n\n### Classes\n\n- [GifRenderer](../classes/Frontend_Renderers_GifRenderer.GifRenderer.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Renderers_LandingPageCanvas.md",
    "content": "# Module: Frontend/Renderers/LandingPageCanvas\n\n## Table of contents\n\n### Functions\n\n- [LandingPageBackground](Frontend_Renderers_LandingPageCanvas.md#landingpagebackground)\n- [default](Frontend_Renderers_LandingPageCanvas.md#default)\n\n## Functions\n\n### LandingPageBackground\n\n▸ **LandingPageBackground**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### default\n\n▸ **default**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md",
    "content": "# Module: Frontend/Renderers/PlanetscapeRenderer/PlanetIcons\n\n## Table of contents\n\n### Functions\n\n- [PlanetIcons](Frontend_Renderers_PlanetscapeRenderer_PlanetIcons.md#planeticons)\n\n## Functions\n\n### PlanetIcons\n\n▸ **PlanetIcons**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Styles_Colors.md",
    "content": "# Module: Frontend/Styles/Colors\n\n## Table of contents\n\n### Variables\n\n- [ANCIENT_BLUE](Frontend_Styles_Colors.md#ancient_blue)\n- [ANCIENT_PURPLE](Frontend_Styles_Colors.md#ancient_purple)\n- [BiomeBackgroundColors](Frontend_Styles_Colors.md#biomebackgroundcolors)\n- [BiomeTextColors](Frontend_Styles_Colors.md#biometextcolors)\n- [RarityColors](Frontend_Styles_Colors.md#raritycolors)\n\n## Variables\n\n### ANCIENT_BLUE\n\n• `Const` **ANCIENT_BLUE**: `\"#b2fffc\"`\n\n---\n\n### ANCIENT_PURPLE\n\n• `Const` **ANCIENT_PURPLE**: `\"#d23191\"`\n\n---\n\n### BiomeBackgroundColors\n\n• `Const` **BiomeBackgroundColors**: `Object`\n\n---\n\n### BiomeTextColors\n\n• `Const` **BiomeTextColors**: `Object`\n\n---\n\n### RarityColors\n\n• `Const` **RarityColors**: `Object`\n"
  },
  {
    "path": "docs/modules/Frontend_Styles_Mixins.md",
    "content": "# Module: Frontend/Styles/Mixins\n\n## Table of contents\n\n### Functions\n\n- [planetBackground](Frontend_Styles_Mixins.md#planetbackground)\n\n## Functions\n\n### planetBackground\n\n▸ **planetBackground**(`__namedParameters`): `\"\"` \\| `FlattenSimpleInterpolation`\n\n#### Parameters\n\n| Name                       | Type                    |\n| :------------------------- | :---------------------- |\n| `__namedParameters`        | `Object`                |\n| `__namedParameters.planet` | `undefined` \\| `Planet` |\n\n#### Returns\n\n`\"\"` \\| `FlattenSimpleInterpolation`\n"
  },
  {
    "path": "docs/modules/Frontend_Styles_dfstyles.md",
    "content": "# Module: Frontend/Styles/dfstyles\n\n## Table of contents\n\n### Variables\n\n- [ARTIFACT_ROW_H](Frontend_Styles_dfstyles.md#artifact_row_h)\n- [SPACE_TYPE_COLORS](Frontend_Styles_dfstyles.md#space_type_colors)\n- [default](Frontend_Styles_dfstyles.md#default)\n- [snips](Frontend_Styles_dfstyles.md#snips)\n\n## Variables\n\n### ARTIFACT_ROW_H\n\n• `Const` **ARTIFACT_ROW_H**: `48`\n\n---\n\n### SPACE_TYPE_COLORS\n\n• `Const` **SPACE_TYPE_COLORS**: `Object`\n\n---\n\n### default\n\n• `Const` **default**: `Object`\n\n#### Type declaration\n\n| Name                           | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| :----------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `borderRadius`                 | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors`                       | { `artifactBackground`: `string` = 'rgb(21, 17, 71)'; `background`: `string` ; `backgrounddark`: `string` ; `backgroundlight`: `string` ; `backgroundlighter`: `string` ; `blueBackground`: `string` ; `border`: `string` ; `borderDark`: `string` ; `borderDarker`: `string` ; `borderDarkest`: `string` ; `dfblue`: `string` ; `dfgreen`: `string` ; `dfgreendark`: `string` ; `dfgreenlight`: `string` ; `dforange`: `string` ; `dfpurple`: `string` ; `dfred`: `string` ; `dfwhite`: `string` ; `dfyellow`: `string` ; `icons`: { `blog`: `string` = '#ffcb1f'; `discord`: `string` = '#7289da'; `email`: `string` = '#D44638'; `github`: `string` = '#8e65db'; `twitter`: `string` = '#1DA1F2' } ; `subbertext`: `string` ; `subbesttext`: `string` ; `subtext`: `string` ; `text`: `string` ; `textLight`: `string` }                 |\n| `colors.artifactBackground`    | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.background`            | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.backgrounddark`        | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.backgroundlight`       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.backgroundlighter`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.blueBackground`        | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.border`                | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.borderDark`            | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.borderDarker`          | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.borderDarkest`         | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfblue`                | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfgreen`               | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfgreendark`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfgreenlight`          | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dforange`              | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfpurple`              | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfred`                 | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfwhite`               | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.dfyellow`              | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.icons`                 | { `blog`: `string` = '#ffcb1f'; `discord`: `string` = '#7289da'; `email`: `string` = '#D44638'; `github`: `string` = '#8e65db'; `twitter`: `string` = '#1DA1F2' }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           |\n| `colors.icons.blog`            | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.icons.discord`         | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.icons.email`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.icons.github`          | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.icons.twitter`         | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.subbertext`            | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.subbesttext`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.subtext`               | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.text`                  | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `colors.textLight`             | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontH1`                       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontH1S`                      | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontH2`                       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontSize`                     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontSizeS`                    | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `fontSizeXS`                   | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game`                         | { `bonuscolors`: { `def`: `string` = 'hsl(231, 73%, 70%)'; `energyCap`: `string` = 'hsl(360, 73%, 70%)'; `energyGro`: `string` = 'hsl(136, 73%, 70%)'; `range`: `string` = 'hsl(50, 73%, 70%)'; `spaceJunk`: `string` = 'hsl(43, 33%, 29%)'; `speed`: `string` = 'hsl(290, 73%, 70%)' } ; `canvasbg`: `string` = '#100544'; `fontSize`: `string` = '12pt'; `rangecolors`: { `color100`: `string` = '#050228'; `color25`: `string` = '#050238'; `color50`: `string` = '#050233'; `colorenergy`: `string` = '#080330'; `dash`: `string` = '#9691bf'; `dashenergy`: `string` = '#f5c082' } ; `styles`: { `active`: `string` = 'filter: brightness(80%)'; `animProps`: `string` = 'ease-in-out infinite alternate-reverse' } ; `terminalFontSize`: `string` = '10pt'; `terminalWidth`: `string` = '240pt'; `toolbarHeight`: `string` = '12em' } |\n| `game.bonuscolors`             | { `def`: `string` = 'hsl(231, 73%, 70%)'; `energyCap`: `string` = 'hsl(360, 73%, 70%)'; `energyGro`: `string` = 'hsl(136, 73%, 70%)'; `range`: `string` = 'hsl(50, 73%, 70%)'; `spaceJunk`: `string` = 'hsl(43, 33%, 29%)'; `speed`: `string` = 'hsl(290, 73%, 70%)' }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |\n| `game.bonuscolors.def`         | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.bonuscolors.energyCap`   | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.bonuscolors.energyGro`   | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.bonuscolors.range`       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.bonuscolors.spaceJunk`   | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.bonuscolors.speed`       | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.canvasbg`                | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.fontSize`                | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors`             | { `color100`: `string` = '#050228'; `color25`: `string` = '#050238'; `color50`: `string` = '#050233'; `colorenergy`: `string` = '#080330'; `dash`: `string` = '#9691bf'; `dashenergy`: `string` = '#f5c082' }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |\n| `game.rangecolors.color100`    | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors.color25`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors.color50`     | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors.colorenergy` | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors.dash`        | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.rangecolors.dashenergy`  | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.styles`                  | { `active`: `string` = 'filter: brightness(80%)'; `animProps`: `string` = 'ease-in-out infinite alternate-reverse' }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |\n| `game.styles.active`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.styles.animProps`        | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.terminalFontSize`        | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.terminalWidth`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `game.toolbarHeight`           | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `prefabs`                      | { `noselect`: `FlattenSimpleInterpolation` }                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| `prefabs.noselect`             | `FlattenSimpleInterpolation`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                |\n| `screenSizeS`                  | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n| `titleFont`                    | `string`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |\n\n---\n\n### snips\n\n• `Const` **snips**: `Object`\n\n#### Type declaration\n\n| Name                     | Type                                    |\n| :----------------------- | :-------------------------------------- |\n| `absoluteTopLeft`        | `FlattenSimpleInterpolation`            |\n| `bigPadding`             | `FlattenSimpleInterpolation`            |\n| `defaultBackground`      | `string`                                |\n| `defaultModalWidth`      | `FlattenSimpleInterpolation`            |\n| `destroyedBackground`    | `CSSStyleDeclaration` & `CSSProperties` |\n| `pane`                   | `string`                                |\n| `roundedBorders`         | `string`                                |\n| `roundedBordersWithEdge` | `FlattenSimpleInterpolation`            |\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_AppHooks.md",
    "content": "# Module: Frontend/Utils/AppHooks\n\n## Table of contents\n\n### Type aliases\n\n- [TransactionRecord](Frontend_Utils_AppHooks.md#transactionrecord)\n\n### Variables\n\n- [TopLevelDivProvider](Frontend_Utils_AppHooks.md#topleveldivprovider)\n- [UIManagerProvider](Frontend_Utils_AppHooks.md#uimanagerprovider)\n\n### Functions\n\n- [useAccount](Frontend_Utils_AppHooks.md#useaccount)\n- [useActiveArtifact](Frontend_Utils_AppHooks.md#useactiveartifact)\n- [useArtifact](Frontend_Utils_AppHooks.md#useartifact)\n- [useHoverArtifact](Frontend_Utils_AppHooks.md#usehoverartifact)\n- [useHoverArtifactId](Frontend_Utils_AppHooks.md#usehoverartifactid)\n- [useHoverPlanet](Frontend_Utils_AppHooks.md#usehoverplanet)\n- [useLeaderboard](Frontend_Utils_AppHooks.md#useleaderboard)\n- [useMyArtifactsList](Frontend_Utils_AppHooks.md#usemyartifactslist)\n- [useOverlayContainer](Frontend_Utils_AppHooks.md#useoverlaycontainer)\n- [usePaused](Frontend_Utils_AppHooks.md#usepaused)\n- [usePlanet](Frontend_Utils_AppHooks.md#useplanet)\n- [usePlanetArtifacts](Frontend_Utils_AppHooks.md#useplanetartifacts)\n- [usePlanetInactiveArtifacts](Frontend_Utils_AppHooks.md#useplanetinactiveartifacts)\n- [usePlayer](Frontend_Utils_AppHooks.md#useplayer)\n- [usePopAllOnSelectedPlanetChanged](Frontend_Utils_AppHooks.md#usepopallonselectedplanetchanged)\n- [useSelectedArtifact](Frontend_Utils_AppHooks.md#useselectedartifact)\n- [useSelectedPlanet](Frontend_Utils_AppHooks.md#useselectedplanet)\n- [useSelectedPlanetId](Frontend_Utils_AppHooks.md#useselectedplanetid)\n- [useTopLevelDiv](Frontend_Utils_AppHooks.md#usetopleveldiv)\n- [useTransactionLog](Frontend_Utils_AppHooks.md#usetransactionlog)\n- [useUIManager](Frontend_Utils_AppHooks.md#useuimanager)\n\n## Type aliases\n\n### TransactionRecord\n\nƬ **TransactionRecord**: `Record`<`TransactionId`, `Transaction`\\>\n\n## Variables\n\n### TopLevelDivProvider\n\n• **TopLevelDivProvider**: `Provider`<`HTMLDivElement`\\>\n\n---\n\n### UIManagerProvider\n\n• **UIManagerProvider**: `Provider`<[`default`](../classes/Backend_GameLogic_GameUIManager.default.md)\\>\n\n## Functions\n\n### useAccount\n\n▸ **useAccount**(`uiManager`): `EthAddress` \\| `undefined`\n\nGet the currently used account on the client.\n\n#### Parameters\n\n| Name        | Type                                                               | Description               |\n| :---------- | :----------------------------------------------------------------- | :------------------------ |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager |\n\n#### Returns\n\n`EthAddress` \\| `undefined`\n\n---\n\n### useActiveArtifact\n\n▸ **useActiveArtifact**(`planet`, `uiManager`): `Artifact` \\| `undefined`\n\n#### Parameters\n\n| Name        | Type                                                                               |\n| :---------- | :--------------------------------------------------------------------------------- |\n| `planet`    | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md)                 |\n\n#### Returns\n\n`Artifact` \\| `undefined`\n\n---\n\n### useArtifact\n\n▸ **useArtifact**(`uiManager`, `artifactId`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Artifact`\\>\n\n#### Parameters\n\n| Name         | Type                                                               |\n| :----------- | :----------------------------------------------------------------- |\n| `uiManager`  | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `artifactId` | `ArtifactId`                                                       |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Artifact`\\>\n\n---\n\n### useHoverArtifact\n\n▸ **useHoverArtifact**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \\| `undefined`\\>\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \\| `undefined`\\>\n\n---\n\n### useHoverArtifactId\n\n▸ **useHoverArtifactId**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`ArtifactId` \\| `undefined`\\>\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`ArtifactId` \\| `undefined`\\>\n\n---\n\n### useHoverPlanet\n\n▸ **useHoverPlanet**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\nCreate a subscription to the currently hovering planet.\n\n#### Parameters\n\n| Name        | Type                                                               | Description               |\n| :---------- | :----------------------------------------------------------------- | :------------------------ |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\n---\n\n### useLeaderboard\n\n▸ **useLeaderboard**(`poll?`): `Object`\n\nLoads the leaderboard\n\n#### Parameters\n\n| Name   | Type                    | Default value |\n| :----- | :---------------------- | :------------ |\n| `poll` | `undefined` \\| `number` | `undefined`   |\n\n#### Returns\n\n`Object`\n\n| Name          | Type                         |\n| :------------ | :--------------------------- |\n| `error`       | `Error` \\| `undefined`       |\n| `leaderboard` | `Leaderboard` \\| `undefined` |\n\n---\n\n### useMyArtifactsList\n\n▸ **useMyArtifactsList**(`uiManager`): `Artifact`[]\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### useOverlayContainer\n\n▸ **useOverlayContainer**(): `HTMLDivElement` \\| `null`\n\n#### Returns\n\n`HTMLDivElement` \\| `null`\n\n---\n\n### usePaused\n\n▸ **usePaused**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### usePlanet\n\n▸ **usePlanet**(`uiManager`, `locationId`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\n#### Parameters\n\n| Name         | Type                                                               |\n| :----------- | :----------------------------------------------------------------- |\n| `uiManager`  | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `locationId` | `undefined` \\| `LocationId`                                        |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\n---\n\n### usePlanetArtifacts\n\n▸ **usePlanetArtifacts**(`planet`, `uiManager`): `Artifact`[]\n\n#### Parameters\n\n| Name        | Type                                                                               |\n| :---------- | :--------------------------------------------------------------------------------- |\n| `planet`    | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md)                 |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### usePlanetInactiveArtifacts\n\n▸ **usePlanetInactiveArtifacts**(`planet`, `uiManager`): `Artifact`[]\n\n#### Parameters\n\n| Name        | Type                                                                               |\n| :---------- | :--------------------------------------------------------------------------------- |\n| `planet`    | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md)                 |\n\n#### Returns\n\n`Artifact`[]\n\n---\n\n### usePlayer\n\n▸ **usePlayer**(`uiManager`, `ethAddress?`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Player` \\| `undefined`\\>\n\nHook which gets you the player, and updates whenever that player's twitter or score changes.\n\n#### Parameters\n\n| Name          | Type                                                               |\n| :------------ | :----------------------------------------------------------------- |\n| `uiManager`   | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `ethAddress?` | `EthAddress`                                                       |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Player` \\| `undefined`\\>\n\n---\n\n### usePopAllOnSelectedPlanetChanged\n\n▸ **usePopAllOnSelectedPlanetChanged**(`modal`, `startingId`): `void`\n\n#### Parameters\n\n| Name         | Type                                                                   |\n| :----------- | :--------------------------------------------------------------------- |\n| `modal`      | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n| `startingId` | `undefined` \\| `LocationId`                                            |\n\n#### Returns\n\n`void`\n\n---\n\n### useSelectedArtifact\n\n▸ **useSelectedArtifact**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \\| `undefined`\\>\n\nCreate a subscription to the currently selected artifact.\n\n#### Parameters\n\n| Name        | Type                                                               | Description               |\n| :---------- | :----------------------------------------------------------------- | :------------------------ |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Artifact` \\| `undefined`\\>\n\n---\n\n### useSelectedPlanet\n\n▸ **useSelectedPlanet**(`uiManager`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\nCreate a subscription to the currently selected planet.\n\n#### Parameters\n\n| Name        | Type                                                               | Description               |\n| :---------- | :----------------------------------------------------------------- | :------------------------ |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) | instance of GameUIManager |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`Planet` \\| `undefined`\\>\n\n---\n\n### useSelectedPlanetId\n\n▸ **useSelectedPlanetId**(`uiManager`, `defaultId?`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `LocationId`\\>\n\n#### Parameters\n\n| Name         | Type                                                               |\n| :----------- | :----------------------------------------------------------------- |\n| `uiManager`  | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `defaultId?` | `LocationId`                                                       |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `LocationId`\\>\n\n---\n\n### useTopLevelDiv\n\n▸ **useTopLevelDiv**(): `HTMLDivElement`\n\n#### Returns\n\n`HTMLDivElement`\n\n---\n\n### useTransactionLog\n\n▸ **useTransactionLog**(): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<[`TransactionRecord`](Frontend_Utils_AppHooks.md#transactionrecord)\\>\n\nCreates subscriptions to all contract transaction events to keep an up to date\nlist of all transactions and their states.\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<[`TransactionRecord`](Frontend_Utils_AppHooks.md#transactionrecord)\\>\n\n---\n\n### useUIManager\n\n▸ **useUIManager**(): [`default`](../classes/Backend_GameLogic_GameUIManager.default.md)\n\n#### Returns\n\n[`default`](../classes/Backend_GameLogic_GameUIManager.default.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_BrowserChecks.md",
    "content": "# Module: Frontend/Utils/BrowserChecks\n\n## Table of contents\n\n### Enumerations\n\n- [Incompatibility](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md)\n\n### Functions\n\n- [hasTouchscreen](Frontend_Utils_BrowserChecks.md#hastouchscreen)\n- [isBrave](Frontend_Utils_BrowserChecks.md#isbrave)\n- [isChrome](Frontend_Utils_BrowserChecks.md#ischrome)\n- [isFirefox](Frontend_Utils_BrowserChecks.md#isfirefox)\n- [isMobileOrTablet](Frontend_Utils_BrowserChecks.md#ismobileortablet)\n- [unsupportedFeatures](Frontend_Utils_BrowserChecks.md#unsupportedfeatures)\n\n## Functions\n\n### hasTouchscreen\n\n▸ **hasTouchscreen**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isBrave\n\n▸ **isBrave**(): `Promise`<`boolean`\\>\n\n#### Returns\n\n`Promise`<`boolean`\\>\n\n---\n\n### isChrome\n\n▸ **isChrome**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isFirefox\n\n▸ **isFirefox**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### isMobileOrTablet\n\n▸ **isMobileOrTablet**(): `boolean`\n\n#### Returns\n\n`boolean`\n\n---\n\n### unsupportedFeatures\n\n▸ **unsupportedFeatures**(): `Promise`<[`Incompatibility`](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md)[]\\>\n\n#### Returns\n\n`Promise`<[`Incompatibility`](../enums/Frontend_Utils_BrowserChecks.Incompatibility.md)[]\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_EmitterHooks.md",
    "content": "# Module: Frontend/Utils/EmitterHooks\n\n## Table of contents\n\n### Functions\n\n- [useEmitterSubscribe](Frontend_Utils_EmitterHooks.md#useemittersubscribe)\n- [useEmitterValue](Frontend_Utils_EmitterHooks.md#useemittervalue)\n- [useWrappedEmitter](Frontend_Utils_EmitterHooks.md#usewrappedemitter)\n\n## Functions\n\n### useEmitterSubscribe\n\n▸ **useEmitterSubscribe**<`T`\\>(`emitter`, `callback`, `deps`): `void`\n\nExecute something on emitter callback\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name       | Type               | Description                  |\n| :--------- | :----------------- | :--------------------------- |\n| `emitter`  | `Monomitter`<`T`\\> | `Monomitter` to subscribe to |\n| `callback` | `Callback`<`T`\\>   | callback to subscribe        |\n| `deps`     | `DependencyList`   | -                            |\n\n#### Returns\n\n`void`\n\n---\n\n### useEmitterValue\n\n▸ **useEmitterValue**<`T`\\>(`emitter`, `initialVal`): `T`\n\nUse returned value from an emitter\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name         | Type               | Description                  |\n| :----------- | :----------------- | :--------------------------- |\n| `emitter`    | `Monomitter`<`T`\\> | `Monomitter` to subscribe to |\n| `initialVal` | `T`                | initial state value          |\n\n#### Returns\n\n`T`\n\n---\n\n### useWrappedEmitter\n\n▸ **useWrappedEmitter**<`T`\\>(`emitter`, `initialVal`): [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`T` \\| `undefined`\\>\n\nUse returned value from an emitter, and clone the reference - used to force an update to the UI\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name         | Type                              | Description                  |\n| :----------- | :-------------------------------- | :--------------------------- |\n| `emitter`    | `Monomitter`<`undefined` \\| `T`\\> | `Monomitter` to subscribe to |\n| `initialVal` | `undefined` \\| `T`                | initial state value          |\n\n#### Returns\n\n[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`T` \\| `undefined`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_EmitterUtils.md",
    "content": "# Module: Frontend/Utils/EmitterUtils\n\n## Table of contents\n\n### Interfaces\n\n- [Diff](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)\n\n### Functions\n\n- [generateDiffEmitter](Frontend_Utils_EmitterUtils.md#generatediffemitter)\n- [getArtifactId](Frontend_Utils_EmitterUtils.md#getartifactid)\n- [getArtifactOwner](Frontend_Utils_EmitterUtils.md#getartifactowner)\n- [getDisposableEmitter](Frontend_Utils_EmitterUtils.md#getdisposableemitter)\n- [getObjectWithIdFromMap](Frontend_Utils_EmitterUtils.md#getobjectwithidfrommap)\n- [getPlanetId](Frontend_Utils_EmitterUtils.md#getplanetid)\n- [getPlanetOwner](Frontend_Utils_EmitterUtils.md#getplanetowner)\n- [setObjectSyncState](Frontend_Utils_EmitterUtils.md#setobjectsyncstate)\n\n## Functions\n\n### generateDiffEmitter\n\n▸ **generateDiffEmitter**<`Obj`\\>(`emitter`): `Monomitter`<[`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Obj`\\> \\| `undefined`\\>\n\nWraps an existing emitter and emits an event with the current and previous values\n\n#### Type parameters\n\n| Name  |\n| :---- |\n| `Obj` |\n\n#### Parameters\n\n| Name      | Type                                | Description                        |\n| :-------- | :---------------------------------- | :--------------------------------- |\n| `emitter` | `Monomitter`<`undefined` \\| `Obj`\\> | an emitter announcing game objects |\n\n#### Returns\n\n`Monomitter`<[`Diff`](../interfaces/Frontend_Utils_EmitterUtils.Diff.md)<`Obj`\\> \\| `undefined`\\>\n\n---\n\n### getArtifactId\n\n▸ **getArtifactId**(`a`): `ArtifactId`\n\n#### Parameters\n\n| Name | Type       |\n| :--- | :--------- |\n| `a`  | `Artifact` |\n\n#### Returns\n\n`ArtifactId`\n\n---\n\n### getArtifactOwner\n\n▸ **getArtifactOwner**(`a`): `EthAddress`\n\n#### Parameters\n\n| Name | Type       |\n| :--- | :--------- |\n| `a`  | `Artifact` |\n\n#### Returns\n\n`EthAddress`\n\n---\n\n### getDisposableEmitter\n\n▸ **getDisposableEmitter**<`Obj`, `Id`\\>(`objMap`, `objId`, `objUpdated$`): `Monomitter`<`Obj` \\| `undefined`\\>\n\nCreate a monomitter to emit objects with a given id from a cached map of ids to objects. Not intended for re-use\n\n#### Type parameters\n\n| Name  |\n| :---- |\n| `Obj` |\n| `Id`  |\n\n#### Parameters\n\n| Name          | Type                | Description                                             |\n| :------------ | :------------------ | :------------------------------------------------------ |\n| `objMap`      | `Map`<`Id`, `Obj`\\> | the cached map of `<Id, Obj>`                           |\n| `objId`       | `Id`                | the object id to select                                 |\n| `objUpdated$` | `Monomitter`<`Id`\\> | emitter which indicates when an object has been updated |\n\n#### Returns\n\n`Monomitter`<`Obj` \\| `undefined`\\>\n\n---\n\n### getObjectWithIdFromMap\n\n▸ **getObjectWithIdFromMap**<`Obj`, `Id`\\>(`objMap`, `objId$`, `objUpdated$`): `Monomitter`<`Obj` \\| `undefined`\\>\n\nCreate a monomitter to emit objects with a given id from a cached map of ids to objects.\n\n#### Type parameters\n\n| Name  |\n| :---- |\n| `Obj` |\n| `Id`  |\n\n#### Parameters\n\n| Name          | Type                               | Description                                             |\n| :------------ | :--------------------------------- | :------------------------------------------------------ |\n| `objMap`      | `Map`<`Id`, `Obj`\\>                | the cached map of `<Id, Obj>`                           |\n| `objId$`      | `Monomitter`<`undefined` \\| `Id`\\> | the object id to select                                 |\n| `objUpdated$` | `Monomitter`<`Id`\\>                | emitter which indicates when an object has been updated |\n\n#### Returns\n\n`Monomitter`<`Obj` \\| `undefined`\\>\n\n---\n\n### getPlanetId\n\n▸ **getPlanetId**(`p`): `LocationId`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `p`  | `Planet` |\n\n#### Returns\n\n`LocationId`\n\n---\n\n### getPlanetOwner\n\n▸ **getPlanetOwner**(`p`): `EthAddress`\n\n#### Parameters\n\n| Name | Type     |\n| :--- | :------- |\n| `p`  | `Planet` |\n\n#### Returns\n\n`EthAddress`\n\n---\n\n### setObjectSyncState\n\n▸ **setObjectSyncState**<`Obj`, `Id`\\>(`objectMap`, `myObjectMap`, `address`, `objUpdated$`, `myObjListUpdated$`, `getId`, `getOwner`, `obj`): `void`\n\nUtility function for setting a game entity into our internal data stores in a way\nthat is friendly to our application. Caches the object into a map, syncs it to a map\nof our owned objects, and also emits a message that the object was updated.\n\n#### Type parameters\n\n| Name  |\n| :---- |\n| `Obj` |\n| `Id`  |\n\n#### Parameters\n\n| Name                | Type                               | Description                                     |\n| :------------------ | :--------------------------------- | :---------------------------------------------- |\n| `objectMap`         | `Map`<`Id`, `Obj`\\>                | map that caches known objects                   |\n| `myObjectMap`       | `Map`<`Id`, `Obj`\\>                | map that caches known objects owned by the user |\n| `address`           | `undefined` \\| `EthAddress`        | the user's account address                      |\n| `objUpdated$`       | `Monomitter`<`Id`\\>                | emitter for announcing object updates           |\n| `myObjListUpdated$` | `Monomitter`<`Map`<`Id`, `Obj`\\>\\> | -                                               |\n| `getId`             | (`o`: `Obj`) => `Id`               | -                                               |\n| `getOwner`          | (`o`: `Obj`) => `EthAddress`       | -                                               |\n| `obj`               | `Obj`                              | the object we want to cache                     |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_Hooks.md",
    "content": "# Module: Frontend/Utils/Hooks\n\n## Table of contents\n\n### Functions\n\n- [usePoll](Frontend_Utils_Hooks.md#usepoll)\n\n## Functions\n\n### usePoll\n\n▸ **usePoll**(`cb`, `poll?`, `execFirst?`): `void`\n\nExecutes the callback `cb` every `poll` ms\n\n#### Parameters\n\n| Name        | Type                     | Default value | Description                                        |\n| :---------- | :----------------------- | :------------ | :------------------------------------------------- |\n| `cb`        | () => `void`             | `undefined`   | callback to execute                                |\n| `poll`      | `undefined` \\| `number`  | `undefined`   | ms to poll                                         |\n| `execFirst` | `undefined` \\| `boolean` | `undefined`   | if we want to execute the callback on first render |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_KeyEmitters.md",
    "content": "# Module: Frontend/Utils/KeyEmitters\n\n## Table of contents\n\n### Variables\n\n- [keyDown$](Frontend_Utils_KeyEmitters.md#keydown$)\n- [keyUp$](Frontend_Utils_KeyEmitters.md#keyup$)\n\n### Functions\n\n- [listenForKeyboardEvents](Frontend_Utils_KeyEmitters.md#listenforkeyboardevents)\n- [unlinkKeyboardEvents](Frontend_Utils_KeyEmitters.md#unlinkkeyboardevents)\n- [useIsDown](Frontend_Utils_KeyEmitters.md#useisdown)\n- [useOnUp](Frontend_Utils_KeyEmitters.md#useonup)\n\n## Variables\n\n### keyDown$\n\n• `Const` **keyDown$**: `Monomitter`<[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`string`\\>\\>\n\n---\n\n### keyUp$\n\n• `Const` **keyUp$**: `Monomitter`<[`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`string`\\>\\>\n\n## Functions\n\n### listenForKeyboardEvents\n\n▸ **listenForKeyboardEvents**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### unlinkKeyboardEvents\n\n▸ **unlinkKeyboardEvents**(): `void`\n\n#### Returns\n\n`void`\n\n---\n\n### useIsDown\n\n▸ **useIsDown**(`key?`): `boolean`\n\n#### Parameters\n\n| Name   | Type     |\n| :----- | :------- |\n| `key?` | `string` |\n\n#### Returns\n\n`boolean`\n\n---\n\n### useOnUp\n\n▸ **useOnUp**(`key`, `onUp`, `deps?`): `void`\n\n#### Parameters\n\n| Name   | Type             | Default value |\n| :----- | :--------------- | :------------ |\n| `key`  | `string`         | `undefined`   |\n| `onUp` | () => `void`     | `undefined`   |\n| `deps` | `DependencyList` | `[]`          |\n\n#### Returns\n\n`void`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_SettingsHooks.md",
    "content": "# Module: Frontend/Utils/SettingsHooks\n\n## Table of contents\n\n### Variables\n\n- [ALL_AUTO_GAS_SETTINGS](Frontend_Utils_SettingsHooks.md#all_auto_gas_settings)\n- [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$)\n\n### Functions\n\n- [BooleanSetting](Frontend_Utils_SettingsHooks.md#booleansetting)\n- [ColorSetting](Frontend_Utils_SettingsHooks.md#colorsetting)\n- [MultiSelectSetting](Frontend_Utils_SettingsHooks.md#multiselectsetting)\n- [NumberSetting](Frontend_Utils_SettingsHooks.md#numbersetting)\n- [StringSetting](Frontend_Utils_SettingsHooks.md#stringsetting)\n- [getBooleanSetting](Frontend_Utils_SettingsHooks.md#getbooleansetting)\n- [getLocalStorageSettingKey](Frontend_Utils_SettingsHooks.md#getlocalstoragesettingkey)\n- [getNumberSetting](Frontend_Utils_SettingsHooks.md#getnumbersetting)\n- [getSetting](Frontend_Utils_SettingsHooks.md#getsetting)\n- [pollSetting](Frontend_Utils_SettingsHooks.md#pollsetting)\n- [setBooleanSetting](Frontend_Utils_SettingsHooks.md#setbooleansetting)\n- [setNumberSetting](Frontend_Utils_SettingsHooks.md#setnumbersetting)\n- [setSetting](Frontend_Utils_SettingsHooks.md#setsetting)\n- [useBooleanSetting](Frontend_Utils_SettingsHooks.md#usebooleansetting)\n- [useNumberSetting](Frontend_Utils_SettingsHooks.md#usenumbersetting)\n- [useSetting](Frontend_Utils_SettingsHooks.md#usesetting)\n\n## Variables\n\n### ALL_AUTO_GAS_SETTINGS\n\n• `Const` **ALL_AUTO_GAS_SETTINGS**: `AutoGasSetting`[]\n\n---\n\n### settingChanged$\n\n• `Const` **settingChanged$**: `Monomitter`<`Setting`\\>\n\nWhenever a setting changes, we publish the setting's name to this event emitter.\n\n## Functions\n\n### BooleanSetting\n\n▸ **BooleanSetting**(`__namedParameters`): `Element`\n\nReact component that renders a checkbox representing the current value of this particular\nsetting, interpreting its value as a boolean. Allows the player to click on the checkbox to\ntoggle the setting. Toggling the setting both notifies the rest of the game that the given\nsetting was changed, and also saves it to local storage.\n\n#### Parameters\n\n| Name                                    | Type                                                               |\n| :-------------------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`                     | `Object`                                                           |\n| `__namedParameters.setting`             | `Setting`                                                          |\n| `__namedParameters.settingDescription?` | `string`                                                           |\n| `__namedParameters.uiManager`           | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### ColorSetting\n\n▸ **ColorSetting**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                    | Type                                                               |\n| :-------------------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`                     | `Object`                                                           |\n| `__namedParameters.setting`             | `Setting`                                                          |\n| `__namedParameters.settingDescription?` | `string`                                                           |\n| `__namedParameters.uiManager`           | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### MultiSelectSetting\n\n▸ **MultiSelectSetting**(`__namedParameters`): `Element`\n\nUI that is kept in-sync with a particular setting which allows you to set that setting to one of\nseveral options.\n\n#### Parameters\n\n| Name                          | Type                                                               |\n| :---------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`           | `Object`                                                           |\n| `__namedParameters.labels`    | `string`[]                                                         |\n| `__namedParameters.setting`   | `Setting`                                                          |\n| `__namedParameters.style?`    | `CSSProperties`                                                    |\n| `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `__namedParameters.values`    | `string`[]                                                         |\n| `__namedParameters.wide?`     | `boolean`                                                          |\n\n#### Returns\n\n`Element`\n\n---\n\n### NumberSetting\n\n▸ **NumberSetting**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                          | Type                                                               |\n| :---------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`           | `Object`                                                           |\n| `__namedParameters.setting`   | `Setting`                                                          |\n| `__namedParameters.uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### StringSetting\n\n▸ **StringSetting**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                    | Type                                                               |\n| :-------------------------------------- | :----------------------------------------------------------------- |\n| `__namedParameters`                     | `Object`                                                           |\n| `__namedParameters.setting`             | `Setting`                                                          |\n| `__namedParameters.settingDescription?` | `string`                                                           |\n| `__namedParameters.uiManager`           | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n\n#### Returns\n\n`Element`\n\n---\n\n### getBooleanSetting\n\n▸ **getBooleanSetting**(`config`, `setting`): `boolean`\n\nLoads from local storage, and interprets as a boolean the setting with the given name.\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n\n#### Returns\n\n`boolean`\n\n---\n\n### getLocalStorageSettingKey\n\n▸ **getLocalStorageSettingKey**(`__namedParameters`, `setting`): `string`\n\nEach setting is stored in local storage. Each account has their own setting.\n\n#### Parameters\n\n| Name                | Type                   |\n| :------------------ | :--------------------- |\n| `__namedParameters` | `SettingStorageConfig` |\n| `setting`           | `Setting`              |\n\n#### Returns\n\n`string`\n\n---\n\n### getNumberSetting\n\n▸ **getNumberSetting**(`config`, `setting`): `number`\n\nLoads from local storage, and interprets as a boolean the setting with the given name.\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n\n#### Returns\n\n`number`\n\n---\n\n### getSetting\n\n▸ **getSetting**(`config`, `setting`): `string`\n\nRead the local storage setting from local storage.\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n\n#### Returns\n\n`string`\n\n---\n\n### pollSetting\n\n▸ **pollSetting**(`config`, `setting`): `ReturnType`<typeof `setInterval`\\>\n\nSome settings can be set from another browser window. In particular, the 'auto accept\ntransaction' setting is set from multiple browser windows. As a result, the local storage setting\ncan get out of sync with the in memory setting. To fix this, we can poll the given setting from\nlocal storage, and notify the rest of the game that it changed if it changed.\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n\n#### Returns\n\n`ReturnType`<typeof `setInterval`\\>\n\n---\n\n### setBooleanSetting\n\n▸ **setBooleanSetting**(`config`, `setting`, `value`): `void`\n\nSave the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$).\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n| `value`   | `boolean`              |\n\n#### Returns\n\n`void`\n\n---\n\n### setNumberSetting\n\n▸ **setNumberSetting**(`config`, `setting`, `value`): `void`\n\nSave the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$).\n\n#### Parameters\n\n| Name      | Type                   |\n| :-------- | :--------------------- |\n| `config`  | `SettingStorageConfig` |\n| `setting` | `Setting`              |\n| `value`   | `number`               |\n\n#### Returns\n\n`void`\n\n---\n\n### setSetting\n\n▸ **setSetting**(`__namedParameters`, `setting`, `value`): `void`\n\nSave the given setting to local storage. Publish an event to [settingChanged$](Frontend_Utils_SettingsHooks.md#settingchanged$).\n\n#### Parameters\n\n| Name                | Type                   |\n| :------------------ | :--------------------- |\n| `__namedParameters` | `SettingStorageConfig` |\n| `setting`           | `Setting`              |\n| `value`             | `string`               |\n\n#### Returns\n\n`void`\n\n---\n\n### useBooleanSetting\n\n▸ **useBooleanSetting**(`uiManager`, `setting`): [`boolean`, (`newValue`: `boolean`) => `void`]\n\nAllows a react component to subscribe to changes to the given setting, interpreting its value as\na boolean.\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `setting`   | `Setting`                                                          |\n\n#### Returns\n\n[`boolean`, (`newValue`: `boolean`) => `void`]\n\n---\n\n### useNumberSetting\n\n▸ **useNumberSetting**(`uiManager`, `setting`): [`number`, (`newValue`: `number`) => `void`]\n\nAllows a react component to subscribe to changes and set the given setting as a number. Doesn't\nallow you to set the value of this setting to anything but a valid number.\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `setting`   | `Setting`                                                          |\n\n#### Returns\n\n[`number`, (`newValue`: `number`) => `void`]\n\n---\n\n### useSetting\n\n▸ **useSetting**(`uiManager`, `setting`): [`string`, (`newValue`: `string` \\| `undefined`) => `void`]\n\nAllows a react component to subscribe to changes and set the given setting.\n\n#### Parameters\n\n| Name        | Type                                                               |\n| :---------- | :----------------------------------------------------------------- |\n| `uiManager` | [`default`](../classes/Backend_GameLogic_GameUIManager.default.md) |\n| `setting`   | `Setting`                                                          |\n\n#### Returns\n\n[`string`, (`newValue`: `string` \\| `undefined`) => `void`]\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_ShortcutConstants.md",
    "content": "# Module: Frontend/Utils/ShortcutConstants\n\n## Table of contents\n\n### Variables\n\n- [CLOSE_MODAL](Frontend_Utils_ShortcutConstants.md#close_modal)\n- [EXIT_PANE](Frontend_Utils_ShortcutConstants.md#exit_pane)\n- [INVADE](Frontend_Utils_ShortcutConstants.md#invade)\n- [MINE_ARTIFACT](Frontend_Utils_ShortcutConstants.md#mine_artifact)\n- [MODAL_BACK_SHORTCUT](Frontend_Utils_ShortcutConstants.md#modal_back_shortcut)\n- [TOGGLE_ABANDON](Frontend_Utils_ShortcutConstants.md#toggle_abandon)\n- [TOGGLE_BROADCAST_PANE](Frontend_Utils_ShortcutConstants.md#toggle_broadcast_pane)\n- [TOGGLE_DIAGNOSTICS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_diagnostics_pane)\n- [TOGGLE_EXPLORE](Frontend_Utils_ShortcutConstants.md#toggle_explore)\n- [TOGGLE_HAT_PANE](Frontend_Utils_ShortcutConstants.md#toggle_hat_pane)\n- [TOGGLE_HELP_PANE](Frontend_Utils_ShortcutConstants.md#toggle_help_pane)\n- [TOGGLE_PLANET_ARTIFACTS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_planet_artifacts_pane)\n- [TOGGLE_PLANET_INFO_PANE](Frontend_Utils_ShortcutConstants.md#toggle_planet_info_pane)\n- [TOGGLE_PLUGINS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_plugins_pane)\n- [TOGGLE_SEND](Frontend_Utils_ShortcutConstants.md#toggle_send)\n- [TOGGLE_SETTINGS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_settings_pane)\n- [TOGGLE_TARGETTING](Frontend_Utils_ShortcutConstants.md#toggle_targetting)\n- [TOGGLE_TRANSACTIONS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_transactions_pane)\n- [TOGGLE_UPGRADES_PANE](Frontend_Utils_ShortcutConstants.md#toggle_upgrades_pane)\n- [TOGGLE_YOUR_ARTIFACTS_PANE](Frontend_Utils_ShortcutConstants.md#toggle_your_artifacts_pane)\n- [TOGGLE_YOUR_PLANETS_DEX_PANE](Frontend_Utils_ShortcutConstants.md#toggle_your_planets_dex_pane)\n\n## Variables\n\n### CLOSE_MODAL\n\n• `Const` **CLOSE_MODAL**: `\"t\"`\n\n---\n\n### EXIT_PANE\n\n• `Const` **EXIT_PANE**: `\"Escape\"`\n\n---\n\n### INVADE\n\n• `Const` **INVADE**: `\"y\"`\n\n---\n\n### MINE_ARTIFACT\n\n• `Const` **MINE_ARTIFACT**: `\"f\"`\n\n---\n\n### MODAL_BACK_SHORTCUT\n\n• `Const` **MODAL_BACK_SHORTCUT**: `\"t\"`\n\n---\n\n### TOGGLE_ABANDON\n\n• `Const` **TOGGLE_ABANDON**: `\"r\"`\n\n---\n\n### TOGGLE_BROADCAST_PANE\n\n• `Const` **TOGGLE_BROADCAST_PANE**: `\"z\"`\n\n---\n\n### TOGGLE_DIAGNOSTICS_PANE\n\n• `Const` **TOGGLE_DIAGNOSTICS_PANE**: `\"i\"`\n\n---\n\n### TOGGLE_EXPLORE\n\n• `Const` **TOGGLE_EXPLORE**: `\" \"`\n\n---\n\n### TOGGLE_HAT_PANE\n\n• `Const` **TOGGLE_HAT_PANE**: `\"x\"`\n\n---\n\n### TOGGLE_HELP_PANE\n\n• `Const` **TOGGLE_HELP_PANE**: `\"j\"`\n\n---\n\n### TOGGLE_PLANET_ARTIFACTS_PANE\n\n• `Const` **TOGGLE_PLANET_ARTIFACTS_PANE**: `\"s\"`\n\n---\n\n### TOGGLE_PLANET_INFO_PANE\n\n• `Const` **TOGGLE_PLANET_INFO_PANE**: `\"c\"`\n\n---\n\n### TOGGLE_PLUGINS_PANE\n\n• `Const` **TOGGLE_PLUGINS_PANE**: `\"k\"`\n\n---\n\n### TOGGLE_SEND\n\n• `Const` **TOGGLE_SEND**: `\"q\"`\n\n---\n\n### TOGGLE_SETTINGS_PANE\n\n• `Const` **TOGGLE_SETTINGS_PANE**: `\"h\"`\n\n---\n\n### TOGGLE_TARGETTING\n\n• `Const` **TOGGLE_TARGETTING**: `` \"`\" ``\n\n---\n\n### TOGGLE_TRANSACTIONS_PANE\n\n• `Const` **TOGGLE_TRANSACTIONS_PANE**: `\"'\"`\n\n---\n\n### TOGGLE_UPGRADES_PANE\n\n• `Const` **TOGGLE_UPGRADES_PANE**: `\"a\"`\n\n---\n\n### TOGGLE_YOUR_ARTIFACTS_PANE\n\n• `Const` **TOGGLE_YOUR_ARTIFACTS_PANE**: `\"l\"`\n\n---\n\n### TOGGLE_YOUR_PLANETS_DEX_PANE\n\n• `Const` **TOGGLE_YOUR_PLANETS_DEX_PANE**: `\";\"`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_TerminalTypes.md",
    "content": "# Module: Frontend/Utils/TerminalTypes\n\n## Table of contents\n\n### Enumerations\n\n- [TerminalTextStyle](../enums/Frontend_Utils_TerminalTypes.TerminalTextStyle.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_TimeUtils.md",
    "content": "# Module: Frontend/Utils/TimeUtils\n\n## Table of contents\n\n### Functions\n\n- [formatDuration](Frontend_Utils_TimeUtils.md#formatduration)\n\n## Functions\n\n### formatDuration\n\n▸ **formatDuration**(`durationMs`): `string`\n\n#### Parameters\n\n| Name         | Type     |\n| :----------- | :------- |\n| `durationMs` | `number` |\n\n#### Returns\n\n`string`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_UIEmitter.md",
    "content": "# Module: Frontend/Utils/UIEmitter\n\n## Table of contents\n\n### Enumerations\n\n- [UIEmitterEvent](../enums/Frontend_Utils_UIEmitter.UIEmitterEvent.md)\n\n### Classes\n\n- [default](../classes/Frontend_Utils_UIEmitter.default.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_constants.md",
    "content": "# Module: Frontend/Utils/constants\n\n## Table of contents\n\n### Enumerations\n\n- [DFZIndex](../enums/Frontend_Utils_constants.DFZIndex.md)\n\n### Variables\n\n- [LOCATION_ID_UB](Frontend_Utils_constants.md#location_id_ub)\n- [MAX_CHUNK_SIZE](Frontend_Utils_constants.md#max_chunk_size)\n- [MIN_CHUNK_SIZE](Frontend_Utils_constants.md#min_chunk_size)\n\n## Variables\n\n### LOCATION_ID_UB\n\n• `Const` **LOCATION_ID_UB**: `BigInteger`\n\n---\n\n### MAX_CHUNK_SIZE\n\n• `Const` **MAX_CHUNK_SIZE**: `number`\n\n**`tutorial`** to speed up the game's background rendering code, it is possible to set this value to\nbe a higher power of two. This means that smaller chunks will be merged into larger chunks via\nthe algorithms implemented in {@link ChunkUtils}.\n\n{@code Math.floor(Math.pow(2, 16))} should be large enough for most.\n\n---\n\n### MIN_CHUNK_SIZE\n\n• `Const` **MIN_CHUNK_SIZE**: `16`\n"
  },
  {
    "path": "docs/modules/Frontend_Utils_createDefinedContext.md",
    "content": "# Module: Frontend/Utils/createDefinedContext\n\n## Table of contents\n\n### Functions\n\n- [createDefinedContext](Frontend_Utils_createDefinedContext.md#createdefinedcontext)\n\n## Functions\n\n### createDefinedContext\n\n▸ **createDefinedContext**<`T`\\>(): `ContextHookWithProvider`<`T`\\>\n\nReturn a hook and a provider which return a value that must be defined. Normally is difficult\nbecause `React.createContext()` defaults to `undefined`.\n\n`useDefinedContext()` must be called inside of `provider`, otherwise an error will be thrown.\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Returns\n\n`ContextHookWithProvider`<`T`\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Views_ArtifactLink.md",
    "content": "# Module: Frontend/Views/ArtifactLink\n\n## Table of contents\n\n### Functions\n\n- [ArtifactLink](Frontend_Views_ArtifactLink.md#artifactlink)\n\n## Functions\n\n### ArtifactLink\n\n▸ **ArtifactLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                           | Type                                                                   |\n| :----------------------------- | :--------------------------------------------------------------------- |\n| `__namedParameters`            | `Object`                                                               |\n| `__namedParameters.artifact`   | `Artifact`                                                             |\n| `__namedParameters.children`   | `ReactNode` \\| `ReactNode`[]                                           |\n| `__namedParameters.depositOn?` | `LocationId`                                                           |\n| `__namedParameters.modal?`     | [`ModalHandle`](../interfaces/Frontend_Views_ModalPane.ModalHandle.md) |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_ArtifactRow.md",
    "content": "# Module: Frontend/Views/ArtifactRow\n\n## Table of contents\n\n### Functions\n\n- [ArtifactThumb](Frontend_Views_ArtifactRow.md#artifactthumb)\n- [SelectArtifactRow](Frontend_Views_ArtifactRow.md#selectartifactrow)\n\n## Functions\n\n### ArtifactThumb\n\n▸ **ArtifactThumb**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                  | Type                                              |\n| :------------------------------------ | :------------------------------------------------ |\n| `__namedParameters`                   | `Object`                                          |\n| `__namedParameters.artifact`          | `Artifact`                                        |\n| `__namedParameters.selectedArtifact?` | `Artifact`                                        |\n| `__namedParameters.onArtifactChange?` | (`artifact`: `undefined` \\| `Artifact`) => `void` |\n\n#### Returns\n\n`Element`\n\n---\n\n### SelectArtifactRow\n\n▸ **SelectArtifactRow**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                  | Type                                              |\n| :------------------------------------ | :------------------------------------------------ |\n| `__namedParameters`                   | `Object`                                          |\n| `__namedParameters.artifacts`         | `Artifact`[]                                      |\n| `__namedParameters.selectedArtifact?` | `Artifact`                                        |\n| `__namedParameters.onArtifactChange?` | (`artifact`: `undefined` \\| `Artifact`) => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_CadetWormhole.md",
    "content": "# Module: Frontend/Views/CadetWormhole\n\n## Table of contents\n\n### Functions\n\n- [CadetWormhole](Frontend_Views_CadetWormhole.md#cadetwormhole)\n\n## Functions\n\n### CadetWormhole\n\n▸ **CadetWormhole**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type     |\n| :------------------------- | :------- |\n| `__namedParameters`        | `Object` |\n| `__namedParameters.imgUrl` | `string` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_DFErrorBoundary.md",
    "content": "# Module: Frontend/Views/DFErrorBoundary\n\n## Table of contents\n\n### Classes\n\n- [DFErrorBoundary](../classes/Frontend_Views_DFErrorBoundary.DFErrorBoundary.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Views_DarkForestTips.md",
    "content": "# Module: Frontend/Views/DarkForestTips\n\n## Table of contents\n\n### Functions\n\n- [DarkForestTips](Frontend_Views_DarkForestTips.md#darkforesttips)\n- [MakeDarkForestTips](Frontend_Views_DarkForestTips.md#makedarkforesttips)\n\n## Functions\n\n### DarkForestTips\n\n▸ **DarkForestTips**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                      |\n| :------------------------- | :------------------------ |\n| `__namedParameters`        | `Object`                  |\n| `__namedParameters.tips`   | (`string` \\| `Element`)[] |\n| `__namedParameters.title?` | `string`                  |\n\n#### Returns\n\n`Element`\n\n---\n\n### MakeDarkForestTips\n\n▸ **MakeDarkForestTips**(`tips`): `Element`\n\n#### Parameters\n\n| Name   | Type       |\n| :----- | :--------- |\n| `tips` | `string`[] |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_EmojiPicker.md",
    "content": "# Module: Frontend/Views/EmojiPicker\n\n## Table of contents\n\n### Functions\n\n- [EmojiPicker](Frontend_Views_EmojiPicker.md#emojipicker)\n\n## Functions\n\n### EmojiPicker\n\n▸ **EmojiPicker**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                          |\n| :--------------------------- | :---------------------------- |\n| `__namedParameters`          | `Object`                      |\n| `__namedParameters.emoji`    | `undefined` \\| `string`       |\n| `__namedParameters.setEmoji` | (`emoji`: `string`) => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_EmojiPlanetNotification.md",
    "content": "# Module: Frontend/Views/EmojiPlanetNotification\n\n## Table of contents\n\n### Functions\n\n- [EmojiPlanetNotification](Frontend_Views_EmojiPlanetNotification.md#emojiplanetnotification)\n\n## Functions\n\n### EmojiPlanetNotification\n\n▸ **EmojiPlanetNotification**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                                                                               |\n| :-------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`         | `Object`                                                                           |\n| `__namedParameters.wrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_GameWindowLayout.md",
    "content": "# Module: Frontend/Views/GameWindowLayout\n\n## Table of contents\n\n### Functions\n\n- [GameWindowLayout](Frontend_Views_GameWindowLayout.md#gamewindowlayout)\n\n## Functions\n\n### GameWindowLayout\n\n▸ **GameWindowLayout**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                   | Type                             |\n| :------------------------------------- | :------------------------------- |\n| `__namedParameters`                    | `Object`                         |\n| `__namedParameters.terminalVisible`    | `boolean`                        |\n| `__namedParameters.setTerminalVisible` | (`visible`: `boolean`) => `void` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_GenericErrorBoundary.md",
    "content": "# Module: Frontend/Views/GenericErrorBoundary\n\n## Table of contents\n\n### Classes\n\n- [GenericErrorBoundary](../classes/Frontend_Views_GenericErrorBoundary.GenericErrorBoundary.md)\n"
  },
  {
    "path": "docs/modules/Frontend_Views_LandingPageRoundArt.md",
    "content": "# Module: Frontend/Views/LandingPageRoundArt\n\n## Table of contents\n\n### Functions\n\n- [LandingPageRoundArt](Frontend_Views_LandingPageRoundArt.md#landingpageroundart)\n\n## Functions\n\n### LandingPageRoundArt\n\n▸ **LandingPageRoundArt**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Leaderboard.md",
    "content": "# Module: Frontend/Views/Leaderboard\n\n## Table of contents\n\n### Functions\n\n- [LeadboardDisplay](Frontend_Views_Leaderboard.md#leadboarddisplay)\n\n## Functions\n\n### LeadboardDisplay\n\n▸ **LeadboardDisplay**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_ModalIcon.md",
    "content": "# Module: Frontend/Views/ModalIcon\n\n## Table of contents\n\n### Functions\n\n- [ModalToggleButton](Frontend_Views_ModalIcon.md#modaltogglebutton)\n\n## Functions\n\n### ModalToggleButton\n\n▸ **ModalToggleButton**(`__namedParameters`): `Element`\n\nA button which allows you to open a modal.\n\n#### Parameters\n\n| Name                | Type                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| :------------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `__namedParameters` | { `hook`: [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> ; `modal`: `ModalName` ; `style?`: `CSSProperties` ; `text?`: `string` } & `Partial`<`Omit`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestButton`](../classes/Frontend_Components_Btn.DarkForestButton.md), `MouseEvent`\\>) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\> & { `hook`: [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> ; `modal`: `ModalName` ; `style?`: `CSSProperties` ; `text?`: `string` } & `Partial`<`Omit`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `\"children\"`\\>\\> & `Events`<{ `onClick`: (`evt`: `Event` & `MouseEvent`<[`DarkForestShortcutButton`](../classes/Frontend_Components_Btn.DarkForestShortcutButton.md), `MouseEvent`\\>) => `void` ; `onShortcutPressed`: (`evt`: [`ShortcutPressedEvent`](../classes/Frontend_Components_Btn.ShortcutPressedEvent.md)) => `void` }\\> & `HTMLAttributes`<`HTMLElement`\\> & {} & `RefAttributes`<`unknown`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_ModalPane.md",
    "content": "# Module: Frontend/Views/ModalPane\n\n## Table of contents\n\n### Interfaces\n\n- [ModalFrame](../interfaces/Frontend_Views_ModalPane.ModalFrame.md)\n- [ModalHandle](../interfaces/Frontend_Views_ModalPane.ModalHandle.md)\n\n### Type aliases\n\n- [ModalProps](Frontend_Views_ModalPane.md#modalprops)\n\n### Functions\n\n- [ModalPane](Frontend_Views_ModalPane.md#modalpane)\n\n## Type aliases\n\n### ModalProps\n\nƬ **ModalProps**: [`PaneProps`](Frontend_Components_GameWindowComponents.md#paneprops) & { `hideClose?`: `boolean` ; `id`: `ModalId` ; `initialPosition?`: { `x`: `number` ; `y`: `number` } ; `style?`: `CSSStyleDeclaration` & `React.CSSProperties` ; `title`: `string` \\| `React.ReactNode` ; `visible`: `boolean` ; `width?`: `string` ; `helpContent?`: () => `ReactNode` ; `onClose`: () => `void` }\n\n## Functions\n\n### ModalPane\n\n▸ **ModalPane**(`__namedParameters`): `null` \\| `Element`\n\n#### Parameters\n\n| Name                | Type                                                   |\n| :------------------ | :----------------------------------------------------- |\n| `__namedParameters` | [`ModalProps`](Frontend_Views_ModalPane.md#modalprops) |\n\n#### Returns\n\n`null` \\| `Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_NetworkHealth.md",
    "content": "# Module: Frontend/Views/NetworkHealth\n\n## Table of contents\n\n### Functions\n\n- [NetworkHealth](Frontend_Views_NetworkHealth.md#networkhealth)\n\n## Functions\n\n### NetworkHealth\n\n▸ **NetworkHealth**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Notifications.md",
    "content": "# Module: Frontend/Views/Notifications\n\n## Table of contents\n\n### Functions\n\n- [NotificationsPane](Frontend_Views_Notifications.md#notificationspane)\n\n## Functions\n\n### NotificationsPane\n\n▸ **NotificationsPane**(): `Element`\n\nReact component in charge of listening for new notifications and displaying them interactively to\nthe user.\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Paused.md",
    "content": "# Module: Frontend/Views/Paused\n\n## Table of contents\n\n### Functions\n\n- [Paused](Frontend_Views_Paused.md#paused)\n\n## Functions\n\n### Paused\n\n▸ **Paused**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_PlanetCard.md",
    "content": "# Module: Frontend/Views/PlanetCard\n\n## Table of contents\n\n### Functions\n\n- [PlanetCard](Frontend_Views_PlanetCard.md#planetcard)\n- [PlanetCardTitle](Frontend_Views_PlanetCard.md#planetcardtitle)\n\n## Functions\n\n### PlanetCard\n\n▸ **PlanetCard**(`__namedParameters`): `Element`\n\nPreview basic planet information - used in `PlanetContextPane` and `HoverPlanetPane`\n\n#### Parameters\n\n| Name                              | Type                                                                               |\n| :-------------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`               | `Object`                                                                           |\n| `__namedParameters.planetWrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `__namedParameters.standalone?`   | `boolean`                                                                          |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetCardTitle\n\n▸ **PlanetCardTitle**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                                                                               |\n| :------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`        | `Object`                                                                           |\n| `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `__namedParameters.small?` | `boolean`                                                                          |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_PlanetCardComponents.md",
    "content": "# Module: Frontend/Views/PlanetCardComponents\n\n## Table of contents\n\n### Variables\n\n- [TitleBar](Frontend_Views_PlanetCardComponents.md#titlebar)\n\n### Functions\n\n- [Halved](Frontend_Views_PlanetCardComponents.md#halved)\n- [PlanetActiveArtifact](Frontend_Views_PlanetCardComponents.md#planetactiveartifact)\n- [RowTip](Frontend_Views_PlanetCardComponents.md#rowtip)\n- [TimesTwo](Frontend_Views_PlanetCardComponents.md#timestwo)\n\n## Variables\n\n### TitleBar\n\n• `Const` **TitleBar**: `StyledComponent`<`\"div\"`, `any`, {}, `never`\\>\n\n## Functions\n\n### Halved\n\n▸ **Halved**(): `Element`\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetActiveArtifact\n\n▸ **PlanetActiveArtifact**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type                    |\n| :--------------------------- | :---------------------- |\n| `__namedParameters`          | `Object`                |\n| `__namedParameters.artifact` | `Artifact`              |\n| `__namedParameters.planet`   | `undefined` \\| `Planet` |\n\n#### Returns\n\n`Element`\n\n---\n\n### RowTip\n\n▸ **RowTip**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type          |\n| :--------------------------- | :------------ |\n| `__namedParameters`          | `Object`      |\n| `__namedParameters.children` | `ReactNode`   |\n| `__namedParameters.name`     | `TooltipName` |\n\n#### Returns\n\n`Element`\n\n---\n\n### TimesTwo\n\n▸ **TimesTwo**(): `Element`\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_PlanetLink.md",
    "content": "# Module: Frontend/Views/PlanetLink\n\n## Table of contents\n\n### Functions\n\n- [PlanetLink](Frontend_Views_PlanetLink.md#planetlink)\n\n## Functions\n\n### PlanetLink\n\n▸ **PlanetLink**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                         | Type        |\n| :--------------------------- | :---------- |\n| `__namedParameters`          | `Object`    |\n| `__namedParameters.children` | `ReactNode` |\n| `__namedParameters.planet`   | `Planet`    |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_PlanetNotifications.md",
    "content": "# Module: Frontend/Views/PlanetNotifications\n\n## Table of contents\n\n### Enumerations\n\n- [PlanetNotifType](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)\n\n### Functions\n\n- [DistanceFromCenterRow](Frontend_Views_PlanetNotifications.md#distancefromcenterrow)\n- [PlanetClaimedRow](Frontend_Views_PlanetNotifications.md#planetclaimedrow)\n- [PlanetNotifications](Frontend_Views_PlanetNotifications.md#planetnotifications)\n- [getNotifsForPlanet](Frontend_Views_PlanetNotifications.md#getnotifsforplanet)\n\n## Functions\n\n### DistanceFromCenterRow\n\n▸ **DistanceFromCenterRow**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                                                                               |\n| :------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`        | `Object`                                                                           |\n| `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetClaimedRow\n\n▸ **PlanetClaimedRow**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                                                                               |\n| :------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`        | `Object`                                                                           |\n| `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n\n---\n\n### PlanetNotifications\n\n▸ **PlanetNotifications**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                       | Type                                                                                  |\n| :------------------------- | :------------------------------------------------------------------------------------ |\n| `__namedParameters`        | `Object`                                                                              |\n| `__namedParameters.notifs` | [`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[] |\n| `__namedParameters.planet` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\>    |\n\n#### Returns\n\n`Element`\n\n---\n\n### getNotifsForPlanet\n\n▸ **getNotifsForPlanet**(`planet`, `account`): [`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[]\n\n#### Parameters\n\n| Name      | Type                        |\n| :-------- | :-------------------------- |\n| `planet`  | `undefined` \\| `Planet`     |\n| `account` | `undefined` \\| `EthAddress` |\n\n#### Returns\n\n[`PlanetNotifType`](../enums/Frontend_Views_PlanetNotifications.PlanetNotifType.md)[]\n"
  },
  {
    "path": "docs/modules/Frontend_Views_SendResources.md",
    "content": "# Module: Frontend/Views/SendResources\n\n## Table of contents\n\n### Functions\n\n- [SendResources](Frontend_Views_SendResources.md#sendresources)\n\n## Functions\n\n### SendResources\n\n▸ **SendResources**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                   | Type                                                                               |\n| :------------------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`                    | `Object`                                                                           |\n| `__namedParameters.planetWrapper`      | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n| `__namedParameters.onToggleAbandon`    | () => `void`                                                                       |\n| `__namedParameters.onToggleSendForces` | () => `void`                                                                       |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Share.md",
    "content": "# Module: Frontend/Views/Share\n\n## Table of contents\n\n### Interfaces\n\n- [ShareProps](../interfaces/Frontend_Views_Share.ShareProps.md)\n\n### Functions\n\n- [Share](Frontend_Views_Share.md#share)\n\n## Functions\n\n### Share\n\n▸ **Share**<`T`\\>(`props`): `Element`\n\nHelper component that allows you to load data from the contract, as if it was\nviewed from a particular account. Allows you to switch accounts. Just pass in:\n\n1. a function that loads the data you want, given a [[ReaderDataStore]]\n2. a function that renders the given data with React\n\n... and this component will take care of loading what you want.\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name    | Type                                                                   |\n| :------ | :--------------------------------------------------------------------- |\n| `props` | [`ShareProps`](../interfaces/Frontend_Views_Share.ShareProps.md)<`T`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_SidebarPane.md",
    "content": "# Module: Frontend/Views/SidebarPane\n\n## Table of contents\n\n### Functions\n\n- [SidebarPane](Frontend_Views_SidebarPane.md#sidebarpane)\n\n## Functions\n\n### SidebarPane\n\n▸ **SidebarPane**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                   | Type                                                   |\n| :------------------------------------- | :----------------------------------------------------- |\n| `__namedParameters`                    | `Object`                                               |\n| `__namedParameters.helpHook`           | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n| `__namedParameters.planetdexHook`      | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n| `__namedParameters.pluginsHook`        | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n| `__namedParameters.settingsHook`       | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n| `__namedParameters.transactionLogHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n| `__namedParameters.yourArtifactsHook`  | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_SortableTable.md",
    "content": "# Module: Frontend/Views/SortableTable\n\n## Table of contents\n\n### Functions\n\n- [SortableTable](Frontend_Views_SortableTable.md#sortabletable)\n\n## Functions\n\n### SortableTable\n\n▸ **SortableTable**<`T`\\>(`__namedParameters`): `Element`\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name                              | Type                                       |\n| :-------------------------------- | :----------------------------------------- |\n| `__namedParameters`               | `Object`                                   |\n| `__namedParameters.alignments?`   | (`\"r\"` \\| `\"l\"` \\| `\"c\"`)[]                |\n| `__namedParameters.columns`       | (`t`: `T`, `i`: `number`) => `ReactNode`[] |\n| `__namedParameters.headers`       | `ReactNode`[]                              |\n| `__namedParameters.paginated?`    | `boolean`                                  |\n| `__namedParameters.rows`          | `T`[]                                      |\n| `__namedParameters.sortFunctions` | (`left`: `T`, `right`: `T`) => `number`[]  |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_TabbedView.md",
    "content": "# Module: Frontend/Views/TabbedView\n\n## Table of contents\n\n### Functions\n\n- [TabbedView](Frontend_Views_TabbedView.md#tabbedview)\n\n## Functions\n\n### TabbedView\n\n▸ **TabbedView**(`__namedParameters`): `Element`\n\nThis component allows you to render several tabs of content. Each tab can be selected for viewing\nby clicking on its corresponding tab button. Useful for displaying lots of slightly different but\nrelated information to the user.\n\n#### Parameters\n\n| Name                            | Type                                  |\n| :------------------------------ | :------------------------------------ |\n| `__namedParameters`             | `Object`                              |\n| `__namedParameters.style?`      | `CSSProperties`                       |\n| `__namedParameters.tabTitles`   | `string`[]                            |\n| `__namedParameters.tabContents` | (`tabIndex`: `number`) => `ReactNode` |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Table.md",
    "content": "# Module: Frontend/Views/Table\n\n## Table of contents\n\n### Functions\n\n- [Table](Frontend_Views_Table.md#table)\n\n## Functions\n\n### Table\n\n▸ **Table**<`T`\\>(`__namedParameters`): `Element`\n\nReact api for creating tables.\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n\n#### Parameters\n\n| Name                             | Type                                       |\n| :------------------------------- | :----------------------------------------- |\n| `__namedParameters`              | `Object`                                   |\n| `__namedParameters.alignments?`  | (`\"r\"` \\| `\"l\"` \\| `\"c\"`)[]                |\n| `__namedParameters.columns`      | (`t`: `T`, `i`: `number`) => `ReactNode`[] |\n| `__namedParameters.headerStyle?` | `CSSProperties`                            |\n| `__namedParameters.headers`      | `ReactNode`[]                              |\n| `__namedParameters.paginated?`   | `boolean`                                  |\n| `__namedParameters.rows`         | `T`[]                                      |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_Terminal.md",
    "content": "# Module: Frontend/Views/Terminal\n\n## Table of contents\n\n### Interfaces\n\n- [TerminalHandle](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\n- [TerminalProps](../interfaces/Frontend_Views_Terminal.TerminalProps.md)\n\n### Variables\n\n- [Terminal](Frontend_Views_Terminal.md#terminal)\n\n## Variables\n\n### Terminal\n\n• `Const` **Terminal**: `ForwardRefExoticComponent`<[`TerminalProps`](../interfaces/Frontend_Views_Terminal.TerminalProps.md) & `RefAttributes`<`undefined` \\| [`TerminalHandle`](../interfaces/Frontend_Views_Terminal.TerminalHandle.md)\\>\\>\n"
  },
  {
    "path": "docs/modules/Frontend_Views_TopBar.md",
    "content": "# Module: Frontend/Views/TopBar\n\n## Table of contents\n\n### Functions\n\n- [TopBar](Frontend_Views_TopBar.md#topbar)\n\n## Functions\n\n### TopBar\n\n▸ **TopBar**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                                  | Type                                                   |\n| :------------------------------------ | :----------------------------------------------------- |\n| `__namedParameters`                   | `Object`                                               |\n| `__namedParameters.twitterVerifyHook` | [`Hook`](types_global_GlobalTypes.md#hook)<`boolean`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_UpgradePreview.md",
    "content": "# Module: Frontend/Views/UpgradePreview\n\n## Table of contents\n\n### Functions\n\n- [UpgradePreview](Frontend_Views_UpgradePreview.md#upgradepreview)\n\n## Functions\n\n### UpgradePreview\n\n▸ **UpgradePreview**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                            | Type                               |\n| :------------------------------ | :--------------------------------- |\n| `__namedParameters`             | `Object`                           |\n| `__namedParameters.branchName`  | `undefined` \\| `UpgradeBranchName` |\n| `__namedParameters.cantUpgrade` | `boolean`                          |\n| `__namedParameters.planet`      | `undefined` \\| `Planet`            |\n| `__namedParameters.upgrade`     | `undefined` \\| `Upgrade`           |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/Frontend_Views_WithdrawSilver.md",
    "content": "# Module: Frontend/Views/WithdrawSilver\n\n## Table of contents\n\n### Functions\n\n- [WithdrawSilver](Frontend_Views_WithdrawSilver.md#withdrawsilver)\n\n## Functions\n\n### WithdrawSilver\n\n▸ **WithdrawSilver**(`__namedParameters`): `Element`\n\n#### Parameters\n\n| Name                        | Type                                                                               |\n| :-------------------------- | :--------------------------------------------------------------------------------- |\n| `__namedParameters`         | `Object`                                                                           |\n| `__namedParameters.wrapper` | [`Wrapper`](../classes/Backend_Utils_Wrapper.Wrapper.md)<`undefined` \\| `Planet`\\> |\n\n#### Returns\n\n`Element`\n"
  },
  {
    "path": "docs/modules/types_darkforest_api_ChunkStoreTypes.md",
    "content": "# Module: \\_types/darkforest/api/ChunkStoreTypes\n\n## Table of contents\n\n### Interfaces\n\n- [ChunkStore](../interfaces/types_darkforest_api_ChunkStoreTypes.ChunkStore.md)\n- [PersistedChunk](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedChunk.md)\n- [PersistedLocation](../interfaces/types_darkforest_api_ChunkStoreTypes.PersistedLocation.md)\n\n### Type aliases\n\n- [BucketId](types_darkforest_api_ChunkStoreTypes.md#bucketid)\n- [ChunkId](types_darkforest_api_ChunkStoreTypes.md#chunkid)\n\n## Type aliases\n\n### BucketId\n\nƬ **BucketId**: `Abstract`<`string`, `\"BucketId\"`\\>\n\none of \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n\n---\n\n### ChunkId\n\nƬ **ChunkId**: `Abstract`<`string`, `\"ChunkId\"`\\>\n\nDon't worry about the values here. Never base code off the values here. PLEASE.\n"
  },
  {
    "path": "docs/modules/types_darkforest_api_ContractsAPITypes.md",
    "content": "# Module: \\_types/darkforest/api/ContractsAPITypes\n\n## Table of contents\n\n### Enumerations\n\n- [ContractEvent](../enums/types_darkforest_api_ContractsAPITypes.ContractEvent.md)\n- [ContractsAPIEvent](../enums/types_darkforest_api_ContractsAPITypes.ContractsAPIEvent.md)\n- [InitArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.InitArgIdxs.md)\n- [MoveArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.MoveArgIdxs.md)\n- [PlanetEventType](../enums/types_darkforest_api_ContractsAPITypes.PlanetEventType.md)\n- [UpgradeArgIdxs](../enums/types_darkforest_api_ContractsAPITypes.UpgradeArgIdxs.md)\n- [ZKArgIdx](../enums/types_darkforest_api_ContractsAPITypes.ZKArgIdx.md)\n\n### Interfaces\n\n- [ContractConstants](../interfaces/types_darkforest_api_ContractsAPITypes.ContractConstants.md)\n\n### Type aliases\n\n- [ClaimArgs](types_darkforest_api_ContractsAPITypes.md#claimargs)\n- [ClientMockchainData](types_darkforest_api_ContractsAPITypes.md#clientmockchaindata)\n- [DepositArtifactArgs](types_darkforest_api_ContractsAPITypes.md#depositartifactargs)\n- [MoveArgs](types_darkforest_api_ContractsAPITypes.md#moveargs)\n- [PlanetTypeWeights](types_darkforest_api_ContractsAPITypes.md#planettypeweights)\n- [PlanetTypeWeightsByLevel](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel)\n- [PlanetTypeWeightsBySpaceType](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbyspacetype)\n- [UpgradeArgs](types_darkforest_api_ContractsAPITypes.md#upgradeargs)\n- [WhitelistArgs](types_darkforest_api_ContractsAPITypes.md#whitelistargs)\n- [WithdrawArtifactArgs](types_darkforest_api_ContractsAPITypes.md#withdrawartifactargs)\n\n## Type aliases\n\n### ClaimArgs\n\nƬ **ClaimArgs**: [[`string`, `string`], [[`string`, `string`], [`string`, `string`]], [`string`, `string`], [`string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`]]\n\n---\n\n### ClientMockchainData\n\nƬ **ClientMockchainData**: `null` \\| `undefined` \\| `number` \\| `string` \\| `boolean` \\| `EthersBN` \\| [`ClientMockchainData`](types_darkforest_api_ContractsAPITypes.md#clientmockchaindata)[] \\| { [key in string \\| number]: ClientMockchainData }\n\n---\n\n### DepositArtifactArgs\n\nƬ **DepositArtifactArgs**: [`string`, `string`]\n\n---\n\n### MoveArgs\n\nƬ **MoveArgs**: [[`string`, `string`], [[`string`, `string`], [`string`, `string`]], [`string`, `string`], [`string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`, `string`]]\n\n---\n\n### PlanetTypeWeights\n\nƬ **PlanetTypeWeights**: [`number`, `number`, `number`, `number`, `number`]\n\n---\n\n### PlanetTypeWeightsByLevel\n\nƬ **PlanetTypeWeightsByLevel**: [[`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights), [`PlanetTypeWeights`](types_darkforest_api_ContractsAPITypes.md#planettypeweights)]\n\n---\n\n### PlanetTypeWeightsBySpaceType\n\nƬ **PlanetTypeWeightsBySpaceType**: [[`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel), [`PlanetTypeWeightsByLevel`](types_darkforest_api_ContractsAPITypes.md#planettypeweightsbylevel)]\n\n---\n\n### UpgradeArgs\n\nƬ **UpgradeArgs**: [`string`, `string`]\n\n---\n\n### WhitelistArgs\n\nƬ **WhitelistArgs**: [`string`, `string`]\n\n---\n\n### WithdrawArtifactArgs\n\nƬ **WithdrawArtifactArgs**: [`string`, `string`]\n"
  },
  {
    "path": "docs/modules/types_darkforest_api_UtilityServerAPITypes.md",
    "content": "# Module: \\_types/darkforest/api/UtilityServerAPITypes\n\n## Table of contents\n\n### Type aliases\n\n- [AddressTwitterMap](types_darkforest_api_UtilityServerAPITypes.md#addresstwittermap)\n\n## Type aliases\n\n### AddressTwitterMap\n\nƬ **AddressTwitterMap**: `Object`\n\n#### Index signature\n\n▪ [ethAddress: `string`]: `string`\n"
  },
  {
    "path": "docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md",
    "content": "# Namespace: \"@darkforest_eth/contracts/abis/\\*.json\"\n\n[\\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md).\"@darkforest_eth/contracts/abis/\\*.json\"\n\n## Table of contents\n\n### Variables\n\n- [default](types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md#default)\n\n## Variables\n\n### default\n\n• `Const` **default**: `string`\n"
  },
  {
    "path": "docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md",
    "content": "# Namespace: \"@darkforest_eth/snarks/\\*.wasm\"\n\n[\\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md).\"@darkforest_eth/snarks/\\*.wasm\"\n\n## Table of contents\n\n### Variables\n\n- [default](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md#default)\n\n## Variables\n\n### default\n\n• `Const` **default**: `string`\n"
  },
  {
    "path": "docs/modules/types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md",
    "content": "# Namespace: \"@darkforest_eth/snarks/\\*.zkey\"\n\n[\\_types/file-loader/FileWorkerTypes](types_file_loader_FileWorkerTypes.md).\"@darkforest_eth/snarks/\\*.zkey\"\n\n## Table of contents\n\n### Variables\n\n- [default](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md#default)\n\n## Variables\n\n### default\n\n• `Const` **default**: `string`\n"
  },
  {
    "path": "docs/modules/types_file_loader_FileWorkerTypes.md",
    "content": "# Module: \\_types/file-loader/FileWorkerTypes\n\n## Table of contents\n\n### Namespaces\n\n- [&quot;@darkforest_eth/contracts/abis/\\*.json&quot;](types_file_loader_FileWorkerTypes.__darkforest_eth_contracts_abis___json_.md)\n- [&quot;@darkforest_eth/snarks/\\*.wasm&quot;](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___wasm_.md)\n- [&quot;@darkforest_eth/snarks/\\*.zkey&quot;](types_file_loader_FileWorkerTypes.__darkforest_eth_snarks___zkey_.md)\n"
  },
  {
    "path": "docs/modules/types_global_GlobalTypes.md",
    "content": "# Module: \\_types/global/GlobalTypes\n\n## Table of contents\n\n### Enumerations\n\n- [StatIdx](../enums/types_global_GlobalTypes.StatIdx.md)\n\n### Interfaces\n\n- [ClaimCountdownInfo](../interfaces/types_global_GlobalTypes.ClaimCountdownInfo.md)\n- [MinerWorkerMessage](../interfaces/types_global_GlobalTypes.MinerWorkerMessage.md)\n- [RevealCountdownInfo](../interfaces/types_global_GlobalTypes.RevealCountdownInfo.md)\n\n### Type aliases\n\n- [HashConfig](types_global_GlobalTypes.md#hashconfig)\n- [Hook](types_global_GlobalTypes.md#hook)\n\n## Type aliases\n\n### HashConfig\n\nƬ **HashConfig**: `Object`\n\n#### Type declaration\n\n| Name                | Type      |\n| :------------------ | :-------- |\n| `biomebaseKey`      | `number`  |\n| `perlinLengthScale` | `number`  |\n| `perlinMirrorX`     | `boolean` |\n| `perlinMirrorY`     | `boolean` |\n| `planetHashKey`     | `number`  |\n| `planetRarity`      | `number`  |\n| `spaceTypeKey`      | `number`  |\n\n---\n\n### Hook\n\nƬ **Hook**<`T`\\>: [`T`, `Dispatch`<`SetStateAction`<`T`\\>\\>]\n\n#### Type parameters\n\n| Name |\n| :--- |\n| `T`  |\n"
  },
  {
    "path": "embedded_plugins/Admin-Controls.ts",
    "content": "// organize-imports-ignore\nimport type { EthAddress, LocatablePlanet, LocationId, Planet } from '@darkforest_eth/types';\nimport {\n  MAX_ARTIFACT_RARITY,\n  MAX_SPACESHIP_TYPE,\n  MIN_ARTIFACT_RARITY,\n  MIN_ARTIFACT_TYPE,\n  MIN_SPACESHIP_TYPE,\n  MIN_BIOME,\n  MAX_BIOME,\n  //@ts-ignore\n} from 'https://cdn.skypack.dev/@darkforest_eth/constants';\n//@ts-ignore\nimport { getPlanetNameHash } from 'https://cdn.skypack.dev/@darkforest_eth/procedural';\nimport {\n  locationIdToDecStr,\n  artifactIdFromHexStr,\n  locationIdFromDecStr,\n  //@ts-ignore\n} from 'https://cdn.skypack.dev/@darkforest_eth/serde';\nimport {\n  ArtifactRarityNames,\n  ArtifactType,\n  ArtifactTypeNames,\n  BiomeNames,\n  Player,\n  PlanetType,\n  PlanetTypeNames,\n  WorldCoords,\n  //@ts-ignore\n} from 'https://cdn.skypack.dev/@darkforest_eth/types';\nimport {\n  html,\n  render,\n  useEffect,\n  useState,\n  useCallback,\n  //@ts-ignore\n} from 'https://unpkg.com/htm/preact/standalone.module.js';\n\nfunction random256Id() {\n  const alphabet = '0123456789ABCDEF'.split('');\n  let result = '0x';\n  for (let i = 0; i < 256 / 4; i++) {\n    result += alphabet[Math.floor(Math.random() * alphabet.length)];\n  }\n  return result;\n}\n\nasync function createArtifact(\n  owner: EthAddress,\n  type: ArtifactType,\n  planet: Planet,\n  rarity: string,\n  biome: string\n) {\n  if (!owner) {\n    alert('no account');\n    return;\n  }\n\n  const tokenId = random256Id();\n  // see contracts/types/ActionTypes.sol - CreateArtifactArgs\n  const args = Promise.resolve([\n    {\n      tokenId,\n      discoverer: owner,\n      planetId: locationIdToDecStr(planet.locationId),\n      rarity,\n      biome,\n      artifactType: type,\n      owner: owner,\n      controller: '0x0000000000000000000000000000000000000000',\n    },\n  ]);\n\n  const tx = await df.submitTransaction({\n    args,\n    contract: df.getContract(),\n    methodName: 'adminGiveArtifact',\n  });\n  tx.confirmedPromise.then(() => {\n    df.hardRefreshArtifact(artifactIdFromHexStr(tokenId.slice(2)));\n    df.hardRefreshPlanet(planet.locationId);\n  });\n\n  return tx;\n}\n\nasync function initPlanet(planet: LocatablePlanet) {\n  if (planet.isInContract) return;\n\n  const args = Promise.resolve([locationIdToDecStr(planet.locationId), planet.perlin]);\n\n  const tx = await df.submitTransaction({\n    args,\n    contract: df.getContract(),\n    methodName: 'adminInitializePlanet',\n  });\n\n  await tx.confirmedPromise;\n\n  return tx;\n}\n\nasync function spawnSpaceship(\n  planet: LocatablePlanet | undefined,\n  owner: EthAddress | undefined,\n  shipType: ArtifactType\n) {\n  if (!owner) {\n    alert('no account');\n    return;\n  }\n\n  if (!planet) {\n    alert('no selected planet');\n    return;\n  }\n\n  await initPlanet(planet);\n\n  const args = Promise.resolve([locationIdToDecStr(planet.locationId), owner, shipType]);\n\n  const tx = await df.submitTransaction({\n    args,\n    contract: df.getContract(),\n    methodName: 'adminGiveSpaceShip',\n  });\n\n  tx.confirmedPromise.then(() => df.hardRefreshPlanet(planet.locationId));\n\n  return tx;\n}\n\nasync function takeOwnership(\n  planet: LocatablePlanet | undefined,\n  newOwner: EthAddress | undefined\n) {\n  if (!newOwner) {\n    alert('no account');\n    return;\n  }\n\n  if (!planet) {\n    alert('no selected planet');\n    return;\n  }\n\n  const snarkArgs = await df.getSnarkHelper().getInitArgs(\n    planet.location.coords.x,\n    planet.location.coords.y,\n    Math.floor(Math.sqrt(planet.location.coords.x ** 2 + planet.location.coords.y ** 2)) + 1 // floor(sqrt(x^2 + y^2)) + 1\n  );\n\n  const args = Promise.resolve([newOwner, ...snarkArgs]);\n\n  const tx = await df.submitTransaction({\n    locationId: planet.locationId,\n    newOwner,\n    args,\n    contract: df.getContract(),\n    methodName: 'safeSetOwner',\n  });\n\n  tx.confirmedPromise.then(() => df.hardRefreshPlanet(planet.locationId));\n\n  return tx;\n}\n\nasync function pauseGame() {\n  const tx = await df.submitTransaction({\n    args: Promise.resolve([]),\n    contract: df.getContract(),\n    methodName: 'pause',\n  });\n\n  return tx;\n}\n\nasync function unpauseGame() {\n  const tx = await df.submitTransaction({\n    args: Promise.resolve([]),\n    contract: df.getContract(),\n    methodName: 'unpause',\n  });\n\n  return tx;\n}\n\nasync function addAddressToWhitelist(address: EthAddress) {\n  const args = Promise.resolve([address]);\n\n  const tx = await df.submitTransaction({\n    args,\n    contract: df.getContract(),\n    methodName: 'addToWhitelist',\n  });\n\n  return tx;\n}\n\nasync function createPlanet(coords: WorldCoords, level: number, type: PlanetType) {\n  coords.x = Math.round(coords.x);\n  coords.y = Math.round(coords.y);\n\n  const location = df.locationBigIntFromCoords(coords).toString();\n  const perlinValue = df.biomebasePerlin(coords, true);\n\n  const args = Promise.resolve([\n    {\n      x: coords.x,\n      y: coords.y,\n      level,\n      planetType: type,\n      requireValidLocationId: false,\n      location: location,\n      perlin: perlinValue,\n    },\n  ]);\n\n  const tx = await df.submitTransaction({\n    args,\n    contract: df.getContract(),\n    methodName: 'createPlanet',\n  });\n\n  await tx.confirmedPromise;\n\n  const revealArgs = df.getSnarkHelper().getRevealArgs(coords.x, coords.y);\n  const revealTx = await df.submitTransaction({\n    args: revealArgs,\n    contract: df.getContract(),\n    methodName: 'revealLocation',\n  });\n\n  await revealTx.confirmedPromise;\n\n  await df.hardRefreshPlanet(locationIdFromDecStr(location));\n}\n\nfunction PlanetLink({ planetId }: { planetId?: LocationId }) {\n  if (planetId) {\n    return html`<a\n      style=${{ cursor: 'pointer', textDecoration: 'underline', color: '#00ADE1' }}\n      onClick=${() => ui.centerLocationId(planetId)}\n    >\n      ${getPlanetNameHash(planetId)}\n    </a>`;\n  } else {\n    return '(none selected)';\n  }\n}\n\nfunction Heading({ title }: { title: string }) {\n  return html`<h2 style=${{ fontSize: '14pt', textDecoration: 'underline' }}>${title}</h2>`;\n}\n\nfunction shipOptions() {\n  const options = [] as HTMLOptionElement[];\n  for (let i = MIN_SPACESHIP_TYPE; i <= MAX_SPACESHIP_TYPE; i++) {\n    options.push(html`<option value=${i}>${ArtifactTypeNames[i]}</option>`);\n  }\n  return options;\n}\n\nfunction artifactOptions() {\n  const options = [] as HTMLOptionElement[];\n  for (let i = MIN_ARTIFACT_TYPE; i < MIN_SPACESHIP_TYPE; i++) {\n    options.push(html`<option value=${i}>${ArtifactTypeNames[i]}</option>`);\n  }\n  return options;\n}\n\nfunction artifactRarityOptions() {\n  const options = [] as HTMLOptionElement[];\n  for (let i = MIN_ARTIFACT_RARITY; i <= MAX_ARTIFACT_RARITY; i++) {\n    options.push(html`<option value=${i}>${ArtifactRarityNames[i]}</option>`);\n  }\n  return options;\n}\n\nfunction artifactBiomeOptions() {\n  const options = [] as HTMLOptionElement[];\n  for (let i = MIN_BIOME; i <= MAX_BIOME; i++) {\n    options.push(html`<option value=${i}>${BiomeNames[i]}</option>`);\n  }\n  return options;\n}\n\nfunction accountOptions(players: Player[]) {\n  const options = [] as HTMLOptionElement[];\n  for (const player of players) {\n    options.push(\n      html`<option value=${player.address}>${player.twitter || player.address}</option>`\n    );\n  }\n  return options;\n}\nfunction planetTypeOptions() {\n  const options = [] as HTMLOptionElement[];\n  for (let i = 0; i <= Object.values(PlanetType).length - 1; i++) {\n    options.push(html`<option value=${i}>${PlanetTypeNames[i]}</option>`);\n  }\n  return options;\n}\n\nfunction Select({\n  style,\n  value,\n  onChange,\n  items,\n}: {\n  style: Record<string, string>;\n  value: string;\n  onChange: (e: InputEvent) => void;\n  items: unknown[];\n}) {\n  return html`\n    <select\n      style=${{\n        ...style,\n        outline: 'none',\n        background: '#151515',\n        color: '#838383',\n        borderRadius: '4px',\n        border: '1px solid #777',\n        width: '100%',\n        padding: '2px 6px',\n        cursor: 'pointer',\n      }}\n      value=${value}\n      onChange=${onChange}\n    >\n      ${items}\n    </select>\n  `;\n}\n\nconst wrapperStyle = {\n  display: 'flex',\n  flexDirection: 'column',\n  gap: '8px',\n};\n\nconst rowStyle = {\n  display: 'flex',\n  gap: '8px',\n  alignItems: 'center',\n};\n\nfunction PlanetCreator() {\n  const uiEmitter = ui.getUIEmitter();\n\n  const [level, setLevel] = useState(0);\n  const [planetType, setPlanetType] = useState(PlanetType.PLANET);\n  const [choosingLocation, setChoosingLocation] = useState(false);\n  const [planetCoords, setPlanetCoords] = useState(null);\n\n  const placePlanet = useCallback(\n    (coords: WorldCoords) => {\n      createPlanet(coords, parseInt(level), planetType);\n      setChoosingLocation(false);\n    },\n    [level, planetType, setChoosingLocation]\n  );\n\n  const updatePlanetCoords = useCallback(\n    (coords: WorldCoords) => {\n      setPlanetCoords(coords);\n    },\n    [setPlanetCoords]\n  );\n\n  useEffect(() => {\n    if (choosingLocation) {\n      uiEmitter.on('WorldMouseClick', placePlanet);\n      uiEmitter.on('WorldMouseMove', updatePlanetCoords);\n\n      return () => {\n        uiEmitter.off('WorldMouseClick', placePlanet);\n        uiEmitter.off('WorldMouseMove', updatePlanetCoords);\n      };\n    }\n\n    return () => {};\n  }, [uiEmitter, choosingLocation, placePlanet, updatePlanetCoords]);\n\n  return html`\n    <div style=${{ width: '100%' }}>\n      <h2>Create Planet</h2>\n      <div style=${rowStyle}>\n        <df-slider\n          label=\"Planet Level\"\n          value=${level}\n          onChange=${(e: InputEvent) => setLevel((e.target as HTMLInputElement).value)}\n          max=${9}\n        ></df-slider>\n        <div>\n          <label for=\"planet-type-selector\">Planet Type</label>\n          <${Select}\n            id=\"planet-type-selector\"\n            value=${planetType}\n            onChange=${(e: InputEvent) => setPlanetType((e.target as HTMLSelectElement).value)}\n            items=${planetTypeOptions()}\n          />\n        </div>\n      </div>\n      <div style=${{ ...rowStyle, justifyContent: 'space-between' }}>\n        ${!choosingLocation &&\n        html`\n          <df-button\n            onClick=${() => {\n              setChoosingLocation(true);\n            }}\n          >\n            Choose Planet Location\n          </df-button>\n        `}\n        ${choosingLocation &&\n        html` <p>\n          Creating planet on coords <br />\n          (${Math.round(planetCoords?.x)}, ${Math.round(planetCoords?.y)})\n        </p>`}\n        ${choosingLocation &&\n        html`<df-button onClick=${() => setChoosingLocation(false)}> Cancel Creation</df-button>`}\n      </div>\n    </div>\n  `;\n}\n\nfunction App() {\n  const [selectedPlanet, setSelectedPlanet] = useState(null);\n  const [selectedShip, setSelectedShip] = useState(MIN_SPACESHIP_TYPE);\n  const [selectedArtifact, setSelectedArtifact] = useState(MIN_ARTIFACT_TYPE);\n  const [artifactRarity, setArtifactRarity] = useState('1');\n  const [artifactBiome, setArtifactBiome] = useState(MIN_BIOME.toString());\n  const [whitelistAddress, setWhitelistAddress] = useState(null);\n  const [account, setAccount] = useState(null);\n  const [targetAccount, setTargetAccount] = useState(null);\n  const [allPlayers, setAllPlayers] = useState([]);\n\n  useEffect(() => {\n    const account = df.getAccount();\n    setAccount(account);\n    setTargetAccount(account);\n  }, []);\n\n  useEffect(() => {\n    const refreshPlayers = () => {\n      setAllPlayers(df.getAllPlayers());\n    };\n\n    const sub = df.playersUpdated$.subscribe(refreshPlayers);\n    refreshPlayers();\n\n    return () => sub.unsubscribe();\n  }, []);\n\n  useEffect(() => {\n    const subscription = ui.selectedPlanetId$.subscribe((p: LocationId) => {\n      setSelectedPlanet(ui.getPlanetWithId(p));\n    });\n\n    return () => subscription.unsubscribe();\n  }, [setSelectedPlanet]);\n\n  return html`\n    <div style=${wrapperStyle}>\n      <p>Logged in as account: ${account}</p>\n\n      <${Heading} title=\"Game state\" />\n\n      <div style=${rowStyle}>\n        <span>Change game state:</span>\n        <df-button onClick=${() => pauseGame()}> Pause </df-button>\n        <df-button onClick=${() => unpauseGame()}> Unpause </df-button>\n      </div>\n\n      <${Heading} title=\"Whitelist players\" />\n\n      <div style=${rowStyle}>\n        <df-text-input\n          style=${{ flex: '1' }}\n          value=${whitelistAddress}\n          onInput=${(e: InputEvent) => setWhitelistAddress((e.target as HTMLInputElement).value)}\n          placeholder=\"Address to whitelist\"\n        ></df-text-input>\n        <df-button onClick=${() => addAddressToWhitelist(whitelistAddress)}>\n          Whitelist Address\n        </df-button>\n      </div>\n\n      <${Heading} title=\"Give Planets\" />\n\n      <div style=${rowStyle}>\n        <span> Planet: <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} /> </span>\n        <span> to </span>\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${targetAccount}\n          onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)}\n          items=${accountOptions(allPlayers)}\n        />\n        <df-button onClick=${() => takeOwnership(selectedPlanet, targetAccount)}>\n          Give Planet\n        </df-button>\n      </div>\n\n      <${Heading} title=\"Give Spaceships\" />\n\n      <div style=${rowStyle}>\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${selectedShip}\n          onChange=${(e: InputEvent) => setSelectedShip((e.target as HTMLSelectElement).value)}\n          items=${shipOptions()}\n        />\n\n        <span> to </span>\n\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${targetAccount}\n          onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)}\n          items=${accountOptions(allPlayers)}\n        />\n      </div>\n\n      <div style=${{ ...rowStyle, justifyContent: 'space-between' }}>\n        <span>\n          ${'On planet: '}\n          <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} />\n        </span>\n\n        <df-button onClick=${() => spawnSpaceship(selectedPlanet, targetAccount, selectedShip)}>\n          Spawn Spaceship\n        </df-button>\n      </div>\n\n      <${Heading} title=\"Give Artifacts\" />\n\n      <div style=${rowStyle}>\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${artifactRarity}\n          onChange=${(e: InputEvent) => setArtifactRarity((e.target as HTMLSelectElement).value)}\n          items=${artifactRarityOptions()}\n        />\n\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${artifactBiome}\n          onChange=${(e: InputEvent) => setArtifactBiome((e.target as HTMLSelectElement).value)}\n          items=${artifactBiomeOptions()}\n        />\n\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${selectedArtifact}\n          onChange=${(e: InputEvent) => setSelectedArtifact((e.target as HTMLSelectElement).value)}\n          items=${artifactOptions()}\n        />\n\n        <span> to </span>\n\n        <${Select}\n          style=${{ flex: '1' }}\n          value=${targetAccount}\n          onChange=${(e: InputEvent) => setTargetAccount((e.target as HTMLSelectElement).value)}\n          items=${accountOptions(allPlayers)}\n        />\n      </div>\n\n      <div style=${{ ...rowStyle, justifyContent: 'space-between' }}>\n        <span>\n          ${'On planet: '}\n          <${PlanetLink} planetId=${(selectedPlanet as Planet)?.locationId} />\n        </span>\n\n        <df-button\n          onClick=${() =>\n            createArtifact(\n              targetAccount,\n              selectedArtifact,\n              selectedPlanet,\n              artifactRarity,\n              artifactBiome\n            )}\n        >\n          Give Artifact\n        </df-button>\n      </div>\n\n      <div style=${rowStyle}>\n        <${PlanetCreator} />\n      </div>\n    </div>\n  `;\n}\n\nclass Plugin implements DFPlugin {\n  async render(container: HTMLDivElement) {\n    container.style.width = '525px';\n\n    render(html`<${App} />`, container);\n  }\n}\n\nexport default Plugin;\n"
  },
  {
    "path": "embedded_plugins/Getting-Started.ts",
    "content": "/**\n * Hi there!\n *\n * Looks like you've found the Dark Forest plugins system.\n * Read through this script to learn how to write plugins!\n *\n * Most importantly, you have access these globals:\n * 1. df - Just like the df object in your console.\n * 2. ui - For interacting with the game's user interface.\n *\n * Let's log these to the console when you run your plugin!\n */\nconsole.log(df, ui);\n\n/**\n * Plugins are just TypeScript (or modern JavaScript, if you prefer), so you can use imports, too!\n */\n// @ts-ignore\nimport confetti from 'https://cdn.skypack.dev/canvas-confetti';\n\n/**\n * A plugin is a Class with render and destroy methods.\n * Other than that, you are free to do whatever, so be careful!\n */\nclass Readme implements DFPlugin {\n  private canvas: HTMLCanvasElement;\n\n  /**\n   * A constructor can be used to keep track of information.\n   */\n  constructor() {\n    this.canvas = document.createElement('canvas');\n    this.canvas.width = 400;\n    this.canvas.height = 150;\n  }\n\n  /**\n   * A plugin's render function is called once.\n   * Here, you can insert custom html into a game modal.\n   * You render any sort of UI that makes sense for the plugin!\n   */\n  async render(div: HTMLDivElement) {\n    div.style.width = '400px';\n\n    const firstTextDiv = document.createElement('div');\n    firstTextDiv.innerText =\n      'This is an example plugin. Check out its source by' +\n      ' clicking \"edit\" button that is to the right of the' +\n      ' README plugin in the Plugin Manager modal! ';\n\n    const secondTextDiv = document.createElement('div');\n    secondTextDiv.innerText = '... Or, click the button below to get a free artifact!';\n\n    const myButton = document.createElement('df-button');\n    myButton.innerText = 'give me an artifact';\n    myButton.addEventListener('click', async () => {\n      await confetti.create(this.canvas)({\n        origin: { x: 0.5, y: 1 },\n      });\n      const ctx = this.canvas.getContext('2d');\n      if (ctx) {\n        ctx.fillStyle = 'white';\n        ctx.font = '20px Sans-serif';\n        ctx.fillText('Gotcha!', 150, 60);\n      }\n    });\n\n    div.appendChild(firstTextDiv);\n    div.appendChild(document.createElement('br'));\n    div.appendChild(secondTextDiv);\n    div.appendChild(document.createElement('br'));\n    div.appendChild(this.canvas);\n    div.appendChild(myButton);\n  }\n\n  /**\n   * When this is unloaded, the game calls the destroy method.\n   * So you can clean up everything nicely!\n   */\n  destroy() {\n    const ctx = this.canvas.getContext('2d');\n    if (ctx) {\n      ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);\n    }\n  }\n}\n\n/**\n * For the game to know about your plugin, you must export it!\n *\n * Use `export default` to expose your plugin Class.\n */\nexport default Readme;\n"
  },
  {
    "path": "embedded_plugins/Locate-Artifacts.ts",
    "content": "/**\n * Remember, you have access these globals:\n * 1. df - Just like the df object in your console.\n * 2. ui - For interacting with the game's user interface.\n *\n * Let's log these to the console when you run your plugin!\n */\nconsole.log(df, ui);\n\nclass ArtifactsFinder implements DFPlugin {\n  private planetList: HTMLDivElement;\n\n  renderPlanets = () => {\n    this.planetList.innerHTML = '';\n\n    // keep track of how many we have found\n    let count = 0;\n\n    const countText = document.createElement('div');\n    this.planetList.appendChild(countText);\n\n    for (const planet of df.getAllPlanets()) {\n      // @ts-ignore\n      if (planet.location) {\n        if (df.isPlanetMineable(planet)) {\n          // is there any other filtering you'd want to do?\n          // sometimes planets have artifacts deposited on them!\n          // somtimes a planet's artifact has already been mined.\n          // see if you can modify this plugin to make it do what\n          // you want!\n          const planetEntry = document.createElement('div');\n          this.planetList.appendChild(planetEntry);\n\n          // hint: have a hard time finding planets?\n          // ui.centerCoords might help...\n          planetEntry.innerText =\n            '(' +\n            // @ts-ignore\n            planet.location.coords.x +\n            ', ' +\n            // @ts-ignore\n            planet.location.coords.y +\n            ')';\n\n          count++;\n        }\n      }\n    }\n\n    if (count === 0) {\n      countText.innerText = 'you have not found any artifacts yet';\n    } else {\n      countText.innerText = 'you have found ' + count + ' artifacts';\n    }\n  };\n\n  async render(container: HTMLDivElement) {\n    console.log('rendered 1 artifacts finder');\n    const findArtifactsButton = document.createElement('df-button');\n    findArtifactsButton.innerText = 'find me some artifacts!';\n    container.appendChild(findArtifactsButton);\n    container.appendChild(document.createElement('br'));\n    container.appendChild(document.createElement('br'));\n    findArtifactsButton.addEventListener('click', this.renderPlanets);\n    this.planetList = document.createElement('div');\n    container.appendChild(this.planetList);\n\n    this.planetList.style.maxHeight = '300px';\n    this.planetList.style.width = '400px';\n    this.planetList.style.overflowX = 'hidden';\n    this.planetList.style.overflowY = 'scroll';\n\n    console.log('rendered artifacts finder');\n  }\n}\n\n/**\n * And don't forget to export it!\n */\nexport default ArtifactsFinder;\n"
  },
  {
    "path": "embedded_plugins/Rage-Cage.ts",
    "content": "class RageCage implements DFPlugin {\n  private img: HTMLImageElement;\n  private loaded: boolean;\n\n  /**\n   * As you saw in the README plugin, you can render\n   * arbitrary HTML UI into a Dark Forest modal.\n   */\n  async render(div: HTMLDivElement) {\n    // once this plugin is run, let's load a nice image\n    // from the internet, in order to draw it on top of\n    // planets\n    this.img = document.createElement('img');\n    this.loaded = false;\n    div.appendChild(this.img);\n\n    this.img.addEventListener('load', () => {\n      // we should only use the image once\n      // it actually loads.\n      this.loaded = true;\n      div.innerText = 'welcome to nicolas cage world';\n    });\n\n    this.img.src =\n      'https://upload.wikimedia.org/wikipedia/' + 'commons/c/c0/Nicolas_Cage_Deauville_2013.jpg';\n\n    // hide the image, it doesn't need to show up\n    // in the modal, we only need this img element\n    // to load the image.\n    this.img.style.display = 'none';\n\n    div.style.width = '100px';\n    div.style.height = '100px';\n    div.innerText = 'loading, please wait!';\n    // check out the helpful functions that appear\n    // in the Viewport class!\n    console.log(ui.getViewport());\n  }\n\n  /**\n   * In addition to rendering HTML UI into a div, plugins\n   * can draw directly onto the game UI. This function is\n   * optional, but if it exists, it is called in sync with\n   * the rest of the game, and allows you to draw onto an\n   * HTML5 canvas that lays on top of the rest of the game.\n   *\n   * In the example below, we render an image on top of every\n   * planet.\n   *\n   * ctx is an instance of CanvasRenderingContext2D.\n   */\n  draw(ctx: CanvasRenderingContext2D) {\n    // don't draw anything until nic cage loads\n    if (!this.loaded) return;\n\n    // the viewport class provides helpful functions for\n    // interacting with the currently-visible area of the\n    // game\n    const viewport = ui.getViewport();\n    const planets = ui.getPlanetsInViewport();\n\n    for (const p of planets) {\n      // use the Viewport class to determine the pixel\n      // coordinates of the planet on the screen\n      const pixelCenter = viewport.worldToCanvasCoords(\n        // @ts-ignore\n        p.location.coords\n      );\n\n      // how many pixels is the radius of the planet?\n      const trueRadius = viewport.worldToCanvasDist(ui.getRadiusOfPlanetLevel(p.planetLevel));\n\n      // draw nicolas cage on top of the planet\n      ctx.drawImage(\n        this.img,\n        50,\n        50,\n        400,\n        400,\n        pixelCenter.x - trueRadius,\n        pixelCenter.y - trueRadius,\n        trueRadius * 2,\n        trueRadius * 2\n      );\n    }\n  }\n}\n\nexport default RageCage;\n"
  },
  {
    "path": "embedded_plugins/Remote-Explorer.ts",
    "content": "// organize-imports-ignore\nimport type { Chunk, WorldCoords } from '@darkforest_eth/types';\n//@ts-ignore\nimport { locationIdFromDecStr } from 'https://cdn.skypack.dev/@darkforest_eth/serde';\nimport {\n  html,\n  render,\n  useEffect,\n  useState,\n  //@ts-ignore\n} from 'https://unpkg.com/htm/preact/standalone.module.js';\nimport type MinerManager from '../src/Backend/Miner/MinerManager';\nimport type { MinerWorkerMessage } from '../src/_types/global/GlobalTypes';\n\ntype ExtendedMinerManager = MinerManager & {\n  url: string;\n  id: number;\n  chunkSize: number;\n  patternType: string;\n};\n\nconst {\n  MinerManager: Miner,\n  SwissCheesePattern,\n  SpiralPattern,\n  TowardsCenterPattern,\n  TowardsCenterPatternV2,\n} = df.getConstructors();\n\nconst NEW_CHUNK = 'DiscoveredNewChunk';\n\nfunction getPattern(coords: WorldCoords, patternType: string, chunkSize: number) {\n  if (patternType === 'swiss') {\n    return new SwissCheesePattern(coords, chunkSize);\n  } else if (patternType === 'spiral') {\n    return new SpiralPattern(coords, chunkSize);\n  } else if (patternType === 'towardsCenter') {\n    return new TowardsCenterPattern(coords, chunkSize);\n  } else {\n    return new TowardsCenterPatternV2(coords, chunkSize);\n  }\n}\n\nclass RemoteWorker implements Worker {\n  private url: string;\n\n  constructor(url: string) {\n    this.url = url;\n  }\n\n  async postMessage(msg: string) {\n    const msgJson: MinerWorkerMessage = JSON.parse(msg);\n\n    const resp = await fetch(this.url, {\n      method: 'POST',\n      body: JSON.stringify({\n        chunkFootprint: msgJson.chunkFootprint,\n        planetRarity: msgJson.planetRarity,\n        planetHashKey: msgJson.planetHashKey,\n      }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    });\n\n    const exploredChunk = await resp.json();\n\n    const chunkCenter = {\n      x: exploredChunk.chunkFootprint.bottomLeft.x + exploredChunk.chunkFootprint.sideLength / 2,\n      y: exploredChunk.chunkFootprint.bottomLeft.y + exploredChunk.chunkFootprint.sideLength / 2,\n    };\n\n    exploredChunk.perlin = df.spaceTypePerlin(chunkCenter, false);\n    for (const planetLoc of exploredChunk.planetLocations) {\n      planetLoc.hash = locationIdFromDecStr(planetLoc.hash);\n      planetLoc.perlin = df.spaceTypePerlin({ x: planetLoc.coords.x, y: planetLoc.coords.y }, true);\n      planetLoc.biomebase = df.biomebasePerlin(\n        { x: planetLoc.coords.x, y: planetLoc.coords.y },\n        true\n      );\n    }\n\n    this.onmessage({ data: JSON.stringify([exploredChunk, msgJson.jobId]) });\n  }\n\n  onmessage(_a: { data: string }) {\n    console.warn('Unimplemented: onmessage');\n  }\n  terminate() {\n    console.warn('Unimplemented: terminate');\n  }\n  onmessageerror() {\n    console.warn('Unimplemented: onmessageerror');\n  }\n  addEventListener() {\n    console.warn('Unimplemented: addEventListener');\n  }\n  removeEventListener() {\n    console.warn('Unimplemented: removeEventListener');\n  }\n  dispatchEvent(_event: Event): boolean {\n    return false;\n  }\n  onerror() {\n    console.warn('Unimplemented: onerror');\n  }\n}\n\nfunction Target() {\n  const wrapper = {\n    width: '1em',\n    height: '1em',\n    display: 'inline-block',\n    position: 'relative',\n    verticalAlign: 'text-bottom',\n  };\n\n  const svg = {\n    width: '100%',\n    height: '100%',\n  };\n\n  const path = {\n    fill: 'white',\n  };\n\n  return html`\n    <span style=${wrapper}>\n      <svg\n        style=${svg}\n        version=\"1.1\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"512\"\n        height=\"512\"\n        viewBox=\"0 0 512 512\"\n      >\n        <path\n          style=${path}\n          d=\"M512 224h-50.462c-13.82-89.12-84.418-159.718-173.538-173.538v-50.462h-64v50.462c-89.12 13.82-159.718 84.418-173.538 173.538h-50.462v64h50.462c13.82 89.12 84.418 159.718 173.538 173.538v50.462h64v-50.462c89.12-13.82 159.718-84.418 173.538-173.538h50.462v-64zM396.411 224h-49.881c-9.642-27.275-31.255-48.889-58.53-58.53v-49.881c53.757 12.245 96.166 54.655 108.411 108.411zM256 288c-17.673 0-32-14.327-32-32s14.327-32 32-32c17.673 0 32 14.327 32 32s-14.327 32-32 32zM224 115.589v49.881c-27.275 9.641-48.889 31.255-58.53 58.53h-49.881c12.245-53.756 54.655-96.166 108.411-108.411zM115.589 288h49.881c9.641 27.275 31.255 48.889 58.53 58.53v49.881c-53.756-12.245-96.166-54.654-108.411-108.411zM288 396.411v-49.881c27.275-9.642 48.889-31.255 58.53-58.53h49.881c-12.245 53.757-54.654 96.166-108.411 108.411z\"\n        ></path>\n      </svg>\n    </span>\n  `;\n}\n\nfunction MinerUI({\n  miner,\n  onRemove,\n}: {\n  miner: ExtendedMinerManager;\n  onRemove: (miner: ExtendedMinerManager) => void;\n}) {\n  const [hashRate, setHashRate] = useState(0);\n\n  useEffect(() => {\n    const calcHash = (chunk: Chunk, miningTimeMillis: number) => {\n      df.addNewChunk(chunk);\n      const hashRate = chunk.chunkFootprint.sideLength ** 2 / (miningTimeMillis / 1000);\n      setHashRate(Math.floor(hashRate));\n\n      const res = miner.getCurrentlyExploringChunk();\n      if (res) {\n        const { bottomLeft, sideLength } = res;\n        ui?.setExtraMinerLocation?.(miner.id, {\n          x: bottomLeft.x + sideLength / 2,\n          y: bottomLeft.y + sideLength / 2,\n        });\n      } else {\n        ui?.removeExtraMinerLocation?.(miner.id);\n      }\n    };\n    miner.on(NEW_CHUNK, calcHash);\n\n    return () => {\n      miner.off(NEW_CHUNK, calcHash);\n    };\n  }, [miner]);\n\n  const wrapper = {\n    paddingBottom: '10px',\n    display: 'flex',\n    justifyContent: 'space-between',\n    whiteSpace: 'nowrap',\n  };\n\n  const buttonWrapper = {\n    width: '50px',\n    display: 'flex',\n    justifyContent: 'space-between',\n  };\n\n  const remove = () => {\n    onRemove(miner);\n  };\n\n  const [targeting, setTargeting] = useState(false);\n  const target = () => setTargeting(true);\n\n  useEffect(() => {\n    const hover = () => {\n      const coords = ui.getHoveringOverCoords();\n      if (coords) {\n        ui?.setExtraMinerLocation?.(miner.id, coords);\n      }\n    };\n    const click = () => {\n      window.removeEventListener('mousemove', hover);\n      window.removeEventListener('click', click);\n      const coords = ui.getHoveringOverCoords();\n      if (coords) {\n        const pattern = getPattern(coords, miner.patternType, miner.chunkSize);\n        miner.setMiningPattern(pattern);\n      }\n      miner.startExplore();\n      setTargeting(false);\n    };\n    if (targeting) {\n      miner.stopExplore();\n      window.addEventListener('mousemove', hover);\n      window.addEventListener('click', click);\n    }\n\n    return () => {\n      window.removeEventListener('mousemove', hover);\n      window.removeEventListener('click', click);\n    };\n  }, [targeting, miner]);\n\n  return html`\n    <div style=${wrapper}>\n      <span>${miner.url} - ${hashRate} hashes/sec</span>\n      <div style=${buttonWrapper}>\n        <button onClick=${target}><${Target} /></button>\n        <button onClick=${remove}>X</button>\n      </div>\n    </div>\n  `;\n}\n\nfunction App({\n  initialMiners = [],\n  addMiner,\n  removeMiner,\n}: {\n  initialMiners: ExtendedMinerManager[];\n  addMiner: (url: string, patternType: string) => ExtendedMinerManager[];\n  removeMiner: (miner: ExtendedMinerManager) => ExtendedMinerManager[];\n}) {\n  const wrapper = { display: 'flex' };\n  const input = {\n    flex: '1',\n    padding: '5px',\n    outline: 'none',\n    color: 'black',\n  };\n  const button = {\n    marginLeft: '5px',\n    outline: 'none',\n  };\n  const select = {\n    background: 'rgb(8,8,8)',\n  };\n  const [miners, setMiners] = useState(initialMiners);\n  const [nextUrl, setNextUrl] = useState<string | null>(null);\n  const [patternType, setPatternType] = useState('spiral');\n\n  const onChange = (evt: InputEvent) => {\n    setNextUrl((evt.target as HTMLInputElement).value);\n  };\n\n  const add = () => {\n    if (nextUrl) {\n      const miners = addMiner(nextUrl, patternType);\n      setMiners(miners);\n      setNextUrl(null);\n    }\n  };\n\n  const remove = (miner: ExtendedMinerManager) => {\n    const miners = removeMiner(miner);\n    setMiners(miners);\n  };\n\n  const changePattern = (evt: InputEvent) => {\n    setPatternType((evt.target as HTMLSelectElement).value);\n  };\n\n  return html`\n    <div>\n      ${miners.map(\n        (miner: ExtendedMinerManager) => html`\n          <${MinerUI} key=${miner.url} miner=${miner} onRemove=${remove} />\n        `\n      )}\n      <div style=${wrapper}>\n        <df-text-input\n          style=${input}\n          value=${nextUrl}\n          onInput=${onChange}\n          placeholder=\"URL for explore server\"\n        ></df-text-input>\n        <select style=${select} value=${patternType} onChange=${changePattern}>\n          <option value=\"spiral\">Spiral</option>\n          <option value=\"swiss\">Swiss</option>\n          <option value=\"towardsCenter\">TowardsCenter</option>\n          <option value=\"towardsCenterV2\">TowardsCenterV2</option>\n        </select>\n        <df-button style=${button} onClick=${add}>Explore!</df-button>\n      </div>\n    </div>\n  `;\n}\n\nclass RemoteExplorerPlugin implements DFPlugin {\n  private miners: ExtendedMinerManager[];\n  private id: number;\n\n  constructor() {\n    this.miners = [];\n    this.id = 0;\n\n    this.addMiner('http://0.0.0.0:8000/mine', 'spiral', 256);\n  }\n\n  addMiner = (url: string, patternType = 'spiral', chunkSize = 256) => {\n    // TODO: Somehow set a default coords\n    const pattern = getPattern({ x: 0, y: 0 }, patternType, chunkSize);\n    const miner = Miner.create(\n      df.getChunkStore(),\n      pattern,\n      df.getWorldRadius(),\n      df.planetRarity,\n      df.getHashConfig(),\n      false,\n      () => new RemoteWorker(url)\n    ) as ExtendedMinerManager;\n\n    miner.url = url;\n    miner.id = this.id++;\n    miner.chunkSize = chunkSize;\n    miner.patternType = patternType;\n\n    miner.startExplore();\n\n    this.miners.push(miner);\n\n    return this.miners;\n  };\n\n  removeMiner = (miner: ExtendedMinerManager) => {\n    this.miners = this.miners.filter((m) => {\n      if (m === miner) {\n        ui?.removeExtraMinerLocation?.(m.id);\n        m.stopExplore();\n        m.destroy();\n        return false;\n      } else {\n        return true;\n      }\n    });\n\n    return this.miners;\n  };\n\n  async render(container: HTMLDivElement) {\n    container.style.minWidth = '450px';\n    container.style.width = 'auto';\n\n    render(\n      html`\n        <${App}\n          initialMiners=${this.miners}\n          addMiner=${this.addMiner}\n          removeMiner=${this.removeMiner}\n        />\n      `,\n      container\n    );\n  }\n\n  destroy() {\n    for (const miner of this.miners) {\n      ui?.removeExtraMinerLocation?.(miner.id);\n      miner.stopExplore();\n      miner.destroy();\n    }\n  }\n}\n\nexport default RemoteExplorerPlugin;\n"
  },
  {
    "path": "embedded_plugins/Renderer-Showcase.ts",
    "content": "/* eslint-disable */\n/**\n * Below is a list of class definitions for renderers.\n * These are blank renderers as they have no functionality.\n * The result of using these renderers is the same as disabling the renderer.\n */\nimport {\n  engineConsts,\n  EngineUtils,\n  GameGLManager,\n  GenericRenderer,\n  glsl,\n//@ts-ignore\n} from 'https://cdn.skypack.dev/@darkforest_eth/renderer';\nimport {\n  AsteroidRendererType,\n  AttribType,\n  BackgroundRendererType,\n  BeltRendererType,\n  BlackDomainRendererType,\n  CaptureZoneRendererType,\n  CanvasCoords,\n  Chunk,\n  CircleRendererType,\n  GameViewport,\n  LineRendererType,\n  LocatablePlanet,\n  LocationId,\n  MineBodyRendererType,\n  MineRendererType,\n  PerlinRendererType,\n  Planet,\n  PlanetRendererType,\n  PlanetRenderInfo,\n  PlanetRenderManagerType,\n  QuasarBodyRendererType,\n  QuasarRayRendererType,\n  QuasarRendererType,\n  RectRendererType,\n  RenderedArtifact,\n  RendererType,\n  RGBAVec,\n  RGBVec,\n  RingRendererType,\n  RuinsRendererType,\n  SpaceRendererType,\n  SpacetimeRipRendererType,\n  SpriteRendererType,\n  TextAlign,\n  TextAnchor,\n  TextRendererType,\n  UIRendererType,\n  UniformType,\n  UnminedRendererType,\n  VoyageRendererType,\n  WorldCoords,\n  WormholeRendererType,\n//@ts-ignore\n} from 'https://cdn.skypack.dev/@darkforest_eth/types';\n//@ts-ignore\nimport { html, render } from 'https://unpkg.com/htm/preact/standalone.module.js';\n\n// Line 78 - 350: Blank Renderer\n// Line 350 - 651: Circle Renderer\n// Line 626 - End: Plugin\n \n\n\n\n// Line 78 - 376\n// \"Blank\" renderer class definitions\n// When passing in these renderers into the Dark Forest API, the result would be the same as disabling that type of renderer.\n \nclass PlanetRenderer implements PlanetRendererType {\n  rendererType = RendererType.Planet;\n  queuePlanetBody(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\n\nclass MineRenderer implements MineRendererType {\n  rendererType = RendererType.Mine;\n  queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\n\nclass SpacetimeRipRenderer implements SpacetimeRipRendererType {\n  rendererType = RendererType.SpacetimeRip;\n  queueRip(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\n\nclass QuasarRenderer implements QuasarRendererType {\n  rendererType = RendererType.Quasar;\n\n  queueQuasar(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\n\nclass RuinsRenderer implements RuinsRendererType {\n  rendererType = RendererType.Ruins;\n  queueRuins(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\nclass AsteroidRenderer implements AsteroidRendererType {\n  rendererType = RendererType.Asteroid;\n  queueAsteroid(planet: Planet, centerW: CanvasCoords, radiusW: number, color?: RGBVec): void {}\n  flush(): void {}\n}\n\nclass RingRenderer implements RingRendererType {\n  rendererType = RendererType.Ring;\n  queueRingAtIdx(\n    planet: Planet,\n    centerW: WorldCoords,\n    radiusW: number,\n    color?: RGBVec,\n    beltIdx?: number,\n    angle?: number\n  ): void {}\n  flush(): void {}\n}\n\nclass SpriteRenderer implements SpriteRendererType {\n  rendererType = RendererType.Sprite;\n  //drawing artifacts around world\n  queueArtifactWorld(\n    artifact: RenderedArtifact,\n    posW: CanvasCoords,\n    widthW: number,\n    alpha?: number,\n    atFrame?: number | undefined,\n    color?: RGBVec | undefined,\n    theta?: number | undefined,\n    viewport?: GameViewport\n  ): void {}\n  //drawing artifacts when traveling with voyagers\n  queueArtifact(\n    artifact: RenderedArtifact,\n    pos: CanvasCoords,\n    width?: number,\n    alpha?: number,\n    atFrame?: number | undefined,\n    color?: RGBVec | undefined,\n    theta?: number | undefined\n  ): void {}\n  flush(): void {}\n}\n\nclass BlackDomainRenderer implements BlackDomainRendererType {\n  rendererType = RendererType.BlackDomain;\n  queueBlackDomain(planet: Planet, centerW: WorldCoords, radiusW: number): void {}\n  flush(): void {}\n}\n\nclass TextRenderer implements TextRendererType {\n  rendererType = RendererType.Text;\n  queueTextWorld(\n    text: string,\n    coords: WorldCoords,\n    color?: RGBAVec,\n    offY?: number, // measured in text units - constant screen-coord offset that it useful for drawing nice things\n    align?: TextAlign,\n    anchor?: TextAnchor,\n    zIdx?: number\n  ): void {}\n  flush(): void {}\n}\n\nclass VoyageRenderer implements VoyageRendererType {\n  rendererType = RendererType.Voyager;\n  queueVoyages(): void {}\n  flush(): void {}\n}\n\nclass WormholeRenderer implements WormholeRendererType {\n  rendererType = RendererType.Wormhole;\n  queueWormholes(): void {}\n  flush(): void {}\n}\n\nclass MineBodyRenderer implements MineBodyRendererType {\n  rendererType = RendererType.MineBody;\n  queueMineScreen(planet: Planet, center: WorldCoords, radius: number, z: number): void {}\n  flush(): void {}\n  setUniforms(): void {}\n}\n\nclass BeltRenderer implements BeltRendererType {\n  rendererType = RendererType.Belt;\n  queueBeltAtIdx(\n    planet: Planet,\n    center: WorldCoords | CanvasCoords,\n    radius?: number,\n    color?: RGBVec,\n    beltIdx?: number,\n    angle?: number,\n    screen?: boolean\n  ): void {}\n  flush(): void {}\n  setUniforms(): void {}\n}\n\nclass BackgroundRenderer implements BackgroundRendererType {\n  rendererType = RendererType.Background;\n  queueChunks(\n    exploredChunks: Iterable<Chunk>,\n    highPerfMode: boolean,\n    drawChunkBorders: boolean,\n    disableFancySpaceEffect: boolean,\n    innerNebulaColor?: string,\n    nebulaColor?: string,\n    spaceColor?: string,\n    deepSpaceColor?: string,\n    deadSpaceColor?: string\n  ): void {}\n  flush(): void {}\n}\n\nclass SpaceRenderer implements SpaceRendererType {\n  rendererType = RendererType.Space;\n  queueChunk(chunk: Chunk): void {}\n  setColorConfiguration(\n    innerNebulaColor: string,\n    nebulaColor: string,\n    spaceColor: string,\n    deepSpaceColor: string,\n    deadSpaceColor: string\n  ): void {}\n  flush(): void {}\n}\n\nclass UnminedRenderer implements UnminedRendererType {\n  rendererType = RendererType.Unmined;\n  queueRect(\n    { x, y }: CanvasCoords,\n    width: number,\n    height: number,\n    color: RGBVec,\n    zIdx: number\n  ): void {}\n  flush(): void {}\n}\n\nclass PerlinRenderer implements PerlinRendererType {\n  rendererType = RendererType.Perlin;\n  queueChunk(chunk: Chunk): void {}\n  flush(): void {}\n}\n\nclass LineRenderer implements LineRendererType {\n  rendererType = RendererType.Line;\n  queueLineWorld(\n    start: WorldCoords,\n    end: WorldCoords,\n    color?: RGBAVec,\n    width?: number,\n    zIdx?: number,\n    dashed?: boolean\n  ): void {}\n  flush(): void {}\n}\n\nclass RectRenderer implements RectRendererType {\n  rendererType = RendererType.Rect;\n  queueRectCenterWorld(\n    center: WorldCoords,\n    width: number,\n    height: number,\n    color?: RGBVec,\n    stroke?: number,\n    zIdx?: number\n  ): void {}\n  flush(): void {}\n}\n\nclass CircleRenderer implements CircleRendererType {\n  rendererType = RendererType.Circle;\n  queueCircleWorld(\n    center: CanvasCoords,\n    radius: number,\n    color?: RGBAVec,\n    stroke?: number,\n    angle?: number, // percent of arc to render\n    dashed?: boolean\n  ): void {}\n  queueCircleWorldCenterOnly(\n    center: WorldCoords,\n    radius: number, // canvas coords\n    color?: RGBAVec\n  ): void {}\n  flush(): void {}\n}\n\nclass UIRenderer implements UIRendererType {\n  rendererType = RendererType.UI;\n  queueBorders(): void {}\n  queueSelectedRangeRing(): void {}\n  queueSelectedRect(): void {}\n  queueHoveringRect(): void {}\n  queueMousePath(): void {}\n  drawMiner(): void {}\n  flush(): void {}\n}\n\nclass PlanetRenderManager implements PlanetRenderManagerType {\n  rendererType = RendererType.PlanetManager;\n  queueRangeRings(planet: LocatablePlanet): void {}\n  queuePlanets(\n    cachedPlanets: Map<LocationId, PlanetRenderInfo>,\n    now: number,\n    highPerfMode: boolean,\n    disableEmojis: boolean,\n    disableHats: boolean\n  ): void {}\n  flush(): void {}\n}\n\nclass QuasarBodyRenderer implements QuasarBodyRendererType {\n  rendererType = RendererType.QuasarBody;\n  queueQuasarBody(\n    planet: Planet,\n    centerW: WorldCoords,\n    radiusW: number,\n    z?: number,\n    angle?: number\n  ): void {}\n  flush(): void {}\n}\n\nclass QuasarRayRenderer implements QuasarRayRendererType {\n  rendererType = RendererType.QuasarRay;\n  queueQuasarRay(\n    planet: Planet,\n    centerW: WorldCoords,\n    radiusW: number,\n    z?: number,\n    top?: boolean,\n    angle?: number\n  ): void {}\n  flush(): void {}\n}\n\nclass CaptureZoneRenderer implements CaptureZoneRendererType{\n    rendererType = RendererType.CaptureZone;\n\n    queueCaptureZones(): void {}\n\n    flush(): void {} \n}\n\n// line 350 - 351\n// Circle Renderer Definitions using WebGl\n\n\n\n// Program Definition\n// A program is what we use to organizie the attributes and shaders of WebGl Programs\n \nconst u = {\n  matrix: 'u_matrix', // matrix to convert from world coords to clipspace\n};\n\nconst a = {\n  position: 'a_position', // as [posx, posy, rectposx, rectposy]\n  color: 'a_color',\n  props: 'a_props', // as [stroke, angle, dash]\n  eps: 'a_eps',\n  planetInfo: 'a_planetInfo', //as [planetlevel, radius]\n  PlanetUpgrades: 'a_planetUpgrades', //as [defense:number , range: number, speed: number]\n  PlanetResources: 'a_planetResources', //as [energy:number , energy cap: number, silver: number, silver cap: number]\n};\n\nconst GENERIC_PLANET_PROGRAM_DEFINITION = {\n  uniforms: {\n    matrix: { name: u.matrix, type: UniformType.Mat4 },\n  },\n  attribs: {\n    position: {\n      dim: 4,\n      type: AttribType.Float,\n      normalize: false,\n      name: a.position,\n    },\n    eps: {\n      dim: 1,\n      type: AttribType.Float,\n      normalize: false,\n      name: a.eps,\n    },\n    color: {\n      dim: 4,\n      type: AttribType.UByte,\n      normalize: true,\n      name: a.color,\n    },\n    props: {\n      dim: 2,\n      type: AttribType.Float,\n      normalize: false,\n      name: a.props,\n    },\n    planetInfo: {\n      dim: 2,\n      type: AttribType.UByte,\n      normalize: false,\n      name: a.planetInfo,\n    },\n    planetUpgrades: {\n      dim: 3,\n      type: AttribType.UByte,\n      normalize: false,\n      name: a.PlanetUpgrades,\n    },\n    planetResources: {\n      dim: 4,\n      type: AttribType.UByte,\n      normalize: false,\n      name: a.PlanetResources,\n    },\n  },\n\n  vertexShader: glsl`\n          in vec4 a_position;\n          in vec4 a_color;\n          in vec2 a_props;\n          in float a_eps;\n          in vec2 a_planetInfo;\n          in vec4 a_planetResources;\n        \n          uniform mat4 u_matrix;\n        \n          out float v_planetLevel;\n          out vec4 v_color;\n          out vec2 v_rectPos;\n          out float v_angle;\n          out float v_dash;\n          out float v_eps;\n          out float energy;\n          out float energy_cap;\n        \n          void main() {\n            gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0);\n        \n            v_rectPos = a_position.zw;\n            v_color = a_color;\n            v_angle = a_props.x;\n            v_dash = a_props.y;\n            v_eps = a_eps;\n            v_planetLevel = a_planetInfo[0];\n            energy = a_planetResources[0];\n            energy_cap = a_planetResources[1];\n          }\n          `,\n\n  fragmentShader: glsl`\n        #define PI 3.1415926535\n        \n        precision highp float;\n        out vec4 outColor;\n        \n        in vec4 v_color;\n        in vec2 v_rectPos;\n        in float v_angle;\n        in float v_dash;\n        in float v_eps;\n        in float v_planetLevel;\n        in float energy;\n        in float energy_cap;\n        \n        void main() {\n          vec4 color = v_color;\n          float dist = length(v_rectPos);\n        \n          if (dist > 1.0) discard; // if it's outside the circle\n        \n          // anti-aliasing if barely in the circle\n          float ratio = (1.0 - dist) / v_eps;\n          if (ratio < 1.) {\n            color.a *= ratio;\n          }\n        \n        \n          /* get angle for both angle + dash checks */\n          float angle = atan(v_rectPos.y, v_rectPos.x);\n        \n          // add 5pi/2 to translate it to [-PI/2, 3PI / 2]\n          float check = angle + (5.0 * PI / 2.0);\n          check -= (check > 2.0 * PI ? 2.0 * PI : 0.0);\n          float pct = check / (2.0 * PI);\n        \n          /* do angle check */\n        \n          if (v_angle != 1.0 && pct > v_angle) discard;\n        \n          /* do dash check */\n          bool isDash = v_dash > 0.0;\n          float interval = angle / v_dash;\n          float modulo = interval - 2.0 * floor(interval / 2.0);\n          bool isGap = modulo > 1.0;\n          if (isDash && isGap) discard;\n        \n          /* now draw it */\n          outColor = vec4(1,1.0/energy_cap*energy,0,1);\n        }\n          `,\n};\nclass CirclePlanetRenderer extends GenericRenderer<\n  typeof GENERIC_PLANET_PROGRAM_DEFINITION,\n  GameGLManager\n> {\n  quadBuffer: number[];\n\n  viewport: GameViewport;\n  rendererType: number;\n\n  vertexShader: string;\n  fragmentShader: string;\n\n  manager: GameGLManager;\n\n  constructor(glManager: GameGLManager, n: number) {\n    super(glManager, GENERIC_PLANET_PROGRAM_DEFINITION);\n    //@ts-ignore \n    this.verts = 0; //found in generic renderer\n\n    this.manager = glManager;\n    const { vertexShader: vert, fragmentShader: frag } = GENERIC_PLANET_PROGRAM_DEFINITION;\n    this.vertexShader = vert;\n    this.fragmentShader = frag;\n    this.rendererType = n;\n    this.viewport = this.manager.renderer.getViewport();\n    this.quadBuffer = EngineUtils.makeEmptyDoubleQuad();\n  }\n\n  public queuePlanet(\n    center: CanvasCoords,\n    radius: number,\n    planet: Planet,\n    angle = 1, // percent of arc to render\n    dashed = false\n  ): void {\n    const color = [255, 255, 255, 255] as RGBAVec;\n    const {\n      position: posA,\n      color: colorA,\n      props: propsA,\n      eps: epsA,\n      planetInfo: planetInfoA,\n      planetUpgrades: planetUpgradesA,\n      planetResources: planetResourcesA,\n    //@ts-ignore \n    } = this.attribManagers;\n    const { x, y } = center;\n    // 1 on either side for antialiasing\n    const r = radius + 1;\n\n    const { x1, y1 } = { x1: x - r, y1: y - r };\n    const { x2, y2 } = { x2: x + r, y2: y + r };\n\n    // prettier-ignore\n    EngineUtils.makeDoubleQuadBuffered(\n           this.quadBuffer,\n           x1, y1, x2, y2, -1, -1, 1, 1\n         );\n    //@ts-ignore\n    posA.setVertex(this.quadBuffer, this.verts);\n\n    // convert pixels to radians\n    const interval = engineConsts.dashLength;\n    const pixPerRad = radius;\n\n    const dashRad = interval / pixPerRad;\n    const dash = dashed ? dashRad : -1;\n\n    const eps = 1 / radius;\n    const resources = [planet.energy, planet.energyCap, planet.silver, planet.silverCap];\n    for (let i = 0; i < 6; i++) {\n      //@ts-ignore  \n      colorA.setVertex(color, this.verts + i);\n      //@ts-ignore\n      propsA.setVertex([angle, dash], this.verts + i);\n      //@ts-ignore\n      planetInfoA.setVertex([planet.planetLevel, radius], this.verts + i);\n      //@ts-ignore\n      planetUpgradesA.setVertex(planet.upgradeState, this.verts + i);\n      //@ts-ignore\n      planetResourcesA.setVertex(resources, this.verts + i);\n      //@ts-ignore\n      epsA.setVertex([eps], this.verts + i);\n    }\n    //@ts-ignore\n    this.verts += 6;\n  }\n\n  public queueGenericPlanet(\n    planet: Planet,\n    center: WorldCoords,\n    radius: number, // world coords\n    stroke = -1,\n    angle = 1,\n    dashed = false\n  ) {\n    const centerCanvas = this.viewport.worldToCanvasCoords(center);\n    const rCanvas = this.viewport.worldToCanvasDist(radius);\n    this.queuePlanet(centerCanvas, rCanvas, planet, angle, dashed);\n  }\n\n  public setUniforms() {\n    //@ts-ignore\n    this.uniformSetters.matrix(this.manager.projectionMatrix);\n  }\n\n  public queuePlanetBody(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n  public queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n  public queueRip(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n\n  public queueQuasar(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n\n  public queueRuins(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n\n  public queueAsteroid(planet: Planet, centerW: CanvasCoords, radiusW: number): void {\n    this.queueGenericPlanet(planet, centerW, radiusW);\n  }\n}\n\n/**\n * 626-END: Plugin\n */\n\nexport default class EmbeddedRendererShowcase implements DFPlugin {\n  CirclePlanetLibrary: { [key: string]: any };\n  circleSelector: string;\n  circleChecker: { [key: string]: boolean };\n\n  constructor() {\n    let glMan = ui.getGlManager();\n    this.circleSelector = 'Planet';\n    if (glMan) {\n      this.CirclePlanetLibrary = {\n        Planet: new CirclePlanetRenderer(glMan, RendererType.Planet),\n        Mine: new CirclePlanetRenderer(glMan, RendererType.Mine),\n        SpacetimeRip: new CirclePlanetRenderer(glMan, RendererType.SpacetimeRip),\n        Quasar: new CirclePlanetRenderer(glMan, RendererType.Quasar),\n        Ruins: new CirclePlanetRenderer(glMan, RendererType.Ruins),\n      };\n    }\n    this.circleChecker = {};\n    for (let key in this.CirclePlanetLibrary) {\n      this.circleChecker[key] = false;\n    }\n  }\n  async render(div: HTMLDivElement) {\n    div.style.width = '500px';\n    render(\n      html`\n        <section>Disabling Renderers</section>\n        <section style=${discriptionStyle}>\n          Whats actually happening here is that the current renderer is being replaced with a\n          'blank' renderer. A blank renderer is a renderer who's flush function has no\n          functionality. So the behavior is similar to disabling the current renderer.\n        </section>\n        <section>\n          <select\n            name=\"Renderers\"\n            id=\"Renderer-select\"\n            style=${selectStyle}\n            onChange=${(e: any) => {\n              currentPlanet = e.target.value;\n              const btn = document.getElementById('RegPlanetBtn');\n              if (disabled[currentPlanet] === false) {\n                btn!.innerText = 'Disable';\n              } else {\n                btn!.innerText = 'Enable';\n              }\n            }}\n          >\n            <option value=\"Planet\">Planet</option>\n            <option value=\"Mine\">Mine</option>\n            <option value=\"MineBody\">Mine Body</option>\n            <option value=\"MineBelt\">Mine Belt</option>\n            <option value=\"SpacetimeRip\">Spacetime RIP</option>\n            <option value=\"Ruins\">Ruins</option>\n            <option value=\"Quasar\">Quasar</option>\n            <option value=\"Asteroid\">Asteroid</option>\n            <option value=\"Background\">Background</option>\n            <option value=\"UnminedBackground\">Unmined Background</option>\n            <option value=\"SpaceBackground\">Space Background</option>\n            <option value=\"Artifact\">Artifact</option>\n            <option value=\"AllPlanets\">All Planets</option>\n            <option value=\"Text\">Text</option>\n            <option value=\"Voyager\">Voyager</option>\n            <option value=\"Perlin\">Perlin</option>\n            <option value=\"Wormhole\">Wormhole</option>\n            <option value=\"BlackDomain\">BlackDomain</option>\n            <option value=\"Rectangles\">Rectangles</option>\n            <option value=\"Line\">Line</option>\n            <option value=\"Circle\">Circle</option>\n            <option value=\"Ring\">Ring</option>\n            <option value=\"UI\">UI</option>\n            <option value=\"QuasarBody\">QuasarBody</option>\n            <option value=\"QuasarRay\">QuasarRay</option>\n            <option value=\"CaptureZone\">Capture Zone</option>\n          </select>\n          <df-button\n            id=\"RegPlanetBtn\"\n            onClick=${() => {\n              disable();\n            }}\n          >\n            Disable\n          </df-button>\n        </section>\n        <section id=\"Reg-Discription\"></section>\n        <section>Circle Planets</section>\n        <section style=${discriptionStyle}>\n          This replaces the current planet renderer with a renderer that uses WebGL to create a\n          circle where the color of the circle changes on the percentage of energy on the planet.\n        </section>\n        <section>\n          <select\n            name=\"CirclePlanets\"\n            id=\"Circle-select\"\n            style=${selectStyle}\n            onChange=${(e: any) => {\n              this.circleSelector = e.target.value;\n              console.log(this.circleSelector);\n              const btn = document.getElementById('CirclePlanetBtn');\n              if (this.circleChecker[this.circleSelector] === false) {\n                btn!.innerText = 'Enable';\n              } else {\n                btn!.innerText = 'Disable';\n              }\n            }}\n          >\n            <option value=\"Planet\">Planet</option>\n            <option value=\"Mine\">Mine</option>\n            <option value=\"SpacetimeRip\">Spacetime RIP</option>\n            <option value=\"Ruins\">Ruins</option>\n            <option value=\"Quasar\">Quasar</option>\n          </select>\n\n          <df-button\n            id=\"CirclePlanetBtn\"\n            onClick=${() => {\n              const btn = document.getElementById('CirclePlanetBtn');\n              if (this.circleChecker[this.circleSelector] === false) {\n                ui.setCustomRenderer(this.CirclePlanetLibrary[this.circleSelector]);\n                this.circleChecker[this.circleSelector] = true;\n                btn!.innerText = 'Disable';\n              } else {\n                ui.disableCustomRenderer(this.CirclePlanetLibrary[this.circleSelector]);\n                this.circleChecker[this.circleSelector] = false;\n                btn!.innerText = 'Enable';\n              }\n            }}\n          >\n            Enable\n          </df-button>\n        </section>\n        <section id=\"Circle-Discription\"></section>\n        <section>Renderer Descriptions</section>\n        <select\n          id=\"Description-Select\"\n          style=${selectStyle}\n          onChange=${(e: any) => {\n            currentPlanet = e.target.value;\n            const disc = document.getElementById('Renderer-Descriptions');\n            disc!.innerText = rendererDescription[currentPlanet];\n          }}\n        >\n          <option value=\"Blank\"></option>\n          <option value=\"Planet\">Planet</option>\n          <option value=\"Mine\">Mine</option>\n          <option value=\"MineBody\">Mine Body</option>\n          <option value=\"MineBelt\">Mine Belt</option>\n          <option value=\"SpacetimeRip\">Spacetime RIP</option>\n          <option value=\"Ruins\">Ruins</option>\n          <option value=\"Quasar\">Quasar</option>\n          <option value=\"Asteroid\">Asteroid</option>\n          <option value=\"Background\">Background</option>\n          <option value=\"UnminedBackground\">Unmined Background</option>\n          <option value=\"SpaceBackground\">Space Background</option>\n          <option value=\"Artifact\">Artifact</option>\n          <option value=\"AllPlanets\">All Planets</option>\n          <option value=\"Text\">Text</option>\n          <option value=\"Voyager\">Voyager</option>\n          <option value=\"Perlin\">Perlin</option>\n          <option value=\"Wormhole\">Wormhole</option>\n          <option value=\"BlackDomain\">BlackDomain</option>\n          <option value=\"Rectangles\">Rectangles</option>\n          <option value=\"Line\">Line</option>\n          <option value=\"Circle\">Circle</option>\n          <option value=\"Ring\">Ring</option>\n          <option value=\"UI\">UI</option>\n          <option value=\"QuasarBody\">QuasarBody</option>\n          <option value=\"QuasarRay\">QuasarRay</option>\n          <option value=\"CaptureZone\">Capture Zone</option>\n        </select>\n        <section id=\"Renderer-Descriptions\" style=${discriptionStyle}></section>\n      `,\n      div\n    );\n  }\n\n  destroy(): void {\n    currentPlanet = 'Planet';\n    for (let key in rendererLibrary) {\n      ui.disableCustomRenderer(rendererLibrary[key]);\n    }\n    for (let key in this.CirclePlanetLibrary) {\n      ui.disableCustomRenderer(this.CirclePlanetLibrary[key]);\n    }\n  }\n}\n\nlet selectStyle = {\n  outline: 'none',\n  background: '#151515',\n  color: '#838383',\n  borderRadius: '4px',\n  border: '1px solid #777',\n  width: '100px',\n  padding: '2px 6px',\n  cursor: 'pointer',\n  margin: '10px',\n};\n\nlet inputStyle = {\n  background: 'rgb(8,8,8)',\n  width: '400px',\n  padding: '3px 5px',\n};\n\nlet buttonStyle = { height: '25px', padding: '3px 5px', margin: '10px', 'text-align': 'center' };\n\nlet rendererLibrary: { [key: string]: any } = {\n  Planet: new PlanetRenderer(),\n  Mine: new MineRenderer(),\n  SpacetimeRip: new SpacetimeRipRenderer(),\n  Ruins: new RuinsRenderer(),\n  Quasar: new QuasarRenderer(),\n  Asteroid: new AsteroidRenderer(),\n  MineBody: new MineBodyRenderer(),\n  MineBelt: new BeltRenderer(),\n  Background: new BackgroundRenderer(),\n  UnminedBackground: new UnminedRenderer(),\n  SpaceBackground: new SpaceRenderer(),\n  Artifact: new SpriteRenderer(),\n  AllPlanets: new PlanetRenderManager(),\n  Text: new TextRenderer(),\n  Voyager: new VoyageRenderer(),\n  Perlin: new PerlinRenderer(),\n  Wormhole: new WormholeRenderer(),\n  BlackDomain: new BlackDomainRenderer(),\n  Rectangles: new RectRenderer(),\n  Line: new LineRenderer(),\n  Circle: new CircleRenderer(),\n  Ring: new RingRenderer(),\n  UI: new UIRenderer(),\n  QuasarBody: new QuasarBodyRenderer(),\n  QuasarRay: new QuasarRayRenderer(),\n  CaptureZone: new CaptureZoneRenderer(),\n};\n\nlet disabled: { [key: string]: boolean } = {};\nfor (let key in rendererLibrary) {\n  disabled[key] = false;\n}\n\nlet currentPlanet: string = 'Planet';\n\nfunction disable() {\n  const btn = document.getElementById('RegPlanetBtn');\n  if (disabled[currentPlanet] === false) {\n    ui.setCustomRenderer(rendererLibrary[currentPlanet]);\n    disabled[currentPlanet] = true;\n    btn!.innerText = 'Enable';\n  } else {\n    ui.disableCustomRenderer(rendererLibrary[currentPlanet]);\n    disabled[currentPlanet] = false;\n    btn!.innerText = 'Disable';\n  }\n}\n\nconst cellStyle = {\n  width: '25%',\n  float: 'left',\n};\n\nconst discriptionStyle = {\n  TextAlign: 'justify',\n};\n\n// Used for discription of each type of renderer\nlet rendererDescription: { [key: string]: any } = {\n  Blank: '',\n  Planet: 'basic planets',\n  Mine: 'asteroid fields',\n  SpacetimeRip: 'Spacetime Rips',\n  Ruins: 'foundries',\n  Quasar: 'quasars',\n  Asteroid: 'asteroid that hover around different planets',\n  MineBody: 'the body/asteroids of asteroid fields',\n  MineBelt: 'the belts/rings around asteroid fields',\n  Background: 'the background of the game',\n  UnminedBackground: 'unmined space chunks (part of the background)',\n  SpaceBackground: 'mined space chunks (part of the background)',\n  Artifact: 'artifacts',\n  AllPlanets: 'All planet types',\n  Text: 'text that are displayed on the game canvas',\n  Voyager: 'Voyages. The transfer of energy between planets.',\n  Perlin:\n    'the game background. Perlin is the method in which we generate the location of space biomes.',\n  Wormhole: 'visual effects of wormholes. Wormholes are generated by a special type of artifact.',\n  BlackDomain:\n    'visual effects of black domains. Black Domains are created by a special type of artifact',\n  Rectangles: 'all rectangles drawn in game: indicators for selection of planet, ',\n  Line: ' all lines drawn in game: The line that connect planets during voyages and wormholes ',\n  Circle:\n    'all circles drawn in the game: The voyager(circle) in voyages, Circles used to indicate the range a planets has, The boarder of the game world.  ',\n  Ring: 'rings that indicate the level of a planet',\n  UI: 'in game user interface: game borders, range indicators, selection indicators, mouse path, miner',\n  QuasarBody: 'the body of the Quasar',\n  QuasarRay: 'the ray of the Quasar',\n  CaptureZone: 'the capture zones'\n};\n/* eslint-enable */\n"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n    <meta\n      name=\"description\"\n      content=\"Dark Forest, the world's first decentralized real-time strategy game. Built on Ethereum with zkSNARKs.\"\n    />\n    <meta property=\"og:title\" content=\"Dark Forest\" />\n    <!-- 70 char max for twitter -->\n    <meta property=\"og:type\" content=\"game\" />\n    <meta property=\"og:url\" content=\"https://zkga.me\" />\n    <meta property=\"og:image\" content=\"https://zkga.me/public/og_image.png\" />\n    <!-- Size: The recommended size for an OG Image is 1.91:1. The recommended pixel dimensions are 1200:630 -->\n    <meta\n      property=\"og:description\"\n      content=\"Dark Forest, the world's first decentralized real-time strategy game. Built on Ethereum with zkSNARKs.\"\n    />\n    <!-- 200 char max for twitter -->\n\n    <meta name=\"twitter:card\" content=\"summary_large_image\" />\n    <meta name=\"twitter:site\" content=\"@darkforest_eth\" />\n\n    <!-- favicon stuff -->\n    <link rel=\"shortcut icon\" href=\"/public/favicons.ico\" />\n    <link rel=\"apple-touch-icon\" sizes=\"57x57\" href=\"/public/favicons.ico/apple-icon-57x57.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"60x60\" href=\"/public/favicons.ico/apple-icon-60x60.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"72x72\" href=\"/public/favicons.ico/apple-icon-72x72.png\" />\n    <link rel=\"apple-touch-icon\" sizes=\"76x76\" href=\"/public/favicons.ico/apple-icon-76x76.png\" />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"114x114\"\n      href=\"/public/favicons.ico/apple-icon-114x114.png\"\n    />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"120x120\"\n      href=\"/public/favicons.ico/apple-icon-120x120.png\"\n    />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"144x144\"\n      href=\"/public/favicons.ico/apple-icon-144x144.png\"\n    />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"152x152\"\n      href=\"/public/favicons.ico/apple-icon-152x152.png\"\n    />\n    <link\n      rel=\"apple-touch-icon\"\n      sizes=\"180x180\"\n      href=\"/public/favicons.ico/apple-icon-180x180.png\"\n    />\n    <link\n      rel=\"icon\"\n      type=\"image/png\"\n      sizes=\"192x192\"\n      href=\"/public/favicons.ico/android-icon-192x192.png\"\n    />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"32x32\" href=\"/public/favicons.ico/favicon-32x32.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"96x96\" href=\"/public/favicons.ico/favicon-96x96.png\" />\n    <link rel=\"icon\" type=\"image/png\" sizes=\"16x16\" href=\"/public/favicons.ico/favicon-16x16.png\" />\n    <link rel=\"manifest\" href=\"/public/manifest.json\" />\n    <meta name=\"msapplication-TileColor\" content=\"#080808\" />\n    <meta name=\"msapplication-TileImage\" content=\"/public/favicons.ico/ms-icon-144x144.png\" />\n    <meta name=\"theme-color\" content=\"#080808\" />\n\n    <script src=\"/public/snarkjs.min.js\"></script>\n    <title>Dark Forest</title>\n  </head>\n\n  <body>\n    <noscript>You need to enable JavaScript to run this app.</noscript>\n    <div id=\"root\"></div>\n  </body>\n</html>\n"
  },
  {
    "path": "last_updated.txt",
    "content": "last updated: Mon Apr 11 18:20:58 UTC 2022\n"
  },
  {
    "path": "netlify.toml",
    "content": "[build]\n  command = \"yarn build\"\n  functions = \"functions\"\n  publish = \"dist\"\n\n[[redirects]]\n  from = \"/*\"\n  to = \"/index.html\"\n  status = 200\n\n[[redirects]]\n  from = \"/archive/8d8a7d2a/*\"\n  to = \"https://6247dc3ca1de3b6ce0c8e184--df-prod.netlify.app/:splat\"\n  status = 200\n\n## (optional) Settings for Netlify Dev\n## https://github.com/netlify/cli/blob/master/docs/netlify-dev.md#project-detection\n#[dev]\n#  command = \"yarn start\" # Command to start your dev server\n#  port = 3000 # Port that the dev server will be listening on\n#  publish = \"dist\" # Folder with the static content for _redirect file\n\n## more info on configuring this file: https://www.netlify.com/docs/netlify-toml-reference/\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"client\",\n  \"version\": \"6.7.29\",\n  \"private\": true,\n  \"license\": \"GPL-3.0\",\n  \"author\": \"0xPARC <ivan@0xPARC.org>\",\n  \"dependencies\": {\n    \"@darkforest_eth/constants\": \"6.7.29\",\n    \"@darkforest_eth/contracts\": \"6.7.29\",\n    \"@darkforest_eth/events\": \"6.7.29\",\n    \"@darkforest_eth/gamelogic\": \"6.7.29\",\n    \"@darkforest_eth/hashing\": \"6.7.29\",\n    \"@darkforest_eth/hexgen\": \"6.7.29\",\n    \"@darkforest_eth/network\": \"6.7.29\",\n    \"@darkforest_eth/procedural\": \"6.7.29\",\n    \"@darkforest_eth/renderer\": \"6.7.29\",\n    \"@darkforest_eth/serde\": \"6.7.29\",\n    \"@darkforest_eth/settings\": \"6.7.29\",\n    \"@darkforest_eth/snarks\": \"6.7.29\",\n    \"@darkforest_eth/types\": \"6.7.29\",\n    \"@darkforest_eth/ui\": \"6.7.29\",\n    \"@darkforest_eth/whitelist\": \"6.7.29\",\n    \"@lit-labs/react\": \"^1.0.0\",\n    \"animejs\": \"^3.2.1\",\n    \"auto-bind\": \"^4.0.0\",\n    \"bad-words\": \"^3.0.4\",\n    \"big-integer\": \"^1.6.48\",\n    \"canvas-confetti\": \"^1.4.0\",\n    \"color\": \"^3.0.2\",\n    \"delay\": \"^5.0.0\",\n    \"email-validator\": \"^2.0.4\",\n    \"emoji-picker-react\": \"^3.4.8\",\n    \"ethers\": \"^5.5.1\",\n    \"events\": \"^3.0.0\",\n    \"fastq\": \"^1.10.0\",\n    \"file-saver\": \"^2.0.5\",\n    \"gl-matrix\": \"^3.3.0\",\n    \"htm\": \"^3.0.4\",\n    \"idb\": \"^5.0.1\",\n    \"js-quadtree\": \"^3.3.5\",\n    \"json-stable-stringify\": \"^1.0.1\",\n    \"jszip\": \"^3.5.0\",\n    \"lodash\": \"^4.17.15\",\n    \"mnemonist\": \"^0.38.1\",\n    \"p-defer\": \"^3.0.0\",\n    \"p-timeout\": \"^4.0.0\",\n    \"preact\": \"^10.5.13\",\n    \"prismjs\": \"^1.22.0\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-loader-spinner\": \"^4.0.0\",\n    \"react-router-dom\": \"^5.3.0\",\n    \"react-simple-code-editor\": \"^0.11.0\",\n    \"react-sortablejs\": \"^6.0.0\",\n    \"react-timeago\": \"^6.2.1\",\n    \"sortablejs\": \"^1.10.2\",\n    \"styled-components\": \"^5.3.3\",\n    \"ts-dedent\": \"^2.0.0\",\n    \"uuid\": \"^8.3.2\"\n  },\n  \"scripts\": {\n    \"declarations\": \"tsc -p tsconfig.decs.json\",\n    \"test\": \"exit 0\",\n    \"lint\": \"eslint .\",\n    \"format\": \"prettier --write .\",\n    \"start\": \"webpack-dev-server --mode development --hot\",\n    \"build\": \"webpack --mode production\",\n    \"clean\": \"del-cli dist node_modules declarations public/contracts tsconfig.ref.tsbuildinfo\",\n    \"docs\": \"typedoc && yarn format\",\n    \"deploy\": \"netlify build && netlify deploy\",\n    \"deploy:prod\": \"netlify build && netlify deploy --prod\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\"\n    ],\n    \"development\": [\n      \"last 1 chrome version\",\n      \"last 1 firefox version\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \">=16\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.17.5\",\n    \"@babel/preset-env\": \"^7.16.11\",\n    \"@babel/preset-react\": \"^7.16.7\",\n    \"@babel/preset-typescript\": \"^7.16.7\",\n    \"@types/animejs\": \"^3.1.3\",\n    \"@types/color\": \"^3.0.2\",\n    \"@types/gl-matrix\": \"^3.2.0\",\n    \"@types/json-stable-stringify\": \"^1.0.32\",\n    \"@types/lodash\": \"^4.14.160\",\n    \"@types/prismjs\": \"^1.16.2\",\n    \"@types/react\": \"^17.0.34\",\n    \"@types/react-dom\": \"^17.0.11\",\n    \"@types/react-router-dom\": \"^5.3.2\",\n    \"@types/react-timeago\": \"^4.1.3\",\n    \"@types/sortablejs\": \"^1.10.6\",\n    \"@types/styled-components\": \"^5.1.15\",\n    \"@types/uuid\": \"^8.3.0\",\n    \"@types/webpack-env\": \"^1.16.3\",\n    \"babel-loader\": \"^8.2.3\",\n    \"babel-plugin-styled-components\": \"^2.0.5\",\n    \"copy-webpack-plugin\": \"^9.0.1\",\n    \"css-loader\": \"^6.5.1\",\n    \"del-cli\": \"^4.0.1\",\n    \"dotenv\": \"^10.0.0\",\n    \"eslint\": \"^7.30.0\",\n    \"fork-ts-checker-webpack-plugin\": \"^7.2.1\",\n    \"html-webpack-plugin\": \"^5.5.0\",\n    \"netlify-cli\": \"^3.8.5\",\n    \"prettier\": \"^2.3.0\",\n    \"raw-loader\": \"^4.0.2\",\n    \"resolve-package-path\": \"^4.0.3\",\n    \"source-map-loader\": \"^3.0.0\",\n    \"style-loader\": \"^3.3.1\",\n    \"typedoc\": \"^0.22.8\",\n    \"typedoc-plugin-markdown\": \"3.11.x\",\n    \"typescript\": \"4.5.x\",\n    \"webpack\": \"^5.62.1\",\n    \"webpack-bundle-analyzer\": \"^4.5.0\",\n    \"webpack-cli\": \"^4.9.1\",\n    \"webpack-dev-server\": \"^4.4.0\"\n  }\n}\n"
  },
  {
    "path": "plugins/PluginTemplate.ts",
    "content": "/**\n * Remember, you have access these globals:\n * 1. df - Just like the df object in your console.\n * 2. ui - For interacting with the game's user interface.\n *\n * Let's log these to the console when you run your plugin!\n */\nconsole.log(df, ui);\n\nclass PluginTemplate implements DFPlugin {\n  constructor() {}\n\n  /**\n   * Called when plugin is launched with the \"run\" button.\n   */\n  async render(container: HTMLDivElement) {}\n\n  /**\n   * Called when plugin modal is closed.\n   */\n  destroy() {}\n}\n\n/**\n * And don't forget to export it!\n */\nexport default PluginTemplate;\n"
  },
  {
    "path": "plugins/README.md",
    "content": "# Plugins\n\nTypeScript or JavaScript files in this directory will automatically be bundled and served as plugins when running `df-plugin-dev-server` in the root of the game `client`!\n\nCheck out [Plugin Development](../README.md#plugin-development) in the main README for the steps to set it up.\n"
  },
  {
    "path": "plugins/RendererPlugin.md",
    "content": "# Pluginnable Renderers\n\nThis article is about creating custom renderers for Dark Forest through the use of our plugin system.\n\n# Background\n\nDark Forest uses WebGL to visualize the game. WebGL is a JavaScript API that is used for rendering high-performance 3D graphics.\n\nOnly certain browsers support WebGL. Click here to check: https://get.webgl.org/WebGL/.\n\nClick here to Learn more about WebGL: https://WebGLfundamentals.org/webgl/lessons/webgl-getting-WebGL.html\n\n# What is a Renderer\n\nRenderers are classes that are used to draw specific types of entities such as planets or asteroids onto the Dark Forest game canvas. Custom made renderers can be passed into the Dark Forest API to replace the current renderer for that entity type.\n\nRenderers have two main methods:\n\n- Queue (a method that starts with `queue` followed by the name of the object it's rendering): The game calls the queue method for each instance of an object specified by the renderer type and accepts as an input any information relevant to the object. This information is then added to some implementation of a queue within the renderer for later use in the flush method.\n\n- Flush: Flush is called every frame in the game. When called, the entities in the queue should be rendered onto the game canvas and the queue cleared.\n\nFor the game to recognize if the class is a renderer, it has to follow one of the renderer interfaces that can be found in `@darkforest_eth/types`\n\nFor instance `MineRendererType` is an interface for mine renderers. Mine renderers are used to draw Asteroid Fields which is a type of planet (not to be confused with Asteroids).\n\n![Asteroid Field](./CircleBefore.png)\n\nThe `MineRendererType` has 2 abstract methods:\n\n- `queueMine` is called by the game to queue an Asteroid Field to be drawn. The method has 3 parameters:\n  - `planet`: A Planet object that contains information about the current Asteroid Field\n  - `centerW`: a set of x and y coordinates that represent the position of the center of the Asteroid Field relative to the game world.\n  - `radiusW`: the size of the Asteroid Field relative to the game world.\n- `flush` called by the game to draw all queued up Asteroid Field.\n\nHere is Dark Forest's current implementation of its mine renderer: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/MineRenderer.ts\n\n## Draw Order\n\n### The Order in Which Renderer Managers and Renderers Are Flushed\n\n1. Background\n   - Unmined\n   - Space\n2. Capture Zones\n3. Line Renderer\n4. Planet Manager\n   - Planet\n   - Asteroid\n   - Mine\n   - Spacetime Rip\n   - Ruins\n   - Ring\n   - Quasar\n   - Black Domain\n5. UI\n6. Voyager\n7. Wormhole\n8. Circle\n9. Rect\n10. Text\n11. Sprite (Artifacts)\n\n### When Multiple Renderers Implementing the Same Interface Are Passed Into the API:\n\nWhen you replace a renderer in the game, the renderer is then put on our rendering stack. The rendering stack is used to determine which renderer to use if multiple renderers loaded for the same entity at the same time. The game will use the top most renderer. A renderer at any position in the stack can be popped.\n\n# Example Plugin\n\nWe will run through the creation of a renderer that will replace the renderer for Asteroid Fields.\n\nTo create a renderer it has to follow one of the renderer interfaces that can be found in ​​`@darkforest_eth/types`. For the Asteroid Fields, we will be using the `MineRendererType`.\n\n```javascript\nimport { RendererType } from 'https://cdn.skypack.dev/@darkforest_eth/types';\n\nclass GenericMineRenderer {\n  constructor() {\n    this.rendererType = RendererType.Mine;\n  }\n\n  queueMine(planet, centerW, radiusW) {}\n\n  flush() {}\n}\n```\n\nAfter implementing the interface the code should look similar to the code above. The code above can be considered a fully functional renderer. The resulting behavior would be as if you were disabling the renderer for the Asteroid Renderer.\n\nBefore:\n\n![Before](./DisableBefore.png)\n\nAfter:\n\n![After](./DisableAfter.png)\n\nIf you noticed in the queue function, the coordinate of the planet and the size of planet is relative to the game world. However when drawn on the canvas, the size and location of planet change based on the position of the players camera. We provide developers a way to easily transform between coordinate systems via the viewport class. You can get the viewport from the global ui class. `ui.getViewport()`. An example of the use of the viewport can be seen in the code of the `queueMine` function below.\n\n```javascript\nimport { RendererType } from 'https://cdn.skypack.dev/@darkforest_eth/types';\n\nclass GenericMineRenderer {\n  constructor() {\n    this.viewport = ui.getViewport();\n    this.rendererType = RendererType.Mine;\n  }\n\n  queueMine(planet, centerW, radiusW) {\n    const centerCanvas = this.viewport.worldToCanvasCoords(centerW);\n    const rCanvas = this.viewport.worldToCanvasDist(radiusW);\n  }\n\n  flush() {}\n}\n```\n\nThe `Viewport` class is used to translate between game world and canvas for distances and coordinates: https://github.com/darkforest-eth/packages/blob/master/types/interfaces/GameViewport.md\n\nThese are the 2 functions used above.\n\n- `worldToCanvasCoords` will translate from game world coordinates to canvas coordinates. In the code above we are translating the location of the Asteroid Field to its location relative to the canvas.\n- `worldToCanvasDist` will translate a distance relative to the game world to the pixel distance on the canvas.\n\nAfter implementing the above code you should be ready to start implementing code that will draw on the game canvas.  \nTo do this you will need to be able to access the `WebGLRenderingContext`.\n\n## WebGL Code\n\nThe next few sections will be about writing the WebGL Code.\n\nWe will be creating WebGL code that renders a circle instead of the original asteroid field. The color of the asteroid field while change based on the current energy level of the planet. The RGB value of the color is determined by this equation `red: 255 green: 255/(energy cap) \\*(current energy) blue: 0`.\n\nThe goal of this section is to give you a basic understanding of how the WebGL code works. If you want to dive deeper into WebGL check out this website:\n\nhttps://WebGLfundamentals.org/webgl/lessons/webgl-how-it-works.html\n\n## Definitions\n\nClip space: The 3D coordinate system used by WebGL for its canvas. All coordinates range fro`m -1 to 1.\n\nVertex shader: The vertex shader is used to determine the location of a vertex on the WebGL clip space. WebGL will draw a shape onto the canvas based on the passed in vertices. For instance, the triangle draw mode will connect every 3 consecutive vertices to create a triangle.\n\nFragment Shader: Once a shape is created, the fragment shader is used to determine the color of each individual pixel in the shape.\n\nAttribute Variables: The attributes are values that are passed in from outside the shader program to the vertex shader. Each vertex has its on set of attribute variables.\n\nUniform Variables: The uniforms are effectively global variables you set before the execution of the program. All vertices share the same uniform variables.\n\nVarying Variables: Variables that will be transferred to be used in the fragment shader\n\nThe code below contains all of the objects that we will explore to demonstrate WebGL rendering. The two shaders are wrapped into a RendererProgram class. The RendererProgram is how the Dark Forest team structures our code. You do not have to follow this structure to create a working WebGL Program.\n\n## Fragment and Vertex Shaders\n\n```javascript\nimport { glsl } from 'https://cdn.skypack.dev/@darkforest_eth/renderer';\nimport {\n  RendererProgram,\n  AttribeType,\n  UniformType,\n} from 'https://cdn.skypack.dev/@darkforest_eth/types';\nconst program = {\n  uniforms: {\n    matrix: { name: 'u_matrix', type: UniformType.Mat4 },\n  },\n  attribs: {\n    position: {\n      dim: 4,\n      type: AttribType.Float,\n      normalize: false,\n      name: 'a_position', //the name of the attribute variable\n    },\n    energyInfo: {\n      dim: 2,\n      type: AttribType.Float,\n      normalize: false,\n      name: 'a_energy',\n    },\n  },\n  vertexShader: glsl`\n        in vec4 a_position;\n        in vec2 a_energy;\n        uniform mat4 u_matrix;\n\n        out vec2 v_rectPos;\n        out float v_energy;\n        out float v_energy_cap;\n        void main() {\n            // converting from canvas coordinates too clip space\n            gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0); \n            //setting the varrying variables for use in the fragment shader\n            v_energy = a_energy.x;\n            v_energy_cap = a_energy.y;\n            v_rectPos = a_position.zw;\n        }\n  `,\n  fragmentShader: glsl`\n        #define PI 3.1415926535\n\n        precision highp float;\n        out vec4 outColor;\n\n        in vec2 v_rectPos;\n        in float v_energy;\n        in float v_energy_cap;\n\n        void main() {\n            float dist = length(v_rectPos);\n\n            // if it's outside the circle\n            if (dist > 1.0) discard; \n\n            //determine the color of the pixel using rgb values \n            //[red,green,blue,opacity] the range of the numbers is from 0 to 1\n            outColor = vec4(1,1.0/v_energy_cap*v_energy,0,1);\n        }\n  `,\n};\n```\n\nFor the both the Vertex and Fragment shader `glsl` is used. `glsl` formats the string to be readable by the shader compiler.\n\n### Vertex Shader\n\n`a_position` and `a_energy` are attributes and `u_matrix` is a uniform for this shader program.\n\n- `a_position` is a vector that contains the location of the vertex being drawn\n- `a_energy` is a vector that contains information about the energy of the Asteroid Field.\n\nThe `u_matrix` is a projection matrix. It is used to do some fancy math in line 10:\n\n`gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0);`\n\nThis is transforming coordinates from the canvas into clip space coordinates. If you want to read more about how this math done, checkout this link: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_model_view_projection.\n\nWith the combination of some code we will write later, the vertex shader will draw a square at the coordinates we decide.\n\n### Fragment Shader\n\nThe fragment shader is called for every pixel in the square. The `outColor` is the color of the pixel. This fragment draw a circle with a clip space radius of 1. The fragment shader checks if the pixel is one unit away from the center of the square by use of the length function and `discard`s the pixel away if it is not.\n\n## Renderer Code\n\n```javascript\nimport { EngineUtils, GenericRenderer, glsl } from 'https://cdn.skypack.dev/@darkforest_eth/renderer';\nimport { AttribType, RendererType, UniformType,\n} from 'https://cdn.skypack.dev/@darkforest_eth/types';\nclass GenericMineRenderer extends GenericRenderer {\n    constructor(gl, vp) {\n        super(gl, program);\n        this.rendererType = RendererType.Mine;\n        this.gl = gl;\n        this.viewport = vp;\n        this.quadBuffer = EngineUtils.makeEmptyDoubleQuad();\n    }\n```\n\nThe `GameGLManager` is a wrapper class that contains the WebGLRenderingContext: https://github.com/darkforest-eth/packages/blob/master/renderer/classes/GameGLManager.md\n\n`GenericRenderer` is a class that was created by the Dark Forest team for the purposes of organization. `GenericRenderer` sets up a lot of the commonly used WebGL code. You do not have to use this to create functioning WebGL Code. You can look at the `GenericRenderer` Code here: https://github.com/darkforest-eth/packages/blob/master/renderer/src/WebGL/GenericRenderer.ts\n\nThe `quadBuffer` is an array that will contain the vertices of the square being drawn.\n\n`EngineUtils` is a tool the Dark Forest team created for commonly used WebGL operations: https://github.com/darkforest-eth/packages/blob/master/renderer/src/EngineUtils.ts\n\n```typescript\n  public flush(drawMode: DrawMode = DrawMode.Triangles) {\n    if (this.verts === 0) return;\n\n    const { gl } = this.manager;\n    gl.useProgram(this.program);\n\n    this.setUniforms();\n\n    for (const attrib in this.attribManagers) {\n      this.attribManagers[attrib].bufferData(this.verts);\n    }\n\n    // draw\n    gl.drawArrays(drawMode, 0, this.verts);\n\n    this.verts = 0;\n  }\n```\n\nThis is the flush function which was inherited by `GenericRenderer`.\nThe default `DrawMode` is to use triangles. This means whenever 3 vertices are put into the vertex shader it will draw a triangle.\n\nAs stated above the goal of the vertex shader was to draw a square. To do that with triangle we need to draw 2 triangles connected by one their sides. The result of this will require us to import 6 vertices.\n\nSquare:\n\n![Square](./square.png)\n\n```javascript\n  queueMine(planet: Planet, centerW: WorldCoords, radiusW: number): void {\n    //converting from game coordinates to canvas coordinates\n    const centerCanvas = this.viewport.worldToCanvasCoords(centerW);\n    const rCanvas = this.viewport.worldToCanvasDist(radiusW);\n    this.queueCircle(planet, centerCanvas, rCanvas);\n  }\n  queueCircle(planet: Planet, center: CanvasCoords, radius: number) {\n    const { position: posA, energyInfo: energyA } = this.attribManagers;\n    const r = radius + 1;\n    const { x, y } = center;\n    //calculating the top left and bottom right of the bounding square.\n    const { x1, y1 } = { x1: x - r, y1: y - r };\n    const { x2, y2 } = { x2: x + r, y2: y + r };\n\n    //creating an array of all the vertices for the triangle 14\n    EngineUtils.makeDoubleQuadBuffered(\n          this.quadBuffer,\n          x1, y1, x2, y2, -1, -1, 1, 1\n        );\n    //passing in the vertices to the vertex shader\n    posA.setVertex(this.quadBuffer, this.verts);\n\n    //passing in the energy information. 6 times because we passed in 6 vertices.\n    for (let i = 0; i < 6; i++) {\n        energyA.setVertex([planet.energy, planet.energyCap], this.verts + i);\n    }\n    this.verts += 6;\n  }\n\n   setUniforms() {\n    if (!this.gl) return;\n    this.uniformSetters.matrix(this.gl.projectionMatrix);\n  }\n```\n\nThe `makeDoubleQuadBuffered` code creates an array of size 24. We are then inserting the array to be imported into fragment shader through `a_position` attribute.\n\nSince `a_position` is imported as a `vec4` the `quadbuffer` is split evenly into 6 vectors of size 4, one for each vertex. Each vector is formatted like this:\n[canvas x, canvas y, clip space x, clip space y]\n\nIn relation to the square, the coordinates of the 6 vertices are the top left, top right, bottom left, bottom right of the square. Where the top right and bottom left repeat twice.\n\n```javascript\nfor (let i = 0; i < 6; i++) {\n  energyA.setVertex([planet.energy, planet.energyCap], this.verts + i);\n}\nthis.verts += 6;\n```\n\nFor each vertex we are importing the information about the planet's energy to the `a_energy` attribute.\n\n## Final Renderer Code\n\nThe final code for the renderer looks like this:\n\n```javascript\nimport {\n  EngineUtils,\n  GenericRenderer,\n  glsl,\n} from 'https://cdn.skypack.dev/@darkforest_eth/renderer';\nimport {\n  AttribType,\n  RendererType,\n  UniformType,\n} from 'https://cdn.skypack.dev/@darkforest_eth/types';\n\nclass GenericMineRenderer extends GenericRenderer {\n  constructor(gl, vp) {\n    super(gl, program);\n    this.rendererType = RendererType.Mine;\n    this.gl = gl;\n    this.viewport = vp;\n    this.quadBuffer = EngineUtils.makeEmptyDoubleQuad();\n  }\n  queueMine(planet, centerW, radiusW) {\n    //converting from game coordinates to canvas coordinates\n    const centerCanvas = this.viewport.worldToCanvasCoords(centerW);\n    const rCanvas = this.viewport.worldToCanvasDist(radiusW);\n    this.queueCircle(planet, centerCanvas, rCanvas);\n  }\n  queueCircle(planet, center, radius) {\n    const { position: posA, energyInfo: energyA } = this.attribManagers;\n    const r = radius + 1;\n    const { x, y } = center;\n    //calculating the top left and bottom right of the bounding square.\n    const { x1, y1 } = { x1: x - r, y1: y - r };\n    const { x2, y2 } = { x2: x + r, y2: y + r };\n    //creating an array of all the vertices for the triangle 14\n    EngineUtils.makeDoubleQuadBuffered(this.quadBuffer, x1, y1, x2, y2, -1, -1, 1, 1);\n    //passing in the vertices to the vertex shader\n    posA.setVertex(this.quadBuffer, this.verts);\n    //passing in the energy information. 6 times because we passed in 6 vertices.\n    for (let i = 0; i < 6; i++) {\n      energyA.setVertex([planet.energy, planet.energyCap], this.verts + i);\n    }\n    this.verts += 6;\n  }\n  setUniforms() {\n    if (!this.gl) return;\n\n    this.uniformSetters.matrix(this.gl.projectionMatrix);\n  }\n}\nconst program = {\n  uniforms: {\n    matrix: { name: 'u_matrix', type: UniformType.Mat4 },\n  },\n  attribs: {\n    position: {\n      dim: 4,\n      type: AttribType.Float,\n      normalize: false,\n      name: 'a_position', //the name of the attribute variable\n    },\n    energyInfo: {\n      dim: 2,\n      type: AttribType.Float,\n      normalize: false,\n      name: 'a_energy',\n    },\n  },\n  vertexShader: glsl`\n        in vec4 a_position;\n        in vec2 a_energy;\n        uniform mat4 u_matrix;\n\n        out vec2 v_rectPos;\n        out float v_energy;\n        out float v_energy_cap;\n        void main() {\n            // converting from canvas coordinates too clip space\n            gl_Position = u_matrix * vec4(a_position.xy, 0.0, 1.0); \n            //setting the varrying variables for use in the fragment shader\n            v_energy = a_energy.x;\n            v_energy_cap = a_energy.y;\n            v_rectPos = a_position.zw;\n        }\n  `,\n  fragmentShader: glsl`\n        #define PI 3.1415926535\n\n        precision highp float;\n        out vec4 outColor;\n\n        in vec2 v_rectPos;\n        in float v_energy;\n        in float v_energy_cap;\n\n        void main() {\n            float dist = length(v_rectPos);\n\n            // if it's outside the circle\n            if (dist > 1.0) discard; \n\n            //determine the color of the pixel using rgb values \n            //[red,green,blue,opacity] the range of the numbers is from 0 to 1\n            outColor = vec4(1,1.0/v_energy_cap*v_energy,0,1);\n        }\n  `,\n};\n```\n\nOnce we are done implementing the renderer we can call `ui.setCustomRenderer` to start rendering our own Asteroid Field Renderer.\n\n```javascript\nexport default class ExamplePlugin {\n  constructor() {\n    const gl = ui.getGlManager();\n    if (!gl) return;\n    this.mineRenderer = new GenericMineRenderer(gl, ui.getViewport());\n    ui.setCustomRenderer(this.mineRenderer);\n  }\n  async render(div) {}\n  destroy() {\n    console.log('destroying renderer plugin');\n    ui.disableCustomRenderer(this.mineRenderer);\n  }\n}\n```\n\nThe code above is an example of implementing the renderer with plugins. When the plugin is started it creates a new instance of our custom Asteroid Field Renderer and then uses `setCustomRenderer` to activate it. When the plugin is destroyed we disable the renderer by using `disableCustomRenderer`.\n\nThe result of running the plugin can be seen below.\n\nBefore:\n![CirlceBefore](./CircleBefore.png)\n\nAfter:\n![CirlceAfter](./CircleAfter.png)\n\n# Documentation Links\n\n## Renderer Types\n\nAll renderer types can be found here: https://github.com/darkforest-eth/packages/tree/master/types#type-declaration-27\n\nTo find the interface for each renderer go to the link below. The name of the interface is the entity name followed by `RendererType`: https://github.com/darkforest-eth/packages/tree/master/types#interfaces\n\n## Dark Forest Renderers\n\nBelow are examples of Dark Forest renderers\n\nBasic Planets\n\n- Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/PlanetRenderer.ts\n- WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/PlanetProgram.ts\n\nBackground Renderer:\n\n- Background Renderer is a renderer manager that contains multiple renderer used to draw different sections of the background.\n- Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/BackgroundRenderer.ts\n- Unmined Renderer:\n  - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/UnminedRenderer.ts\n  - WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/UnminedProgram.ts\n- Space Renderer:\n  - Renderer Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Entities/SpaceRenderer.ts\n  - WebGL Code: https://github.com/darkforest-eth/packages/blob/master/renderer/src/Programs/SpaceProgram.ts\n\nIf you want to look at more renderers, you can find all the Dark Forest renderers here:\nhttps://github.com/darkforest-eth/packages/tree/master/renderer/src/Entities\n\nThe WebGl Code for those renderers can be found here: https://github.com/darkforest-eth/packages/tree/master/renderer/src/Programs\n\n## Rendering Tools\n\nGeneric Renderer is base class that Dark Forest team created for all of its renderers: https://github.com/darkforest-eth/packages/blob/master/renderer/src/WebGL/GenericRenderer.ts\n\nEngine Utils contains function for common WebGl Operations : https://github.com/darkforest-eth/packages/blob/master/renderer/classes/EngineUtils.md\n\nhttps://github.com/darkforest-eth/packages/blob/master/renderer/src/EngineUtils.ts\n\nThe viewport class is used to translate between game and canvas coordinate systems: https://github.com/darkforest-eth/packages/blob/master/types/interfaces/GameViewport.md\n\nOur Wrapper class for the WebGL2RenderingContext\nGameGlManager: https://github.com/darkforest-eth/packages/blob/master/renderer/classes/GameGLManager.md\n\nIf you want to mess with canvas 2D rendering you can access a `CanvasRenderingContext2D` by calling `ui.get2dRenderer()` WARNING: all images drawn with the `CanvasRenderingContext2D` will all be on top of WebGl\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"name\": \"Dark Forest\",\n  \"short_name\": \"Dark Forest\",\n  \"background_color\": \"#080808\",\n  \"theme_color\": \"#080808\",\n  \"icons\": [\n    {\n      \"src\": \"/favicons.ico/android-icon-36x36.png\",\n      \"sizes\": \"36x36\",\n      \"type\": \"image/png\",\n      \"density\": \"0.75\"\n    },\n    {\n      \"src\": \"/favicons.ico/android-icon-48x48.png\",\n      \"sizes\": \"48x48\",\n      \"type\": \"image/png\",\n      \"density\": \"1.0\"\n    },\n    {\n      \"src\": \"/favicons.ico/android-icon-72x72.png\",\n      \"sizes\": \"72x72\",\n      \"type\": \"image/png\",\n      \"density\": \"1.5\"\n    },\n    {\n      \"src\": \"/favicons.ico/android-icon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\",\n      \"density\": \"2.0\"\n    },\n    {\n      \"src\": \"/favicons.ico/android-icon-144x144.png\",\n      \"sizes\": \"144x144\",\n      \"type\": \"image/png\",\n      \"density\": \"3.0\"\n    },\n    {\n      \"src\": \"/favicons.ico/android-icon-192x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\",\n      \"density\": \"4.0\"\n    },\n    {\n      \"src\": \"/favicons.ico/favicon-96x96.png\",\n      \"sizes\": \"96x96\",\n      \"type\": \"image/png\",\n      \"density\": \"2.0\"\n    }\n  ]\n}\n"
  },
  {
    "path": "public/robots.txt",
    "content": "# https://www.robotstxt.org/robotstxt.html\nUser-agent: *\n"
  },
  {
    "path": "src/Backend/GameLogic/ArrivalUtils.ts",
    "content": "import { CONTRACT_PRECISION } from '@darkforest_eth/constants';\nimport { hasOwner, isActivated, isEmojiFlagMessage } from '@darkforest_eth/gamelogic';\nimport {\n  ArrivalType,\n  Artifact,\n  ArtifactType,\n  EmojiFlagBody,\n  Planet,\n  PlanetMessage,\n  PlanetType,\n  QueuedArrival,\n  Upgrade,\n} from '@darkforest_eth/types';\nimport _ from 'lodash';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\n\n// TODO: planet class, cmon, let's go\nexport const blocksLeftToProspectExpiration = (\n  currentBlockNumber: number,\n  prospectedBlockNumber?: number\n) => {\n  return (prospectedBlockNumber || 0) + 255 - currentBlockNumber;\n};\n\n// TODO: Planet. Class.\nexport const prospectExpired = (currentBlockNumber: number, prospectedBlockNumber: number) => {\n  return blocksLeftToProspectExpiration(currentBlockNumber, prospectedBlockNumber) <= 0;\n};\n\nexport const isFindable = (planet: Planet, currentBlockNumber?: number): boolean => {\n  return (\n    currentBlockNumber !== undefined &&\n    planet.planetType === PlanetType.RUINS &&\n    planet.prospectedBlockNumber !== undefined &&\n    !planet.hasTriedFindingArtifact &&\n    !prospectExpired(currentBlockNumber, planet.prospectedBlockNumber)\n  );\n};\n\nexport const isProspectable = (planet: Planet): boolean => {\n  return planet.planetType === PlanetType.RUINS && planet.prospectedBlockNumber === undefined;\n};\n\nconst getSilverOverTime = (\n  planet: Planet,\n  startTimeMillis: number,\n  endTimeMillis: number\n): number => {\n  if (!hasOwner(planet)) {\n    return planet.silver;\n  }\n\n  if (planet.silver > planet.silverCap) {\n    return planet.silverCap;\n  }\n  const timeElapsed = endTimeMillis / 1000 - startTimeMillis / 1000;\n\n  return Math.min(timeElapsed * planet.silverGrowth + planet.silver, planet.silverCap);\n};\n\nconst getEnergyAtTime = (planet: Planet, atTimeMillis: number): number => {\n  if (planet.energy === 0) {\n    return 0;\n  }\n  if (!hasOwner(planet)) {\n    return planet.energy;\n  }\n\n  if (planet.planetType === PlanetType.SILVER_BANK) {\n    if (planet.energy > planet.energyCap) {\n      return planet.energyCap;\n    }\n  }\n\n  const timeElapsed = atTimeMillis / 1000 - planet.lastUpdated;\n  const denominator =\n    Math.exp((-4 * planet.energyGrowth * timeElapsed) / planet.energyCap) *\n      (planet.energyCap / planet.energy - 1) +\n    1;\n  return planet.energyCap / denominator;\n};\n\nexport const updatePlanetToTime = (\n  planet: Planet,\n  planetArtifacts: Artifact[],\n  atTimeMillis: number,\n  contractConstants: ContractConstants,\n  setPlanet: (p: Planet) => void = () => {}\n): void => {\n  if (atTimeMillis < planet.lastUpdated * 1000) {\n    return;\n  }\n\n  if (planet.pausers === 0) {\n    planet.silver = getSilverOverTime(planet, planet.lastUpdated * 1000, atTimeMillis);\n    planet.energy = getEnergyAtTime(planet, atTimeMillis);\n  }\n\n  planet.lastUpdated = atTimeMillis / 1000;\n\n  const photoidActivationTime = contractConstants.PHOTOID_ACTIVATION_DELAY * 1000;\n  const activePhotoid = planetArtifacts.find(\n    (a) =>\n      a.artifactType === ArtifactType.PhotoidCannon &&\n      isActivated(a) &&\n      atTimeMillis - a.lastActivated * 1000 >= photoidActivationTime\n  );\n\n  if (activePhotoid && !planet.localPhotoidUpgrade) {\n    planet.localPhotoidUpgrade = activePhotoid.timeDelayedUpgrade;\n    applyUpgrade(planet, activePhotoid.timeDelayedUpgrade);\n  }\n\n  setPlanet(planet);\n};\n\nexport const applyUpgrade = (planet: Planet, upgrade: Upgrade, unApply = false) => {\n  if (unApply) {\n    planet.speed /= upgrade.energyCapMultiplier / 100;\n    planet.energyGrowth /= upgrade.energyGroMultiplier / 100;\n    planet.range /= upgrade.rangeMultiplier / 100;\n    planet.speed /= upgrade.speedMultiplier / 100;\n    planet.defense /= upgrade.defMultiplier / 100;\n  } else {\n    planet.speed *= upgrade.energyCapMultiplier / 100;\n    planet.energyGrowth *= upgrade.energyGroMultiplier / 100;\n    planet.range *= upgrade.rangeMultiplier / 100;\n    planet.speed *= upgrade.speedMultiplier / 100;\n    planet.defense *= upgrade.defMultiplier / 100;\n  }\n};\n\n/**\n * @param previous The previously calculated state of a planet\n * @param current The current calculated state of the planet\n * @param arrival The Arrival that caused the state change\n */\nexport interface PlanetDiff {\n  previous: Planet;\n  current: Planet;\n  arrival: QueuedArrival;\n}\n\nexport const arrive = (\n  toPlanet: Planet,\n  artifactsOnPlanet: Artifact[],\n  arrival: QueuedArrival,\n  arrivingArtifact: Artifact | undefined,\n  contractConstants: ContractConstants\n): PlanetDiff => {\n  // this function optimistically simulates an arrival\n  if (toPlanet.locationId !== arrival.toPlanet) {\n    throw new Error(`attempted to apply arrival for wrong toPlanet ${toPlanet.locationId}`);\n  }\n\n  // update toPlanet energy and silver right before arrival\n  updatePlanetToTime(toPlanet, artifactsOnPlanet, arrival.arrivalTime * 1000, contractConstants);\n\n  const prevPlanet = _.cloneDeep(toPlanet);\n  if (toPlanet.destroyed) {\n    return { arrival: arrival, previous: toPlanet, current: toPlanet };\n  }\n\n  // apply energy\n  const { energyArriving } = arrival;\n\n  if (arrival.player !== toPlanet.owner) {\n    if (arrival.arrivalType === ArrivalType.Wormhole) {\n      // if this is a wormhole arrival to a planet that isn't owned by the initiator of\n      // the move, then don't move any energy\n    }\n    // attacking enemy - includes emptyAddress\n    else if (\n      toPlanet.energy >\n      Math.floor((energyArriving * CONTRACT_PRECISION * 100) / toPlanet.defense) /\n        CONTRACT_PRECISION\n    ) {\n      // attack reduces target planet's garrison but doesn't conquer it\n      toPlanet.energy -=\n        Math.floor((energyArriving * CONTRACT_PRECISION * 100) / toPlanet.defense) /\n        CONTRACT_PRECISION;\n    } else {\n      // conquers planet\n      toPlanet.owner = arrival.player;\n      toPlanet.energy =\n        energyArriving -\n        Math.floor((toPlanet.energy * CONTRACT_PRECISION * toPlanet.defense) / 100) /\n          CONTRACT_PRECISION;\n    }\n  } else {\n    // moving between my own planets\n    toPlanet.energy += energyArriving;\n  }\n\n  if (toPlanet.planetType === PlanetType.SILVER_BANK || toPlanet.pausers !== 0) {\n    if (toPlanet.energy > toPlanet.energyCap) {\n      toPlanet.energy = toPlanet.energyCap;\n    }\n  }\n\n  // apply silver\n  if (toPlanet.silver + arrival.silverMoved > toPlanet.silverCap) {\n    toPlanet.silver = toPlanet.silverCap;\n  } else {\n    toPlanet.silver += arrival.silverMoved;\n  }\n\n  // transfer artifact if necessary\n  if (arrival.artifactId) {\n    toPlanet.heldArtifactIds.push(arrival.artifactId);\n  }\n\n  if (arrivingArtifact) {\n    if (arrivingArtifact.artifactType === ArtifactType.ShipMothership) {\n      toPlanet.energyGrowth *= 2;\n    } else if (arrivingArtifact.artifactType === ArtifactType.ShipWhale) {\n      toPlanet.silverGrowth *= 2;\n    } else if (arrivingArtifact.artifactType === ArtifactType.ShipTitan) {\n      toPlanet.pausers++;\n    }\n    arrivingArtifact.onPlanetId = toPlanet.locationId;\n  }\n\n  return { arrival, current: toPlanet, previous: prevPlanet };\n};\n\n/**\n * @todo ArrivalUtils has become a dumping ground for functions that should just live inside of a\n * `Planet` class.\n */\nexport function getEmojiMessage(\n  planet: Planet | undefined\n): PlanetMessage<EmojiFlagBody> | undefined {\n  return planet?.messages?.find(isEmojiFlagMessage);\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/CaptureZoneGenerator.ts",
    "content": "import { monomitter, Monomitter } from '@darkforest_eth/events';\nimport { CaptureZone, Chunk, LocationId } from '@darkforest_eth/types';\nimport bigInt from 'big-integer';\nimport { utils } from 'ethers';\nimport GameManager, { GameManagerEvent } from './GameManager';\n\nexport type CaptureZonesGeneratedEvent = {\n  changeBlock: number;\n  nextChangeBlock: number;\n  zones: CaptureZone[];\n};\n\n/**\n * Given a game start block and a zone change block interval, decide when to generate new Capture Zones.\n */\nexport class CaptureZoneGenerator {\n  private gameManager: GameManager;\n\n  private zones: Set<CaptureZone>;\n  private capturablePlanets: Set<LocationId>;\n\n  private lastChangeBlock: number;\n  private nextChangeBlock: number;\n  private changeInterval: number;\n\n  public readonly generated$: Monomitter<CaptureZonesGeneratedEvent>;\n\n  constructor(gameManager: GameManager, gameStartBlock: number, changeInterval: number) {\n    this.gameManager = gameManager;\n    this.changeInterval = changeInterval;\n    this.nextChangeBlock = gameStartBlock;\n    this.generated$ = monomitter();\n    this.capturablePlanets = new Set();\n    this.zones = new Set();\n\n    gameManager.on(GameManagerEvent.DiscoveredNewChunk, this.onNewChunk.bind(this));\n  }\n\n  /**\n   * Call when a new block is received to check if generation is needed.\n   * @param blockNumber Current block number.\n   */\n  async generate(blockNumber: number) {\n    this.setNextGenerationBlock(blockNumber);\n\n    const newZones = await this._generate(this.nextChangeBlock - this.changeInterval);\n    this.zones = newZones;\n    this.updateCapturablePlanets();\n    this.generated$.publish({\n      changeBlock: this.lastChangeBlock,\n      nextChangeBlock: this.nextChangeBlock,\n      zones: Array.from(this.zones),\n    });\n  }\n\n  private setNextGenerationBlock(blockNumber: number) {\n    const totalGameBlocks = blockNumber - this.gameManager.getContractConstants().GAME_START_BLOCK;\n    const numPastIntervals = Math.floor(totalGameBlocks / this.changeInterval);\n    this.nextChangeBlock =\n      this.gameManager.getContractConstants().GAME_START_BLOCK +\n      (numPastIntervals + 1) * this.changeInterval;\n  }\n\n  private async _generate(blockNumber: number) {\n    const block = await this.gameManager.getEthConnection().getProvider().getBlock(blockNumber);\n    const worldRadius = await this.gameManager.getContractAPI().getWorldRadius();\n\n    const captureZones = new Set<CaptureZone>();\n    const ringSize = 5000;\n    const ringCount = Math.floor(worldRadius / ringSize);\n    const zonesPerRing =\n      this.gameManager.getContractConstants().CAPTURE_ZONES_PER_5000_WORLD_RADIUS;\n\n    for (let ring = 0; ring < ringCount; ring++) {\n      const nonceBase = ring * zonesPerRing;\n\n      for (let j = 0; j < zonesPerRing; j++) {\n        const nonce = nonceBase + j;\n        const blockAndNonceHash = utils.solidityKeccak256(\n          ['bytes32', 'uint256'],\n          [block.hash, nonce]\n        );\n        // Chop off 0x and convert to BigInt\n        const seed = bigInt(blockAndNonceHash.substring(2, blockAndNonceHash.length), 16);\n        // Last 3 hex characters\n        const angleSeed = seed.mod(0xfff);\n        // Max value of 0xfff is 4095\n        // 4095 / 651 is max radians in circle\n        // Mult by 1e18 to convert to big number math\n        const angleRads = angleSeed.multiply(1e18).divide(651);\n\n        // Next 6 hex characters\n        const distanceSeed = seed.minus(angleSeed).divide(4096).mod(0xffffff);\n        // 16777215 is value of FFFFFF\n        // Clamp distance within ring radius\n        const divisor = Math.floor(16777215 / ringSize);\n        // Add in distance from origin point\n        const distance = distanceSeed.divide(divisor).add(ring * ringSize);\n\n        // Bring it back down to number\n        const angleNumber = Number(angleRads) / 1e18;\n        const distanceNumber = Number(distance);\n\n        const coords = {\n          x: Math.floor(distanceNumber * Math.cos(angleNumber)),\n          y: Math.floor(distanceNumber * Math.sin(angleNumber)),\n        };\n\n        captureZones.add({\n          coords,\n          radius: this.gameManager.getContractConstants().CAPTURE_ZONE_RADIUS,\n        });\n      }\n    }\n\n    this.lastChangeBlock = blockNumber;\n\n    return captureZones;\n  }\n\n  private updateCapturablePlanets() {\n    this.capturablePlanets = new Set<LocationId>();\n\n    for (const zone of this.getZones()) {\n      const planetsInZone = this.gameObjects.getPlanetsInWorldCircle(zone.coords, zone.radius);\n      for (const planet of planetsInZone) {\n        this.capturablePlanets.add(planet.locationId);\n      }\n    }\n  }\n\n  private get gameObjects() {\n    return this.gameManager.getGameObjects();\n  }\n\n  private onNewChunk(chunk: Chunk) {\n    for (const worldLocation of chunk.planetLocations) {\n      for (const zone of this.getZones()) {\n        const { x: planetX, y: planetY } = worldLocation.coords;\n        const { x: zoneX, y: zoneY } = zone.coords;\n\n        const distance = Math.sqrt((planetX - zoneX) ** 2 + (planetY - zoneY) ** 2);\n        if (distance <= zone.radius) {\n          this.capturablePlanets.add(worldLocation.hash);\n        }\n      }\n    }\n  }\n\n  /**\n   * Is the given planet inside of a Capture Zone.\n   */\n  public isInZone(locationId: LocationId) {\n    return this.capturablePlanets.has(locationId);\n  }\n\n  /**\n   * The next block that will trigger a Capture Zone generation.\n   */\n  public getNextChangeBlock() {\n    return this.nextChangeBlock;\n  }\n\n  public getZones() {\n    return this.zones;\n  }\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/ContractsAPI.ts",
    "content": "import { EMPTY_LOCATION_ID } from '@darkforest_eth/constants';\nimport { DarkForest } from '@darkforest_eth/contracts/typechain';\nimport {\n  aggregateBulkGetter,\n  ContractCaller,\n  EthConnection,\n  ethToWei,\n  TxCollection,\n  TxExecutor,\n} from '@darkforest_eth/network';\nimport {\n  address,\n  artifactIdFromEthersBN,\n  artifactIdToDecStr,\n  decodeArrival,\n  decodeArtifact,\n  decodeArtifactPointValues,\n  decodePlanet,\n  decodePlanetDefaults,\n  decodePlayer,\n  decodeRevealedCoords,\n  decodeUpgradeBranches,\n  locationIdFromEthersBN,\n  locationIdToDecStr,\n} from '@darkforest_eth/serde';\nimport {\n  Artifact,\n  ArtifactId,\n  ArtifactType,\n  AutoGasSetting,\n  DiagnosticUpdater,\n  EthAddress,\n  LocationId,\n  Planet,\n  Player,\n  QueuedArrival,\n  RevealedCoords,\n  Setting,\n  Transaction,\n  TransactionId,\n  TxIntent,\n  VoyageId,\n} from '@darkforest_eth/types';\nimport { BigNumber as EthersBN, ContractFunction, Event, providers } from 'ethers';\nimport { EventEmitter } from 'events';\nimport _ from 'lodash';\nimport NotificationManager from '../../Frontend/Game/NotificationManager';\nimport { openConfirmationWindowForTransaction } from '../../Frontend/Game/Popups';\nimport { getSetting } from '../../Frontend/Utils/SettingsHooks';\nimport {\n  ContractConstants,\n  ContractEvent,\n  ContractsAPIEvent,\n  PlanetTypeWeightsBySpaceType,\n} from '../../_types/darkforest/api/ContractsAPITypes';\nimport { loadDiamondContract } from '../Network/Blockchain';\nimport { eventLogger, EventType } from '../Network/EventLogger';\n\ninterface ContractsApiConfig {\n  connection: EthConnection;\n  contractAddress: EthAddress;\n}\n\n/**\n * Roughly contains methods that map 1:1 with functions that live in the contract. Responsible for\n * reading and writing to and from the blockchain.\n *\n * @todo don't inherit from {@link EventEmitter}. instead use {@link Monomitter}\n */\nexport class ContractsAPI extends EventEmitter {\n  /**\n   * Don't allow users to submit txs if balance falls below this amount/\n   */\n  private static readonly MIN_BALANCE = ethToWei(0.002);\n\n  /**\n   * Instrumented {@link ThrottledConcurrentQueue} for blockchain reads.\n   */\n  private readonly contractCaller: ContractCaller;\n\n  /**\n   * Instrumented {@link ThrottledConcurrentQueue} for blockchain writes.\n   */\n  public readonly txExecutor: TxExecutor;\n\n  /**\n   * Our connection to the blockchain. In charge of low level networking, and also of the burner\n   * wallet.\n   */\n  public readonly ethConnection: EthConnection;\n\n  /**\n   * The contract address is saved on the object upon construction\n   */\n  private contractAddress: EthAddress;\n\n  get contract() {\n    return this.ethConnection.getContract<DarkForest>(this.contractAddress);\n  }\n\n  public constructor({ connection, contractAddress }: ContractsApiConfig) {\n    super();\n    this.contractCaller = new ContractCaller();\n    this.ethConnection = connection;\n    this.contractAddress = contractAddress;\n    this.txExecutor = new TxExecutor(\n      connection,\n      this.getGasFeeForTransaction.bind(this),\n      this.beforeQueued.bind(this),\n      this.beforeTransaction.bind(this),\n      this.afterTransaction.bind(this)\n    );\n\n    this.setupEventListeners();\n  }\n\n  /**\n   * We pass this function into {@link TxExecutor} to calculate what gas fee we should use for the\n   * given transaction. The result is either a number, measured in gwei, represented as a string, or\n   * a string representing that we want to use an auto gas setting.\n   */\n  private getGasFeeForTransaction(tx: Transaction): AutoGasSetting | string {\n    if (\n      (tx.intent.methodName === 'initializePlayer' || tx.intent.methodName === 'getSpaceShips') &&\n      tx.intent.contract.address === this.contract.address\n    ) {\n      return '50';\n    }\n\n    const config = {\n      contractAddress: this.contractAddress,\n      account: this.ethConnection.getAddress(),\n    };\n\n    return getSetting(config, Setting.GasFeeGwei);\n  }\n\n  /**\n   * This function is called by {@link TxExecutor} before a transaction is queued.\n   * It gives the client an opportunity to prevent a transaction from being queued based\n   * on business logic or user interaction.\n   *\n   * Reject the promise to prevent the queued transaction from being queued.\n   */\n  private async beforeQueued(\n    id: TransactionId,\n    intent: TxIntent,\n    overrides?: providers.TransactionRequest\n  ): Promise<void> {\n    const address = this.ethConnection.getAddress();\n    if (!address) throw new Error(\"can't send a transaction, no signer\");\n\n    const balance = await this.ethConnection.loadBalance(address);\n\n    if (balance.lt(ContractsAPI.MIN_BALANCE)) {\n      const notifsManager = NotificationManager.getInstance();\n      notifsManager.balanceEmpty();\n      throw new Error('xDAI balance too low!');\n    }\n\n    const gasFeeGwei = EthersBN.from(overrides?.gasPrice || '1000000000');\n\n    await openConfirmationWindowForTransaction({\n      contractAddress: this.contractAddress,\n      connection: this.ethConnection,\n      id,\n      intent,\n      overrides,\n      from: address,\n      gasFeeGwei,\n    });\n  }\n\n  /**\n   * This function is called by {@link TxExecutor} before each transaction. It gives the client an\n   * opportunity to prevent a transaction from going through based on business logic or user\n   * interaction. To prevent the queued transaction from being submitted, throw an Error.\n   */\n  private async beforeTransaction(tx: Transaction): Promise<void> {\n    this.emit(ContractsAPIEvent.TxProcessing, tx);\n  }\n\n  private async afterTransaction(_txRequest: Transaction, txDiagnosticInfo: unknown) {\n    eventLogger.logEvent(EventType.Transaction, txDiagnosticInfo);\n  }\n\n  public destroy(): void {\n    this.removeEventListeners();\n  }\n\n  private makeCall<T>(contractViewFunction: ContractFunction<T>, args: unknown[] = []): Promise<T> {\n    return this.contractCaller.makeCall(contractViewFunction, args);\n  }\n\n  public async setupEventListeners(): Promise<void> {\n    const { contract } = this;\n\n    const filter = {\n      address: contract.address,\n      topics: [\n        [\n          contract.filters.ArrivalQueued(null, null, null, null, null).topics,\n          contract.filters.ArtifactActivated(null, null, null).topics,\n          contract.filters.ArtifactDeactivated(null, null, null).topics,\n          contract.filters.ArtifactDeposited(null, null, null).topics,\n          contract.filters.ArtifactFound(null, null, null).topics,\n          contract.filters.ArtifactWithdrawn(null, null, null).topics,\n          contract.filters.LocationRevealed(null, null, null, null).topics,\n          contract.filters.PlanetHatBought(null, null, null).topics,\n          contract.filters.PlanetProspected(null, null).topics,\n          contract.filters.PlanetSilverWithdrawn(null, null, null).topics,\n          contract.filters.PlanetTransferred(null, null, null).topics,\n          contract.filters.PlanetInvaded(null, null).topics,\n          contract.filters.PlanetCaptured(null, null).topics,\n          contract.filters.PlayerInitialized(null, null).topics,\n          contract.filters.AdminOwnershipChanged(null, null).topics,\n          contract.filters.AdminGiveSpaceship(null, null).topics,\n          contract.filters.PauseStateChanged(null).topics,\n          contract.filters.LobbyCreated(null, null).topics,\n        ].map((topicsOrUndefined) => (topicsOrUndefined || [])[0]),\n      ] as Array<string | Array<string>>,\n    };\n\n    const eventHandlers = {\n      [ContractEvent.PauseStateChanged]: (paused: boolean) => {\n        this.emit(ContractsAPIEvent.PauseStateChanged, paused);\n      },\n      [ContractEvent.AdminOwnershipChanged]: (location: EthersBN, _newOwner: string) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n      },\n      [ContractEvent.AdminGiveSpaceship]: (\n        location: EthersBN,\n        _newOwner: string,\n        _type: ArtifactType\n      ) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n      },\n      [ContractEvent.ArtifactFound]: (\n        _playerAddr: string,\n        rawArtifactId: EthersBN,\n        loc: EthersBN\n      ) => {\n        const artifactId = artifactIdFromEthersBN(rawArtifactId);\n        this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId);\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc));\n      },\n      [ContractEvent.ArtifactDeposited]: (\n        _playerAddr: string,\n        rawArtifactId: EthersBN,\n        loc: EthersBN\n      ) => {\n        const artifactId = artifactIdFromEthersBN(rawArtifactId);\n        this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId);\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc));\n      },\n      [ContractEvent.ArtifactWithdrawn]: (\n        _playerAddr: string,\n        rawArtifactId: EthersBN,\n        loc: EthersBN\n      ) => {\n        const artifactId = artifactIdFromEthersBN(rawArtifactId);\n        this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId);\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc));\n      },\n      [ContractEvent.ArtifactActivated]: (\n        _playerAddr: string,\n        rawArtifactId: EthersBN,\n        loc: EthersBN\n      ) => {\n        const artifactId = artifactIdFromEthersBN(rawArtifactId);\n        this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId);\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc));\n      },\n      [ContractEvent.ArtifactDeactivated]: (\n        _playerAddr: string,\n        rawArtifactId: EthersBN,\n        loc: EthersBN\n      ) => {\n        const artifactId = artifactIdFromEthersBN(rawArtifactId);\n        this.emit(ContractsAPIEvent.ArtifactUpdate, artifactId);\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(loc));\n      },\n      [ContractEvent.PlayerInitialized]: async (player: string, locRaw: EthersBN, _: Event) => {\n        this.emit(ContractsAPIEvent.PlayerUpdate, address(player));\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(locRaw));\n        this.emit(ContractsAPIEvent.RadiusUpdated);\n      },\n      [ContractEvent.PlanetTransferred]: async (\n        _senderAddress: string,\n        planetId: EthersBN,\n        receiverAddress: string,\n        _: Event\n      ) => {\n        this.emit(\n          ContractsAPIEvent.PlanetTransferred,\n          locationIdFromEthersBN(planetId),\n          address(receiverAddress)\n        );\n      },\n      [ContractEvent.ArrivalQueued]: async (\n        playerAddr: string,\n        arrivalId: EthersBN,\n        fromLocRaw: EthersBN,\n        toLocRaw: EthersBN,\n        _artifactIdRaw: EthersBN,\n        _: Event\n      ) => {\n        this.emit(\n          ContractsAPIEvent.ArrivalQueued,\n          arrivalId.toString() as VoyageId,\n          locationIdFromEthersBN(fromLocRaw),\n          locationIdFromEthersBN(toLocRaw)\n        );\n        this.emit(ContractsAPIEvent.PlayerUpdate, address(playerAddr));\n        this.emit(ContractsAPIEvent.RadiusUpdated);\n      },\n      [ContractEvent.PlanetUpgraded]: async (\n        _playerAddr: string,\n        location: EthersBN,\n        _branch: EthersBN,\n        _toBranchLevel: EthersBN,\n        _: Event\n      ) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n      },\n      [ContractEvent.PlanetInvaded]: async (_playerAddr: string, location: EthersBN, _: Event) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n      },\n      [ContractEvent.PlanetCaptured]: async (_playerAddr: string, location: EthersBN, _: Event) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n      },\n      [ContractEvent.PlanetHatBought]: async (\n        _playerAddress: string,\n        location: EthersBN,\n        _: Event\n      ) => this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location)),\n      [ContractEvent.LocationRevealed]: async (\n        revealerAddr: string,\n        location: EthersBN,\n        _x: EthersBN,\n        _y: EthersBN,\n        _: Event\n      ) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n        this.emit(\n          ContractsAPIEvent.LocationRevealed,\n          locationIdFromEthersBN(location),\n          address(revealerAddr.toLowerCase())\n        );\n        this.emit(ContractsAPIEvent.PlayerUpdate, address(revealerAddr));\n      },\n      [ContractEvent.PlanetSilverWithdrawn]: async (\n        player: string,\n        location: EthersBN,\n        _amount: EthersBN,\n        _: Event\n      ) => {\n        this.emit(ContractsAPIEvent.PlanetUpdate, locationIdFromEthersBN(location));\n        this.emit(ContractsAPIEvent.PlayerUpdate, address(player));\n      },\n      [ContractEvent.LobbyCreated]: (ownerAddr: string, lobbyAddr: string) => {\n        this.emit(ContractsAPIEvent.LobbyCreated, address(ownerAddr), address(lobbyAddr));\n      },\n    };\n\n    this.ethConnection.subscribeToContractEvents(contract, eventHandlers, filter);\n  }\n\n  public removeEventListeners(): void {\n    const { contract } = this;\n\n    contract.removeAllListeners(ContractEvent.PlayerInitialized);\n    contract.removeAllListeners(ContractEvent.ArrivalQueued);\n    contract.removeAllListeners(ContractEvent.PlanetUpgraded);\n    contract.removeAllListeners(ContractEvent.PlanetHatBought);\n    contract.removeAllListeners(ContractEvent.PlanetTransferred);\n    contract.removeAllListeners(ContractEvent.ArtifactFound);\n    contract.removeAllListeners(ContractEvent.ArtifactDeposited);\n    contract.removeAllListeners(ContractEvent.ArtifactWithdrawn);\n    contract.removeAllListeners(ContractEvent.ArtifactActivated);\n    contract.removeAllListeners(ContractEvent.ArtifactDeactivated);\n    contract.removeAllListeners(ContractEvent.LocationRevealed);\n    contract.removeAllListeners(ContractEvent.PlanetSilverWithdrawn);\n    contract.removeAllListeners(ContractEvent.PlanetInvaded);\n    contract.removeAllListeners(ContractEvent.PlanetCaptured);\n  }\n\n  public getContractAddress(): EthAddress {\n    return this.contractAddress;\n  }\n\n  async getConstants(): Promise<ContractConstants> {\n    const {\n      DISABLE_ZK_CHECKS,\n      PLANETHASH_KEY,\n      SPACETYPE_KEY,\n      BIOMEBASE_KEY,\n      PERLIN_LENGTH_SCALE,\n      PERLIN_MIRROR_X,\n      PERLIN_MIRROR_Y,\n    } = await this.makeCall(this.contract.getSnarkConstants);\n    const {\n      ADMIN_CAN_ADD_PLANETS,\n      WORLD_RADIUS_LOCKED,\n      WORLD_RADIUS_MIN,\n      MAX_NATURAL_PLANET_LEVEL,\n      TIME_FACTOR_HUNDREDTHS,\n      PERLIN_THRESHOLD_1,\n      PERLIN_THRESHOLD_2,\n      PERLIN_THRESHOLD_3,\n      INIT_PERLIN_MIN,\n      INIT_PERLIN_MAX,\n      SPAWN_RIM_AREA,\n      BIOME_THRESHOLD_1,\n      BIOME_THRESHOLD_2,\n      SILVER_SCORE_VALUE,\n      // TODO: Actually put this in game constants\n      // PLANET_LEVEL_THRESHOLDS,\n      PLANET_RARITY,\n      PLANET_TRANSFER_ENABLED,\n      PHOTOID_ACTIVATION_DELAY,\n      LOCATION_REVEAL_COOLDOWN,\n      SPACE_JUNK_ENABLED,\n      SPACE_JUNK_LIMIT,\n      PLANET_LEVEL_JUNK,\n      ABANDON_SPEED_CHANGE_PERCENT,\n      ABANDON_RANGE_CHANGE_PERCENT,\n      // Capture Zones\n      GAME_START_BLOCK,\n      CAPTURE_ZONES_ENABLED,\n      CAPTURE_ZONE_COUNT,\n      CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL,\n      CAPTURE_ZONE_RADIUS,\n      CAPTURE_ZONE_PLANET_LEVEL_SCORE,\n      CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED,\n      CAPTURE_ZONES_PER_5000_WORLD_RADIUS,\n    } = await this.makeCall(this.contract.getGameConstants);\n\n    const TOKEN_MINT_END_SECONDS = (\n      await this.makeCall(this.contract.TOKEN_MINT_END_TIMESTAMP)\n    ).toNumber();\n\n    const adminAddress = address(await this.makeCall(this.contract.adminAddress));\n\n    const upgrades = decodeUpgradeBranches(await this.makeCall(this.contract.getUpgrades));\n\n    const PLANET_TYPE_WEIGHTS: PlanetTypeWeightsBySpaceType =\n      await this.makeCall<PlanetTypeWeightsBySpaceType>(this.contract.getTypeWeights);\n\n    const rawPointValues = await this.makeCall(this.contract.getArtifactPointValues);\n    const ARTIFACT_POINT_VALUES = decodeArtifactPointValues(rawPointValues);\n\n    const planetDefaults = decodePlanetDefaults(await this.makeCall(this.contract.getDefaultStats));\n\n    const planetLevelThresholds = (\n      await this.makeCall<EthersBN[]>(this.contract.getPlanetLevelThresholds)\n    ).map((x: EthersBN) => x.toNumber());\n\n    const planetCumulativeRarities = (\n      await this.makeCall<EthersBN[]>(this.contract.getCumulativeRarities)\n    ).map((x: EthersBN) => x.toNumber());\n\n    const constants: ContractConstants = {\n      ADMIN_CAN_ADD_PLANETS,\n      WORLD_RADIUS_LOCKED,\n      WORLD_RADIUS_MIN: WORLD_RADIUS_MIN.toNumber(),\n\n      DISABLE_ZK_CHECKS,\n\n      PLANETHASH_KEY: PLANETHASH_KEY.toNumber(),\n      SPACETYPE_KEY: SPACETYPE_KEY.toNumber(),\n      BIOMEBASE_KEY: BIOMEBASE_KEY.toNumber(),\n      PERLIN_LENGTH_SCALE: PERLIN_LENGTH_SCALE.toNumber(),\n      PERLIN_MIRROR_X,\n      PERLIN_MIRROR_Y,\n      CLAIM_PLANET_COOLDOWN: 0,\n      TOKEN_MINT_END_SECONDS,\n      MAX_NATURAL_PLANET_LEVEL: MAX_NATURAL_PLANET_LEVEL.toNumber(),\n      TIME_FACTOR_HUNDREDTHS: TIME_FACTOR_HUNDREDTHS.toNumber(),\n      PERLIN_THRESHOLD_1: PERLIN_THRESHOLD_1.toNumber(),\n      PERLIN_THRESHOLD_2: PERLIN_THRESHOLD_2.toNumber(),\n      PERLIN_THRESHOLD_3: PERLIN_THRESHOLD_3.toNumber(),\n      INIT_PERLIN_MIN: INIT_PERLIN_MIN.toNumber(),\n      INIT_PERLIN_MAX: INIT_PERLIN_MAX.toNumber(),\n      BIOME_THRESHOLD_1: BIOME_THRESHOLD_1.toNumber(),\n      BIOME_THRESHOLD_2: BIOME_THRESHOLD_2.toNumber(),\n      SILVER_SCORE_VALUE: SILVER_SCORE_VALUE.toNumber(),\n      PLANET_LEVEL_THRESHOLDS: [\n        planetLevelThresholds[0],\n        planetLevelThresholds[1],\n        planetLevelThresholds[2],\n        planetLevelThresholds[3],\n        planetLevelThresholds[4],\n        planetLevelThresholds[5],\n        planetLevelThresholds[6],\n        planetLevelThresholds[7],\n        planetLevelThresholds[8],\n        planetLevelThresholds[9],\n      ],\n      PLANET_RARITY: PLANET_RARITY.toNumber(),\n      PLANET_TRANSFER_ENABLED,\n      PLANET_TYPE_WEIGHTS,\n      ARTIFACT_POINT_VALUES,\n\n      SPACE_JUNK_ENABLED,\n      SPACE_JUNK_LIMIT: SPACE_JUNK_LIMIT.toNumber(),\n      PLANET_LEVEL_JUNK: [\n        PLANET_LEVEL_JUNK[0].toNumber(),\n        PLANET_LEVEL_JUNK[1].toNumber(),\n        PLANET_LEVEL_JUNK[2].toNumber(),\n        PLANET_LEVEL_JUNK[3].toNumber(),\n        PLANET_LEVEL_JUNK[4].toNumber(),\n        PLANET_LEVEL_JUNK[5].toNumber(),\n        PLANET_LEVEL_JUNK[6].toNumber(),\n        PLANET_LEVEL_JUNK[7].toNumber(),\n        PLANET_LEVEL_JUNK[8].toNumber(),\n        PLANET_LEVEL_JUNK[9].toNumber(),\n      ],\n      ABANDON_SPEED_CHANGE_PERCENT: ABANDON_RANGE_CHANGE_PERCENT.toNumber(),\n      ABANDON_RANGE_CHANGE_PERCENT: ABANDON_SPEED_CHANGE_PERCENT.toNumber(),\n\n      PHOTOID_ACTIVATION_DELAY: PHOTOID_ACTIVATION_DELAY.toNumber(),\n      SPAWN_RIM_AREA: SPAWN_RIM_AREA.toNumber(),\n      LOCATION_REVEAL_COOLDOWN: LOCATION_REVEAL_COOLDOWN.toNumber(),\n\n      defaultPopulationCap: planetDefaults.populationCap,\n      defaultPopulationGrowth: planetDefaults.populationGrowth,\n      defaultRange: planetDefaults.range,\n      defaultSpeed: planetDefaults.speed,\n      defaultDefense: planetDefaults.defense,\n      defaultSilverGrowth: planetDefaults.silverGrowth,\n      defaultSilverCap: planetDefaults.silverCap,\n      defaultBarbarianPercentage: planetDefaults.barbarianPercentage,\n      planetLevelThresholds,\n      planetCumulativeRarities,\n      upgrades,\n\n      adminAddress,\n      // Capture Zones\n      GAME_START_BLOCK: GAME_START_BLOCK.toNumber(),\n      CAPTURE_ZONES_ENABLED,\n      CAPTURE_ZONE_COUNT: CAPTURE_ZONE_COUNT.toNumber(),\n      CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL.toNumber(),\n      CAPTURE_ZONE_RADIUS: CAPTURE_ZONE_RADIUS.toNumber(),\n      CAPTURE_ZONE_PLANET_LEVEL_SCORE: [\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[0].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[1].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[2].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[3].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[4].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[5].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[6].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[7].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[8].toNumber(),\n        CAPTURE_ZONE_PLANET_LEVEL_SCORE[9].toNumber(),\n      ],\n      CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED.toNumber(),\n      CAPTURE_ZONES_PER_5000_WORLD_RADIUS: CAPTURE_ZONES_PER_5000_WORLD_RADIUS.toNumber(),\n    };\n\n    return constants;\n  }\n\n  public async getPlayers(\n    onProgress?: (fractionCompleted: number) => void\n  ): Promise<Map<string, Player>> {\n    const nPlayers: number = (await this.makeCall<EthersBN>(this.contract.getNPlayers)).toNumber();\n\n    const players = await aggregateBulkGetter<Player>(\n      nPlayers,\n      200,\n      async (start, end) =>\n        (await this.makeCall(this.contract.bulkGetPlayers, [start, end])).map(decodePlayer),\n      onProgress\n    );\n\n    const playerMap: Map<EthAddress, Player> = new Map();\n    for (const player of players) {\n      playerMap.set(player.address, player);\n    }\n    return playerMap;\n  }\n\n  public async getPlayerById(playerId: EthAddress): Promise<Player | undefined> {\n    const rawPlayer = await this.makeCall(this.contract.players, [playerId]);\n    if (!rawPlayer.isInitialized) return undefined;\n    const player = decodePlayer(rawPlayer);\n\n    return player;\n  }\n\n  public async getWorldRadius(): Promise<number> {\n    const radius = (await this.makeCall<EthersBN>(this.contract.worldRadius)).toNumber();\n    return radius;\n  }\n\n  // timestamp since epoch (in seconds)\n  public async getTokenMintEndTimestamp(): Promise<number> {\n    const timestamp = (\n      await this.makeCall<EthersBN>(this.contract.TOKEN_MINT_END_TIMESTAMP)\n    ).toNumber();\n    return timestamp;\n  }\n\n  public async getArrival(arrivalId: number): Promise<QueuedArrival | undefined> {\n    const rawArrival = await this.makeCall(this.contract.planetArrivals, [arrivalId]);\n    return decodeArrival(rawArrival);\n  }\n\n  public async getArrivalsForPlanet(planetId: LocationId): Promise<QueuedArrival[]> {\n    const events = (\n      await this.makeCall(this.contract.getPlanetArrivals, [locationIdToDecStr(planetId)])\n    ).map(decodeArrival);\n\n    return events;\n  }\n\n  public async getAllArrivals(\n    planetsToLoad: LocationId[],\n    onProgress?: (fractionCompleted: number) => void\n  ): Promise<QueuedArrival[]> {\n    const arrivalsUnflattened = await aggregateBulkGetter<QueuedArrival[]>(\n      planetsToLoad.length,\n      200,\n      async (start, end) => {\n        return (\n          await this.makeCall(this.contract.bulkGetPlanetArrivalsByIds, [\n            planetsToLoad.slice(start, end).map(locationIdToDecStr),\n          ])\n        ).map((arrivals) => arrivals.map(decodeArrival));\n      },\n      onProgress\n    );\n\n    return _.flatten(arrivalsUnflattened);\n  }\n\n  public async getTouchedPlanetIds(\n    startingAt: number,\n    onProgress?: (fractionCompleted: number) => void\n  ): Promise<LocationId[]> {\n    const nPlanets: number = (await this.makeCall<EthersBN>(this.contract.getNPlanets)).toNumber();\n\n    const planetIds = await aggregateBulkGetter<EthersBN>(\n      nPlanets - startingAt,\n      1000,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetPlanetIds, [start + startingAt, end + startingAt]),\n      onProgress\n    );\n    return planetIds.map(locationIdFromEthersBN);\n  }\n\n  public async getRevealedCoordsByIdIfExists(\n    planetId: LocationId\n  ): Promise<RevealedCoords | undefined> {\n    const decStrId = locationIdToDecStr(planetId);\n    const rawRevealedCoords = await this.makeCall(this.contract.revealedCoords, [decStrId]);\n    const ret = decodeRevealedCoords(rawRevealedCoords);\n    if (ret.hash === EMPTY_LOCATION_ID) {\n      return undefined;\n    }\n    return ret;\n  }\n\n  public async getIsPaused(): Promise<boolean> {\n    return this.makeCall(this.contract.paused);\n  }\n\n  public async getRevealedPlanetsCoords(\n    startingAt: number,\n    onProgressIds?: (fractionCompleted: number) => void,\n    onProgressCoords?: (fractionCompleted: number) => void\n  ): Promise<RevealedCoords[]> {\n    const nRevealedPlanets: number = (\n      await this.makeCall<EthersBN>(this.contract.getNRevealedPlanets)\n    ).toNumber();\n\n    const rawRevealedPlanetIds = await aggregateBulkGetter<EthersBN>(\n      nRevealedPlanets - startingAt,\n      500,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetRevealedPlanetIds, [\n          start + startingAt,\n          end + startingAt,\n        ]),\n      onProgressIds\n    );\n\n    const rawRevealedCoords = await aggregateBulkGetter(\n      rawRevealedPlanetIds.length,\n      500,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetRevealedCoordsByIds, [\n          rawRevealedPlanetIds.slice(start, end),\n        ]),\n      onProgressCoords\n    );\n\n    return rawRevealedCoords.map(decodeRevealedCoords);\n  }\n\n  public async bulkGetPlanets(\n    toLoadPlanets: LocationId[],\n    onProgressPlanet?: (fractionCompleted: number) => void,\n    onProgressMetadata?: (fractionCompleted: number) => void\n  ): Promise<Map<LocationId, Planet>> {\n    const rawPlanets = await aggregateBulkGetter(\n      toLoadPlanets.length,\n      200,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetPlanetsByIds, [\n          toLoadPlanets.slice(start, end).map(locationIdToDecStr),\n        ]),\n      onProgressPlanet\n    );\n\n    const rawPlanetsExtendedInfo = await aggregateBulkGetter(\n      toLoadPlanets.length,\n      200,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetPlanetsExtendedInfoByIds, [\n          toLoadPlanets.slice(start, end).map(locationIdToDecStr),\n        ]),\n      (fractionCompleted) => {\n        if (!onProgressMetadata) return;\n        onProgressMetadata(fractionCompleted / 2);\n      }\n    );\n\n    const rawPlanetsExtendedInfo2 = await aggregateBulkGetter(\n      toLoadPlanets.length,\n      200,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetPlanetsExtendedInfo2ByIds, [\n          toLoadPlanets.slice(start, end).map(locationIdToDecStr),\n        ]),\n      (fractionCompleted) => {\n        if (!onProgressMetadata) return;\n        onProgressMetadata(0.5 + fractionCompleted / 2);\n      }\n    );\n\n    const planets: Map<LocationId, Planet> = new Map();\n\n    for (let i = 0; i < toLoadPlanets.length; i += 1) {\n      if (!!rawPlanets[i] && !!rawPlanetsExtendedInfo[i]) {\n        const planet = decodePlanet(\n          locationIdToDecStr(toLoadPlanets[i]),\n          rawPlanets[i],\n          rawPlanetsExtendedInfo[i],\n          rawPlanetsExtendedInfo2[i]\n        );\n        planet.transactions = new TxCollection();\n        planets.set(planet.locationId, planet);\n      }\n    }\n    return planets;\n  }\n\n  public async getPlanetById(planetId: LocationId): Promise<Planet | undefined> {\n    const decStrId = locationIdToDecStr(planetId);\n    const rawExtendedInfo = await this.makeCall(this.contract.planetsExtendedInfo, [decStrId]);\n    const rawExtendedInfo2 = await this.makeCall(this.contract.planetsExtendedInfo2, [decStrId]);\n\n    if (!rawExtendedInfo[0]) return undefined; // planetExtendedInfo.isInitialized is false\n    if (!rawExtendedInfo2[0]) return undefined; // planetExtendedInfo.isInitialized is false\n    const rawPlanet = await this.makeCall(this.contract.planets, [decStrId]);\n    return decodePlanet(decStrId, rawPlanet, rawExtendedInfo, rawExtendedInfo2);\n  }\n\n  public async getArtifactById(artifactId: ArtifactId): Promise<Artifact | undefined> {\n    const exists = await this.makeCall<boolean>(this.contract.doesArtifactExist, [\n      artifactIdToDecStr(artifactId),\n    ]);\n    if (!exists) return undefined;\n    const rawArtifact = await this.makeCall(this.contract.getArtifactById, [\n      artifactIdToDecStr(artifactId),\n    ]);\n\n    const artifact = decodeArtifact(rawArtifact);\n    artifact.transactions = new TxCollection();\n    return artifact;\n  }\n\n  public async bulkGetArtifactsOnPlanets(\n    locationIds: LocationId[],\n    onProgress?: (fractionCompleted: number) => void\n  ): Promise<Artifact[][]> {\n    const rawArtifacts = await aggregateBulkGetter(\n      locationIds.length,\n      200,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetPlanetArtifacts, [\n          locationIds.slice(start, end).map(locationIdToDecStr),\n        ]),\n      onProgress\n    );\n\n    return rawArtifacts.map((rawArtifactArray) => {\n      return rawArtifactArray.map(decodeArtifact);\n    });\n  }\n\n  public async bulkGetArtifacts(\n    artifactIds: ArtifactId[],\n    onProgress?: (fractionCompleted: number) => void\n  ): Promise<Artifact[]> {\n    const rawArtifacts = await aggregateBulkGetter(\n      artifactIds.length,\n      200,\n      async (start, end) =>\n        await this.makeCall(this.contract.bulkGetArtifactsByIds, [\n          artifactIds.slice(start, end).map(artifactIdToDecStr),\n        ]),\n      onProgress\n    );\n\n    const ret: Artifact[] = rawArtifacts.map(decodeArtifact);\n    ret.forEach((a) => (a.transactions = new TxCollection()));\n\n    return ret;\n  }\n\n  public async getPlayerArtifacts(\n    playerId?: EthAddress,\n    onProgress?: (percent: number) => void\n  ): Promise<Artifact[]> {\n    if (playerId === undefined) return [];\n\n    const myArtifactIds = (await this.makeCall(this.contract.getPlayerArtifactIds, [playerId])).map(\n      artifactIdFromEthersBN\n    );\n    return this.bulkGetArtifacts(myArtifactIds, onProgress);\n  }\n\n  public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) {\n    this.contractCaller.setDiagnosticUpdater(diagnosticUpdater);\n    this.txExecutor?.setDiagnosticUpdater(diagnosticUpdater);\n    this.ethConnection.setDiagnosticUpdater(diagnosticUpdater);\n  }\n\n  public async submitTransaction<T extends TxIntent>(\n    txIntent: T,\n    overrides?: providers.TransactionRequest\n  ): Promise<Transaction<T>> {\n    const queuedTx = await this.txExecutor.queueTransaction(txIntent, overrides);\n\n    this.emit(ContractsAPIEvent.TxQueued, queuedTx);\n    // TODO: Why is this setTimeout here? Can it be removed?\n    setTimeout(() => this.emitTransactionEvents(queuedTx), 0);\n\n    return queuedTx;\n  }\n\n  /**\n   * Remove a transaction from the queue.\n   */\n  public cancelTransaction(tx: Transaction): void {\n    this.txExecutor.dequeueTransction(tx);\n    this.emit(ContractsAPIEvent.TxCancelled, tx);\n  }\n\n  /**\n   * Make sure this transaction is the next to be executed.\n   */\n  public prioritizeTransaction(tx: Transaction): void {\n    this.txExecutor.prioritizeTransaction(tx);\n    this.emit(ContractsAPIEvent.TxPrioritized, tx);\n  }\n\n  /**\n   * This is a strange interface between the transaction queue system and the rest of the game. The\n   * strange thing about it is that introduces another way by which transactions are pushed into the\n   * game - these {@code ContractsAPIEvent} events.\n   */\n  public emitTransactionEvents(tx: Transaction): void {\n    tx.submittedPromise\n      .then(() => {\n        this.emit(ContractsAPIEvent.TxSubmitted, tx);\n      })\n      .catch(() => {\n        this.emit(ContractsAPIEvent.TxErrored, tx);\n      });\n\n    tx.confirmedPromise\n      .then(() => {\n        this.emit(ContractsAPIEvent.TxConfirmed, tx);\n      })\n      .catch(() => {\n        this.emit(ContractsAPIEvent.TxErrored, tx);\n      });\n  }\n\n  public getAddress() {\n    return this.ethConnection.getAddress();\n  }\n}\n\nexport async function makeContractsAPI({\n  connection,\n  contractAddress,\n}: ContractsApiConfig): Promise<ContractsAPI> {\n  await connection.loadContract(contractAddress, loadDiamondContract);\n\n  return new ContractsAPI({ connection, contractAddress });\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/GameManager.ts",
    "content": "import {\n  BLOCK_EXPLORER_URL,\n  CONTRACT_PRECISION,\n  EMPTY_ADDRESS,\n  MIN_PLANET_LEVEL,\n} from '@darkforest_eth/constants';\nimport type { DarkForest } from '@darkforest_eth/contracts/typechain';\nimport { monomitter, Monomitter, Subscription } from '@darkforest_eth/events';\nimport {\n  getRange,\n  isActivated,\n  isLocatable,\n  isSpaceShip,\n  timeUntilNextBroadcastAvailable,\n} from '@darkforest_eth/gamelogic';\nimport { fakeHash, mimcHash, perlin } from '@darkforest_eth/hashing';\nimport {\n  createContract,\n  EthConnection,\n  ThrottledConcurrentQueue,\n  verifySignature,\n  weiToEth,\n} from '@darkforest_eth/network';\nimport { getPlanetName } from '@darkforest_eth/procedural';\nimport {\n  artifactIdToDecStr,\n  isUnconfirmedActivateArtifactTx,\n  isUnconfirmedBuyHatTx,\n  isUnconfirmedCapturePlanetTx,\n  isUnconfirmedDeactivateArtifactTx,\n  isUnconfirmedDepositArtifactTx,\n  isUnconfirmedFindArtifactTx,\n  isUnconfirmedInitTx,\n  isUnconfirmedInvadePlanetTx,\n  isUnconfirmedMoveTx,\n  isUnconfirmedProspectPlanetTx,\n  isUnconfirmedRevealTx,\n  isUnconfirmedUpgradeTx,\n  isUnconfirmedWithdrawArtifactTx,\n  isUnconfirmedWithdrawSilverTx,\n  locationIdFromBigInt,\n  locationIdToDecStr,\n} from '@darkforest_eth/serde';\nimport {\n  Artifact,\n  ArtifactId,\n  ArtifactRarity,\n  ArtifactType,\n  CaptureZone,\n  Chunk,\n  ClaimedCoords,\n  ClaimedLocation,\n  Diagnostics,\n  EthAddress,\n  LocatablePlanet,\n  LocationId,\n  NetworkHealthSummary,\n  Planet,\n  PlanetLevel,\n  PlanetMessageType,\n  PlanetType,\n  Player,\n  QueuedArrival,\n  Radii,\n  Rectangle,\n  RevealedCoords,\n  RevealedLocation,\n  Setting,\n  SignedMessage,\n  SpaceType,\n  Transaction,\n  TxIntent,\n  UnconfirmedActivateArtifact,\n  UnconfirmedBuyHat,\n  UnconfirmedCapturePlanet,\n  UnconfirmedDeactivateArtifact,\n  UnconfirmedDepositArtifact,\n  UnconfirmedFindArtifact,\n  UnconfirmedInit,\n  UnconfirmedInvadePlanet,\n  UnconfirmedMove,\n  UnconfirmedPlanetTransfer,\n  UnconfirmedProspectPlanet,\n  UnconfirmedReveal,\n  UnconfirmedUpgrade,\n  UnconfirmedWithdrawArtifact,\n  UnconfirmedWithdrawSilver,\n  Upgrade,\n  VoyageId,\n  WorldCoords,\n  WorldLocation,\n  Wormhole,\n} from '@darkforest_eth/types';\nimport bigInt, { BigInteger } from 'big-integer';\nimport delay from 'delay';\nimport { BigNumber, Contract, ContractInterface, providers } from 'ethers';\nimport { EventEmitter } from 'events';\nimport NotificationManager from '../../Frontend/Game/NotificationManager';\nimport { MIN_CHUNK_SIZE } from '../../Frontend/Utils/constants';\nimport { Diff, generateDiffEmitter, getDisposableEmitter } from '../../Frontend/Utils/EmitterUtils';\nimport {\n  getBooleanSetting,\n  getNumberSetting,\n  pollSetting,\n  setBooleanSetting,\n  setSetting,\n  settingChanged$,\n} from '../../Frontend/Utils/SettingsHooks';\nimport { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes';\nimport UIEmitter from '../../Frontend/Utils/UIEmitter';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\nimport {\n  ContractConstants,\n  ContractsAPIEvent,\n  MoveArgIdxs,\n  MoveArgs,\n  ZKArgIdx,\n} from '../../_types/darkforest/api/ContractsAPITypes';\nimport { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes';\nimport { HashConfig, RevealCountdownInfo } from '../../_types/global/GlobalTypes';\nimport MinerManager, { HomePlanetMinerChunkStore, MinerManagerEvent } from '../Miner/MinerManager';\nimport {\n  MiningPattern,\n  SpiralPattern,\n  SwissCheesePattern,\n  TowardsCenterPattern,\n  TowardsCenterPatternV2,\n} from '../Miner/MiningPatterns';\nimport { eventLogger, EventType } from '../Network/EventLogger';\nimport { loadLeaderboard } from '../Network/LeaderboardApi';\nimport { addMessage, deleteMessages, getMessagesOnPlanets } from '../Network/MessageAPI';\nimport { loadNetworkHealth } from '../Network/NetworkHealthApi';\nimport {\n  disconnectTwitter,\n  getAllTwitters,\n  verifyTwitterHandle,\n} from '../Network/UtilityServerAPI';\nimport { SerializedPlugin } from '../Plugins/SerializedPlugin';\nimport PersistentChunkStore from '../Storage/PersistentChunkStore';\nimport { easeInAnimation, emojiEaseOutAnimation } from '../Utils/Animation';\nimport SnarkArgsHelper from '../Utils/SnarkArgsHelper';\nimport { hexifyBigIntNestedArray } from '../Utils/Utils';\nimport { getEmojiMessage } from './ArrivalUtils';\nimport { CaptureZoneGenerator, CaptureZonesGeneratedEvent } from './CaptureZoneGenerator';\nimport { ContractsAPI, makeContractsAPI } from './ContractsAPI';\nimport { GameObjects } from './GameObjects';\nimport { InitialGameStateDownloader } from './InitialGameStateDownloader';\n\nexport enum GameManagerEvent {\n  PlanetUpdate = 'PlanetUpdate',\n  DiscoveredNewChunk = 'DiscoveredNewChunk',\n  InitializedPlayer = 'InitializedPlayer',\n  InitializedPlayerError = 'InitializedPlayerError',\n  ArtifactUpdate = 'ArtifactUpdate',\n  Moved = 'Moved',\n}\n\nclass GameManager extends EventEmitter {\n  /**\n   * This variable contains the internal state of objects that live in the game world.\n   */\n  private readonly entityStore: GameObjects;\n\n  /**\n   * Kind of hacky, but we store a reference to the terminal that the player sees when the initially\n   * load into the game. This is the same exact terminal that appears inside the collapsable right\n   * bar of the game.\n   */\n  private readonly terminal: React.MutableRefObject<TerminalHandle | undefined>;\n\n  /**\n   * The ethereum address of the player who is currently logged in. We support 'no account',\n   * represented by `undefined` in the case when you want to simply load the game state from the\n   * contract and view it without be able to make any moves.\n   */\n  private readonly account: EthAddress | undefined;\n\n  /**\n   * Map from ethereum addresses to player objects. This isn't stored in {@link GameObjects},\n   * because it's not techincally an entity that exists in the world. A player just controls planets\n   * and artifacts that do exist in the world.\n   *\n   * @todo move this into a new `Players` class.\n   */\n  private readonly players: Map<string, Player>;\n\n  /**\n   * Allows us to make contract calls, and execute transactions. Be careful about how you use this\n   * guy. You don't want to cause your client to send an excessive amount of traffic to whatever\n   * node you're connected to.\n   *\n   * Interacting with the blockchain isn't free, and we need to be mindful about about the way our\n   * application interacts with the blockchain. The current rate limiting strategy consists of three\n   * points:\n   *\n   * - data that needs to be fetched often should be fetched in bulk.\n   * - rate limit smart contract calls (reads from the blockchain), implemented by\n   *   {@link ContractCaller} and transactions (writes to the blockchain on behalf of the player),\n   *   implemented by {@link TxExecutor} via two separately tuned {@link ThrottledConcurrentQueue}s.\n   */\n  private readonly contractsAPI: ContractsAPI;\n\n  /**\n   * An object that syncs any newly added or deleted chunks to the player's IndexedDB.\n   *\n   * @todo it also persists other game data to IndexedDB. This class needs to be renamed `GameSaver`\n   * or something like that.\n   */\n  private readonly persistentChunkStore: PersistentChunkStore;\n\n  /**\n   * Responsible for generating snark proofs.\n   */\n  private readonly snarkHelper: SnarkArgsHelper;\n\n  /**\n   * In debug builds of the game, we can connect to a set of contracts deployed to a local\n   * blockchain, which are tweaked to not verify planet hashes, meaning we can use a faster hash\n   * function with similar properties to mimc. This allows us to mine the map faster in debug mode.\n   *\n   * @todo move this into a separate `GameConfiguration` class.\n   */\n  private readonly useMockHash: boolean;\n\n  /**\n   * Game parameters set by the contract. Stuff like perlin keys, which are important for mining the\n   * correct universe, or the time multiplier, which allows us to tune how quickly voyages go.\n   *\n   * @todo move this into a separate `GameConfiguration` class.\n   */\n  private readonly contractConstants: ContractConstants;\n\n  private paused: boolean;\n\n  /**\n   * @todo change this to the correct timestamp each round.\n   */\n  private readonly endTimeSeconds: number = 1948939200; // new Date(\"2031-10-05T04:00:00.000Z\").getTime() / 1000\n\n  /**\n   * An interface to the blockchain that is a little bit lower-level than {@link ContractsAPI}. It\n   * allows us to do basic operations such as wait for a transaction to complete, check the player's\n   * address and balance, etc.\n   */\n  private readonly ethConnection: EthConnection;\n\n  /**\n   * Each round we change the hash configuration of the game. The hash configuration is download\n   * from the blockchain, and essentially acts as a salt, permuting the universe into a unique\n   * configuration for each new round.\n   *\n   * @todo deduplicate this and `useMockHash` somehow.\n   */\n  private readonly hashConfig: HashConfig;\n\n  /**\n   * The aforementioned hash function. In debug mode where `DISABLE_ZK_CHECKS` is on, we use a\n   * faster hash function. Othewise, in production mode, use MiMC hash (https://byt3bit.github.io/primesym/).\n   */\n  private readonly planetHashMimc: (...inputs: number[]) => BigInteger;\n\n  /**\n   * Whenever we refresh the players twitter accounts or scores, we publish an event here.\n   */\n  public readonly playersUpdated$: Monomitter<void>;\n\n  /**\n   * Handle to an interval that periodically uploads diagnostic information from this client.\n   */\n  private diagnosticsInterval: ReturnType<typeof setInterval>;\n\n  /**\n   * Handle to an interval that periodically refreshes some information about the player from the\n   * blockchain.\n   *\n   * @todo move this into a new `PlayerState` class.\n   */\n  private playerInterval: ReturnType<typeof setInterval>;\n\n  /**\n   * Handle to an interval that periodically refreshes the scoreboard from our webserver.\n   */\n  private scoreboardInterval: ReturnType<typeof setInterval>;\n\n  /**\n   * Handle to an interval that periodically refreshes the network's health from our webserver.\n   */\n  private networkHealthInterval: ReturnType<typeof setInterval>;\n\n  /**\n   * Manages the process of mining new space territory.\n   */\n  private minerManager?: MinerManager;\n\n  /**\n   * Continuously updated value representing the total hashes per second that the game is currently\n   * mining the universe at.\n   *\n   * @todo keep this in {@link MinerManager}\n   */\n  private hashRate: number;\n\n  /**\n   * The spawn location of the current player.\n   *\n   * @todo, make this smarter somehow. It's really annoying to have to import world coordinates, and\n   * get them wrong or something. Maybe we need to mark a planet, once it's been initialized\n   * contract-side, as the homeworld of the user who initialized on it. That way, when you import a\n   * new account into the game, and you import map data that contains your home planet, the client\n   * would be able to automatically detect which planet is the player's home planet.\n   *\n   * @todo move this into a new `PlayerState` class.\n   */\n  private homeLocation: WorldLocation | undefined;\n\n  /**\n   * Sometimes the universe gets bigger... Sometimes it doesn't.\n   *\n   * @todo move this into a new `GameConfiguration` class.\n   */\n  private worldRadius: number;\n\n  /**\n   * Emits whenever we load the network health summary from the webserver, which is derived from\n   * diagnostics that the client sends up to the webserver as well.\n   */\n  public networkHealth$: Monomitter<NetworkHealthSummary>;\n\n  public paused$: Monomitter<boolean>;\n\n  /**\n   * Diagnostic information about the game.\n   */\n  private diagnostics: Diagnostics;\n\n  /**\n   * Subscription to act on setting changes\n   */\n  private settingsSubscription: Subscription | undefined;\n\n  /**\n   * Setting to allow players to start game without plugins that were running during the previous\n   * run of the game client. By default, the game launches plugins that were running that were\n   * running when the game was last closed.\n   */\n  private safeMode: boolean;\n\n  public get planetRarity(): number {\n    return this.contractConstants.PLANET_RARITY;\n  }\n\n  /**\n   * Generates capture zones.\n   */\n  private captureZoneGenerator: CaptureZoneGenerator | undefined;\n\n  private constructor(\n    terminal: React.MutableRefObject<TerminalHandle | undefined>,\n    account: EthAddress | undefined,\n    players: Map<string, Player>,\n    touchedPlanets: Map<LocationId, Planet>,\n    allTouchedPlanetIds: Set<LocationId>,\n    revealedCoords: Map<LocationId, RevealedCoords>,\n    claimedCoords: Map<LocationId, ClaimedCoords>,\n    worldRadius: number,\n    unprocessedArrivals: Map<VoyageId, QueuedArrival>,\n    unprocessedPlanetArrivalIds: Map<LocationId, VoyageId[]>,\n    contractsAPI: ContractsAPI,\n    contractConstants: ContractConstants,\n    persistentChunkStore: PersistentChunkStore,\n    snarkHelper: SnarkArgsHelper,\n    homeLocation: WorldLocation | undefined,\n    useMockHash: boolean,\n    artifacts: Map<ArtifactId, Artifact>,\n    ethConnection: EthConnection,\n    paused: boolean\n  ) {\n    super();\n\n    this.diagnostics = {\n      rpcUrl: 'unknown',\n      totalPlanets: 0,\n      visiblePlanets: 0,\n      visibleChunks: 0,\n      fps: 0,\n      chunkUpdates: 0,\n      callsInQueue: 0,\n      totalCalls: 0,\n      totalTransactions: 0,\n      transactionsInQueue: 0,\n      totalChunks: 0,\n      width: 0,\n      height: 0,\n    };\n    this.terminal = terminal;\n    this.account = account;\n    this.players = players;\n    this.worldRadius = worldRadius;\n    this.networkHealth$ = monomitter(true);\n    this.paused$ = monomitter(true);\n    this.playersUpdated$ = monomitter();\n\n    if (contractConstants.CAPTURE_ZONES_ENABLED) {\n      this.captureZoneGenerator = new CaptureZoneGenerator(\n        this,\n        contractConstants.GAME_START_BLOCK,\n        contractConstants.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL\n      );\n    }\n\n    this.hashConfig = {\n      planetHashKey: contractConstants.PLANETHASH_KEY,\n      spaceTypeKey: contractConstants.SPACETYPE_KEY,\n      biomebaseKey: contractConstants.BIOMEBASE_KEY,\n      perlinLengthScale: contractConstants.PERLIN_LENGTH_SCALE,\n      perlinMirrorX: contractConstants.PERLIN_MIRROR_X,\n      perlinMirrorY: contractConstants.PERLIN_MIRROR_Y,\n      planetRarity: contractConstants.PLANET_RARITY,\n    };\n    this.planetHashMimc = useMockHash\n      ? fakeHash(this.hashConfig.planetRarity)\n      : mimcHash(this.hashConfig.planetHashKey);\n\n    this.contractConstants = contractConstants;\n    this.homeLocation = homeLocation;\n\n    const revealedLocations = new Map<LocationId, RevealedLocation>();\n    for (const [locationId, coords] of revealedCoords) {\n      const planet = touchedPlanets.get(locationId);\n      if (planet) {\n        const location: WorldLocation = {\n          hash: locationId,\n          coords,\n          perlin: planet.perlin,\n          biomebase: this.biomebasePerlin(coords, true),\n        };\n        revealedLocations.set(locationId, { ...location, revealer: coords.revealer });\n      }\n    }\n\n    const claimedLocations = new Map<LocationId, ClaimedLocation>();\n\n    for (const [locationId, coords] of claimedCoords) {\n      const planet = touchedPlanets.get(locationId);\n\n      if (planet) {\n        const location: WorldLocation = {\n          hash: locationId,\n          coords,\n          perlin: planet.perlin,\n          biomebase: this.biomebasePerlin(coords, true),\n        };\n\n        const revealedLocation = { ...location, revealer: coords.revealer };\n\n        revealedLocations.set(locationId, revealedLocation);\n        claimedLocations.set(locationId, revealedLocation);\n      }\n    }\n\n    this.entityStore = new GameObjects(\n      account,\n      touchedPlanets,\n      allTouchedPlanetIds,\n      revealedLocations,\n      claimedLocations,\n      artifacts,\n      persistentChunkStore.allChunks(),\n      unprocessedArrivals,\n      unprocessedPlanetArrivalIds,\n      contractConstants,\n      worldRadius\n    );\n\n    this.contractsAPI = contractsAPI;\n    this.persistentChunkStore = persistentChunkStore;\n    this.snarkHelper = snarkHelper;\n    this.useMockHash = useMockHash;\n    this.paused = paused;\n\n    this.ethConnection = ethConnection;\n\n    this.diagnosticsInterval = setInterval(this.uploadDiagnostics.bind(this), 10_000);\n    this.scoreboardInterval = setInterval(this.refreshScoreboard.bind(this), 10_000);\n    this.networkHealthInterval = setInterval(this.refreshNetworkHealth.bind(this), 10_000);\n\n    this.playerInterval = setInterval(() => {\n      if (this.account) {\n        this.hardRefreshPlayer(this.account);\n      }\n    }, 5000);\n\n    this.hashRate = 0;\n\n    this.settingsSubscription = settingChanged$.subscribe((setting: Setting) => {\n      if (setting === Setting.MiningCores) {\n        if (this.minerManager) {\n          const config = {\n            contractAddress: this.getContractAddress(),\n            account: this.account,\n          };\n          const cores = getNumberSetting(config, Setting.MiningCores);\n          this.minerManager.setCores(cores);\n        }\n      }\n    });\n\n    this.refreshScoreboard();\n    this.refreshNetworkHealth();\n    this.getSpaceships();\n\n    this.safeMode = false;\n  }\n\n  private async uploadDiagnostics() {\n    eventLogger.logEvent(EventType.Diagnostics, this.diagnostics);\n  }\n\n  private async refreshNetworkHealth() {\n    try {\n      this.networkHealth$.publish(await loadNetworkHealth());\n    } catch (e) {\n      // @todo - what do we do if we can't connect to the webserver\n    }\n  }\n\n  private async refreshScoreboard() {\n    try {\n      const leaderboard = await loadLeaderboard();\n\n      for (const entry of leaderboard.entries) {\n        const player = this.players.get(entry.ethAddress);\n        if (player) {\n          // current player's score is updated via `this.playerInterval`\n          if (player.address !== this.account && entry.score !== undefined) {\n            player.score = entry.score;\n          }\n        }\n      }\n\n      this.playersUpdated$.publish();\n    } catch (e) {\n      // @todo - what do we do if we can't connect to the webserver? in general this should be a\n      // valid state of affairs because arenas is a thing.\n    }\n  }\n\n  public getEthConnection() {\n    return this.ethConnection;\n  }\n\n  public destroy(): void {\n    // removes singletons of ContractsAPI, LocalStorageManager, MinerManager\n    if (this.minerManager) {\n      this.minerManager.removeAllListeners(MinerManagerEvent.DiscoveredNewChunk);\n      this.minerManager.destroy();\n    }\n    this.contractsAPI.destroy();\n    this.persistentChunkStore.destroy();\n    clearInterval(this.playerInterval);\n    clearInterval(this.diagnosticsInterval);\n    clearInterval(this.scoreboardInterval);\n    clearInterval(this.networkHealthInterval);\n    this.settingsSubscription?.unsubscribe();\n  }\n\n  static async create({\n    connection,\n    terminal,\n    contractAddress,\n  }: {\n    connection: EthConnection;\n    terminal: React.MutableRefObject<TerminalHandle | undefined>;\n    contractAddress: EthAddress;\n  }): Promise<GameManager> {\n    if (!terminal.current) {\n      throw new Error('you must pass in a handle to a terminal');\n    }\n\n    const account = connection.getAddress();\n\n    if (!account) {\n      throw new Error('no account on eth connection');\n    }\n\n    const gameStateDownloader = new InitialGameStateDownloader(terminal.current);\n    const contractsAPI = await makeContractsAPI({ connection, contractAddress });\n\n    terminal.current?.println('Loading game data from disk...');\n\n    const persistentChunkStore = await PersistentChunkStore.create({ account, contractAddress });\n\n    terminal.current?.println('Downloading data from Ethereum blockchain...');\n    terminal.current?.println('(the contract is very big. this may take a while)');\n    terminal.current?.newline();\n\n    const initialState = await gameStateDownloader.download(contractsAPI, persistentChunkStore);\n    const possibleHomes = await persistentChunkStore.getHomeLocations();\n\n    terminal.current?.println('');\n    terminal.current?.println('Building Index...');\n\n    await persistentChunkStore.saveTouchedPlanetIds(initialState.allTouchedPlanetIds);\n    await persistentChunkStore.saveRevealedCoords(initialState.allRevealedCoords);\n\n    const knownArtifacts: Map<ArtifactId, Artifact> = new Map();\n\n    for (let i = 0; i < initialState.loadedPlanets.length; i++) {\n      const planet = initialState.touchedAndLocatedPlanets.get(initialState.loadedPlanets[i]);\n\n      if (!planet) {\n        continue;\n      }\n\n      planet.heldArtifactIds = initialState.heldArtifacts[i].map((a) => a.id);\n\n      for (const heldArtifact of initialState.heldArtifacts[i]) {\n        knownArtifacts.set(heldArtifact.id, heldArtifact);\n      }\n    }\n\n    for (const myArtifact of initialState.myArtifacts) {\n      knownArtifacts.set(myArtifact.id, myArtifact);\n    }\n\n    for (const artifact of initialState.artifactsOnVoyages) {\n      knownArtifacts.set(artifact.id, artifact);\n    }\n\n    // figure out what's my home planet\n    let homeLocation: WorldLocation | undefined = undefined;\n    for (const loc of possibleHomes) {\n      if (initialState.allTouchedPlanetIds.includes(loc.hash)) {\n        homeLocation = loc;\n        await persistentChunkStore.confirmHomeLocation(loc);\n        break;\n      }\n    }\n\n    const hashConfig: HashConfig = {\n      planetHashKey: initialState.contractConstants.PLANETHASH_KEY,\n      spaceTypeKey: initialState.contractConstants.SPACETYPE_KEY,\n      biomebaseKey: initialState.contractConstants.BIOMEBASE_KEY,\n      perlinLengthScale: initialState.contractConstants.PERLIN_LENGTH_SCALE,\n      perlinMirrorX: initialState.contractConstants.PERLIN_MIRROR_X,\n      perlinMirrorY: initialState.contractConstants.PERLIN_MIRROR_Y,\n      planetRarity: initialState.contractConstants.PLANET_RARITY,\n    };\n\n    const useMockHash = initialState.contractConstants.DISABLE_ZK_CHECKS;\n    const snarkHelper = SnarkArgsHelper.create(hashConfig, terminal, useMockHash);\n\n    const gameManager = new GameManager(\n      terminal,\n      account,\n      initialState.players,\n      initialState.touchedAndLocatedPlanets,\n      new Set(Array.from(initialState.allTouchedPlanetIds)),\n      initialState.revealedCoordsMap,\n      initialState.claimedCoordsMap\n        ? initialState.claimedCoordsMap\n        : new Map<LocationId, ClaimedCoords>(),\n      initialState.worldRadius,\n      initialState.arrivals,\n      initialState.planetVoyageIdMap,\n      contractsAPI,\n      initialState.contractConstants,\n      persistentChunkStore,\n      snarkHelper,\n      homeLocation,\n      useMockHash,\n      knownArtifacts,\n      connection,\n      initialState.paused\n    );\n\n    gameManager.setPlayerTwitters(initialState.twitters);\n\n    const config = {\n      contractAddress,\n      account: gameManager.getAccount(),\n    };\n    pollSetting(config, Setting.AutoApproveNonPurchaseTransactions);\n\n    persistentChunkStore.setDiagnosticUpdater(gameManager);\n    contractsAPI.setDiagnosticUpdater(gameManager);\n\n    // important that this happens AFTER we load the game state from the blockchain. Otherwise our\n    // 'loading game state' contract calls will be competing with events from the blockchain that\n    // are happening now, which makes no sense.\n    contractsAPI.setupEventListeners();\n\n    // get twitter handles\n    gameManager.refreshTwitters();\n\n    gameManager.listenForNewBlock();\n\n    // set up listeners: whenever ContractsAPI reports some game state update, do some logic\n    gameManager.contractsAPI\n      .on(ContractsAPIEvent.ArtifactUpdate, async (artifactId: ArtifactId) => {\n        await gameManager.hardRefreshArtifact(artifactId);\n        gameManager.emit(GameManagerEvent.ArtifactUpdate, artifactId);\n      })\n      .on(\n        ContractsAPIEvent.PlanetTransferred,\n        async (planetId: LocationId, newOwner: EthAddress) => {\n          await gameManager.hardRefreshPlanet(planetId);\n          const planetAfter = gameManager.getPlanetWithId(planetId);\n\n          if (planetAfter && newOwner === gameManager.account) {\n            NotificationManager.getInstance().receivedPlanet(planetAfter);\n          }\n        }\n      )\n      .on(ContractsAPIEvent.PlayerUpdate, async (playerId: EthAddress) => {\n        await gameManager.hardRefreshPlayer(playerId);\n      })\n      .on(ContractsAPIEvent.PauseStateChanged, async (paused: boolean) => {\n        gameManager.paused = paused;\n        gameManager.paused$.publish(paused);\n      })\n      .on(ContractsAPIEvent.PlanetUpdate, async (planetId: LocationId) => {\n        // don't reload planets that you don't have in your map. once a planet\n        // is in your map it will be loaded from the contract.\n        const localPlanet = gameManager.entityStore.getPlanetWithId(planetId);\n        if (localPlanet && isLocatable(localPlanet)) {\n          await gameManager.hardRefreshPlanet(planetId);\n          gameManager.emit(GameManagerEvent.PlanetUpdate);\n        }\n      })\n      .on(\n        ContractsAPIEvent.ArrivalQueued,\n        async (_arrivalId: VoyageId, fromId: LocationId, toId: LocationId) => {\n          // only reload planets if the toPlanet is in the map\n          const localToPlanet = gameManager.entityStore.getPlanetWithId(toId);\n          if (localToPlanet && isLocatable(localToPlanet)) {\n            await gameManager.bulkHardRefreshPlanets([fromId, toId]);\n            gameManager.emit(GameManagerEvent.PlanetUpdate);\n          }\n        }\n      )\n      .on(\n        ContractsAPIEvent.LocationRevealed,\n        async (planetId: LocationId, _revealer: EthAddress) => {\n          // TODO: hook notifs or emit event to UI if you want\n          await gameManager.hardRefreshPlanet(planetId);\n          gameManager.emit(GameManagerEvent.PlanetUpdate);\n        }\n      )\n      .on(ContractsAPIEvent.TxQueued, (tx: Transaction) => {\n        gameManager.entityStore.onTxIntent(tx);\n      })\n      .on(ContractsAPIEvent.TxSubmitted, (tx: Transaction) => {\n        gameManager.persistentChunkStore.onEthTxSubmit(tx);\n        gameManager.onTxSubmit(tx);\n      })\n      .on(ContractsAPIEvent.TxConfirmed, async (tx: Transaction) => {\n        if (!tx.hash) return; // this should never happen\n        gameManager.persistentChunkStore.onEthTxComplete(tx.hash);\n\n        if (isUnconfirmedRevealTx(tx)) {\n          await gameManager.hardRefreshPlanet(tx.intent.locationId);\n        } else if (isUnconfirmedInitTx(tx)) {\n          terminal.current?.println('Loading Home Planet from Blockchain...');\n          const retries = 5;\n          for (let i = 0; i < retries; i++) {\n            const planet = await gameManager.contractsAPI.getPlanetById(tx.intent.locationId);\n            if (planet) {\n              break;\n            } else if (i === retries - 1) {\n              console.error(\"couldn't load player's home planet\");\n            } else {\n              await delay(2000);\n            }\n          }\n          await gameManager.hardRefreshPlanet(tx.intent.locationId);\n          // mining manager should be initialized already via joinGame, but just in case...\n          gameManager.initMiningManager(tx.intent.location.coords, 4);\n        } else if (isUnconfirmedMoveTx(tx)) {\n          const promises = [gameManager.bulkHardRefreshPlanets([tx.intent.from, tx.intent.to])];\n          if (tx.intent.artifact) {\n            promises.push(gameManager.hardRefreshArtifact(tx.intent.artifact));\n          }\n          await Promise.all(promises);\n        } else if (isUnconfirmedUpgradeTx(tx)) {\n          await gameManager.hardRefreshPlanet(tx.intent.locationId);\n        } else if (isUnconfirmedBuyHatTx(tx)) {\n          await gameManager.hardRefreshPlanet(tx.intent.locationId);\n        } else if (isUnconfirmedInitTx(tx)) {\n          await gameManager.hardRefreshPlanet(tx.intent.locationId);\n        } else if (isUnconfirmedFindArtifactTx(tx)) {\n          await gameManager.hardRefreshPlanet(tx.intent.planetId);\n        } else if (isUnconfirmedDepositArtifactTx(tx)) {\n          await Promise.all([\n            gameManager.hardRefreshPlanet(tx.intent.locationId),\n            gameManager.hardRefreshArtifact(tx.intent.artifactId),\n          ]);\n        } else if (isUnconfirmedWithdrawArtifactTx(tx)) {\n          await Promise.all([\n            await gameManager.hardRefreshPlanet(tx.intent.locationId),\n            await gameManager.hardRefreshArtifact(tx.intent.artifactId),\n          ]);\n        } else if (isUnconfirmedProspectPlanetTx(tx)) {\n          await gameManager.softRefreshPlanet(tx.intent.planetId);\n        } else if (isUnconfirmedActivateArtifactTx(tx)) {\n          await Promise.all([\n            gameManager.hardRefreshPlanet(tx.intent.locationId),\n            gameManager.hardRefreshArtifact(tx.intent.artifactId),\n          ]);\n        } else if (isUnconfirmedDeactivateArtifactTx(tx)) {\n          await Promise.all([\n            gameManager.hardRefreshPlanet(tx.intent.locationId),\n            gameManager.hardRefreshArtifact(tx.intent.artifactId),\n          ]);\n        } else if (isUnconfirmedWithdrawSilverTx(tx)) {\n          await gameManager.softRefreshPlanet(tx.intent.locationId);\n        } else if (isUnconfirmedCapturePlanetTx(tx)) {\n          await Promise.all([\n            gameManager.hardRefreshPlayer(gameManager.getAccount()),\n            gameManager.hardRefreshPlanet(tx.intent.locationId),\n          ]);\n        } else if (isUnconfirmedInvadePlanetTx(tx)) {\n          await Promise.all([\n            gameManager.hardRefreshPlayer(gameManager.getAccount()),\n            gameManager.hardRefreshPlanet(tx.intent.locationId),\n          ]);\n        }\n\n        gameManager.entityStore.clearUnconfirmedTxIntent(tx);\n        gameManager.onTxConfirmed(tx);\n      })\n      .on(ContractsAPIEvent.TxErrored, async (tx: Transaction) => {\n        gameManager.entityStore.clearUnconfirmedTxIntent(tx);\n        if (tx.hash) {\n          gameManager.persistentChunkStore.onEthTxComplete(tx.hash);\n        }\n        gameManager.onTxReverted(tx);\n      })\n      .on(ContractsAPIEvent.TxCancelled, async (tx: Transaction) => {\n        gameManager.onTxCancelled(tx);\n      })\n      .on(ContractsAPIEvent.RadiusUpdated, async () => {\n        const newRadius = await gameManager.contractsAPI.getWorldRadius();\n        gameManager.setRadius(newRadius);\n      });\n\n    const unconfirmedTxs = await persistentChunkStore.getUnconfirmedSubmittedEthTxs();\n    const confirmationQueue = new ThrottledConcurrentQueue({\n      invocationIntervalMs: 1000,\n      maxInvocationsPerIntervalMs: 10,\n      maxConcurrency: 1,\n    });\n\n    for (const unconfirmedTx of unconfirmedTxs) {\n      confirmationQueue.add(async () => {\n        const tx = gameManager.contractsAPI.txExecutor.waitForTransaction(unconfirmedTx);\n        gameManager.contractsAPI.emitTransactionEvents(tx);\n        return tx.confirmedPromise;\n      });\n    }\n\n    // we only want to initialize the mining manager if the player has already joined the game\n    // if they haven't, we'll do this once the player has joined the game\n    if (!!homeLocation && initialState.players.has(account as string)) {\n      gameManager.initMiningManager(homeLocation.coords);\n    }\n\n    return gameManager;\n  }\n\n  private async hardRefreshPlayer(address?: EthAddress): Promise<void> {\n    if (!address) return;\n    const playerFromBlockchain = await this.contractsAPI.getPlayerById(address);\n    if (!playerFromBlockchain) return;\n\n    const localPlayer = this.getPlayer(address);\n\n    if (localPlayer?.twitter) {\n      playerFromBlockchain.twitter = localPlayer.twitter;\n    }\n\n    this.players.set(address, playerFromBlockchain);\n    this.playersUpdated$.publish();\n  }\n\n  // Dirty hack for only refreshing properties on a planet and nothing else\n  private async softRefreshPlanet(planetId: LocationId): Promise<void> {\n    const planet = await this.contractsAPI.getPlanetById(planetId);\n    if (!planet) return;\n    this.entityStore.replacePlanetFromContractData(planet);\n  }\n\n  public async hardRefreshPlanet(planetId: LocationId): Promise<void> {\n    const planet = await this.contractsAPI.getPlanetById(planetId);\n    if (!planet) return;\n    const arrivals = await this.contractsAPI.getArrivalsForPlanet(planetId);\n    const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets([planetId]);\n    const artifactsOnPlanet = artifactsOnPlanets[0];\n\n    const revealedCoords = await this.contractsAPI.getRevealedCoordsByIdIfExists(planetId);\n    let revealedLocation: RevealedLocation | undefined;\n    let claimedCoords: ClaimedCoords | undefined;\n\n    if (revealedCoords) {\n      revealedLocation = {\n        ...this.locationFromCoords(revealedCoords),\n        revealer: revealedCoords.revealer,\n      };\n    }\n\n    this.entityStore.replacePlanetFromContractData(\n      planet,\n      arrivals,\n      artifactsOnPlanet.map((a) => a.id),\n      revealedLocation,\n      claimedCoords?.revealer\n    );\n\n    // it's important that we reload the artifacts that are on the planet after the move\n    // completes because this move could have been a photoid canon move. one of the side\n    // effects of this type of move is that the active photoid canon deactivates upon a move\n    // meaning we need to reload its data from the blockchain.\n    artifactsOnPlanet.forEach((a) => this.entityStore.replaceArtifactFromContractData(a));\n  }\n\n  private async bulkHardRefreshPlanets(planetIds: LocationId[]): Promise<void> {\n    const planetVoyageMap: Map<LocationId, QueuedArrival[]> = new Map();\n\n    const allVoyages = await this.contractsAPI.getAllArrivals(planetIds);\n    const planetsToUpdateMap = await this.contractsAPI.bulkGetPlanets(planetIds);\n    const artifactsOnPlanets = await this.contractsAPI.bulkGetArtifactsOnPlanets(planetIds);\n    planetsToUpdateMap.forEach((planet, locId) => {\n      if (planetsToUpdateMap.has(locId)) {\n        planetVoyageMap.set(locId, []);\n      }\n    });\n\n    for (const voyage of allVoyages) {\n      const voyagesForToPlanet = planetVoyageMap.get(voyage.toPlanet);\n      if (voyagesForToPlanet) {\n        voyagesForToPlanet.push(voyage);\n        planetVoyageMap.set(voyage.toPlanet, voyagesForToPlanet);\n      }\n    }\n\n    for (let i = 0; i < planetIds.length; i++) {\n      const planetId = planetIds[i];\n      const planet = planetsToUpdateMap.get(planetId);\n\n      // This shouldn't really happen, but we are better off being safe - opposed to throwing\n      if (!planet) {\n        continue;\n      }\n\n      const voyagesForPlanet = planetVoyageMap.get(planet.locationId);\n      if (voyagesForPlanet) {\n        this.entityStore.replacePlanetFromContractData(\n          planet,\n          voyagesForPlanet,\n          artifactsOnPlanets[i].map((a) => a.id)\n        );\n      }\n    }\n\n    for (const artifacts of artifactsOnPlanets) {\n      this.entityStore.replaceArtifactsFromContractData(artifacts);\n    }\n  }\n\n  public async hardRefreshArtifact(artifactId: ArtifactId): Promise<void> {\n    const artifact = await this.contractsAPI.getArtifactById(artifactId);\n    if (!artifact) return;\n    this.entityStore.replaceArtifactFromContractData(artifact);\n  }\n\n  private onTxSubmit(tx: Transaction): void {\n    this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Blue);\n    this.terminal.current?.printLink(\n      `${tx.hash?.slice(0, 6) ?? ''}`,\n      () => {\n        window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`);\n      },\n      TerminalTextStyle.White\n    );\n    this.terminal.current?.println(`) submitted`, TerminalTextStyle.Blue);\n  }\n\n  private onTxConfirmed(tx: Transaction) {\n    this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Green);\n    this.terminal.current?.printLink(\n      `${tx.hash?.slice(0, 6) ?? ''}`,\n      () => {\n        window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`);\n      },\n      TerminalTextStyle.White\n    );\n    this.terminal.current?.println(`) confirmed`, TerminalTextStyle.Green);\n  }\n\n  private onTxReverted(tx: Transaction) {\n    this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Red);\n    this.terminal.current?.printLink(\n      `${tx.hash?.slice(0, 6) ?? ''}`,\n      () => {\n        window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`);\n      },\n      TerminalTextStyle.White\n    );\n\n    this.terminal.current?.println(`) reverted`, TerminalTextStyle.Red);\n  }\n\n  private onTxCancelled(tx: Transaction) {\n    this.entityStore.clearUnconfirmedTxIntent(tx);\n    this.terminal.current?.print(`${tx.intent.methodName} transaction (`, TerminalTextStyle.Red);\n    this.terminal.current?.printLink(\n      `${tx.hash?.slice(0, 6) ?? ''}`,\n      () => {\n        window.open(`${BLOCK_EXPLORER_URL}/${tx.hash ?? ''}`);\n      },\n      TerminalTextStyle.White\n    );\n\n    this.terminal.current?.println(`) cancelled`, TerminalTextStyle.Red);\n  }\n\n  /**\n   * Gets the address of the player logged into this game manager.\n   */\n  public getAccount(): EthAddress | undefined {\n    return this.account;\n  }\n\n  /**\n   * Get the thing that handles contract interaction.\n   */\n  public getContractAPI(): ContractsAPI {\n    return this.contractsAPI;\n  }\n\n  /**\n   * Gets the address of the `DarkForest` contract, which is the 'backend' of the game.\n   */\n  public getContractAddress(): EthAddress {\n    return this.contractsAPI.getContractAddress();\n  }\n\n  /**\n   * Gets the twitter handle of the given ethereum account which is associated\n   * with Dark Forest.\n   */\n  public getTwitter(address: EthAddress | undefined): string | undefined {\n    let myAddress;\n    if (!address) myAddress = this.getAccount();\n    else myAddress = address;\n\n    if (!myAddress) {\n      return undefined;\n    }\n    const twitter = this.players.get(myAddress)?.twitter;\n    return twitter;\n  }\n\n  /**\n   * The game ends at a particular time in the future - get this time measured\n   * in seconds from the epoch.\n   */\n  public getEndTimeSeconds(): number {\n    return this.endTimeSeconds;\n  }\n\n  /**\n   * Dark Forest tokens can only be minted up to a certain time - get this time measured in seconds from epoch.\n   */\n  public getTokenMintEndTimeSeconds(): number {\n    return this.contractConstants.TOKEN_MINT_END_SECONDS;\n  }\n\n  /**\n   * Gets the rarity of planets in the universe\n   */\n  public getPlanetRarity(): number {\n    return this.contractConstants.PLANET_RARITY;\n  }\n\n  /**\n   * returns timestamp (seconds) that planet will reach percent% of energycap\n   * time may be in the past\n   */\n  public getEnergyCurveAtPercent(planet: Planet, percent: number): number {\n    return this.entityStore.getEnergyCurveAtPercent(planet, percent);\n  }\n\n  /**\n   * returns timestamp (seconds) that planet will reach percent% of silcap if\n   * doesn't produce silver, returns undefined if already over percent% of silcap,\n   */\n  public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined {\n    return this.entityStore.getSilverCurveAtPercent(planet, percent);\n  }\n\n  /**\n   * Returns the upgrade that would be applied to a planet given a particular\n   * upgrade branch (defense, range, speed) and level of upgrade.\n   */\n  public getUpgrade(branch: number, level: number): Upgrade {\n    return this.contractConstants.upgrades[branch][level];\n  }\n\n  /**\n   * Gets a list of all the players in the game (not just the ones you've\n   * encounterd)\n   */\n  public getAllPlayers(): Player[] {\n    return Array.from(this.players.values());\n  }\n\n  /**\n   * Gets either the given player, or if no address was provided, gets the player that is logged\n   * this client.\n   */\n  public getPlayer(address?: EthAddress): Player | undefined {\n    address = address || this.account;\n\n    if (!address) {\n      return undefined;\n    }\n\n    return this.players.get(address);\n  }\n\n  /**\n   * Gets all the map chunks that this client is aware of. Chunks may have come from\n   * mining, or from importing map data.\n   */\n  public getExploredChunks(): Iterable<Chunk> {\n    return this.persistentChunkStore.allChunks();\n  }\n\n  /**\n   * Gets the ids of all the planets that are both within the given bounding box (defined by its bottom\n   * left coordinate, width, and height) in the world and of a level that was passed in via the\n   * `planetLevels` parameter.\n   */\n  public getPlanetsInWorldRectangle(\n    worldX: number,\n    worldY: number,\n    worldWidth: number,\n    worldHeight: number,\n    levels: number[],\n    planetLevelToRadii: Map<number, Radii>,\n    updateIfStale = true\n  ): LocatablePlanet[] {\n    return this.entityStore.getPlanetsInWorldRectangle(\n      worldX,\n      worldY,\n      worldWidth,\n      worldHeight,\n      levels,\n      planetLevelToRadii,\n      updateIfStale\n    );\n  }\n\n  /**\n   * Returns whether or not the current round has ended.\n   */\n  public isRoundOver(): boolean {\n    return Date.now() / 1000 > this.getTokenMintEndTimeSeconds();\n  }\n\n  /**\n   * Gets the radius of the playable area of the universe.\n   */\n  public getWorldRadius(): number {\n    return this.worldRadius;\n  }\n\n  /**\n   * Gets the total amount of silver that lives on a planet that somebody owns.\n   */\n  public getWorldSilver(): number {\n    return this.getAllOwnedPlanets().reduce(\n      (totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.silver,\n      0\n    );\n  }\n\n  /**\n   * Gets the total amount of energy that lives on a planet that somebody owns.\n   */\n  public getUniverseTotalEnergy(): number {\n    return this.getAllOwnedPlanets().reduce(\n      (totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.energy,\n      0\n    );\n  }\n\n  /**\n   * Gets the total amount of silver that lives on planets that the given player owns.\n   */\n  public getSilverOfPlayer(player: EthAddress): number {\n    return this.getAllOwnedPlanets()\n      .filter((planet) => planet.owner === player)\n      .reduce((totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.silver, 0);\n  }\n\n  /**\n   * Gets the total amount of energy that lives on planets that the given player owns.\n   */\n  public getEnergyOfPlayer(player: EthAddress): number {\n    return this.getAllOwnedPlanets()\n      .filter((planet) => planet.owner === player)\n      .reduce((totalSoFar: number, nextPlanet: Planet) => totalSoFar + nextPlanet.energy, 0);\n  }\n\n  public getPlayerScore(addr: EthAddress): number | undefined {\n    const player = this.players.get(addr);\n    return player?.score;\n  }\n\n  public getPlayerSpaceJunk(addr: EthAddress): number | undefined {\n    const player = this.players.get(addr);\n    return player?.spaceJunk;\n  }\n\n  public getPlayerSpaceJunkLimit(addr: EthAddress): number | undefined {\n    const player = this.players.get(addr);\n    return player?.spaceJunkLimit;\n  }\n\n  public getDefaultSpaceJunkForPlanetLevel(level: number) {\n    return this.contractConstants.PLANET_LEVEL_JUNK[level];\n  }\n\n  private initMiningManager(homeCoords: WorldCoords, cores?: number): void {\n    if (this.minerManager) return;\n\n    const myPattern: MiningPattern = new SpiralPattern(homeCoords, MIN_CHUNK_SIZE);\n\n    this.minerManager = MinerManager.create(\n      this.persistentChunkStore,\n      myPattern,\n      this.worldRadius,\n      this.planetRarity,\n      this.hashConfig,\n      this.useMockHash\n    );\n\n    const config = {\n      contractAddress: this.getContractAddress(),\n      account: this.account,\n    };\n\n    this.minerManager.setCores(cores || getNumberSetting(config, Setting.MiningCores));\n\n    this.minerManager.on(\n      MinerManagerEvent.DiscoveredNewChunk,\n      (chunk: Chunk, miningTimeMillis: number) => {\n        this.addNewChunk(chunk);\n        this.hashRate = chunk.chunkFootprint.sideLength ** 2 / (miningTimeMillis / 1000);\n        this.emit(GameManagerEvent.DiscoveredNewChunk, chunk);\n      }\n    );\n\n    const isMining = getBooleanSetting(config, Setting.IsMining);\n    if (isMining) {\n      this.minerManager.startExplore();\n    }\n  }\n\n  /**\n   * Sets the mining pattern of the miner. This kills the old miner and starts this one.\n   */\n  setMiningPattern(pattern: MiningPattern): void {\n    if (this.minerManager) {\n      this.minerManager.setMiningPattern(pattern);\n    }\n  }\n\n  /**\n   * Gets the mining pattern that the miner is currently using.\n   */\n  getMiningPattern(): MiningPattern | undefined {\n    if (this.minerManager) return this.minerManager.getMiningPattern();\n    else return undefined;\n  }\n\n  /**\n   * Set the amount of cores to mine the universe with. More cores equals faster!\n   */\n  setMinerCores(nCores: number): void {\n    const config = {\n      contractAddress: this.getContractAddress(),\n      account: this.account,\n    };\n    setSetting(config, Setting.MiningCores, nCores + '');\n  }\n\n  /**\n   * Whether or not the miner is currently exploring space.\n   */\n  isMining(): boolean {\n    return this.minerManager?.isMining() || false;\n  }\n\n  /**\n   * Changes the amount of move snark proofs that are cached.\n   */\n  setSnarkCacheSize(size: number): void {\n    this.snarkHelper.setSnarkCacheSize(size);\n  }\n\n  /**\n   * Gets the rectangle bounding the chunk that the miner is currently in the process\n   * of hashing.\n   */\n  getCurrentlyExploringChunk(): Rectangle | undefined {\n    if (this.minerManager) {\n      return this.minerManager.getCurrentlyExploringChunk();\n    }\n    return undefined;\n  }\n\n  /**\n   * Whether or not this client has successfully found and landed on a home planet.\n   */\n  hasJoinedGame(): boolean {\n    return this.players.has(this.account as string);\n  }\n\n  /**\n   * Returns info about the next time you can broadcast coordinates\n   */\n  getNextRevealCountdownInfo(): RevealCountdownInfo {\n    if (!this.account) {\n      throw new Error('no account set');\n    }\n    const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp;\n    return {\n      myLastRevealTimestamp: myLastRevealTimestamp || undefined,\n      currentlyRevealing: this.entityStore.transactions.hasTransaction(isUnconfirmedRevealTx),\n      revealCooldownTime: this.contractConstants.LOCATION_REVEAL_COOLDOWN,\n    };\n  }\n\n  /**\n   * gets both deposited artifacts that are on planets i own as well as artifacts i own\n   */\n  getMyArtifacts(): Artifact[] {\n    if (!this.account) return [];\n    const ownedByMe = this.entityStore.getArtifactsOwnedBy(this.account);\n    const onPlanetsOwnedByMe = this.entityStore\n      .getArtifactsOnPlanetsOwnedBy(this.account)\n      // filter out space ships because they always show up\n      // in the `ownedByMe` array.\n      .filter((a) => !isSpaceShip(a.artifactType));\n\n    return [...ownedByMe, ...onPlanetsOwnedByMe];\n  }\n\n  /**\n   * Gets the planet that is located at the given coordinates. Returns undefined if not a valid\n   * location or if no planet exists at location. If the planet needs to be updated (because\n   * some time has passed since we last updated the planet), then updates that planet first.\n   */\n  getPlanetWithCoords(coords: WorldCoords): LocatablePlanet | undefined {\n    return this.entityStore.getPlanetWithCoords(coords);\n  }\n\n  /**\n   * Gets the planet with the given hash. Returns undefined if the planet is neither in the contract\n   * nor has been discovered locally. If the planet needs to be updated (because some time has\n   * passed since we last updated the planet), then updates that planet first.\n   */\n  getPlanetWithId(planetId: LocationId | undefined): Planet | undefined {\n    return planetId && this.entityStore.getPlanetWithId(planetId);\n  }\n\n  /**\n   * Gets a list of planets in the client's memory with the given ids. If a planet with the given id\n   * doesn't exist, no entry for that planet will be returned in the result.\n   */\n  getPlanetsWithIds(planetId: LocationId[]): Planet[] {\n    return planetId.map((id) => this.getPlanetWithId(id)).filter((p) => !!p) as Planet[];\n  }\n\n  getStalePlanetWithId(planetId: LocationId): Planet | undefined {\n    return this.entityStore.getPlanetWithId(planetId, false);\n  }\n\n  /**\n   * Get the score of the currently logged-in account.\n   */\n  getMyScore(): number | undefined {\n    if (!this.account) {\n      return undefined;\n    }\n    const player = this.players.get(this.account);\n    return player?.score;\n  }\n\n  /**\n   * Gets the artifact with the given id. Null if no artifact with id exists.\n   */\n  getArtifactWithId(artifactId?: ArtifactId): Artifact | undefined {\n    return this.entityStore.getArtifactById(artifactId);\n  }\n\n  /**\n   * Gets the artifacts with the given ids, including ones we know exist but haven't been loaded,\n   * represented by `undefined`.\n   */\n  getArtifactsWithIds(artifactIds: ArtifactId[] = []): Array<Artifact | undefined> {\n    return artifactIds.map((id) => this.getArtifactWithId(id));\n  }\n\n  /**\n   * Gets the level of the given planet. Returns undefined if the planet does not exist. Does\n   * NOT update the planet if the planet is stale, which means this function is fast.\n   */\n  getPlanetLevel(planetId: LocationId): PlanetLevel | undefined {\n    return this.entityStore.getPlanetLevel(planetId);\n  }\n\n  /**\n   * Gets the location of the given planet. Returns undefined if the planet does not exist, or if\n   * we do not know the location of this planet NOT update the planet if the planet is stale,\n   * which means this function is fast.\n   */\n  getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined {\n    return this.entityStore.getLocationOfPlanet(planetId);\n  }\n\n  /**\n   * Gets all voyages that have not completed.\n   */\n  getAllVoyages(): QueuedArrival[] {\n    return this.entityStore.getAllVoyages();\n  }\n\n  /**\n   * Gets all planets. This means all planets that are in the contract, and also all\n   * planets that have been mined locally. Does not update planets if they are stale.\n   * NOT PERFORMANT - for scripting only.\n   */\n  getAllPlanets(): Iterable<Planet> {\n    return this.entityStore.getAllPlanets();\n  }\n\n  /**\n   * Gets a list of planets that have an owner.\n   */\n  getAllOwnedPlanets(): Planet[] {\n    return this.entityStore.getAllOwnedPlanets();\n  }\n\n  /**\n   * Gets a list of the planets that the player logged into this `GameManager` owns.\n   */\n  getMyPlanets(): Planet[] {\n    return this.getAllOwnedPlanets().filter((planet) => planet.owner === this.account);\n  }\n\n  /**\n   * Gets a map of all location IDs whose coords have been publically revealed\n   */\n  getRevealedLocations(): Map<LocationId, RevealedLocation> {\n    return this.entityStore.getRevealedLocations();\n  }\n\n  /**\n   * Gets a map of all location IDs which have been claimed.\n   */\n  getClaimedLocations(): Map<LocationId, ClaimedLocation> {\n    return this.entityStore.getClaimedLocations();\n  }\n\n  /**\n   * Each coordinate lives in a particular type of space, determined by a smooth random\n   * function called 'perlin noise.\n   */\n  spaceTypeFromPerlin(perlin: number): SpaceType {\n    return this.entityStore.spaceTypeFromPerlin(perlin);\n  }\n\n  /**\n   * Gets the amount of hashes per second that the miner manager is calculating.\n   */\n  getHashesPerSec(): number {\n    return this.hashRate;\n  }\n\n  /**\n   * Signs the given twitter handle with the private key of the current user. Used to\n   * verify that the person who owns the Dark Forest account was the one that attempted\n   * to link a twitter to their account.\n   */\n  async getSignedTwitter(twitter: string): Promise<string> {\n    return this.ethConnection.signMessage(twitter);\n  }\n\n  /**\n   * Gets the private key of the burner wallet used by this account.\n   */\n  getPrivateKey(): string | undefined {\n    return this.ethConnection.getPrivateKey();\n  }\n\n  /**\n   * Gets the balance of the account measured in Eth (i.e. in full units of the chain).\n   */\n  getMyBalanceEth(): number {\n    if (!this.account) return 0;\n    return weiToEth(this.getMyBalance());\n  }\n\n  /**\n   * Gets the balance of the account\n   */\n  getMyBalance(): BigNumber {\n    return this.ethConnection.getMyBalance() || BigNumber.from('0');\n  }\n\n  /**\n   * Returns the monomitter which publishes events whenever the player's balance changes.\n   */\n  getMyBalance$(): Monomitter<BigNumber> {\n    return this.ethConnection.myBalance$;\n  }\n\n  /**\n   * Gets all moves that this client has queued to be uploaded to the contract, but\n   * have not been successfully confirmed yet.\n   */\n  getUnconfirmedMoves(): Transaction<UnconfirmedMove>[] {\n    return this.entityStore.transactions.getTransactions(isUnconfirmedMoveTx);\n  }\n\n  /**\n   * Gets all upgrades that this client has queued to be uploaded to the contract, but\n   * have not been successfully confirmed yet.\n   */\n  getUnconfirmedUpgrades(): Transaction<UnconfirmedUpgrade>[] {\n    return this.entityStore.transactions.getTransactions(isUnconfirmedUpgradeTx);\n  }\n\n  getUnconfirmedWormholeActivations(): Transaction<UnconfirmedActivateArtifact>[] {\n    return this.entityStore.transactions\n      .getTransactions(isUnconfirmedActivateArtifactTx)\n      .filter((tx) => tx.intent.wormholeTo !== undefined);\n  }\n\n  /**\n   * Gets the location of your home planet.\n   */\n  getHomeCoords(): WorldCoords | undefined {\n    if (!this.homeLocation) return undefined;\n    return {\n      x: this.homeLocation.coords.x,\n      y: this.homeLocation.coords.y,\n    };\n  }\n\n  /**\n   * Gets the hash of the location of your home planet.\n   */\n  getHomeHash(): LocationId | undefined {\n    return this.homeLocation?.hash;\n  }\n\n  /**\n   * Gets the HASH CONFIG\n   */\n  getHashConfig(): HashConfig {\n    return { ...this.hashConfig };\n  }\n\n  /**\n   * Whether or not the given rectangle has been mined.\n   */\n  hasMinedChunk(chunkLocation: Rectangle): boolean {\n    return this.persistentChunkStore.hasMinedChunk(chunkLocation);\n  }\n\n  getChunk(chunkFootprint: Rectangle): Chunk | undefined {\n    return this.persistentChunkStore.getChunkByFootprint(chunkFootprint);\n  }\n\n  getChunkStore(): PersistentChunkStore {\n    return this.persistentChunkStore;\n  }\n\n  /**\n   * The perlin value at each coordinate determines the space type. There are four space\n   * types, which means there are four ranges on the number line that correspond to\n   * each space type. This function returns the boundary values between each of these\n   * four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`.\n   */\n  getPerlinThresholds(): [number, number, number] {\n    return [\n      this.contractConstants.PERLIN_THRESHOLD_1,\n      this.contractConstants.PERLIN_THRESHOLD_2,\n      this.contractConstants.PERLIN_THRESHOLD_3,\n    ];\n  }\n\n  /**\n   * Starts the miner.\n   */\n  startExplore(): void {\n    if (this.minerManager) {\n      const config = {\n        contractAddress: this.getContractAddress(),\n        account: this.account,\n      };\n      setBooleanSetting(config, Setting.IsMining, true);\n      this.minerManager.startExplore();\n    }\n  }\n\n  /**\n   * Stops the miner.\n   */\n  stopExplore(): void {\n    if (this.minerManager) {\n      const config = {\n        contractAddress: this.getContractAddress(),\n        account: this.account,\n      };\n      setBooleanSetting(config, Setting.IsMining, false);\n      this.hashRate = 0;\n      this.minerManager.stopExplore();\n    }\n  }\n\n  private setRadius(worldRadius: number) {\n    this.worldRadius = worldRadius;\n\n    if (this.minerManager) {\n      this.minerManager.setRadius(this.worldRadius);\n    }\n  }\n\n  private async refreshTwitters(): Promise<void> {\n    const addressTwitters = await getAllTwitters();\n    this.setPlayerTwitters(addressTwitters);\n  }\n\n  private setPlayerTwitters(twitters: AddressTwitterMap): void {\n    for (const [address, player] of this.players.entries()) {\n      const newPlayerTwitter = twitters[address];\n      player.twitter = newPlayerTwitter;\n    }\n    this.playersUpdated$.publish();\n  }\n\n  /**\n   * Once you have posted the verification tweet - complete the twitter-account-linking\n   * process by telling the Dark Forest webserver to look at that tweet.\n   */\n  async submitVerifyTwitter(twitter: string): Promise<boolean> {\n    if (!this.account) return Promise.resolve(false);\n    const success = await verifyTwitterHandle(\n      await this.ethConnection.signMessageObject({ twitter })\n    );\n    await this.refreshTwitters();\n    return success;\n  }\n\n  private checkGameHasEnded(): boolean {\n    if (Date.now() / 1000 > this.endTimeSeconds) {\n      this.terminal.current?.println('[ERROR] Game has ended.');\n      return true;\n    }\n    return false;\n  }\n\n  /**\n   * Gets the timestamp (ms) of the next time that we can broadcast the coordinates of a planet.\n   */\n  public getNextBroadcastAvailableTimestamp() {\n    return Date.now() + this.timeUntilNextBroadcastAvailable();\n  }\n\n  /**\n   * Gets the amount of time (ms) until the next time the current player can broadcast a planet.\n   */\n  public timeUntilNextBroadcastAvailable() {\n    if (!this.account) {\n      throw new Error('no account set');\n    }\n\n    const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp;\n\n    return timeUntilNextBroadcastAvailable(\n      myLastRevealTimestamp,\n      this.contractConstants.LOCATION_REVEAL_COOLDOWN\n    );\n  }\n\n  /**\n   * Gets the timestamp (ms) of the next time that we can claim a planet.\n   */\n  public getNextClaimAvailableTimestamp() {\n    if (!this.account) {\n      throw new Error('no account set');\n    }\n    const myLastClaimTimestamp = this.players.get(this.account)?.lastClaimTimestamp;\n\n    if (!myLastClaimTimestamp) {\n      return Date.now();\n    }\n    if (!this.contractConstants.CLAIM_PLANET_COOLDOWN) {\n      return 0;\n    }\n\n    // both the variables in the next line are denominated in seconds\n    return (myLastClaimTimestamp + this.contractConstants.CLAIM_PLANET_COOLDOWN) * 1000;\n  }\n\n  public getCaptureZones(): Set<CaptureZone> {\n    return this.captureZoneGenerator?.getZones() || new Set();\n  }\n\n  /**\n   * Reveals a planet's location on-chain.\n   */\n  public async revealLocation(planetId: LocationId): Promise<Transaction<UnconfirmedReveal>> {\n    try {\n      if (!this.account) {\n        throw new Error('no account set');\n      }\n\n      const planet = this.entityStore.getPlanetWithId(planetId);\n\n      if (!planet) {\n        throw new Error(\"you can't reveal a planet you haven't discovered\");\n      }\n\n      if (!isLocatable(planet)) {\n        throw new Error(\"you can't reveal a planet whose coordinates you don't know\");\n      }\n\n      if (planet.coordsRevealed) {\n        throw new Error(\"this planet's location is already revealed\");\n      }\n\n      if (planet.transactions?.hasTransaction(isUnconfirmedRevealTx)) {\n        throw new Error(\"you're already revealing this planet's location\");\n      }\n\n      if (this.entityStore.transactions.hasTransaction(isUnconfirmedRevealTx)) {\n        throw new Error(\"you're already broadcasting coordinates\");\n      }\n\n      const myLastRevealTimestamp = this.players.get(this.account)?.lastRevealTimestamp;\n      if (myLastRevealTimestamp && Date.now() < this.getNextBroadcastAvailableTimestamp()) {\n        throw new Error('still on cooldown for broadcasting');\n      }\n\n      // this is shitty. used for the popup window\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-revealLocationId`, planetId);\n\n      const getArgs = async () => {\n        const revealArgs = await this.snarkHelper.getRevealArgs(\n          planet.location.coords.x,\n          planet.location.coords.y\n        );\n\n        this.terminal.current?.println(\n          'REVEAL: calculated SNARK with args:',\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.println(\n          JSON.stringify(hexifyBigIntNestedArray(revealArgs.slice(0, 3))),\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.newline();\n\n        return revealArgs;\n      };\n\n      const txIntent: UnconfirmedReveal = {\n        methodName: 'revealLocation',\n        contract: this.contractsAPI.contract,\n        locationId: planetId,\n        location: planet.location,\n        args: getArgs(),\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('revealLocation', e.message);\n      throw e;\n    }\n  }\n\n  public async invadePlanet(locationId: LocationId) {\n    try {\n      if (!this.captureZoneGenerator) {\n        throw new Error('Capture zones are not enabled in this game');\n      }\n\n      const planet = this.entityStore.getPlanetWithId(locationId);\n\n      if (!planet || !isLocatable(planet)) {\n        throw new Error(\"you can't invade a planet you haven't discovered\");\n      }\n\n      if (planet.destroyed) {\n        throw new Error(\"you can't invade destroyed planets\");\n      }\n\n      if (planet.invader !== EMPTY_ADDRESS) {\n        throw new Error(\"you can't invade planets that have already been invaded\");\n      }\n\n      if (planet.owner !== this.account) {\n        throw new Error('you can only invade planets you own');\n      }\n\n      if (!this.captureZoneGenerator.isInZone(planet.locationId)) {\n        throw new Error(\"you can't invade planets that are not in a capture zone\");\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-invadePlanet`, locationId);\n\n      const getArgs = async () => {\n        const revealArgs = await this.snarkHelper.getRevealArgs(\n          planet.location.coords.x,\n          planet.location.coords.y\n        );\n\n        this.terminal.current?.println(\n          'REVEAL: calculated SNARK with args:',\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.println(\n          JSON.stringify(hexifyBigIntNestedArray(revealArgs.slice(0, 3))),\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.newline();\n\n        return revealArgs;\n      };\n\n      const txIntent: UnconfirmedInvadePlanet = {\n        methodName: 'invadePlanet',\n        contract: this.contractsAPI.contract,\n        locationId,\n        args: getArgs(),\n      };\n\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('invadePlanet', e.message);\n      throw e;\n    }\n  }\n\n  public async capturePlanet(locationId: LocationId) {\n    try {\n      const planet = this.entityStore.getPlanetWithId(locationId);\n\n      if (!planet) {\n        throw new Error('planet is not loaded');\n      }\n\n      if (planet.destroyed) {\n        throw new Error(\"you can't capture destroyed planets\");\n      }\n\n      if (planet.capturer !== EMPTY_ADDRESS) {\n        throw new Error(\"you can't capture planets that have already been captured\");\n      }\n\n      if (planet.owner !== this.account) {\n        throw new Error('you can only capture planets you own');\n      }\n\n      if (planet.energy < planet.energyCap * 0.8) {\n        throw new Error('the planet needs >80% energy before capturing');\n      }\n\n      if (\n        !planet.invadeStartBlock ||\n        this.ethConnection.getCurrentBlockNumber() <\n          planet.invadeStartBlock + this.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED\n      ) {\n        throw new Error(\n          `you need to hold a planet for ${this.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED} blocks before capturing`\n        );\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-capturePlanet`, locationId);\n\n      const txIntent: UnconfirmedCapturePlanet = {\n        methodName: 'capturePlanet',\n        contract: this.contractsAPI.contract,\n        locationId,\n        args: Promise.resolve([locationIdToDecStr(locationId)]),\n      };\n\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('capturePlanet', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Attempts to join the game. Should not be called once you've already joined.\n   */\n  public async joinGame(beforeRetry: (e: Error) => Promise<boolean>): Promise<void> {\n    try {\n      if (this.checkGameHasEnded()) {\n        throw new Error('game has ended');\n      }\n\n      const planet = await this.findRandomHomePlanet();\n      this.homeLocation = planet.location;\n      this.terminal.current?.println('');\n      this.terminal.current?.println(`Found Suitable Home Planet: ${getPlanetName(planet)} `);\n      this.terminal.current?.println(\n        `Its coordinates are: (${planet.location.coords.x}, ${planet.location.coords.y})`\n      );\n      this.terminal.current?.println('');\n\n      await this.persistentChunkStore.addHomeLocation(planet.location);\n\n      const getArgs = async () => {\n        const args = await this.snarkHelper.getInitArgs(\n          planet.location.coords.x,\n          planet.location.coords.y,\n          Math.floor(Math.sqrt(planet.location.coords.x ** 2 + planet.location.coords.y ** 2)) + 1 // floor(sqrt(x^2 + y^2)) + 1\n        );\n        this.terminal.current?.println('INIT: calculated SNARK with args:', TerminalTextStyle.Sub);\n        this.terminal.current?.println(\n          JSON.stringify(hexifyBigIntNestedArray(args.slice(0, 3) as unknown as string[])),\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.newline();\n        return args;\n      };\n\n      const txIntent: UnconfirmedInit = {\n        methodName: 'initializePlayer',\n        contract: this.contractsAPI.contract,\n        locationId: planet.location.hash,\n        location: planet.location,\n        args: getArgs(),\n      };\n\n      this.terminal.current?.println('INIT: proving that planet exists', TerminalTextStyle.Sub);\n\n      this.initMiningManager(planet.location.coords); // get an early start\n\n      // if player initialization causes an error, give the caller an opportunity\n      // to resolve that error. if the asynchronous `beforeRetry` function returns\n      // true, retry initializing the player. if it returns false, or if the\n      // `beforeRetry` is undefined, then don't retry and throw an exception.\n      while (true) {\n        try {\n          const tx = await this.contractsAPI.submitTransaction(txIntent);\n          await tx.confirmedPromise;\n          break;\n        } catch (e) {\n          if (beforeRetry) {\n            if (await beforeRetry(e)) {\n              continue;\n            }\n          } else {\n            throw e;\n          }\n        }\n      }\n\n      await this.getSpaceships();\n      await this.hardRefreshPlanet(planet.locationId);\n\n      this.emit(GameManagerEvent.InitializedPlayer);\n    } catch (e) {\n      this.getNotificationsManager().txInitError('initializePlayer', e.message);\n      throw e;\n    }\n  }\n\n  private async getSpaceships() {\n    if (!this.account || !this.homeLocation?.hash) return;\n\n    const player = await this.contractsAPI.getPlayerById(this.account);\n    if (player?.claimedShips) return;\n\n    if (this.getGameObjects().isGettingSpaceships()) return;\n    const tx = await this.contractsAPI.submitTransaction({\n      methodName: 'giveSpaceShips',\n      contract: this.contractsAPI.contract,\n      args: Promise.resolve(['0x' + this.homeLocation?.hash]),\n    });\n    await tx.confirmedPromise;\n    this.hardRefreshPlanet(this.homeLocation?.hash);\n  }\n\n  // this is slow, do not call in i.e. render/draw loop\n  /**\n   *\n   * computes the WorldLocation object corresponding to a set of coordinates\n   * very slow since it actually calculates the hash; do not use in render loop\n   */\n  private locationFromCoords(coords: WorldCoords): WorldLocation {\n    return {\n      coords,\n      hash: locationIdFromBigInt(this.planetHashMimc(coords.x, coords.y)),\n      perlin: this.spaceTypePerlin(coords, true),\n      biomebase: this.biomebasePerlin(coords, true),\n    };\n  }\n\n  /**\n   * Initializes a new player's game to start at the given home planet. Must have already\n   * initialized the player on the contract.\n   */\n  async addAccount(coords: WorldCoords): Promise<boolean> {\n    const loc: WorldLocation = this.locationFromCoords(coords);\n    await this.persistentChunkStore.addHomeLocation(loc);\n    this.initMiningManager(coords);\n    this.homeLocation = loc;\n    return true;\n  }\n\n  private async findRandomHomePlanet(): Promise<LocatablePlanet> {\n    return new Promise<LocatablePlanet>((resolve, reject) => {\n      const initPerlinMin = this.contractConstants.INIT_PERLIN_MIN;\n      const initPerlinMax = this.contractConstants.INIT_PERLIN_MAX;\n      let minedChunksCount = 0;\n\n      let x: number;\n      let y: number;\n      let d: number;\n      let p: number;\n\n      // if this.contractConstants.SPAWN_RIM_AREA is non-zero, then players must spawn in that\n      // area, distributed evenly in the inner perimeter of the world\n      let spawnInnerRadius = Math.sqrt(\n        Math.max(Math.PI * this.worldRadius ** 2 - this.contractConstants.SPAWN_RIM_AREA, 0) /\n          Math.PI\n      );\n\n      if (this.contractConstants.SPAWN_RIM_AREA === 0) {\n        spawnInnerRadius = 0;\n      }\n\n      do {\n        // sample from square\n        x = Math.random() * this.worldRadius * 2 - this.worldRadius;\n        y = Math.random() * this.worldRadius * 2 - this.worldRadius;\n        d = Math.sqrt(x ** 2 + y ** 2);\n        p = this.spaceTypePerlin({ x, y }, false);\n      } while (\n        p >= initPerlinMax || // keep searching if above or equal to the max\n        p < initPerlinMin || // keep searching if below the minimum\n        d >= this.worldRadius || // can't be out of bound\n        d <= spawnInnerRadius // can't be inside spawn area ring\n      );\n\n      // when setting up a new account in development mode, you can tell\n      // the game where to start searching for planets using this query\n      // string parameter. for example:\n      //\n      // ?searchCenter=2866,5627\n      //\n\n      const params = new URLSearchParams(window.location.search);\n\n      if (params.has('searchCenter')) {\n        const parts = params.get('searchCenter')?.split(',');\n\n        if (parts) {\n          x = parseInt(parts[0], 10);\n          y = parseInt(parts[1], 10);\n        }\n      }\n\n      const pattern: MiningPattern = new SpiralPattern({ x, y }, MIN_CHUNK_SIZE);\n      const chunkStore = new HomePlanetMinerChunkStore(\n        initPerlinMin,\n        initPerlinMax,\n        this.hashConfig\n      );\n      const homePlanetFinder = MinerManager.create(\n        chunkStore,\n        pattern,\n        this.worldRadius,\n        this.planetRarity,\n        this.hashConfig,\n        this.useMockHash\n      );\n\n      this.terminal.current?.println(``);\n      this.terminal.current?.println(`Initializing Home Planet Search...`);\n      this.terminal.current?.println(``);\n      this.terminal.current?.println(`Chunked explorer: start!`);\n      this.terminal.current?.println(\n        `Each chunk contains ${MIN_CHUNK_SIZE}x${MIN_CHUNK_SIZE} coordinates.`\n      );\n      const percentSpawn = (1 / this.contractConstants.PLANET_RARITY) * 100;\n      const printProgress = 8;\n      this.terminal.current?.print(`Each coordinate has a`);\n      this.terminal.current?.print(` ${percentSpawn}%`, TerminalTextStyle.Text);\n      this.terminal.current?.print(` chance of spawning a planet.`);\n      this.terminal.current?.println('');\n\n      this.terminal.current?.println(\n        `Hashing first ${MIN_CHUNK_SIZE ** 2 * printProgress} potential home planets...`\n      );\n\n      homePlanetFinder.on(MinerManagerEvent.DiscoveredNewChunk, (chunk: Chunk) => {\n        chunkStore.addChunk(chunk);\n        minedChunksCount++;\n        if (minedChunksCount % printProgress === 0) {\n          this.terminal.current?.println(\n            `Hashed ${minedChunksCount * MIN_CHUNK_SIZE ** 2} potential home planets...`\n          );\n        }\n        for (const homePlanetLocation of chunk.planetLocations) {\n          const planetPerlin = homePlanetLocation.perlin;\n          const planetX = homePlanetLocation.coords.x;\n          const planetY = homePlanetLocation.coords.y;\n          const planetLevel = this.entityStore.planetLevelFromHexPerlin(\n            homePlanetLocation.hash,\n            homePlanetLocation.perlin\n          );\n          const planetType = this.entityStore.planetTypeFromHexPerlin(\n            homePlanetLocation.hash,\n            homePlanetLocation.perlin\n          );\n          const planet = this.getPlanetWithId(homePlanetLocation.hash);\n          const distFromOrigin = Math.sqrt(planetX ** 2 + planetY ** 2);\n          if (\n            planetPerlin < initPerlinMax &&\n            planetPerlin >= initPerlinMin &&\n            distFromOrigin < this.worldRadius &&\n            distFromOrigin > spawnInnerRadius &&\n            planetLevel === MIN_PLANET_LEVEL &&\n            planetType === PlanetType.PLANET &&\n            (!planet || !planet.isInContract) // init will fail if planet has been initialized in contract already\n          ) {\n            // valid home planet\n            homePlanetFinder.stopExplore();\n            homePlanetFinder.destroy();\n\n            const homePlanet = this.getGameObjects().getPlanetWithLocation(homePlanetLocation);\n\n            if (!homePlanet) {\n              reject(new Error(\"Unable to create default planet for your home planet's location.\"));\n            } else {\n              // can cast to `LocatablePlanet` because we know its location, as we just mined it.\n              resolve(homePlanet as LocatablePlanet);\n            }\n\n            break;\n          }\n        }\n      });\n      homePlanetFinder.startExplore();\n    });\n  }\n\n  public async prospectPlanet(\n    planetId: LocationId,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedProspectPlanet>> {\n    const planet = this.entityStore.getPlanetWithId(planetId);\n\n    try {\n      if (!planet || !isLocatable(planet)) {\n        throw new Error(\"you can't prospect a planet you haven't discovered\");\n      }\n\n      if (!bypassChecks) {\n        if (this.checkGameHasEnded()) throw new Error('game ended');\n\n        if (!planet) {\n          throw new Error(\"you can't prospect a planet you haven't discovered\");\n        }\n\n        if (planet.owner !== this.getAccount()) {\n          throw new Error(\"you can't prospect a planet you don't own\");\n        }\n\n        if (!isLocatable(planet)) {\n          throw new Error(\"you don't know this planet's location\");\n        }\n\n        if (planet.prospectedBlockNumber !== undefined) {\n          throw new Error('someone already prospected this planet');\n        }\n\n        if (planet.transactions?.hasTransaction(isUnconfirmedProspectPlanetTx)) {\n          throw new Error(\"you're already looking bro...\");\n        }\n\n        if (planet.planetType !== PlanetType.RUINS) {\n          throw new Error(\"this planet doesn't have an artifact on it.\");\n        }\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-prospectPlanet`, planetId);\n\n      const txIntent: UnconfirmedProspectPlanet = {\n        methodName: 'prospectPlanet',\n        contract: this.contractsAPI.contract,\n        planetId: planetId,\n        args: Promise.resolve([locationIdToDecStr(planetId)]),\n      };\n\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      tx.confirmedPromise.then(() =>\n        NotificationManager.getInstance().artifactProspected(planet as LocatablePlanet)\n      );\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('prospectPlanet', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Calls the contract to find an artifact on the given planet.\n   */\n  public async findArtifact(\n    planetId: LocationId,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedFindArtifact>> {\n    const planet = this.entityStore.getPlanetWithId(planetId);\n\n    try {\n      if (!planet) {\n        throw new Error(\"you can't find artifacts on a planet you haven't discovered\");\n      }\n\n      if (!isLocatable(planet)) {\n        throw new Error(\"you don't know the biome of this planet\");\n      }\n\n      if (!bypassChecks) {\n        if (this.checkGameHasEnded()) {\n          throw new Error('game has ended');\n        }\n\n        if (planet.owner !== this.getAccount()) {\n          throw new Error(\"you can't find artifacts on planets you don't own\");\n        }\n\n        if (planet.hasTriedFindingArtifact) {\n          throw new Error('someone already tried finding an artifact on this planet');\n        }\n\n        if (planet.transactions?.hasTransaction(isUnconfirmedFindArtifactTx)) {\n          throw new Error(\"you're already looking bro...\");\n        }\n\n        if (planet.planetType !== PlanetType.RUINS) {\n          throw new Error(\"this planet doesn't have an artifact on it.\");\n        }\n      }\n\n      // this is shitty. used for the popup window\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-findArtifactOnPlanet`, planetId);\n\n      const txIntent: UnconfirmedFindArtifact = {\n        methodName: 'findArtifact',\n        contract: this.contractsAPI.contract,\n        planetId: planet.locationId,\n        args: this.snarkHelper.getFindArtifactArgs(\n          planet.location.coords.x,\n          planet.location.coords.y\n        ),\n      };\n\n      const tx = await this.contractsAPI.submitTransaction<UnconfirmedFindArtifact>(txIntent);\n\n      tx.confirmedPromise\n        .then(() => {\n          return this.waitForPlanet<Artifact>(planet.locationId, ({ current }: Diff<Planet>) => {\n            return current.heldArtifactIds\n              .map(this.getArtifactWithId.bind(this))\n              .find((a: Artifact) => a?.planetDiscoveredOn === planet.locationId) as Artifact;\n          }).then((foundArtifact) => {\n            if (!foundArtifact) throw new Error('Artifact not found?');\n            const notifManager = NotificationManager.getInstance();\n\n            notifManager.artifactFound(planet as LocatablePlanet, foundArtifact);\n          });\n        })\n        .catch(console.log);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('findArtifact', e.message);\n      throw e;\n    }\n  }\n\n  getContractConstants(): ContractConstants {\n    return this.contractConstants;\n  }\n\n  /**\n   * Submits a transaction to the blockchain to deposit an artifact on a given planet.\n   * You must own the planet and you must own the artifact directly (can't be locked in contract)\n   */\n  public async depositArtifact(\n    locationId: LocationId,\n    artifactId: ArtifactId\n  ): Promise<Transaction<UnconfirmedDepositArtifact>> {\n    try {\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-depositPlanet`, locationId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-depositArtifact`, artifactId);\n\n      if (this.checkGameHasEnded()) {\n        const error = new Error('game has ended');\n        this.getNotificationsManager().txInitError('depositArtifact', error.message);\n        throw error;\n      }\n\n      const txIntent: UnconfirmedDepositArtifact = {\n        methodName: 'depositArtifact',\n        contract: this.contractsAPI.contract,\n        locationId,\n        artifactId,\n        args: Promise.resolve([locationIdToDecStr(locationId), artifactIdToDecStr(artifactId)]),\n      };\n\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      tx.confirmedPromise.then(() =>\n        this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = locationId))\n      );\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('depositArtifact', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Withdraws the artifact that is locked up on the given planet.\n   */\n  public async withdrawArtifact(\n    locationId: LocationId,\n    artifactId: ArtifactId,\n    bypassChecks = true\n  ): Promise<Transaction<UnconfirmedWithdrawArtifact>> {\n    try {\n      if (!bypassChecks) {\n        if (this.checkGameHasEnded()) {\n          throw new Error('game has ended');\n        }\n        const planet = this.entityStore.getPlanetWithId(locationId);\n        if (!planet) {\n          throw new Error('tried to withdraw from unknown planet');\n        }\n        if (!artifactId) {\n          throw new Error('must supply an artifact id');\n        }\n      }\n\n      // this is shitty. used for the popup window\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawPlanet`, locationId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawArtifact`, artifactId);\n\n      const txIntent: UnconfirmedWithdrawArtifact = {\n        methodName: 'withdrawArtifact',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(locationId), artifactIdToDecStr(artifactId)]),\n        locationId,\n        artifactId,\n      };\n\n      this.terminal.current?.println(\n        'WITHDRAW_ARTIFACT: sending withdrawal to blockchain',\n        TerminalTextStyle.Sub\n      );\n      this.terminal.current?.newline();\n\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      tx.confirmedPromise.then(() =>\n        this.getGameObjects().updateArtifact(artifactId, (a) => (a.onPlanetId = undefined))\n      );\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('withdrawArtifact', e.message);\n      throw e;\n    }\n  }\n\n  public async activateArtifact(\n    locationId: LocationId,\n    artifactId: ArtifactId,\n    wormholeTo: LocationId | undefined,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedActivateArtifact>> {\n    try {\n      if (this.checkGameHasEnded()) {\n        throw new Error('game has ended');\n      }\n      if (!bypassChecks) {\n        const planet = this.entityStore.getPlanetWithId(locationId);\n        if (this.checkGameHasEnded()) {\n          throw new Error('game has ended');\n        }\n\n        if (!planet) {\n          throw new Error('tried to activate on an unknown planet');\n        }\n        if (!artifactId) {\n          throw new Error('must supply an artifact id');\n        }\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-activatePlanet`, locationId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-activateArtifact`, artifactId);\n\n      const txIntent: UnconfirmedActivateArtifact = {\n        methodName: 'activateArtifact',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([\n          locationIdToDecStr(locationId),\n          artifactIdToDecStr(artifactId),\n          wormholeTo ? locationIdToDecStr(wormholeTo) : '0',\n        ]),\n        locationId,\n        artifactId,\n        wormholeTo,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('activateArtifact', e.message);\n      throw e;\n    }\n  }\n\n  public async deactivateArtifact(\n    locationId: LocationId,\n    artifactId: ArtifactId,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedDeactivateArtifact>> {\n    try {\n      if (!bypassChecks) {\n        const planet = this.entityStore.getPlanetWithId(locationId);\n        if (!planet) {\n          throw new Error('tried to deactivate on an unknown planet');\n        }\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-deactivatePlanet`, locationId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-deactivateArtifact`, artifactId);\n\n      const txIntent: UnconfirmedDeactivateArtifact = {\n        methodName: 'deactivateArtifact',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(locationId)]),\n        locationId,\n        artifactId,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('deactivateArtifact', e.message);\n      throw e;\n    }\n  }\n\n  public async withdrawSilver(\n    locationId: LocationId,\n    amount: number,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedWithdrawSilver>> {\n    try {\n      if (!bypassChecks) {\n        if (!this.account) throw new Error('no account');\n        if (this.checkGameHasEnded()) {\n          throw new Error('game has ended');\n        }\n        const planet = this.entityStore.getPlanetWithId(locationId);\n        if (!planet) {\n          throw new Error('tried to withdraw silver from an unknown planet');\n        }\n        if (planet.planetType !== PlanetType.TRADING_POST) {\n          throw new Error('can only withdraw silver from spacetime rips');\n        }\n        if (planet.owner !== this.account) {\n          throw new Error('can only withdraw silver from a planet you own');\n        }\n        if (planet.transactions?.hasTransaction(isUnconfirmedWithdrawSilverTx)) {\n          throw new Error('a withdraw silver action is already in progress for this planet');\n        }\n        if (amount > planet.silver) {\n          throw new Error('not enough silver to withdraw!');\n        }\n        if (amount === 0) {\n          throw new Error('must withdraw more than 0 silver!');\n        }\n        if (planet.destroyed) {\n          throw new Error(\"can't withdraw silver from a destroyed planet\");\n        }\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-withdrawSilverPlanet`, locationId);\n\n      const txIntent: UnconfirmedWithdrawSilver = {\n        methodName: 'withdrawSilver',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(locationId), amount * CONTRACT_PRECISION]),\n        locationId,\n        amount,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('withdrawSilver', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * We have two locations which planet state can live: on the server, and on the blockchain. We use\n   * the blockchain for the 'physics' of the universe, and the webserver for optional 'add-on'\n   * features, which are cryptographically secure, but live off-chain.\n   *\n   * This function loads the planet states which live on the server. Plays nicely with our\n   * notifications system and sets the appropriate loading state values on the planet.\n   */\n  public async refreshServerPlanetStates(planetIds: LocationId[]) {\n    const planets = this.getPlanetsWithIds(planetIds);\n\n    planetIds.forEach((id) =>\n      this.getGameObjects().updatePlanet(id, (p) => {\n        p.loadingServerState = true;\n      })\n    );\n\n    const messages = await getMessagesOnPlanets({ planets: planetIds });\n\n    planets.forEach((planet) => {\n      const previousPlanetEmoji = getEmojiMessage(planet);\n      planet.messages = messages[planet.locationId];\n      const nowPlanetEmoji = getEmojiMessage(planet);\n\n      // an emoji was added\n      if (previousPlanetEmoji === undefined && nowPlanetEmoji !== undefined) {\n        planet.emojiZoopAnimation = easeInAnimation(2000);\n        // an emoji was removed\n      } else if (nowPlanetEmoji === undefined && previousPlanetEmoji !== undefined) {\n        planet.emojiZoopAnimation = undefined;\n        planet.emojiZoopOutAnimation = emojiEaseOutAnimation(3000, previousPlanetEmoji.body.emoji);\n      }\n    });\n\n    planetIds.forEach((id) =>\n      this.getGameObjects().updatePlanet(id, (p) => {\n        p.loadingServerState = false;\n        p.needsServerRefresh = false;\n      })\n    );\n  }\n\n  /**\n   * If you are the owner of this planet, you can set an 'emoji' to hover above the planet.\n   * `emojiStr` must be a string that contains a single emoji, otherwise this function will throw an\n   * error.\n   *\n   * The emoji is stored off-chain in a postgres database. We verify planet ownership via a contract\n   * call from the webserver, and by verifying that the request to add (or remove) an emoji from a\n   * planet was signed by the owner.\n   */\n  public setPlanetEmoji(locationId: LocationId, emojiStr: string) {\n    return this.submitPlanetMessage(locationId, PlanetMessageType.EmojiFlag, {\n      emoji: emojiStr,\n    });\n  }\n\n  /**\n   * If you are the owner of this planet, you can delete the emoji that is hovering above the\n   * planet.\n   */\n  public async clearEmoji(locationId: LocationId) {\n    if (this.account === undefined) {\n      throw new Error(\"can't clear emoji: not logged in\");\n    }\n\n    if (this.getPlanetWithId(locationId)?.unconfirmedClearEmoji) {\n      throw new Error(`can't clear emoji: alreading clearing emoji from ${locationId}`);\n    }\n\n    this.getGameObjects().updatePlanet(locationId, (p) => {\n      p.unconfirmedClearEmoji = true;\n    });\n\n    const request = await this.ethConnection.signMessageObject({\n      locationId,\n      ids: this.getPlanetWithId(locationId)?.messages?.map((m) => m.id) || [],\n    });\n\n    try {\n      await deleteMessages(request);\n    } catch (e) {\n      throw e;\n    } finally {\n      this.getGameObjects().updatePlanet(locationId, (p) => {\n        p.needsServerRefresh = true;\n        p.unconfirmedClearEmoji = false;\n      });\n    }\n\n    await this.refreshServerPlanetStates([locationId]);\n  }\n\n  public async submitDisconnectTwitter(twitter: string) {\n    await disconnectTwitter(await this.ethConnection.signMessageObject({ twitter }));\n    await this.refreshTwitters();\n  }\n\n  /**\n   * The planet emoji feature is built on top of a more general 'Planet Message' system, which\n   * allows players to upload pieces of data called 'Message's to planets that they own. Emojis are\n   * just one type of message. Their implementation leaves the door open to more off-chain data.\n   */\n  private async submitPlanetMessage(\n    locationId: LocationId,\n    type: PlanetMessageType,\n    body: unknown\n  ) {\n    if (this.account === undefined) {\n      throw new Error(\"can't submit planet message not logged in\");\n    }\n\n    if (this.getPlanetWithId(locationId)?.unconfirmedAddEmoji) {\n      throw new Error(`can't submit planet message: already submitting for planet ${locationId}`);\n    }\n\n    this.getGameObjects().updatePlanet(locationId, (p) => {\n      p.unconfirmedAddEmoji = true;\n    });\n\n    const request = await this.ethConnection.signMessageObject({\n      locationId,\n      sender: this.account,\n      type,\n      body,\n    });\n\n    try {\n      await addMessage(request);\n    } catch (e) {\n      throw e;\n    } finally {\n      this.getGameObjects().updatePlanet(locationId, (p) => {\n        p.unconfirmedAddEmoji = false;\n        p.needsServerRefresh = true;\n      });\n    }\n\n    await this.refreshServerPlanetStates([locationId]);\n  }\n\n  /**\n   * Checks that a message signed by {@link GameManager#signMessage} was signed by the address that\n   * it claims it was signed by.\n   */\n  private async verifyMessage(message: SignedMessage<unknown>): Promise<boolean> {\n    const preSigned = JSON.stringify(message.message);\n\n    return verifySignature(preSigned, message.signature as string, message.sender);\n  }\n\n  /**\n   * Submits a transaction to the blockchain to move the given amount of resources from\n   * the given planet to the given planet.\n   */\n  public async move(\n    from: LocationId,\n    to: LocationId,\n    forces: number,\n    silver: number,\n    artifactMoved?: ArtifactId,\n    abandoning = false,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedMove>> {\n    localStorage.setItem(`${this.getAccount()?.toLowerCase()}-fromPlanet`, from);\n    localStorage.setItem(`${this.getAccount()?.toLowerCase()}-toPlanet`, to);\n\n    try {\n      if (!bypassChecks && this.checkGameHasEnded()) {\n        throw new Error('game has ended');\n      }\n\n      const arrivalsToOriginPlanet = this.entityStore.getArrivalIdsForLocation(from);\n      const hasIncomingVoyage = arrivalsToOriginPlanet && arrivalsToOriginPlanet.length > 0;\n      if (abandoning && hasIncomingVoyage) {\n        throw new Error('cannot abandon a planet that has incoming voyages');\n      }\n\n      const oldLocation = this.entityStore.getLocationOfPlanet(from);\n      const newLocation = this.entityStore.getLocationOfPlanet(to);\n      if (!oldLocation) {\n        throw new Error('tried to move from planet that does not exist');\n      }\n      if (!newLocation) {\n        throw new Error('tried to move from planet that does not exist');\n      }\n\n      const oldX = oldLocation.coords.x;\n      const oldY = oldLocation.coords.y;\n      const newX = newLocation.coords.x;\n      const newY = newLocation.coords.y;\n      const xDiff = newX - oldX;\n      const yDiff = newY - oldY;\n\n      const distMax = Math.ceil(Math.sqrt(xDiff ** 2 + yDiff ** 2));\n\n      // Contract will automatically send full forces/silver on abandon\n      const shipsMoved = !abandoning ? forces : 0;\n      const silverMoved = !abandoning ? silver : 0;\n\n      if (newX ** 2 + newY ** 2 >= this.worldRadius ** 2) {\n        throw new Error('attempted to move out of bounds');\n      }\n\n      const oldPlanet = this.entityStore.getPlanetWithLocation(oldLocation);\n\n      if (\n        ((!bypassChecks && !this.account) || !oldPlanet || oldPlanet.owner !== this.account) &&\n        !isSpaceShip(this.getArtifactWithId(artifactMoved)?.artifactType)\n      ) {\n        throw new Error('attempted to move from a planet not owned by player');\n      }\n\n      const getArgs = async (): Promise<unknown[]> => {\n        const snarkArgs = await this.snarkHelper.getMoveArgs(\n          oldX,\n          oldY,\n          newX,\n          newY,\n          this.worldRadius,\n          distMax\n        );\n\n        const args: MoveArgs = [\n          snarkArgs[ZKArgIdx.PROOF_A],\n          snarkArgs[ZKArgIdx.PROOF_B],\n          snarkArgs[ZKArgIdx.PROOF_C],\n          [\n            ...snarkArgs[ZKArgIdx.DATA],\n            (shipsMoved * CONTRACT_PRECISION).toString(),\n            (silverMoved * CONTRACT_PRECISION).toString(),\n            '0',\n            abandoning ? '1' : '0',\n          ],\n        ] as MoveArgs;\n\n        this.terminal.current?.println('MOVE: calculated SNARK with args:', TerminalTextStyle.Sub);\n        this.terminal.current?.println(\n          JSON.stringify(hexifyBigIntNestedArray(args)),\n          TerminalTextStyle.Sub\n        );\n        this.terminal.current?.newline();\n\n        if (artifactMoved) {\n          args[ZKArgIdx.DATA][MoveArgIdxs.ARTIFACT_SENT] = artifactIdToDecStr(artifactMoved);\n        }\n\n        return args;\n      };\n\n      const txIntent: UnconfirmedMove = {\n        methodName: 'move',\n        contract: this.contractsAPI.contract,\n        args: getArgs(),\n        from: oldLocation.hash,\n        to: newLocation.hash,\n        forces: shipsMoved,\n        silver: silverMoved,\n        artifact: artifactMoved,\n        abandoning,\n      };\n\n      if (artifactMoved) {\n        const artifact = this.entityStore.getArtifactById(artifactMoved);\n\n        if (!bypassChecks) {\n          if (!artifact) {\n            throw new Error(\"couldn't find this artifact\");\n          }\n          if (isActivated(artifact)) {\n            throw new Error(\"can't move an activated artifact\");\n          }\n          if (!oldPlanet?.heldArtifactIds?.includes(artifactMoved)) {\n            throw new Error(\"that artifact isn't on this planet!\");\n          }\n        }\n      }\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('move', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Submits a transaction to the blockchain to upgrade the given planet with the given\n   * upgrade branch. You must own the planet, and have enough silver on it to complete\n   * the upgrade.\n   */\n  public async upgrade(\n    planetId: LocationId,\n    branch: number,\n    _bypassChecks = false\n  ): Promise<Transaction<UnconfirmedUpgrade>> {\n    try {\n      // this is shitty\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-upPlanet`, planetId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-branch`, branch.toString());\n\n      const txIntent: UnconfirmedUpgrade = {\n        methodName: 'upgradePlanet',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(planetId), branch.toString()]),\n        locationId: planetId,\n        upgradeBranch: branch,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('upgradePlanet', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Submits a transaction to the blockchain to buy a hat for the given planet. You must own the\n   * planet. Warning costs real xdai. Hats are permanently locked to a planet. They are purely\n   * cosmetic and a great way to BM your opponents or just look your best. Just like in the real\n   * world, more money means more hat.\n   */\n  public async buyHat(\n    planetId: LocationId,\n    _bypassChecks = false\n  ): Promise<Transaction<UnconfirmedBuyHat>> {\n    const planetLoc = this.entityStore.getLocationOfPlanet(planetId);\n    const planet = this.entityStore.getPlanetWithLocation(planetLoc);\n\n    try {\n      if (!planetLoc) {\n        console.error('planet not found');\n        throw new Error('[TX ERROR] Planet not found');\n      }\n      if (!planet) {\n        console.error('planet not found');\n        throw new Error('[TX ERROR] Planet not found');\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-hatPlanet`, planetId);\n      localStorage.setItem(\n        `${this.getAccount()?.toLowerCase()}-hatLevel`,\n        planet.hatLevel.toString()\n      );\n\n      const txIntent: UnconfirmedBuyHat = {\n        methodName: 'buyHat',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(planetId)]),\n        locationId: planetId,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent, {\n        gasLimit: 500000,\n        value: bigInt(1000000000000000000)\n          .multiply(2 ** planet.hatLevel)\n          .toString(),\n      });\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('buyHat', e.message);\n      throw e;\n    }\n  }\n\n  // TODO: Change this to transferPlanet in a breaking release\n  public async transferOwnership(\n    planetId: LocationId,\n    newOwner: EthAddress,\n    bypassChecks = false\n  ): Promise<Transaction<UnconfirmedPlanetTransfer>> {\n    try {\n      if (!bypassChecks) {\n        if (this.checkGameHasEnded()) {\n          throw new Error('game has ended');\n        }\n        const planetLoc = this.entityStore.getLocationOfPlanet(planetId);\n        if (!planetLoc) {\n          console.error('planet not found');\n          throw new Error('[TX ERROR] Planet not found');\n        }\n        const planet = this.entityStore.getPlanetWithLocation(planetLoc);\n        if (!planet) {\n          console.error('planet not found');\n          throw new Error('[TX ERROR] Planet not found');\n        }\n      }\n\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-transferPlanet`, planetId);\n      localStorage.setItem(`${this.getAccount()?.toLowerCase()}-transferOwner`, newOwner);\n\n      const txIntent: UnconfirmedPlanetTransfer = {\n        methodName: 'transferPlanet',\n        contract: this.contractsAPI.contract,\n        args: Promise.resolve([locationIdToDecStr(planetId), newOwner]),\n        planetId,\n        newOwner,\n      };\n\n      // Always await the submitTransaction so we can catch rejections\n      const tx = await this.contractsAPI.submitTransaction(txIntent);\n\n      return tx;\n    } catch (e) {\n      this.getNotificationsManager().txInitError('transferPlanet', e.message);\n      throw e;\n    }\n  }\n\n  /**\n   * Makes this game manager aware of a new chunk - which includes its location, size,\n   * as well as all of the planets contained in that chunk. Causes the client to load\n   * all of the information about those planets from the blockchain.\n   */\n  addNewChunk(chunk: Chunk): GameManager {\n    this.persistentChunkStore.addChunk(chunk, true);\n    for (const planetLocation of chunk.planetLocations) {\n      this.entityStore.addPlanetLocation(planetLocation);\n\n      if (this.entityStore.isPlanetInContract(planetLocation.hash)) {\n        this.hardRefreshPlanet(planetLocation.hash); // don't need to await, just start the process of hard refreshing\n      }\n    }\n    return this;\n  }\n\n  listenForNewBlock() {\n    this.getEthConnection().blockNumber$.subscribe((blockNumber) => {\n      if (this.captureZoneGenerator) {\n        this.captureZoneGenerator.generate(blockNumber);\n      }\n    });\n  }\n\n  /**\n   * To add multiple chunks at once, use this function rather than `addNewChunk`, in order\n   * to load all of the associated planet data in an efficient manner.\n   */\n  async bulkAddNewChunks(chunks: Chunk[]): Promise<void> {\n    this.terminal.current?.println(\n      'IMPORTING MAP: if you are importing a large map, this may take a while...'\n    );\n    const planetIdsToUpdate: LocationId[] = [];\n    for (const chunk of chunks) {\n      this.persistentChunkStore.addChunk(chunk, true);\n      for (const planetLocation of chunk.planetLocations) {\n        this.entityStore.addPlanetLocation(planetLocation);\n\n        if (this.entityStore.isPlanetInContract(planetLocation.hash)) {\n          // Await this so we don't crash the game\n          planetIdsToUpdate.push(planetLocation.hash);\n        }\n      }\n    }\n    this.terminal.current?.println(\n      `downloading data for ${planetIdsToUpdate.length} planets...`,\n      TerminalTextStyle.Sub\n    );\n    this.bulkHardRefreshPlanets(planetIdsToUpdate);\n  }\n\n  // utils - scripting only\n\n  /**\n   * Gets the maximuim distance that you can send your energy from the given planet,\n   * using the given percentage of that planet's current silver.\n   */\n  getMaxMoveDist(planetId: LocationId, sendingPercent: number, abandoning: boolean): number {\n    const planet = this.getPlanetWithId(planetId);\n    if (!planet) throw new Error('origin planet unknown');\n    return getRange(planet, sendingPercent, this.getRangeBuff(abandoning));\n  }\n\n  /**\n   * Gets the distance between two planets. Throws an exception if you don't\n   * know the location of either planet. Takes into account wormholes.\n   */\n  getDist(fromId: LocationId, toId: LocationId): number {\n    const from = this.entityStore.getPlanetWithId(fromId);\n    const to = this.entityStore.getPlanetWithId(toId);\n\n    if (!from) throw new Error('origin planet unknown');\n    if (!to) throw new Error('destination planet unknown');\n    if (!isLocatable(from)) throw new Error('origin location unknown');\n    if (!isLocatable(to)) throw new Error('destination location unknown');\n\n    const wormholeFactors = this.getWormholeFactors(from, to);\n\n    let distance = this.getDistCoords(from.location.coords, to.location.coords);\n\n    if (wormholeFactors) {\n      distance /= wormholeFactors.distanceFactor;\n    }\n\n    return distance;\n  }\n\n  /**\n   * Gets the distance between two coordinates in space.\n   */\n  getDistCoords(fromCoords: WorldCoords, toCoords: WorldCoords) {\n    return Math.sqrt((fromCoords.x - toCoords.x) ** 2 + (fromCoords.y - toCoords.y) ** 2);\n  }\n\n  /**\n   * Gets all the planets that you can reach with at least 1 energy from\n   * the given planet. Does not take into account wormholes.\n   */\n  getPlanetsInRange(planetId: LocationId, sendingPercent: number, abandoning: boolean): Planet[] {\n    const planet = this.entityStore.getPlanetWithId(planetId);\n    if (!planet) throw new Error('planet unknown');\n    if (!isLocatable(planet)) throw new Error('planet location unknown');\n\n    // Performance improvements originally suggested by [@modokon](https://github.com/modukon)\n    // at https://github.com/darkforest-eth/client/issues/15\n    // Improved by using `planetMap` by [@phated](https://github.com/phated)\n    const result = [];\n    const range = getRange(planet, sendingPercent, this.getRangeBuff(abandoning));\n    for (const p of this.getPlanetMap().values()) {\n      if (isLocatable(p)) {\n        if (this.getDistCoords(planet.location.coords, p.location.coords) < range) {\n          result.push(p);\n        }\n      }\n    }\n\n    return result;\n  }\n\n  /**\n   * Gets the amount of energy needed in order for a voyage from the given to the given\n   * planet to arrive with your desired amount of energy.\n   */\n  getEnergyNeededForMove(\n    fromId: LocationId,\n    toId: LocationId,\n    arrivingEnergy: number,\n    abandoning = false\n  ): number {\n    const from = this.getPlanetWithId(fromId);\n    if (!from) throw new Error('origin planet unknown');\n    const dist = this.getDist(fromId, toId);\n    const range = from.range * this.getRangeBuff(abandoning);\n    const rangeSteps = dist / range;\n\n    const arrivingProp = arrivingEnergy / from.energyCap + 0.05;\n\n    return arrivingProp * Math.pow(2, rangeSteps) * from.energyCap;\n  }\n\n  /**\n   * Gets the amount of energy that would arrive if a voyage with the given parameters\n   * was to occur. The toPlanet is optional, in case you want an estimate that doesn't include\n   * wormhole speedups.\n   */\n  getEnergyArrivingForMove(\n    fromId: LocationId,\n    toId: LocationId | undefined,\n    distance: number | undefined,\n    sentEnergy: number,\n    abandoning: boolean\n  ) {\n    const from = this.getPlanetWithId(fromId);\n    const to = this.getPlanetWithId(toId);\n\n    if (!from) throw new Error(`unknown planet`);\n    if (distance === undefined && toId === undefined)\n      throw new Error(`you must provide either a target planet or a distance`);\n\n    const dist = (toId && this.getDist(fromId, toId)) || (distance as number);\n\n    if (to && toId) {\n      const wormholeFactors = this.getWormholeFactors(from, to);\n      if (wormholeFactors !== undefined) {\n        if (to.owner !== from.owner) {\n          return 0;\n        }\n      }\n    }\n\n    const range = from.range * this.getRangeBuff(abandoning);\n    const scale = (1 / 2) ** (dist / range);\n    let ret = scale * sentEnergy - 0.05 * from.energyCap;\n    if (ret < 0) ret = 0;\n\n    return ret;\n  }\n\n  /**\n   * Gets the active artifact on this planet, if one exists.\n   */\n  getActiveArtifact(planet: Planet): Artifact | undefined {\n    const artifacts = this.getArtifactsWithIds(planet.heldArtifactIds);\n    const active = artifacts.find((a) => a && isActivated(a));\n\n    return active;\n  }\n\n  /**\n   * If there's an active artifact on either of these planets which happens to be a wormhole which\n   * is active and targetting the other planet, return the wormhole boost which is greater. Values\n   * represent a multiplier.\n   */\n  getWormholeFactors(\n    fromPlanet: Planet,\n    toPlanet: Planet\n  ): { distanceFactor: number; speedFactor: number } | undefined {\n    const fromActiveArtifact = this.getActiveArtifact(fromPlanet);\n    const toActiveArtifact = this.getActiveArtifact(toPlanet);\n\n    let greaterRarity: ArtifactRarity | undefined;\n\n    if (\n      fromActiveArtifact?.artifactType === ArtifactType.Wormhole &&\n      fromActiveArtifact.wormholeTo === toPlanet.locationId\n    ) {\n      greaterRarity = fromActiveArtifact.rarity;\n    }\n\n    if (\n      toActiveArtifact?.artifactType === ArtifactType.Wormhole &&\n      toActiveArtifact.wormholeTo === fromPlanet.locationId\n    ) {\n      if (greaterRarity === undefined) {\n        greaterRarity = toActiveArtifact.rarity;\n      } else {\n        greaterRarity = Math.max(greaterRarity, toActiveArtifact.rarity) as ArtifactRarity;\n      }\n    }\n\n    const rangeUpgradesPerRarity = [0, 2, 4, 6, 8, 10];\n    const speedUpgradesPerRarity = [0, 10, 20, 30, 40, 50];\n\n    if (!greaterRarity || greaterRarity <= ArtifactRarity.Unknown) {\n      return undefined;\n    }\n\n    return {\n      distanceFactor: rangeUpgradesPerRarity[greaterRarity],\n      speedFactor: speedUpgradesPerRarity[greaterRarity],\n    };\n  }\n\n  /**\n   * Gets the amount of time, in seconds that a voyage between from the first to the\n   * second planet would take.\n   */\n  getTimeForMove(fromId: LocationId, toId: LocationId, abandoning = false): number {\n    const from = this.getPlanetWithId(fromId);\n    if (!from) throw new Error('origin planet unknown');\n    const dist = this.getDist(fromId, toId);\n\n    const speed = from.speed * this.getSpeedBuff(abandoning);\n    return dist / (speed / 100);\n  }\n\n  /**\n   * Gets the temperature of a given location.\n   */\n  getTemperature(coords: WorldCoords): number {\n    const p = this.spaceTypePerlin(coords, false);\n    return (16 - p) * 16;\n  }\n\n  /**\n   * Load the serialized versions of all the plugins that this player has.\n   */\n  public async loadPlugins(): Promise<SerializedPlugin[]> {\n    return this.persistentChunkStore.loadPlugins();\n  }\n\n  /**\n   * Overwrites all the saved plugins to equal the given array of plugins.\n   */\n  public async savePlugins(savedPlugins: SerializedPlugin[]): Promise<void> {\n    await this.persistentChunkStore.savePlugins(savedPlugins);\n  }\n\n  /**\n   * Whether or not the given planet is capable of minting an artifact.\n   */\n  public isPlanetMineable(p: Planet): boolean {\n    return p.planetType === PlanetType.RUINS;\n  }\n\n  /**\n   * Returns constructors of classes that may be useful for developing plugins.\n   */\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  public getConstructors() {\n    return {\n      MinerManager,\n      SpiralPattern,\n      SwissCheesePattern,\n      TowardsCenterPattern,\n      TowardsCenterPatternV2,\n    };\n  }\n\n  /**\n   * Gets the perlin value at the given location in the world. SpaceType is based\n   * on this value.\n   */\n  public spaceTypePerlin(coords: WorldCoords, floor: boolean): number {\n    return perlin(coords, {\n      key: this.hashConfig.spaceTypeKey,\n      scale: this.hashConfig.perlinLengthScale,\n      mirrorX: this.hashConfig.perlinMirrorX,\n      mirrorY: this.hashConfig.perlinMirrorY,\n      floor,\n    });\n  }\n\n  /**\n   * Gets the biome perlin valie at the given location in the world.\n   */\n  public biomebasePerlin(coords: WorldCoords, floor: boolean): number {\n    return perlin(coords, {\n      key: this.hashConfig.biomebaseKey,\n      scale: this.hashConfig.perlinLengthScale,\n      mirrorX: this.hashConfig.perlinMirrorX,\n      mirrorY: this.hashConfig.perlinMirrorY,\n      floor,\n    });\n  }\n\n  public locationBigIntFromCoords(coords: WorldCoords): BigInteger {\n    return this.planetHashMimc(coords.x, coords.y);\n  }\n\n  /**\n   * Helpful for listening to user input events.\n   */\n  public getUIEventEmitter() {\n    return UIEmitter.getInstance();\n  }\n\n  public getCaptureZoneGenerator() {\n    return this.captureZoneGenerator;\n  }\n\n  /**\n   * Emits when new capture zones are generated.\n   */\n  public get captureZoneGeneratedEmitter(): Monomitter<CaptureZonesGeneratedEvent> | undefined {\n    return this.captureZoneGenerator?.generated$;\n  }\n\n  public getNotificationsManager() {\n    return NotificationManager.getInstance();\n  }\n\n  getWormholes(): Iterable<Wormhole> {\n    return this.entityStore.getWormholes();\n  }\n\n  /** Return a reference to the planet map */\n  public getPlanetMap(): Map<LocationId, Planet> {\n    return this.entityStore.getPlanetMap();\n  }\n\n  /** Return a reference to the artifact map */\n  public getArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.entityStore.getArtifactMap();\n  }\n\n  /** Return a reference to the map of my planets */\n  public getMyPlanetMap(): Map<LocationId, Planet> {\n    return this.entityStore.getMyPlanetMap();\n  }\n\n  /** Return a reference to the map of my artifacts */\n  public getMyArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.entityStore.getMyArtifactMap();\n  }\n\n  public getPlanetUpdated$(): Monomitter<LocationId> {\n    return this.entityStore.planetUpdated$;\n  }\n\n  public getArtifactUpdated$(): Monomitter<ArtifactId> {\n    return this.entityStore.artifactUpdated$;\n  }\n\n  public getMyPlanetsUpdated$(): Monomitter<Map<LocationId, Planet>> {\n    return this.entityStore.myPlanetsUpdated$;\n  }\n\n  public getMyArtifactsUpdated$(): Monomitter<Map<ArtifactId, Artifact>> {\n    return this.entityStore.myArtifactsUpdated$;\n  }\n\n  /**\n   * Returns an instance of a `Contract` from the ethersjs library. This is the library we use to\n   * connect to the blockchain. For documentation about how `Contract` works, see:\n   * https://docs.ethers.io/v5/api/contract/contract/\n   *\n   * Also, registers your contract in the system to make calls against it and to reload it when\n   * necessary (such as the RPC endpoint changing).\n   */\n  public loadContract<T extends Contract>(\n    contractAddress: string,\n    contractABI: ContractInterface\n  ): Promise<T> {\n    return this.ethConnection.loadContract(contractAddress, async (address, provider, signer) =>\n      createContract<T>(address, contractABI, provider, signer)\n    );\n  }\n\n  public testNotification() {\n    NotificationManager.getInstance().reallyLongNotification();\n  }\n\n  /**\n   * Gets a reference to the game's internal representation of the world state. This includes\n   * voyages, planets, artifacts, and active wormholes,\n   */\n  public getGameObjects(): GameObjects {\n    return this.entityStore;\n  }\n\n  public forceTick(locationId: LocationId) {\n    this.getGameObjects().forceTick(locationId);\n  }\n\n  /**\n   * Gets some diagnostic information about the game. Returns a copy, you can't modify it.\n   */\n  public getDiagnostics(): Diagnostics {\n    return { ...this.diagnostics };\n  }\n\n  /**\n   * Updates the diagnostic info of the game using the supplied function. Ideally, each spot in the\n   * codebase that would like to record a metric is able to update its specific metric in a\n   * convenient manner.\n   */\n  public updateDiagnostics(updateFn: (d: Diagnostics) => void): void {\n    updateFn(this.diagnostics);\n  }\n\n  /**\n   * Listen for changes to a planet take action,\n   * eg.\n   * waitForPlanet(\"yourAsteroidId\", ({current}) => current.silverCap / current.silver > 90)\n   * .then(() => {\n   *  // Send Silver to nearby planet\n   * })\n   *\n   * @param locationId A locationId to watch for updates\n   * @param predicate a function that accepts a Diff and should return a truth-y value, value will be passed to promise.resolve()\n   * @returns a promise that will resolve with results returned from the predicate function\n   */\n  public waitForPlanet<T>(\n    locationId: LocationId,\n    predicate: ({ current, previous }: Diff<Planet>) => T | undefined\n  ): Promise<T> {\n    const disposableEmitter = getDisposableEmitter<Planet, LocationId>(\n      this.getPlanetMap(),\n      locationId,\n      this.getPlanetUpdated$()\n    );\n    const diffEmitter = generateDiffEmitter(disposableEmitter);\n    return new Promise((resolve, reject) => {\n      diffEmitter.subscribe(({ current, previous }: Diff<Planet>) => {\n        try {\n          const predicateResults = predicate({ current, previous });\n          if (!!predicateResults) {\n            disposableEmitter.clear();\n            diffEmitter.clear();\n            resolve(predicateResults);\n          }\n        } catch (err) {\n          disposableEmitter.clear();\n          diffEmitter.clear();\n          reject(err);\n        }\n      });\n    });\n  }\n\n  public getSafeMode() {\n    return this.safeMode;\n  }\n\n  public setSafeMode(safeMode: boolean) {\n    this.safeMode = safeMode;\n  }\n\n  public getAddress() {\n    return this.ethConnection.getAddress();\n  }\n\n  public isAdmin(): boolean {\n    return this.getAddress() === this.contractConstants.adminAddress;\n  }\n\n  /**\n   * Right now the only buffs supported in this way are\n   * speed/range buffs from Abandoning a planet.\n   *\n   * The abandoning argument is used when interacting with\n   * this function programmatically.\n   */\n  public getSpeedBuff(abandoning: boolean): number {\n    const { SPACE_JUNK_ENABLED, ABANDON_SPEED_CHANGE_PERCENT } = this.contractConstants;\n    if (SPACE_JUNK_ENABLED && abandoning) {\n      return ABANDON_SPEED_CHANGE_PERCENT / 100;\n    }\n\n    return 1;\n  }\n\n  public getRangeBuff(abandoning: boolean): number {\n    const { SPACE_JUNK_ENABLED, ABANDON_RANGE_CHANGE_PERCENT } = this.contractConstants;\n    if (SPACE_JUNK_ENABLED && abandoning) {\n      return ABANDON_RANGE_CHANGE_PERCENT / 100;\n    }\n\n    return 1;\n  }\n\n  public getSnarkHelper(): SnarkArgsHelper {\n    return this.snarkHelper;\n  }\n\n  public async submitTransaction<T extends TxIntent>(\n    txIntent: T,\n    overrides?: providers.TransactionRequest\n  ): Promise<Transaction<T>> {\n    return this.contractsAPI.submitTransaction(txIntent, overrides);\n  }\n\n  public getContract(): DarkForest {\n    return this.contractsAPI.contract;\n  }\n\n  public getPaused(): boolean {\n    return this.paused;\n  }\n\n  public getPaused$(): Monomitter<boolean> {\n    return this.paused$;\n  }\n}\n\nexport default GameManager;\n"
  },
  {
    "path": "src/Backend/GameLogic/GameObjects.ts",
    "content": "import { EMPTY_ADDRESS, MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants';\nimport { Monomitter, monomitter } from '@darkforest_eth/events';\nimport { hasOwner, isActivated, isLocatable } from '@darkforest_eth/gamelogic';\nimport { bonusFromHex, getBytesFromHex } from '@darkforest_eth/hexgen';\nimport { TxCollection } from '@darkforest_eth/network';\nimport {\n  isUnconfirmedActivateArtifact,\n  isUnconfirmedActivateArtifactTx,\n  isUnconfirmedBuyHat,\n  isUnconfirmedBuyHatTx,\n  isUnconfirmedCapturePlanetTx,\n  isUnconfirmedDeactivateArtifact,\n  isUnconfirmedDeactivateArtifactTx,\n  isUnconfirmedDepositArtifact,\n  isUnconfirmedDepositArtifactTx,\n  isUnconfirmedFindArtifact,\n  isUnconfirmedFindArtifactTx,\n  isUnconfirmedGetShipsTx,\n  isUnconfirmedInvadePlanetTx,\n  isUnconfirmedMove,\n  isUnconfirmedMoveTx,\n  isUnconfirmedProspectPlanet,\n  isUnconfirmedProspectPlanetTx,\n  isUnconfirmedReveal,\n  isUnconfirmedRevealTx,\n  isUnconfirmedTransfer,\n  isUnconfirmedTransferTx,\n  isUnconfirmedUpgrade,\n  isUnconfirmedUpgradeTx,\n  isUnconfirmedWithdrawArtifact,\n  isUnconfirmedWithdrawArtifactTx,\n  isUnconfirmedWithdrawSilver,\n  isUnconfirmedWithdrawSilverTx,\n} from '@darkforest_eth/serde';\nimport {\n  Abstract,\n  ArrivalWithTimer,\n  Artifact,\n  ArtifactId,\n  ArtifactType,\n  Biome,\n  Chunk,\n  ClaimedLocation,\n  EthAddress,\n  LocatablePlanet,\n  LocationId,\n  Planet,\n  PlanetLevel,\n  PlanetType,\n  QueuedArrival,\n  Radii,\n  RevealedLocation,\n  SpaceType,\n  Transaction,\n  TransactionCollection,\n  VoyageId,\n  WorldCoords,\n  WorldLocation,\n  Wormhole,\n} from '@darkforest_eth/types';\nimport autoBind from 'auto-bind';\nimport bigInt from 'big-integer';\nimport { ethers } from 'ethers';\nimport _ from 'lodash';\nimport NotificationManager from '../../Frontend/Game/NotificationManager';\nimport {\n  getArtifactId,\n  getArtifactOwner,\n  getPlanetId,\n  getPlanetOwner,\n  setObjectSyncState,\n} from '../../Frontend/Utils/EmitterUtils';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { arrive, PlanetDiff, updatePlanetToTime } from './ArrivalUtils';\nimport { LayeredMap } from './LayeredMap';\n\ntype CoordsString = Abstract<string, 'CoordString'>;\n\nconst getCoordsString = (coords: WorldCoords): CoordsString => {\n  return `${coords.x},${coords.y}` as CoordsString;\n};\n\n/**\n * Representation of the objects which exist in the world.\n */\nexport class GameObjects {\n  /**\n   * This is a data structure that allows us to efficiently calculate which planets are visible on\n   * the player's screen given the viewport's position and size.\n   */\n  private readonly layeredMap: LayeredMap;\n\n  /**\n   * This address of the player that is currently logged in.\n   *\n   * @todo move this, along with all other objects relating to the currently logged-on player into a\n   * new field: {@code player: PlayerInfo}\n   */\n  private readonly address: EthAddress | undefined;\n\n  /**\n   * Cached index of all known planet data.\n   *\n   * Warning!\n   *\n   * This should NEVER be set to directly! Any time you want to update a planet, you must call the\n   * {@link GameObjects#setPlanet()} function. Following this rule enables us to reliably notify\n   * other parts of the client when a particular object has been updated. TODO: what is the best way\n   * to do this?\n   *\n   * @todo extract the pattern we're using for the field tuples\n   *   - {planets, myPlanets, myPlanetsUpdated, planetUpdated$}\n   *   - {artifacts, myArtifacts, myArtifactsUpdated, artifactUpdated$}\n   *\n   *   into some sort of class.\n   */\n  private readonly planets: Map<LocationId, Planet>;\n\n  /**\n   * Cached index of planets owned by the player.\n   *\n   * @see The same warning applys as the one on {@link GameObjects.planets}\n   */\n  private readonly myPlanets: Map<LocationId, Planet>;\n\n  /**\n   * Cached index of all known artifact data.\n   *\n   * @see The same warning applys as the one on {@link GameObjects.planets}\n   */\n  private readonly artifacts: Map<ArtifactId, Artifact>;\n\n  /**\n   * Cached index of artifacts owned by the player.\n   *\n   * @see The same warning applys as the one on {@link GameObjects.planets}\n   */\n  private readonly myArtifacts: Map<ArtifactId, Artifact>;\n\n  /**\n   * Map from artifact ids to wormholes.\n   */\n  private readonly wormholes: Map<ArtifactId, Wormhole>;\n\n  /**\n   * Set of all planet ids that we know have been interacted-with on-chain.\n   */\n  private readonly touchedPlanetIds: Set<LocationId>;\n\n  /**\n   * Map of arrivals to timers that fire when an arrival arrives, in case that handler needs to be\n   * cancelled for whatever reason.\n   */\n  private readonly arrivals: Map<VoyageId, ArrivalWithTimer>;\n\n  /**\n   * Map from a location id (think of it as the unique id of each planet) to all the ids of the\n   * voyages that are arriving on that planet. These include both the player's own voyages, and also\n   * any potential invader's voyages.\n   */\n  private readonly planetArrivalIds: Map<LocationId, VoyageId[]>;\n\n  /**\n   * Map from location id (unique id of each planet) to some information about the location at which\n   * this planet is located, if this client happens to know the coordinates of this planet.\n   */\n  private readonly planetLocationMap: Map<LocationId, WorldLocation>;\n\n  /**\n   * Map from location ids to, if that location id has been revealed on-chain, the world coordinates\n   * of that location id, as well as some extra information regarding the circumstances of the\n   * revealing of this planet.\n   */\n  private readonly revealedLocations: Map<LocationId, RevealedLocation>;\n\n  /**\n   * Map from location ids to, if that location id has been claimed on-chain, the world coordinates\n   * of that location id, as well as some extra information regarding the circumstances of the\n   * revealing of this planet.\n   */\n  private readonly claimedLocations: Map<LocationId, ClaimedLocation>;\n\n  /**\n   * Some of the game's parameters are downloaded from the blockchain. This allows the client to be\n   * flexible, and connect to any compatible set of Dark Forest contracts, download the parameters,\n   * and join the game, taking into account the unique configuration of those specific Dark Forest\n   * contracts.\n   */\n  private readonly contractConstants: ContractConstants;\n\n  /**\n   * Map from a stringified representation of an x-y coordinate to an object that contains some more\n   * information about the world at that location.\n   */\n  private readonly coordsToLocation: Map<CoordsString, WorldLocation>;\n\n  /**\n   * Transactions that are currently in flight.\n   */\n  public readonly transactions: TransactionCollection;\n\n  /**\n   * Event emitter which publishes whenever a planet is updated.\n   */\n  public readonly planetUpdated$: Monomitter<LocationId>;\n\n  /**\n   * Event emitter which publishes whenever an artifact has been updated.\n   */\n  public readonly artifactUpdated$: Monomitter<ArtifactId>;\n\n  /**\n   * Whenever a planet is updated, we publish to this event with a reference to a map from location\n   * id to planet. We need to rethink this event emitter because it currently publishes every time\n   * that any planet is updated, and if a lot of them are updated at once (which i think is the case\n   * once every two minutes) then this event emitter will publish a shitton of events.\n   * TODO: rethink this\n   */\n  public readonly myPlanetsUpdated$: Monomitter<Map<LocationId, Planet>>;\n\n  /**\n   * Whenever one of the player's artifacts are updated, this event emitter publishes. See\n   * {@link GameObjects.myPlanetsUpdated$} for more info.\n   */\n  public readonly myArtifactsUpdated$: Monomitter<Map<ArtifactId, Artifact>>;\n\n  constructor(\n    address: EthAddress | undefined,\n    touchedPlanets: Map<LocationId, Planet>,\n    allTouchedPlanetIds: Set<LocationId>,\n    revealedLocations: Map<LocationId, RevealedLocation>,\n    claimedLocations: Map<LocationId, ClaimedLocation>,\n    artifacts: Map<ArtifactId, Artifact>,\n    allChunks: Iterable<Chunk>,\n    unprocessedArrivals: Map<VoyageId, QueuedArrival>,\n    unprocessedPlanetArrivalIds: Map<LocationId, VoyageId[]>,\n    contractConstants: ContractConstants,\n    worldRadius: number\n  ) {\n    autoBind(this);\n\n    this.address = address;\n    this.planets = touchedPlanets;\n    this.myPlanets = new Map();\n    this.touchedPlanetIds = allTouchedPlanetIds;\n    this.revealedLocations = revealedLocations;\n    this.claimedLocations = claimedLocations;\n    this.artifacts = artifacts;\n    this.myArtifacts = new Map();\n    this.contractConstants = contractConstants;\n    this.coordsToLocation = new Map();\n    this.planetLocationMap = new Map();\n    const planetArrivalIds = new Map();\n    const arrivals = new Map();\n    this.transactions = new TxCollection();\n    this.wormholes = new Map();\n    this.layeredMap = new LayeredMap(worldRadius);\n\n    this.planetUpdated$ = monomitter();\n    this.artifactUpdated$ = monomitter();\n    this.myArtifactsUpdated$ = monomitter();\n    this.myPlanetsUpdated$ = monomitter();\n\n    for (const chunk of allChunks) {\n      for (const planetLocation of chunk.planetLocations) {\n        this.addPlanetLocation(planetLocation);\n      }\n    }\n    for (const location of revealedLocations.values()) {\n      this.markLocationRevealed(location);\n      this.addPlanetLocation(location);\n    }\n\n    this.replaceArtifactsFromContractData(artifacts.values());\n\n    touchedPlanets.forEach((planet, planetId) => {\n      const arrivalIds = unprocessedPlanetArrivalIds.get(planetId);\n\n      if (planet && arrivalIds) {\n        const arrivalsForPlanetNull: (QueuedArrival | undefined)[] = arrivalIds.map((arrivalId) =>\n          unprocessedArrivals.get(arrivalId)\n        );\n        const arrivalsForPlanet: QueuedArrival[] = arrivalsForPlanetNull.filter(\n          (x) => !!x\n        ) as QueuedArrival[];\n\n        const revealedLocation = revealedLocations.get(planetId);\n        if (revealedLocation) {\n          planet.coordsRevealed = true;\n          planet.revealer = revealedLocation.revealer;\n        }\n        const arrivalsWithTimers = this.processArrivalsForPlanet(\n          planet.locationId,\n          arrivalsForPlanet\n        );\n        planetArrivalIds.set(\n          planetId,\n          arrivalsWithTimers.map((arrival) => arrival.arrivalData.eventId)\n        );\n        for (const arrivalWithTimer of arrivalsWithTimers) {\n          const arrivalId = arrivalWithTimer.arrivalData.eventId;\n          arrivals.set(arrivalId, arrivalWithTimer);\n        }\n        const planetLocation = this.planetLocationMap.get(planetId);\n        if (planet && planetLocation) {\n          (planet as LocatablePlanet).location = planetLocation;\n          (planet as LocatablePlanet).biome = this.getBiome(planetLocation);\n        }\n\n        this.setPlanet(planet);\n      }\n    });\n\n    this.arrivals = arrivals;\n    this.planetArrivalIds = planetArrivalIds;\n\n    for (const [_locId, claimedLoc] of claimedLocations) {\n      this.updatePlanet(claimedLoc.hash, (p) => {\n        p.claimer = claimedLoc.revealer;\n      });\n    }\n\n    // TODO: do this better...\n    // set interval to update all planets every 120s\n    setInterval(() => {\n      this.planets.forEach((planet) => {\n        if (planet && hasOwner(planet)) {\n          updatePlanetToTime(\n            planet,\n            this.getPlanetArtifacts(planet.locationId),\n            Date.now(),\n            this.contractConstants\n          );\n        }\n      });\n    }, 120 * 1000);\n  }\n\n  public getWormholes(): Iterable<Wormhole> {\n    return this.wormholes.values();\n  }\n\n  public getArtifactById(artifactId?: ArtifactId): Artifact | undefined {\n    return artifactId ? this.artifacts.get(artifactId) : undefined;\n  }\n\n  public getArtifactsOwnedBy(addr: EthAddress): Artifact[] {\n    const ret: Artifact[] = [];\n    this.artifacts.forEach((artifact) => {\n      if (artifact.currentOwner === addr || artifact.controller === addr) {\n        ret.push(artifact);\n      }\n    });\n    return ret;\n  }\n\n  public getPlanetArtifacts(planetId: LocationId): Artifact[] {\n    return (this.planets.get(planetId)?.heldArtifactIds || [])\n      .map((id) => this.artifacts.get(id))\n      .filter((a) => !!a) as Artifact[];\n  }\n\n  public getArtifactsOnPlanetsOwnedBy(addr: EthAddress): Artifact[] {\n    const ret: Artifact[] = [];\n    this.artifacts.forEach((artifact) => {\n      if (artifact.onPlanetId) {\n        const planet = this.getPlanetWithId(artifact.onPlanetId, false);\n        if (planet && planet.owner === addr) {\n          ret.push(artifact);\n        }\n      }\n    });\n    return ret;\n  }\n\n  // get planet by ID - must be in contract or known chunks\n  public getPlanetWithId(planetId: LocationId, updateIfStale = true): Planet | undefined {\n    const planet = this.planets.get(planetId);\n    if (planet) {\n      if (updateIfStale) {\n        this.updatePlanetIfStale(planet);\n      }\n      return planet;\n    }\n    const loc = this.getLocationOfPlanet(planetId);\n    if (!loc) return undefined;\n    return this.getPlanetWithLocation(loc);\n  }\n\n  // returns undefined if this planet is neither in contract nor in known chunks\n  // fast query that doesn't update planet if stale\n  public getPlanetLevel(planetId: LocationId): PlanetLevel | undefined {\n    const planet = this.planets.get(planetId);\n    if (planet) {\n      return planet.planetLevel;\n    }\n    return undefined;\n  }\n\n  // returns undefined if this planet is neither in contract nor in known chunks\n  // fast query that doesn't update planet if stale\n  public getPlanetDetailLevel(planetId: LocationId): number | undefined {\n    const planet = this.planets.get(planetId);\n    if (planet) {\n      let detailLevel = planet.planetLevel as number;\n      if (hasOwner(planet)) {\n        detailLevel += 1;\n      }\n      return detailLevel;\n    } else {\n      return undefined;\n    }\n  }\n\n  /**\n   * received some artifact data from the contract. update our stores\n   */\n  public replaceArtifactFromContractData(artifact: Artifact): void {\n    const localArtifact = this.artifacts.get(artifact.id);\n    if (localArtifact) {\n      artifact.transactions = localArtifact.transactions;\n      artifact.onPlanetId = localArtifact.onPlanetId;\n    }\n\n    this.setArtifact(artifact);\n  }\n\n  public replaceArtifactsFromContractData(artifacts: Iterable<Artifact>) {\n    for (const artifact of artifacts) {\n      this.replaceArtifactFromContractData(artifact);\n    }\n  }\n\n  /**\n   * Given a planet id, update the state of the given planet by calling the given update function.\n   * If the planet was updated, then also publish the appropriate event.\n   */\n  public updatePlanet(id: LocationId, updateFn: (p: Planet) => void) {\n    const planet = this.getPlanetWithId(id);\n\n    if (planet !== undefined) {\n      updateFn(planet);\n      this.setPlanet(planet);\n    }\n  }\n\n  /**\n   * Given a planet id, update the state of the given planet by calling the given update function.\n   * If the planet was updated, then also publish the appropriate event.\n   */\n  public updateArtifact(id: ArtifactId | undefined, updateFn: (p: Artifact) => void) {\n    const artifact = this.getArtifactById(id);\n\n    if (artifact !== undefined) {\n      updateFn(artifact);\n      this.setArtifact(artifact);\n    }\n  }\n\n  /**\n   * received some planet data from the contract. update our stores\n   */\n  public replacePlanetFromContractData(\n    planet: Planet,\n    updatedArrivals?: QueuedArrival[],\n    updatedArtifactsOnPlanet?: ArtifactId[],\n    revealedLocation?: RevealedLocation,\n    claimerEthAddress?: EthAddress // TODO: Remove this\n  ): void {\n    this.touchedPlanetIds.add(planet.locationId);\n    // does not modify unconfirmed txs\n    // that is handled by onTxConfirm\n    const localPlanet = this.planets.get(planet.locationId);\n    if (localPlanet) {\n      const {\n        transactions,\n        loadingServerState,\n        needsServerRefresh,\n        lastLoadedServerState,\n        emojiBobAnimation,\n        emojiZoopAnimation,\n        emojiZoopOutAnimation,\n        messages,\n      } = localPlanet;\n      planet.transactions = transactions;\n      planet.loadingServerState = loadingServerState;\n      planet.needsServerRefresh = needsServerRefresh;\n      planet.lastLoadedServerState = lastLoadedServerState;\n      planet.emojiBobAnimation = emojiBobAnimation;\n      planet.emojiZoopAnimation = emojiZoopAnimation;\n      planet.emojiZoopOutAnimation = emojiZoopOutAnimation;\n      planet.messages = messages;\n\n      // Possibly non updated props\n      planet.heldArtifactIds = localPlanet.heldArtifactIds;\n    } else {\n      this.planets.set(planet.locationId, planet);\n    }\n\n    if (updatedArtifactsOnPlanet) {\n      planet.heldArtifactIds = updatedArtifactsOnPlanet;\n    }\n    // make planet Locatable if we know its location\n    const loc = this.planetLocationMap.get(planet.locationId) || revealedLocation;\n    if (loc) {\n      (planet as LocatablePlanet).location = loc;\n      (planet as LocatablePlanet).biome = this.getBiome(loc);\n    }\n    if (revealedLocation) {\n      this.markLocationRevealed(revealedLocation);\n      this.addPlanetLocation(revealedLocation);\n      planet.coordsRevealed = true;\n      planet.revealer = revealedLocation.revealer;\n    }\n\n    if (claimerEthAddress) {\n      planet.claimer = claimerEthAddress;\n    }\n\n    this.setPlanet(planet);\n\n    if (updatedArrivals) {\n      // apply arrivals\n      this.clearOldArrivals(planet);\n      const updatedAwts = this.processArrivalsForPlanet(planet.locationId, updatedArrivals);\n      for (const awt of updatedAwts) {\n        const arrivalId = awt.arrivalData.eventId;\n        this.arrivals.set(arrivalId, awt);\n        const arrivalIds = this.planetArrivalIds.get(planet.locationId);\n        if (arrivalIds) {\n          arrivalIds.push(arrivalId);\n          this.planetArrivalIds.set(planet.locationId, arrivalIds);\n        }\n      }\n    }\n  }\n\n  // returns an empty planet if planet is not in contract\n  // returns undefined if this isn't a planet, according to hash and coords\n  public getPlanetWithCoords(coords: WorldCoords): LocatablePlanet | undefined {\n    const str = getCoordsString(coords);\n\n    const location = this.coordsToLocation.get(str);\n    if (!location) {\n      return undefined;\n    }\n\n    return this.getPlanetWithLocation(location) as LocatablePlanet;\n  }\n\n  // - returns an empty planet if planet is not in contract\n  // - returns undefined if this isn't a planet, according to hash and coords\n  // - if this planet hasn't been initialized in the client yet, initializes it\n  public getPlanetWithLocation(location: WorldLocation | undefined): Planet | undefined {\n    if (!location) return undefined;\n\n    const planet = this.planets.get(location.hash);\n    if (planet) {\n      this.updatePlanetIfStale(planet);\n      return planet;\n    }\n\n    // return a default unowned planet\n    const defaultPlanet = this.defaultPlanetFromLocation(location);\n    this.setPlanet(defaultPlanet);\n\n    return defaultPlanet;\n  }\n\n  public isPlanetInContract(planetId: LocationId): boolean {\n    return this.touchedPlanetIds.has(planetId);\n  }\n\n  /**\n   * Called when we load chunk data into memory (on startup), when we're loading all revealed locations (on startup),\n   * when miner has mined a new chunk while exploring, and when a planet's location is revealed onchain during the course of play\n   * Adds a WorldLocation to the planetLocationMap, making it known to the player locally\n   * Sets an unsynced default planet in the PlanetMap this.planets\n   * IMPORTANT: This is the only way a LocatablePlanet gets constructed\n   * IMPORTANT: Idempotent\n   */\n  public addPlanetLocation(planetLocation: WorldLocation): void {\n    this.layeredMap.insertPlanet(\n      planetLocation,\n      this.getPlanetWithId(planetLocation.hash, false)?.planetLevel ??\n        this.planetLevelFromHexPerlin(planetLocation.hash, planetLocation.perlin)\n    );\n\n    this.planetLocationMap.set(planetLocation.hash, planetLocation);\n    const str = getCoordsString(planetLocation.coords);\n\n    if (!this.coordsToLocation.has(str)) {\n      this.coordsToLocation.set(str, planetLocation);\n    }\n\n    if (!this.planets.get(planetLocation.hash)) {\n      this.setPlanet(this.defaultPlanetFromLocation(planetLocation));\n    }\n\n    const planet = this.planets.get(planetLocation.hash);\n\n    if (planet) {\n      (planet as LocatablePlanet).location = planetLocation;\n      (planet as LocatablePlanet).biome = this.getBiome(planetLocation);\n    }\n  }\n\n  // marks that a location is revealed on-chain\n  public markLocationRevealed(revealedLocation: RevealedLocation): void {\n    this.revealedLocations.set(revealedLocation.hash, revealedLocation);\n  }\n\n  public getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined {\n    return this.planetLocationMap.get(planetId) || undefined;\n  }\n\n  /**\n   * Returns all planets in the game.\n   *\n   * Warning! Simply iterating over this is not performant, and is meant for scripting.\n   *\n   * @tutorial For plugin developers!\n   */\n  public getAllPlanets(): Iterable<Planet> {\n    return this.planets.values();\n  }\n\n  /**\n   * Returns all planets in the game, as a map from their location id to the planet.\n   *\n   * @tutorial For plugin developers!\n   * @see Warning in {@link GameObjects.getAllPlanets()}\n   */\n  public getAllPlanetsMap(): Map<LocationId, Planet> {\n    return this.planets;\n  }\n\n  /**\n   * Returns all the planets in the game which this client is aware of that have an owner, as a map\n   * from their id to the planet\n   *\n   * @tutorial For plugin developers!\n   * @see Warning in {@link GameObjects.getAllPlanets()}\n   */\n  public getAllOwnedPlanets(): Planet[] {\n    return Array.from(this.planets.values()).filter(hasOwner);\n  }\n\n  /**\n   * Returns all voyages that are scheduled to arrive at some point in the future.\n   *\n   * @tutorial For plugin developers!\n   * @see Warning in {@link GameObjects.getAllPlanets()}\n   */\n  public getAllVoyages(): QueuedArrival[] {\n    return Array.from(this.arrivals.values()).map((awt) => awt.arrivalData);\n  }\n\n  /**\n   * We call this function whenever the user requests that we send a transaction to the blockchain\n   * with their localstorage wallet. You can think of it as one of the hubs which connects\n   * `GameObjects` to the rest of the world.\n   *\n   * Inside this function, we update the relevant internal game objects to reflect that the user has\n   * requested a particular action. Additionally, we publish the appropriate events to the relevant\n   * {@link Monomitter} instances that are stored in this class.\n   *\n   * In the case of something like prospecting for an artifact, this allows us to display a spinner\n   * text which says \"Prospecting...\"\n   *\n   * In the case of the user sending energy from one planet to another planet, this allows us to\n   * display a dashed line between the two planets in their new voyage.\n   *\n   * Whenever we update an entity, we must do it via that entity's type's corresponding\n   * `set<EntityType>` function, in order for us to publish these events.\n   *\n   * @todo: this entire function could be automated by implementing a new interface called\n   * {@code TxFilter}.\n   */\n  public onTxIntent(tx: Transaction) {\n    this.transactions.addTransaction(tx);\n\n    if (isUnconfirmedRevealTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedMoveTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.from);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (tx.intent.artifact) {\n        const artifact = this.getArtifactById(tx.intent.artifact);\n        if (artifact) {\n          artifact.transactions?.addTransaction(tx);\n          this.setArtifact(artifact);\n        }\n      }\n    } else if (isUnconfirmedUpgradeTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedBuyHatTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedTransferTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedProspectPlanetTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedFindArtifactTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedDepositArtifactTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.addTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedWithdrawArtifactTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.addTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedActivateArtifactTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.addTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedDeactivateArtifactTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.addTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedWithdrawSilverTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedCapturePlanetTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedInvadePlanetTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.addTransaction(tx);\n        this.setPlanet(planet);\n      }\n    }\n  }\n\n  /**\n   * Whenever a transaction that the user initiated either succeeds or fails, we need to clear the\n   * fact that it was in progress from the event's corresponding entities. For example, whenever a\n   * transaction that sends a voyage from one planet to another either succeeds or fails, we need to\n   * remove the dashed line that connected them.\n   *\n   * Making sure that we never miss something here is very tedious.\n   *\n   * @todo Make this less tedious.\n   */\n  public clearUnconfirmedTxIntent(tx: Transaction) {\n    this.transactions.removeTransaction(tx);\n\n    if (isUnconfirmedReveal(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedMove(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.from);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (tx.intent.artifact) {\n        const artifact = this.getArtifactById(tx.intent.artifact);\n        if (artifact) {\n          artifact.transactions?.removeTransaction(tx);\n          this.setArtifact(artifact);\n        }\n      }\n    } else if (isUnconfirmedUpgrade(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedBuyHat(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedFindArtifact(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedDepositArtifact(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.removeTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedWithdrawArtifact(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.removeTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedTransfer(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedProspectPlanet(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.planetId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedActivateArtifact(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.removeTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedDeactivateArtifact(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      const artifact = this.getArtifactById(tx.intent.artifactId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n      if (artifact) {\n        artifact.transactions?.removeTransaction(tx);\n        this.setArtifact(artifact);\n      }\n    } else if (isUnconfirmedWithdrawSilver(tx.intent)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedCapturePlanetTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    } else if (isUnconfirmedInvadePlanetTx(tx)) {\n      const planet = this.getPlanetWithId(tx.intent.locationId);\n      if (planet) {\n        planet.transactions?.removeTransaction(tx);\n        this.setPlanet(planet);\n      }\n    }\n  }\n\n  public getPlanetMap(): Map<LocationId, Planet> {\n    return this.planets;\n  }\n\n  public getArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.artifacts;\n  }\n\n  public getMyPlanetMap(): Map<LocationId, Planet> {\n    return this.myPlanets;\n  }\n\n  public getMyArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.myArtifacts;\n  }\n\n  public getRevealedLocations(): Map<LocationId, RevealedLocation> {\n    return this.revealedLocations;\n  }\n\n  public getClaimedLocations(): Map<LocationId, ClaimedLocation> {\n    return this.claimedLocations;\n  }\n\n  public setClaimedLocation(claimedLocation: ClaimedLocation) {\n    this.claimedLocations.set(claimedLocation.hash, claimedLocation);\n  }\n\n  /**\n   * Gets all the planets with the given ids, giltering out the ones that we don't have.\n   */\n  public getPlanetsWithIds(locationIds: LocationId[], updateIfStale = true): Planet[] {\n    return locationIds\n      .map((id) => this.getPlanetWithId(id, updateIfStale))\n      .filter((p) => p !== undefined) as Planet[];\n  }\n\n  /**\n   * Gets all the planets that are within {@code radius} world units from the given coordinate. Fast\n   * because it uses {@link LayeredMap}.\n   */\n  public getPlanetsInWorldCircle(coords: WorldCoords, radius: number): LocatablePlanet[] {\n    const locationIds = this.layeredMap.getPlanetsInCircle(coords, radius);\n    return this.getPlanetsWithIds(locationIds) as LocatablePlanet[];\n  }\n\n  /**\n   * Gets the ids of all the planets that are both within the given bounding box (defined by its\n   * bottom left coordinate, width, and height) in the world and of a level that was passed in via\n   * the `planetLevels` parameter. Fast because it uses {@link LayeredMap}.\n   */\n  public getPlanetsInWorldRectangle(\n    worldX: number,\n    worldY: number,\n    worldWidth: number,\n    worldHeight: number,\n    levels: number[],\n    planetLevelToRadii: Map<number, Radii>,\n    updateIfStale = true\n  ): LocatablePlanet[] {\n    const locationIds = this.layeredMap.getPlanets(\n      worldX,\n      worldY,\n      worldWidth,\n      worldHeight,\n      levels,\n      planetLevelToRadii\n    );\n    return this.getPlanetsWithIds(locationIds, updateIfStale) as LocatablePlanet[];\n  }\n\n  public forceTick(locationId: LocationId) {\n    const planet = this.getPlanetWithId(locationId);\n    if (planet) {\n      this.setPlanet(planet);\n    }\n  }\n\n  /**\n   * Set a planet into our cached store. Should ALWAYS call this when setting a planet.\n   * `this.planets` and `this.myPlanets` should NEVER be accessed directly!\n   * This function also handles managing planet update messages and indexing the map of owned planets.\n   * @param planet the planet to set\n   */\n  private setPlanet(planet: Planet) {\n    if (isLocatable(planet)) {\n      this.layeredMap.insertPlanet(planet.location, planet.planetLevel);\n    }\n\n    setObjectSyncState<Planet, LocationId>(\n      this.planets,\n      this.myPlanets,\n      this.address,\n      this.planetUpdated$,\n      this.myPlanetsUpdated$,\n      getPlanetId,\n      getPlanetOwner,\n      planet\n    );\n  }\n\n  /**\n   * Set an artifact into our cached store. Should ALWAYS call this when setting an artifact.\n   * `this.artifacts` and `this.myArtifacts` should NEVER be accessed directly!\n   * This function also handles managing artifact update messages and indexing the map of owned artifacts.\n   * @param artifact the artifact to set\n   */\n  private setArtifact(artifact: Artifact) {\n    if (artifact.artifactType === ArtifactType.Wormhole && artifact.onPlanetId) {\n      if (artifact.wormholeTo && isActivated(artifact)) {\n        this.wormholes.set(artifact.id, {\n          from: artifact.onPlanetId,\n          to: artifact.wormholeTo,\n        });\n      } else {\n        this.wormholes.delete(artifact.id);\n      }\n    }\n\n    setObjectSyncState<Artifact, ArtifactId>(\n      this.artifacts,\n      this.myArtifacts,\n      this.address,\n      this.artifactUpdated$,\n      this.myArtifactsUpdated$,\n      getArtifactId,\n      getArtifactOwner,\n      artifact\n    );\n  }\n  /**\n   * Emit notifications based on a planet's state change\n   */\n  private emitArrivalNotifications({ previous, current, arrival }: PlanetDiff) {\n    const notifManager = NotificationManager.getInstance();\n    if (\n      !GameObjects.planetCanUpgrade(previous) &&\n      GameObjects.planetCanUpgrade(current) &&\n      current.owner === this.address\n    ) {\n      notifManager.planetCanUpgrade(current);\n    }\n    if (\n      previous.owner !== this.address &&\n      previous.owner !== ethers.constants.AddressZero &&\n      current.owner === this.address\n    ) {\n      notifManager.planetConquered(current as LocatablePlanet);\n    }\n    if (previous.owner === this.address && current.owner !== this.address) {\n      notifManager.planetLost(current as LocatablePlanet);\n    }\n    if (\n      arrival.player !== this.address &&\n      current.owner === this.address &&\n      arrival.energyArriving !== 0\n    ) {\n      notifManager.planetAttacked(current as LocatablePlanet);\n    }\n  }\n\n  private removeArrival(planetId: LocationId, arrivalId: VoyageId) {\n    this.arrivals?.delete(arrivalId);\n    const planetArrivalIds = this.planetArrivalIds?.get(planetId) ?? [];\n    _.remove(planetArrivalIds, (id) => id === arrivalId);\n  }\n\n  private processArrivalsForPlanet(\n    planetId: LocationId,\n    arrivals: QueuedArrival[]\n  ): ArrivalWithTimer[] {\n    const planet = this.planets.get(planetId);\n    if (!planet) {\n      console.error(`attempted to process arrivals for planet not in memory: ${planetId}`);\n      return [];\n    }\n    // process the QueuedArrival[] for a single planet\n    const arrivalsWithTimers: ArrivalWithTimer[] = [];\n\n    // sort arrivals by timestamp\n    arrivals.sort((a, b) => a.arrivalTime - b.arrivalTime);\n    const nowInSeconds = Date.now() / 1000;\n    for (const arrival of arrivals) {\n      try {\n        if (nowInSeconds - arrival.arrivalTime > 0) {\n          // if arrival happened in the past, run this arrival\n          const update = arrive(\n            planet,\n            this.getPlanetArtifacts(planet.locationId),\n            arrival,\n            this.getArtifactById(arrival.artifactId),\n            this.contractConstants\n          );\n\n          this.removeArrival(planetId, update.arrival.eventId);\n          this.emitArrivalNotifications(update);\n        } else {\n          // otherwise, set a timer to do this arrival in the future\n          // and append it to arrivalsWithTimers\n          const applyFutureArrival = setTimeout(() => {\n            const update = arrive(\n              planet,\n              this.getPlanetArtifacts(planet.locationId),\n              arrival,\n              this.getArtifactById(arrival.artifactId),\n              this.contractConstants\n            );\n            this.emitArrivalNotifications(update);\n            this.removeArrival(planetId, update.arrival.eventId);\n          }, arrival.arrivalTime * 1000 - Date.now());\n\n          const arrivalWithTimer = {\n            arrivalData: arrival,\n            timer: applyFutureArrival,\n          };\n          arrivalsWithTimers.push(arrivalWithTimer);\n        }\n      } catch (e) {\n        console.error(`error occurred processing arrival for updated planet ${planetId}: ${e}`);\n      }\n    }\n    return arrivalsWithTimers;\n  }\n\n  private clearOldArrivals(planet: Planet): void {\n    const planetId = planet.locationId;\n    // clear old timeouts\n    const arrivalIds = this.planetArrivalIds.get(planetId);\n    if (arrivalIds) {\n      // clear if the planet already had stored arrivals\n      for (const arrivalId of arrivalIds) {\n        const arrivalWithTimer = this.arrivals.get(arrivalId);\n        if (arrivalWithTimer) {\n          clearTimeout(arrivalWithTimer.timer);\n        } else {\n          console.error(`arrival with id ${arrivalId} wasn't found`);\n        }\n        this.arrivals.delete(arrivalId);\n      }\n    }\n    this.planetArrivalIds.set(planetId, []);\n  }\n\n  public planetLevelFromHexPerlin(hex: LocationId, perlin: number): PlanetLevel {\n    const spaceType = this.spaceTypeFromPerlin(perlin);\n\n    const levelBigInt = getBytesFromHex(hex, 4, 7);\n\n    let ret = MIN_PLANET_LEVEL;\n\n    for (let type = MAX_PLANET_LEVEL; type >= MIN_PLANET_LEVEL; type--) {\n      if (levelBigInt < bigInt(this.contractConstants.planetLevelThresholds[type])) {\n        ret = type;\n        break;\n      }\n    }\n\n    if (spaceType === SpaceType.NEBULA && ret > PlanetLevel.FOUR) {\n      ret = PlanetLevel.FOUR;\n    }\n    if (spaceType === SpaceType.SPACE && ret > PlanetLevel.FIVE) {\n      ret = PlanetLevel.FIVE;\n    }\n    if (ret > this.contractConstants.MAX_NATURAL_PLANET_LEVEL) {\n      ret = this.contractConstants.MAX_NATURAL_PLANET_LEVEL as PlanetLevel;\n    }\n\n    return ret;\n  }\n\n  public spaceTypeFromPerlin(perlin: number): SpaceType {\n    if (perlin < this.contractConstants.PERLIN_THRESHOLD_1) {\n      return SpaceType.NEBULA;\n    } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_2) {\n      return SpaceType.SPACE;\n    } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_3) {\n      return SpaceType.DEEP_SPACE;\n    } else {\n      return SpaceType.DEAD_SPACE;\n    }\n  }\n\n  public static getSilverNeeded(planet: Planet): number {\n    const totalLevel = planet.upgradeState.reduce((a, b) => a + b);\n    return (totalLevel + 1) * 0.2 * planet.silverCap;\n  }\n\n  public static planetCanUpgrade(planet: Planet): boolean {\n    const totalRank = planet.upgradeState.reduce((a, b) => a + b);\n    if (planet.spaceType === SpaceType.NEBULA && totalRank >= 3) return false;\n    if (planet.spaceType === SpaceType.SPACE && totalRank >= 4) return false;\n    if (planet.spaceType === SpaceType.DEEP_SPACE && totalRank >= 5) return false;\n    if (planet.spaceType === SpaceType.DEAD_SPACE && totalRank >= 5) return false;\n    return (\n      planet.planetLevel !== 0 &&\n      planet.planetType === PlanetType.PLANET &&\n      planet.silver >= this.getSilverNeeded(planet)\n    );\n  }\n\n  public planetTypeFromHexPerlin(hex: LocationId, perlin: number): PlanetType {\n    // level must be sufficient - too low level planets have 0 silver growth\n    const planetLevel = this.planetLevelFromHexPerlin(hex, perlin);\n\n    const spaceType = this.spaceTypeFromPerlin(perlin);\n    const weights = this.contractConstants.PLANET_TYPE_WEIGHTS[spaceType][planetLevel];\n    const weightSum = weights.reduce((x, y) => x + y);\n    let thresholds = [weightSum - weights[0]];\n    for (let i = 1; i < weights.length; i++) {\n      thresholds.push(thresholds[i - 1] - weights[i]);\n    }\n    thresholds = thresholds.map((x) => Math.floor((x * 256) / weightSum));\n    const typeByte = Number(getBytesFromHex(hex, 8, 9));\n    for (let i = 0; i < thresholds.length; i++) {\n      if (typeByte >= thresholds[i]) {\n        return i as PlanetType;\n      }\n    }\n    // this should never happen\n    return PlanetType.PLANET;\n  }\n\n  private getBiome(loc: WorldLocation): Biome {\n    const { perlin, biomebase } = loc;\n    const spaceType = this.spaceTypeFromPerlin(perlin);\n\n    if (spaceType === SpaceType.DEAD_SPACE) return Biome.CORRUPTED;\n\n    let biome = 3 * spaceType;\n    if (biomebase < this.contractConstants.BIOME_THRESHOLD_1) biome += 1;\n    else if (biomebase < this.contractConstants.BIOME_THRESHOLD_2) biome += 2;\n    else biome += 3;\n\n    return biome as Biome;\n  }\n\n  /**\n   * returns the data for an unowned, untouched planet at location\n   * most planets in the game are untouched and not stored in the contract,\n   * so we need to generate their data optimistically in the client\n   */\n  private defaultPlanetFromLocation(location: WorldLocation): LocatablePlanet {\n    const { perlin } = location;\n    const hex = location.hash;\n    const planetLevel = this.planetLevelFromHexPerlin(hex, perlin);\n    const planetType = this.planetTypeFromHexPerlin(hex, perlin);\n    const spaceType = this.spaceTypeFromPerlin(perlin);\n\n    const [energyCapBonus, energyGroBonus, rangeBonus, speedBonus, defBonus, spaceJunkBonus] =\n      bonusFromHex(hex);\n\n    let energyCap = this.contractConstants.defaultPopulationCap[planetLevel];\n    let energyGro = this.contractConstants.defaultPopulationGrowth[planetLevel];\n    let range = this.contractConstants.defaultRange[planetLevel];\n    let speed = this.contractConstants.defaultSpeed[planetLevel];\n    let defense = this.contractConstants.defaultDefense[planetLevel];\n    let silCap = this.contractConstants.defaultSilverCap[planetLevel];\n    let spaceJunk = this.contractConstants.PLANET_LEVEL_JUNK[planetLevel];\n\n    let silGro = 0;\n    if (planetType === PlanetType.SILVER_MINE) {\n      silGro = this.contractConstants.defaultSilverGrowth[planetLevel];\n    }\n\n    energyCap *= energyCapBonus ? 2 : 1;\n    energyGro *= energyGroBonus ? 2 : 1;\n    range *= rangeBonus ? 2 : 1;\n    speed *= speedBonus ? 2 : 1;\n    defense *= defBonus ? 2 : 1;\n    spaceJunk = Math.floor(spaceJunk / (spaceJunkBonus ? 2 : 1));\n\n    if (spaceType === SpaceType.DEAD_SPACE) {\n      range *= 2;\n      speed *= 2;\n      energyCap *= 2;\n      energyGro *= 2;\n      silCap *= 2;\n      silGro *= 2;\n\n      defense = Math.floor((defense * 3) / 20);\n    } else if (spaceType === SpaceType.DEEP_SPACE) {\n      range *= 1.5;\n      speed *= 1.5;\n      energyCap *= 1.5;\n      energyGro *= 1.5;\n      silCap *= 1.5;\n      silGro *= 1.5;\n\n      defense *= 0.25;\n    } else if (spaceType === SpaceType.SPACE) {\n      range *= 1.25;\n      speed *= 1.25;\n      energyCap *= 1.25;\n      energyGro *= 1.25;\n      silCap *= 1.25;\n      silGro *= 1.25;\n\n      defense *= 0.5;\n    }\n\n    // apply stat modifiers for special planet types\n    if (planetType === PlanetType.SILVER_MINE) {\n      silCap *= 2;\n      defense *= 0.5;\n    } else if (planetType === PlanetType.SILVER_BANK) {\n      speed /= 2;\n      silCap *= 10;\n      energyGro = 0;\n      energyCap *= 5;\n    } else if (planetType === PlanetType.TRADING_POST) {\n      defense *= 0.5;\n      silCap *= 2;\n    }\n\n    let pirates =\n      (energyCap * this.contractConstants.defaultBarbarianPercentage[planetLevel]) / 100;\n    // increase pirates\n    if (spaceType === SpaceType.DEAD_SPACE) pirates *= 20;\n    else if (spaceType === SpaceType.DEEP_SPACE) pirates *= 10;\n    else if (spaceType === SpaceType.SPACE) pirates *= 4;\n\n    if (planetType === PlanetType.SILVER_BANK) pirates /= 2;\n\n    const silver = planetType === PlanetType.SILVER_MINE ? silCap / 2 : 0;\n\n    speed *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100;\n    energyGro *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100;\n    silGro *= this.contractConstants.TIME_FACTOR_HUNDREDTHS / 100;\n\n    const biome = this.getBiome(location);\n\n    return {\n      locationId: hex,\n      perlin,\n      spaceType,\n      owner: EMPTY_ADDRESS,\n      hatLevel: 0,\n      bonus: bonusFromHex(hex),\n\n      planetLevel,\n      planetType,\n      isHomePlanet: false,\n\n      energyCap: energyCap,\n      energyGrowth: energyGro,\n\n      silverCap: silCap,\n      silverGrowth: silGro,\n\n      range,\n      speed,\n      defense,\n\n      energy: pirates,\n      silver,\n\n      spaceJunk,\n\n      lastUpdated: Math.floor(Date.now() / 1000),\n\n      upgradeState: [0, 0, 0],\n\n      transactions: new TxCollection(),\n      unconfirmedClearEmoji: false,\n      unconfirmedAddEmoji: false,\n      loadingServerState: false,\n      silverSpent: 0,\n\n      prospectedBlockNumber: undefined,\n      heldArtifactIds: [],\n      destroyed: false,\n      isInContract: this.touchedPlanetIds.has(hex),\n      syncedWithContract: false,\n      needsServerRefresh: false,\n      coordsRevealed: false,\n      location,\n      biome,\n      hasTriedFindingArtifact: false,\n      messages: undefined,\n      pausers: 0,\n\n      invader: EMPTY_ADDRESS,\n      capturer: EMPTY_ADDRESS,\n    };\n  }\n\n  private updatePlanetIfStale(planet: Planet): void {\n    const now = Date.now();\n    if (now / 1000 - planet.lastUpdated > 1) {\n      updatePlanetToTime(\n        planet,\n        this.getPlanetArtifacts(planet.locationId),\n        now,\n        this.contractConstants,\n        this.setPlanet\n      );\n    }\n  }\n\n  /**\n   * returns timestamp (seconds) that planet will reach percent% of energycap\n   * time may be in the past\n   */\n  public getEnergyCurveAtPercent(planet: Planet, percent: number): number {\n    const p1 = (percent / 100) * planet.energyCap;\n    const c = planet.energyCap;\n    const p0 = planet.energy;\n    const g = planet.energyGrowth;\n    const t0 = planet.lastUpdated;\n\n    const t1 = (c / (4 * g)) * Math.log((p1 * (c - p0)) / (p0 * (c - p1))) + t0;\n\n    return t1;\n  }\n\n  /**\n   * returns timestamp (seconds) that planet will reach percent% of silcap if\n   * doesn't produce silver, returns undefined if already over percent% of silcap,\n   * returns undefined\n   */\n  public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined {\n    if (planet.silverGrowth <= 0) {\n      return undefined;\n    }\n    const silverTarget = (percent / 100) * planet.silverCap;\n    const silverDiff = silverTarget - planet.silver;\n    if (silverDiff <= 0) {\n      return undefined;\n    }\n    let timeToTarget = 0;\n    timeToTarget += silverDiff / planet.silverGrowth;\n    return planet.lastUpdated + timeToTarget;\n  }\n\n  /**\n   * Returns the EthAddress of the player who can control the owner:\n   * if the artifact is on a planet, this is the owner of the planet\n   * if the artifact is on a voyage, this is the initiator of the voyage\n   * if the artifact is not on either, then it is the owner of the artifact NFT\n   */\n  public getArtifactController(artifactId: ArtifactId): EthAddress | undefined {\n    const artifact = this.getArtifactById(artifactId);\n    if (!artifact) {\n      return undefined;\n    }\n\n    if (artifact.onPlanetId) {\n      const planet = this.getPlanetWithId(artifact.onPlanetId);\n      if (!planet) {\n        return undefined;\n      }\n      return planet.owner === EMPTY_ADDRESS ? undefined : planet.owner;\n    } else if (artifact.onVoyageId) {\n      const arrival = this.arrivals.get(artifact.onVoyageId);\n      return arrival?.arrivalData.player || undefined;\n    } else {\n      return artifact.currentOwner === EMPTY_ADDRESS ? undefined : artifact.currentOwner;\n    }\n  }\n\n  /**\n   * Get all of the incoming voyages for a given location.\n   */\n  public getArrivalIdsForLocation(location: LocationId | undefined): VoyageId[] | undefined {\n    if (!location) return [];\n\n    return this.planetArrivalIds.get(location);\n  }\n\n  /**\n   * Whether or not we're already asking the game to give us spaceships.\n   */\n  public isGettingSpaceships(): boolean {\n    return this.transactions.hasTransaction(isUnconfirmedGetShipsTx);\n  }\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/GameUIManager.ts",
    "content": "import { EMPTY_ADDRESS } from '@darkforest_eth/constants';\nimport { Monomitter, monomitter } from '@darkforest_eth/events';\nimport { biomeName, isLocatable, isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { planetHasBonus } from '@darkforest_eth/hexgen';\nimport { EthConnection } from '@darkforest_eth/network';\nimport { GameGLManager, Renderer } from '@darkforest_eth/renderer';\nimport { isUnconfirmedMoveTx } from '@darkforest_eth/serde';\nimport {\n  Artifact,\n  ArtifactId,\n  BaseRenderer,\n  Biome,\n  Chunk,\n  CursorState,\n  Diagnostics,\n  EthAddress,\n  LocatablePlanet,\n  LocationId,\n  PerlinConfig,\n  Planet,\n  PlanetLevel,\n  PlanetType,\n  Player,\n  QueuedArrival,\n  Rectangle,\n  Setting,\n  SpaceType,\n  Transaction,\n  UnconfirmedActivateArtifact,\n  UnconfirmedMove,\n  UnconfirmedUpgrade,\n  Upgrade,\n  UpgradeBranchName,\n  WorldCoords,\n  WorldLocation,\n  Wormhole,\n} from '@darkforest_eth/types';\nimport autoBind from 'auto-bind';\nimport { BigNumber } from 'ethers';\nimport EventEmitter from 'events';\nimport deferred from 'p-defer';\nimport React from 'react';\nimport ModalManager from '../../Frontend/Game/ModalManager';\nimport NotificationManager from '../../Frontend/Game/NotificationManager';\nimport Viewport from '../../Frontend/Game/Viewport';\nimport { getObjectWithIdFromMap } from '../../Frontend/Utils/EmitterUtils';\nimport { listenForKeyboardEvents, unlinkKeyboardEvents } from '../../Frontend/Utils/KeyEmitters';\nimport {\n  getBooleanSetting,\n  getSetting,\n  setBooleanSetting,\n} from '../../Frontend/Utils/SettingsHooks';\nimport UIEmitter, { UIEmitterEvent } from '../../Frontend/Utils/UIEmitter';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { HashConfig } from '../../_types/global/GlobalTypes';\nimport { MiningPattern } from '../Miner/MiningPatterns';\nimport { coordsEqual } from '../Utils/Coordinates';\nimport GameManager, { GameManagerEvent } from './GameManager';\nimport { GameObjects } from './GameObjects';\nimport { PluginManager } from './PluginManager';\nimport TutorialManager, { TutorialState } from './TutorialManager';\nimport { ViewportEntities } from './ViewportEntities';\n\nexport const enum GameUIManagerEvent {\n  InitializedPlayer = 'InitializedPlayer',\n  InitializedPlayerError = 'InitializedPlayerError',\n}\n\nclass GameUIManager extends EventEmitter {\n  private readonly radiusMap: { [PlanetLevel: number]: number };\n  private readonly gameManager: GameManager;\n  private modalManager: ModalManager;\n\n  private terminal: React.MutableRefObject<TerminalHandle | undefined>;\n\n  /**\n   * In order to render React on top of the game, we need to insert React nodes into an overlay\n   * container. We keep a reference to this container, so that our React components can optionally\n   * choose to render themselves into this overlay container using React Portals.\n   */\n  private overlayContainer?: HTMLDivElement;\n  private previousSelectedPlanetId: LocationId | undefined;\n  private selectedPlanetId: LocationId | undefined;\n  private selectedCoords: WorldCoords | undefined;\n  private mouseDownOverPlanet: LocatablePlanet | undefined;\n  private mouseDownOverCoords: WorldCoords | undefined;\n  private mouseHoveringOverPlanet: LocatablePlanet | undefined;\n  private mouseHoveringOverCoords: WorldCoords | undefined;\n  private sendingPlanet: LocatablePlanet | undefined;\n  private sendingCoords: WorldCoords | undefined;\n  private isSending = false;\n  private abandoning = false;\n  private viewportEntities: ViewportEntities;\n\n  /**\n   * The Wormhole artifact requires you to choose a target planet. This value\n   * indicates whether or not the player is currently selecting a target planet.\n   */\n  private isChoosingTargetPlanet = false;\n  private onChooseTargetPlanet?: (planet: LocatablePlanet | undefined) => void;\n  // TODO: Remove later and just use minerLocations array\n  private minerLocation: WorldCoords | undefined;\n  private extraMinerLocations: WorldCoords[] = [];\n\n  private forcesSending: { [key: string]: number } = {}; // this is a percentage\n  private silverSending: { [key: string]: number } = {}; // this is a percentage\n\n  private artifactSending: { [key: string]: Artifact | undefined } = {};\n\n  private plugins: PluginManager;\n\n  public readonly selectedPlanetId$: Monomitter<LocationId | undefined>;\n  public readonly hoverPlanetId$: Monomitter<LocationId | undefined>;\n  public readonly hoverPlanet$: Monomitter<Planet | undefined>;\n  public readonly hoverArtifactId$: Monomitter<ArtifactId | undefined>;\n  public readonly hoverArtifact$: Monomitter<Artifact | undefined>;\n  public readonly myArtifacts$: Monomitter<Map<ArtifactId, Artifact>>;\n\n  public readonly isSending$: Monomitter<boolean>;\n  public readonly isAbandoning$: Monomitter<boolean>;\n\n  private planetHoveringInRenderer = false;\n\n  // lifecycle methods\n\n  private constructor(\n    gameManager: GameManager,\n    terminalHandle: React.MutableRefObject<TerminalHandle | undefined>\n  ) {\n    super();\n\n    this.gameManager = gameManager;\n    this.terminal = terminalHandle;\n\n    // if planets are more dense, make radii smaller\n    // if planets are less dense, make radii larger\n    // the default radii are tuned for a default planet rarity of 16384\n    const scaleFactor = Math.sqrt(this.gameManager.getPlanetRarity() / 16384);\n\n    // TODO: will radii this large degrade performance?\n    this.radiusMap = {\n      [PlanetLevel.ZERO]: 1 * scaleFactor,\n      [PlanetLevel.ONE]: 3 * scaleFactor,\n      [PlanetLevel.TWO]: 9 * scaleFactor,\n      [PlanetLevel.THREE]: 27 * scaleFactor,\n      [PlanetLevel.FOUR]: 81 * scaleFactor,\n      [PlanetLevel.FIVE]: 243 * scaleFactor,\n      [PlanetLevel.SIX]: 486 * scaleFactor,\n      [PlanetLevel.SEVEN]: 729 * scaleFactor,\n      [PlanetLevel.EIGHT]: 972 * scaleFactor,\n      [PlanetLevel.NINE]: 1215 * scaleFactor,\n    } as const;\n\n    this.plugins = new PluginManager(gameManager);\n\n    this.selectedPlanetId$ = monomitter<LocationId | undefined>(true);\n    this.hoverPlanetId$ = monomitter<LocationId | undefined>();\n    this.hoverPlanet$ = getObjectWithIdFromMap<Planet, LocationId>(\n      this.getPlanetMap(),\n      this.hoverPlanetId$,\n      this.gameManager.getPlanetUpdated$()\n    );\n\n    this.hoverArtifactId$ = monomitter<ArtifactId | undefined>();\n    this.hoverArtifact$ = getObjectWithIdFromMap<Artifact, ArtifactId>(\n      this.getArtifactMap(),\n      this.hoverArtifactId$,\n      this.gameManager.getArtifactUpdated$()\n    );\n    this.myArtifacts$ = this.gameManager.getMyArtifactsUpdated$();\n    this.viewportEntities = new ViewportEntities(this.gameManager, this);\n\n    this.isSending$ = monomitter(true);\n    this.isAbandoning$ = monomitter(true);\n\n    autoBind(this);\n  }\n\n  /**\n   * Sets the overlay container. See {@link GameUIManger.overlayContainer} for more information\n   * about what the overlay container is.\n   */\n  public setOverlayContainer(randomContainer?: HTMLDivElement) {\n    this.overlayContainer = randomContainer;\n  }\n\n  /**\n   * Gets the overlay container. See {@link GameUIManger.overlayContainer} for more information\n   * about what the overlay container is.\n   */\n  public getOverlayContainer(): HTMLDivElement | undefined {\n    return this.overlayContainer;\n  }\n\n  public static async create(\n    gameManager: GameManager,\n    terminalHandle: React.MutableRefObject<TerminalHandle | undefined>\n  ) {\n    listenForKeyboardEvents();\n    const uiEmitter = UIEmitter.getInstance();\n\n    const uiManager = new GameUIManager(gameManager, terminalHandle);\n    const modalManager = await ModalManager.create(gameManager.getChunkStore());\n\n    uiManager.setModalManager(modalManager);\n\n    uiEmitter.on(UIEmitterEvent.WorldMouseDown, uiManager.onMouseDown);\n    uiEmitter.on(UIEmitterEvent.WorldMouseClick, uiManager.onMouseClick);\n    uiEmitter.on(UIEmitterEvent.WorldMouseMove, uiManager.onMouseMove);\n    uiEmitter.on(UIEmitterEvent.WorldMouseUp, uiManager.onMouseUp);\n    uiEmitter.on(UIEmitterEvent.WorldMouseOut, uiManager.onMouseOut);\n\n    uiEmitter.on(UIEmitterEvent.SendInitiated, uiManager.onSendInit);\n    uiEmitter.on(UIEmitterEvent.SendCancelled, uiManager.onSendCancel);\n    uiEmitter.on(UIEmitterEvent.SendCompleted, uiManager.onSendComplete);\n\n    gameManager.on(GameManagerEvent.PlanetUpdate, uiManager.updatePlanets);\n    gameManager.on(GameManagerEvent.DiscoveredNewChunk, uiManager.onDiscoveredChunk);\n\n    return uiManager;\n  }\n\n  public destroy(): void {\n    unlinkKeyboardEvents();\n    const uiEmitter = UIEmitter.getInstance();\n\n    uiEmitter.removeListener(UIEmitterEvent.WorldMouseDown, this.onMouseDown);\n    uiEmitter.removeListener(UIEmitterEvent.WorldMouseClick, this.onMouseClick);\n    uiEmitter.removeListener(UIEmitterEvent.WorldMouseMove, this.onMouseMove);\n    uiEmitter.removeListener(UIEmitterEvent.WorldMouseUp, this.onMouseUp);\n    uiEmitter.removeListener(UIEmitterEvent.WorldMouseOut, this.onMouseOut);\n\n    uiEmitter.removeListener(UIEmitterEvent.SendInitiated, this.onSendInit);\n    uiEmitter.removeListener(UIEmitterEvent.SendCancelled, this.onSendCancel);\n    uiEmitter.removeListener(UIEmitterEvent.SendCompleted, this.onSendComplete);\n\n    this.gameManager.removeListener(GameManagerEvent.PlanetUpdate, this.updatePlanets);\n    this.gameManager.removeListener(\n      GameManagerEvent.InitializedPlayer,\n      this.onEmitInitializedPlayer\n    );\n    this.gameManager.removeListener(\n      GameManagerEvent.InitializedPlayerError,\n      this.onEmitInitializedPlayerError\n    );\n    this.gameManager.removeListener(GameManagerEvent.DiscoveredNewChunk, this.onDiscoveredChunk);\n\n    this.gameManager.destroy();\n    this.selectedPlanetId$.clear();\n    this.hoverArtifactId$.clear();\n  }\n\n  public getStringSetting(setting: Setting): string | undefined {\n    const account = this.getAccount();\n    const config = {\n      contractAddress: this.getContractAddress(),\n      account,\n    };\n\n    return account && getSetting(config, setting);\n  }\n\n  public getBooleanSetting(setting: Setting): boolean {\n    const account = this.getAccount();\n\n    if (!account) {\n      return false;\n    }\n\n    const config = { contractAddress: this.getContractAddress(), account };\n\n    return getBooleanSetting(config, setting);\n  }\n\n  public getDiagnostics(): Diagnostics {\n    return this.gameManager.getDiagnostics();\n  }\n\n  public updateDiagnostics(updateFn: (d: Diagnostics) => void) {\n    this.gameManager.updateDiagnostics(updateFn);\n  }\n\n  public getEthConnection(): EthConnection {\n    return this.gameManager.getEthConnection();\n  }\n\n  public getContractAddress(): EthAddress {\n    return this.gameManager.getContractAddress();\n  }\n\n  // actions\n\n  public centerPlanet(planet: LocatablePlanet | undefined) {\n    if (planet) {\n      Viewport.getInstance().centerPlanet(planet);\n      this.setSelectedPlanet(planet);\n    }\n  }\n\n  public centerCoords(coords: WorldCoords) {\n    const planet = this.gameManager.getPlanetWithCoords(coords);\n    if (planet && isLocatable(planet)) {\n      this.centerPlanet(planet);\n    } else {\n      Viewport.getInstance().centerCoords(coords);\n    }\n  }\n\n  public centerLocationId(planetId: LocationId) {\n    const planet = this.getPlanetWithId(planetId);\n    if (planet && isLocatable(planet)) {\n      this.centerPlanet(planet);\n    }\n  }\n\n  public joinGame(beforeRetry: (e: Error) => Promise<boolean>): Promise<void> {\n    return this.gameManager.joinGame(beforeRetry);\n  }\n\n  public addAccount(coords: WorldCoords): Promise<boolean> {\n    return this.gameManager.addAccount(coords);\n  }\n\n  public verifyTwitter(twitter: string): Promise<boolean> {\n    return this.gameManager.submitVerifyTwitter(twitter);\n  }\n\n  public disconnectTwitter(twitter: string) {\n    return this.gameManager.submitDisconnectTwitter(twitter);\n  }\n\n  public getPluginManager(): PluginManager {\n    return this.plugins;\n  }\n\n  public getPrivateKey(): string | undefined {\n    return this.gameManager.getPrivateKey();\n  }\n\n  public getMyBalance(): number {\n    return this.gameManager.getMyBalanceEth();\n  }\n\n  public getMyBalanceBn(): BigNumber {\n    return this.gameManager.getMyBalance();\n  }\n\n  public getMyBalance$(): Monomitter<BigNumber> {\n    return this.gameManager.getMyBalance$();\n  }\n\n  public findArtifact(planetId: LocationId) {\n    if (this.gameManager.isRoundOver()) {\n      alert('This round has ended, and you can no longer find artifacts!');\n      return;\n    }\n    this.gameManager.findArtifact(planetId);\n  }\n\n  public prospectPlanet(planetId: LocationId) {\n    if (this.gameManager.isRoundOver()) {\n      alert('This round has ended, and you can no longer find artifacts!');\n      return;\n    }\n    this.gameManager.prospectPlanet(planetId);\n  }\n\n  public withdrawArtifact(locationId: LocationId, artifactId: ArtifactId) {\n    this.gameManager.withdrawArtifact(locationId, artifactId);\n  }\n\n  public depositArtifact(locationId: LocationId, artifactId: ArtifactId) {\n    this.gameManager.depositArtifact(locationId, artifactId);\n  }\n\n  public drawAllRunningPlugins(ctx: CanvasRenderingContext2D) {\n    this.getPluginManager().drawAllRunningPlugins(ctx);\n  }\n\n  public activateArtifact(locationId: LocationId, id: ArtifactId, wormholeTo?: LocationId) {\n    const confirmationText =\n      `Are you sure you want to activate this artifact? ` +\n      `You can only have one artifact active at time. After` +\n      ` deactivation, you must wait for a long cooldown` +\n      ` before you can activate it again. Some artifacts (bloom filter, black domain, photoid cannon) are consumed on usage.`;\n\n    if (!confirm(confirmationText)) return;\n\n    this.gameManager.activateArtifact(locationId, id, wormholeTo);\n  }\n\n  public deactivateArtifact(locationId: LocationId, artifactId: ArtifactId) {\n    const confirmationText =\n      `Are you sure you want to deactivate this artifact? ` +\n      `After deactivation, you must wait for a long cooldown` +\n      ` before you can activate it again. Some artifacts (planetary shields) are consumed on deactivation.`;\n\n    if (!confirm(confirmationText)) return;\n\n    this.gameManager.deactivateArtifact(locationId, artifactId);\n  }\n\n  public withdrawSilver(locationId: LocationId, amount: number) {\n    const dontShowWarningStorageKey = `${this.getAccount()?.toLowerCase()}-withdrawnWarningAcked`;\n\n    if (localStorage.getItem(dontShowWarningStorageKey) !== 'true') {\n      localStorage.setItem(dontShowWarningStorageKey, 'true');\n      const confirmationText =\n        `Are you sure you want withdraw this silver? Once you withdraw it, you ` +\n        `cannot deposit it again. Your withdrawn silver amount will be added to your score. You'll only see this warning once!`;\n      if (!confirm(confirmationText)) return;\n    }\n\n    this.gameManager.withdrawSilver(locationId, amount);\n  }\n\n  public startWormholeFrom(planet: LocatablePlanet): Promise<LocatablePlanet | undefined> {\n    this.isChoosingTargetPlanet = true;\n    this.mouseDownOverCoords = planet.location.coords;\n    this.mouseDownOverPlanet = planet;\n\n    const { resolve, promise } = deferred<LocatablePlanet | undefined>();\n\n    this.onChooseTargetPlanet = resolve;\n\n    return promise;\n  }\n\n  public revealLocation(locationId: LocationId) {\n    this.gameManager.revealLocation(locationId);\n  }\n\n  public getNextBroadcastAvailableTimestamp() {\n    return this.gameManager.getNextBroadcastAvailableTimestamp();\n  }\n\n  public timeUntilNextBroadcastAvailable() {\n    return this.gameManager.timeUntilNextBroadcastAvailable();\n  }\n\n  public getEnergyArrivingForMove(\n    from: LocationId,\n    to: LocationId | undefined,\n    dist: number | undefined,\n    energy: number\n  ) {\n    return this.gameManager.getEnergyArrivingForMove(from, to, dist, energy, this.abandoning);\n  }\n\n  getIsChoosingTargetPlanet() {\n    return this.isChoosingTargetPlanet;\n  }\n\n  public onMouseDown(coords: WorldCoords) {\n    if (this.sendingPlanet) return;\n\n    const hoveringOverCoords = this.updateMouseHoveringOverCoords(coords);\n    const hoveringOverPlanet = this.gameManager.getPlanetWithCoords(hoveringOverCoords);\n\n    if (this.getIsChoosingTargetPlanet()) {\n      this.isChoosingTargetPlanet = false;\n      if (this.onChooseTargetPlanet) {\n        this.onChooseTargetPlanet(hoveringOverPlanet as LocatablePlanet);\n        this.mouseDownOverPlanet = undefined;\n        this.mouseDownOverCoords = undefined;\n      }\n    } else {\n      this.mouseDownOverPlanet = hoveringOverPlanet;\n      this.mouseDownOverCoords = this.mouseHoveringOverCoords;\n    }\n  }\n\n  public onMouseClick(_coords: WorldCoords) {\n    if (!this.mouseDownOverPlanet && !this.mouseHoveringOverPlanet) {\n      this.setSelectedPlanet(undefined);\n      this.selectedCoords = undefined;\n    }\n  }\n\n  public onMouseMove(coords: WorldCoords) {\n    this.updateMouseHoveringOverCoords(coords);\n  }\n\n  public onMouseUp(coords: WorldCoords) {\n    const mouseUpOverCoords = this.updateMouseHoveringOverCoords(coords);\n    const mouseUpOverPlanet = this.gameManager.getPlanetWithCoords(mouseUpOverCoords);\n\n    const mouseDownPlanet = this.getMouseDownPlanet();\n    const tutorialManager = TutorialManager.getInstance(this);\n\n    const uiEmitter = UIEmitter.getInstance();\n\n    if (mouseUpOverPlanet) {\n      if (\n        this.mouseDownOverPlanet &&\n        mouseUpOverPlanet.locationId === this.mouseDownOverPlanet.locationId\n      ) {\n        // select planet\n        this.setSelectedPlanet(mouseUpOverPlanet);\n        this.selectedCoords = mouseUpOverCoords;\n        this.terminal.current?.println(`Selected: ${mouseUpOverPlanet.locationId}`);\n        this.terminal.current?.println(``);\n      } else if (\n        mouseDownPlanet &&\n        (mouseDownPlanet.owner === this.gameManager.getAccount() ||\n          this.isSendingShip(mouseDownPlanet.locationId))\n      ) {\n        // move initiated if enough forces\n        const from = mouseDownPlanet;\n        const to = mouseUpOverPlanet;\n\n        // TODO: the following code block needs to be in a Planet class\n        let effectiveEnergy = from.energy;\n        for (const unconfirmedMove of from.transactions?.getTransactions(isUnconfirmedMoveTx) ??\n          []) {\n          effectiveEnergy -= unconfirmedMove.intent.forces;\n        }\n        const effPercent = Math.min(this.getForcesSending(from.locationId), 98);\n        let forces = Math.floor((effectiveEnergy * effPercent) / 100);\n\n        // make it so you leave one force behind\n        if (this.isSendingShip(mouseDownPlanet.locationId)) {\n          tutorialManager.acceptInput(TutorialState.Spaceship);\n          forces = 0;\n        } else if (forces >= from.energy) {\n          forces = from.energy - 1;\n          if (forces < 1) return;\n        }\n\n        const dist = this.gameManager.getDist(from.locationId, to.locationId);\n\n        const myAtk: number = this.gameManager.getEnergyArrivingForMove(\n          from.locationId,\n          to.locationId,\n          dist,\n          forces,\n          this.abandoning\n        );\n\n        let effPercentSilver = this.getSilverSending(from.locationId);\n\n        if (\n          effPercentSilver > 98 &&\n          from.planetType === PlanetType.SILVER_MINE &&\n          from.silver < from.silverCap\n        ) {\n          // player is trying to send 100% silver from a silver mine that is not at cap\n          // Date.now() and block.timestamp are occasionally a bit out of sync, so clip\n          effPercentSilver = 98;\n        }\n\n        if (myAtk > 0 || this.isSendingShip(from.locationId)) {\n          const abandoning = this.isAbandoning();\n          const silver = Math.floor((from.silver * effPercentSilver) / 100);\n          // TODO: do something like JSON.stringify(args) so we know formatting is correct\n          this.terminal.current?.printShellLn(\n            `df.move('${from.locationId}', '${to.locationId}', ${forces}, ${silver})`\n          );\n          const artifact = this.getArtifactSending(from.locationId);\n\n          this.gameManager.move(\n            from.locationId,\n            to.locationId,\n            forces,\n            silver,\n            artifact?.id,\n            abandoning\n          );\n          tutorialManager.acceptInput(TutorialState.SendFleet);\n        }\n\n        uiEmitter.emit(UIEmitterEvent.SendCompleted, from.locationId);\n      }\n\n      this.isChoosingTargetPlanet = false;\n    } else {\n      uiEmitter.emit(UIEmitterEvent.SendCancelled);\n    }\n\n    this.mouseDownOverPlanet = undefined;\n    this.mouseDownOverCoords = undefined;\n  }\n\n  public onMouseOut() {\n    this.mouseDownOverPlanet = undefined;\n    this.mouseDownOverCoords = undefined;\n    this.setHoveringOverPlanet(undefined, true);\n    this.mouseHoveringOverCoords = undefined;\n  }\n\n  public startExplore() {\n    this.gameManager.startExplore();\n  }\n\n  public stopExplore() {\n    this.gameManager.stopExplore();\n    this.minerLocation = undefined;\n  }\n\n  public toggleExplore() {\n    if (this.isMining()) {\n      this?.stopExplore();\n      TutorialManager.getInstance(this).acceptInput(TutorialState.MinerPause);\n    } else {\n      this?.startExplore();\n    }\n  }\n\n  public toggleTargettingExplorer() {\n    const modalManager = this.modalManager;\n    if (modalManager.getCursorState() === CursorState.TargetingExplorer)\n      modalManager.setCursorState(CursorState.Normal);\n    else modalManager.setCursorState(CursorState.TargetingExplorer);\n  }\n\n  public setForcesSending(planetId: LocationId, percentage: number) {\n    if (percentage < 0) percentage = 0;\n    if (percentage > 100) percentage = 100;\n    this.forcesSending[planetId] = percentage;\n    this.gameManager.getGameObjects().forceTick(planetId);\n  }\n\n  public setSilverSending(planetId: LocationId, percentage: number) {\n    if (percentage < 0) percentage = 0;\n    if (percentage > 100) percentage = 100;\n    this.silverSending[planetId] = percentage;\n    this.gameManager.getGameObjects().forceTick(planetId);\n  }\n\n  public setSending(sending: boolean): void {\n    this.isSending = sending;\n    this.isSending$.publish(sending);\n  }\n\n  public setAbandoning(abandoning: boolean): void {\n    if (!this.gameManager.getContractConstants().SPACE_JUNK_ENABLED) return;\n\n    const planet = this.getSelectedPlanet();\n    if (planet?.isHomePlanet) return;\n    if (this.isSendingShip(planet?.locationId)) return;\n\n    // An abandon is always a send\n    this.isSending = abandoning;\n    this.abandoning = abandoning;\n    this.isSending$.publish(abandoning);\n    this.isAbandoning$.publish(abandoning);\n  }\n\n  public setArtifactSending(planetId: LocationId, artifact?: Artifact) {\n    this.artifactSending[planetId] = artifact;\n    if (this.isSendingShip(planetId)) {\n      this.abandoning = false;\n      this.isAbandoning$.publish(false);\n    }\n    this.gameManager.getGameObjects().forceTick(planetId);\n  }\n\n  public isOwnedByMe(planet: Planet): boolean {\n    return planet.owner === this.gameManager.getAccount();\n  }\n\n  public addNewChunk(chunk: Chunk) {\n    this.gameManager.addNewChunk(chunk);\n  }\n\n  public bulkAddNewChunks(chunks: Chunk[]): Promise<void> {\n    return this.gameManager.bulkAddNewChunks(chunks);\n  }\n\n  // mining stuff\n  public setMiningPattern(pattern: MiningPattern) {\n    this.gameManager.setMiningPattern(pattern);\n  }\n\n  public getMiningPattern(): MiningPattern | undefined {\n    return this.gameManager.getMiningPattern();\n  }\n\n  public isMining(): boolean {\n    return this.gameManager.isMining();\n  }\n\n  // getters\n\n  public getAccount(): EthAddress | undefined {\n    return this.gameManager.getAccount();\n  }\n\n  public isAdmin(): boolean {\n    return this.gameManager.isAdmin();\n  }\n\n  public getTwitter(address: EthAddress | undefined): string | undefined {\n    return this.gameManager.getTwitter(address);\n  }\n\n  public getEndTimeSeconds(): number {\n    return this.gameManager.getEndTimeSeconds();\n  }\n\n  public isRoundOver(): boolean {\n    return this.gameManager.isRoundOver();\n  }\n\n  public getUpgrade(branch: UpgradeBranchName, level: number): Upgrade {\n    return this.gameManager.getUpgrade(branch, level);\n  }\n\n  private getBiomeKey(biome: Biome) {\n    return `${this.getAccount()}-${this.gameManager.getContractAddress()}-biome-${biome}`;\n  }\n\n  public getDiscoverBiomeName(biome: Biome): string {\n    const item = localStorage.getItem(this.getBiomeKey(biome));\n    if (item === 'true') {\n      return biomeName(biome);\n    }\n    return 'Undiscovered';\n  }\n\n  public getDistCoords(from: WorldCoords, to: WorldCoords) {\n    return this.gameManager.getDistCoords(from, to);\n  }\n\n  public discoverBiome(planet: LocatablePlanet): void {\n    const key = this.getBiomeKey(planet.biome);\n    const item = localStorage.getItem(key);\n    if (item !== 'true') {\n      const notifManager = NotificationManager.getInstance();\n      localStorage.setItem(key, 'true');\n      notifManager.foundBiome(planet);\n    }\n  }\n\n  public getAllPlayers(): Player[] {\n    return this.gameManager.getAllPlayers();\n  }\n\n  public getSelectedPlanet(): LocatablePlanet | undefined {\n    const planet = this.getPlanetWithId(this.selectedPlanetId);\n\n    if (isLocatable(planet)) {\n      return planet;\n    }\n\n    return undefined;\n  }\n\n  public getPreviousSelectedPlanet(): Planet | undefined {\n    return this.getPlanetWithId(this.previousSelectedPlanetId);\n  }\n\n  public setSelectedId(id: LocationId): void {\n    const planet = this.getPlanetWithId(id);\n    if (planet && isLocatable(planet)) this.setSelectedPlanet(planet);\n  }\n\n  public setSelectedPlanet(planet: LocatablePlanet | undefined): void {\n    this.previousSelectedPlanetId = this.selectedPlanetId;\n\n    if (!planet) {\n      const tutorialManager = TutorialManager.getInstance(this);\n      tutorialManager.acceptInput(TutorialState.Deselect);\n    }\n\n    const uiEmitter = UIEmitter.getInstance();\n    this.selectedPlanetId = planet?.locationId;\n    if (!planet) {\n      this.selectedCoords = undefined;\n    } else {\n      const loc = this.getLocationOfPlanet(planet.locationId);\n      if (!loc) this.selectedCoords = undefined;\n      else {\n        // loc is not undefined\n        this.selectedCoords = loc.coords;\n\n        if (coordsEqual(loc.coords, this.getHomeCoords())) {\n          const tutorialManager = TutorialManager.getInstance(this);\n          tutorialManager.acceptInput(TutorialState.HomePlanet);\n        }\n      }\n    }\n    uiEmitter.emit(UIEmitterEvent.GamePlanetSelected);\n\n    this.selectedPlanetId$.publish(planet?.locationId);\n  }\n\n  public getSelectedCoords(): WorldCoords | undefined {\n    return this.selectedCoords;\n  }\n\n  public getMouseDownPlanet(): LocatablePlanet | undefined {\n    if (this.isSending && this.sendingPlanet) return this.sendingPlanet;\n    return this.mouseDownOverPlanet;\n  }\n\n  public onSendInit(planet: LocatablePlanet | undefined): void {\n    this.modalManager.setCursorState(CursorState.TargetingForces);\n    this.isSending = true;\n    this.sendingPlanet = planet;\n    const loc = planet && this.getLocationOfPlanet(planet.locationId);\n    this.sendingCoords = loc ? loc.coords : { x: 0, y: 0 };\n  }\n\n  public onSendComplete(locationId: LocationId): void {\n    this.modalManager.setCursorState(CursorState.Normal);\n\n    // Set to undefined after SendComplete so it can send another one\n    this.artifactSending[locationId] = undefined;\n\n    this.sendingPlanet = undefined;\n    // Done at the end so they clear the artifact\n    this.setSending(false);\n    this.setAbandoning(false);\n  }\n\n  public onSendCancel(): void {\n    this.modalManager.setCursorState(CursorState.Normal);\n\n    this.sendingPlanet = undefined;\n    this.sendingCoords = undefined;\n\n    this.setSending(false);\n    this.setAbandoning(false);\n  }\n\n  public hasMinedChunk(chunkLocation: Rectangle): boolean {\n    return this.gameManager.hasMinedChunk(chunkLocation);\n  }\n\n  public getChunk(chunkFootprint: Rectangle): Chunk | undefined {\n    return this.gameManager.getChunk(chunkFootprint);\n  }\n\n  public spaceTypeFromPerlin(perlin: number): SpaceType {\n    return this.gameManager.spaceTypeFromPerlin(perlin);\n  }\n\n  public getSpaceTypePerlin(coords: WorldCoords, floor: boolean): number {\n    return this.gameManager.spaceTypePerlin(coords, floor);\n  }\n\n  public getBiomePerlin(coords: WorldCoords, floor: boolean): number {\n    return this.gameManager.biomebasePerlin(coords, floor);\n  }\n\n  public onDiscoveredChunk(chunk: Chunk): void {\n    const res = this.gameManager.getCurrentlyExploringChunk();\n    const account = this.getAccount();\n    const config = {\n      contractAddress: this.getContractAddress(),\n      account,\n    };\n\n    if (res) {\n      const { bottomLeft, sideLength } = res;\n      this.minerLocation = {\n        x: bottomLeft.x + sideLength / 2,\n        y: bottomLeft.y + sideLength / 2,\n      };\n    } else {\n      this.minerLocation = undefined;\n    }\n\n    const notifManager = NotificationManager.getInstance();\n\n    for (const loc of chunk.planetLocations) {\n      const planet = this.getPlanetWithId(loc.hash);\n      if (!planet || !account) break;\n\n      if (planet.owner === EMPTY_ADDRESS && planet.energy > 0) {\n        if (\n          !this.getBooleanSetting(Setting.FoundPirates) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundPirates(planet);\n          setBooleanSetting(config, Setting.FoundPirates, true);\n        }\n      }\n\n      if (planet.planetType === PlanetType.SILVER_MINE) {\n        if (\n          !this.getBooleanSetting(Setting.FoundSilver) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundSilver(planet);\n          setBooleanSetting(config, Setting.FoundSilver, true);\n        }\n      }\n      if (planet.planetType === PlanetType.SILVER_BANK) {\n        if (\n          !this.getBooleanSetting(Setting.FoundSilverBank) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundSilverBank(planet);\n          setBooleanSetting(config, Setting.FoundSilverBank, true);\n        }\n      }\n      if (planet.planetType === PlanetType.TRADING_POST) {\n        if (\n          !this.getBooleanSetting(Setting.FoundTradingPost) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundTradingPost(planet);\n          setBooleanSetting(config, Setting.FoundTradingPost, true);\n        }\n      }\n      if (planetHasBonus(planet)) {\n        if (\n          !this.getBooleanSetting(Setting.FoundComet) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundComet(planet);\n          setBooleanSetting(config, Setting.FoundComet, true);\n        }\n      }\n      if (isLocatable(planet) && planet.planetType === PlanetType.PLANET) {\n        this.discoverBiome(planet);\n      }\n      if (isLocatable(planet) && planet.planetType === PlanetType.RUINS) {\n        if (\n          !this.getBooleanSetting(Setting.FoundArtifact) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundFoundry(planet);\n          setBooleanSetting(config, Setting.FoundArtifact, true);\n        }\n      }\n    }\n\n    if (account !== undefined) {\n      if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.DEEP_SPACE) {\n        if (\n          !this.getBooleanSetting(Setting.FoundDeepSpace) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundDeepSpace(chunk);\n          setBooleanSetting(config, Setting.FoundDeepSpace, true);\n        }\n      } else if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.SPACE) {\n        if (\n          !this.getBooleanSetting(Setting.FoundSpace) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundSpace(chunk);\n          setBooleanSetting(config, Setting.FoundSpace, true);\n        }\n      } else if (this.spaceTypeFromPerlin(chunk.perlin) === SpaceType.DEAD_SPACE) {\n        if (\n          !this.getBooleanSetting(Setting.FoundDeepSpace) &&\n          this.getBooleanSetting(Setting.TutorialCompleted)\n        ) {\n          notifManager.foundDeadSpace(chunk);\n          setBooleanSetting(config, Setting.FoundDeepSpace, true);\n        }\n      }\n    }\n  }\n\n  public getMinerLocation(): WorldCoords | undefined {\n    return this.minerLocation;\n  }\n\n  public setExtraMinerLocation(idx: number, coords: WorldCoords): void {\n    this.extraMinerLocations[idx] = coords;\n  }\n\n  public removeExtraMinerLocation(idx: number): void {\n    this.extraMinerLocations.splice(idx, 1);\n  }\n\n  public getAllMinerLocations(): WorldCoords[] {\n    if (this.minerLocation) {\n      return [this.minerLocation, ...this.extraMinerLocations];\n    } else {\n      return this.extraMinerLocations;\n    }\n  }\n\n  public getMouseDownCoords(): WorldCoords | undefined {\n    if (this.isSending && this.sendingPlanet) return this.sendingCoords;\n    return this.mouseDownOverCoords;\n  }\n\n  public setHoveringOverPlanet(planet: LocatablePlanet | undefined, inRenderer: boolean) {\n    const lastHover = this.mouseHoveringOverPlanet;\n\n    this.mouseHoveringOverPlanet = planet;\n\n    if (lastHover?.locationId !== planet?.locationId) {\n      this.hoverPlanetId$.publish(planet?.locationId);\n      this.planetHoveringInRenderer = inRenderer;\n    }\n  }\n\n  public setHoveringOverArtifact(artifactId?: ArtifactId) {\n    this.hoverArtifactId$.publish(artifactId);\n    this.hoverArtifact$.publish(artifactId ? this.getArtifactWithId(artifactId) : undefined);\n  }\n\n  public getHoveringOverPlanet(): Planet | undefined {\n    return this.mouseHoveringOverPlanet;\n  }\n\n  public getHoveringOverCoords(): WorldCoords | undefined {\n    return this.mouseHoveringOverCoords;\n  }\n\n  public isSendingForces(): boolean {\n    return this.isSending;\n  }\n\n  /**\n   * Percent from 0 to 100.\n   */\n  public getForcesSending(planetId?: LocationId): number {\n    const defaultSending = 50;\n    if (!planetId) return defaultSending;\n\n    if (this.isAbandoning()) return 100;\n    if (this.isSendingShip(planetId)) return 0;\n\n    const forces = this.forcesSending[planetId];\n    return forces ?? defaultSending;\n  }\n\n  /**\n   * Percent from 0 to 100.\n   */\n  public getSilverSending(planetId?: LocationId): number {\n    const defaultSending = 0;\n    if (!planetId) return defaultSending;\n\n    if (this.isAbandoning()) return 100;\n    if (this.isSendingShip(planetId)) return 0;\n\n    return this.silverSending[planetId] ?? defaultSending;\n  }\n\n  public isAbandoning(): boolean {\n    return this.abandoning;\n  }\n\n  public getArtifactSending(planetId?: LocationId): Artifact | undefined {\n    if (!planetId) return undefined;\n    return this.artifactSending[planetId];\n  }\n\n  public getAbandonSpeedChangePercent(): number {\n    const { SPACE_JUNK_ENABLED, ABANDON_SPEED_CHANGE_PERCENT } = this.contractConstants;\n    if (SPACE_JUNK_ENABLED) {\n      return ABANDON_SPEED_CHANGE_PERCENT;\n    } else {\n      return 100;\n    }\n  }\n\n  public getAbandonRangeChangePercent(): number {\n    const { SPACE_JUNK_ENABLED, ABANDON_RANGE_CHANGE_PERCENT } = this.contractConstants;\n    if (SPACE_JUNK_ENABLED) {\n      return ABANDON_RANGE_CHANGE_PERCENT;\n    } else {\n      return 100;\n    }\n  }\n\n  public isSendingShip(planetId?: LocationId): boolean {\n    if (!planetId) return false;\n    return isSpaceShip(this.artifactSending[planetId]?.artifactType);\n  }\n\n  public isOverOwnPlanet(coords: WorldCoords): Planet | undefined {\n    const res = this.viewportEntities.getNearestVisiblePlanet(coords);\n    const planet: LocatablePlanet | undefined = res;\n    if (!planet) {\n      return undefined;\n    }\n    return planet.owner === this.gameManager.getAccount() ? planet : undefined;\n  }\n\n  public getMyArtifacts(): Artifact[] {\n    return this.gameManager.getMyArtifacts();\n  }\n\n  public getMyArtifactsNotOnPlanet(): Artifact[] {\n    return this.getMyArtifacts().filter((a) => !a.onPlanetId);\n  }\n\n  public getPlanetWithId(planetId: LocationId | undefined): Planet | undefined {\n    return this.gameManager.getPlanetWithId(planetId);\n  }\n\n  public getMyScore(): number | undefined {\n    return this.gameManager.getMyScore();\n  }\n\n  public getPlayer(address?: EthAddress): Player | undefined {\n    return this.gameManager.getPlayer(address);\n  }\n\n  public getArtifactWithId(artifactId: ArtifactId | undefined): Artifact | undefined {\n    return this.gameManager.getArtifactWithId(artifactId);\n  }\n\n  public getPlanetWithCoords(coords: WorldCoords | undefined): Planet | undefined {\n    return coords && this.gameManager.getPlanetWithCoords(coords);\n  }\n\n  public getArtifactsWithIds(artifactIds?: ArtifactId[]): Array<Artifact | undefined> {\n    return this.gameManager.getArtifactsWithIds(artifactIds);\n  }\n\n  public getArtifactPlanet(artifact: Artifact): Planet | undefined {\n    if (!artifact.onPlanetId) return undefined;\n    return this.getPlanetWithId(artifact.onPlanetId);\n  }\n\n  public getPlanetLevel(planetId: LocationId): PlanetLevel | undefined {\n    return this.gameManager.getPlanetLevel(planetId);\n  }\n\n  public getAllOwnedPlanets(): Planet[] {\n    return this.gameManager.getAllOwnedPlanets();\n  }\n\n  public getAllVoyages(): QueuedArrival[] {\n    return this.gameManager.getAllVoyages();\n  }\n\n  public getSpeedBuff(): number {\n    return this.gameManager.getSpeedBuff(this.abandoning);\n  }\n\n  public getRangeBuff(): number {\n    return this.gameManager.getRangeBuff(this.abandoning);\n  }\n\n  /**\n   * @todo delete this. now that {@link GameObjects} is publically accessible, we shouldn't need to\n   * drill fields like this anymore.\n   * @tutorial Plugin developers, please access fields like this with something like {@code df.getGameObjects().}\n   * @deprecated\n   */\n  public getUnconfirmedMoves(): Transaction<UnconfirmedMove>[] {\n    return this.gameManager.getUnconfirmedMoves();\n  }\n\n  public getUnconfirmedUpgrades(): Transaction<UnconfirmedUpgrade>[] {\n    return this.gameManager.getUnconfirmedUpgrades();\n  }\n\n  public isCurrentlyRevealing(): boolean {\n    return this.gameManager.getNextRevealCountdownInfo().currentlyRevealing;\n  }\n\n  public getUnconfirmedWormholeActivations(): Transaction<UnconfirmedActivateArtifact>[] {\n    return this.gameManager.getUnconfirmedWormholeActivations();\n  }\n\n  public getWormholes(): Iterable<Wormhole> {\n    return this.gameManager.getWormholes();\n  }\n\n  public getLocationOfPlanet(planetId: LocationId): WorldLocation | undefined {\n    return this.gameManager.getLocationOfPlanet(planetId);\n  }\n\n  public getExploredChunks(): Iterable<Chunk> {\n    return this.gameManager.getExploredChunks();\n  }\n\n  public getLocationsAndChunks() {\n    return this.viewportEntities.getPlanetsAndChunks();\n  }\n\n  public getCaptureZones() {\n    return this.gameManager.getCaptureZones();\n  }\n\n  public getCaptureZoneGenerator() {\n    return this.gameManager.getCaptureZoneGenerator();\n  }\n\n  public getIsHighPerfMode(): boolean {\n    const account = this.getAccount();\n\n    if (account === undefined) {\n      return false;\n    }\n\n    return this.getBooleanSetting(Setting.HighPerformanceRendering);\n  }\n\n  public getPlanetsInViewport(): Planet[] {\n    return Array.from(this.viewportEntities.getPlanetsAndChunks().cachedPlanets.values()).map(\n      (p) => p.planet\n    );\n  }\n\n  public getWorldRadius(): number {\n    return this.gameManager.getWorldRadius();\n  }\n\n  public getWorldSilver(): number {\n    return this.gameManager.getWorldSilver();\n  }\n\n  public getUniverseTotalEnergy(): number {\n    return this.gameManager.getUniverseTotalEnergy();\n  }\n\n  public getSilverOfPlayer(player: EthAddress): number {\n    return this.gameManager.getSilverOfPlayer(player);\n  }\n\n  public getEnergyOfPlayer(player: EthAddress): number {\n    return this.gameManager.getEnergyOfPlayer(player);\n  }\n\n  public getPlayerScore(player: EthAddress): number | undefined {\n    return this.gameManager.getPlayerScore(player);\n  }\n\n  public upgrade(planet: Planet, branch: number): void {\n    // TODO: do something like JSON.stringify(args) so we know formatting is correct\n    this.terminal.current?.printShellLn(`df.upgrade('${planet.locationId}', ${branch})`);\n    this.gameManager.upgrade(planet.locationId, branch);\n  }\n\n  public buyHat(planet: Planet): void {\n    // TODO: do something like JSON.stringify(args) so we know formatting is correct\n    this.terminal.current?.printShellLn(`df.buyHat('${planet.locationId}')`);\n    this.gameManager.buyHat(planet.locationId);\n  }\n\n  // non-nullable\n  public getHomeCoords(): WorldCoords {\n    return this.gameManager.getHomeCoords() || { x: 0, y: 0 };\n  }\n\n  public getHomeHash(): LocationId | undefined {\n    return this.gameManager.getHomeHash();\n  }\n\n  public getHomePlanet(): Planet | undefined {\n    const homeHash = this.getHomeHash();\n    if (!homeHash) return undefined;\n    return this.getPlanetWithId(homeHash);\n  }\n\n  public getRadiusOfPlanetLevel(planetRarity: PlanetLevel): number {\n    return this.radiusMap[planetRarity];\n  }\n\n  public getEnergyCurveAtPercent(planet: Planet, percent: number): number {\n    return this.gameManager.getEnergyCurveAtPercent(planet, percent);\n  }\n\n  public getSilverCurveAtPercent(planet: Planet, percent: number): number | undefined {\n    return this.gameManager.getSilverCurveAtPercent(planet, percent);\n  }\n\n  public getHashesPerSec(): number {\n    return this.gameManager.getHashesPerSec();\n  }\n\n  public generateVerificationTweet(twitter: string): Promise<string> {\n    return this.gameManager.getSignedTwitter(twitter);\n  }\n\n  public getPerlinThresholds(): [number, number, number] {\n    return this.gameManager.getPerlinThresholds();\n  }\n\n  public getHashConfig(): HashConfig {\n    return this.gameManager.getHashConfig();\n  }\n\n  public getViewport(): Viewport {\n    return Viewport.getInstance();\n  }\n\n  public getPlanetMap(): Map<LocationId, Planet> {\n    return this.gameManager.getPlanetMap();\n  }\n\n  public getArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.gameManager.getArtifactMap();\n  }\n\n  public getMyPlanetMap(): Map<LocationId, Planet> {\n    return this.gameManager.getMyPlanetMap();\n  }\n\n  public getMyArtifactMap(): Map<ArtifactId, Artifact> {\n    return this.gameManager.getMyArtifactMap();\n  }\n\n  public getTerminal(): TerminalHandle | undefined {\n    return this.terminal.current;\n  }\n\n  public get contractConstants(): ContractConstants {\n    return this.gameManager.getContractConstants();\n  }\n\n  public getSpaceJunkEnabled(): boolean {\n    return this.contractConstants.SPACE_JUNK_ENABLED;\n  }\n\n  public get captureZonesEnabled(): boolean {\n    return this.contractConstants.CAPTURE_ZONES_ENABLED;\n  }\n\n  public potentialCaptureScore(planetLevel: number): number {\n    return this.contractConstants.CAPTURE_ZONE_PLANET_LEVEL_SCORE[planetLevel];\n  }\n\n  public getDefaultSpaceJunkForPlanetLevel(level: number): number {\n    return this.contractConstants.PLANET_LEVEL_JUNK[level];\n  }\n\n  public getPerlinConfig(isBiome = false): PerlinConfig {\n    const hashConfig = this.gameManager.getHashConfig();\n    const key = isBiome ? hashConfig.biomebaseKey : hashConfig.spaceTypeKey;\n\n    return {\n      key,\n      scale: hashConfig.perlinLengthScale,\n      mirrorX: hashConfig.perlinMirrorX,\n      mirrorY: hashConfig.perlinMirrorY,\n      floor: false,\n    };\n  }\n\n  /**\n   * Gets a reference to the game's internal representation of the world state. Beware! Use this for\n   * reading only, otherwise you might mess up the state of the game. You can try modifying the game\n   * state in some way\n   */\n  public getGameObjects(): GameObjects {\n    return this.gameManager.getGameObjects();\n  }\n\n  // internal utils\n\n  private updatePlanets() {\n    if (this.mouseDownOverPlanet) {\n      this.mouseDownOverPlanet = this.gameManager.getPlanetWithId(\n        this.mouseDownOverPlanet.locationId\n      ) as LocatablePlanet;\n    }\n    if (this.mouseHoveringOverPlanet) {\n      this.setHoveringOverPlanet(\n        this.gameManager.getPlanetWithId(\n          this.mouseHoveringOverPlanet.locationId\n        ) as LocatablePlanet,\n        true\n      );\n    }\n  }\n\n  private updateMouseHoveringOverCoords(coords: WorldCoords): WorldCoords {\n    // if the mouse is inside hitbox of a planet, snaps the mouse to center of planet\n    this.mouseHoveringOverCoords = coords;\n    let hoveringPlanet = undefined;\n\n    const res = this.viewportEntities.getNearestVisiblePlanet(coords);\n    if (res) {\n      hoveringPlanet = res;\n      this.mouseHoveringOverCoords = res.location.coords;\n    }\n\n    this.setHoveringOverPlanet(hoveringPlanet, true);\n\n    this.mouseHoveringOverCoords = {\n      x: Math.round(this.mouseHoveringOverCoords.x),\n      y: Math.round(this.mouseHoveringOverCoords.y),\n    };\n    return this.mouseHoveringOverCoords;\n  }\n\n  private onEmitInitializedPlayer() {\n    this.emit(GameUIManagerEvent.InitializedPlayer);\n  }\n\n  private onEmitInitializedPlayerError(err: React.ReactNode) {\n    this.emit(GameUIManagerEvent.InitializedPlayerError, err);\n  }\n\n  public getGameManager(): GameManager {\n    return this.gameManager;\n  }\n\n  private setModalManager(modalManager: ModalManager) {\n    this.modalManager = modalManager;\n  }\n\n  public getModalManager(): ModalManager {\n    return this.modalManager;\n  }\n\n  /**\n   * If there is a planet being hovered over, returns whether or not it's being hovered\n   * over in the renderer.\n   */\n  public getPlanetHoveringInRenderer() {\n    return this.planetHoveringInRenderer;\n  }\n\n  public getRenderer(): Renderer | null {\n    return Renderer.instance;\n  }\n\n  getPaused(): boolean {\n    return this.gameManager.getPaused();\n  }\n\n  getPaused$(): Monomitter<boolean> {\n    return this.gameManager.getPaused$();\n  }\n\n  public getSilverScoreValue(): number {\n    return this.contractConstants.SILVER_SCORE_VALUE;\n  }\n\n  public getArtifactPointValues() {\n    return this.contractConstants.ARTIFACT_POINT_VALUES;\n  }\n\n  public getCaptureZonePointValues() {\n    return this.contractConstants.CAPTURE_ZONE_PLANET_LEVEL_SCORE;\n  }\n\n  public getArtifactUpdated$() {\n    return this.gameManager.getArtifactUpdated$();\n  }\n\n  public getUIEmitter() {\n    return UIEmitter.getInstance();\n  }\n\n  /**\n   * @returns - A wrapper class for the WebGL2RenderingContext.\n   */\n  public getGlManager(): GameGLManager | null {\n    const renderer = this.getRenderer();\n    if (renderer) return renderer.glManager;\n    return null;\n  }\n\n  /**\n   * @returns the CanvasRenderingContext2D for the game canvas.\n   */\n  public get2dRenderer(): CanvasRenderingContext2D | null {\n    const renderer = this.getRenderer();\n    if (renderer) return renderer.get2DRenderer();\n    return null;\n  }\n\n  /**\n   * Replaces the current renderer with the passed in custom renderer and adds the renderer\n   * to the rendering stack. The function will automatically determine which renderer it is\n   * by the rendererType and the methods in the renderer.\n   * @param customRenderer - a Renderer that follows one of the 23 renderer tempaltes\n   */\n  public setCustomRenderer(customRenderer: BaseRenderer) {\n    const renderer = this.getRenderer();\n    if (renderer) renderer.addCustomRenderer(customRenderer);\n  }\n\n  /**\n   * This function will remove the passed in renderer from the renderering stack. If the\n   * renderer is on top of the renderering stack the next renderer will be the one bellow it.\n   * @param customRenderer - a Renderer that follows one of the 23 renderer tempaltes\n   */\n  public disableCustomRenderer(customRenderer: BaseRenderer) {\n    const renderer = this.getRenderer();\n    if (renderer) renderer.removeCustomRenderer(customRenderer);\n  }\n}\n\nexport default GameUIManager;\n"
  },
  {
    "path": "src/Backend/GameLogic/InitialGameStateDownloader.tsx",
    "content": "import {\n  Artifact,\n  ArtifactId,\n  ClaimedCoords,\n  LocationId,\n  Planet,\n  Player,\n  QueuedArrival,\n  RevealedCoords,\n  VoyageId,\n} from '@darkforest_eth/types';\nimport _ from 'lodash';\nimport React from 'react';\nimport { Link } from '../../Frontend/Components/CoreUI';\nimport { LoadingBarHandle } from '../../Frontend/Components/TextLoadingBar';\nimport { DarkForestTips } from '../../Frontend/Views/DarkForestTips';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes';\nimport { tryGetAllTwitters } from '../Network/UtilityServerAPI';\nimport PersistentChunkStore from '../Storage/PersistentChunkStore';\nimport { ContractsAPI } from './ContractsAPI';\n\nexport interface InitialGameState {\n  contractConstants: ContractConstants;\n  players: Map<string, Player>;\n  worldRadius: number;\n  allTouchedPlanetIds: LocationId[];\n  allRevealedCoords: RevealedCoords[];\n  allClaimedCoords?: ClaimedCoords[];\n  pendingMoves: QueuedArrival[];\n  touchedAndLocatedPlanets: Map<LocationId, Planet>;\n  artifactsOnVoyages: Artifact[];\n  myArtifacts: Artifact[];\n  heldArtifacts: Artifact[][];\n  loadedPlanets: LocationId[];\n  revealedCoordsMap: Map<LocationId, RevealedCoords>;\n  claimedCoordsMap?: Map<LocationId, ClaimedCoords>;\n  planetVoyageIdMap: Map<LocationId, VoyageId[]>;\n  arrivals: Map<VoyageId, QueuedArrival>;\n  twitters: AddressTwitterMap;\n  paused: boolean;\n}\n\nexport class InitialGameStateDownloader {\n  private terminal: TerminalHandle;\n\n  public constructor(terminal: TerminalHandle) {\n    this.terminal = terminal;\n  }\n\n  private makeProgressListener(prettyEntityName: string) {\n    const ref = React.createRef<LoadingBarHandle>();\n    this.terminal.printLoadingBar(prettyEntityName, ref);\n    this.terminal.newline();\n\n    return (percent: number) => {\n      ref.current?.setFractionCompleted(percent);\n    };\n  }\n\n  async download(\n    contractsAPI: ContractsAPI,\n    persistentChunkStore: PersistentChunkStore\n  ): Promise<InitialGameState> {\n    const isDev = process.env.NODE_ENV !== 'production';\n\n    /**\n     * In development we use the same contract address every time we deploy,\n     * so storage is polluted with the IDs of old universes.\n     */\n    const storedTouchedPlanetIds = isDev\n      ? []\n      : await persistentChunkStore.getSavedTouchedPlanetIds();\n    const storedRevealedCoords = isDev ? [] : await persistentChunkStore.getSavedRevealedCoords();\n\n    this.terminal.printElement(<DarkForestTips tips={tips} />);\n    this.terminal.newline();\n\n    const planetIdsLoadingBar = this.makeProgressListener('Planet IDs');\n    const playersLoadingBar = this.makeProgressListener('Players');\n    const revealedPlanetsLoadingBar = this.makeProgressListener('Revealed Planet IDs');\n    const revealedPlanetsCoordsLoadingBar = this.makeProgressListener(\n      'Revealed Planet Coordinates'\n    );\n\n    const pendingMovesLoadingBar = this.makeProgressListener('Pending Moves');\n    const planetsLoadingBar = this.makeProgressListener('Planets');\n    const planetsMetadataLoadingBar = this.makeProgressListener('Planet Metadatas');\n    const artifactsOnPlanetsLoadingBar = this.makeProgressListener('Artifacts On Planets');\n    const artifactsInFlightLoadingBar = this.makeProgressListener('Artifacts On Moves');\n    const yourArtifactsLoadingBar = this.makeProgressListener('Your Artifacts');\n\n    const contractConstants = contractsAPI.getConstants();\n    const worldRadius = contractsAPI.getWorldRadius();\n\n    const players = contractsAPI.getPlayers(playersLoadingBar);\n\n    const arrivals: Map<VoyageId, QueuedArrival> = new Map();\n    const planetVoyageIdMap: Map<LocationId, VoyageId[]> = new Map();\n\n    const minedChunks = Array.from(await persistentChunkStore.allChunks());\n    const minedPlanetIds = new Set(\n      _.flatMap(minedChunks, (c) => c.planetLocations).map((l) => l.hash)\n    );\n\n    const loadedTouchedPlanetIds = contractsAPI.getTouchedPlanetIds(\n      storedTouchedPlanetIds.length,\n      planetIdsLoadingBar\n    );\n\n    const loadedRevealedCoords = contractsAPI.getRevealedPlanetsCoords(\n      storedRevealedCoords.length,\n      revealedPlanetsLoadingBar,\n      revealedPlanetsCoordsLoadingBar\n    );\n    const claimedCoordsMap = new Map<LocationId, ClaimedCoords>();\n\n    const allTouchedPlanetIds = storedTouchedPlanetIds.concat(await loadedTouchedPlanetIds);\n    const allRevealedCoords = storedRevealedCoords.concat(await loadedRevealedCoords);\n    const revealedCoordsMap = new Map<LocationId, RevealedCoords>();\n    for (const revealedCoords of allRevealedCoords) {\n      revealedCoordsMap.set(revealedCoords.hash, revealedCoords);\n    }\n\n    let planetsToLoad = allTouchedPlanetIds.filter(\n      (id) => minedPlanetIds.has(id) || revealedCoordsMap.has(id) || claimedCoordsMap.has(id)\n    );\n\n    const pendingMoves = await contractsAPI.getAllArrivals(planetsToLoad, pendingMovesLoadingBar);\n\n    // add origin points of voyages to known planets, because we need to know origin owner to render\n    // the shrinking / incoming circle\n    for (const arrival of pendingMoves) {\n      planetsToLoad.push(arrival.fromPlanet);\n    }\n    planetsToLoad = [...new Set(planetsToLoad)];\n\n    const touchedAndLocatedPlanets = await contractsAPI.bulkGetPlanets(\n      planetsToLoad,\n      planetsLoadingBar,\n      planetsMetadataLoadingBar\n    );\n\n    touchedAndLocatedPlanets.forEach((_planet, locId) => {\n      if (touchedAndLocatedPlanets.has(locId)) {\n        planetVoyageIdMap.set(locId, []);\n      }\n    });\n\n    for (const arrival of pendingMoves) {\n      const voyageIds = planetVoyageIdMap.get(arrival.toPlanet);\n      if (voyageIds) {\n        voyageIds.push(arrival.eventId);\n        planetVoyageIdMap.set(arrival.toPlanet, voyageIds);\n      }\n      arrivals.set(arrival.eventId, arrival);\n    }\n\n    const artifactIdsOnVoyages: ArtifactId[] = [];\n    for (const arrival of pendingMoves) {\n      if (arrival.artifactId) {\n        artifactIdsOnVoyages.push(arrival.artifactId);\n      }\n    }\n\n    const artifactsOnVoyages = await contractsAPI.bulkGetArtifacts(\n      artifactIdsOnVoyages,\n      artifactsInFlightLoadingBar\n    );\n\n    const heldArtifacts = contractsAPI.bulkGetArtifactsOnPlanets(\n      planetsToLoad,\n      artifactsOnPlanetsLoadingBar\n    );\n    const myArtifacts = contractsAPI.getPlayerArtifacts(\n      contractsAPI.getAddress(),\n      yourArtifactsLoadingBar\n    );\n\n    const twitters = await tryGetAllTwitters();\n    const paused = contractsAPI.getIsPaused();\n\n    const initialState: InitialGameState = {\n      contractConstants: await contractConstants,\n      players: await players,\n      worldRadius: await worldRadius,\n      allTouchedPlanetIds,\n      allRevealedCoords,\n      pendingMoves,\n      touchedAndLocatedPlanets,\n      artifactsOnVoyages,\n      myArtifacts: await myArtifacts,\n      heldArtifacts: await heldArtifacts,\n      loadedPlanets: planetsToLoad,\n      revealedCoordsMap,\n      claimedCoordsMap,\n      planetVoyageIdMap,\n      arrivals,\n      twitters,\n      paused: await paused,\n    };\n\n    return initialState;\n  }\n}\n\nconst tips = [\n  'Beware of pirates! To capture a planet with pirates, simply send an attack large enough to overcome its current energy.',\n  <>\n    Navigate the Dark Forest with allies (and enemies) - join the{' '}\n    <Link to='https://discord.gg/C23An5qNGv'>Dark Forest Discord</Link>!\n  </>,\n  'There are many different artifact types, each with unique properties... try activating one on a planet!',\n  'The top 63 players get NFT rewards at the end of each v0.6 round!',\n  \"There are many different ways to enjoy Dark Forest - as long as you're having fun, you're doing it right.\",\n  'Be careful when capturing planets - if you attack a player-owned planet, it may look like an act of war!',\n  'A planet can have at most one active artifact.',\n  'Withdrawing an artifact (via a Spacetime Rip) gives you full control of that artifact as an ERC 721 token. You can deposit artifacts you have withdrawn back into the universe via Spacetime Rips.',\n  'You can use plugins to enhance your capabilities by automating repetitive tasks. The top players are probably using plugins (:',\n  'Quasars can store lots of energy and silver, at the expense of being able to generate neither.',\n  'Never share your private key with anyone else!',\n  'Broadcasting a planet reveals its location to ALL other players!',\n  'You can spend silver to upgrade your planets.',\n  'Planets in Nebula are more difficult to capture than planets in Deep Space.',\n  'Some of the universe is corrupted, and contains special versions of the artifacts.',\n  'You can import and export maps! Be careful importing maps from others, they may contain fabricated map data.',\n  <>\n    If mining the universe is slow on your computer, you can try the Remote Miner plugin. Find that\n    and other plugins on <Link to='https://plugins.zkga.me'>plugins.zkga.me</Link>.\n  </>,\n  \"A planet can only have 6 artifacts on it at any given time. Sometimes more if you get lucky. It's the blockchain, after all.\",\n  'A foundry must be prospected before you can attempt to find an artifact, but make sure to click \"Find\" before 256 blocks or it will be lost forever.',\n  'Defense upgrades make your planets less vulnerable to attack, Range upgrades make your voyages go further and decay less, and Speed upgrades make your voyages go much faster.',\n  'Wormhole artifacts reduce the effective distance between 2 planets. Try using them to link 2 planets very far apart!',\n  'Upon deactivation, some artifacts must cooldown for a period before they can be activated again.',\n  'Photoid Cannon artifacts will debuff your planet on activation, but get a massive stat boost for the first voyage from the planet after that a charging period. Photoid Cannon artifacts are destroyed upon use.',\n  \"Planetary Shield artifacts will massively boost a planet's defense, but at the cost of energy and energy growth stats. Planetary Shield artifacts are destroyed upon deactivation.\",\n  \"Bloom Filter artifacts instantly set a planet's energy and silver to full, but are destroyed upon activation. Try using them on a Quasar!\",\n  'Dark Forest exists on the blockchain, so you can play with an entirely different client if you want.',\n  <>\n    Writing plugins? Check out some documentation{' '}\n    <Link to='https://github.com/darkforest-eth/client/blob/master/docs/classes/Backend_GameLogic_GameManager.default.md'>\n      here\n    </Link>{' '}\n    and{' '}\n    <Link to='https://github.com/darkforest-eth/client/blob/master/docs/classes/Backend_GameLogic_GameUIManager.default.md'>\n      here\n    </Link>\n    .\n  </>,\n];\n"
  },
  {
    "path": "src/Backend/GameLogic/LayeredMap.ts",
    "content": "import { MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants';\nimport { LocationId, Radii, WorldCoords, WorldLocation } from '@darkforest_eth/types';\nimport { Box, Circle, Point, QuadTree } from 'js-quadtree';\n\n/**\n * For every point in each of the planet quadtrees, we store a pointer to the planet.\n */\ninterface PlanetPointData {\n  locationId: LocationId;\n}\n\n/**\n * Data structure which allows us to efficiently query for \"which planets between level X and X + n\n * (for positive n) are present in the given world rectangle\", as well as, in the future, \"which\n * chunks are visible in the vieport\".\n */\nexport class LayeredMap {\n  private perLevelPlanetQuadtrees: Map<number, QuadTree>;\n  private insertedLocations: Set<LocationId>;\n\n  public constructor(worldRadius: number) {\n    // add 500k so that players have the ability to mine far outside the current world radius.\n    worldRadius += 500_000;\n\n    const worldSize = new Box(-worldRadius, -worldRadius, worldRadius * 2, worldRadius * 2);\n\n    this.perLevelPlanetQuadtrees = new Map();\n    this.insertedLocations = new Set();\n\n    for (let i = MIN_PLANET_LEVEL; i <= MAX_PLANET_LEVEL; i++) {\n      const config = {\n        maximumDepth: 10,\n        removeEmptyNodes: true,\n      };\n\n      this.perLevelPlanetQuadtrees.set(i, new QuadTree(worldSize, config));\n    }\n  }\n\n  /**\n   * Records the fact that there is a planet at the given world location.\n   */\n  public insertPlanet(location: WorldLocation, planetLevel: number) {\n    if (this.insertedLocations.has(location.hash)) {\n      return;\n    }\n    const quadTree = this.perLevelPlanetQuadtrees.get(planetLevel);\n    const newPointData: PlanetPointData = { locationId: location.hash };\n    quadTree?.insert(new Point(location.coords.x, location.coords.y, newPointData));\n    this.insertedLocations.add(location.hash);\n  }\n\n  /**\n   * Gets all the planets within the given world radius of a world location.\n   */\n  public getPlanetsInCircle(coords: WorldCoords, worldRadius: number): LocationId[] {\n    const results = [];\n    for (const quad of this.perLevelPlanetQuadtrees.values()) {\n      results.push(\n        ...quad.query(new Circle(coords.x, coords.y, worldRadius)).map(this.getPointLocationId)\n      );\n    }\n    return results;\n  }\n\n  /**\n   * Gets the ids of all the planets that are both within the given bounding box (defined by its bottom\n   * left coordinate, width, and height) in the world and of a level that was passed in via the\n   * `planetLevels` parameter.\n   */\n  public getPlanets(\n    worldX: number,\n    worldY: number,\n    worldWidth: number,\n    worldHeight: number,\n    planetLevels: number[],\n    planetLevelToRadii: Map<number, Radii>\n  ): LocationId[] {\n    const result: LocationId[] = [];\n\n    for (const level of planetLevels) {\n      const radiusPx = planetLevelToRadii.get(level)?.radiusWorld as number;\n      const boundsIncrease = radiusPx * 5;\n\n      const bounds = new Box(\n        worldX - boundsIncrease,\n        worldY - boundsIncrease,\n        worldWidth + boundsIncrease * 2,\n        worldHeight + boundsIncrease * 2\n      );\n\n      const planets =\n        this.perLevelPlanetQuadtrees.get(level)?.query(bounds).map(this.getPointLocationId) || [];\n\n      result.push(...planets);\n    }\n\n    return result;\n  }\n\n  private getPointLocationId(point: Point): LocationId {\n    return (point.data as PlanetPointData).locationId;\n  }\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/PluginManager.tsx",
    "content": "import { Monomitter, monomitter } from '@darkforest_eth/events';\nimport { PluginId } from '@darkforest_eth/types';\nimport { EmbeddedPlugin, getEmbeddedPlugins } from '../Plugins/EmbeddedPluginLoader';\nimport { PluginProcess } from '../Plugins/PluginProcess';\nimport { SerializedPlugin } from '../Plugins/SerializedPlugin';\nimport GameManager from './GameManager';\n\n/**\n * Represents book-keeping information about a running process. We keep it\n * separate from the process code, so that the plugin doesn't accidentally\n * overwrite this information.\n */\nexport class ProcessInfo {\n  rendered = false;\n  hasError = false;\n}\n\n/**\n * This class keeps track of all the plugins that this player has loaded\n * into their game. Acts as a task manager, supports all CRUD operations\n * for plugins, as well as instantiating and destroying running plugins.\n * All library operations are also persisted to IndexDB.\n *\n * Important! Does not run plugins until the user clicks 'run' somewhere in\n * this UI. This is important, because if someone develops a buggy plugin,\n * it would suck if that bricked their game.\n */\nexport class PluginManager {\n  private gameManager: GameManager;\n\n  /**\n   * All the plugins in the player's library. Not all of the player's plugins\n   * are running, and therefore not all exist in `pluginInstances`.\n   * `PluginsManager` keeps this field in sync with the plugins the user has\n   * saved in the IndexDB via {@link PersistentChunkStore}\n   */\n  private pluginLibrary: SerializedPlugin[];\n\n  /**\n   * Plugins that are currently loaded into the game, and are rendering into a modal.\n   * `PluginsManager` makes sure that when a plugin starts executing, it is added into\n   * `pluginInstances`, and that once a plugin is unloaded, its `.destroy()` method is called, and\n   * that the plugin is removed from `pluginInstances`.\n   */\n  private pluginProcesses: Record<string, PluginProcess>;\n\n  /**\n   * parallel to pluginProcesses\n   */\n  private pluginProcessInfos: Record<string, ProcessInfo>;\n\n  /**\n   * Event emitter that publishes whenever the set of plugins changes.\n   */\n  public plugins$: Monomitter<SerializedPlugin[]>;\n\n  public constructor(gameManager: GameManager) {\n    this.gameManager = gameManager;\n    this.pluginLibrary = [];\n    this.pluginProcesses = {};\n    this.pluginProcessInfos = {};\n    this.plugins$ = monomitter<SerializedPlugin[]>();\n  }\n\n  /**\n   * If a plugin with the given id is running, call its `.destroy()` method,\n   * and remove it from `pluginInstances`. Stop listening for new local plugins.\n   */\n  public destroy(id: PluginId): void {\n    if (this.pluginProcesses[id]) {\n      try {\n        const process = this.pluginProcesses[id];\n        if (process && typeof process.destroy === 'function') {\n          // TODO: destroy should also receive the element to cleanup event handlers, etc\n          process.destroy();\n        }\n      } catch (e) {\n        this.pluginProcessInfos[id].hasError = true;\n        console.error('error when destroying plugin', e);\n      } finally {\n        delete this.pluginProcesses[id];\n        delete this.pluginProcessInfos[id];\n      }\n    }\n  }\n\n  /**\n   * Load all plugins from this disk into `pluginLibrary`. Insert the default\n   * plugins into the player's library if the default plugins have never been\n   * added before. Effectively idempotent after the first time you call it.\n   * @param isAdmin Is an admin loading the plugins.\n   * @param overwriteEmbeddedPlugins Reload all embedded plugins even if a local copy is found.\n   * Useful for plugin development.\n   */\n  public async load(isAdmin: boolean, overwriteEmbeddedPlugins: boolean): Promise<void> {\n    this.pluginLibrary = await this.gameManager.loadPlugins();\n\n    this.onNewEmbeddedPlugins(getEmbeddedPlugins(isAdmin), overwriteEmbeddedPlugins);\n\n    this.notifyPluginLibraryUpdated();\n  }\n\n  /**\n   * Remove the given plugin both from the player's library, and kills\n   * the plugin if it is running.\n   */\n  public async deletePlugin(pluginId: PluginId): Promise<void> {\n    this.pluginLibrary = this.pluginLibrary.filter((p) => p.id !== pluginId);\n    this.destroy(pluginId);\n    await this.gameManager.savePlugins(this.pluginLibrary);\n\n    this.notifyPluginLibraryUpdated();\n  }\n\n  /**\n   * Gets the serialized plugin with the given id from the player's plugin\n   * library. `undefined` if no plugin exists.\n   */\n  public getPluginFromLibrary(id?: PluginId): SerializedPlugin | undefined {\n    return this.pluginLibrary.find((p) => p.id === id);\n  }\n\n  /**\n   * 1) kills the plugin if it's running\n   * 2) edits the plugin-library version of this plugin\n   * 3) if a plugin was edited, save the plugin library to disk\n   */\n  public overwritePlugin(newName: string, pluginCode: string, id: PluginId): void {\n    this.destroy(id);\n\n    const plugin = this.getPluginFromLibrary(id);\n\n    if (plugin) {\n      plugin.code = pluginCode;\n      plugin.name = newName;\n      plugin.lastEdited = new Date().getTime();\n      this.gameManager.savePlugins(this.pluginLibrary);\n    }\n\n    this.notifyPluginLibraryUpdated();\n  }\n\n  /**\n   * Reorders the current plugins. plugin ids in `newPluginIdOrder` must correspond\n   * 1:1 to plugins in the plugin library.\n   */\n  public reorderPlugins(newPluginIdOrder: string[]) {\n    const newPluginsList = newPluginIdOrder\n      .map((id) => this.pluginLibrary.find((p) => p.id === id))\n      .filter((p) => !!p) as SerializedPlugin[];\n\n    if (newPluginsList.length !== this.pluginLibrary.length) {\n      throw new Error('to reorder the plugins, you must pass in precisely one id for each plugin');\n    }\n\n    this.pluginLibrary = newPluginsList;\n    this.gameManager.savePlugins(this.pluginLibrary);\n\n    this.notifyPluginLibraryUpdated();\n  }\n\n  /**\n   * adds a new plugin into the plugin library.\n   */\n  public addPluginToLibrary(id: PluginId, name: string, code: string): SerializedPlugin {\n    const newPlugin: SerializedPlugin = {\n      id,\n      lastEdited: new Date().getTime(),\n      name,\n      code,\n    };\n\n    this.pluginLibrary.push(newPlugin);\n    this.gameManager.savePlugins(this.pluginLibrary);\n\n    this.notifyPluginLibraryUpdated();\n\n    return PluginManager.copy(newPlugin);\n  }\n\n  /**\n   * Either spawns the given plugin by evaluating its `pluginCode`, or\n   * returns the already running plugin instance. If starting a plugin\n   * throws an error then returns `undefined`.\n   */\n  public async spawn(id: PluginId): Promise<PluginProcess | undefined> {\n    if (this.pluginProcesses[id as string]) {\n      return this.pluginProcesses[id as string];\n    }\n\n    const plugin = this.getPluginFromLibrary(id);\n\n    if (!plugin) {\n      return;\n    }\n\n    this.pluginProcessInfos[plugin.id] = new ProcessInfo();\n\n    const moduleFile = new File([plugin.code], plugin.name, {\n      type: 'text/javascript',\n      lastModified: plugin.lastEdited,\n    });\n    const moduleUrl = URL.createObjectURL(moduleFile);\n    try {\n      // The `webpackIgnore` \"magic comment\" is almost undocumented, but it makes\n      // webpack skip over this dynamic `import` call so it won't be transformed into\n      // a weird _webpack_require_dynamic_ call\n      const { default: Plugin } = await import(/* webpackIgnore: true */ moduleUrl);\n      if (this.pluginProcesses[id] === undefined) {\n        // instantiate the plugin and attach it to the process list\n        this.pluginProcesses[id] = new Plugin();\n      }\n    } catch (e) {\n      console.error(`Failed to start plugin: ${plugin.name} - Please review stack trace\\n`, e);\n      this.pluginProcessInfos[id].hasError = true;\n    }\n\n    return this.pluginProcesses[plugin.id];\n  }\n\n  /**\n   * If this plugin's `render` method has not been called yet, then\n   * call it! Remembers that this plugin has been rendered.\n   */\n  public async render(id: PluginId, element: HTMLDivElement): Promise<void> {\n    const process = await this.spawn(id);\n    const processInfo = this.pluginProcessInfos[id];\n\n    if (process && typeof process.render === 'function' && processInfo && !processInfo.rendered) {\n      try {\n        // Allows a plugin render to be async which in turns allows\n        // any method to be async since this is the entry point into it\n        await process.render(element);\n        processInfo.rendered = true;\n      } catch (e) {\n        processInfo.hasError = true;\n        console.log('failed to render plugin', e);\n      }\n    }\n  }\n\n  /**\n   * Gets all the plugins in this player's library.\n   */\n  public getLibrary(): SerializedPlugin[] {\n    return this.pluginLibrary.map(PluginManager.copy);\n  }\n\n  /**\n   * If this process has been started, gets its info\n   */\n  public getProcessInfo(id: PluginId): ProcessInfo {\n    return PluginManager.copy(this.pluginProcessInfos[id as string]);\n  }\n\n  /**\n   * Gets a map of all the currently running processes\n   */\n  public getAllProcessInfos(): Map<PluginId, ProcessInfo> {\n    const map = new Map();\n\n    for (const id of Object.getOwnPropertyNames(this.pluginProcessInfos)) {\n      map.set(id as PluginId, PluginManager.copy(this.pluginProcessInfos[id]));\n    }\n\n    return map;\n  }\n\n  /**\n   * For each currently running plugin, if the plugin has a 'draw'\n   * function, then draw that plugin to the screen.\n   */\n  public drawAllRunningPlugins(ctx: CanvasRenderingContext2D) {\n    for (const plugin of this.pluginLibrary) {\n      const processInfo = this.pluginProcessInfos[plugin.id];\n      const pluginInstance = this.pluginProcesses[plugin.id];\n\n      if (pluginInstance && typeof pluginInstance.draw === 'function' && !processInfo.hasError) {\n        try {\n          pluginInstance.draw(ctx);\n        } catch (e) {\n          console.log('failed to draw plugin', e);\n          processInfo.hasError = true;\n        }\n      }\n    }\n  }\n\n  private hasPlugin(plugin: EmbeddedPlugin): boolean {\n    return this.pluginLibrary.some((p) => p.id === plugin.id);\n  }\n\n  private onNewEmbeddedPlugins(newPlugins: EmbeddedPlugin[], overwriteEmbeddedPlugins: boolean) {\n    for (const plugin of newPlugins) {\n      if (!this.hasPlugin(plugin)) {\n        this.addPluginToLibrary(plugin.id, plugin.name, plugin.code);\n      } else if (overwriteEmbeddedPlugins) {\n        this.overwritePlugin(plugin.name, plugin.code, plugin.id);\n      }\n    }\n  }\n\n  private notifyPluginLibraryUpdated() {\n    this.plugins$.publish(this.getLibrary());\n  }\n\n  /**\n   * To prevent users of this class from modifying our plugins library,\n   * we return clones of the plugins. This should probably be a function\n   * in a Utils file somewhere, but I thought I should leave a good comment\n   * about why we return copies of the plugins from the library.\n   */\n  private static copy<T>(plugin: T): T {\n    return JSON.parse(JSON.stringify(plugin)) as T;\n  }\n}\n"
  },
  {
    "path": "src/Backend/GameLogic/TutorialManager.ts",
    "content": "import { Setting } from '@darkforest_eth/types';\nimport { EventEmitter } from 'events';\nimport NotificationManager from '../../Frontend/Game/NotificationManager';\nimport { setBooleanSetting } from '../../Frontend/Utils/SettingsHooks';\nimport GameUIManager from './GameUIManager';\n\nexport const enum TutorialManagerEvent {\n  StateChanged = 'StateChanged',\n}\n\nexport const enum TutorialState {\n  None,\n\n  HomePlanet,\n  SendFleet,\n  SpaceJunk,\n  Spaceship,\n  Deselect,\n  ZoomOut,\n  MinerMove,\n  MinerPause,\n  Terminal,\n  HowToGetScore,\n  ScoringDetails,\n  Valhalla,\n\n  AlmostCompleted,\n  Completed,\n}\n\nclass TutorialManager extends EventEmitter {\n  static instance: TutorialManager;\n  private tutorialState: TutorialState = TutorialState.None;\n\n  private uiManager: GameUIManager;\n\n  private constructor(uiManager: GameUIManager) {\n    super();\n    this.uiManager = uiManager;\n  }\n\n  static getInstance(uiManager: GameUIManager) {\n    if (!TutorialManager.instance) {\n      TutorialManager.instance = new TutorialManager(uiManager);\n    }\n\n    return TutorialManager.instance;\n  }\n\n  private setTutorialState(newState: TutorialState) {\n    this.tutorialState = newState;\n    this.emit(TutorialManagerEvent.StateChanged, newState);\n\n    if (newState === TutorialState.Completed) {\n      const notifManager = NotificationManager.getInstance();\n      notifManager.welcomePlayer();\n    }\n  }\n\n  private advance() {\n    let newState = Math.min(this.tutorialState + 1, TutorialState.Completed);\n    if (this.shouldSkipState(newState)) newState++;\n\n    this.setTutorialState(newState);\n  }\n\n  private shouldSkipState(state: TutorialState) {\n    return !this.uiManager.getSpaceJunkEnabled() && state === TutorialState.SpaceJunk;\n  }\n\n  reset() {\n    const config = {\n      contractAddress: this.uiManager.getContractAddress(),\n      account: this.uiManager.getAccount(),\n    };\n    setBooleanSetting(config, Setting.TutorialOpen, true);\n    this.setTutorialState(TutorialState.None);\n  }\n\n  complete() {\n    this.setTutorialState(TutorialState.Completed);\n    const config = {\n      contractAddress: this.uiManager.getContractAddress(),\n      account: this.uiManager.getAccount(),\n    };\n    setBooleanSetting(config, Setting.TutorialCompleted, true);\n  }\n\n  acceptInput(state: TutorialState) {\n    if (state === this.tutorialState) this.advance();\n  }\n}\n\nexport default TutorialManager;\n"
  },
  {
    "path": "src/Backend/GameLogic/ViewportEntities.ts",
    "content": "import { MAX_PLANET_LEVEL, MIN_PLANET_LEVEL } from '@darkforest_eth/constants';\nimport { isLocatable } from '@darkforest_eth/gamelogic';\nimport {\n  Chunk,\n  LocatablePlanet,\n  LocationId,\n  PlanetLevel,\n  PlanetRenderInfo,\n  Radii,\n  WorldCoords,\n} from '@darkforest_eth/types';\nimport Viewport from '../../Frontend/Game/Viewport';\nimport { planetLevelToAnimationSpeed, sinusoidalAnimation } from '../Utils/Animation';\nimport GameManager from './GameManager';\nimport GameUIManager from './GameUIManager';\n\n/**\n * Efficiently calculates which planets are in the viewport, and allows you to find the nearest\n * visible planet to the mouse.\n */\nexport class ViewportEntities {\n  private readonly gameManager: GameManager;\n  private readonly uiManager: GameUIManager;\n\n  private cachedExploredChunks: Set<Chunk> = new Set();\n  private cachedPlanets: Map<LocationId, PlanetRenderInfo> = new Map();\n\n  public constructor(gameManager: GameManager, gameUIManager: GameUIManager) {\n    this.gameManager = gameManager;\n    this.uiManager = gameUIManager;\n    this.startRefreshing();\n  }\n\n  public startRefreshing() {\n    setInterval(() => {\n      this.loadPlanetMessages();\n    }, 10 * 1000);\n  }\n\n  public getPlanetsAndChunks() {\n    this.updateLocationsAndChunks();\n\n    for (const p of this.cachedPlanets.values()) {\n      p.planet.emojiBobAnimation?.update();\n      p.planet.emojiZoopAnimation?.update();\n    }\n\n    return {\n      chunks: this.cachedExploredChunks,\n      cachedPlanets: this.cachedPlanets,\n    };\n  }\n\n  private updateLocationsAndChunks() {\n    const viewport = Viewport.getInstance();\n    this.recalculateViewportPlanets(viewport);\n    this.recalculateViewportChunks(viewport);\n\n    this.uiManager.updateDiagnostics((d) => {\n      d.visibleChunks = this.cachedExploredChunks.size;\n      d.visiblePlanets = this.cachedPlanets.size;\n      d.totalPlanets = this.gameManager.getGameObjects().getAllPlanetsMap().size;\n    });\n  }\n\n  private recalculateViewportChunks(viewport: Viewport) {\n    if (this.uiManager.getIsHighPerfMode()) {\n      return;\n    }\n\n    const chunks = new Set<Chunk>();\n\n    for (const exploredChunk of this.uiManager.getExploredChunks()) {\n      if (\n        viewport.intersectsViewport(exploredChunk) &&\n        viewport.worldToCanvasDist(exploredChunk.chunkFootprint.sideLength) >= 3\n      ) {\n        chunks.add(exploredChunk);\n      }\n    }\n\n    this.cachedExploredChunks = chunks;\n  }\n\n  private async loadPlanetMessages() {\n    const planetIds = [];\n    for (const p of this.cachedPlanets.values()) {\n      // by definition, only planets that are owned can have planet messages on them, so they must\n      // also be 'in the contract'\n      if (p.planet.isInContract) {\n        planetIds.push(p.planet.locationId);\n      }\n    }\n\n    this.gameManager.refreshServerPlanetStates(planetIds);\n  }\n\n  private recalculateViewportPlanets(viewport: Viewport) {\n    const radii = this.getPlanetRadii(Viewport.getInstance());\n\n    const planetsInViewport = this.gameManager.getPlanetsInWorldRectangle(\n      viewport.getViewportPosition().x - viewport.widthInWorldUnits / 2,\n      viewport.getViewportPosition().y - viewport.heightInWorldUnits / 2,\n      viewport.widthInWorldUnits,\n      viewport.heightInWorldUnits,\n      this.getVisiblePlanetLevels(viewport),\n      radii,\n      true\n    );\n\n    const selectedPlanet = this.uiManager.getSelectedPlanet();\n\n    if (selectedPlanet && isLocatable(selectedPlanet)) {\n      planetsInViewport.push(selectedPlanet);\n    }\n\n    this.replacePlanets(planetsInViewport);\n  }\n\n  private replacePlanets(newPlanetsInViewport: LocatablePlanet[]) {\n    const radii = this.getPlanetRadii(Viewport.getInstance());\n    const planetsToRemove = new Set(Array.from(this.cachedPlanets.keys()));\n\n    newPlanetsInViewport.forEach((planet: LocatablePlanet) => {\n      planetsToRemove.delete(planet.locationId);\n\n      const newPlanetInfo: PlanetRenderInfo = {\n        planet: planet,\n        radii: radii.get(planet.planetLevel) as Radii,\n      };\n\n      if (!planet.emojiBobAnimation) {\n        planet.emojiBobAnimation = sinusoidalAnimation(\n          planetLevelToAnimationSpeed(planet.planetLevel)\n        );\n      }\n\n      this.cachedPlanets.set(planet.locationId, newPlanetInfo);\n    });\n\n    for (const toRemove of planetsToRemove) {\n      this.cachedPlanets.delete(toRemove);\n      const planet = this.cachedPlanets.get(toRemove);\n      if (planet) {\n        planet.planet.emojiBobAnimation = undefined;\n      }\n    }\n  }\n\n  /**\n   * Gets the planet that is closest to the given coordinates. Filters out irrelevant planets\n   * using the `radiusMap` parameter, which specifies how close a planet must be in order to\n   * be returned from this function, given that planet's level. Smaller planets have a smaller\n   * radius, and larger planets have a larger radius.\n   *\n   * If a smaller and a larger planet are both within respective radii of coords, the smaller\n   * planet is returned.\n   */\n  public getNearestVisiblePlanet(coords: WorldCoords): LocatablePlanet | undefined {\n    const radii = this.getPlanetRadii(Viewport.getInstance());\n    let bestPlanet: LocatablePlanet | undefined;\n\n    for (const planetInfo of this.cachedPlanets.values()) {\n      const planet = planetInfo.planet;\n      const distThreshold = radii.get(planet.planetLevel)?.radiusWorld as number;\n\n      if (\n        Math.abs(coords.x - planet.location.coords.x) <= distThreshold &&\n        Math.abs(coords.y - planet.location.coords.y) <= distThreshold\n      ) {\n        if (!bestPlanet || bestPlanet.planetLevel > planet.planetLevel) {\n          bestPlanet = planet;\n        }\n      }\n    }\n\n    return bestPlanet;\n  }\n\n  /**\n   * One entry per planet level - radius in screen pixels of that planet level given the current\n   * viewport configuration, as well as the world radius.\n   */\n  private getPlanetRadii(viewport: Viewport): Map<PlanetLevel, Radii> {\n    const result = new Map();\n\n    for (let i = MIN_PLANET_LEVEL; i <= MAX_PLANET_LEVEL; i++) {\n      const radiusWorld = this.uiManager.getRadiusOfPlanetLevel(i as PlanetLevel);\n      const radiusPixels = viewport.worldToCanvasDist(radiusWorld);\n\n      result.set(i, { radiusWorld, radiusPixels });\n    }\n\n    return result;\n  }\n\n  /**\n   * Returns a list of planet levels which, when rendered, would result in a planet that has a size\n   * larger than one pixel.\n   */\n  private getVisiblePlanetLevels(viewport: Viewport) {\n    const result = [];\n\n    const viewportWidthPx = viewport.worldToCanvasDist(viewport.getViewportWorldWidth());\n    const minPlanetSize = viewportWidthPx > 40_000 ? 3 : 1;\n\n    for (let i = 0; i <= MAX_PLANET_LEVEL; i++) {\n      const radiusW = this.uiManager.getRadiusOfPlanetLevel(i as PlanetLevel);\n      const radiusPx = viewport.worldToCanvasDist(radiusW);\n\n      if (radiusPx >= minPlanetSize) {\n        result.push(i);\n      }\n    }\n\n    return result;\n  }\n}\n"
  },
  {
    "path": "src/Backend/Miner/ChunkUtils.ts",
    "content": "import { Chunk, Rectangle, WorldCoords, WorldLocation } from '@darkforest_eth/types';\nimport { BucketId, ChunkId, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes';\n\n/**\n * Deterministically assigns a bucket ID to a rectangle, based on its position and size in the\n * universe. This is kind of like a shitty hash function. Its purpose is to distribute chunks\n * roughly evenly between the buckets.\n */\nexport function getBucket(chunk: Rectangle): BucketId {\n  const alphanumeric = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n  let sum =\n    (Math.floor(chunk.bottomLeft.x / chunk.sideLength) +\n      Math.floor(chunk.bottomLeft.y / chunk.sideLength)) %\n    alphanumeric.length;\n  if (sum < 0) sum += alphanumeric.length;\n  return alphanumeric[sum] as BucketId;\n}\n\n/**\n * A unique ID generated for each chunk based on its rectangle, as well as its bucket. It's the\n * primary key by which chunks are identified.\n */\nexport function getChunkKey(chunkLoc: Rectangle): ChunkId {\n  return (`${getBucket(chunkLoc)},` +\n    `${chunkLoc.sideLength},` +\n    `${chunkLoc.bottomLeft.x},` +\n    `${chunkLoc.bottomLeft.y}`) as ChunkId;\n}\n\n/**\n * Converts from the in-game representation of a chunk to its persisted representation.\n */\nexport function toPersistedChunk(chunk: Chunk): PersistedChunk {\n  const planetLocations = chunk.planetLocations.map((location) => ({\n    x: location.coords.x,\n    y: location.coords.y,\n    h: location.hash,\n    p: location.perlin,\n    b: location.biomebase,\n  }));\n\n  return {\n    x: chunk.chunkFootprint.bottomLeft.x,\n    y: chunk.chunkFootprint.bottomLeft.y,\n    s: chunk.chunkFootprint.sideLength,\n    l: planetLocations,\n    p: chunk.perlin,\n  };\n}\n\n/**\n * Converts from the persisted representation of a chunk to the in-game representation of a chunk.\n */\nexport const toExploredChunk = (chunk: PersistedChunk): Chunk => {\n  const planetLocations = chunk.l.map((location) => ({\n    coords: { x: location.x, y: location.y },\n    hash: location.h,\n    perlin: location.p,\n    biomebase: location.b,\n  }));\n\n  return {\n    chunkFootprint: {\n      bottomLeft: { x: chunk.x, y: chunk.y },\n      sideLength: chunk.s,\n    },\n    planetLocations,\n    perlin: chunk.p,\n  };\n};\n\n/**\n * An aligned chunk is one whose corner's coordinates are multiples of its side length, and its side\n * length is a power of two between {@link MIN_CHUNK_SIZE} and {@link MAX_CHUNK_SIZE} inclusive.\n *\n * \"Aligned\" chunks is that they can be merged into other aligned chunks. Non-aligned chunks cannot\n * always be merged into squares. The reason we care about merging is that merging chunks allows us\n * to represent more world-space using fewer chunks. This saves memory at both runtime and\n * storage-time. Therefore, we only store aligned chunks.\n *\n * As an example, chunks with any corner at (0, 0) are always aligned. A chunk with side length 4 is\n * aligned if it's on (4, 4), (8, 12), but not (4, 6).\n *\n * This function returns the other three chunks with the same side length of the given chunk, such\n * that the four chunks, if merged, would result in an \"aligned\" chunk whose side length is double\n * the given chunk.\n */\nexport const getSiblingLocations = (chunkLoc: Rectangle): [Rectangle, Rectangle, Rectangle] => {\n  const doubleSideLen = 2 * chunkLoc.sideLength;\n  const newBottomLeftX = Math.floor(chunkLoc.bottomLeft.x / doubleSideLen) * doubleSideLen;\n  const newBottomLeftY = Math.floor(chunkLoc.bottomLeft.y / doubleSideLen) * doubleSideLen;\n  const newBottomLeft = { x: newBottomLeftX, y: newBottomLeftY };\n\n  const siblingLocs: Rectangle[] = [];\n\n  for (let i = 0; i < 2; i += 1) {\n    for (let j = 0; j < 2; j += 1) {\n      const x = newBottomLeft.x + i * chunkLoc.sideLength;\n      const y = newBottomLeft.y + j * chunkLoc.sideLength;\n\n      if (x === chunkLoc.bottomLeft.x && y === chunkLoc.bottomLeft.y) {\n        continue;\n      }\n\n      siblingLocs.push({\n        bottomLeft: { x, y },\n        sideLength: chunkLoc.sideLength,\n      });\n    }\n  }\n  return [siblingLocs[0], siblingLocs[1], siblingLocs[2]];\n};\n\n/**\n * Returns the unique aligned chunk (for definition of \"aligned\" see comment on\n * `getSiblingLocations`) with the given side length that contains the given point. A chunk contains\n * all of the points strictly inside of its bounds, as well as the bottom and left edges. This means\n * it does not contain points which are on its right or top edges.\n */\nexport function getChunkOfSideLengthContainingPoint(\n  coords: WorldCoords,\n  sideLength: number\n): Rectangle {\n  return {\n    sideLength,\n    bottomLeft: {\n      x: Math.floor(coords.x / sideLength) * sideLength,\n      y: Math.floor(coords.y / sideLength) * sideLength,\n    },\n  };\n}\n\n/**\n * At a high level, call this function to update an efficient quadtree-like store containing all of\n * the chunks that a player has either mined or imported in their client.\n *\n * More speecifically, adds the given new chunk to the given map of chunks. If the map of chunks\n * contains all of the \"sibling\" chunks to this new chunk, then instead of adding it, we merge the 4\n * sibling chunks, and add the merged chunk to the map and remove the existing sibling chunks. This\n * function is recursive, which means that if the newly created merged chunk can also be merged with\n * its siblings, then we merge it, add the new larger chunk, and also remove the previously existing\n * sibling chunks.\n *\n * The maximum chunk size is represented by the `maxChunkSize` parameter (which has to be a power of\n * two). If no `maxChunkSize` parameter is provided, then there is no maxmimum chunk size, meaning\n * that chunks will be merged until no further merging is possible.\n *\n * `onAdd` and `onRemove` are called for each of the chunks that we add and remove to/from the\n * `existingChunks` map. `onAdd` will be called exactly once, whereas `onRemove` only ever be called\n * for sibling chunks that existed prior to this function being called.\n */\nexport function addToChunkMap(\n  existingChunks: Map<ChunkId, Chunk>,\n  newChunk: Chunk,\n  onAdd?: (arg: Chunk) => void,\n  onRemove?: (arg: Chunk) => void,\n  maxChunkSize?: number\n) {\n  let sideLength = newChunk.chunkFootprint.sideLength;\n  let chunkToAdd: Chunk = {\n    chunkFootprint: {\n      bottomLeft: newChunk.chunkFootprint.bottomLeft,\n      sideLength,\n    },\n    planetLocations: [...newChunk.planetLocations],\n    perlin: newChunk.perlin,\n  };\n  while (!maxChunkSize || sideLength < maxChunkSize) {\n    const siblingLocs = getSiblingLocations(chunkToAdd.chunkFootprint);\n    let siblingsMined = true;\n    for (const siblingLoc of siblingLocs) {\n      if (!existingChunks.get(getChunkKey(siblingLoc))) {\n        siblingsMined = false;\n        break;\n      }\n    }\n    if (!siblingsMined) break;\n    sideLength *= 2;\n    let planetLocations: WorldLocation[] = chunkToAdd.planetLocations;\n    let newPerlin = chunkToAdd.perlin / 4;\n    for (const siblingLoc of siblingLocs) {\n      const siblingKey = getChunkKey(siblingLoc);\n      const sibling = existingChunks.get(siblingKey);\n      if (onRemove !== undefined && sibling) {\n        onRemove(sibling);\n      } else {\n        existingChunks.delete(siblingKey);\n      }\n      if (sibling) {\n        planetLocations = planetLocations.concat(sibling.planetLocations);\n        newPerlin += sibling.perlin / 4;\n      }\n    }\n    const chunkFootprint = getChunkOfSideLengthContainingPoint(\n      chunkToAdd.chunkFootprint.bottomLeft,\n      sideLength\n    );\n    chunkToAdd = {\n      chunkFootprint,\n      planetLocations,\n      perlin: Math.floor(newPerlin * 1000) / 1000,\n    };\n  }\n  if (onAdd !== undefined) {\n    onAdd(chunkToAdd);\n  } else {\n    existingChunks.set(getChunkKey(chunkToAdd.chunkFootprint), chunkToAdd);\n  }\n}\n"
  },
  {
    "path": "src/Backend/Miner/MinerManager.ts",
    "content": "import { perlin } from '@darkforest_eth/hashing';\nimport { Chunk, PerlinConfig, Rectangle } from '@darkforest_eth/types';\nimport { EventEmitter } from 'events';\nimport _ from 'lodash';\nimport { ChunkStore } from '../../_types/darkforest/api/ChunkStoreTypes';\nimport { HashConfig, MinerWorkerMessage } from '../../_types/global/GlobalTypes';\nimport { getChunkKey } from './ChunkUtils';\nimport { MiningPattern } from './MiningPatterns';\n\nexport const enum MinerManagerEvent {\n  DiscoveredNewChunk = 'DiscoveredNewChunk',\n}\n\nexport type workerFactory = () => Worker;\n\nfunction defaultWorker() {\n  return new Worker(new URL('./miner.worker.ts', import.meta.url));\n}\n\nexport class HomePlanetMinerChunkStore implements ChunkStore {\n  private initPerlinMin: number;\n  private initPerlinMax: number;\n  private minedChunkKeys: Set<string>;\n  private perlinOptions: PerlinConfig;\n\n  constructor(initPerlinMin: number, initPerlinMax: number, hashConfig: HashConfig) {\n    this.initPerlinMin = initPerlinMin;\n    this.initPerlinMax = initPerlinMax;\n    this.minedChunkKeys = new Set<string>();\n    this.perlinOptions = {\n      key: hashConfig.spaceTypeKey,\n      scale: hashConfig.perlinLengthScale,\n      mirrorX: hashConfig.perlinMirrorX,\n      mirrorY: hashConfig.perlinMirrorY,\n      floor: false,\n    };\n  }\n\n  addChunk(exploredChunk: Chunk) {\n    this.minedChunkKeys.add(getChunkKey(exploredChunk.chunkFootprint));\n  }\n\n  hasMinedChunk(chunkFootprint: Rectangle) {\n    // return true if this chunk mined, or if perlin value >= threshold\n    if (this.minedChunkKeys.has(getChunkKey(chunkFootprint))) return true;\n    const center = {\n      x: chunkFootprint.bottomLeft.x + chunkFootprint.sideLength / 2,\n      y: chunkFootprint.bottomLeft.y + chunkFootprint.sideLength / 2,\n    };\n    const chunkPerlin = perlin(center, this.perlinOptions);\n    if (chunkPerlin >= this.initPerlinMax || chunkPerlin < this.initPerlinMin) return true;\n    return false;\n  }\n}\n\nclass MinerManager extends EventEmitter {\n  private readonly minedChunksStore: ChunkStore;\n  private readonly planetRarity: number;\n\n  private isExploring = false;\n  private miningPattern: MiningPattern;\n  private workers: Worker[];\n  private worldRadius: number;\n  private cores = 1;\n  // chunks we're exploring\n  private exploringChunk: { [chunkKey: string]: Chunk } = {};\n  // when we started exploring this chunk\n  private exploringChunkStart: { [chunkKey: string]: number } = {};\n  private minersComplete: { [chunkKey: string]: number } = {};\n  private currentJobId = 0;\n  private useMockHash: boolean;\n  private perlinOptions: PerlinConfig;\n  private hashConfig: HashConfig;\n  private workerFactory: workerFactory;\n\n  private constructor(\n    minedChunksStore: ChunkStore,\n    miningPattern: MiningPattern,\n    worldRadius: number,\n    planetRarity: number,\n    hashConfig: HashConfig,\n    useMockHash: boolean,\n    workerFactory: workerFactory\n  ) {\n    super();\n    this.minedChunksStore = minedChunksStore;\n    this.miningPattern = miningPattern;\n    this.worldRadius = worldRadius;\n    this.planetRarity = planetRarity;\n    this.workers = [];\n    this.hashConfig = hashConfig;\n    this.perlinOptions = {\n      key: hashConfig.spaceTypeKey,\n      scale: hashConfig.perlinLengthScale,\n      mirrorX: hashConfig.perlinMirrorX,\n      mirrorY: hashConfig.perlinMirrorY,\n      floor: false,\n    };\n    this.workerFactory = workerFactory;\n    this.useMockHash = useMockHash;\n  }\n\n  setMiningPattern(pattern: MiningPattern): void {\n    this.miningPattern = pattern;\n\n    if (this.isExploring) {\n      this.stopExplore();\n      this.startExplore();\n    }\n  }\n\n  getMiningPattern(): MiningPattern {\n    return this.miningPattern;\n  }\n\n  destroy(): void {\n    this.workers.map((x) => x.terminate());\n  }\n\n  static create(\n    chunkStore: ChunkStore,\n    miningPattern: MiningPattern,\n    worldRadius: number,\n    planetRarity: number,\n    hashConfig: HashConfig,\n    useMockHash = false,\n    workerFactory: workerFactory = defaultWorker\n  ): MinerManager {\n    const minerManager = new MinerManager(\n      chunkStore,\n      miningPattern,\n      worldRadius,\n      planetRarity,\n      hashConfig,\n      useMockHash,\n      workerFactory\n    );\n    _.range(minerManager.cores).forEach((i) => minerManager.initWorker(i));\n\n    return minerManager;\n  }\n\n  private initWorker(index: number): void {\n    this.workers[index] = this.workerFactory();\n    this.workers[index].onmessage = (e: MessageEvent) => {\n      // worker explored a slice of a chunk\n      const [exploredChunk, jobId] = JSON.parse(e.data) as [Chunk, number];\n      const chunkKey = this.chunkLocationToKey(exploredChunk.chunkFootprint, jobId);\n      this.exploringChunk[chunkKey].planetLocations.push(...exploredChunk.planetLocations);\n\n      this.minersComplete[chunkKey] += 1;\n      if (this.minersComplete[chunkKey] === this.workers.length) {\n        this.onDiscovered(this.exploringChunk[chunkKey], jobId);\n      }\n    };\n  }\n\n  private async onDiscovered(exploredChunk: Chunk, jobId: number): Promise<void> {\n    const discoveredLoc = exploredChunk.chunkFootprint;\n    const chunkKey = this.chunkLocationToKey(discoveredLoc, jobId);\n    const miningTimeMillis = Date.now() - this.exploringChunkStart[chunkKey];\n    this.emit(MinerManagerEvent.DiscoveredNewChunk, exploredChunk, miningTimeMillis);\n    delete this.exploringChunk[chunkKey];\n    delete this.minersComplete[chunkKey];\n    delete this.exploringChunkStart[chunkKey];\n\n    if (this.isExploring && this.currentJobId === jobId) {\n      this.exploreNext(discoveredLoc, jobId);\n    }\n  }\n\n  private exploreNext(fromChunk: Rectangle, jobId: number) {\n    this.nextValidExploreTarget(fromChunk, jobId).then((nextChunk: Rectangle | undefined) => {\n      if (!!nextChunk) {\n        const nextChunkKey = this.chunkLocationToKey(nextChunk, jobId);\n        const center = {\n          x: nextChunk.bottomLeft.x + nextChunk.sideLength / 2,\n          y: nextChunk.bottomLeft.y + nextChunk.sideLength / 2,\n        };\n        const centerPerlin = perlin(center, this.perlinOptions);\n        this.exploringChunk[nextChunkKey] = {\n          chunkFootprint: nextChunk,\n          planetLocations: [],\n          perlin: centerPerlin,\n        };\n        this.exploringChunkStart[nextChunkKey] = Date.now();\n        this.minersComplete[nextChunkKey] = 0;\n        this.sendMessageToWorkers(nextChunk, jobId);\n      }\n    });\n  }\n\n  public setCores(nCores: number): void {\n    const wasMining = this.isMining();\n    this.stopExplore();\n\n    this.workers.map((x) => x.terminate());\n    this.workers = [];\n\n    if (this.useMockHash) {\n      this.cores = 1;\n    } else {\n      this.cores = nCores;\n    }\n\n    _.range(this.cores).forEach((i) => this.initWorker(i));\n\n    if (wasMining) {\n      this.startExplore();\n    }\n  }\n\n  public startExplore(): void {\n    // increments the current job ID\n    if (!this.isExploring) {\n      this.isExploring = true;\n      this.currentJobId += 1;\n      const jobId = this.currentJobId;\n      this.exploreNext(this.miningPattern.fromChunk, jobId);\n    }\n  }\n\n  public stopExplore(): void {\n    this.isExploring = false;\n  }\n\n  public isMining(): boolean {\n    return this.isExploring;\n  }\n\n  public getCurrentlyExploringChunk(): Rectangle | undefined {\n    if (!this.isExploring) {\n      return undefined;\n    }\n\n    for (const key in this.exploringChunk) {\n      const res = this.chunkKeyToLocation(key);\n      if (res) {\n        const [chunkLocation, jobId] = res;\n        if (jobId === this.currentJobId) {\n          return chunkLocation;\n        }\n      }\n    }\n    return undefined;\n  }\n\n  public setRadius(radius: number): void {\n    this.worldRadius = radius;\n  }\n\n  private async nextValidExploreTarget(\n    chunkLocation: Rectangle,\n    jobId: number\n  ): Promise<Rectangle | undefined> {\n    // returns the first valid chunk equal to or after `chunk` (in the explore order of mining pattern) that hasn't been explored\n    // async because it may take indefinitely long to find the next target. this will block UI if done sync\n    // we use this trick to promisify:\n    // https://stackoverflow.com/questions/10344498/best-way-to-iterate-over-an-array-without-blocking-the-ui/10344560#10344560\n\n    // this function may return undefined if user chooses to stop exploring or changes mining pattern in the middle of its resolution\n    // so any function calling it should handle the undefined case appropriately\n    let candidateChunk = chunkLocation;\n    let count = 10000;\n    while (!this.isValidExploreTarget(candidateChunk) && count > 0) {\n      candidateChunk = this.miningPattern.nextChunk(candidateChunk);\n      count -= 1;\n    }\n    // since user might have switched jobs or stopped exploring during the above loop\n    if (!this.isExploring && jobId !== this.currentJobId) {\n      return undefined;\n    }\n    if (this.isValidExploreTarget(candidateChunk)) {\n      return candidateChunk;\n    }\n    return new Promise((resolve) => {\n      setTimeout(async () => {\n        const nextNextChunk = await this.nextValidExploreTarget(candidateChunk, jobId);\n        resolve(nextNextChunk);\n      }, 0);\n    });\n  }\n\n  private isValidExploreTarget(chunkLocation: Rectangle): boolean {\n    const { bottomLeft, sideLength } = chunkLocation;\n    const xCenter = bottomLeft.x + sideLength / 2;\n    const yCenter = bottomLeft.y + sideLength / 2;\n    const xMinAbs = Math.abs(xCenter) - sideLength / 2;\n    const yMinAbs = Math.abs(yCenter) - sideLength / 2;\n    const squareDist = xMinAbs ** 2 + yMinAbs ** 2;\n    // should be inbounds, and unexplored\n    return (\n      squareDist < this.worldRadius ** 2 && !this.minedChunksStore.hasMinedChunk(chunkLocation)\n    );\n  }\n\n  private sendMessageToWorkers(chunkToExplore: Rectangle, jobId: number): void {\n    for (let workerIndex = 0; workerIndex < this.workers.length; workerIndex += 1) {\n      const msg: MinerWorkerMessage = {\n        chunkFootprint: chunkToExplore,\n        workerIndex,\n        totalWorkers: this.workers.length,\n        jobId,\n        useMockHash: this.useMockHash,\n        ...this.hashConfig,\n      };\n      this.workers[workerIndex].postMessage(JSON.stringify(msg));\n    }\n  }\n\n  private chunkLocationToKey(chunkLocation: Rectangle, jobId: number) {\n    const x = chunkLocation.bottomLeft.x;\n    const y = chunkLocation.bottomLeft.y;\n    const sideLength = chunkLocation.sideLength;\n    return `${x},${y},${sideLength},${jobId}`;\n  }\n\n  private chunkKeyToLocation(chunkKey: string): [Rectangle, number] | undefined {\n    // returns chunk footprint and job id\n    try {\n      const [x, y, sideLength, jobId] = chunkKey.split(',').map((v) => parseInt(v));\n      return [\n        {\n          bottomLeft: { x, y },\n          sideLength,\n        },\n        jobId,\n      ];\n    } catch (e) {\n      console.error(`error while deserializing miner chunk key: ${e}`);\n      return undefined;\n    }\n  }\n}\n\nexport default MinerManager;\n"
  },
  {
    "path": "src/Backend/Miner/MiningPatterns.ts",
    "content": "import { Rectangle, WorldCoords } from '@darkforest_eth/types';\n\nexport const enum MiningPatternType {\n  Home,\n  Target,\n  Spiral,\n  Cone,\n  Grid,\n  ETH,\n  SwissCheese,\n  TowardsCenter,\n  TowardsCenterV2,\n}\n\nexport interface MiningPattern {\n  type: MiningPatternType;\n  fromChunk: Rectangle;\n  nextChunk: (prevLoc: Rectangle) => Rectangle;\n}\n\nexport class SpiralPattern implements MiningPattern {\n  type: MiningPatternType = MiningPatternType.Spiral;\n  fromChunk: Rectangle;\n  chunkSideLength: number;\n\n  constructor(center: WorldCoords, chunkSize: number) {\n    const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize;\n    const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize;\n    const bottomLeft = { x: bottomLeftX, y: bottomLeftY };\n    this.fromChunk = {\n      bottomLeft,\n      sideLength: chunkSize,\n    };\n    this.chunkSideLength = chunkSize;\n  }\n\n  nextChunk(chunk: Rectangle): Rectangle {\n    const homeX = this.fromChunk.bottomLeft.x;\n    const homeY = this.fromChunk.bottomLeft.y;\n    const currX = chunk.bottomLeft.x;\n    const currY = chunk.bottomLeft.y;\n\n    const nextBottomLeft = { x: currX, y: currY };\n\n    if (currX === homeX && currY === homeY) {\n      nextBottomLeft.y = homeY + this.chunkSideLength;\n    } else if (currY - currX > homeY - homeX && currY + currX >= homeX + homeY) {\n      if (currY + currX === homeX + homeY) {\n        // break the circle\n        nextBottomLeft.y = currY + this.chunkSideLength;\n      } else {\n        nextBottomLeft.x = currX + this.chunkSideLength;\n      }\n    } else if (currX + currY > homeX + homeY && currY - currX <= homeY - homeX) {\n      nextBottomLeft.y = currY - this.chunkSideLength;\n    } else if (currX + currY <= homeX + homeY && currY - currX < homeY - homeX) {\n      nextBottomLeft.x = currX - this.chunkSideLength;\n    } else {\n      // if (currX + currY < homeX + homeY && currY - currX >= homeY - homeX)\n      nextBottomLeft.y = currY + this.chunkSideLength;\n    }\n\n    return {\n      bottomLeft: nextBottomLeft,\n      sideLength: this.chunkSideLength,\n    };\n  }\n}\n\nexport class SwissCheesePattern implements MiningPattern {\n  type: MiningPatternType = MiningPatternType.SwissCheese;\n  fromChunk: Rectangle;\n  chunkSideLength: number;\n\n  constructor(center: WorldCoords, chunkSize: number) {\n    const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize;\n    const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize;\n    const bottomLeft = { x: bottomLeftX, y: bottomLeftY };\n    this.fromChunk = {\n      bottomLeft,\n      sideLength: chunkSize,\n    };\n    this.chunkSideLength = chunkSize;\n  }\n\n  nextChunk(chunk: Rectangle): Rectangle {\n    const homeX = this.fromChunk.bottomLeft.x;\n    const homeY = this.fromChunk.bottomLeft.y;\n    const currX = chunk.bottomLeft.x;\n    const currY = chunk.bottomLeft.y;\n\n    const nextBottomLeft = { x: currX, y: currY };\n\n    if (currX === homeX && currY === homeY) {\n      nextBottomLeft.y = homeY + this.chunkSideLength * 2;\n    } else if (currY - currX > homeY - homeX && currY + currX >= homeX + homeY) {\n      if (currY + currX === homeX + homeY) {\n        // break the circle\n        nextBottomLeft.y = currY + this.chunkSideLength * 2;\n      } else {\n        nextBottomLeft.x = currX + this.chunkSideLength * 2;\n      }\n    } else if (currX + currY > homeX + homeY && currY - currX <= homeY - homeX) {\n      nextBottomLeft.y = currY - this.chunkSideLength * 2;\n    } else if (currX + currY <= homeX + homeY && currY - currX < homeY - homeX) {\n      nextBottomLeft.x = currX - this.chunkSideLength * 2;\n    } else {\n      // if (currX + currY < homeX + homeY && currY - currX >= homeY - homeX)\n      nextBottomLeft.y = currY + this.chunkSideLength * 2;\n    }\n\n    return {\n      bottomLeft: nextBottomLeft,\n      sideLength: this.chunkSideLength,\n    };\n  }\n}\n\nexport class TowardsCenterPattern implements MiningPattern {\n  type: MiningPatternType = MiningPatternType.TowardsCenter;\n  fromChunk: Rectangle;\n  chunkSideLength: number;\n  private tipX: number;\n  private tipY: number;\n  private maxWidth = 1600;\n\n  constructor(center: WorldCoords, chunkSize: number) {\n    const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize;\n    const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize;\n    const bottomLeft = { x: bottomLeftX, y: bottomLeftY };\n    this.fromChunk = {\n      bottomLeft,\n      sideLength: chunkSize,\n    };\n    if (bottomLeftX < 0) {\n      this.tipX = bottomLeftX + chunkSize;\n    } else {\n      this.tipX = bottomLeftX - chunkSize;\n    }\n    if (bottomLeftY < 0) {\n      this.tipY = bottomLeftY + chunkSize;\n    } else {\n      this.tipY = bottomLeftY - chunkSize;\n    }\n    this.chunkSideLength = chunkSize;\n  }\n\n  nextChunk(chunk: Rectangle): Rectangle {\n    const homeX = this.fromChunk.bottomLeft.x;\n    const homeY = this.fromChunk.bottomLeft.y;\n    const currX = chunk.bottomLeft.x;\n    const currY = chunk.bottomLeft.y;\n\n    const absHomeX = Math.abs(homeX);\n    const absHomeY = Math.abs(homeY);\n    const absTipX = Math.abs(this.tipX);\n    const absTipY = Math.abs(this.tipY);\n    const absCurrX = Math.abs(currX);\n    const absCurrY = Math.abs(currY);\n\n    const endX =\n      currX <= 0\n        ? Math.max(homeX, this.tipX - this.maxWidth)\n        : Math.min(homeX, this.tipX + this.maxWidth);\n\n    const nextBottomLeft = {\n      x: currX,\n      y: currY,\n    };\n\n    if (currX === homeX && currY === homeY) {\n      nextBottomLeft.x = this.tipX;\n      nextBottomLeft.y = this.tipY;\n    } else if (currX === this.tipX && currY === this.tipY) {\n      if (currX < 0) {\n        nextBottomLeft.x = currX - this.chunkSideLength;\n      } else if (currX > 0) {\n        nextBottomLeft.x = currX + this.chunkSideLength;\n      } else {\n        // Exactly 0\n        if (homeX < 0) {\n          nextBottomLeft.x = currX - this.chunkSideLength;\n        } else {\n          nextBottomLeft.x = currX + this.chunkSideLength;\n        }\n      }\n    } else if (absCurrX < absHomeX && currY === this.tipY && absCurrX - absTipX < this.maxWidth) {\n      if (currX < 0) {\n        nextBottomLeft.x = currX - this.chunkSideLength;\n      } else if (currX > 0) {\n        nextBottomLeft.x = currX + this.chunkSideLength;\n      } else {\n        // Exactly 0\n        if (homeX < 0) {\n          nextBottomLeft.x = currX - this.chunkSideLength;\n        } else {\n          nextBottomLeft.x = currX + this.chunkSideLength;\n        }\n      }\n    } else if (currX === endX && currY === this.tipY) {\n      nextBottomLeft.x = this.tipX;\n      if (currY < 0) {\n        nextBottomLeft.y = currY - this.chunkSideLength;\n      } else if (currY > 0) {\n        nextBottomLeft.y = currY + this.chunkSideLength;\n      } else {\n        // Exactly 0\n        if (homeY < 0) {\n          nextBottomLeft.y = currY - this.chunkSideLength;\n        } else {\n          nextBottomLeft.y = currY + this.chunkSideLength;\n        }\n      }\n    } else if (currX === this.tipX && absCurrY < absHomeY && absCurrY - absTipY < this.maxWidth) {\n      if (currY < 0) {\n        nextBottomLeft.y = currY - this.chunkSideLength;\n      } else if (currY > 0) {\n        nextBottomLeft.y = currY + this.chunkSideLength;\n      } else {\n        // Exactly 0\n        if (homeY < 0) {\n          nextBottomLeft.y = currY - this.chunkSideLength;\n        } else {\n          nextBottomLeft.y = currY + this.chunkSideLength;\n        }\n      }\n    } else {\n      if (this.tipX < 0) {\n        this.tipX += this.chunkSideLength;\n      } else if (this.tipX > 0) {\n        this.tipX -= this.chunkSideLength;\n      } else {\n        this.tipX = 0;\n      }\n      if (this.tipY < 0) {\n        this.tipY += this.chunkSideLength;\n      } else if (this.tipY > 0) {\n        this.tipY -= this.chunkSideLength;\n      } else {\n        this.tipY = 0;\n      }\n      nextBottomLeft.x = this.tipX;\n      nextBottomLeft.y = this.tipY;\n    }\n\n    return {\n      bottomLeft: nextBottomLeft,\n      sideLength: this.chunkSideLength,\n    };\n  }\n}\n\nexport class TowardsCenterPatternV2 implements MiningPattern {\n  type: MiningPatternType = MiningPatternType.TowardsCenterV2;\n  fromChunk: Rectangle;\n  chunkSideLength: number;\n  private rowRadius: number;\n  private yDominant: boolean;\n  private slopeToCenter: number;\n\n  constructor(center: WorldCoords, chunkSize: number) {\n    const bottomLeftX = Math.floor(center.x / chunkSize) * chunkSize;\n    const bottomLeftY = Math.floor(center.y / chunkSize) * chunkSize;\n    const bottomLeft = { x: bottomLeftX, y: bottomLeftY };\n    this.fromChunk = {\n      bottomLeft,\n      sideLength: chunkSize,\n    };\n    this.chunkSideLength = chunkSize;\n    this.rowRadius = 5; // In chunks\n    this.yDominant = Math.abs(bottomLeftY) > Math.abs(bottomLeftX);\n    this.slopeToCenter = bottomLeftX === 0 ? 1 : bottomLeftY / bottomLeftX; // i.e. deltaY / deltaX\n  }\n\n  toChunk(coord: number): number {\n    return Math.floor(coord / this.chunkSideLength) * this.chunkSideLength;\n  }\n\n  nextChunk(chunk: Rectangle): Rectangle {\n    const homeX = this.fromChunk.bottomLeft.x;\n    const homeY = this.fromChunk.bottomLeft.y;\n    const currX = chunk.bottomLeft.x;\n    const currY = chunk.bottomLeft.y;\n\n    if (this.yDominant) {\n      const centerOfRowX = Math.floor(homeX + (currY - homeY) / this.slopeToCenter);\n      if (currX < centerOfRowX + this.chunkSideLength * (this.rowRadius - 1)) {\n        return {\n          bottomLeft: { x: currX + this.chunkSideLength, y: currY },\n          sideLength: this.chunkSideLength,\n        };\n      } else {\n        const nextCenterOfRowX = Math.floor(\n          centerOfRowX + this.chunkSideLength / this.slopeToCenter\n        );\n        return {\n          bottomLeft: {\n            x: this.toChunk(nextCenterOfRowX - (this.rowRadius - 1) * this.chunkSideLength),\n            y: currY < 0 ? currY + this.chunkSideLength : currY - this.chunkSideLength,\n          },\n          sideLength: this.chunkSideLength,\n        };\n      }\n    }\n\n    // We are now in the X dominant case\n    const centerOfRowY = Math.floor(homeY + (currX - homeX) * this.slopeToCenter);\n    if (currY < centerOfRowY + this.chunkSideLength * (this.rowRadius - 1)) {\n      return {\n        bottomLeft: { x: currX, y: currY + this.chunkSideLength },\n        sideLength: this.chunkSideLength,\n      };\n    } else {\n      const nextCenterOfRowY = Math.floor(centerOfRowY + this.chunkSideLength * this.slopeToCenter);\n      return {\n        bottomLeft: {\n          x: currX < 0 ? currX + this.chunkSideLength : currX - this.chunkSideLength,\n          y: this.toChunk(nextCenterOfRowY - (this.rowRadius - 1) * this.chunkSideLength),\n        },\n        sideLength: this.chunkSideLength,\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "src/Backend/Miner/miner.worker.ts",
    "content": "import { mimcHash, perlin } from '@darkforest_eth/hashing';\nimport { locationIdFromBigInt } from '@darkforest_eth/serde';\nimport { Chunk, PerlinConfig, Rectangle, WorldLocation } from '@darkforest_eth/types';\nimport * as bigInt from 'big-integer';\nimport { BigInteger } from 'big-integer';\nimport { LOCATION_ID_UB } from '../../Frontend/Utils/constants';\nimport { MinerWorkerMessage } from '../../_types/global/GlobalTypes';\nimport { getPlanetLocations } from './permutation';\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nconst ctx: Worker = self as any;\n/* eslint-enable @typescript-eslint/no-explicit-any */\n\nconst exploreChunk = (\n  chunkFootprint: Rectangle,\n  workerIndex: number,\n  totalWorkers: number,\n  planetRarity: number,\n  jobId: number,\n  useFakeHash: boolean,\n  planetHashKey: number,\n  spaceTypeKey: number,\n  biomebaseKey: number,\n  perlinLengthScale: number,\n  perlinMirrorX: boolean,\n  perlinMirrorY: boolean\n) => {\n  const planetHashFn = mimcHash(planetHashKey);\n  const spaceTypePerlinOpts: PerlinConfig = {\n    key: spaceTypeKey,\n    scale: perlinLengthScale,\n    mirrorX: perlinMirrorX,\n    mirrorY: perlinMirrorY,\n    floor: true,\n  };\n  const biomebasePerlinOpts: PerlinConfig = {\n    key: biomebaseKey,\n    scale: perlinLengthScale,\n    mirrorX: perlinMirrorX,\n    mirrorY: perlinMirrorY,\n    floor: true,\n  };\n\n  let planetLocations: WorldLocation[] = [];\n  if (useFakeHash) {\n    planetLocations =\n      workerIndex > 0\n        ? []\n        : getPlanetLocations(\n            spaceTypeKey,\n            biomebaseKey,\n            perlinLengthScale,\n            perlinMirrorY,\n            perlinMirrorY\n          )(chunkFootprint, planetRarity);\n  } else {\n    const planetRarityBI: BigInteger = bigInt(planetRarity);\n    let count = 0;\n    const { x: bottomLeftX, y: bottomLeftY } = chunkFootprint.bottomLeft;\n    const { sideLength } = chunkFootprint;\n    for (let x = bottomLeftX; x < bottomLeftX + sideLength; x++) {\n      for (let y = bottomLeftY; y < bottomLeftY + sideLength; y++) {\n        if (count % totalWorkers === workerIndex) {\n          const hash: BigInteger = planetHashFn(x, y);\n          if (hash.lesser(LOCATION_ID_UB.divide(planetRarityBI))) {\n            planetLocations.push({\n              coords: { x, y },\n              hash: locationIdFromBigInt(hash),\n              perlin: perlin({ x, y }, spaceTypePerlinOpts),\n              biomebase: perlin({ x, y }, biomebasePerlinOpts),\n            });\n          }\n        }\n        count += 1;\n      }\n    }\n  }\n  const chunkCenter = {\n    x: chunkFootprint.bottomLeft.x + chunkFootprint.sideLength / 2,\n    y: chunkFootprint.bottomLeft.y + chunkFootprint.sideLength / 2,\n  };\n  const chunkData: Chunk = {\n    chunkFootprint,\n    planetLocations,\n    perlin: perlin(chunkCenter, { ...spaceTypePerlinOpts, floor: false }),\n  };\n  ctx.postMessage(JSON.stringify([chunkData, jobId]));\n};\n\nctx.addEventListener('message', (e: MessageEvent) => {\n  const exploreMessage: MinerWorkerMessage = JSON.parse(e.data) as MinerWorkerMessage;\n\n  exploreChunk(\n    exploreMessage.chunkFootprint,\n    exploreMessage.workerIndex,\n    exploreMessage.totalWorkers,\n    exploreMessage.planetRarity,\n    exploreMessage.jobId,\n    exploreMessage.useMockHash,\n    exploreMessage.planetHashKey,\n    exploreMessage.spaceTypeKey,\n    exploreMessage.biomebaseKey,\n    exploreMessage.perlinLengthScale,\n    exploreMessage.perlinMirrorX,\n    exploreMessage.perlinMirrorY\n  );\n});\n"
  },
  {
    "path": "src/Backend/Miner/permutation.ts",
    "content": "import { fakeHash, perlin, seededRandom } from '@darkforest_eth/hashing';\nimport { locationIdFromBigInt } from '@darkforest_eth/serde';\nimport { Rectangle, WorldCoords, WorldLocation } from '@darkforest_eth/types';\n\ntype IdxWithRand = {\n  idx: number;\n  rand: number;\n};\n\nconst SIZE = 65536; // we permute 256x256 grids of 256x256 mega-chunks\nlet globalSeed = 1;\n\nconst globalRandom = () => {\n  return seededRandom(globalSeed++);\n};\n\nconst arr: IdxWithRand[] = [];\nfor (let i = 0; i < SIZE; i += 1) {\n  arr.push({\n    idx: i,\n    rand: globalRandom(),\n  });\n}\narr.sort((a, b) => a.rand - b.rand);\nconst lookup = arr.map((a) => a.idx);\nconst lookupInv = Array(SIZE).fill(0);\nfor (let i = 0; i < SIZE; i += 1) {\n  lookupInv[lookup[i]] = i;\n}\n\n// return the number in [0, n) congruent to m (mod n)\nconst posMod = (m: number, n: number) => {\n  const val = Math.floor(m / n) * n;\n  return m - val;\n};\n\n// permutation by lookup table\nconst sigma = (x: number, y: number) => {\n  const val = 256 * x + y;\n  const idx = posMod(val, SIZE);\n  const ret: [number, number] = [Math.floor(lookup[idx] / 256), lookup[idx] % 256];\n  return ret;\n};\n\nconst sigmaInv = (x: number, y: number) => {\n  const val = 256 * x + y;\n  const idx = posMod(val, SIZE);\n  const ret: [number, number] = [Math.floor(lookupInv[idx] / 256), lookupInv[idx] % 256];\n  return ret;\n};\n\n// cyclic permutation\nconst cyc = (m: number, n: number) => (r: number, s: number) => {\n  const val = posMod(256 * (r + m) + (s + n), SIZE);\n  const ret: [number, number] = [Math.floor(val / 256), val % 256];\n  return ret;\n};\n\nconst cycInv = (m: number, n: number) => (r: number, s: number) => {\n  return cyc(-m, -n)(r, s);\n};\n\nexport const getPlanetLocations =\n  (\n    spaceTypeKey: number,\n    biomebaseKey: number,\n    perlinLengthScale: number,\n    perlinMirrorX: boolean,\n    perlinMirrorY: boolean\n  ) =>\n  (chunkFootprint: Rectangle, planetRarity: number) => {\n    // assume that the chunkFootprint is entirely contained within a 256x256 grid square\n    const { bottomLeft, sideLength } = chunkFootprint;\n    const { x, y } = bottomLeft;\n    const m = Math.floor(x / 256);\n    const n = Math.floor(y / 256);\n    const [mPrime, nPrime] = sigma(m, n);\n    const postImages: [number, number][] = [];\n    for (let i = 0; i < SIZE / planetRarity; i += 1) {\n      postImages.push([Math.floor(i / 256), i % 256]);\n    }\n    const preImages: [number, number][] = [];\n    for (const postImage of postImages) {\n      preImages.push(sigmaInv(...cycInv(mPrime, nPrime)(...sigmaInv(postImage[0], postImage[1]))));\n    }\n    const coords: WorldCoords[] = preImages.map((preImage) => ({\n      x: m * 256 + preImage[0],\n      y: n * 256 + preImage[1],\n    }));\n\n    const locs: WorldLocation[] = coords\n      .filter(\n        (coords) =>\n          coords.x - bottomLeft.x < sideLength &&\n          coords.x >= bottomLeft.x &&\n          coords.y - bottomLeft.y < sideLength &&\n          coords.y >= bottomLeft.y\n      )\n      .map((coords) => ({\n        coords,\n        hash: locationIdFromBigInt(fakeHash(planetRarity)(coords.x, coords.y)),\n        perlin: perlin(coords, {\n          key: spaceTypeKey,\n          scale: perlinLengthScale,\n          mirrorX: perlinMirrorX,\n          mirrorY: perlinMirrorY,\n          floor: true,\n        }),\n        biomebase: perlin(coords, {\n          key: biomebaseKey,\n          scale: perlinLengthScale,\n          mirrorX: perlinMirrorX,\n          mirrorY: perlinMirrorY,\n          floor: true,\n        }),\n      }));\n\n    return locs;\n  };\n"
  },
  {
    "path": "src/Backend/Network/AccountManager.ts",
    "content": "import { address } from '@darkforest_eth/serde';\nimport { EthAddress } from '@darkforest_eth/types';\nimport { utils } from 'ethers';\nimport stringify from 'json-stable-stringify';\n\n/**\n * Represents an account with which the user plays the game.\n */\nexport interface Account {\n  address: EthAddress;\n  privateKey: string;\n}\n\n/**\n * This is the key in local storage in which we keep an array of all the public addresses of the\n * accounts that have been imported/generated into this client.\n */\nconst ADDRESS_LOCAL_STORAGE_KEY = 'KNOWN_ADDRESSES';\n\n/**\n * In-memory representation of all the accounts in this client.\n */\nconst accounts: Account[] = load();\n\n/**\n * Store all of the accounts in local storage.\n */\nfunction save() {\n  localStorage.setItem(\n    ADDRESS_LOCAL_STORAGE_KEY,\n    stringify(accounts.map((account) => account.address))\n  );\n\n  for (const account of accounts) {\n    localStorage.setItem(`skey-${account.address}`, account.privateKey);\n  }\n}\n\n/**\n * Load all of the accounts from local storage.\n */\nfunction load(): Account[] {\n  const knownAddresses: EthAddress[] = [];\n  const accounts: Account[] = [];\n\n  // first we load the public addresses\n  const serializedAddresses = localStorage.getItem(ADDRESS_LOCAL_STORAGE_KEY);\n  if (serializedAddresses !== null) {\n    const addresses = JSON.parse(serializedAddresses) as string[];\n    for (const addressStr of addresses) {\n      knownAddresses.push(address(addressStr));\n    }\n  }\n\n  // then we load the private keys\n  for (const addy of knownAddresses) {\n    const skey = localStorage.getItem(`skey-${addy}`);\n\n    if (skey !== null) {\n      accounts.push({\n        address: addy,\n        privateKey: skey,\n      });\n    }\n  }\n\n  return accounts;\n}\n\n/**\n * Returns the list of accounts that are logged into the game.\n */\nexport function getAccounts(): Account[] {\n  return [...accounts];\n}\n\n/**\n * Adds the given account, and saves it to localstorage.\n */\nexport function addAccount(privateKey: string) {\n  accounts.push({\n    address: address(utils.computeAddress(privateKey)),\n    privateKey,\n  });\n\n  save();\n}\n"
  },
  {
    "path": "src/Backend/Network/Blockchain.ts",
    "content": "// These are loaded as URL paths by a webpack loader\nimport diamondContractAbiUrl from '@darkforest_eth/contracts/abis/DarkForest.json';\nimport { createContract, createEthConnection, EthConnection } from '@darkforest_eth/network';\nimport type { Contract, providers, Wallet } from 'ethers';\n\n/**\n * Loads the game contract, which is responsible for updating the state of the game.\n */\nexport async function loadDiamondContract<T extends Contract>(\n  address: string,\n  provider: providers.JsonRpcProvider,\n  signer?: Wallet\n): Promise<T> {\n  const abi = await fetch(diamondContractAbiUrl).then((r) => r.json());\n\n  return createContract<T>(address, abi, provider, signer);\n}\n\nexport function getEthConnection(): Promise<EthConnection> {\n  const isProd = process.env.NODE_ENV === 'production';\n  const defaultUrl = process.env.DEFAULT_RPC as string;\n\n  let url: string;\n\n  if (isProd) {\n    url = localStorage.getItem('XDAI_RPC_ENDPOINT_v5') || defaultUrl;\n  } else {\n    url = 'http://localhost:8545';\n  }\n\n  console.log(`GAME METADATA:`);\n  console.log(`rpc url: ${url}`);\n  console.log(`is production: ${isProd}`);\n  console.log(`webserver url: ${process.env.DF_WEBSERVER_URL}`);\n\n  return createEthConnection(url);\n}\n"
  },
  {
    "path": "src/Backend/Network/EventLogger.ts",
    "content": "export const enum EventType {\n  Transaction = 'transaction',\n  Diagnostics = 'diagnostics',\n}\n\nexport class EventLogger {\n  private static augmentEvent(event: unknown, eventType: EventType) {\n    return Object.assign(event, { df_event_type: eventType });\n  }\n\n  logEvent(eventType: EventType, event: unknown) {\n    if (!process.env.DF_WEBSERVER_URL) {\n      return;\n    }\n\n    fetch(`${process.env.DF_WEBSERVER_URL}/event`, {\n      method: 'POST',\n      body: JSON.stringify(EventLogger.augmentEvent(event, eventType)),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    }).catch((err) => console.log(err));\n  }\n}\n\nexport const eventLogger = new EventLogger();\n"
  },
  {
    "path": "src/Backend/Network/LeaderboardApi.ts",
    "content": "import { Leaderboard } from '@darkforest_eth/types';\n\nexport async function loadLeaderboard(): Promise<Leaderboard> {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return { entries: [] };\n  }\n\n  const address = `${process.env.DF_WEBSERVER_URL}/leaderboard`;\n  const res = await fetch(address, {\n    method: 'GET',\n  });\n\n  const rep = await res.json();\n\n  if (rep.error) {\n    throw new Error(rep.error);\n  }\n\n  return rep;\n}\n"
  },
  {
    "path": "src/Backend/Network/MessageAPI.ts",
    "content": "import {\n  DeleteMessagesRequest,\n  PlanetMessageRequest,\n  PlanetMessageResponse,\n  PostMessageRequest,\n  SignedMessage,\n} from '@darkforest_eth/types';\n\nexport async function getMessagesOnPlanets(\n  request: PlanetMessageRequest\n): Promise<PlanetMessageResponse> {\n  if (request.planets.length === 0 || !process.env.DF_WEBSERVER_URL) {\n    return {};\n  }\n\n  try {\n    const response = await fetch(`${process.env.DF_WEBSERVER_URL}/messages`, {\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(request),\n      method: 'POST',\n    });\n    const responseBody = (await response.json()) as PlanetMessageResponse;\n    if (response.status === 500) {\n      throw new Error('failed to load messages');\n    }\n    return responseBody;\n  } catch (e) {\n    throw e;\n  }\n}\n\nexport async function addMessage(\n  request: SignedMessage<PostMessageRequest<unknown>>\n): Promise<void> {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return;\n  }\n\n  try {\n    const res = await fetch(`${process.env.DF_WEBSERVER_URL}/add-message`, {\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(request),\n      method: 'POST',\n    });\n\n    if (res.status === 500) {\n      throw new Error('server error');\n    }\n  } catch (e) {\n    console.log('failed to add message', request);\n    console.log(e);\n  }\n}\n\nexport async function deleteMessages(request: SignedMessage<DeleteMessagesRequest>): Promise<void> {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return;\n  }\n\n  try {\n    const res = await fetch(`${process.env.DF_WEBSERVER_URL}/delete-messages`, {\n      headers: {\n        'Content-Type': 'application/json',\n      },\n      body: JSON.stringify(request),\n      method: 'POST',\n    });\n\n    if (res.status === 500) {\n      throw new Error('server error');\n    }\n  } catch (e) {\n    console.log('failed delete messages', request);\n    console.log(e);\n  }\n}\n"
  },
  {
    "path": "src/Backend/Network/NetworkHealthApi.ts",
    "content": "import { NetworkHealthSummary } from '@darkforest_eth/types';\n\n/**\n * The Dark Forest webserver keeps track of network health, this function loads that information\n * from the webserver.\n */\nexport async function loadNetworkHealth(): Promise<NetworkHealthSummary> {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return [];\n  }\n\n  const result = await fetch(`${process.env.DF_WEBSERVER_URL}/network-health`, {\n    method: 'POST',\n    headers: {\n      'Content-Type': 'application/json',\n    },\n  }).then((x) => x.json());\n\n  return result as NetworkHealthSummary;\n}\n"
  },
  {
    "path": "src/Backend/Network/UtilityServerAPI.ts",
    "content": "import {\n  EthAddress,\n  RegisterResponse,\n  SignedMessage,\n  WhitelistStatusResponse,\n} from '@darkforest_eth/types';\nimport * as EmailValidator from 'email-validator';\nimport timeout from 'p-timeout';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\nimport { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes';\n\nexport const enum EmailResponse {\n  Success,\n  Invalid,\n  ServerError,\n}\n\nexport const submitInterestedEmail = async (email: string): Promise<EmailResponse> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return EmailResponse.ServerError;\n  }\n\n  if (!EmailValidator.validate(email)) {\n    return EmailResponse.Invalid;\n  }\n  const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/interested`, {\n    method: 'POST',\n    body: JSON.stringify({ email }),\n    headers: {\n      'Content-Type': 'application/json',\n    },\n  }).then((x) => x.json());\n\n  return success ? EmailResponse.Success : EmailResponse.ServerError;\n};\n\nexport const submitUnsubscribeEmail = async (email: string): Promise<EmailResponse> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return EmailResponse.ServerError;\n  }\n\n  if (!EmailValidator.validate(email)) {\n    return EmailResponse.Invalid;\n  }\n  const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/unsubscribe`, {\n    method: 'POST',\n    body: JSON.stringify({ email }),\n    headers: {\n      'Content-Type': 'application/json',\n    },\n  }).then((x) => x.json());\n\n  return success ? EmailResponse.Success : EmailResponse.ServerError;\n};\n\nexport const submitPlayerEmail = async (\n  request?: SignedMessage<{ email: string }>\n): Promise<EmailResponse> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return EmailResponse.ServerError;\n  }\n\n  if (!request || !EmailValidator.validate(request.message.email)) {\n    return EmailResponse.Invalid;\n  }\n\n  const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/email/playing`, {\n    method: 'POST',\n    body: JSON.stringify(request),\n    headers: {\n      'Content-Type': 'application/json',\n    },\n  }).then((x) => x.json());\n\n  return success ? EmailResponse.Success : EmailResponse.ServerError;\n};\n\nasync function sleep(timeoutMs: number) {\n  return new Promise<void>((resolve) => {\n    setTimeout(() => resolve(), timeoutMs);\n  });\n}\n\nexport type RegisterConfirmationResponse = {\n  /**\n   * If the whitelist registration is successful,\n   * this is populated with the hash of the\n   * transaction.\n   */\n  txHash?: string;\n  /**\n   * If the whitelist registration is unsuccessful,\n   * this is populated with the error message explaining\n   * why.\n   */\n  errorMessage?: string;\n  /**\n   * If the whitelist registration is unsuccessful, this\n   * is true if the client is able to retry registration.\n   */\n  canRetry?: boolean;\n};\n\n/**\n * Starts the registration process for the user then\n * polls for success.\n */\nexport async function callRegisterAndWaitForConfirmation(\n  key: string,\n  address: EthAddress,\n  terminal: React.MutableRefObject<TerminalHandle | undefined>\n): Promise<RegisterConfirmationResponse> {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return { errorMessage: 'Cannot connect to server.', canRetry: false };\n  }\n\n  const response = await submitWhitelistKey(key, address);\n\n  if (response?.error) {\n    return { errorMessage: response.error, canRetry: false };\n  }\n\n  while (true) {\n    const statusResponse = await whitelistStatus(address);\n    if (!statusResponse) return { errorMessage: 'Cannot connect to server.', canRetry: false };\n\n    terminal.current?.newline();\n    if (statusResponse.failedAt) {\n      return { errorMessage: 'Transaction failed.', canRetry: true };\n    } else if (statusResponse.txHash) {\n      return { txHash: statusResponse.txHash };\n    } else if (statusResponse.position) {\n      if (statusResponse.position !== '0') {\n        terminal.current?.print('Position in queue: ' + statusResponse.position + '\\n');\n      } else {\n        terminal.current?.print('Position in queue: You are up next!');\n      }\n    } else {\n      terminal.current?.print('Entering queue...');\n    }\n\n    await sleep(3000);\n  }\n}\n\nexport const whitelistStatus = async (\n  address: EthAddress\n): Promise<WhitelistStatusResponse | null> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return null;\n  }\n\n  return await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/address/${address}/isWhitelisted`, {\n    method: 'GET',\n    headers: {\n      'Content-Type': 'application/json',\n    },\n  }).then((x) => x.json());\n};\n\n/**\n * Submits a whitelist key to register the given player to the game. Returns null if there was an\n * error.\n */\nexport const submitWhitelistKey = async (\n  key: string,\n  address: EthAddress\n): Promise<RegisterResponse | null> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return null;\n  }\n\n  try {\n    return await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/register`, {\n      method: 'POST',\n      body: JSON.stringify({\n        key,\n        address,\n      }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    }).then((x) => x.json());\n  } catch (e) {\n    console.error(`error when registering for whitelist: ${e}`);\n    return null;\n  }\n};\n\nexport const requestDevFaucet = async (address: EthAddress): Promise<boolean> => {\n  if (!process.env.DF_WEBSERVER_URL) {\n    return false;\n  }\n\n  // TODO: Provide own env variable for this feature\n  if (process.env.NODE_ENV === 'production') {\n    return false;\n  }\n\n  try {\n    const { success } = await fetch(`${process.env.DF_WEBSERVER_URL}/whitelist/faucet`, {\n      method: 'POST',\n      body: JSON.stringify({\n        address,\n      }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    }).then((x) => x.json());\n\n    return success;\n  } catch (e) {\n    console.error(`error when requesting drip: ${e}`);\n    return false;\n  }\n};\n\n/**\n * Swallows all errors. Either loads the address to twitter map from the webserver in 5 seconds, or\n * returan empty map.\n */\nexport const tryGetAllTwitters = async (): Promise<AddressTwitterMap> => {\n  try {\n    return await timeout(getAllTwitters(), 1000 * 5, \"couldn't get twitter map\");\n  } catch (e) {}\n  return {};\n};\n\nexport const getAllTwitters = async (): Promise<AddressTwitterMap> => {\n  try {\n    const twitterMap: AddressTwitterMap = await fetch(\n      `${process.env.DF_WEBSERVER_URL}/twitter/all-twitters`\n    ).then((x) => x.json());\n    return twitterMap;\n  } catch (e) {\n    return {};\n  }\n};\n\nexport const verifyTwitterHandle = async (\n  verifyMessage: SignedMessage<{ twitter: string }>\n): Promise<boolean> => {\n  try {\n    const res = await fetch(`${process.env.DF_WEBSERVER_URL}/twitter/verify-twitter`, {\n      method: 'POST',\n      body: JSON.stringify({\n        verifyMessage,\n      }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    }).then((x) => x.json());\n\n    return res.success;\n  } catch (e) {\n    console.error(`error when verifying twitter handle: ${e}`);\n    return false;\n  }\n};\n\nexport const disconnectTwitter = async (\n  disconnectMessage: SignedMessage<{ twitter: string }>\n): Promise<boolean> => {\n  try {\n    const res = await fetch(`${process.env.DF_WEBSERVER_URL}/twitter/disconnect`, {\n      method: 'POST',\n      body: JSON.stringify({\n        disconnectMessage,\n      }),\n      headers: {\n        'Content-Type': 'application/json',\n      },\n    }).then((x) => x.json());\n\n    return res.success;\n  } catch (e) {\n    console.error(`error when disconnecting twitter handle: ${e}`);\n    return false;\n  }\n};\n"
  },
  {
    "path": "src/Backend/Plugins/EmbeddedPluginLoader.ts",
    "content": "import { PluginId } from '@darkforest_eth/types';\n\n/**\n * This interface represents an embedded plugin, which is stored in `embedded_plugins/`.\n */\nexport interface EmbeddedPlugin {\n  id: PluginId;\n  name: string;\n  code: string;\n}\n\n/**\n * Load all of the embedded plugins in the dist directory of the `embedded_plugins/` project\n * as Plain Text files. This means that `embedded_plugins/` can't use `import` for relative paths.\n */\nconst pluginsContext = require.context('../../../embedded_plugins/', false, /\\.[jt]sx?$/);\n\nfunction cleanFilename(filename: string) {\n  return filename\n    .replace(/^\\.\\//, '')\n    .replace(/[_-]/g, ' ')\n    .replace(/\\.[jt]sx?$/, '');\n}\n\nexport function getEmbeddedPlugins(isAdmin: boolean) {\n  return pluginsContext\n    .keys()\n    .filter((filename) => {\n      if (isAdmin) {\n        return true;\n      } else {\n        return !filename.startsWith('./Admin-Controls');\n      }\n    })\n    .map((filename) => {\n      return {\n        id: filename as PluginId,\n        name: cleanFilename(filename),\n        code: pluginsContext<{ default: string }>(filename).default,\n      };\n    });\n}\n"
  },
  {
    "path": "src/Backend/Plugins/PluginProcess.ts",
    "content": "/**\n * All plugins must conform to this interface. Provides facilities for\n * displaying an interactive UI, as well as references to game state,\n * which are set externally.\n */\nexport interface PluginProcess {\n  new (): this;\n\n  /**\n   * If present, called once when the user clicks 'run' in the plugin\n   * manager modal.\n   */\n  render?: (into: HTMLDivElement) => void;\n\n  /**\n   * If present, called at the same framerate the the game is running at,\n   * and allows you to draw on top of the game UI.\n   */\n  draw?: (ctx: CanvasRenderingContext2D) => void;\n\n  /**\n   * Called when the plugin is unloaded. Plugins unload whenever the\n   * plugin is edited (modified and saved, or deleted).\n   */\n  destroy?: () => void;\n}\n"
  },
  {
    "path": "src/Backend/Plugins/PluginTemplate.ts",
    "content": "import dedent from 'ts-dedent';\n\nexport const PLUGIN_TEMPLATE = dedent`\n  /**\n   * Remember, you have access these globals:\n   * 1. df - Just like the df object in your console.\n   * 2. ui - For interacting with the game's user interface.\n   *\n   * Let's log these to the console when you run your plugin!\n   */\n  console.log(df, ui);\n\n  class Plugin {\n    constructor() {}\n\n    /**\n     * Called when plugin is launched with the \"run\" button.\n     */\n    async render(container) {}\n\n    /**\n     * Called when plugin modal is closed.\n     */\n    destroy() {}\n  }\n\n  /**\n   * And don't forget to export it!\n   */\n  export default Plugin;\n`;\n"
  },
  {
    "path": "src/Backend/Plugins/SerializedPlugin.ts",
    "content": "import { PluginId } from '@darkforest_eth/types';\n\n/**\n * Represents a plugin that the user has added to their game. Used\n * internally for storing plugins. Not used for evaluating plugins!\n */\nexport interface SerializedPlugin {\n  /**\n   * Unique ID, assigned at the time the plugin is first saved.\n   */\n  id: PluginId;\n\n  /**\n   * This code is a javascript object that complies with the\n   * {@link PluginProcess} interface.\n   */\n  code: string;\n\n  /**\n   * Shown in the list of plugins.\n   */\n  name: string;\n\n  /**\n   * {@code new Date.getTime()} at the point that this plugin was saved\n   */\n  lastEdited: number;\n}\n"
  },
  {
    "path": "src/Backend/Storage/PersistentChunkStore.ts",
    "content": "import {\n  Chunk,\n  ClaimedCoords,\n  DiagnosticUpdater,\n  EthAddress,\n  LocationId,\n  ModalId,\n  ModalPosition,\n  PersistedTransaction,\n  Rectangle,\n  RevealedCoords,\n  Transaction,\n  WorldLocation,\n} from '@darkforest_eth/types';\nimport { IDBPDatabase, openDB } from 'idb';\nimport stringify from 'json-stable-stringify';\nimport _ from 'lodash';\nimport { MAX_CHUNK_SIZE } from '../../Frontend/Utils/constants';\nimport { ChunkId, ChunkStore, PersistedChunk } from '../../_types/darkforest/api/ChunkStoreTypes';\nimport {\n  addToChunkMap,\n  getChunkKey,\n  getChunkOfSideLengthContainingPoint,\n  toExploredChunk,\n  toPersistedChunk,\n} from '../Miner/ChunkUtils';\nimport { SerializedPlugin } from '../Plugins/SerializedPlugin';\n\nconst enum ObjectStore {\n  DEFAULT = 'default',\n  BOARD = 'knownBoard',\n  UNCONFIRMED_ETH_TXS = 'unminedEthTxs',\n  PLUGINS = 'plugins',\n  /**\n   * Store modal positions so that we can keep modal panes open across sessions.\n   */\n  MODAL_POS = 'modalPositions',\n}\n\nconst enum DBActionType {\n  UPDATE,\n  DELETE,\n}\n\ninterface DBAction<T extends string> {\n  type: DBActionType;\n  dbKey: T;\n  dbValue?: Chunk;\n}\n\ntype DBTx = DBAction<ChunkId | string>[];\n\ninterface DebouncedFunc<T extends () => void> {\n  (...args: Parameters<T>): ReturnType<T> | undefined;\n  cancel(): void;\n}\n\ninterface PersistentChunkStoreConfig {\n  db: IDBPDatabase;\n  contractAddress: EthAddress;\n  account: EthAddress;\n}\n\nexport const MODAL_POSITIONS_KEY = 'modal_positions';\n\nclass PersistentChunkStore implements ChunkStore {\n  private diagnosticUpdater?: DiagnosticUpdater;\n  private db: IDBPDatabase;\n  private queuedChunkWrites: DBTx[];\n  private throttledSaveChunkCacheToDisk: DebouncedFunc<() => Promise<void>>;\n  private nUpdatesLastTwoMins = 0; // we save every 5s, unless this goes above 50\n  private chunkMap: Map<ChunkId, Chunk>;\n  private confirmedTxHashes: Set<string>;\n  private account: EthAddress;\n  private contractAddress: EthAddress;\n\n  constructor({ db, account, contractAddress }: PersistentChunkStoreConfig) {\n    this.db = db;\n    this.queuedChunkWrites = [];\n    this.confirmedTxHashes = new Set<string>();\n    this.throttledSaveChunkCacheToDisk = _.throttle(\n      this.persistQueuedChunks,\n      2000 // TODO\n    );\n    this.chunkMap = new Map();\n    this.account = account;\n    this.contractAddress = contractAddress;\n  }\n\n  destroy(): void {\n    // no-op; we don't actually destroy the instance, we leave the db connection open in case we need it in the future\n  }\n\n  /**\n   * NOTE! if you're creating a new object store, it will not be *added* to existing dark forest\n   * accounts. This creation code runs once per account. Therefore, if you're adding a new object\n   * store, and need to test it out, you must either clear the indexed db databse for this account,\n   * or create a brand new account.\n   */\n  static async create({\n    account,\n    contractAddress,\n  }: Omit<PersistentChunkStoreConfig, 'db'>): Promise<PersistentChunkStore> {\n    const db = await openDB(`darkforest-${contractAddress}-${account}`, 1, {\n      upgrade(db) {\n        db.createObjectStore(ObjectStore.DEFAULT);\n        db.createObjectStore(ObjectStore.BOARD);\n        db.createObjectStore(ObjectStore.UNCONFIRMED_ETH_TXS);\n        db.createObjectStore(ObjectStore.PLUGINS);\n        db.createObjectStore(ObjectStore.MODAL_POS);\n      },\n    });\n\n    const localStorageManager = new PersistentChunkStore({ db, account, contractAddress });\n\n    await localStorageManager.loadChunks();\n\n    return localStorageManager;\n  }\n\n  public setDiagnosticUpdater(diagnosticUpdater?: DiagnosticUpdater) {\n    this.diagnosticUpdater = diagnosticUpdater;\n  }\n\n  /**\n   * Important! This sets the key in indexed db per account and per contract. This means the same\n   * client can connect to multiple different dark forest contracts, with multiple different\n   * accounts, and the persistent storage will not overwrite data that is not relevant for the\n   * current configuration of the client.\n   */\n  private async getKey(\n    key: string,\n    objStore: ObjectStore = ObjectStore.DEFAULT\n  ): Promise<string | undefined> {\n    return await this.db.get(objStore, `${this.contractAddress}-${this.account}-${key}`);\n  }\n\n  /**\n   * Important! This sets the key in indexed db per account and per contract. This means the same\n   * client can connect to multiple different dark forest contracts, with multiple different\n   * accounts, and the persistent storage will not overwrite data that is not relevant for the\n   * current configuration of the client.\n   */\n  private async setKey(\n    key: string,\n    value: string,\n    objStore: ObjectStore = ObjectStore.DEFAULT\n  ): Promise<void> {\n    await this.db.put(objStore, value, `${this.contractAddress}-${this.account}-${key}`);\n  }\n\n  private async removeKey(key: string, objStore: ObjectStore = ObjectStore.DEFAULT): Promise<void> {\n    await this.db.delete(objStore, `${this.contractAddress}-${this.account}-${key}`);\n  }\n\n  private async bulkSetKeyInCollection(\n    updateChunkTxs: DBTx[],\n    collection: ObjectStore\n  ): Promise<void> {\n    const tx = this.db.transaction(collection, 'readwrite');\n    updateChunkTxs.forEach((updateChunkTx) => {\n      updateChunkTx.forEach(({ type, dbKey: key, dbValue: value }) => {\n        if (type === DBActionType.UPDATE) {\n          tx.store.put(toPersistedChunk(value as Chunk), key);\n        } else if (type === DBActionType.DELETE) {\n          tx.store.delete(key);\n        }\n      });\n    });\n    await tx.done;\n  }\n\n  /**\n   * This function loads all chunks persisted in the user's storage into the game.\n   */\n  private async loadChunks(): Promise<void> {\n    // we can't bulk get all chunks, since idb will crash/hang\n    // we also can't assign random non-primary keys and query on ranges\n    // so we append a random alphanumeric character to the front of keys\n    // and then bulk query for keys starting with 0, then 1, then 2, etc.\n    // see the `getBucket` function in `ChunkUtils.ts` for more information.\n    const borders = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ~';\n    let chunkCount = 0;\n\n    for (let idx = 0; idx < borders.length - 1; idx += 1) {\n      const bucketOfChunks = await this.db.getAll(\n        ObjectStore.BOARD,\n        IDBKeyRange.bound(borders[idx], borders[idx + 1], false, true)\n      );\n\n      bucketOfChunks.forEach((chunk: PersistedChunk) => {\n        this.addChunk(toExploredChunk(chunk), false);\n      });\n\n      chunkCount += bucketOfChunks.length;\n    }\n\n    console.log(`loaded ${chunkCount} chunks from local storage`);\n  }\n\n  /**\n   * Rather than saving a chunk immediately after it's mined, we queue up new chunks, and\n   * periodically save them. This function gets all of the queued new chunks, and persists them to\n   * indexed db.\n   */\n  private async persistQueuedChunks() {\n    const toSave = [...this.queuedChunkWrites]; // make a copy\n    this.queuedChunkWrites = [];\n    this.diagnosticUpdater &&\n      this.diagnosticUpdater.updateDiagnostics((d) => {\n        d.chunkUpdates = 0;\n      });\n    await this.bulkSetKeyInCollection(toSave, ObjectStore.BOARD);\n  }\n\n  /**\n   * we keep a list rather than a single location, since client/contract can\n   * often go out of sync on initialization - if client thinks that init\n   * failed but is wrong, it will prompt user to initialize with new home coords,\n   * which bricks the user's account.\n   */\n  public async getHomeLocations(): Promise<WorldLocation[]> {\n    const homeLocations = await this.getKey('homeLocations');\n    let parsed: WorldLocation[] = [];\n    if (homeLocations) {\n      parsed = JSON.parse(homeLocations) as WorldLocation[];\n    }\n\n    return parsed;\n  }\n\n  public async addHomeLocation(location: WorldLocation): Promise<void> {\n    let locationList = await this.getHomeLocations();\n    if (locationList) {\n      locationList.push(location);\n    } else {\n      locationList = [location];\n    }\n    locationList = Array.from(new Set(locationList));\n    await this.setKey('homeLocations', stringify(locationList));\n  }\n\n  public async confirmHomeLocation(location: WorldLocation): Promise<void> {\n    await this.setKey('homeLocations', stringify([location]));\n  }\n\n  public async getSavedTouchedPlanetIds(): Promise<LocationId[]> {\n    const touchedPlanetIds = await this.getKey('touchedPlanetIds');\n\n    if (touchedPlanetIds) {\n      const parsed = JSON.parse(touchedPlanetIds) as LocationId[];\n      return parsed;\n    }\n\n    return [];\n  }\n\n  public async getSavedRevealedCoords(): Promise<RevealedCoords[]> {\n    const revealedPlanetIds = await this.getKey('revealedPlanetIds');\n\n    if (revealedPlanetIds) {\n      const parsed = JSON.parse(revealedPlanetIds);\n      // changed the type on 6/1/21 to include revealer field\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      if (parsed.length === 0 || !(parsed[0] as any).revealer) {\n        return [];\n      }\n      return parsed as RevealedCoords[];\n    }\n\n    return [];\n  }\n  public async getSavedClaimedCoords(): Promise<ClaimedCoords[]> {\n    const claimedPlanetIds = await this.getKey('claimedPlanetIds');\n\n    if (claimedPlanetIds) {\n      const parsed = JSON.parse(claimedPlanetIds);\n      // changed the type on 6/1/21 to include revealer field\n      // eslint-disable-next-line @typescript-eslint/no-explicit-any\n      if (parsed.length === 0 || !(parsed[0] as any).revealer) {\n        return [];\n      }\n      return parsed as ClaimedCoords[];\n    }\n\n    return [];\n  }\n\n  public async saveTouchedPlanetIds(ids: LocationId[]) {\n    await this.setKey('touchedPlanetIds', stringify(ids));\n  }\n\n  public async saveRevealedCoords(revealedCoordTups: RevealedCoords[]) {\n    await this.setKey('revealedPlanetIds', stringify(revealedCoordTups));\n  }\n\n  public async saveClaimedCoords(claimedCoordTupps: ClaimedCoords[]) {\n    await this.setKey('claimedPlanetIds', stringify(claimedCoordTupps));\n  }\n\n  /**\n   * Returns the explored chunk data for the given rectangle if that chunk has been mined. If this\n   * chunk is entirely contained within another bigger chunk that has been mined, return that chunk.\n   * `chunkLoc` is an aligned square, as defined in ChunkUtils.ts in the `getSiblingLocations`\n   * function.\n   */\n  public getChunkByFootprint(chunkLoc: Rectangle): Chunk | undefined {\n    let sideLength = chunkLoc.sideLength;\n\n    while (sideLength <= MAX_CHUNK_SIZE) {\n      const testChunkLoc = getChunkOfSideLengthContainingPoint(chunkLoc.bottomLeft, sideLength);\n      const chunk = this.getChunkById(getChunkKey(testChunkLoc));\n      if (chunk) {\n        return chunk;\n      }\n      sideLength *= 2;\n    }\n\n    return undefined;\n  }\n\n  public hasMinedChunk(chunkLoc: Rectangle): boolean {\n    return !!this.getChunkByFootprint(chunkLoc);\n  }\n\n  private getChunkById(chunkId: ChunkId): Chunk | undefined {\n    return this.chunkMap.get(chunkId);\n  }\n\n  /**\n   * When a chunk is mined, or a chunk is imported via map import, or a chunk is loaded from\n   * persistent storage for the first time, we need to add this chunk to the game. This function\n   * allows you to add a new chunk to the game, and optionally persist that chunk. The reason you\n   * might not want to persist the chunk is if you are sure that you got it from persistent storage.\n   * i.e. it already exists in persistent storage.\n   */\n  public addChunk(chunk: Chunk, persistChunk = true): void {\n    if (this.hasMinedChunk(chunk.chunkFootprint)) {\n      return;\n    }\n\n    const tx: DBAction<ChunkId>[] = [];\n\n    if (persistChunk) {\n      const minedSubChunks = this.getMinedSubChunks(chunk);\n      for (const subChunk of minedSubChunks) {\n        tx.push({\n          type: DBActionType.DELETE,\n          dbKey: getChunkKey(subChunk.chunkFootprint),\n        });\n      }\n    }\n\n    addToChunkMap(\n      this.chunkMap,\n      chunk,\n      (chunk) => {\n        tx.push({\n          type: DBActionType.UPDATE,\n          dbKey: getChunkKey(chunk.chunkFootprint),\n          dbValue: chunk,\n        });\n      },\n      (chunk) => {\n        tx.push({\n          type: DBActionType.DELETE,\n          dbKey: getChunkKey(chunk.chunkFootprint),\n        });\n      },\n      MAX_CHUNK_SIZE\n    );\n\n    // modify in-memory store\n    for (const action of tx) {\n      if (action.type === DBActionType.UPDATE && action.dbValue) {\n        this.chunkMap.set(action.dbKey, action.dbValue);\n      } else if (action.type === DBActionType.DELETE) {\n        this.chunkMap.delete(action.dbKey);\n      }\n    }\n\n    this.diagnosticUpdater?.updateDiagnostics((d) => {\n      d.totalChunks = this.chunkMap.size;\n    });\n\n    // can stop here, if we're just loading into in-memory store from storage\n    if (!persistChunk) {\n      return;\n    }\n\n    this.queuedChunkWrites.push(tx);\n\n    this.diagnosticUpdater &&\n      this.diagnosticUpdater.updateDiagnostics((d) => {\n        d.chunkUpdates = this.queuedChunkWrites.length;\n      });\n\n    // save chunks every 5s if we're just starting up, or 30s once we're moving\n    this.recomputeSaveThrottleAfterUpdate();\n    this.throttledSaveChunkCacheToDisk();\n  }\n\n  /**\n   * Returns all the mined chunks with smaller sidelength strictly contained in the chunk.\n   *\n   * TODO: move this into ChunkUtils, and also make use of it, the way that it is currently used, in\n   * the function named `addToChunkMap`.\n   */\n  private getMinedSubChunks(chunk: Chunk): Chunk[] {\n    const ret: Chunk[] = [];\n    for (\n      let clearingSideLen = 16;\n      clearingSideLen < chunk.chunkFootprint.sideLength;\n      clearingSideLen *= 2\n    ) {\n      for (let x = 0; x < chunk.chunkFootprint.sideLength; x += clearingSideLen) {\n        for (let y = 0; y < chunk.chunkFootprint.sideLength; y += clearingSideLen) {\n          const queryChunk: Rectangle = {\n            bottomLeft: {\n              x: chunk.chunkFootprint.bottomLeft.x + x,\n              y: chunk.chunkFootprint.bottomLeft.y + y,\n            },\n            sideLength: clearingSideLen,\n          };\n          const queryChunkKey = getChunkKey(queryChunk);\n          const exploredChunk = this.getChunkById(queryChunkKey);\n          if (exploredChunk) {\n            ret.push(exploredChunk);\n          }\n        }\n      }\n    }\n    return ret;\n  }\n\n  private recomputeSaveThrottleAfterUpdate() {\n    this.nUpdatesLastTwoMins += 1;\n    if (this.nUpdatesLastTwoMins === 50) {\n      this.throttledSaveChunkCacheToDisk.cancel();\n      this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 30000);\n    }\n    setTimeout(() => {\n      this.nUpdatesLastTwoMins -= 1;\n      if (this.nUpdatesLastTwoMins === 49) {\n        this.throttledSaveChunkCacheToDisk.cancel();\n        this.throttledSaveChunkCacheToDisk = _.throttle(this.persistQueuedChunks, 5000);\n      }\n    }, 120000);\n  }\n\n  public allChunks(): Iterable<Chunk> {\n    return this.chunkMap.values();\n  }\n\n  /**\n   * Whenever a transaction is submitted, it is persisted. When the transaction either fails or\n   * succeeds, it is un-persisted. The reason we persist submitted transactions is to be able to\n   * wait for them upon a fresh start of the game if you close the game before a transaction\n   * confirms.\n   */\n  public async onEthTxSubmit(tx: Transaction): Promise<void> {\n    // in case the tx was mined and saved already\n    if (!tx.hash || this.confirmedTxHashes.has(tx.hash)) return;\n    const ser: PersistedTransaction = { hash: tx.hash, intent: tx.intent };\n    await this.db.put(ObjectStore.UNCONFIRMED_ETH_TXS, JSON.parse(JSON.stringify(ser)), tx.hash);\n  }\n\n  /**\n   * Partner function to {@link PersistentChunkStore#onEthTxSubmit}\n   */\n  public async onEthTxComplete(txHash: string): Promise<void> {\n    this.confirmedTxHashes.add(txHash);\n    await this.db.delete(ObjectStore.UNCONFIRMED_ETH_TXS, txHash);\n  }\n\n  public async getUnconfirmedSubmittedEthTxs(): Promise<PersistedTransaction[]> {\n    const ret: PersistedTransaction[] = await this.db.getAll(ObjectStore.UNCONFIRMED_ETH_TXS);\n    return ret;\n  }\n\n  public async loadPlugins(): Promise<SerializedPlugin[]> {\n    const savedPlugins = await this.getKey('plugins', ObjectStore.PLUGINS);\n\n    if (!savedPlugins) {\n      return [];\n    }\n\n    return JSON.parse(savedPlugins) as SerializedPlugin[];\n  }\n\n  public async savePlugins(plugins: SerializedPlugin[]): Promise<void> {\n    await this.setKey('plugins', JSON.stringify(plugins), ObjectStore.PLUGINS);\n  }\n\n  public async saveModalPositions(modalPositions: Map<ModalId, ModalPosition>): Promise<void> {\n    if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return;\n    const serialized = JSON.stringify(Array.from(modalPositions.entries()));\n    await this.setKey(MODAL_POSITIONS_KEY, serialized, ObjectStore.MODAL_POS);\n  }\n\n  public async loadModalPositions(): Promise<Map<ModalId, ModalPosition>> {\n    if (!this.db.objectStoreNames.contains(ObjectStore.MODAL_POS)) return new Map();\n    const winPos = await this.getKey(MODAL_POSITIONS_KEY, ObjectStore.MODAL_POS);\n    return new Map(winPos ? JSON.parse(winPos) : null);\n  }\n}\n\nexport default PersistentChunkStore;\n"
  },
  {
    "path": "src/Backend/Storage/ReaderDataStore.ts",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { EthConnection } from '@darkforest_eth/network';\nimport {\n  ArtifactId,\n  Biome,\n  EthAddress,\n  LocatablePlanet,\n  LocationId,\n  Planet,\n  SpaceType,\n  WorldLocation,\n} from '@darkforest_eth/types';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { AddressTwitterMap } from '../../_types/darkforest/api/UtilityServerAPITypes';\nimport { arrive, updatePlanetToTime } from '../GameLogic/ArrivalUtils';\nimport { ContractsAPI, makeContractsAPI } from '../GameLogic/ContractsAPI';\nimport { getAllTwitters } from '../Network/UtilityServerAPI';\nimport PersistentChunkStore from './PersistentChunkStore';\n\nexport const enum SinglePlanetDataStoreEvent {\n  REFRESHED_PLANET = 'REFRESHED_PLANET',\n  REFRESHED_ARTIFACT = 'REFRESHED_ARTIFACT',\n}\n\ninterface ReaderDataStoreConfig {\n  contractAddress: EthAddress;\n  viewer: EthAddress | undefined;\n  addressTwitterMap: AddressTwitterMap;\n  contractConstants: ContractConstants;\n  contractsAPI: ContractsAPI;\n  persistentChunkStore: PersistentChunkStore | undefined;\n}\n\n/**\n * A data store that allows you to retrieve data from the contract,\n * and combine it with data that is stored in this browser about a\n * particular user.\n */\nclass ReaderDataStore {\n  private readonly viewer: EthAddress | undefined;\n  private readonly addressTwitterMap: AddressTwitterMap;\n  private readonly contractConstants: ContractConstants;\n  private readonly contractsAPI: ContractsAPI;\n  private readonly persistentChunkStore: PersistentChunkStore | undefined;\n\n  private constructor({\n    viewer,\n    addressTwitterMap,\n    contractConstants,\n    contractsAPI,\n    persistentChunkStore,\n  }: ReaderDataStoreConfig) {\n    this.viewer = viewer;\n    this.addressTwitterMap = addressTwitterMap;\n    this.contractConstants = contractConstants;\n    this.contractsAPI = contractsAPI;\n    this.persistentChunkStore = persistentChunkStore;\n  }\n\n  public destroy(): void {\n    this.contractsAPI.destroy();\n    this.persistentChunkStore?.destroy();\n  }\n\n  public static async create({\n    connection,\n    viewer,\n    contractAddress,\n  }: {\n    connection: EthConnection;\n    viewer: EthAddress | undefined;\n    contractAddress: EthAddress;\n  }): Promise<ReaderDataStore> {\n    const contractsAPI = await makeContractsAPI({ connection, contractAddress });\n    const addressTwitterMap = await getAllTwitters();\n    const contractConstants = await contractsAPI.getConstants();\n    const persistentChunkStore =\n      viewer && (await PersistentChunkStore.create({ account: viewer, contractAddress }));\n\n    const singlePlanetStore = new ReaderDataStore({\n      contractAddress,\n      viewer,\n      addressTwitterMap,\n      contractConstants,\n      contractsAPI,\n      persistentChunkStore,\n    });\n\n    return singlePlanetStore;\n  }\n\n  public getViewer(): EthAddress | undefined {\n    return this.viewer;\n  }\n\n  public getTwitter(owner: EthAddress | undefined): string | undefined {\n    if (owner) {\n      return this.addressTwitterMap[owner];\n    }\n  }\n\n  private setPlanetLocationIfKnown(planet: Planet): void {\n    let planetLocation = undefined;\n\n    if (planet && isLocatable(planet)) {\n      // clear the location of the LocatablePlanet, turning it back into a planet\n      /* eslint-disable @typescript-eslint/no-unused-vars */\n      const { location, biome, ...nonLocatable } = planet;\n      /* eslint-enable @typescript-eslint/no-unused-vars */\n      planet = nonLocatable;\n    }\n\n    if (this.persistentChunkStore) {\n      for (const chunk of this.persistentChunkStore.allChunks()) {\n        for (const loc of chunk.planetLocations) {\n          if (loc.hash === planet.locationId) {\n            planetLocation = loc;\n            break;\n          }\n        }\n        if (planetLocation) break;\n      }\n    }\n\n    if (planetLocation && planet) {\n      (planet as LocatablePlanet).location = planetLocation;\n      (planet as LocatablePlanet).biome = this.getBiome(planetLocation);\n    }\n  }\n\n  public async loadPlanetFromContract(planetId: LocationId): Promise<Planet | LocatablePlanet> {\n    const planet = await this.contractsAPI.getPlanetById(planetId);\n    const contractConstants = await this.contractsAPI.getConstants();\n\n    if (!planet) {\n      throw new Error(`unable to load planet with id ${planetId}`);\n    }\n\n    const arrivals = await this.contractsAPI.getArrivalsForPlanet(planetId);\n\n    arrivals.sort((a, b) => a.arrivalTime - b.arrivalTime);\n    const nowInSeconds = Date.now() / 1000;\n\n    for (const arrival of arrivals) {\n      if (nowInSeconds < arrival.arrivalTime) break;\n      arrive(planet, [], arrival, undefined, contractConstants);\n    }\n\n    updatePlanetToTime(planet, [], Date.now(), contractConstants);\n    this.setPlanetLocationIfKnown(planet);\n\n    return planet;\n  }\n\n  public async loadArtifactFromContract(artifactId: ArtifactId) {\n    const artifact = await this.contractsAPI.getArtifactById(artifactId);\n\n    if (!artifact) {\n      throw new Error(`unable to load artifact with id ${artifactId}`);\n    }\n\n    return artifact;\n  }\n\n  // copied from GameEntityMemoryStore. needed to determine biome if we know planet location\n  private spaceTypeFromPerlin(perlin: number): SpaceType {\n    if (perlin < this.contractConstants.PERLIN_THRESHOLD_1) {\n      return SpaceType.NEBULA;\n    } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_2) {\n      return SpaceType.SPACE;\n    } else if (perlin < this.contractConstants.PERLIN_THRESHOLD_3) {\n      return SpaceType.DEEP_SPACE;\n    } else {\n      return SpaceType.DEAD_SPACE;\n    }\n  }\n\n  // copied from GameEntityMemoryStore. needed to determine biome if we know planet location\n  private getBiome(loc: WorldLocation): Biome {\n    const { perlin, biomebase } = loc;\n    const spaceType = this.spaceTypeFromPerlin(perlin);\n\n    if (spaceType === SpaceType.DEAD_SPACE) return Biome.CORRUPTED;\n\n    let biome = 3 * spaceType;\n    if (biomebase < this.contractConstants.BIOME_THRESHOLD_1) biome += 1;\n    else if (biomebase < this.contractConstants.BIOME_THRESHOLD_2) biome += 2;\n    else biome += 3;\n\n    return biome as Biome;\n  }\n}\n\nexport default ReaderDataStore;\n"
  },
  {
    "path": "src/Backend/Utils/Animation.ts",
    "content": "import { DFAnimation, DFStatefulAnimation, PlanetLevel } from '@darkforest_eth/types';\nimport anime from 'animejs';\n\nexport function sinusoidalAnimation(rps: number): DFAnimation {\n  const startTime = Date.now() + Math.random() * 1000;\n\n  return new DFAnimation(() => {\n    const timeElapsed = Date.now() - startTime;\n    return Math.sin((timeElapsed / 1000) * rps * Math.PI * 2);\n  });\n}\n\nexport function easeInAnimation(durationMs: number, delayMs?: number): DFAnimation {\n  const progress = {\n    percent: 0,\n  };\n\n  setTimeout(\n    () => {\n      anime({\n        targets: progress, // this library will take a value in the `progress` object\n        percent: 1, // .. and animate it between 0 and 1\n        duration: durationMs,\n        easing: 'easeInOutCubic', // .. using this easing function\n        update: () => {},\n      });\n    },\n    delayMs === undefined ? 0 : delayMs\n  );\n\n  return new DFAnimation(() => {\n    return progress.percent;\n  });\n}\n\nexport function emojiEaseOutAnimation(durationMs: number, emoji: string) {\n  const progress = {\n    percent: 0,\n  };\n\n  anime({\n    targets: progress, // this library will take a value in the `progress` object\n    percent: 1, // .. and animate it between 0 and 1\n    duration: durationMs,\n    easing: 'easeInOutCubic', // .. using this easing function\n    update: () => {},\n  });\n\n  return new DFStatefulAnimation(emoji, () => {\n    return progress.percent;\n  });\n}\n\nexport function constantAnimation(constant: number): DFAnimation {\n  return new DFAnimation(() => constant);\n}\n\nexport function planetLevelToAnimationSpeed(level: PlanetLevel): number {\n  return 1 / (1 + level);\n}\n"
  },
  {
    "path": "src/Backend/Utils/Coordinates.ts",
    "content": "import { CanvasCoords, WorldCoords } from '@darkforest_eth/types';\n\nexport const coordsEqual = (a: WorldCoords, b: WorldCoords): boolean => a.x === b.x && a.y === b.y;\n\nexport const distL2 = (a: CanvasCoords | WorldCoords, b: CanvasCoords | WorldCoords): number =>\n  (a.x - b.x) ** 2 + (a.y - b.y) ** 2;\n\nexport const vectorLength = (a: CanvasCoords | WorldCoords): number =>\n  Math.sqrt(a.x ** 2 + a.y ** 2);\n\nexport const normalizeVector = (a: WorldCoords): WorldCoords => {\n  const len = vectorLength(a);\n\n  if (len < 0.00001) return a; // prevent div by 0\n\n  return {\n    x: a.x / len,\n    y: a.y / len,\n  };\n};\n\nexport const scaleVector = (a: WorldCoords, k: number) => {\n  const norm = normalizeVector(a);\n  const len = vectorLength(a);\n\n  return {\n    x: norm.x * k * len,\n    y: norm.y * k * len,\n  };\n};\n"
  },
  {
    "path": "src/Backend/Utils/SnarkArgsHelper.ts",
    "content": "import { fakeHash, mimcHash, modPBigInt, modPBigIntNative, perlin } from '@darkforest_eth/hashing';\nimport {\n  BiomebaseSnarkContractCallArgs,\n  BiomebaseSnarkInput,\n  buildContractCallArgs,\n  fakeProof,\n  InitSnarkContractCallArgs,\n  InitSnarkInput,\n  MoveSnarkContractCallArgs,\n  MoveSnarkInput,\n  RevealSnarkContractCallArgs,\n  RevealSnarkInput,\n  SnarkJSProofAndSignals,\n} from '@darkforest_eth/snarks';\nimport biomebaseCircuitPath from '@darkforest_eth/snarks/biomebase.wasm';\nimport biomebaseZkeyPath from '@darkforest_eth/snarks/biomebase.zkey';\nimport initCircuitPath from '@darkforest_eth/snarks/init.wasm';\nimport initZkeyPath from '@darkforest_eth/snarks/init.zkey';\nimport moveCircuitPath from '@darkforest_eth/snarks/move.wasm';\nimport moveZkeyPath from '@darkforest_eth/snarks/move.zkey';\nimport revealCircuitPath from '@darkforest_eth/snarks/reveal.wasm';\nimport revealZkeyPath from '@darkforest_eth/snarks/reveal.zkey';\nimport { PerlinConfig } from '@darkforest_eth/types';\nimport * as bigInt from 'big-integer';\nimport { BigInteger } from 'big-integer';\nimport FastQueue from 'fastq';\nimport { LRUMap } from 'mnemonist';\nimport { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\nimport { HashConfig } from '../../_types/global/GlobalTypes';\n\ntype ZKPTask = {\n  taskId: number;\n  input: unknown;\n  circuit: string; // path\n  zkey: string; // path\n\n  onSuccess: (proof: SnarkJSProofAndSignals) => void;\n  onError: (e: Error) => void;\n};\n\ntype SnarkInput = RevealSnarkInput | InitSnarkInput | MoveSnarkInput | BiomebaseSnarkInput;\n\nclass SnarkProverQueue {\n  private taskQueue: FastQueue.queue;\n  private taskCount: number;\n\n  constructor() {\n    this.taskQueue = FastQueue(this.execute.bind(this), 1);\n    this.taskCount = 0;\n  }\n\n  public doProof(\n    input: SnarkInput,\n    circuit: string,\n    zkey: string\n  ): Promise<SnarkJSProofAndSignals> {\n    const taskId = this.taskCount++;\n    const task = {\n      input,\n      circuit,\n      zkey,\n      taskId,\n    };\n\n    return new Promise<SnarkJSProofAndSignals>((resolve, reject) => {\n      this.taskQueue.push(task, (err, result) => {\n        if (err) {\n          reject(err);\n        } else {\n          resolve(result);\n        }\n      });\n    });\n  }\n\n  private async execute(\n    task: ZKPTask,\n    cb: (err: Error | null, result: SnarkJSProofAndSignals | null) => void\n  ) {\n    try {\n      console.log(`proving ${task.taskId}`);\n      const res = await window.snarkjs.groth16.fullProve(task.input, task.circuit, task.zkey);\n      console.log(`proved ${task.taskId}`);\n      cb(null, res);\n    } catch (e) {\n      console.error('error while calculating SNARK proof:');\n      console.error(e);\n      cb(e, null);\n    }\n  }\n}\n\nclass SnarkArgsHelper {\n  /**\n   * How many snark results to keep in an LRU cache.\n   */\n  private static readonly DEFAULT_SNARK_CACHE_SIZE = 20;\n  private readonly useMockHash: boolean;\n  private readonly snarkProverQueue: SnarkProverQueue;\n  private readonly terminal: React.MutableRefObject<TerminalHandle | undefined>;\n  private readonly hashConfig: HashConfig;\n  private readonly spaceTypePerlinOpts: PerlinConfig;\n  private readonly biomebasePerlinOpts: PerlinConfig;\n  private readonly planetHashMimc: (...inputs: number[]) => BigInteger;\n  private moveSnarkCache: LRUMap<string, MoveSnarkContractCallArgs>;\n\n  private constructor(\n    hashConfig: HashConfig,\n    terminal: React.MutableRefObject<TerminalHandle | undefined>,\n    useMockHash: boolean\n  ) {\n    this.useMockHash = useMockHash;\n    this.terminal = terminal;\n    this.snarkProverQueue = new SnarkProverQueue();\n    this.hashConfig = hashConfig;\n    this.planetHashMimc = useMockHash\n      ? fakeHash(hashConfig.planetRarity)\n      : mimcHash(hashConfig.planetHashKey);\n    this.spaceTypePerlinOpts = {\n      key: hashConfig.spaceTypeKey,\n      scale: hashConfig.perlinLengthScale,\n      mirrorX: hashConfig.perlinMirrorX,\n      mirrorY: hashConfig.perlinMirrorY,\n      floor: true,\n    };\n    this.biomebasePerlinOpts = {\n      key: hashConfig.biomebaseKey,\n      scale: hashConfig.perlinLengthScale,\n      mirrorX: hashConfig.perlinMirrorX,\n      mirrorY: hashConfig.perlinMirrorY,\n      floor: true,\n    };\n    this.moveSnarkCache = new LRUMap<string, MoveSnarkContractCallArgs>(\n      SnarkArgsHelper.DEFAULT_SNARK_CACHE_SIZE\n    );\n  }\n\n  static create(\n    hashConfig: HashConfig,\n    terminal: React.MutableRefObject<TerminalHandle | undefined>,\n    fakeHash = false\n  ): SnarkArgsHelper {\n    const snarkArgsHelper = new SnarkArgsHelper(hashConfig, terminal, fakeHash);\n    return snarkArgsHelper;\n  }\n\n  setSnarkCacheSize(size: number) {\n    if (size <= 0) {\n      throw new Error(`cache size has to be positive`);\n    }\n\n    const newCache = new LRUMap<string, MoveSnarkContractCallArgs>(size);\n    const oldKeys = Array.from(this.moveSnarkCache.keys());\n\n    for (let i = 0; i < newCache.capacity && i < this.moveSnarkCache.size; i++) {\n      newCache.set(oldKeys[i], this.moveSnarkCache.get(oldKeys[i]) as MoveSnarkContractCallArgs);\n    }\n\n    this.moveSnarkCache.clear();\n    this.moveSnarkCache = newCache;\n  }\n\n  async getRevealArgs(x: number, y: number): Promise<RevealSnarkContractCallArgs> {\n    try {\n      const start = Date.now();\n      this.terminal.current?.println(\n        'REVEAL: calculating witness and proof',\n        TerminalTextStyle.Sub\n      );\n      const input: RevealSnarkInput = {\n        x: modPBigInt(x).toString(),\n        y: modPBigInt(y).toString(),\n        PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(),\n        SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(),\n        SCALE: this.hashConfig.perlinLengthScale.toString(),\n        xMirror: this.hashConfig.perlinMirrorX ? '1' : '0',\n        yMirror: this.hashConfig.perlinMirrorY ? '1' : '0',\n      };\n\n      const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash\n        ? this.fakeRevealProof(x, y)\n        : await this.snarkProverQueue.doProof(input, revealCircuitPath, revealZkeyPath);\n      const ret = buildContractCallArgs(proof, publicSignals) as RevealSnarkContractCallArgs;\n      const end = Date.now();\n      this.terminal.current?.println(\n        `REVEAL: calculated witness and proof in ${end - start}ms`,\n        TerminalTextStyle.Sub\n      );\n\n      return ret;\n    } catch (e) {\n      throw e;\n    }\n  }\n\n  async getInitArgs(x: number, y: number, r: number): Promise<InitSnarkContractCallArgs> {\n    try {\n      const start = Date.now();\n      this.terminal.current?.println('INIT: calculating witness and proof', TerminalTextStyle.Sub);\n      const input: InitSnarkInput = {\n        x: modPBigInt(x).toString(),\n        y: modPBigInt(y).toString(),\n        r: r.toString(),\n        PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(),\n        SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(),\n        SCALE: this.hashConfig.perlinLengthScale.toString(),\n        xMirror: this.hashConfig.perlinMirrorX ? '1' : '0',\n        yMirror: this.hashConfig.perlinMirrorY ? '1' : '0',\n      };\n\n      const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash\n        ? this.fakeInitProof(x, y, r)\n        : await this.snarkProverQueue.doProof(input, initCircuitPath, initZkeyPath);\n      const ret = buildContractCallArgs(proof, publicSignals) as InitSnarkContractCallArgs;\n      const end = Date.now();\n      this.terminal.current?.println(\n        `INIT: calculated witness and proof in ${end - start}ms`,\n        TerminalTextStyle.Sub\n      );\n\n      return ret;\n    } catch (e) {\n      throw e;\n    }\n  }\n\n  async getMoveArgs(\n    x1: number,\n    y1: number,\n    x2: number,\n    y2: number,\n    r: number,\n    distMax: number\n  ): Promise<MoveSnarkContractCallArgs> {\n    const cacheKey = `${x1}-${y1}-${x2}-${y2}-${r}-${distMax}`;\n    const cachedResult = this.moveSnarkCache.get(cacheKey);\n    if (cachedResult) {\n      console.log('MOVE: retrieved snark args from cache');\n      return Promise.resolve(cachedResult);\n    }\n\n    try {\n      const start = Date.now();\n      this.terminal.current?.println('MOVE: calculating witness and proof', TerminalTextStyle.Sub);\n      const input: MoveSnarkInput = {\n        x1: modPBigInt(x1).toString(),\n        y1: modPBigInt(y1).toString(),\n        x2: modPBigInt(x2).toString(),\n        y2: modPBigInt(y2).toString(),\n        r: r.toString(),\n        distMax: distMax.toString(),\n        PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(),\n        SPACETYPE_KEY: this.hashConfig.spaceTypeKey.toString(),\n        SCALE: this.hashConfig.perlinLengthScale.toString(),\n        xMirror: this.hashConfig.perlinMirrorX ? '1' : '0',\n        yMirror: this.hashConfig.perlinMirrorY ? '1' : '0',\n      };\n\n      const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash\n        ? this.fakeMoveProof(x1, y1, x2, y2, r, distMax)\n        : await this.snarkProverQueue.doProof(input, moveCircuitPath, moveZkeyPath);\n\n      const proofArgs = buildContractCallArgs(proof, publicSignals) as MoveSnarkContractCallArgs;\n      const end = Date.now();\n      this.terminal.current?.println(\n        `MOVE: calculated witness and proof in ${end - start}ms`,\n        TerminalTextStyle.Sub\n      );\n\n      this.moveSnarkCache.set(cacheKey, proofArgs);\n      return proofArgs;\n    } catch (e) {\n      throw e;\n    }\n  }\n\n  async getFindArtifactArgs(x: number, y: number): Promise<BiomebaseSnarkContractCallArgs> {\n    try {\n      const start = Date.now();\n      this.terminal.current?.println(\n        'ARTIFACT: calculating witness and proof',\n        TerminalTextStyle.Sub\n      );\n      const input: BiomebaseSnarkInput = {\n        x: modPBigInt(x).toString(),\n        y: modPBigInt(y).toString(),\n        PLANETHASH_KEY: this.hashConfig.planetHashKey.toString(),\n        BIOMEBASE_KEY: this.hashConfig.biomebaseKey.toString(),\n        SCALE: this.hashConfig.perlinLengthScale.toString(),\n        xMirror: this.hashConfig.perlinMirrorX ? '1' : '0',\n        yMirror: this.hashConfig.perlinMirrorY ? '1' : '0',\n      };\n\n      const { proof, publicSignals }: SnarkJSProofAndSignals = this.useMockHash\n        ? this.fakeBiomebaseProof(x, y)\n        : await this.snarkProverQueue.doProof(input, biomebaseCircuitPath, biomebaseZkeyPath);\n\n      const proofArgs = buildContractCallArgs(proof, publicSignals);\n      const end = Date.now();\n      this.terminal.current?.println(\n        `ARTIFACT: calculated witness and proof in ${end - start}ms`,\n        TerminalTextStyle.Sub\n      );\n\n      return proofArgs as BiomebaseSnarkContractCallArgs;\n    } catch (e) {\n      throw e;\n    }\n  }\n\n  private fakeRevealProof(x: number, y: number) {\n    const hash = this.planetHashMimc(x, y);\n    const perl = perlin({ x, y }, this.spaceTypePerlinOpts);\n    const publicSignals: BigInteger[] = [\n      hash,\n      bigInt(perl),\n      bigInt(x),\n      bigInt(y),\n      bigInt(this.hashConfig.planetHashKey),\n      bigInt(this.hashConfig.spaceTypeKey),\n      bigInt(this.hashConfig.perlinLengthScale),\n      bigInt(this.hashConfig.perlinMirrorX ? 1 : 0),\n      bigInt(this.hashConfig.perlinMirrorY ? 1 : 0),\n    ];\n    return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10)));\n  }\n\n  private fakeInitProof(x: number, y: number, r: number) {\n    const hash = this.planetHashMimc(x, y);\n    const perl = perlin({ x, y }, this.spaceTypePerlinOpts);\n    const publicSignals: BigInteger[] = [\n      hash,\n      bigInt(perl),\n      bigInt(r),\n      bigInt(this.hashConfig.planetHashKey),\n      bigInt(this.hashConfig.spaceTypeKey),\n      bigInt(this.hashConfig.perlinLengthScale),\n      bigInt(this.hashConfig.perlinMirrorX ? 1 : 0),\n      bigInt(this.hashConfig.perlinMirrorY ? 1 : 0),\n    ];\n    return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10)));\n  }\n\n  private fakeMoveProof(\n    x1: number,\n    y1: number,\n    x2: number,\n    y2: number,\n    r: number,\n    distMax: number\n  ) {\n    const hash1 = this.planetHashMimc(x1, y1);\n    const hash2 = this.planetHashMimc(x2, y2);\n    const perl2 = perlin({ x: x2, y: y2 }, this.spaceTypePerlinOpts);\n    const publicSignals: BigInteger[] = [\n      hash1,\n      hash2,\n      bigInt(perl2),\n      bigInt(r),\n      bigInt(distMax),\n      bigInt(this.hashConfig.planetHashKey),\n      bigInt(this.hashConfig.spaceTypeKey),\n      bigInt(this.hashConfig.perlinLengthScale),\n      bigInt(this.hashConfig.perlinMirrorX ? 1 : 0),\n      bigInt(this.hashConfig.perlinMirrorY ? 1 : 0),\n    ];\n    return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10)));\n  }\n\n  private fakeBiomebaseProof(x: number, y: number) {\n    const hash = this.planetHashMimc(x, y);\n    const biomebase = bigInt(perlin({ x, y }, this.biomebasePerlinOpts));\n    const publicSignals: BigInteger[] = [\n      hash,\n      biomebase,\n      bigInt(this.hashConfig.planetHashKey),\n      bigInt(this.hashConfig.biomebaseKey),\n      bigInt(this.hashConfig.perlinLengthScale),\n      bigInt(this.hashConfig.perlinMirrorX ? 1 : 0),\n      bigInt(this.hashConfig.perlinMirrorY ? 1 : 0),\n    ];\n    return fakeProof(publicSignals.map((x) => modPBigIntNative(x).toString(10)));\n  }\n}\n\nexport default SnarkArgsHelper;\n"
  },
  {
    "path": "src/Backend/Utils/Utils.ts",
    "content": "import { formatNumber } from '@darkforest_eth/gamelogic';\nimport { EthAddress, Planet, SpaceType, Upgrade, UpgradeBranchName } from '@darkforest_eth/types';\nimport * as bigInt from 'big-integer';\nimport { BigInteger } from 'big-integer';\nimport { StatIdx } from '../../_types/global/GlobalTypes';\n\nexport const ONE_DAY = 24 * 60 * 60 * 1000;\n\ntype NestedBigIntArray = (BigInteger | string | NestedBigIntArray)[];\ntype NestedStringArray = (string | NestedStringArray)[];\n\nexport const hexifyBigIntNestedArray = (arr: NestedBigIntArray): NestedStringArray => {\n  return arr.map((value) => {\n    if (Array.isArray(value)) {\n      return hexifyBigIntNestedArray(value);\n    } else {\n      if (typeof value === 'string') {\n        const valueBI = bigInt(value as string);\n        return '0x' + valueBI.toString(16);\n      } else {\n        // eslint-disable-next-line @typescript-eslint/no-explicit-any\n        return '0x' + (value as any).toString(16);\n      }\n    }\n  });\n};\n\n/*\n * returns stat of an upgrade given a stat index\n */\nexport const getUpgradeStat = (upgrade: Upgrade, stat: StatIdx): number => {\n  if (stat === StatIdx.EnergyCap) return upgrade.energyCapMultiplier;\n  else if (stat === StatIdx.EnergyGro) return upgrade.energyGroMultiplier;\n  else if (stat === StatIdx.Range) return upgrade.rangeMultiplier;\n  else if (stat === StatIdx.Speed) return upgrade.speedMultiplier;\n  else if (stat === StatIdx.Defense) return upgrade.defMultiplier;\n  else return upgrade.energyCapMultiplier;\n};\n\n// color utils\n\nexport const hslStr: (h: number, s: number, l: number) => string = (h, s, l) => {\n  return `hsl(${h % 360},${s}%,${l}%)`;\n};\nfunction hashToHue(hash: string): number {\n  let seed = bigInt(hash, 16).and(0xffffff).toString(16);\n  seed = '0x' + '0'.repeat(6 - seed.length) + seed;\n\n  const baseHue = parseInt(seed) % 360;\n  return baseHue;\n}\n\nexport const getPlayerColor: (player: EthAddress) => string = (player) => {\n  return hslStr(hashToHue(player.slice(2)), 100, 70); // remove 0x\n};\n\nexport const getOwnerColor: (planet: Planet) => string = (planet) => {\n  return planet.owner ? getPlayerColor(planet.owner) : 'hsl(0,1%,50%)';\n};\n\nexport const getRandomActionId = () => {\n  const hex = '0123456789abcdef';\n\n  let ret = '';\n  for (let i = 0; i < 10; i += 1) {\n    ret += hex[Math.floor(hex.length * Math.random())];\n  }\n  return ret;\n};\n\nexport const getFormatProp = (planet: Planet | undefined, prop: string): string => {\n  if (!planet) return '0';\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any\n  const myPlanet = planet as any;\n  if (prop === 'silverGrowth') return formatNumber(myPlanet[prop] * 60);\n  else return formatNumber(myPlanet[prop]);\n};\n\nexport const getPlanetRank = (planet: Planet | undefined): number => {\n  if (!planet) return 0;\n  return planet.upgradeState.reduce((a, b) => a + b);\n};\n\nexport const getPlanetShortHash = (planet: Planet | undefined): string => {\n  if (!planet) return '00000';\n  else return planet.locationId.substring(4, 9);\n};\n\nexport const getPlayerShortHash = (address: EthAddress): string => {\n  return address.substring(0, 6);\n};\n\nexport const isFullRank = (planet: Planet | undefined): boolean => {\n  if (!planet) return true;\n  const maxRank = getPlanetMaxRank(planet);\n  const rank = getPlanetRank(planet);\n\n  return rank >= maxRank;\n};\n\nexport const upgradeName = (branchName: UpgradeBranchName) => {\n  return ['Defense', 'Range', 'Speed'][branchName];\n};\n\nexport const getPlanetMaxRank = (planet: Planet | undefined): number => {\n  if (!planet) return 0;\n\n  if (planet.spaceType === SpaceType.NEBULA) return 3;\n  else if (planet.spaceType === SpaceType.SPACE) return 4;\n  else return 5;\n};\n\nexport const titleCase = (title: string): string =>\n  title\n    .split(/ /g)\n    .map((word, i) => {\n      // don't capitalize articles unless it's the first word\n      if (i !== 0 && ['of', 'the'].includes(word)) return word;\n      return `${word.substring(0, 1).toUpperCase()}${word.substring(1)}`;\n    })\n    .join(' ');\n"
  },
  {
    "path": "src/Backend/Utils/WhitelistSnarkArgsHelper.ts",
    "content": "import {\n  buildContractCallArgs,\n  SnarkJSProofAndSignals,\n  WhitelistSnarkContractCallArgs,\n  WhitelistSnarkInput,\n} from '@darkforest_eth/snarks';\nimport whitelistCircuitPath from '@darkforest_eth/snarks/whitelist.wasm';\nimport whitelistZkeyPath from '@darkforest_eth/snarks/whitelist.zkey';\nimport { EthAddress } from '@darkforest_eth/types';\nimport bigInt, { BigInteger } from 'big-integer';\nimport { TerminalTextStyle } from '../../Frontend/Utils/TerminalTypes';\nimport { TerminalHandle } from '../../Frontend/Views/Terminal';\n\n/**\n * Helper method for generating whitelist SNARKS.\n * This is separate from the existing {@link SnarkArgsHelper}\n * because whitelist txs require far less setup compared\n * to SNARKS that are sent in context of the game.\n */\nexport const getWhitelistArgs = async (\n  key: BigInteger,\n  recipient: EthAddress,\n  terminal?: React.MutableRefObject<TerminalHandle | undefined>\n): Promise<WhitelistSnarkContractCallArgs> => {\n  try {\n    const start = Date.now();\n    terminal?.current?.println(\n      'WHITELIST REGISTER: calculating witness and proof',\n      TerminalTextStyle.Sub\n    );\n    const input: WhitelistSnarkInput = {\n      key: key.toString(),\n      recipient: bigInt(recipient.substring(2), 16).toString(),\n    };\n\n    const fullProveResponse = await window.snarkjs.groth16.fullProve(\n      input,\n      whitelistCircuitPath,\n      whitelistZkeyPath\n    );\n    const { proof, publicSignals }: SnarkJSProofAndSignals = fullProveResponse;\n    const ret = buildContractCallArgs(proof, publicSignals) as WhitelistSnarkContractCallArgs;\n    const end = Date.now();\n    terminal?.current?.println(\n      `WHITELIST REGISTER: calculated witness and proof in ${end - start}ms`,\n      TerminalTextStyle.Sub\n    );\n\n    return ret;\n  } catch (e) {\n    throw e;\n  }\n};\n"
  },
  {
    "path": "src/Backend/Utils/Wrapper.ts",
    "content": "/**\n * React uses referential identity to detect changes, and rerender. Rather\n * than copying an object into a new object, to force a rerender, we can\n * just wrap it in a new {@code Wrapper}, which will force a rerender.\n */\nexport class Wrapper<T> {\n  public readonly value: T;\n\n  public constructor(value: T) {\n    this.value = value;\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Components/AncientLabel.tsx",
    "content": "import React from 'react';\n/* ancient label */\nimport styled, { css, keyframes } from 'styled-components';\nimport { ANCIENT_BLUE, ANCIENT_PURPLE } from '../Styles/Colors';\n\nconst shakeAndFlash = keyframes`\n0%   { \n  transform: skewX(-30deg); \n  color: ${ANCIENT_BLUE}; \n}\n5%   { \n  transform: skewX( 30deg); \n  color: ${ANCIENT_BLUE}; \n  text-shadow: -1px -1px 0 ${ANCIENT_BLUE};\n}\n10%  { \n  transform: skewX(-30deg); \n  color: ${ANCIENT_PURPLE}; \n}\n15%  { \n  transform: skewX( 30deg); \n  color: ${ANCIENT_PURPLE}; \n  text-shadow: -1px -1px 0 ${ANCIENT_BLUE};\n}\n20%  { \n  transform: skewX(  0deg); \n  color: ${ANCIENT_PURPLE}; \n}\n100% { \n  transform: skewX(  0deg); \n  color: ${ANCIENT_PURPLE}; \n} \n`;\n\nexport const ancientAnim = css`\n  display: inline-block;\n  animation: 1.2s ${shakeAndFlash} linear infinite alternate;\n`;\n\nconst StyledAncientLabelAnim = styled.span`\n  ${ancientAnim}\n  color: ${ANCIENT_PURPLE};\n`;\n\nexport const AncientLabelAnim = () => <StyledAncientLabelAnim>Ancient</StyledAncientLabelAnim>;\n\nconst StyledAncientLabel = styled.span`\n  color: ${ANCIENT_PURPLE};\n`;\n\nexport const AncientLabel = () => <StyledAncientLabel>Ancient</StyledAncientLabel>;\n"
  },
  {
    "path": "src/Frontend/Components/ArtifactImage.tsx",
    "content": "import { ArtifactFileColor, artifactFileName, isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { Artifact } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled, { css } from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\n\nexport const ARTIFACT_URL = 'https://d2wspbczt15cqu.cloudfront.net/v0.6.0-artifacts/';\n// const ARTIFACT_URL = '/public/img/artifacts/videos/';\n\nfunction getArtifactUrl(thumb: boolean, artifact: Artifact, color: ArtifactFileColor): string {\n  const fileName = artifactFileName(true, thumb, artifact, color);\n  return ARTIFACT_URL + fileName;\n}\n\nexport function ArtifactImage({\n  artifact,\n  size,\n  thumb,\n  bgColor,\n}: {\n  artifact: Artifact;\n  size: number;\n  thumb?: boolean;\n  bgColor?: ArtifactFileColor;\n}) {\n  const url = getArtifactUrl(thumb || false, artifact, bgColor || ArtifactFileColor.BLUE);\n  const image = isSpaceShip(artifact.artifactType) ? (\n    <img width={size} height={size} src={url} />\n  ) : (\n    <video width={size} height={size} loop autoPlay key={artifact.id}>\n      <source src={url} type={'video/webm'} />\n    </video>\n  );\n\n  return (\n    <Container width={size} height={size}>\n      {image}\n    </Container>\n  );\n}\n\nconst Container = styled.div`\n  image-rendering: crisp-edges;\n\n  ${({ width, height }: { width: number; height: number }) => css`\n    width: ${width}px;\n    height: ${height}px;\n    min-width: ${width}px;\n    min-height: ${height}px;\n    background-color: ${dfstyles.colors.artifactBackground};\n    display: inline-block;\n  `}\n`;\n"
  },
  {
    "path": "src/Frontend/Components/BiomeAnims.tsx",
    "content": "import { css, keyframes } from 'styled-components';\n\nconst shake = keyframes`\n  0% { transform: skewX(-30deg); }\n  5% { transform: skewX(30deg); }\n  10% { transform: skewX(-30deg); }\n  15% { transform: skewX(30deg); }\n  20% { transform: skewX(0deg); }\n  100% { transform: skewX(0deg); }  \n`;\n\nexport const shakeAnim = css`\n  display: inline-block;\n  animation: 1.2s ${shake} linear infinite alternate;\n`;\n\nconst yellow = '#fed91f';\nconst orange = '#ff9500';\nconst red = '#f95e5e';\n\nconst base = `\n    1px 1px 0px ${red},\n`;\n\nconst burn = keyframes`\n  0% {\n    text-shadow:\n    ${base}\n    1px -1px 2px ${yellow},\n    0 0px 7px ${red},\n    5px -7px 7px ${orange};\n  }\n  33% {\n    text-shadow:\n    ${base}\n    1px -1px 2px ${yellow},\n    5px -2px 7px ${red},\n    3px 0 10px ${orange};\n  }\n  66% {\n    text-shadow:\n    ${base}\n    1px -1px 2px ${yellow},\n    2px -3px 7px ${red},\n    0 -3px 10px ${orange};\n  }\n  100% {\n    text-shadow:\n    ${base}\n    1px -1px 2px ${yellow},\n    2px -5px 7px ${red},\n    5px 0 10px ${orange};\n  }\n`;\n\nexport const burnAnim = css`\n  animation: 5s ${burn} infinite alternate;\n`;\n\nexport const wiggle = keyframes`\n0%   { transform: rotate(-15deg); }\n48%  { transform: rotate(-15deg); }\n50%  { transform: rotate(15deg); }\n98%  { transform: rotate(15deg); }\n100% { transform: rotate(-15deg); }\n`;\n\nconst s1 = `hsla(198, 78%, 77%, 0.60)`;\nconst s2 = `hsla(198, 78%, 77%, 0.30)`;\nconst s3 = `hsla(198, 78%, 77%, 0.15)`;\n\nexport const icyAnim = css`\n  text-shadow: 0 -3px 0 ${s1}, 0 -6px 0 ${s2}, 0 -9px 0 ${s3};\n  display: inline-block;\n  animation: 4s ${wiggle} linear infinite;\n`;\n"
  },
  {
    "path": "src/Frontend/Components/Btn.tsx",
    "content": "import {\n  DarkForestButton,\n  DarkForestShortcutButton,\n  ShortcutPressedEvent,\n} from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestButton.tagName, DarkForestButton);\ncustomElements.define(DarkForestShortcutButton.tagName, DarkForestShortcutButton);\n\nexport { DarkForestButton, DarkForestShortcutButton, ShortcutPressedEvent };\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Btn = createComponent<\n  DarkForestButton,\n  {\n    onClick: (evt: Event & React.MouseEvent<DarkForestButton>) => void;\n  }\n>(React, DarkForestButton.tagName, DarkForestButton, {\n  onClick: 'click',\n});\n\nexport const ShortcutBtn = createComponent<\n  DarkForestShortcutButton,\n  {\n    onClick: (evt: Event & React.MouseEvent<DarkForestShortcutButton>) => void;\n    onShortcutPressed: (evt: ShortcutPressedEvent) => void;\n  }\n>(React, DarkForestShortcutButton.tagName, DarkForestShortcutButton, {\n  onClick: 'click',\n  onShortcutPressed: 'shortcut-pressed',\n});\n"
  },
  {
    "path": "src/Frontend/Components/Button.tsx",
    "content": "import React, { useState } from 'react';\n\ninterface ButtonProps {\n  onClick?(event: React.MouseEvent<HTMLButtonElement>): Promise<void> | void;\n  children: React.ReactNode;\n  style?: React.CSSProperties;\n  hoverStyle?: React.CSSProperties;\n}\n\nexport default function Button({\n  children,\n  onClick: _onClick = () => {},\n  style = {},\n  hoverStyle = {},\n  ...rest\n}: ButtonProps) {\n  const [submitting, setSubmitting] = useState<boolean>(false);\n  const [hover, setHover] = useState<boolean>(false);\n\n  return (\n    <button\n      disabled={submitting}\n      onClick={async (e) => {\n        setSubmitting(true);\n        await _onClick(e);\n        setSubmitting(false);\n      }}\n      style={{\n        cursor: submitting ? 'not-allowed' : '',\n        width: 'fit-content',\n        ...style,\n        ...(hover ? hoverStyle : {}),\n      }}\n      onMouseEnter={() => setHover(true)}\n      onMouseLeave={() => setHover(false)}\n      {...rest}\n    >\n      {children}\n    </button>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/CapturePlanetButton.tsx",
    "content": "import { EMPTY_ADDRESS } from '@darkforest_eth/constants';\nimport { isUnconfirmedCapturePlanetTx, isUnconfirmedInvadePlanetTx } from '@darkforest_eth/serde';\nimport { Planet, TooltipName } from '@darkforest_eth/types';\nimport React, { useCallback, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport { useAccount, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { INVADE } from '../Utils/ShortcutConstants';\nimport { ShortcutBtn } from './Btn';\nimport { LoadingSpinner } from './LoadingSpinner';\nimport { MaybeShortcutButton } from './MaybeShortcutButton';\nimport { Row } from './Row';\nimport { Green, Red, White } from './Text';\n\nconst StyledRow = styled(Row)`\n  .button {\n    margin-bottom: 4px;\n    flex-grow: 1;\n  }\n`;\n\nexport function CapturePlanetButton({\n  planetWrapper,\n}: {\n  planetWrapper: Wrapper<Planet | undefined>;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const gameManager = uiManager.getGameManager();\n  const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined);\n  const owned = planetWrapper.value?.owner === account;\n  const captureZoneGenerator = uiManager.getCaptureZoneGenerator();\n\n  const canGiveScore = useMemo(() => {\n    if (!planetWrapper.value) return;\n    const planet = planetWrapper.value;\n    return uiManager.potentialCaptureScore(planet.planetLevel) > 0;\n  }, [planetWrapper, uiManager]);\n\n  const shouldShow = useMemo(() => {\n    if (!planetWrapper.value) return false;\n    const planet = planetWrapper.value;\n    return canGiveScore && owned && planet.capturer === EMPTY_ADDRESS;\n  }, [owned, planetWrapper, canGiveScore]);\n\n  const shouldShowInvade = useMemo(() => {\n    if (!planetWrapper.value) return false;\n    const planet = planetWrapper.value;\n    const inZone = captureZoneGenerator?.isInZone(planet.locationId);\n\n    return owned && inZone && planet.invader === EMPTY_ADDRESS && planet.capturer === EMPTY_ADDRESS;\n  }, [owned, planetWrapper, captureZoneGenerator]);\n\n  const planetHasEnoughEnergy = useMemo(() => {\n    if (!planetWrapper.value) return false;\n    const { energy, energyCap } = planetWrapper.value;\n\n    return energy > energyCap * 0.8; // real percentage is 78. need time for contract to catch up.\n  }, [planetWrapper]);\n\n  const invadable = useMemo(() => {\n    if (!planetWrapper.value) return false;\n    const planet = planetWrapper.value;\n    return planet.capturer === EMPTY_ADDRESS && planet.invader === EMPTY_ADDRESS;\n  }, [planetWrapper]);\n\n  const invading = useMemo(\n    () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedInvadePlanetTx),\n    [planetWrapper]\n  );\n\n  const invade = useCallback(() => {\n    if (!planetWrapper.value) return;\n    gameManager.invadePlanet(planetWrapper.value.locationId);\n  }, [gameManager, planetWrapper]);\n\n  const shouldShowCapture = useMemo(() => {\n    if (!planetWrapper.value) return;\n    const planet = planetWrapper.value;\n\n    return owned && planet.capturer === EMPTY_ADDRESS && planet.invader !== EMPTY_ADDRESS;\n  }, [owned, planetWrapper]);\n\n  const blocksLeft = useMemo(() => {\n    if (!planetWrapper.value || !currentBlockNumber) {\n      return undefined;\n    }\n    const planet = planetWrapper.value;\n\n    if (!planet.invadeStartBlock) return undefined;\n    const holdBlocksRequired = uiManager\n      .getGameManager()\n      .getContractConstants().CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED;\n\n    // it is possible that we have an outdated currentBlockNumber in this calculation\n    // this would result in an incorrect number of blocks required being shown\n    // only show a max of holdBlocksRequired instead\n    return Math.min(\n      planet.invadeStartBlock + holdBlocksRequired - currentBlockNumber,\n      holdBlocksRequired\n    );\n  }, [uiManager, planetWrapper, currentBlockNumber]);\n\n  const capturable = useMemo(() => {\n    return blocksLeft !== undefined && blocksLeft <= 0 && planetHasEnoughEnergy;\n  }, [planetHasEnoughEnergy, blocksLeft]);\n\n  const capturing = useMemo(\n    () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedCapturePlanetTx),\n    [planetWrapper]\n  );\n\n  const capture = useCallback(() => {\n    if (!planetWrapper.value) return;\n    gameManager.capturePlanet(planetWrapper.value.locationId);\n  }, [gameManager, planetWrapper]);\n\n  return (\n    <StyledRow>\n      {shouldShow && (\n        <>\n          {shouldShowInvade && (\n            <MaybeShortcutButton\n              className='button'\n              size='stretch'\n              active={invading}\n              disabled={!invadable || invading}\n              onClick={invade}\n              onShortcutPressed={invade}\n              shortcutKey={INVADE}\n              shortcutText={INVADE}\n            >\n              <TooltipTrigger\n                style={{ width: '100%', textAlign: 'center' }}\n                name={TooltipName.Empty}\n                extraContent={<>Invade this planet. </>}\n              >\n                {invading ? <LoadingSpinner initialText={'Invading...'} /> : 'Invade'}\n              </TooltipTrigger>\n            </MaybeShortcutButton>\n          )}\n\n          {shouldShowCapture && (\n            <ShortcutBtn\n              className='button'\n              size='stretch'\n              active={capturing}\n              disabled={!capturable || capturing}\n              onClick={capture}\n              onShortcutPressed={capture}\n              shortcutKey={INVADE}\n              shortcutText={INVADE}\n            >\n              <TooltipTrigger\n                style={{ width: '100%', textAlign: 'center' }}\n                name={TooltipName.Empty}\n                extraContent={\n                  <>\n                    <Green>\n                      Capture this planet for score!{' '}\n                      {!!blocksLeft && blocksLeft >= 0 && (\n                        <>\n                          You must wait <White>{blocksLeft}</White> blocks until you can capture\n                          this planet.\n                        </>\n                      )}\n                      {!planetHasEnoughEnergy && (\n                        <Red>The planet requires above 80% energy before you can capture it.</Red>\n                      )}\n                    </Green>\n                  </>\n                }\n              >\n                {capturing ? <LoadingSpinner initialText={'Capturing...'} /> : 'Capture!'}\n              </TooltipTrigger>\n            </ShortcutBtn>\n          )}\n        </>\n      )}\n    </StyledRow>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/CoreUI.tsx",
    "content": "import colors from 'color';\nimport React, { ChangeEvent, useCallback } from 'react';\nimport styled, { css } from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\nimport { DFZIndex } from '../Utils/constants';\n\nexport const InlineBlock = styled.div`\n  display: inline-block;\n`;\n\nexport const Separator = styled.div`\n  width: 100%;\n  box-sizing: border-box;\n  padding-left: 2px;\n  padding-right: 2px;\n  height: 1px;\n  background-color: ${dfstyles.colors.borderDark};\n`;\n\nexport const FloatRight = styled.div`\n  float: right;\n`;\n\nexport const TextButton = styled.span`\n  color: ${dfstyles.colors.subtext};\n  cursor: pointer;\n  user-select: none;\n  text-decoration: underline;\n\n  &:hover {\n    color: white;\n  }\n`;\n\nexport const Padded = styled.div`\n  ${({\n    left,\n    top,\n    right,\n    bottom,\n  }: {\n    left?: string;\n    top?: string;\n    right?: string;\n    bottom?: string;\n  }) => css`\n    padding-left: ${left || '8px'};\n    padding-top: ${top || '8px'};\n    padding-right: ${right || '8px'};\n    padding-bottom: ${bottom || '8px'};\n  `}\n`;\n\nexport const BorderlessPane = styled.div`\n  transition: 200ms;\n  display: flex;\n  flex-direction: column;\n  z-index: ${DFZIndex.MenuBar};\n  margin: 8px;\n  padding: 8px;\n  border-radius: ${dfstyles.borderRadius};\n  background-color: ${dfstyles.colors.background};\n  border-radius: ${dfstyles.borderRadius};\n  background-color: ${dfstyles.colors.background};\n`;\n\nexport const Underline = styled.span`\n  text-decoration: underline;\n`;\n\nexport const Display = styled.div`\n  ${({ visible }: { visible?: boolean }) => css`\n    ${!visible && `display: none;`}\n  `}\n`;\n\nexport const Emphasized = styled.span`\n  font-weight: bold;\n  color: ${dfstyles.colors.subtext};\n`;\n\nexport const HeaderText = styled.div`\n  color: ${dfstyles.colors.text};\n  text-decoration: underline;\n  font-weight: bold;\n  display: inline;\n`;\n\nexport const SectionHeader = styled(HeaderText)`\n  margin-bottom: 16px;\n  display: block;\n`;\n\nexport const Section = styled.div`\n  padding: 1em 0;\n\n  &:first-child {\n    margin-top: -8px;\n  }\n\n  &:last-child {\n    border-bottom: none;\n  }\n`;\n\nexport const Bottom = styled.div`\n  position: absolute;\n  bottom: 0;\n  left: 0;\n`;\n\nexport const VerticalSplit = function ({\n  children,\n}: {\n  children: [React.ReactNode, React.ReactNode];\n}) {\n  return (\n    <VerticalSplitRow>\n      <VerticalSplitChild>{children[0]}</VerticalSplitChild>\n      <VerticalSplitChild>{children[1]}</VerticalSplitChild>\n    </VerticalSplitRow>\n  );\n};\n\nconst VerticalSplitRow = styled.div`\n  display: flex;\n  flex-direction: row;\n  gap: 8px;\n`;\n\nconst VerticalSplitChild = styled.div`\n  flex: 1 1 50%;\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n`;\n\nexport const FullHeight = styled.div`\n  height: 100%;\n`;\n\n/**\n * Fills parent width, aligns children horizontally in the center.\n */\nexport const AlignCenterHorizontally = styled.div`\n  display: inline-flex;\n  flex-direction: row;\n  justify-content: center;\n  align-items: center;\n`;\n\nexport const AlignCenterVertically = styled.div`\n  height: 100%;\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n  align-items: center;\n`;\n\n/**\n * Expands to fill space in a flexbox.\n */\nexport const Expand = styled.div`\n  display: inline-box;\n  flex-grow: 1;\n`;\n\n/**\n * Don't shrink in a flexbox.\n */\nexport const DontShrink = styled.div`\n  display: inline-box;\n  flex-shrink: 0;\n`;\n\n/**\n * This is the link that all core ui in Dark Forest should use. Please!\n */\nexport function Link(\n  props: {\n    to?: string;\n    color?: string;\n    openInThisTab?: boolean;\n    children: React.ReactNode;\n  } & React.HtmlHTMLAttributes<HTMLAnchorElement>\n) {\n  const { to, color, openInThisTab, children } = props;\n\n  return (\n    <LinkImpl {...props} href={to} color={color} target={openInThisTab ? undefined : '_blank'}>\n      {children}\n    </LinkImpl>\n  );\n}\n\nconst LinkImpl = styled.a`\n  cursor: pointer;\n\n  ${({ color }: { color?: string }) => css`\n    text-decoration: underline;\n    color: ${color || dfstyles.colors.dfblue};\n  }\n\n    &:hover {\n      color: ${colors(color || dfstyles.colors.dfblue)\n        .lighten(0.3)\n        .hex()};\n    }\n  `}\n`;\n\n/**\n * Inline block rectangle, measured in ems, default 1em by 1em.\n */\nexport const EmSpacer = styled.div`\n  ${({ width, height }: { width?: number; height?: number }) => css`\n    width: ${width === undefined ? '1em' : width};\n    height: ${height === undefined ? '1em' : height};\n    flex-grow: 0;\n    flex-shrink: 0;\n    ${width && !height ? 'display: inline-block;' : ''}\n    ${width ? `width: ${width}em;` : ''}\n    ${height ? `height: ${height}em;min-height:${height}em;` : ''}\n  `}\n`;\n\nexport const Spacer = styled.div`\n  ${({ width, height }: { width?: number; height?: number }) => css`\n    width: 1px;\n    height: 1px;\n    ${width && !height ? 'display: inline-block;' : ''}\n    ${width ? `width: ${width}px;` : ''}\n    ${height ? `height: ${height}px;min-height:${height}px;` : ''}\n  `}\n`;\n\nexport const Truncate = styled.div`\n  ${({ maxWidth }: { maxWidth?: string }) => css`\n    vertical-align: bottom;\n    display: inline-block;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    ${maxWidth !== undefined && `max-width: ${maxWidth};`};\n  `}\n`;\n\nexport const Title = styled(Truncate)`\n  flex-grow: 1;\n  text-align: left;\n`;\n\n/**\n * The container element into which a plugin renders its html elements.\n * Contains styles for child elements so that plugins can use UI\n * that is consistent with the rest of Dark Forest's UI. Keeping this up\n * to date will be an ongoing challange, but there's probably some better\n * way to do this.\n */\nexport const PluginElements = styled.div`\n  color: ${dfstyles.colors.text};\n  width: 400px;\n  min-height: 100px;\n  max-height: 600px;\n  overflow: scroll;\n\n  button {\n    border: 1px solid white;\n    border-radius: 4px;\n    padding: 0 0.3em;\n\n    transition: background-color 0.2s colors 0.2s;\n\n    &:hover {\n      background-color: ${dfstyles.colors.text};\n      color: black;\n      border: 1px transparent;\n    }\n  }\n`;\n\nexport const MaxWidth = styled.div`\n  ${({ width }: { width: string }) => {\n    return css`\n      max-width: ${width};\n    `;\n  }}\n`;\n\nexport const Hidden = styled.div`\n  display: none;\n`;\n\nexport const Select = styled.select`\n  ${({ wide }: { wide?: boolean }) => css`\n    outline: none;\n    background: ${dfstyles.colors.background};\n    color: ${dfstyles.colors.subtext};\n    border-radius: 4px;\n    border: 1px solid ${dfstyles.colors.border};\n    width: ${wide ? '100%' : '12em'};\n    padding: 2px 6px;\n    cursor: pointer;\n\n    &:hover {\n      border: 1px solid ${dfstyles.colors.subtext};\n      background: ${dfstyles.colors.subtext};\n      color: ${dfstyles.colors.background};\n    }\n  `}\n`;\n\n/**\n * Controllable input that allows the user to select from one of the\n * given string values.\n */\nexport function SelectFrom({\n  values,\n  value,\n  setValue,\n  labels,\n  style,\n  wide,\n}: {\n  values: string[];\n  value: string;\n  setValue: (value: string) => void;\n  labels: string[];\n  style?: React.CSSProperties;\n  wide?: boolean;\n}) {\n  const onChange = useCallback(\n    (e: ChangeEvent<HTMLSelectElement>) => {\n      setValue(e.target.value);\n    },\n    [setValue]\n  );\n\n  const copyOfValues = [...values];\n  const copyOfLabels = [...labels];\n\n  if (!copyOfValues.includes(value)) {\n    copyOfLabels.push(value);\n    copyOfValues.push(value);\n  }\n\n  return (\n    <Select wide={wide} style={style} value={value} onChange={onChange}>\n      {copyOfValues.map((value, i) => {\n        return (\n          <option key={value} value={value}>\n            {copyOfLabels[i]}\n          </option>\n        );\n      })}\n    </Select>\n  );\n}\n\nexport const CenterRow = styled.div`\n  display: flex;\n  justify-content: flex-start;\n  align-items: center;\n  flex-direction: row;\n`;\n\n/**\n * A box which centers some darkened text. Useful for displaying\n * *somthing* instead of empty space, if there isn't something to\n * be displayed. Think of it as a placeholder.\n */\nexport const CenterBackgroundSubtext = styled.div`\n  ${({ width, height }: { width: string; height: string }) => css`\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    flex-direction: column;\n    color: ${dfstyles.colors.subtext};\n    width: ${width};\n    height: ${height};\n    user-select: none;\n    text-align: center;\n  `}\n`;\n\n/**\n * Expands to fit the width of container. Is itself a flex box that spreads out its children\n * horizontally.\n */\nexport const SpreadApart = styled.div`\n  width: 100%;\n  box-sizing: border-box;\n  flex-grow: 1;\n  display: inline-flex;\n  justify-content: space-between;\n  flex-direction: row;\n  align-items: center;\n\n  &:first-child {\n    border-left: none;\n  }\n`;\n\n/**\n * Expands to fit the width of container. Is itself a flex box that spreads out its children\n * horizontally.\n */\nexport const Spread = styled.div`\n  width: 100%;\n  box-sizing: border-box;\n  flex-grow: 1;\n  display: inline-flex;\n  justify-content: space-around;\n  flex-direction: row;\n  align-items: center;\n\n  &:first-child {\n    border-left: none;\n  }\n`;\n"
  },
  {
    "path": "src/Frontend/Components/Corner.tsx",
    "content": "import React from 'react';\n\ninterface CornerProps {\n  children: React.ReactNode;\n  top?: boolean;\n  left?: boolean;\n  right?: boolean;\n  bottom?: boolean;\n  style?: React.CSSProperties;\n}\n\nconst cornerStyle: React.CSSProperties = {\n  position: 'absolute',\n  margin: '4px',\n};\n\nexport function Corner({\n  children,\n  style = {},\n  top = false,\n  bottom = false,\n  left = false,\n  right = false,\n}: CornerProps) {\n  const posStyles: React.CSSProperties = {\n    top: top ? 0 : undefined,\n    bottom: bottom ? 0 : undefined,\n    left: left ? 0 : undefined,\n    right: right ? 0 : undefined,\n  };\n  return (\n    <div\n      style={{\n        ...posStyles,\n        ...cornerStyle,\n        ...style,\n      }}\n    >\n      {children}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/DisplayGasPrices.tsx",
    "content": "import { GasPrices } from '@darkforest_eth/types';\nimport React from 'react';\n\nexport function DisplayGasPrices({ gasPrices }: { gasPrices?: GasPrices }) {\n  return (\n    <div>\n      {!gasPrices ? (\n        'unknown'\n      ) : (\n        <>\n          slo: {gasPrices.slow + ' '}\n          avg: {gasPrices.average + ' '}\n          fst: {gasPrices.fast + ' '}\n        </>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/Email.tsx",
    "content": "import React, { useState } from 'react';\nimport {\n  EmailResponse,\n  submitInterestedEmail,\n  submitUnsubscribeEmail,\n} from '../../Backend/Network/UtilityServerAPI';\nimport dfstyles from '../Styles/dfstyles';\nimport Button from './Button';\nimport { Green, Red, Sub } from './Text';\n\nexport const enum EmailCTAMode {\n  SUBSCRIBE,\n  UNSUBSCRIBE,\n}\n\nconst styles: {\n  [name: string]: React.CSSProperties;\n} = {\n  wrapper: {\n    display: 'flex',\n    flexDirection: 'column',\n    alignItems: 'center',\n  },\n  hwrapper: {\n    display: 'flex',\n    flexDirection: 'row',\n    alignItems: 'center',\n  },\n  btn: {\n    background: 'rgba(0, 0, 0, 0)',\n    color: dfstyles.colors.text,\n    marginLeft: '8pt',\n    width: '24pt',\n    height: '24pt',\n    borderRadius: '12pt',\n    lineHeight: '24pt',\n    transition: 'background 0.2s, color 0.2s',\n  },\n  btnHov: {\n    color: dfstyles.colors.background,\n    background: dfstyles.colors.text,\n  },\n  input: {\n    padding: '4px 8px',\n    borderRadius: '5px',\n    border: `1px solid ${dfstyles.colors.text}`,\n    transition: 'color 0.2s, background 0.2s, width 0.2s',\n  },\n};\n\nexport const EmailCTA = ({ mode }: { mode: EmailCTAMode }) => {\n  const [email, setEmail] = useState('');\n  const [status, setStatus] = useState<EmailResponse | null>(null);\n\n  const [focus, setFocus] = useState<boolean>(false);\n\n  const doSubmit = async () => {\n    const response =\n      mode === EmailCTAMode.SUBSCRIBE\n        ? await submitInterestedEmail(email)\n        : await submitUnsubscribeEmail(email);\n    setStatus(response);\n    if (response === EmailResponse.Success) {\n      setEmail('');\n    }\n  };\n\n  const responseToMessage = (response: EmailResponse): React.ReactNode => {\n    if (response === EmailResponse.Success) {\n      if (mode === EmailCTAMode.SUBSCRIBE) {\n        return (\n          <Sub>\n            <Green>email successfully recorded</Green>\n          </Sub>\n        );\n      } else {\n        return (\n          <Sub>\n            <Green>successfully unsubscribed</Green>\n          </Sub>\n        );\n      }\n    } else if (response === EmailResponse.Invalid) return 'invalid address';\n    else if (response === EmailResponse.ServerError) return 'server error';\n    else {\n      console.error('invalid email outcome');\n      return (\n        <Sub>\n          <Red>Error</Red>\n        </Sub>\n      );\n    }\n  };\n\n  return (\n    <div style={styles.wrapper}>\n      <div style={styles.hwrapper}>\n        <p\n          style={{\n            marginRight: '14pt',\n            color: dfstyles.colors.text,\n          }}\n        >\n          {mode === EmailCTAMode.SUBSCRIBE ? 'info' : 'unsubscribe'}:\n        </p>\n\n        <input\n          style={{\n            ...styles.input,\n            color: focus ? dfstyles.colors.text : dfstyles.colors.subtext,\n            background: focus ? dfstyles.colors.backgroundlighter : 'rgba(0, 0, 0, 0)',\n            width: focus ? '9em' : '7em',\n          }}\n          type='text'\n          name={email}\n          value={email}\n          onChange={(e) => setEmail(e.target.value)}\n          placeholder={'name@email.com'}\n          onKeyDown={(e) => {\n            if (e.keyCode === 13) e.preventDefault();\n          }}\n          onKeyUp={(e) => {\n            e.preventDefault();\n            if (e.keyCode === 13) doSubmit();\n          }}\n          onFocus={() => setFocus(true)}\n          onBlur={() => setFocus(false)}\n        />\n\n        <Button onClick={doSubmit} style={styles.btn} hoverStyle={styles.btnHov}>\n          <span>{'>'}</span>\n        </Button>\n      </div>\n      {status !== null && <p style={{ marginTop: '8px' }}>{responseToMessage(status)}</p>}\n    </div>\n  );\n};\n"
  },
  {
    "path": "src/Frontend/Components/GameLandingPageComponents.tsx",
    "content": "import React, { Dispatch, SetStateAction, useLayoutEffect } from 'react';\nimport styled, { css } from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\n\nexport const enum InitRenderState {\n  NONE,\n  LOADING,\n  COMPLETE,\n}\n\ntype LandingWrapperProps = {\n  children: React.ReactNode;\n  initRender: InitRenderState;\n  terminalEnabled: boolean;\n};\n\nconst StyledWrapper = styled.div<{\n  initRender: InitRenderState;\n}>`\n  width: 100%;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  display: flex;\n  flex-direction: row;\n\n  justify-content: ${(props) =>\n    props.initRender !== InitRenderState.NONE ? 'space-between' : 'space-around'};\n`;\n\nexport function Wrapper({ children, initRender }: LandingWrapperProps) {\n  return <StyledWrapper initRender={initRender}>{children}</StyledWrapper>;\n}\n\nconst STWInit = css`\n  position: absolute;\n  width: ${dfstyles.game.terminalWidth};\n  right: 0;\n  top: 0;\n  padding: 1em;\n  font-size: ${dfstyles.game.terminalFontSize};\n`;\n\nconst STWNoInit = css`\n  max-width: 60em;\n  width: 60%;\n  padding: 2em 0;\n  font-size: ${dfstyles.fontSizeS};\n`;\n\nconst StyledTerminalWrapper = styled.div<{\n  initRender: InitRenderState;\n  terminalEnabled: boolean;\n}>`\n  display: ${({ initRender, terminalEnabled }) => {\n    if (initRender === InitRenderState.NONE) return 'block';\n    else return terminalEnabled ? 'block' : 'none';\n  }};\n  border-left: ${({ terminalEnabled, initRender }) =>\n    terminalEnabled && initRender !== InitRenderState.NONE\n      ? `1px solid ${dfstyles.colors.border}`\n      : 'none'};\n  height: 100%;\n  // overflow: hidden;\n  background: ${dfstyles.colors.background};\n  position: relative;\n\n  ${(props) => (props.initRender !== InitRenderState.NONE ? STWInit : STWNoInit)};\n\n  @media (max-width: 660px) {\n    width: 100%;\n    padding: 1.5em 2em;\n  }\n`;\n\nexport function TerminalWrapper({ children, initRender, terminalEnabled }: LandingWrapperProps) {\n  return (\n    <StyledTerminalWrapper initRender={initRender} terminalEnabled={terminalEnabled}>\n      {children}\n    </StyledTerminalWrapper>\n  );\n}\n\nconst StyledTerminalToggler = styled.div<{ terminalEnabled: boolean }>`\n  position: absolute;\n  right: 0;\n  top: 0;\n  height: 100%;\n  width: 1em;\n\n  background: ${dfstyles.colors.text};\n  z-index: 1000;\n\n  color: ${dfstyles.colors.background};\n\n  display: flex;\n  flex-direction: column;\n  justify-content: space-around;\n  align-items: center;\n\n  opacity: 0;\n\n  &:hover {\n    opacity: 1;\n    cursor: pointer;\n  }\n\n  & span {\n    font-size: 1.25em;\n    transform: scaleY(2);\n  }\n`;\n\nexport function TerminalToggler({\n  terminalEnabled,\n  setTerminalEnabled,\n}: {\n  terminalEnabled: boolean;\n  setTerminalEnabled: Dispatch<SetStateAction<boolean>>;\n}) {\n  const uiEmitter = UIEmitter.getInstance();\n  useLayoutEffect(() => {\n    uiEmitter.emit(UIEmitterEvent.UIChange);\n  }, [terminalEnabled, uiEmitter]);\n\n  return (\n    <StyledTerminalToggler\n      terminalEnabled={terminalEnabled}\n      onClick={() => setTerminalEnabled((b: boolean): boolean => !b)}\n    >\n      <span>{terminalEnabled ? '>' : '<'}</span>\n    </StyledTerminalToggler>\n  );\n}\n\nconst StyledGameWindowWrapper = styled.div<{\n  initRender: InitRenderState;\n  terminalEnabled: boolean;\n}>`\n  background: ${dfstyles.colors.background};\n  position: absolute;\n  left: 0;\n  top: 0;\n\n  width: ${(props) =>\n    props.terminalEnabled ? `calc(100% - ${dfstyles.game.terminalWidth})` : '100%'};\n  height: 100%;\n\n  display: ${(props) => (props.initRender !== InitRenderState.NONE ? 'block' : 'none')};\n`;\n\nexport function GameWindowWrapper({ children, initRender, terminalEnabled }: LandingWrapperProps) {\n  return (\n    <StyledGameWindowWrapper initRender={initRender} terminalEnabled={terminalEnabled}>\n      {initRender && <>{children}</>}\n    </StyledGameWindowWrapper>\n  );\n}\n\nexport const Hidden = styled.div`\n  display: none;\n  position: absolute;\n  top: -10000;\n  left: -10000;\n`;\n"
  },
  {
    "path": "src/Frontend/Components/GameWindowComponents.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport dfstyles from '../../Frontend/Styles/dfstyles';\n\nexport const WindowWrapper = styled.div`\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  background: ${dfstyles.colors.background};\n  height: 100%;\n  width: 100%;\n\n  font-size: ${dfstyles.game.fontSize};\n`;\n\nexport const StyledPane = styled.div`\n  display: flex;\n  flex-direction: column;\n\n  .pane-header {\n    padding: 16pt 8pt;\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n    align-items: center;\n    height: 1.5em;\n\n    & > p {\n      text-decoration: underline;\n      line-height: 1em;\n    }\n\n    & > div {\n      display: flex;\n      flex-direction: row;\n      justify-content: flex-end;\n      & > p,\n      & > span,\n      & > div {\n        margin-left: 0.2em;\n      }\n    }\n  }\n  .pane-content {\n    padding: 4pt 8pt 8pt 8pt;\n    position: relative;\n  }\n`;\n\nexport type PaneProps = {\n  title: string | ((small: boolean) => React.ReactNode);\n  children: React.ReactNode;\n  headerItems?: React.ReactNode;\n};\n\nexport const MainWindow = styled.div`\n  // position and sizing\n  height: 100%;\n  width: 100%;\n  position: absolute;\n  top: 0;\n  flex-grow: 1;\n\n  // styling\n  background: ${dfstyles.colors.background};\n\n  // display inner things\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n`;\n\nexport const CanvasContainer = styled.div`\n  flex-grow: 1;\n  position: relative;\n`;\n\nexport const CanvasWrapper = styled.div`\n  position: absolute;\n  width: 100%;\n  height: 100%;\n`;\n\nexport const UpperLeft = styled.div`\n  position: absolute;\n  left: 0;\n\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n`;\n"
  },
  {
    "path": "src/Frontend/Components/Icons.tsx",
    "content": "// should be able to be treated as a text element\nimport { Planet, UpgradeBranchName } from '@darkforest_eth/types';\nimport { DarkForestIcon, IconType } from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\nimport { getPlanetRank, isFullRank } from '../../Backend/Utils/Utils';\nimport { StatIdx } from '../../_types/global/GlobalTypes';\n\n// TODO: Decide if this is the best place to register the custom elements\ncustomElements.define(DarkForestIcon.tagName, DarkForestIcon);\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Icon = createComponent(React, DarkForestIcon.tagName, DarkForestIcon, {\n  // If we had any, we would map DOM events to React handlers passed in as props. For example:\n  // onClick: 'click'\n});\n\n// Re-export the IconType abstract type & the \"enum\" object for easier access\nexport { IconType } from '@darkforest_eth/ui';\n\n// Utilities for calculating an Icon from some context.\n// I think these should be made into utilities that return the `IconType`\n// instead of an Icon JSXElement\nexport const RankIcon = ({ planet }: { planet: Planet | undefined }) => {\n  const rank = getPlanetRank(planet);\n  if (isFullRank(planet)) return <Icon type={IconType.RankMax} />;\n  if (rank === 1) return <Icon type={IconType.RankOne} />;\n  else if (rank === 2) return <Icon type={IconType.RankTwo} />;\n  else if (rank === 3) return <Icon type={IconType.RankThree} />;\n  return <Icon type={IconType.RankFour} />;\n};\n\nexport const BranchIcon = ({ branch }: { branch: number }) => {\n  if (branch === UpgradeBranchName.Range) return <Icon type={IconType.Energy} />;\n  // TODO: Wat\n  else if (branch === UpgradeBranchName.Defense) return <Icon type={IconType.Silver} />;\n  else return <Icon type={IconType.Range} />;\n};\n\nexport const StatIcon = ({ stat }: { stat: StatIdx }) => {\n  if (stat === StatIdx.Defense) return <Icon type={IconType.Defense} />;\n  else if (stat === StatIdx.EnergyGro) return <Icon type={IconType.EnergyGrowth} />;\n  else if (stat === StatIdx.EnergyCap) return <Icon type={IconType.Energy} />;\n  else if (stat === StatIdx.Range) return <Icon type={IconType.Range} />;\n  else if (stat === StatIdx.Speed) return <Icon type={IconType.Speed} />;\n  // TODO: lulz what\n  else return <Icon type={IconType.Defense} />;\n};\n\n/**\n Allow for tweaking the size of an icon based on the context.\n Biome & Spacetype Notifications should fill the notification box\n Others should be 3/4's the size and centered\n*/\ninterface AlertIcon {\n  height?: string;\n  width?: string;\n}\n\nexport const Quasar = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/planettypes/quasar.svg' />;\n};\n\nexport const FoundRuins = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/planettypes/ruins.svg' />;\n};\n\nexport const FoundSpacetimeRip = ({ height, width }: AlertIcon) => {\n  return (\n    <img height={height} width={width} src='/public/icons/alerts/planettypes/tradingpost.svg' />\n  );\n};\n\nexport const FoundSilver = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/planettypes/asteroid.svg' />;\n};\n\nexport const FoundTradingPost = ({ height, width }: AlertIcon) => {\n  return (\n    <img height={height} width={width} src='/public/icons/alerts/planettypes/tradingpost.svg' />\n  );\n};\n\nexport const FoundSpace = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/spacetypes/space.svg' />;\n};\n\nexport const FoundDeepSpace = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/spacetypes/deepspace.svg' />;\n};\n\nexport const FoundDeadSpace = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/spacetypes/deadspace.svg' />;\n};\n\nexport const FoundPirates = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/combat/pirates.svg' />;\n};\n\nexport const Generic = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/generic/generic.svg' />;\n};\n\nexport const ArtifactFound = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/artifacts/find.svg' />;\n};\nexport const ArtifactProspected = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/artifacts/prospect.svg' />;\n};\n\nexport const FoundOcean = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/ocean.svg' />;\n};\n\nexport const FoundForest = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/forest.svg' />;\n};\n\nexport const FoundGrassland = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/grassland.svg' />;\n};\n\nexport const FoundTundra = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/tundra.svg' />;\n};\n\nexport const FoundSwamp = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/swamp.svg' />;\n};\n\nexport const FoundDesert = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/desert.svg' />;\n};\n\nexport const FoundIce = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/ice.svg' />;\n};\n\nexport const FoundWasteland = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/wasteland.svg' />;\n};\n\nexport const FoundLava = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/lava.svg' />;\n};\n\nexport const FoundCorrupted = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/biomes/corrupted.svg' />;\n};\nexport const PlanetAttacked = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/combat/planetattacked.svg' />;\n};\nexport const PlanetLost = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/combat/planetlost.svg' />;\n};\nexport const PlanetConquered = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/combat/planetwon.svg' />;\n};\nexport const MetPlayer = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/combat/metplayer.svg' />;\n};\nexport const TxAccepted = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/transactions/accepted.svg' />;\n};\nexport const TxConfirmed = ({ height, width }: AlertIcon) => {\n  return (\n    <img height={height} width={width} src='/public/icons/alerts/transactions/confirmed.svg' />\n  );\n};\nexport const TxInitialized = ({ height, width }: AlertIcon) => {\n  return (\n    <img height={height} width={width} src='/public/icons/alerts/transactions/initialized.svg' />\n  );\n};\nexport const TxDeclined = ({ height, width }: AlertIcon) => {\n  return <img height={height} width={width} src='/public/icons/alerts/transactions/declined.svg' />;\n};\n"
  },
  {
    "path": "src/Frontend/Components/Input.tsx",
    "content": "import {\n  DarkForestCheckbox,\n  DarkForestColorInput,\n  DarkForestNumberInput,\n  DarkForestTextInput,\n} from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestCheckbox.tagName, DarkForestCheckbox);\ncustomElements.define(DarkForestColorInput.tagName, DarkForestColorInput);\ncustomElements.define(DarkForestNumberInput.tagName, DarkForestNumberInput);\ncustomElements.define(DarkForestTextInput.tagName, DarkForestTextInput);\n\nexport { DarkForestCheckbox, DarkForestColorInput, DarkForestNumberInput, DarkForestTextInput };\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Checkbox = createComponent<\n  DarkForestCheckbox,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestCheckbox>) => void;\n  }\n>(React, DarkForestCheckbox.tagName, DarkForestCheckbox, {\n  onChange: 'input',\n});\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const ColorInput = createComponent<\n  DarkForestColorInput,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestColorInput>) => void;\n  }\n>(React, DarkForestColorInput.tagName, DarkForestColorInput, {\n  // The `input` event is more like what we expect as `onChange` in React\n  onChange: 'input',\n});\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const NumberInput = createComponent<\n  DarkForestNumberInput,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestNumberInput>) => void;\n  }\n>(React, DarkForestNumberInput.tagName, DarkForestNumberInput, {\n  // The `input` event is more like what we expect as `onChange` in React\n  onChange: 'input',\n});\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const TextInput = createComponent<\n  DarkForestTextInput,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestTextInput>) => void;\n    onBlur: (e: Event) => void;\n  }\n>(React, DarkForestTextInput.tagName, DarkForestTextInput, {\n  // The `input` event is more like what we expect as `onChange` in React\n  onChange: 'input',\n  onBlur: 'blur',\n});\n"
  },
  {
    "path": "src/Frontend/Components/Labels/ArtifactLabels.tsx",
    "content": "import { isAncient } from '@darkforest_eth/gamelogic';\nimport {\n  Artifact,\n  ArtifactRarity,\n  ArtifactRarityNames,\n  ArtifactTypeNames,\n  BiomeNames,\n} from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { RarityColors } from '../../Styles/Colors';\nimport { LegendaryLabel } from './LegendaryLabel';\nimport { MythicLabel } from './MythicLabel';\n\nexport const ArtifactRarityText = ({ rarity }: { rarity: ArtifactRarity }) => (\n  <>{ArtifactRarityNames[rarity]}</>\n);\n\nexport const ArtifactBiomeText = ({ artifact }: { artifact: Artifact }) => (\n  <>{isAncient(artifact) ? 'Ancient' : BiomeNames[artifact.planetBiome]}</>\n);\n\nexport const ArtifactTypeText = ({ artifact }: { artifact: Artifact }) => (\n  <>{ArtifactTypeNames[artifact.artifactType]}</>\n);\n\n// colored labels\n\nexport const StyledArtifactRarityLabel = styled.span<{ rarity: ArtifactRarity }>`\n  color: ${({ rarity }) => RarityColors[rarity]};\n`;\n\nexport const ArtifactRarityLabel = ({ rarity }: { rarity: ArtifactRarity }) => (\n  <StyledArtifactRarityLabel rarity={rarity}>\n    <ArtifactRarityText rarity={rarity} />\n  </StyledArtifactRarityLabel>\n);\n\nexport const ArtifactRarityLabelAnim = ({ rarity }: { rarity: ArtifactRarity }) =>\n  rarity === ArtifactRarity.Mythic ? (\n    <MythicLabel />\n  ) : rarity === ArtifactRarity.Legendary ? (\n    <LegendaryLabel />\n  ) : (\n    <ArtifactRarityLabel rarity={rarity} />\n  );\n\n// combined labels\n\nexport const ArtifactRarityBiomeTypeText = ({ artifact }: { artifact: Artifact }) => (\n  <>\n    <ArtifactRarityText rarity={artifact.rarity} /> <ArtifactBiomeText artifact={artifact} />{' '}\n    <ArtifactTypeText artifact={artifact} />\n  </>\n);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/BiomeLabels.tsx",
    "content": "import { isAncient, isLocatable } from '@darkforest_eth/gamelogic';\nimport { Artifact, Biome, BiomeNames, LocatablePlanet, Planet } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { BiomeTextColors } from '../../Styles/Colors';\nimport { AncientLabel, AncientLabelAnim } from '../AncientLabel';\nimport { icyAnim, shakeAnim } from '../BiomeAnims';\nimport { LavaLabel } from './LavaLabel';\nimport { WastelandLabel } from './WastelandLabel';\n\n/** Renders colored text corresponding to a biome */\nexport const BiomeLabel = styled.span<{ biome: Biome }>`\n  color: ${({ biome }) => BiomeTextColors[biome]};\n`;\n\nconst StyledBiomeLabelAnim = styled(BiomeLabel)<{ biome: Biome }>`\n  ${({ biome }) => biome === Biome.CORRUPTED && shakeAnim};\n  ${({ biome }) => biome === Biome.ICE && icyAnim};\n`;\n\n/** Renders animated colored text corresponding to a biome */\nexport const BiomeLabelAnim = ({ biome }: { biome: Biome }) =>\n  biome === Biome.LAVA ? (\n    <LavaLabel />\n  ) : biome === Biome.WASTELAND ? (\n    <WastelandLabel />\n  ) : (\n    <StyledBiomeLabelAnim biome={biome}>{BiomeNames[biome]}</StyledBiomeLabelAnim>\n  );\n\nexport const PlanetBiomeLabelAnim = ({ planet }: { planet: LocatablePlanet }) => (\n  <BiomeLabelAnim biome={planet.biome} />\n);\n\nexport const OptionalPlanetBiomeLabelAnim = ({ planet }: { planet: Planet | undefined }) => (\n  <>{planet && isLocatable(planet) && <PlanetBiomeLabelAnim planet={planet} />}</>\n);\n\nexport const ArtifactBiomeLabel = ({ artifact }: { artifact: Artifact }) =>\n  isAncient(artifact) ? <AncientLabel /> : <BiomeLabel biome={artifact.planetBiome} />;\n\nexport const ArtifactBiomeLabelAnim = ({ artifact }: { artifact: Artifact }) =>\n  isAncient(artifact) ? <AncientLabelAnim /> : <BiomeLabelAnim biome={artifact.planetBiome} />;\n"
  },
  {
    "path": "src/Frontend/Components/Labels/KeywordLabels.tsx",
    "content": "import { TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { TooltipTrigger } from '../../Panes/Tooltip';\nimport dfstyles from '../../Styles/dfstyles';\n\nconst StyledSilverLabel = styled.span`\n  color: ${dfstyles.colors.text};\n`;\nexport const SilverLabel = () => <StyledSilverLabel>Silver</StyledSilverLabel>;\nexport const SilverLabelTip = () => (\n  <TooltipTrigger name={TooltipName.Silver}>\n    <SilverLabel />\n  </TooltipTrigger>\n);\n\nconst StyledScoreLabel = styled.span`\n  color: ${dfstyles.colors.dfyellow};\n  -webkit-text-stroke: 1px;\n`;\nexport const ScoreLabel = () => <StyledScoreLabel>Score</StyledScoreLabel>;\n\nexport const ScoreLabelTip = () => (\n  <TooltipTrigger name={TooltipName.Score}>\n    <ScoreLabel />\n  </TooltipTrigger>\n);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/Labels.tsx",
    "content": "import { EMPTY_ADDRESS } from '@darkforest_eth/constants';\nimport { getPlayerColor } from '@darkforest_eth/procedural';\nimport { EthAddress } from '@darkforest_eth/types';\nimport colorFn from 'color';\nimport React from 'react';\nimport { usePlayer, useUIManager } from '../../Utils/AppHooks';\nimport { Link } from '../CoreUI';\nimport { Sub } from '../Text';\nimport { TextPreview } from '../TextPreview';\n\nexport function AccountLabel({\n  includeAddressIfHasTwitter,\n  ethAddress,\n  width,\n  style,\n}: {\n  includeAddressIfHasTwitter?: boolean;\n  ethAddress?: EthAddress;\n  width?: string;\n  style?: React.CSSProperties;\n}) {\n  const uiManager = useUIManager();\n  const player = usePlayer(uiManager, ethAddress);\n\n  if (player.value !== undefined && player.value.twitter !== undefined) {\n    const color = colorFn(getPlayerColor(player.value.address)).darken(0.5).hex();\n    return (\n      <span style={style}>\n        <TwitterLink twitter={player.value.twitter} color={color} />\n        {includeAddressIfHasTwitter && (\n          <Sub>\n            {' '}\n            <TextPreview\n              text={ethAddress || uiManager.getAccount() || '<no account>'}\n              unFocusedWidth={'50px'}\n              focusedWidth={'50px'}\n            />\n          </Sub>\n        )}\n      </span>\n    );\n  }\n\n  if (ethAddress === EMPTY_ADDRESS) {\n    return <>nobody</>;\n  }\n\n  return (\n    <TextPreview\n      text={ethAddress || uiManager.getAccount() || '<no account>'}\n      unFocusedWidth={width ?? '150px'}\n      focusedWidth={width ?? '150px'}\n    />\n  );\n}\n\n/**\n * Link to a twitter account.\n */\nexport function TwitterLink({ twitter, color }: { twitter: string; color?: string }) {\n  return (\n    <Link color={color} to={`https://twitter.com/${twitter}`}>\n      @{twitter}\n    </Link>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/Labels/LavaLabel.tsx",
    "content": "import { Biome, BiomeNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled, { keyframes } from 'styled-components';\nimport { BiomeTextColors } from '../../Styles/Colors';\n\nconst Wrap = styled.span`\n  position: relative;\n  top: -3px;\n`;\n\nconst Static = styled.span`\n  opacity: 0;\n`;\n\nconst bounce = keyframes`\n  0% { top: 0px; filter: none; }\n  20% { top: -3px; filter: brightness(1.75); }\n  40% { top: -3px; filter: brightness(1.75); }\n  60% { top: 0px; filter: none; }\n  100% { top: 0px; filter: none;}\n`;\n\nconst AnimDelay = styled.span<{ i: number }>`\n  position: relative;\n  animation: ${bounce} 1s ease-in-out infinite alternate;\n  ${({ i }) => `animation-delay: ${-i * 0.1}s;`}\n`;\n\nconst Anim = styled.span`\n  position: absolute;\n  color: ${BiomeTextColors[Biome.LAVA]};\n  transition: color 0.2s;\n  display: inline-flex;\n  flex-direction: row;\n  top: 0;\n  left: 0;\n`;\n\n// TODO pull this logic out from here and SpaceTimeRipLabel into a component\nexport function LavaLabelRaw() {\n  return (\n    <Wrap>\n      <Static>{BiomeNames[Biome.LAVA]}</Static>\n      <Anim>\n        {BiomeNames[Biome.LAVA].split('').map((c, i) => (\n          <AnimDelay i={i} key={i}>\n            {c === ' ' ? <>&nbsp;</> : c}\n          </AnimDelay>\n        ))}\n      </Anim>\n    </Wrap>\n  );\n}\n\nexport const LavaLabel = React.memo(LavaLabelRaw);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/LegendaryLabel.tsx",
    "content": "import { ArtifactRarity, ArtifactRarityNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled, { keyframes } from 'styled-components';\nimport { RarityColors } from '../../Styles/Colors';\n\nconst color = keyframes`\n  0% {\n    color: ${RarityColors[ArtifactRarity.Legendary]};\n  }\n  70% {\n    color: ${RarityColors[ArtifactRarity.Legendary]};\n  }\n  100% {\n    color: #ffffff;\n  }\n`;\n\nconst AnimDelay = styled.span<{ i: number }>`\n  animation: ${color} 1s linear infinite alternate;\n  ${({ i }) => `animation-delay: ${-i * 0.04}s;`}\n`;\n\nconst Anim = styled.span`\n  color: ${RarityColors[ArtifactRarity.Legendary]};\n`;\n\nfunction LegendaryLabelRaw() {\n  return (\n    <Anim>\n      {ArtifactRarityNames[ArtifactRarity.Legendary].split('').map((c, i) => (\n        <AnimDelay i={i} key={i}>\n          {c === ' ' ? <>&nbsp;</> : c}\n        </AnimDelay>\n      ))}\n    </Anim>\n  );\n}\n\nexport const LegendaryLabel = React.memo(LegendaryLabelRaw);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/MythicLabel.tsx",
    "content": "import { ArtifactRarity, ArtifactRarityNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled, { keyframes } from 'styled-components';\nimport { RarityColors } from '../../Styles/Colors';\n\nconst color = keyframes`\n  0% {\n    filter: hue-rotate(0);\n  }\n  30% {\n    filter: hue-rotate(0);\n  }\n  100% {\n    filter: hue-rotate(360deg);\n  }\n`;\n\nconst AnimDelay = styled.span<{ i: number }>`\n  animation: ${color} 1s ease-in-out infinite alternate;\n  ${({ i }) => `animation-delay: ${-i * 0.04}s;`}\n`;\n\nconst Anim = styled.span`\n  color: ${RarityColors[ArtifactRarity.Mythic]};\n`;\n\nexport function MythicLabelText({ text, style }: { text: string; style?: React.CSSProperties }) {\n  return (\n    <Anim style={style}>\n      {text.split('').map((c, i) => (\n        <AnimDelay i={i} key={i}>\n          {c === ' ' ? <>&nbsp;</> : c}\n        </AnimDelay>\n      ))}\n    </Anim>\n  );\n}\n\nfunction MythicLabelRaw() {\n  return <MythicLabelText text={ArtifactRarityNames[ArtifactRarity.Mythic]} />;\n}\n\nexport const MythicLabel = React.memo(MythicLabelRaw);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/PlanetLabels.tsx",
    "content": "import { EMPTY_ADDRESS } from '@darkforest_eth/constants';\nimport { formatNumber } from '@darkforest_eth/gamelogic';\nimport { getPlayerColor } from '@darkforest_eth/procedural';\nimport { Planet, PlanetType, PlanetTypeNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport { getPlanetRank } from '../../../Backend/Utils/Utils';\nimport dfstyles from '../../Styles/dfstyles';\nimport { useAccount, usePlayer, useUIManager } from '../../Utils/AppHooks';\nimport { EmSpacer } from '../CoreUI';\nimport { Colored, Sub, Subber, White } from '../Text';\nimport { TextPreview } from '../TextPreview';\nimport { OptionalPlanetBiomeLabelAnim } from './BiomeLabels';\nimport { TwitterLink } from './Labels';\nimport { SpacetimeRipLabel } from './SpacetimeRipLabel';\n\n/* note that we generally prefer `Planet | undefined` over `Planet` because it\n   makes it easier to pass in selected / hovering planet from the emitters      */\n\n/* stat stuff */\n\nexport function StatText({\n  planet,\n  getStat,\n  style,\n  buff,\n}: {\n  planet: Planet | undefined;\n  getStat: (p: Planet) => number;\n  style?: React.CSSProperties;\n  buff?: number;\n}) {\n  const textStyle = {\n    ...style,\n    color: buff ? dfstyles.colors.dforange : undefined,\n  };\n\n  let content;\n  if (planet) {\n    let stat = getStat(planet);\n    if (buff) {\n      stat *= buff;\n    }\n    content = formatNumber(stat, 2);\n  } else {\n    content = 'n/a';\n  }\n  return <span style={textStyle}>{content}</span>;\n}\n\nexport function GrowthText({\n  planet,\n  getStat,\n  style,\n}: {\n  planet: Planet | undefined;\n  getStat: (p: Planet) => number;\n  style?: React.CSSProperties;\n}) {\n  const paused = planet && planet.pausers > 0;\n  const statStyle = { ...style, textDecoration: paused ? 'line-through' : 'none' };\n\n  return <StatText style={statStyle} planet={planet} getStat={getStat} />;\n}\n\nconst getSilver = (p: Planet) => p.silver;\nexport const SilverText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getSilver} />\n);\n\nconst getSilverCap = (p: Planet) => p.silverCap;\nexport const SilverCapText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getSilverCap} />\n);\n\nconst getEnergy = (p: Planet) => p.energy;\nexport const EnergyText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getEnergy} />\n);\n\nconst getEnergyCap = (p: Planet) => p.energyCap;\nexport const EnergyCapText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getEnergyCap} />\n);\n\nexport function PlanetEnergyLabel({ planet }: { planet: Planet | undefined }) {\n  return (\n    <span>\n      <EnergyText planet={planet} /> <Subber>/</Subber> <EnergyCapText planet={planet} />\n    </span>\n  );\n}\n\nexport function PlanetSilverLabel({ planet }: { planet: Planet | undefined }) {\n  return (\n    <span>\n      <SilverText planet={planet} /> <Subber>/</Subber> <SilverCapText planet={planet} />\n    </span>\n  );\n}\n\nconst getDefense = (p: Planet) => p.defense;\nexport const DefenseText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getDefense} />\n);\n\nconst getRange = (p: Planet) => p.range;\nexport const RangeText = ({ planet, buff }: { planet: Planet | undefined; buff?: number }) => (\n  <StatText planet={planet} getStat={getRange} buff={buff} />\n);\n\nconst getJunk = (p: Planet) => p.spaceJunk;\nexport const JunkText = ({ planet }: { planet: Planet | undefined }) => (\n  <StatText planet={planet} getStat={getJunk} />\n);\n\nconst getSpeed = (p: Planet) => p.speed;\nexport const SpeedText = ({ planet, buff }: { planet: Planet | undefined; buff?: number }) => (\n  <StatText planet={planet} getStat={getSpeed} buff={buff} />\n);\n\nconst getEnergyGrowth = (p: Planet) => p.energyGrowth;\nexport const EnergyGrowthText = ({ planet }: { planet: Planet | undefined }) => (\n  <GrowthText planet={planet} getStat={getEnergyGrowth} />\n);\n\nconst getSilverGrowth = (p: Planet) => p.silverGrowth;\nexport const SilverGrowthText = ({ planet }: { planet: Planet | undefined }) => (\n  <GrowthText planet={planet} getStat={getSilverGrowth} />\n);\n\n// level and rank stuff\nexport const PlanetLevelText = ({ planet }: { planet: Planet | undefined }) =>\n  planet ? <>Level {planet.planetLevel}</> : <></>;\n\nexport const PlanetRankText = ({ planet }: { planet: Planet | undefined }) =>\n  planet ? <>Rank {getPlanetRank(planet)}</> : <></>;\n\nexport const LevelRankText = ({\n  planet,\n  delim,\n}: {\n  planet: Planet | undefined;\n  delim?: string;\n}) => (\n  <>\n    <PlanetLevelText planet={planet} />\n    {delim || ', '}\n    <PlanetRankText planet={planet} />\n  </>\n);\n\nexport const LevelRankTextEm = ({\n  planet,\n  delim,\n}: {\n  planet: Planet | undefined;\n  delim?: string;\n}) =>\n  planet ? (\n    <Sub>\n      lvl <White>{planet.planetLevel}</White>\n      {delim || ', '}\n      rank <White>{getPlanetRank(planet)}</White>\n    </Sub>\n  ) : (\n    <></>\n  );\n\nexport const PlanetTypeLabelAnim = ({ planet }: { planet: Planet | undefined }) => (\n  <>\n    {planet &&\n      (planet.planetType === PlanetType.TRADING_POST ? (\n        <SpacetimeRipLabel />\n      ) : (\n        PlanetTypeNames[planet.planetType]\n      ))}\n  </>\n);\n\nexport const PlanetBiomeTypeLabelAnim = ({ planet }: { planet: Planet | undefined }) => (\n  <>\n    {planet?.planetType !== PlanetType.TRADING_POST && (\n      <>\n        <OptionalPlanetBiomeLabelAnim planet={planet} />\n        <EmSpacer width={0.5} />\n      </>\n    )}\n    <PlanetTypeLabelAnim planet={planet} />\n  </>\n);\n\nexport const PlanetLevel = ({ planet }: { planet: Planet | undefined }) => {\n  if (!planet) return <></>;\n  return (\n    <>\n      <Sub>{'Level '}</Sub>\n      {planet.planetLevel}\n    </>\n  );\n};\n\nexport const PlanetRank = ({ planet }: { planet: Planet | undefined }) => {\n  if (!planet) return <></>;\n  return (\n    <>\n      <Sub>{'Rank '}</Sub>\n      {getPlanetRank(planet)}\n    </>\n  );\n};\n\n/**\n * Either 'yours' in green text,\n */\nexport function PlanetOwnerLabel({\n  planet,\n  abbreviateOwnAddress,\n  colorWithOwnerColor,\n}: {\n  planet: Planet | undefined;\n  abbreviateOwnAddress?: boolean;\n  colorWithOwnerColor?: boolean;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const owner = usePlayer(uiManager, planet?.owner);\n\n  const defaultColor = dfstyles.colors.subtext;\n\n  if (!planet) return <>/</>;\n\n  if (planet.owner === EMPTY_ADDRESS) return <Sub>Unclaimed</Sub>;\n\n  if (abbreviateOwnAddress && planet.owner === account) {\n    return <Colored color={dfstyles.colors.dfgreen}>yours!</Colored>;\n  }\n\n  const color = colorWithOwnerColor ? defaultColor : getPlayerColor(planet.owner);\n  if (planet.owner && owner.value?.twitter) {\n    return <TwitterLink color={color} twitter={owner.value.twitter} />;\n  }\n\n  return (\n    <Colored color={color}>\n      <TextPreview text={planet.owner} />\n    </Colored>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/Labels/SpacetimeRipLabel.tsx",
    "content": "import { PlanetType, PlanetTypeNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled, { keyframes } from 'styled-components';\n\nconst Wrap = styled.span`\n  position: relative;\n  top: -5px;\n`;\n\nconst Static = styled.span`\n  opacity: 0;\n`;\n\nconst bounce = keyframes`\n  from {\n    top: 3px;\n    filter: hue-rotate(0);\n  }\n  to {\n    top: -3px;\n    filter: hue-rotate(360deg);\n  }\n`;\n\nconst AnimDelay = styled.span<{ i: number }>`\n  position: relative;\n  animation: ${bounce} 0.5s ease-in-out infinite alternate;\n  ${({ i }) => `animation-delay: ${-i * 0.04}s;`}\n`;\n\nconst Anim = styled.span`\n  position: absolute;\n  color: hsl(0, 100%, 75%);\n  transition: color 0.2s;\n  display: inline-flex;\n  flex-direction: row;\n  top: 0;\n  left: 0;\n`;\n\nfunction SpacetimeRipLabelRaw() {\n  return (\n    <Wrap>\n      <Static>{PlanetTypeNames[PlanetType.TRADING_POST]}</Static>\n      <Anim>\n        {PlanetTypeNames[PlanetType.TRADING_POST].split('').map((c, i) => (\n          <AnimDelay i={i} key={i}>\n            {c === ' ' ? <>&nbsp;</> : c}\n          </AnimDelay>\n        ))}\n      </Anim>\n    </Wrap>\n  );\n}\n\nexport const SpacetimeRipLabel = React.memo(SpacetimeRipLabelRaw);\n"
  },
  {
    "path": "src/Frontend/Components/Labels/WastelandLabel.tsx",
    "content": "import { Biome, BiomeNames } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { BiomeTextColors } from '../../Styles/Colors';\nimport { burnAnim, wiggle } from '../BiomeAnims';\n\nconst Wrap = styled.span`\n  position: relative;\n  top: -3px;\n`;\n\nconst Static = styled.span`\n  opacity: 0;\n`;\n\nconst AnimDelay = styled.span<{ i: number }>`\n  position: relative;\n  animation: ${wiggle} ${({ i }) => 9.4 + ((i * 0.7) % 1.0)}s linear infinite;\n  ${({ i }) => `animation-delay: ${-i * 5.9}s;`}\n  color: ${BiomeTextColors[Biome.WASTELAND]};\n`;\n\nconst Anim = styled.span`\n  position: absolute;\n  ${burnAnim};\n  transition: color 0.2s;\n  display: inline-flex;\n  flex-direction: row;\n  top: 0;\n  left: 0;\n`;\n\n// TODO pull this logic out from here and SpaceTimeRipLabel into a component\nexport function WastelandLabelRaw() {\n  return (\n    <Wrap>\n      <Static>{BiomeNames[Biome.WASTELAND]}</Static>\n      <Anim>\n        {BiomeNames[Biome.WASTELAND].split('').map((c, i) => (\n          <AnimDelay i={i} key={i}>\n            {c === ' ' ? <>&nbsp;</> : c}\n          </AnimDelay>\n        ))}\n      </Anim>\n    </Wrap>\n  );\n}\n\nexport const WastelandLabel = React.memo(WastelandLabelRaw);\n"
  },
  {
    "path": "src/Frontend/Components/LoadingSpinner.tsx",
    "content": "import React, { useEffect, useState } from 'react';\n\nexport function LoadingSpinner({ initialText, rate }: { initialText?: string; rate?: number }) {\n  const speed = rate || 100;\n  const text = initialText || 'Loading...';\n  const [currentText, setCurrentText] = useState(text);\n\n  useEffect(() => {\n    let cursor = 0;\n    const interval = setInterval(() => {\n      cursor = (cursor + 1) % text.length;\n\n      setCurrentText(text.substr(cursor) + text.substr(0, cursor));\n    }, speed);\n\n    return () => clearInterval(interval);\n  }, [text, speed]);\n\n  return <span>{currentText}</span>;\n}\n"
  },
  {
    "path": "src/Frontend/Components/MaybeShortcutButton.tsx",
    "content": "import { Setting } from '@darkforest_eth/types';\nimport React from 'react';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { useBooleanSetting } from '../Utils/SettingsHooks';\nimport { Btn, ShortcutBtn } from './Btn';\n\n/**\n * A button that will show shortcuts if enabled globally in the game, otherwise it will display a normal button\n *\n * Must ONLY be used when a GameUIManager is available.\n */\nexport function MaybeShortcutButton(\n  props: React.ComponentProps<typeof Btn> | React.ComponentProps<typeof ShortcutBtn>\n) {\n  const uiManager = useUIManager();\n  const [disableDefaultShortcuts] = useBooleanSetting(uiManager, Setting.DisableDefaultShortcuts);\n\n  if (disableDefaultShortcuts) {\n    return <Btn {...props} />;\n  } else {\n    return <ShortcutBtn {...props} />;\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Components/MineArtifactButton.tsx",
    "content": "import { isUnconfirmedFindArtifactTx, isUnconfirmedProspectPlanetTx } from '@darkforest_eth/serde';\nimport { ArtifactType, Planet, PlanetType, TooltipName } from '@darkforest_eth/types';\nimport React, { useCallback, useMemo } from 'react';\nimport styled from 'styled-components';\nimport { isFindable } from '../../Backend/GameLogic/ArrivalUtils';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport { useAccount, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { MINE_ARTIFACT } from '../Utils/ShortcutConstants';\nimport { ShortcutBtn } from './Btn';\nimport { MaybeShortcutButton } from './MaybeShortcutButton';\nimport { Row } from './Row';\nimport { Red } from './Text';\n\nconst StyledArtifactRow = styled(Row)`\n  .button {\n    margin-top: 4px;\n    margin-bottom: 4px;\n\n    flex-grow: 1;\n  }\n`;\n\nexport function MineArtifactButton({\n  planetWrapper,\n}: {\n  planetWrapper: Wrapper<Planet | undefined>;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const gameManager = uiManager.getGameManager();\n  const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined);\n  const owned = planetWrapper.value?.owner === account;\n\n  const isRuins = useMemo(\n    () => planetWrapper.value?.planetType === PlanetType.RUINS,\n    [planetWrapper]\n  );\n\n  const isDestroyed = useMemo(() => planetWrapper.value?.destroyed, [planetWrapper]);\n\n  const hasGear = useMemo(\n    () =>\n      planetWrapper.value?.heldArtifactIds\n        .map((id) => uiManager.getArtifactWithId(id))\n        .find((artifact) => artifact?.artifactType === ArtifactType.ShipGear),\n    [planetWrapper, uiManager]\n  );\n\n  const prospectable = isRuins && hasGear;\n\n  const prospecting = useMemo(\n    () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedProspectPlanetTx),\n    [planetWrapper]\n  );\n\n  const findable = planetWrapper.value && isFindable(planetWrapper.value, currentBlockNumber);\n\n  const find = useCallback(() => {\n    if (!planetWrapper.value) return;\n    gameManager.findArtifact(planetWrapper.value.locationId);\n  }, [gameManager, planetWrapper]);\n\n  const finding = useMemo(\n    () => planetWrapper.value?.transactions?.hasTransaction(isUnconfirmedFindArtifactTx),\n    [planetWrapper]\n  );\n\n  const mine = useCallback(async () => {\n    if (!planetWrapper.value) return;\n\n    const tx = await gameManager.prospectPlanet(planetWrapper.value.locationId);\n    await tx.confirmedPromise;\n    await gameManager.findArtifact(planetWrapper.value.locationId);\n  }, [gameManager, planetWrapper]);\n\n  const alreadyMined = planetWrapper.value?.hasTriedFindingArtifact;\n\n  return (\n    <StyledArtifactRow>\n      {owned && !alreadyMined && !isDestroyed && (\n        <>\n          {isRuins && !findable && (\n            <MaybeShortcutButton\n              className='button'\n              size='stretch'\n              active={prospecting}\n              disabled={!prospectable}\n              onClick={mine}\n              onShortcutPressed={mine}\n              shortcutKey={MINE_ARTIFACT}\n              shortcutText={MINE_ARTIFACT}\n            >\n              <TooltipTrigger\n                name={hasGear ? TooltipName.FindArtifact : TooltipName.Empty}\n                extraContent={\n                  hasGear ? (\n                    ''\n                  ) : (\n                    <Red>You must have a Gear ship on this planet to prospect artifacts.</Red>\n                  )\n                }\n              >\n                {!prospecting ? 'Prospect Artifact' : 'Prospecting...'}\n              </TooltipTrigger>\n            </MaybeShortcutButton>\n          )}\n\n          {isRuins && findable && (\n            <ShortcutBtn\n              className='button'\n              size='stretch'\n              active={finding}\n              disabled={!findable || !hasGear}\n              onClick={find}\n              onShortcutPressed={find}\n              shortcutKey={MINE_ARTIFACT}\n              shortcutText={MINE_ARTIFACT}\n            >\n              <TooltipTrigger\n                name={hasGear ? TooltipName.FindArtifact : TooltipName.Empty}\n                extraContent={\n                  hasGear ? (\n                    ''\n                  ) : (\n                    <>\n                      <Red>You must have a Gear ship on this planet to find artifacts.</Red>\n                      <Red>You must find an artifact within 256 blocks of prospecting.</Red>\n                    </>\n                  )\n                }\n              >\n                {!finding ? 'Find Artifact' : 'Finding Artifact...'}\n              </TooltipTrigger>\n            </ShortcutBtn>\n          )}\n        </>\n      )}\n    </StyledArtifactRow>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/Modal.tsx",
    "content": "import { DarkForestModal, PositionChangedEvent } from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestModal.tagName, DarkForestModal);\n\nexport { DarkForestModal, PositionChangedEvent };\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Modal = createComponent<\n  DarkForestModal,\n  {\n    onMouseDown: (evt: Event & React.MouseEvent<DarkForestModal>) => void;\n    onPositionChanged: (evt: PositionChangedEvent) => void;\n  }\n>(React, DarkForestModal.tagName, DarkForestModal, {\n  onMouseDown: 'mousedown',\n  onPositionChanged: 'position-changed',\n});\n"
  },
  {
    "path": "src/Frontend/Components/OpenPaneButtons.tsx",
    "content": "import { LocationId } from '@darkforest_eth/types';\nimport React, { useCallback } from 'react';\nimport { BroadcastPane, BroadcastPaneHelpContent } from '../Panes/BroadcastPane';\nimport { HatPane } from '../Panes/HatPane';\nimport {\n  ManagePlanetArtifactsHelpContent,\n  ManagePlanetArtifactsPane,\n  PlanetInfoHelpContent,\n} from '../Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane';\nimport { PlanetInfoPane } from '../Panes/PlanetInfoPane';\nimport { UpgradeDetailsPane, UpgradeDetailsPaneHelpContent } from '../Panes/UpgradeDetailsPane';\nimport {\n  TOGGLE_BROADCAST_PANE,\n  TOGGLE_HAT_PANE,\n  TOGGLE_PLANET_ARTIFACTS_PANE,\n  TOGGLE_PLANET_INFO_PANE,\n  TOGGLE_UPGRADES_PANE,\n} from '../Utils/ShortcutConstants';\nimport { ModalHandle } from '../Views/ModalPane';\nimport { MaybeShortcutButton } from './MaybeShortcutButton';\n\nexport function OpenPaneButton({\n  modal,\n  title,\n  element,\n  helpContent,\n  shortcut,\n}: {\n  modal: ModalHandle;\n  title: string;\n  element: () => React.ReactElement;\n  helpContent?: React.ReactElement;\n  shortcut?: string;\n}) {\n  const open = useCallback(() => {\n    modal.push({\n      title,\n      element,\n      helpContent,\n    });\n  }, [title, element, helpContent, modal]);\n\n  return (\n    <MaybeShortcutButton\n      size='stretch'\n      onClick={open}\n      onShortcutPressed={open}\n      shortcutKey={shortcut}\n      shortcutText={shortcut}\n    >\n      {title}\n    </MaybeShortcutButton>\n  );\n}\n\nexport function OpenHatPaneButton({\n  modal,\n  planetId,\n}: {\n  modal: ModalHandle;\n  planetId: LocationId | undefined;\n}) {\n  return (\n    <OpenPaneButton\n      modal={modal}\n      title='Hat'\n      shortcut={TOGGLE_HAT_PANE}\n      element={() => <HatPane modal={modal} initialPlanetId={planetId} />}\n    />\n  );\n}\n\nexport function OpenBroadcastPaneButton({\n  modal,\n  planetId,\n}: {\n  modal: ModalHandle;\n  planetId: LocationId | undefined;\n}) {\n  return (\n    <OpenPaneButton\n      modal={modal}\n      title='Broadcast'\n      shortcut={TOGGLE_BROADCAST_PANE}\n      element={() => <BroadcastPane modal={modal} initialPlanetId={planetId} />}\n      helpContent={BroadcastPaneHelpContent()}\n    />\n  );\n}\n\nexport function OpenUpgradeDetailsPaneButton({\n  modal,\n  planetId,\n}: {\n  modal: ModalHandle;\n  planetId: LocationId | undefined;\n}) {\n  return (\n    <OpenPaneButton\n      modal={modal}\n      title='Upgrade'\n      shortcut={TOGGLE_UPGRADES_PANE}\n      element={() => <UpgradeDetailsPane modal={modal} initialPlanetId={planetId} />}\n      helpContent={UpgradeDetailsPaneHelpContent()}\n    />\n  );\n}\nexport function OpenManagePlanetArtifactsButton({\n  modal,\n  planetId,\n}: {\n  modal: ModalHandle;\n  planetId: LocationId | undefined;\n}) {\n  return (\n    <OpenPaneButton\n      modal={modal}\n      title='Inventory'\n      shortcut={TOGGLE_PLANET_ARTIFACTS_PANE}\n      element={() => <ManagePlanetArtifactsPane modal={modal} initialPlanetId={planetId} />}\n      helpContent={ManagePlanetArtifactsHelpContent()}\n    />\n  );\n}\n\nexport function OpenPlanetInfoButton({\n  modal,\n  planetId,\n}: {\n  modal: ModalHandle;\n  planetId: LocationId | undefined;\n}) {\n  return (\n    <OpenPaneButton\n      modal={modal}\n      title='Info'\n      shortcut={TOGGLE_PLANET_INFO_PANE}\n      element={() => <PlanetInfoPane initialPlanetId={planetId} />}\n      helpContent={PlanetInfoHelpContent()}\n    />\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/PluginModal.tsx",
    "content": "import { ModalId } from '@darkforest_eth/types';\nimport React, { useCallback, useState } from 'react';\nimport ReactDOM from 'react-dom';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { useEmitterSubscribe } from '../Utils/EmitterHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { PluginElements } from './CoreUI';\n\nexport function PluginModal({\n  title,\n  container,\n  id,\n  width,\n  onClose,\n  onRender,\n}: {\n  title: string;\n  id: ModalId;\n  container: Element;\n  width?: string;\n  onClose: () => void;\n  onRender: (el: HTMLDivElement) => void;\n}) {\n  const uiManager = useUIManager();\n  const modalManager = uiManager.getModalManager();\n  /**\n   * We use the existence of a window position for a given modal as an indicator\n   * that it should be opened on page load. This is to satisfy the feature of\n   * peristent modal positions across browser sessions for a given account.\n   */\n  const isModalOpen = (modalId: ModalId) => {\n    const pos = modalManager.getModalPosition(modalId);\n    if (pos) {\n      return pos.state !== 'closed';\n    } else {\n      return false;\n    }\n  };\n\n  const [visible, setVisible] = useState(isModalOpen(id));\n  useEmitterSubscribe(\n    modalManager.modalPositionChanged$,\n    (modalId) => {\n      if (modalId === id) {\n        setVisible(isModalOpen(id));\n      }\n    },\n    [setVisible, isModalOpen, id]\n  );\n\n  const handleRef = useCallback(\n    (el: HTMLDivElement | null) => {\n      if (el !== null) {\n        onRender(el);\n      }\n    },\n    [onRender]\n  );\n\n  return ReactDOM.createPortal(\n    <ModalPane id={id} title={title} visible={visible} onClose={onClose} width={width}>\n      <PluginElements ref={handleRef} />\n    </ModalPane>,\n    container\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/ReadMore.tsx",
    "content": "import React, { useCallback, useState } from 'react';\nimport styled from 'styled-components';\nimport { EmSpacer, TextButton } from './CoreUI';\n\nexport function ReadMore({\n  children,\n  height,\n  text,\n  toggleButtonMargin,\n}: {\n  children: React.ReactNode[] | React.ReactNode;\n  height?: string;\n  text?: string;\n  toggleButtonMargin?: string;\n}) {\n  const [collapsed, setCollapsed] = useState(true);\n\n  const toggle = useCallback(() => {\n    setCollapsed((collapsed) => !collapsed);\n  }, []);\n\n  return (\n    <>\n      <ContentContainer style={{ height: collapsed ? height : 'unset' }}>\n        {children}\n      </ContentContainer>\n      {!collapsed && <EmSpacer height={0.1} />}\n      <TextButton\n        style={{\n          fontSize: '80%',\n          display: 'block',\n          textAlign: 'right',\n          marginTop: collapsed ? toggleButtonMargin ?? '-0.6em' : undefined,\n        }}\n        onClick={toggle}\n      >\n        {text ?? (collapsed ? 'more' : 'less')} info\n      </TextButton>\n    </>\n  );\n}\n\nconst ContentContainer = styled.div`\n  margin-top: 4px;\n  overflow: hidden;\n  text-align: justify;\n  line-height: 1.2em;\n`;\n"
  },
  {
    "path": "src/Frontend/Components/RemoteModal.tsx",
    "content": "import { ModalId } from '@darkforest_eth/types';\nimport * as React from 'react';\nimport ReactDOM from 'react-dom';\nimport { ModalPane } from '../Views/ModalPane';\n\n/**\n * Allows you to instantiate a modal, and render it into the desired element.\n * Useful for loading temporary modals from ANYWHERE in the UI, not just\n * {@link GameWindowLayout}\n */\nexport function RemoteModal({\n  title,\n  container,\n  children,\n  visible,\n  onClose,\n  id,\n  width,\n}: React.PropsWithChildren<{\n  title: string;\n  id: ModalId;\n  container: Element;\n  visible: boolean;\n  onClose: () => void;\n  width?: string;\n}>) {\n  return ReactDOM.createPortal(\n    <ModalPane id={id} title={title} visible={visible} onClose={onClose} width={width}>\n      {children}\n    </ModalPane>,\n    container\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Components/Row.tsx",
    "content": "import { DarkForestRow } from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestRow.tagName, DarkForestRow);\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Row = createComponent(React, DarkForestRow.tagName, DarkForestRow);\n"
  },
  {
    "path": "src/Frontend/Components/Slider.tsx",
    "content": "import { DarkForestSlider, DarkForestSliderHandle } from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestSlider.tagName, DarkForestSlider);\ncustomElements.define(DarkForestSliderHandle.tagName, DarkForestSliderHandle);\n\nexport { DarkForestSlider, DarkForestSliderHandle };\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Slider = createComponent<\n  DarkForestSlider,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestSlider>) => void;\n  }\n>(React, DarkForestSlider.tagName, DarkForestSlider, {\n  // The `input` event is more like what we expect as `onChange` in React (live-updating as you slide)\n  onChange: 'input',\n});\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const SliderHandle = createComponent<\n  DarkForestSliderHandle,\n  {\n    onChange: (e: Event & React.ChangeEvent<DarkForestSliderHandle>) => void;\n  }\n>(React, DarkForestSliderHandle.tagName, DarkForestSliderHandle, {\n  // The `change` event on a handle is all we really care about on handles\n  onChange: 'change',\n});\n"
  },
  {
    "path": "src/Frontend/Components/Text.tsx",
    "content": "import { BLOCK_EXPLORER_URL } from '@darkforest_eth/constants';\nimport { isLocatable } from '@darkforest_eth/gamelogic';\nimport { artifactName, getPlanetName } from '@darkforest_eth/procedural';\nimport {\n  Artifact,\n  ArtifactId,\n  Chunk,\n  Planet,\n  Transaction,\n  WorldCoords,\n} from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport Viewport from '../Game/Viewport';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport { Link } from './CoreUI';\n\nexport function BlinkCursor() {\n  const [visible, setVisible] = useState<boolean>(false);\n\n  useEffect(() => {\n    const id = setInterval(() => {\n      setVisible((v) => {\n        return !v;\n      });\n    }, 500);\n    return () => clearInterval(id);\n  }, []);\n\n  return <span>{visible ? '|' : ''} </span>;\n}\n\nexport const Green = styled.span`\n  color: ${dfstyles.colors.dfgreen};\n`;\nexport const Sub = styled.span`\n  color: ${dfstyles.colors.subtext};\n`;\nexport const Subber = styled.span`\n  color: ${dfstyles.colors.subbertext};\n`;\nexport const Text = styled.span`\n  color: ${dfstyles.colors.text};\n`;\nexport const White = styled.span`\n  color: ${dfstyles.colors.dfwhite};\n`;\nexport const Red = styled.span`\n  color: ${dfstyles.colors.dfred};\n`;\nexport const Gold = styled.span`\n  color: ${dfstyles.colors.dfyellow};\n`;\n\nexport const Colored = styled.span<{ color: string }>`\n  color: ${({ color }) => color};\n`;\n\nexport const Blue = styled.span`\n  color: ${dfstyles.colors.dfblue};\n`;\nexport const Invisible = styled.span`\n  color: rgba(0, 0, 0, 0);\n`;\n\nexport const Smaller = styled.span`\n  font-size: 80%;\n`;\n\nexport const HideSmall = styled.span`\n  @media (max-width: ${dfstyles.screenSizeS}) {\n    display: none;\n  }\n`;\n\nexport function TxLink({ tx }: { tx: Transaction }) {\n  if (tx.hash) {\n    return (\n      <>\n        <u>\n          <Link onClick={() => window.open(`${BLOCK_EXPLORER_URL}/${tx.hash}`)}>\n            {tx.hash.substring(0, 7)}\n          </Link>\n        </u>\n      </>\n    );\n  }\n\n  return <Sub>-</Sub>;\n}\n\nexport function CenterPlanetLink({\n  planet,\n  children,\n}: {\n  planet: Planet;\n  children: React.ReactNode;\n}) {\n  const uiManager = useUIManager();\n  return (\n    <a>\n      <u\n        onClick={() => {\n          if (isLocatable(planet)) {\n            uiManager.centerPlanet(planet);\n          }\n        }}\n      >\n        {children}\n      </u>\n    </a>\n  );\n}\n\nexport function ArtifactNameLink({ id }: { id: ArtifactId }) {\n  const uiManager = useUIManager();\n  const artifact: Artifact | undefined = uiManager && uiManager.getArtifactWithId(id);\n\n  const click = () => {\n    UIEmitter.getInstance().emit(UIEmitterEvent.ShowArtifact, artifact);\n  };\n\n  return <Link onClick={click}>{artifactName(artifact)}</Link>;\n}\n\nexport function PlanetNameLink({ planet }: { planet: Planet }) {\n  return <CenterPlanetLink planet={planet}>{getPlanetName(planet)}</CenterPlanetLink>;\n}\n\nexport function CenterChunkLink({ chunk, children }: { chunk: Chunk; children: React.ReactNode }) {\n  return <Link onClick={() => Viewport.getInstance().centerChunk(chunk)}>{children}</Link>;\n}\n\nexport function FAQ04Link({ children }: { children: React.ReactNode }) {\n  return <Link to={'https://blog.zkga.me/df-04-faq'}>{children} </Link>;\n}\n\nexport const LongDash = () => (\n  <span style={{ transform: 'scale(1.5, 1)', display: 'inline-block' }}>-</span>\n);\n\nexport const Coords = ({ coords: { x, y } }: { coords: WorldCoords }) => (\n  <Sub>\n    (<Text>{x}</Text>, <Text>{y}</Text>)\n  </Sub>\n);\n"
  },
  {
    "path": "src/Frontend/Components/TextLoadingBar.tsx",
    "content": "import React, { useImperativeHandle, useState } from 'react';\nimport styled from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\nimport { Sub } from './Text';\n\nexport interface LoadingBarHandle {\n  setFractionCompleted: (fractionCompleted: number) => void;\n}\n\ninterface LoadingBarProps {\n  prettyEntityName: string;\n}\n\nexport const TextLoadingBar = React.forwardRef<LoadingBarHandle | undefined, LoadingBarProps>(\n  TextLoadingBarImpl\n);\n\nexport function TextLoadingBarImpl(\n  { prettyEntityName }: LoadingBarProps,\n  ref: React.Ref<LoadingBarHandle>\n) {\n  // value between 0 and 1\n  const [fractionCompleted, setFractionCompleted] = useState(0);\n\n  useImperativeHandle(ref, () => ({ setFractionCompleted }));\n\n  const progressWidth = 20;\n\n  let progressText = '';\n\n  for (let i = 0; i < progressWidth; i++) {\n    if (i < Math.floor(progressWidth * fractionCompleted)) {\n      progressText += '=';\n    } else {\n      progressText += '\\u00a0'; // &nbsp;\n    }\n  }\n\n  const percentText = Math.floor(fractionCompleted * 100)\n    .toString()\n    .padStart(3, ' ');\n\n  return (\n    <span>\n      [<Sub>{progressText}</Sub>]{' '}\n      <span style={{ fontWeight: percentText === '100' ? 'bold' : undefined }}>\n        {percentText}%{' '}\n      </span>\n      <LoadingTitle>{prettyEntityName}</LoadingTitle>\n    </span>\n  );\n}\n\nconst LoadingTitle = styled.div`\n  display: inline-block;\n  color: ${dfstyles.colors.text};\n`;\n"
  },
  {
    "path": "src/Frontend/Components/TextPreview.tsx",
    "content": "import React, { useCallback, useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport { TextInput } from './Input';\n\nconst DEFAULT_UNFOCUSED_WIDTH = '50px';\nconst DEFAULT_FOCUSED_WIDTH = '150px';\n\nexport function TextPreview({\n  text = '',\n  unFocusedWidth = DEFAULT_UNFOCUSED_WIDTH,\n  focusedWidth = DEFAULT_FOCUSED_WIDTH,\n  style,\n}: {\n  text?: string;\n  unFocusedWidth?: string;\n  focusedWidth?: string;\n  maxLength?: number;\n  style?: React.CSSProperties;\n}) {\n  const [isTextBox, setIsTextbox] = useState(false);\n\n  const onClick = useCallback((e: React.SyntheticEvent) => {\n    e.stopPropagation();\n    setIsTextbox(true);\n  }, []);\n\n  const onBlur = useCallback(() => {\n    setIsTextbox(false);\n  }, []);\n\n  if (isTextBox) {\n    return (\n      <InputContainer style={style} width={focusedWidth}>\n        <TextInput selected={true} readonly={true} value={text} onBlur={onBlur} />\n      </InputContainer>\n    );\n  }\n\n  return (\n    <ShortenedText style={style} width={unFocusedWidth} onClick={onClick}>\n      {text}\n    </ShortenedText>\n  );\n}\n\nconst ShortenedText = styled.span`\n  ${({ width }: { width: string }) => css`\n    cursor: zoom-in;\n    display: inline-block;\n    width: ${width};\n    overflow: hidden;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n    vertical-align: bottom;\n  `}\n`;\n\nconst InputContainer = styled.div`\n  ${({ width }: { width: string }) => css`\n    display: inline-block;\n    width: ${width};\n  `}\n`;\n"
  },
  {
    "path": "src/Frontend/Components/Theme.tsx",
    "content": "import { DarkForestTheme } from '@darkforest_eth/ui';\nimport { createComponent } from '@lit-labs/react';\nimport React from 'react';\n\ncustomElements.define(DarkForestTheme.tagName, DarkForestTheme);\n\n// This wraps the customElement in a React wrapper to make it behave exactly like a React component\nexport const Theme = createComponent(React, DarkForestTheme.tagName, DarkForestTheme, {\n  // If we had any, we would map DOM events to React handlers passed in as props. For example:\n  // onClick: 'click'\n});\n"
  },
  {
    "path": "src/Frontend/Components/TimeUntil.tsx",
    "content": "import React, { useEffect, useState } from 'react';\n\n/**\n * Given a timestamp, displays the amount of time until the timestamp from now in hh:mm:ss format.\n * If the timestamp is in the past, displays the given hardcoded value.\n */\nexport function TimeUntil({ timestamp, ifPassed }: { timestamp: number; ifPassed: string }) {\n  const [value, setValue] = useState('');\n\n  useEffect(() => {\n    const update = () => {\n      const msWait = timestamp - Date.now();\n\n      if (msWait <= 0) {\n        setValue(ifPassed);\n      } else {\n        setValue(formatDuration(msWait));\n      }\n    };\n\n    const interval = setInterval(() => {\n      update();\n    }, 1000);\n\n    update();\n    return () => clearInterval(interval);\n  }, [timestamp, ifPassed]);\n\n  return <span>{value}</span>;\n}\n\nexport function formatDuration(msDuration: number): string {\n  const hoursWait = Math.floor(msDuration / 1000 / 60 / 60);\n  const minutes = Math.floor((msDuration - hoursWait * 60 * 60 * 1000) / 1000 / 60);\n  const seconds = Math.floor(\n    (msDuration - hoursWait * 60 * 60 * 1000 - minutes * 60 * 1000) / 1000\n  );\n  const str =\n    hoursWait + ':' + (minutes + '').padStart(2, '0') + ':' + (seconds + '').padStart(2, '0');\n  return str;\n}\n"
  },
  {
    "path": "src/Frontend/EntryPoints/index.tsx",
    "content": "import * as React from 'react';\nimport * as ReactDOM from 'react-dom';\nimport App from '../Pages/App';\nimport '../Styles/font/stylesheet.css';\nimport '../Styles/icomoon/style.css';\nimport '../Styles/preflight.css';\nimport '../Styles/style.css';\n\nReactDOM.render(<App />, document.getElementById('root'));\n"
  },
  {
    "path": "src/Frontend/Game/ControllableCanvas.tsx",
    "content": "import { Renderer } from '@darkforest_eth/renderer';\nimport { CursorState, ModalManagerEvent, Setting } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport styled from 'styled-components';\nimport { useUIManager } from '../Utils/AppHooks';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport Viewport from './Viewport';\n\nconst CanvasWrapper = styled.div`\n  width: 100%;\n  height: 100%;\n\n  position: relative;\n\n  canvas {\n    width: 100%;\n    height: 100%;\n\n    position: absolute;\n\n    &#buffer {\n      width: auto;\n      height: auto;\n      display: none;\n    }\n  }\n  // TODO put this into a global style\n  canvas,\n  img {\n    image-rendering: -moz-crisp-edges;\n    image-rendering: -webkit-crisp-edges;\n    image-rendering: pixelated;\n    image-rendering: crisp-edges;\n  }\n`;\n\nexport default function ControllableCanvas() {\n  // html canvas element width and height. viewport dimensions are tracked by viewport obj\n  const [width, setWidth] = useState(window.innerWidth);\n  const [height, setHeight] = useState(window.innerHeight);\n\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const glRef = useRef<HTMLCanvasElement | null>(null);\n  const bufferRef = useRef<HTMLCanvasElement | null>(null);\n\n  const evtRef = canvasRef;\n\n  const gameUIManager = useUIManager();\n\n  const modalManager = gameUIManager.getModalManager();\n  const [targeting, setTargeting] = useState<boolean>(false);\n\n  useEffect(() => {\n    const updateTargeting = (newstate: CursorState) => {\n      setTargeting(newstate === CursorState.TargetingExplorer);\n    };\n    modalManager.on(ModalManagerEvent.StateChanged, updateTargeting);\n    return () => {\n      modalManager.removeListener(ModalManagerEvent.StateChanged, updateTargeting);\n    };\n  }, [modalManager]);\n\n  const doResize = useCallback(() => {\n    const uiEmitter: UIEmitter = UIEmitter.getInstance();\n    if (canvasRef.current) {\n      setWidth(canvasRef.current.clientWidth);\n      setHeight(canvasRef.current.clientHeight);\n      uiEmitter.emit(UIEmitterEvent.WindowResize);\n    }\n  }, [canvasRef]);\n\n  // TODO fix this\n  useLayoutEffect(() => {\n    if (canvasRef.current) doResize();\n  }, [\n    // dep array gives eslint issues, but it's fine i tested it i swear - Alan\n    canvasRef,\n    doResize,\n    /* eslint-disable react-hooks/exhaustive-deps */\n    canvasRef.current?.offsetWidth,\n    canvasRef.current?.offsetHeight,\n    /* eslint-enable react-hooks/exhaustive-deps */\n  ]);\n\n  useEffect(() => {\n    if (!gameUIManager) return;\n\n    const uiEmitter: UIEmitter = UIEmitter.getInstance();\n\n    function onResize() {\n      doResize();\n      uiEmitter.emit(UIEmitterEvent.WindowResize);\n    }\n\n    const onWheel = (e: WheelEvent): void => {\n      e.preventDefault();\n      const { deltaY } = e;\n      uiEmitter.emit(UIEmitterEvent.CanvasScroll, deltaY);\n    };\n\n    const canvas = evtRef.current;\n    if (!canvas || !canvasRef.current || !glRef.current || !bufferRef.current) return;\n\n    // This zooms your home world in really close to show the awesome details\n    // TODO: Store this as it changes and re-initialize to that if stored\n    const defaultWorldUnits = 4;\n    Viewport.initialize(gameUIManager, defaultWorldUnits, canvas);\n    Renderer.initialize(\n      canvasRef.current,\n      glRef.current,\n      bufferRef.current,\n      Viewport.getInstance(),\n      gameUIManager,\n      {\n        spaceColors: {\n          innerNebulaColor: gameUIManager.getStringSetting(Setting.RendererColorInnerNebula),\n          nebulaColor: gameUIManager.getStringSetting(Setting.RendererColorNebula),\n          spaceColor: gameUIManager.getStringSetting(Setting.RendererColorSpace),\n          deepSpaceColor: gameUIManager.getStringSetting(Setting.RendererColorDeepSpace),\n          deadSpaceColor: gameUIManager.getStringSetting(Setting.RendererColorDeadSpace),\n        },\n      }\n    );\n    // We can't attach the wheel event onto the canvas due to:\n    // https://www.chromestatus.com/features/6662647093133312\n    canvas.addEventListener('wheel', onWheel);\n    window.addEventListener('resize', onResize);\n\n    uiEmitter.on(UIEmitterEvent.UIChange, doResize);\n\n    return () => {\n      Viewport.destroyInstance();\n      Renderer.destroy();\n      canvas.removeEventListener('wheel', onWheel);\n      window.removeEventListener('resize', onResize);\n      uiEmitter.removeListener(UIEmitterEvent.UIChange, doResize);\n    };\n  }, [gameUIManager, doResize, canvasRef, glRef, bufferRef, evtRef]);\n\n  // attach event listeners\n  useEffect(() => {\n    if (!evtRef.current) return;\n    const canvas = evtRef.current;\n\n    const uiEmitter: UIEmitter = UIEmitter.getInstance();\n\n    function onMouseEvent(emitEventName: UIEmitterEvent, mouseEvent: MouseEvent) {\n      const rect = canvas.getBoundingClientRect();\n      const canvasX = mouseEvent.clientX - rect.left;\n      const canvasY = mouseEvent.clientY - rect.top;\n      uiEmitter.emit(emitEventName, { x: canvasX, y: canvasY });\n    }\n\n    const onMouseDown = (e: MouseEvent) => {\n      onMouseEvent(UIEmitterEvent.CanvasMouseDown, e);\n    };\n    // this is the root of the mousemove event\n    const onMouseMove = (e: MouseEvent) => {\n      onMouseEvent(UIEmitterEvent.CanvasMouseMove, e);\n    };\n    const onMouseUp = (e: MouseEvent) => {\n      onMouseEvent(UIEmitterEvent.CanvasMouseUp, e);\n    };\n    // TODO convert this to mouseleave\n    const onMouseOut = () => {\n      uiEmitter.emit(UIEmitterEvent.CanvasMouseOut);\n    };\n\n    canvas.addEventListener('mousedown', onMouseDown);\n    canvas.addEventListener('mousemove', onMouseMove);\n    canvas.addEventListener('mouseup', onMouseUp);\n    canvas.addEventListener('mouseout', onMouseOut);\n    return () => {\n      canvas.removeEventListener('mousedown', onMouseDown);\n      canvas.removeEventListener('mousemove', onMouseMove);\n      canvas.removeEventListener('mouseup', onMouseUp);\n      canvas.removeEventListener('mouseout', onMouseOut);\n    };\n  }, [evtRef]);\n\n  return (\n    <CanvasWrapper style={{ cursor: targeting ? 'crosshair' : undefined }}>\n      <canvas ref={glRef} width={width} height={height} />\n      <canvas ref={canvasRef} width={width} height={height} />\n      <canvas ref={bufferRef} id='buffer' />\n    </CanvasWrapper>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Game/ModalManager.ts",
    "content": "import { monomitter, Monomitter } from '@darkforest_eth/events';\nimport {\n  CursorState,\n  ModalId,\n  ModalManagerEvent,\n  ModalPosition,\n  WorldCoords,\n} from '@darkforest_eth/types';\nimport { EventEmitter } from 'events';\nimport type PersistentChunkStore from '../../Backend/Storage/PersistentChunkStore';\n\nclass ModalManager extends EventEmitter {\n  static instance: ModalManager;\n  private lastIndex: number;\n  private cursorState: CursorState;\n  private persistentChunkStore: PersistentChunkStore;\n  private modalPositions: Map<ModalId, ModalPosition>;\n\n  public modalPositions$: Monomitter<Map<ModalId, ModalPosition>>;\n  public readonly activeModalId$: Monomitter<string>;\n  public readonly modalPositionChanged$: Monomitter<ModalId>;\n\n  private constructor(\n    persistentChunkStore: PersistentChunkStore,\n    modalPositions: Map<ModalId, ModalPosition>\n  ) {\n    super();\n    this.lastIndex = 0;\n    this.activeModalId$ = monomitter(true);\n    this.modalPositionChanged$ = monomitter();\n    this.persistentChunkStore = persistentChunkStore;\n    this.modalPositions = modalPositions;\n  }\n\n  public static async create(persistentChunkStore: PersistentChunkStore): Promise<ModalManager> {\n    const modalPositions = await persistentChunkStore.loadModalPositions();\n    return new ModalManager(persistentChunkStore, modalPositions);\n  }\n\n  public getIndex(): number {\n    this.lastIndex++;\n    return this.lastIndex;\n  }\n\n  public getCursorState(): CursorState {\n    return this.cursorState;\n  }\n\n  public setCursorState(newstate: CursorState): void {\n    this.cursorState = newstate;\n    this.emit(ModalManagerEvent.StateChanged, newstate);\n  }\n\n  public acceptInputForTarget(input: WorldCoords): void {\n    if (this.cursorState !== CursorState.TargetingExplorer) return;\n    this.emit(ModalManagerEvent.MiningCoordsUpdate, input);\n    this.setCursorState(CursorState.Normal);\n  }\n\n  public getModalPosition(modalId: ModalId): ModalPosition | undefined {\n    return this.modalPositions.get(modalId);\n  }\n\n  public getModalPositions(modalIds: ModalId[] = []): Map<ModalId, ModalPosition> {\n    if (modalIds.length === 0) return this.modalPositions;\n    return modalIds.reduce<Map<ModalId, ModalPosition>>((acc, cur) => {\n      const winPos = this.modalPositions.get(cur);\n      if (!winPos) return acc;\n      return acc.set(cur, winPos);\n    }, new Map());\n  }\n\n  public clearModalPosition(modalId: ModalId): void {\n    this.modalPositions.delete(modalId);\n    this.persistentChunkStore.saveModalPositions(this.modalPositions);\n    this.modalPositionChanged$.publish(modalId);\n  }\n\n  public setModalPosition(modalId: ModalId, pos: ModalPosition): void {\n    this.modalPositions.set(modalId, pos);\n    this.persistentChunkStore.saveModalPositions(this.modalPositions);\n    this.modalPositionChanged$.publish(modalId);\n  }\n\n  public setModalState(modalId: ModalId, state: ModalPosition['state']): void {\n    const pos = this.modalPositions.get(modalId);\n    if (pos) {\n      this.setModalPosition(modalId, {\n        ...pos,\n        state,\n      });\n    } else {\n      this.setModalPosition(modalId, {\n        modalId,\n        state,\n      });\n    }\n  }\n}\n\nexport default ModalManager;\n"
  },
  {
    "path": "src/Frontend/Game/NotificationManager.tsx",
    "content": "import { biomeName, isLocatable } from '@darkforest_eth/gamelogic';\nimport {\n  Artifact,\n  Biome,\n  Chunk,\n  ContractMethodName,\n  EthTxStatus,\n  LocatablePlanet,\n  Planet,\n  TxIntent,\n} from '@darkforest_eth/types';\nimport EventEmitter from 'events';\nimport { startCase } from 'lodash';\nimport React from 'react';\nimport { getRandomActionId } from '../../Backend/Utils/Utils';\nimport {\n  ArtifactFound,\n  ArtifactProspected,\n  FoundCorrupted,\n  FoundDeadSpace,\n  FoundDeepSpace,\n  FoundDesert,\n  FoundForest,\n  FoundGrassland,\n  FoundIce,\n  FoundLava,\n  FoundOcean,\n  FoundPirates,\n  FoundRuins,\n  FoundSilver,\n  FoundSpace,\n  FoundSwamp,\n  FoundTradingPost,\n  FoundTundra,\n  FoundWasteland,\n  Generic,\n  PlanetAttacked,\n  PlanetConquered,\n  PlanetLost,\n  Quasar,\n  TxDeclined,\n} from '../Components/Icons';\nimport {\n  ArtifactBiomeText,\n  ArtifactRarityLabelAnim,\n  ArtifactTypeText,\n} from '../Components/Labels/ArtifactLabels';\nimport { ArtifactNameLink, CenterChunkLink, FAQ04Link, PlanetNameLink } from '../Components/Text';\n\nexport const enum NotificationType {\n  Tx,\n  CanUpgrade,\n  BalanceEmpty,\n  WelcomePlayer,\n  FoundSpace,\n  FoundDeepSpace,\n  FoundDeadSpace,\n  FoundPirates,\n  FoundSilver,\n  FoundSilverBank,\n  FoundTradingPost,\n  FoundComet,\n  FoundFoundry,\n  FoundBiome,\n  FoundBiomeOcean,\n  FoundBiomeForest,\n  FoundBiomeGrassland,\n  FoundBiomeTundra,\n  FoundBiomeSwamp,\n  FoundBiomeDesert,\n  FoundBiomeIce,\n  FoundBiomeWasteland,\n  FoundBiomeLava,\n  FoundBiomeCorrupted,\n  PlanetLost,\n  PlanetWon,\n  PlanetAttacked,\n  ArtifactProspected,\n  ArtifactFound,\n  ReceivedPlanet,\n  Generic,\n  TxInitError,\n}\n\nconst BiomeNotificationMap = {\n  [Biome.OCEAN]: NotificationType.FoundBiomeOcean,\n  [Biome.FOREST]: NotificationType.FoundBiomeForest,\n  [Biome.GRASSLAND]: NotificationType.FoundBiomeGrassland,\n  [Biome.TUNDRA]: NotificationType.FoundBiomeTundra,\n  [Biome.SWAMP]: NotificationType.FoundBiomeSwamp,\n  [Biome.DESERT]: NotificationType.FoundBiomeDesert,\n  [Biome.ICE]: NotificationType.FoundBiomeIce,\n  [Biome.WASTELAND]: NotificationType.FoundBiomeWasteland,\n  [Biome.LAVA]: NotificationType.FoundBiomeLava,\n  [Biome.CORRUPTED]: NotificationType.FoundBiomeCorrupted,\n};\nfunction getNotificationTypeFromPlanetBiome(biome: Biome): NotificationType {\n  if (!biome) throw new Error('Biome is a required to get a NotificationType');\n  return BiomeNotificationMap[biome];\n}\n\nexport type NotificationInfo = {\n  type: NotificationType;\n  message: React.ReactNode;\n  icon: React.ReactNode;\n  id: string;\n  color?: string;\n  txData?: TxIntent;\n  txStatus?: EthTxStatus;\n};\n\nexport const enum NotificationManagerEvent {\n  Notify = 'Notify',\n  ClearNotification = 'ClearNotification',\n}\n\nclass NotificationManager extends EventEmitter {\n  static instance: NotificationManager;\n\n  private constructor() {\n    super();\n  }\n\n  static getInstance(): NotificationManager {\n    if (!NotificationManager.instance) {\n      NotificationManager.instance = new NotificationManager();\n    }\n\n    return NotificationManager.instance;\n  }\n\n  private getIcon(type: NotificationType) {\n    switch (type) {\n      case NotificationType.TxInitError:\n        return <TxDeclined height={'48px'} width={'48px'} />;\n      case NotificationType.FoundSilverBank:\n        return <Quasar height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.FoundSpace:\n        return <FoundSpace height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundDeepSpace:\n        return <FoundDeepSpace height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundDeadSpace:\n        return <FoundDeadSpace height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundPirates:\n        return <FoundPirates height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.FoundSilver:\n        return <FoundSilver height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.FoundTradingPost:\n        return <FoundTradingPost height={'48px'} width={'48px'} />;\n        break;\n\n      case NotificationType.FoundFoundry:\n        return <FoundRuins height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeOcean:\n        return <FoundOcean height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeForest:\n        return <FoundForest height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeGrassland:\n        return <FoundGrassland height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeTundra:\n        return <FoundTundra height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeSwamp:\n        return <FoundSwamp height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeDesert:\n        return <FoundDesert height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeIce:\n        return <FoundIce height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeWasteland:\n        return <FoundWasteland height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeLava:\n        return <FoundLava height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.FoundBiomeCorrupted:\n        return <FoundCorrupted height={'64px'} width={'64px'} />;\n        break;\n      case NotificationType.PlanetAttacked:\n        return <PlanetAttacked height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.PlanetLost:\n        return <PlanetLost height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.PlanetWon:\n        return <PlanetConquered height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.ArtifactProspected:\n        return <ArtifactProspected height={'48px'} width={'48px'} />;\n        break;\n      case NotificationType.ArtifactFound:\n        return <ArtifactFound height={'48px'} width={'48px'} />;\n      default:\n        return <Generic height={'48px'} width={'48px'} />;\n        break;\n    }\n  }\n\n  reallyLongNotification() {\n    let message = '';\n\n    for (let i = 0; i < 100; i++) {\n      message += 'lol ';\n    }\n\n    this.emit(NotificationManagerEvent.Notify, {\n      type: NotificationType.Generic,\n      message,\n      id: getRandomActionId(),\n      icon: this.getIcon(NotificationType.Generic),\n    });\n  }\n\n  clearNotification(id: string) {\n    this.emit(NotificationManagerEvent.ClearNotification, id);\n  }\n\n  notify(type: NotificationType, message: React.ReactNode): void {\n    this.emit(NotificationManagerEvent.Notify, {\n      type,\n      message,\n      id: getRandomActionId(),\n      icon: this.getIcon(type),\n    });\n  }\n\n  welcomePlayer(): void {\n    this.notify(\n      NotificationType.WelcomePlayer,\n      <span>\n        Welcome to the world to Dark Forest! These are your notifications.\n        <br />\n        Click a notification to dismiss it.\n      </span>\n    );\n  }\n\n  foundSpace(chunk: Chunk): void {\n    this.notify(\n      NotificationType.FoundSpace,\n      <span>\n        Congrats! You found space! Space has more valuable resources than <br />\n        the nebula where your home planet is located.{' '}\n        <CenterChunkLink chunk={chunk}>Click to view</CenterChunkLink>.\n      </span>\n    );\n  }\n\n  foundDeepSpace(chunk: Chunk): void {\n    this.notify(\n      NotificationType.FoundDeepSpace,\n      <span>\n        Congrats! You found deep space! Deep space has more rare <br />\n        planets, but all planets in deep space have lowered defense!{' '}\n        <CenterChunkLink chunk={chunk}>Click to view</CenterChunkLink>.\n      </span>\n    );\n  }\n\n  foundDeadSpace(chunk: Chunk): void {\n    this.notify(\n      NotificationType.FoundDeadSpace,\n      <span>\n        Congrats! You found dead space! Dead space is the most valuable <br />\n        and most dangerous part of the universe, where corrupted planets lie...{' '}\n        <CenterChunkLink chunk={chunk}>Click to view</CenterChunkLink>.\n      </span>\n    );\n  }\n\n  foundSilver(planet: Planet): void {\n    this.notify(\n      NotificationType.FoundSilver,\n      <span>\n        You found a silver mine! Silver can be used to upgrade planets. <br />\n        Click to view <PlanetNameLink planet={planet} />.\n      </span>\n    );\n  }\n\n  foundSilverBank(planet: Planet): void {\n    this.notify(\n      NotificationType.FoundSilverBank,\n      <span>\n        You found a quasar! Quasars are weak, but can hold a lot of silver. <br />\n        Click to view <PlanetNameLink planet={planet} />.\n      </span>\n    );\n  }\n\n  foundTradingPost(planet: Planet): void {\n    this.notify(\n      NotificationType.FoundTradingPost,\n      <span>\n        You found a spacetime rip! Now you can move artifacts in and out of the universe. Click to\n        view <PlanetNameLink planet={planet} />.\n      </span>\n    );\n  }\n\n  foundPirates(planet: Planet): void {\n    this.notify(\n      NotificationType.FoundPirates,\n      <span>\n        You found space pirates! Unconquered planets must be defeated first.\n        <br />\n        Click to view <PlanetNameLink planet={planet} />.\n      </span>\n    );\n  }\n\n  foundComet(planet: Planet): void {\n    this.notify(\n      NotificationType.FoundComet,\n      <span>\n        You found a comet! Planets with comets have a stat doubled! <br />\n        Click to view <PlanetNameLink planet={planet} />\n      </span>\n    );\n  }\n\n  foundBiome(planet: LocatablePlanet): void {\n    this.notify(\n      getNotificationTypeFromPlanetBiome(planet.biome),\n      <span>\n        You have discovered the {biomeName(planet.biome)} biome! <br />\n        Click to view <PlanetNameLink planet={planet} />\n      </span>\n    );\n  }\n\n  foundFoundry(planet: LocatablePlanet): void {\n    this.notify(\n      NotificationType.FoundFoundry,\n      <span>\n        You have found a planet that can produce an artifact! Artifacts can be used to power up your\n        planets and moves! <br />\n        Click to view <PlanetNameLink planet={planet} />\n      </span>\n    );\n  }\n  artifactProspected(planet: LocatablePlanet): void {\n    this.notify(\n      NotificationType.ArtifactProspected,\n      <span>\n        You prospected a Foundry! <br />\n        What artifacts are waiting to be found on it? Click to view{' '}\n        <PlanetNameLink planet={planet} />\n      </span>\n    );\n  }\n\n  artifactFound(planet: LocatablePlanet, artifact: Artifact): void {\n    this.notify(\n      NotificationType.ArtifactFound,\n      <span>\n        You have found <ArtifactNameLink id={artifact.id} />, a{' '}\n        <ArtifactRarityLabelAnim rarity={artifact.rarity} />{' '}\n        <ArtifactBiomeText artifact={artifact} /> <ArtifactTypeText artifact={artifact} />\n        {'!'.repeat(artifact.rarity)} <br />\n        Click to view <PlanetNameLink planet={planet} />\n      </span>\n    );\n  }\n  planetConquered(planet: LocatablePlanet): void {\n    this.notify(\n      NotificationType.PlanetWon,\n      <span>\n        You conquered <PlanetNameLink planet={planet}></PlanetNameLink>, you're unstoppable!\n      </span>\n    );\n  }\n  planetLost(planet: LocatablePlanet): void {\n    this.notify(\n      NotificationType.PlanetLost,\n      <span>\n        You lost <PlanetNameLink planet={planet}></PlanetNameLink>, oh no!\n      </span>\n    );\n  }\n  planetAttacked(planet: LocatablePlanet): void {\n    this.notify(\n      NotificationType.PlanetAttacked,\n      <span>\n        Your Planet <PlanetNameLink planet={planet}></PlanetNameLink> has been attacked!\n      </span>\n    );\n  }\n\n  planetCanUpgrade(planet: Planet): void {\n    this.notify(\n      NotificationType.CanUpgrade,\n      <span>\n        Your planet <PlanetNameLink planet={planet} /> can upgrade! <br />\n      </span>\n    );\n  }\n\n  balanceEmpty(): void {\n    this.notify(\n      NotificationType.BalanceEmpty,\n      <span>\n        Your xDAI account is out of balance!\n        <br />\n        Click <FAQ04Link>here</FAQ04Link> to learn how to get more.\n      </span>\n    );\n  }\n\n  receivedPlanet(planet: Planet) {\n    this.notify(\n      NotificationType.ReceivedPlanet,\n      <span>\n        Someone just sent you their planet: <PlanetNameLink planet={planet} />.{' '}\n        {!isLocatable(planet) && \"You'll need to ask the person who sent it for its location.\"}\n      </span>\n    );\n  }\n\n  txInitError(methodName: ContractMethodName, failureReason: string) {\n    this.notify(\n      NotificationType.TxInitError,\n      <span>\n        {startCase(methodName)} failed. Reason: {failureReason}\n      </span>\n    );\n  }\n}\n\nexport default NotificationManager;\n"
  },
  {
    "path": "src/Frontend/Game/Popups.ts",
    "content": "import { EthConnection, isPurchase, weiToEth } from '@darkforest_eth/network';\nimport { EthAddress, Setting, TransactionId, TxIntent } from '@darkforest_eth/types';\nimport { BigNumber as EthersBN, providers } from 'ethers';\nimport { getBooleanSetting } from '../Utils/SettingsHooks';\n\n// tx is killed if user doesn't click popup within 20s\nconst POPUP_TIMEOUT = 20000;\n\ninterface OpenConfirmationConfig {\n  contractAddress: EthAddress;\n  connection: EthConnection;\n  id: TransactionId;\n  intent: TxIntent;\n  overrides?: providers.TransactionRequest;\n  from: EthAddress;\n  gasFeeGwei: EthersBN;\n}\n\nexport async function openConfirmationWindowForTransaction({\n  contractAddress,\n  connection,\n  id,\n  intent,\n  overrides,\n  from,\n  gasFeeGwei,\n}: OpenConfirmationConfig): Promise<void> {\n  const config = {\n    contractAddress,\n    account: connection.getAddress(),\n  };\n  const autoApprove = getBooleanSetting(config, Setting.AutoApproveNonPurchaseTransactions);\n\n  if (!autoApprove || isPurchase(overrides)) {\n    localStorage.setItem(`${from}-gasFeeGwei`, gasFeeGwei.toString());\n    const account = connection.getAddress();\n    if (!account) throw new Error('no account');\n    const balanceEth = weiToEth(await connection.loadBalance(account));\n    const method = intent.methodName;\n    const popup = window.open(\n      `/wallet/${contractAddress}/${from}/${id}/${balanceEth}/${method}`,\n      'confirmationwindow',\n      'width=600,height=500'\n    );\n    if (popup) {\n      const opened = Date.now();\n      await new Promise<void>((resolve, reject) => {\n        const interval = setInterval(() => {\n          if (popup.closed) {\n            const approved = localStorage.getItem(`tx-approved-${from}-${id}`) === 'true';\n            if (approved) {\n              resolve();\n            } else {\n              reject(new Error('User rejected transaction.'));\n            }\n            localStorage.removeItem(`tx-approved-${from}-${id}`);\n            clearInterval(interval);\n          } else {\n            if (Date.now() > opened + POPUP_TIMEOUT) {\n              reject(new Error('Approval window popup timed out; check your popups!'));\n              localStorage.removeItem(`tx-approved-${from}-${id}`);\n              clearInterval(interval);\n              popup.close();\n            }\n          }\n        }, 100);\n      });\n    } else {\n      throw new Error(\n        \"Please enable popups to confirm this transaction. After you've done so, try again.\"\n      );\n    }\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Game/Viewport.ts",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { CanvasCoords, Chunk, DiagnosticUpdater, Planet, WorldCoords } from '@darkforest_eth/types';\nimport autoBind from 'auto-bind';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { distL2, vectorLength } from '../../Backend/Utils/Coordinates';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\n\nexport const getDefaultScroll = (): number => {\n  const isFirefox = navigator.userAgent.indexOf('Firefox') > 0;\n  return isFirefox ? 1.005 : 1.0006;\n};\n\ntype ViewportData = {\n  widthInWorldUnits: number;\n  centerWorldCoords: WorldCoords;\n};\n\n// multiplied by init velocity, since mousemove won't always happen at the same rate as rAF\nconst BASE_VEL = 1;\n// if vel gets below this, kill it\nconst VEL_THRESHOLD = 0.05;\n\nclass Viewport {\n  // The sole listener for events from Canvas\n  // Handles panning and zooming\n  // Handles reports of user interaction in canvas coords, transforms these events to world coords and filters when necessary,\n  // and sends events up to GameUIManager\n\n  static instance: Viewport | undefined;\n\n  // the following two fields represent the position of the vieport in the world\n  centerWorldCoords: WorldCoords;\n  widthInWorldUnits: number;\n  heightInWorldUnits: number;\n\n  viewportWidth: number; // pixels\n  viewportHeight: number; // pixels\n\n  isPanning = false;\n  mouseLastCoords: CanvasCoords | undefined;\n  canvas: HTMLCanvasElement;\n\n  isFirefox: boolean;\n\n  gameUIManager: GameUIManager;\n\n  mousedownCoords: CanvasCoords | undefined = undefined;\n\n  // for momentum stuff\n  velocity: WorldCoords | undefined = undefined;\n  momentum = false;\n  mouseSensitivity: number;\n  intervalId: ReturnType<typeof setTimeout>;\n  frameRequestId: number;\n\n  diagnosticUpdater?: DiagnosticUpdater;\n\n  scale: number;\n  private isSending = false;\n\n  private constructor(\n    gameUIManager: GameUIManager,\n    centerWorldCoords: WorldCoords,\n    widthInWorldUnits: number,\n    viewportWidth: number,\n    viewportHeight: number,\n    canvas: HTMLCanvasElement\n  ) {\n    this.gameUIManager = gameUIManager;\n\n    // each of these is measured relative to the world coordinate system\n    this.centerWorldCoords = centerWorldCoords;\n    this.widthInWorldUnits = widthInWorldUnits;\n    this.heightInWorldUnits = (widthInWorldUnits * viewportHeight) / viewportWidth;\n    // while all of the above are in the world coordinate system, the below are in the page coordinate system\n    this.viewportWidth = viewportWidth; // width / height\n    this.viewportHeight = viewportHeight;\n\n    this.mouseLastCoords = centerWorldCoords;\n    this.canvas = canvas;\n\n    const scroll = localStorage.getItem('scrollSpeed');\n    if (scroll) {\n      this.mouseSensitivity = parseFloat(scroll);\n    } else {\n      this.mouseSensitivity = getDefaultScroll();\n    }\n\n    this.isPanning = false;\n    autoBind(this);\n\n    // fixes issue where viewport inits weirdly - TODO figure out why\n    this.setWorldWidth(this.widthInWorldUnits);\n    this.onScroll(0);\n  }\n\n  public setDiagnosticUpdater(diagnosticUpdater: DiagnosticUpdater) {\n    this.diagnosticUpdater = diagnosticUpdater;\n  }\n\n  onSendInit() {\n    this.isSending = true;\n  }\n\n  onSendComplete() {\n    this.isSending = false;\n  }\n\n  get minWorldWidth(): number {\n    // TODO: Figure out a better way to set this based on viewport\n    return 2;\n  }\n\n  get maxWorldWidth(): number {\n    return this.gameUIManager.getWorldRadius() * 4;\n  }\n\n  public getViewportPosition() {\n    return { ...this.centerWorldCoords };\n  }\n\n  public getBottomBound() {\n    return this.centerWorldCoords.y - this.heightInWorldUnits / 2;\n  }\n\n  public getLeftBound() {\n    return this.centerWorldCoords.x - this.widthInWorldUnits / 2;\n  }\n\n  public getTopBound() {\n    return this.centerWorldCoords.y + this.heightInWorldUnits / 2;\n  }\n\n  public getRightBound() {\n    return this.centerWorldCoords.x + this.widthInWorldUnits / 2;\n  }\n\n  public getViewportWorldWidth() {\n    return this.widthInWorldUnits;\n  }\n\n  public getViewportWorldHeight() {\n    return this.heightInWorldUnits;\n  }\n\n  public setMouseSensitivty(mouseSensitivity: number) {\n    this.mouseSensitivity = 1 + mouseSensitivity;\n    localStorage.setItem('scrollSpeed', this.mouseSensitivity.toString());\n  }\n\n  static getInstance(): Viewport {\n    if (!Viewport.instance) {\n      throw new Error('Attempted to get Viewport object before initialized');\n    }\n\n    return Viewport.instance;\n  }\n\n  static destroyInstance(): void {\n    const uiEmitter = UIEmitter.getInstance();\n    const viewport = Viewport.instance;\n\n    if (viewport) {\n      uiEmitter\n        .removeListener(UIEmitterEvent.CanvasMouseDown, viewport.onMouseDown)\n        .removeListener(UIEmitterEvent.CanvasMouseMove, viewport.onMouseMove)\n        .removeListener(UIEmitterEvent.CanvasMouseUp, viewport.onMouseUp)\n        .removeListener(UIEmitterEvent.CanvasMouseOut, viewport.onMouseOut)\n        .removeListener(UIEmitterEvent.CanvasScroll, viewport.onScroll)\n        .removeListener(UIEmitterEvent.WindowResize, viewport.onWindowResize)\n        .removeListener(UIEmitterEvent.CenterPlanet, viewport.centerPlanet)\n        .removeListener(UIEmitterEvent.ZoomIn, viewport.zoomIn)\n        .removeListener(UIEmitterEvent.ZoomOut, viewport.zoomOut)\n        .removeAllListeners(UIEmitterEvent.SendInitiated)\n        .removeAllListeners(UIEmitterEvent.SendCancelled)\n        .removeAllListeners(UIEmitterEvent.SendCompleted)\n        .removeAllListeners(UIEmitterEvent.WindowResize);\n      clearInterval(viewport.intervalId);\n      window.cancelAnimationFrame(viewport.frameRequestId);\n    }\n    Viewport.instance = undefined;\n  }\n\n  static initialize(\n    gameUIManager: GameUIManager,\n    widthInWorldUnits: number,\n    canvas: HTMLCanvasElement\n  ): Viewport {\n    const uiEmitter = UIEmitter.getInstance();\n\n    const homeCoords = gameUIManager.getHomeCoords();\n\n    const viewport = new Viewport(\n      gameUIManager,\n      homeCoords,\n      widthInWorldUnits,\n      canvas.width,\n      canvas.height,\n      canvas\n    );\n\n    viewport.setDiagnosticUpdater(gameUIManager);\n\n    // set starting position based on storage\n    const stored = viewport.getStorage();\n    if (!stored) {\n      viewport.zoomPlanet(gameUIManager.getHomePlanet());\n    } else {\n      viewport.setData(stored);\n    }\n\n    uiEmitter\n      .on(UIEmitterEvent.CanvasMouseDown, viewport.onMouseDown)\n      .on(UIEmitterEvent.CanvasMouseMove, viewport.onMouseMove)\n      .on(UIEmitterEvent.CanvasMouseUp, viewport.onMouseUp)\n      .on(UIEmitterEvent.CanvasMouseOut, viewport.onMouseOut)\n      .on(UIEmitterEvent.CanvasScroll, viewport.onScroll)\n      .on(UIEmitterEvent.WindowResize, viewport.onWindowResize)\n      .on(UIEmitterEvent.CenterPlanet, viewport.centerPlanet)\n      .on(UIEmitterEvent.ZoomIn, viewport.zoomIn)\n      .on(UIEmitterEvent.ZoomOut, viewport.zoomOut)\n      .on(UIEmitterEvent.SendInitiated, viewport.onSendInit)\n      .on(UIEmitterEvent.SendCancelled, viewport.onSendComplete)\n      .on(UIEmitterEvent.SendCompleted, viewport.onSendComplete)\n      .on(UIEmitterEvent.WindowResize, viewport.onResize);\n\n    viewport.intervalId = setInterval(viewport.setStorage, 5000);\n    Viewport.instance = viewport;\n\n    return viewport;\n  }\n\n  onResize() {\n    this.onScroll(0);\n  }\n\n  private getStorageKey(): string {\n    const acc = this.gameUIManager.getAccount();\n    const addr = this.gameUIManager.getContractAddress();\n    return `${acc}-${addr}-viewport`;\n  }\n\n  // returns this viewport's ViewportData, which will let us initialize it at the same zoom / pos\n  getStorage(): ViewportData | undefined {\n    const key = this.getStorageKey();\n    const stored = localStorage.getItem(key);\n    if (stored) return (JSON.parse(stored) as ViewportData) || undefined;\n    return undefined;\n  }\n\n  // stores this viewport's ViewportData into storage\n  setStorage() {\n    const key = this.getStorageKey();\n    const data: ViewportData = {\n      widthInWorldUnits: this.widthInWorldUnits,\n      centerWorldCoords: this.centerWorldCoords,\n    };\n    localStorage.setItem(key, JSON.stringify(data));\n  }\n\n  // set this viewport's zoom and pos to the given ViewportData\n  setData(data: ViewportData) {\n    // lets us prevent the program from crashing if this was called poorly\n    typeof data.widthInWorldUnits === 'number' && this.setWorldWidth(data.widthInWorldUnits);\n    typeof data.widthInWorldUnits === 'number' && this.centerCoords(data.centerWorldCoords);\n  }\n\n  centerPlanet(planet: Planet | undefined): void {\n    if (planet && isLocatable(planet)) {\n      this.centerCoords(planet.location.coords);\n    }\n  }\n\n  // centers on a planet and makes it fill the viewport\n  zoomPlanet(planet?: Planet, radii?: number): void {\n    if (!planet || !isLocatable(planet)) return;\n    this.centerPlanet(planet);\n    // in world coords\n    const rad = this.gameUIManager.getRadiusOfPlanetLevel(planet.planetLevel);\n\n    if (radii !== undefined) {\n      this.setWorldHeight(radii * rad);\n    }\n  }\n\n  centerCoords(coords: WorldCoords): void {\n    if (typeof coords.x !== 'number' || typeof coords.y !== 'number')\n      throw new Error('please pass in an object that looks like {x: 0, y: 0}');\n    this.centerWorldCoords = { ...coords };\n  }\n\n  centerChunk(chunk: Chunk): void {\n    const { bottomLeft, sideLength } = chunk.chunkFootprint;\n    this.centerWorldCoords = {\n      x: bottomLeft.x + sideLength / 2,\n      y: bottomLeft.y + sideLength / 2,\n    };\n  }\n\n  zoomIn(): void {\n    this.onScroll(-600, true);\n  }\n\n  zoomOut(): void {\n    this.onScroll(600, true);\n  }\n\n  // Event handlers\n  onMouseDown(canvasCoords: CanvasCoords) {\n    if (this.isSending) return;\n\n    this.mousedownCoords = canvasCoords;\n\n    const uiManager = this.gameUIManager;\n    const uiEmitter = UIEmitter.getInstance();\n\n    const worldCoords = this.canvasToWorldCoords(canvasCoords);\n    if (!uiManager.isOverOwnPlanet(worldCoords)) {\n      this.isPanning = true;\n    }\n    uiEmitter.emit(UIEmitterEvent.WorldMouseDown, worldCoords);\n    this.mouseLastCoords = canvasCoords;\n\n    this.momentum = false;\n    this.velocity = undefined;\n  }\n\n  onMouseMove(canvasCoords: CanvasCoords) {\n    const uiEmitter = UIEmitter.getInstance();\n\n    if (this.isPanning && this.mouseLastCoords) {\n      // if panning, don't need to emit mouse move event\n      const dx = this.scale * (this.mouseLastCoords.x - canvasCoords.x);\n      const dy = -this.scale * (this.mouseLastCoords.y - canvasCoords.y);\n\n      this.velocity = { x: dx * BASE_VEL, y: dy * BASE_VEL };\n\n      this.centerWorldCoords.x += dx;\n      this.centerWorldCoords.y += dy;\n    } else {\n      const worldCoords = this.canvasToWorldCoords(canvasCoords);\n      uiEmitter.emit(UIEmitterEvent.WorldMouseMove, worldCoords);\n    }\n    this.mouseLastCoords = canvasCoords;\n  }\n\n  onMouseUp(canvasCoords: CanvasCoords) {\n    const uiEmitter = UIEmitter.getInstance();\n\n    const worldCoords = this.canvasToWorldCoords(canvasCoords);\n    if (this.mousedownCoords && distL2(canvasCoords, this.mousedownCoords) < 3) {\n      uiEmitter.emit(UIEmitterEvent.WorldMouseClick, worldCoords);\n    }\n\n    this.mousedownCoords = undefined;\n    uiEmitter.emit(UIEmitterEvent.WorldMouseUp, worldCoords);\n    this.isPanning = false;\n\n    this.momentum = true;\n    this.mouseLastCoords = canvasCoords;\n\n    if (this.velocity && vectorLength(this.velocity) < VEL_THRESHOLD) {\n      this.velocity = undefined;\n      this.momentum = false;\n    }\n  }\n\n  onMouseOut() {\n    const uiEmitter = UIEmitter.getInstance();\n\n    uiEmitter.emit(UIEmitterEvent.WorldMouseOut);\n    this.isPanning = false;\n    this.mouseLastCoords = undefined;\n  }\n\n  onScroll(deltaY: number, forceZoom = false) {\n    if (this.mouseLastCoords !== undefined || forceZoom) {\n      const base = this.mouseSensitivity;\n      const newWidth = this.widthInWorldUnits * base ** deltaY;\n      if (!this.isValidWorldWidth(newWidth)) {\n        return;\n      }\n\n      let mouseWorldCoords = this.centerWorldCoords;\n      if (this.mouseLastCoords) {\n        mouseWorldCoords = this.canvasToWorldCoords(this.mouseLastCoords);\n      }\n      const centersDiff = {\n        x: this.centerWorldCoords.x - mouseWorldCoords.x,\n        y: this.centerWorldCoords.y - mouseWorldCoords.y,\n      };\n      const newCentersDiff = {\n        x: centersDiff.x * base ** deltaY,\n        y: centersDiff.y * base ** deltaY,\n      };\n      const newCenter = {\n        x: mouseWorldCoords.x + newCentersDiff.x,\n        y: mouseWorldCoords.y + newCentersDiff.y,\n      };\n      this.centerWorldCoords.x = newCenter.x;\n      this.centerWorldCoords.y = newCenter.y;\n\n      this.setWorldWidth(newWidth);\n    }\n  }\n\n  onWindowResize() {\n    this.viewportHeight = this.canvas.height;\n    this.viewportWidth = this.canvas.width;\n    this.scale = this.widthInWorldUnits / this.viewportWidth;\n  }\n\n  // Camera utility functions\n  canvasToWorldCoords(canvasCoords: CanvasCoords): WorldCoords {\n    const worldX = this.canvasToWorldX(canvasCoords.x);\n    const worldY = this.canvasToWorldY(canvasCoords.y);\n    return { x: worldX, y: worldY };\n  }\n\n  worldToCanvasCoords(worldCoords: WorldCoords): CanvasCoords {\n    const canvasX = this.worldToCanvasX(worldCoords.x);\n    const canvasY = this.worldToCanvasY(worldCoords.y);\n    return { x: canvasX, y: canvasY };\n  }\n\n  public worldToCanvasDist(d: number): number {\n    return d / this.scale;\n  }\n\n  public canvasToWorldDist(d: number): number {\n    return d * this.scale;\n  }\n\n  private worldToCanvasX(x: number): number {\n    return (x - this.centerWorldCoords.x) / this.scale + this.viewportWidth / 2;\n  }\n\n  private canvasToWorldX(x: number): number {\n    return (x - this.viewportWidth / 2) * this.scale + this.centerWorldCoords.x;\n  }\n\n  private worldToCanvasY(y: number): number {\n    return (-1 * (y - this.centerWorldCoords.y)) / this.scale + this.viewportHeight / 2;\n  }\n\n  private canvasToWorldY(y: number): number {\n    return -1 * (y - this.viewportHeight / 2) * this.scale + this.centerWorldCoords.y;\n  }\n\n  public isInOrAroundViewport(coords: WorldCoords): boolean {\n    if (Math.abs(coords.x - this.centerWorldCoords.x) > 0.6 * this.widthInWorldUnits) {\n      return false;\n    }\n    if (Math.abs(coords.y - this.centerWorldCoords.y) > 0.6 * this.heightInWorldUnits) {\n      return false;\n    }\n    return true;\n  }\n\n  public isInViewport(coords: WorldCoords) {\n    return (\n      coords.x >= this.centerWorldCoords.x - this.widthInWorldUnits / 2 &&\n      coords.x <= this.centerWorldCoords.x + this.widthInWorldUnits / 2 &&\n      coords.y >= this.centerWorldCoords.y - this.heightInWorldUnits / 2 &&\n      coords.y <= this.centerWorldCoords.y + this.heightInWorldUnits / 2\n    );\n  }\n\n  public intersectsViewport(chunk: Chunk): boolean {\n    const chunkLeft = chunk.chunkFootprint.bottomLeft.x;\n    const chunkRight = chunkLeft + chunk.chunkFootprint.sideLength;\n    const chunkBottom = chunk.chunkFootprint.bottomLeft.y;\n    const chunkTop = chunkBottom + chunk.chunkFootprint.sideLength;\n\n    const viewportLeft = this.centerWorldCoords.x - this.widthInWorldUnits / 2;\n    const viewportRight = this.centerWorldCoords.x + this.widthInWorldUnits / 2;\n    const viewportBottom = this.centerWorldCoords.y - this.heightInWorldUnits / 2;\n    const viewportTop = this.centerWorldCoords.y + this.heightInWorldUnits / 2;\n    if (\n      chunkLeft > viewportRight ||\n      chunkRight < viewportLeft ||\n      chunkBottom > viewportTop ||\n      chunkTop < viewportBottom\n    ) {\n      return false;\n    }\n    return true;\n  }\n\n  private isValidWorldWidth(width: number) {\n    return width >= this.minWorldWidth && width <= this.maxWorldWidth;\n  }\n\n  private setWorldWidth(width: number): void {\n    if (this.isValidWorldWidth(width)) {\n      this.widthInWorldUnits = width;\n      this.heightInWorldUnits = (width * this.viewportHeight) / this.viewportWidth;\n      this.scale = this.widthInWorldUnits / this.viewportWidth;\n      this.updateDiagnostics();\n    }\n  }\n\n  public setWorldHeight(height: number): void {\n    this.heightInWorldUnits = height;\n    this.widthInWorldUnits = (height * this.viewportWidth) / this.viewportHeight;\n    this.updateDiagnostics();\n  }\n\n  private updateDiagnostics() {\n    this.diagnosticUpdater?.updateDiagnostics(\n      (d) => (d.width = Math.floor(this.widthInWorldUnits))\n    );\n    this.diagnosticUpdater?.updateDiagnostics(\n      (d) => (d.height = Math.floor(this.heightInWorldUnits))\n    );\n  }\n}\n\nexport default Viewport;\n"
  },
  {
    "path": "src/Frontend/Pages/App.tsx",
    "content": "import { CONTRACT_ADDRESS } from '@darkforest_eth/contracts';\nimport { address } from '@darkforest_eth/serde';\nimport React from 'react';\nimport { BrowserRouter as Router, Redirect, Route, Switch } from 'react-router-dom';\nimport { createGlobalStyle } from 'styled-components';\nimport { Theme } from '../Components/Theme';\nimport { LandingPageBackground } from '../Renderers/LandingPageCanvas';\nimport dfstyles from '../Styles/dfstyles';\nimport { CreateLobby } from './CreateLobby';\nimport { EventsPage } from './EventsPage';\nimport { GameLandingPage } from './GameLandingPage';\nimport { GifMaker } from './GifMaker';\nimport LandingPage from './LandingPage';\nimport { NotFoundPage } from './NotFoundPage';\nimport { ShareArtifact } from './ShareArtifact';\nimport { SharePlanet } from './SharePlanet';\nimport { TestArtifactImages } from './TestArtifactImages';\nimport { TxConfirmPopup } from './TxConfirmPopup';\nimport UnsubscribePage from './UnsubscribePage';\nimport { ValhallaPage } from './ValhallaPage';\n\nconst isProd = process.env.NODE_ENV === 'production';\n\nconst defaultAddress = address(CONTRACT_ADDRESS);\n\nfunction App() {\n  return (\n    <>\n      <GlobalStyle />\n      {/* Provides theming for WebComponents from the `@darkforest_eth/ui` package */}\n      <Theme color='dark' scale='medium'>\n        <Router>\n          <Switch>\n            <Redirect path='/play' to={`/play/${defaultAddress}`} push={true} exact={true} />\n            <Route path='/play/:contract' component={GameLandingPage} />\n            <Route path='/events' component={EventsPage} />\n            <Route path='/' exact component={LandingPage} />\n            <Redirect path='/lobby' to={`/lobby/${defaultAddress}`} push={true} exact={true} />\n            <Route path='/lobby/:contract' component={CreateLobby} />\n            <Route path='/planet/:locationId' component={SharePlanet} />\n            <Route path='/artifact/:artifactId' component={ShareArtifact} />\n            <Route\n              path='/wallet/:contract/:addr/:actionId/:balance/:method'\n              component={TxConfirmPopup}\n            />\n            <Route path='/unsubscribe' component={UnsubscribePage} />\n            <Route path='/valhalla' component={ValhallaPage} />\n            {!isProd && <Route path='/images' component={TestArtifactImages} />}\n            {!isProd && <Route path='/gifs' component={GifMaker} />}\n            {!isProd && <Route path='/bg' component={LandingPageBackground} />}\n            <Route path='*' component={NotFoundPage} />\n          </Switch>\n        </Router>\n      </Theme>\n    </>\n  );\n}\n\nconst GlobalStyle = createGlobalStyle`\nbody {\n  width: 100vw;\n  min-height: 100vh;\n  background-color: ${dfstyles.colors.background};\n}\n`;\n\nexport default App;\n"
  },
  {
    "path": "src/Frontend/Pages/CreateLobby.tsx",
    "content": "import { INIT_ADDRESS } from '@darkforest_eth/contracts';\n// This is loaded as URL paths by a webpack loader\nimport initContractAbiUrl from '@darkforest_eth/contracts/abis/DFInitialize.json';\nimport { EthConnection } from '@darkforest_eth/network';\nimport { address } from '@darkforest_eth/serde';\nimport { ArtifactRarity, EthAddress, UnconfirmedCreateLobby } from '@darkforest_eth/types';\nimport { Contract } from 'ethers';\nimport _ from 'lodash';\nimport React, { useCallback, useEffect, useMemo, useState } from 'react';\nimport { RouteComponentProps } from 'react-router-dom';\nimport { ContractsAPI, makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI';\nimport { ContractsAPIEvent } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { InitRenderState, Wrapper } from '../Components/GameLandingPageComponents';\nimport { ConfigurationPane } from '../Panes/Lobbies/ConfigurationPane';\nimport { Minimap } from '../Panes/Lobbies/MinimapPane';\nimport { MinimapConfig } from '../Panes/Lobbies/MinimapUtils';\nimport { LobbyInitializers } from '../Panes/Lobbies/Reducer';\nimport { listenForKeyboardEvents, unlinkKeyboardEvents } from '../Utils/KeyEmitters';\nimport { CadetWormhole } from '../Views/CadetWormhole';\nimport { LobbyLandingPage } from './LobbyLandingPage';\n\ntype ErrorState =\n  | { type: 'invalidAddress' }\n  | { type: 'contractLoad' }\n  | { type: 'invalidContract' }\n  | { type: 'invalidCreate' };\n\nexport function CreateLobby({ match }: RouteComponentProps<{ contract: string }>) {\n  const [connection, setConnection] = useState<EthConnection | undefined>();\n  const [ownerAddress, setOwnerAddress] = useState<EthAddress | undefined>();\n  const [contract, setContract] = useState<ContractsAPI | undefined>();\n  const [startingConfig, setStartingConfig] = useState<LobbyInitializers | undefined>();\n  const [lobbyAddress, setLobbyAddress] = useState<EthAddress | undefined>();\n  const [minimapConfig, setMinimapConfig] = useState<MinimapConfig | undefined>();\n\n  const onMapChange = useMemo(() => {\n    return _.debounce((config: MinimapConfig) => setMinimapConfig(config), 500);\n  }, [setMinimapConfig]);\n\n  let contractAddress: EthAddress | undefined;\n  try {\n    contractAddress = address(match.params.contract);\n  } catch (err) {\n    console.error('Invalid address', err);\n  }\n\n  const [errorState, setErrorState] = useState<ErrorState | undefined>(\n    contractAddress ? undefined : { type: 'invalidAddress' }\n  );\n\n  useEffect(() => {\n    listenForKeyboardEvents();\n\n    return () => unlinkKeyboardEvents();\n  }, []);\n\n  const onReady = useCallback(\n    (connection: EthConnection) => {\n      setConnection(connection);\n      setOwnerAddress(connection.getAddress());\n    },\n    [setConnection]\n  );\n\n  useEffect(() => {\n    if (connection && contractAddress) {\n      makeContractsAPI({ connection, contractAddress })\n        .then((contract) => setContract(contract))\n        .catch((e) => {\n          console.log(e);\n          setErrorState({ type: 'contractLoad' });\n        });\n    }\n  }, [connection, contractAddress]);\n\n  useEffect(() => {\n    if (contract) {\n      contract\n        .getConstants()\n        .then((config) => {\n          setStartingConfig({\n            // Explicitly defaulting this to false\n            WHITELIST_ENABLED: false,\n            // TODO: Figure out if we should expose this from contract\n            START_PAUSED: false,\n            ADMIN_CAN_ADD_PLANETS: config.ADMIN_CAN_ADD_PLANETS,\n            WORLD_RADIUS_LOCKED: config.WORLD_RADIUS_LOCKED,\n            WORLD_RADIUS_MIN: config.WORLD_RADIUS_MIN,\n            DISABLE_ZK_CHECKS: config.DISABLE_ZK_CHECKS,\n            PLANETHASH_KEY: config.PLANETHASH_KEY,\n            SPACETYPE_KEY: config.SPACETYPE_KEY,\n            BIOMEBASE_KEY: config.BIOMEBASE_KEY,\n            PERLIN_MIRROR_X: config.PERLIN_MIRROR_X,\n            PERLIN_MIRROR_Y: config.PERLIN_MIRROR_Y,\n            PERLIN_LENGTH_SCALE: config.PERLIN_LENGTH_SCALE,\n            MAX_NATURAL_PLANET_LEVEL: config.MAX_NATURAL_PLANET_LEVEL,\n            TIME_FACTOR_HUNDREDTHS: config.TIME_FACTOR_HUNDREDTHS,\n            PERLIN_THRESHOLD_1: config.PERLIN_THRESHOLD_1,\n            PERLIN_THRESHOLD_2: config.PERLIN_THRESHOLD_2,\n            PERLIN_THRESHOLD_3: config.PERLIN_THRESHOLD_3,\n            INIT_PERLIN_MIN: config.INIT_PERLIN_MIN,\n            INIT_PERLIN_MAX: config.INIT_PERLIN_MAX,\n            SPAWN_RIM_AREA: config.SPAWN_RIM_AREA,\n            BIOME_THRESHOLD_1: config.BIOME_THRESHOLD_1,\n            BIOME_THRESHOLD_2: config.BIOME_THRESHOLD_2,\n            PLANET_LEVEL_THRESHOLDS: config.PLANET_LEVEL_THRESHOLDS,\n            PLANET_RARITY: config.PLANET_RARITY,\n            LOCATION_REVEAL_COOLDOWN: config.LOCATION_REVEAL_COOLDOWN,\n            // TODO: Implement when we add this scoring contract back\n            CLAIM_PLANET_COOLDOWN: 0,\n            // TODO: Need to think through this implementation a bit more, even if only toggling planet types\n            PLANET_TYPE_WEIGHTS: config.PLANET_TYPE_WEIGHTS,\n            // TODO: Rename in one of the places\n            // TODO: Implement... Needs a datetime input component (WIP)\n            TOKEN_MINT_END_TIMESTAMP: 1948939200, // new Date(\"2031-10-05T04:00:00.000Z\").getTime() / 1000,\n            PHOTOID_ACTIVATION_DELAY: config.PHOTOID_ACTIVATION_DELAY,\n            SILVER_SCORE_VALUE: config.SILVER_SCORE_VALUE,\n            ARTIFACT_POINT_VALUES: [\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Unknown],\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Common],\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Rare],\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Epic],\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Legendary],\n              config.ARTIFACT_POINT_VALUES[ArtifactRarity.Mythic],\n            ],\n            PLANET_TRANSFER_ENABLED: config.PLANET_TRANSFER_ENABLED,\n            SPACE_JUNK_ENABLED: config.SPACE_JUNK_ENABLED,\n            SPACE_JUNK_LIMIT: config.SPACE_JUNK_LIMIT,\n            PLANET_LEVEL_JUNK: config.PLANET_LEVEL_JUNK,\n            ABANDON_SPEED_CHANGE_PERCENT: config.ABANDON_SPEED_CHANGE_PERCENT,\n            ABANDON_RANGE_CHANGE_PERCENT: config.ABANDON_RANGE_CHANGE_PERCENT,\n            CAPTURE_ZONES_ENABLED: config.CAPTURE_ZONES_ENABLED,\n            CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL,\n            CAPTURE_ZONE_COUNT: config.CAPTURE_ZONE_COUNT,\n            CAPTURE_ZONE_PLANET_LEVEL_SCORE: config.CAPTURE_ZONE_PLANET_LEVEL_SCORE,\n            CAPTURE_ZONE_RADIUS: config.CAPTURE_ZONE_RADIUS,\n            CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED,\n            CAPTURE_ZONES_PER_5000_WORLD_RADIUS: config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS,\n          });\n        })\n        .catch((e) => {\n          console.log(e);\n          setErrorState({ type: 'invalidContract' });\n        });\n    }\n  }, [contract]);\n\n  async function createLobby(config: LobbyInitializers) {\n    if (!contract) {\n      setErrorState({ type: 'invalidCreate' });\n      return;\n    }\n\n    const initializers = { ...startingConfig, ...config };\n\n    console.log(initializers);\n    const InitABI = await fetch(initContractAbiUrl).then((r) => r.json());\n    const artifactBaseURI = '';\n    const initInterface = Contract.getInterface(InitABI);\n    const initAddress = INIT_ADDRESS;\n    const initFunctionCall = initInterface.encodeFunctionData('init', [\n      initializers.WHITELIST_ENABLED,\n      artifactBaseURI,\n      initializers,\n    ]);\n    const txIntent: UnconfirmedCreateLobby = {\n      methodName: 'createLobby',\n      contract: contract.contract,\n      args: Promise.resolve([initAddress, initFunctionCall]),\n    };\n\n    contract.once(ContractsAPIEvent.LobbyCreated, (owner: EthAddress, lobby: EthAddress) => {\n      if (owner === ownerAddress) {\n        setLobbyAddress(lobby);\n      }\n    });\n\n    const tx = await contract.submitTransaction(txIntent, {\n      // The createLobby function costs somewhere around 12mil gas\n      gasLimit: '16777215',\n    });\n    await tx.confirmedPromise;\n  }\n\n  if (errorState) {\n    switch (errorState.type) {\n      case 'contractLoad':\n        return <CadetWormhole imgUrl='/public/img/wrong-text.png' />;\n      case 'invalidAddress':\n      case 'invalidContract':\n        return <CadetWormhole imgUrl='/public/img/no-contract-text.png' />;\n      case 'invalidCreate':\n        return <CadetWormhole imgUrl='/public/img/wrong-text.png' />;\n      default:\n        // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking\n        const _exhaustive: never = errorState;\n        return _exhaustive;\n    }\n  }\n  let content;\n  if (startingConfig) {\n    content = (\n      <>\n        <ConfigurationPane\n          modalIndex={2}\n          lobbyAddress={lobbyAddress}\n          startingConfig={startingConfig}\n          onMapChange={onMapChange}\n          onCreate={createLobby}\n        />\n        {/* Minimap uses modalIndex=1 so it is always underneath the configuration pane */}\n        <Minimap modalIndex={1} config={minimapConfig} />\n      </>\n    );\n  } else {\n    content = <LobbyLandingPage onReady={onReady} />;\n  }\n\n  return (\n    <Wrapper initRender={InitRenderState.NONE} terminalEnabled={false}>\n      {content}\n    </Wrapper>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/EventsPage.tsx",
    "content": "import React from 'react';\n\nexport function EventsPage() {\n  return (\n    <iframe\n      src='https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23F09300&ctz=America%2FLos_Angeles&src=Y19mdGVzcWw3czE5dDE5a3Y3Mmsxbjc2M29wb0Bncm91cC5jYWxlbmRhci5nb29nbGUuY29t&color=%23D50000'\n      width='100%'\n      height='100%'\n      // frameborder='0'\n      scrolling='no'\n    ></iframe>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/GameLandingPage.tsx",
    "content": "import { BLOCK_EXPLORER_URL } from '@darkforest_eth/constants';\nimport { CONTRACT_ADDRESS } from '@darkforest_eth/contracts';\nimport { DarkForest } from '@darkforest_eth/contracts/typechain';\nimport { EthConnection, neverResolves, weiToEth } from '@darkforest_eth/network';\nimport { address } from '@darkforest_eth/serde';\nimport { bigIntFromKey } from '@darkforest_eth/whitelist';\nimport { utils, Wallet } from 'ethers';\nimport { reverse } from 'lodash';\nimport React, { useCallback, useEffect, useRef, useState } from 'react';\nimport { RouteComponentProps, useHistory } from 'react-router-dom';\nimport { makeContractsAPI } from '../../Backend/GameLogic/ContractsAPI';\nimport GameManager, { GameManagerEvent } from '../../Backend/GameLogic/GameManager';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport TutorialManager, { TutorialState } from '../../Backend/GameLogic/TutorialManager';\nimport { addAccount, getAccounts } from '../../Backend/Network/AccountManager';\nimport { getEthConnection, loadDiamondContract } from '../../Backend/Network/Blockchain';\nimport {\n  callRegisterAndWaitForConfirmation,\n  EmailResponse,\n  RegisterConfirmationResponse,\n  requestDevFaucet,\n  submitInterestedEmail,\n  submitPlayerEmail,\n} from '../../Backend/Network/UtilityServerAPI';\nimport { getWhitelistArgs } from '../../Backend/Utils/WhitelistSnarkArgsHelper';\nimport { ZKArgIdx } from '../../_types/darkforest/api/ContractsAPITypes';\nimport {\n  GameWindowWrapper,\n  InitRenderState,\n  TerminalToggler,\n  TerminalWrapper,\n  Wrapper,\n} from '../Components/GameLandingPageComponents';\nimport { MythicLabelText } from '../Components/Labels/MythicLabel';\nimport { TextPreview } from '../Components/TextPreview';\nimport { TopLevelDivProvider, UIManagerProvider } from '../Utils/AppHooks';\nimport { Incompatibility, unsupportedFeatures } from '../Utils/BrowserChecks';\nimport { TerminalTextStyle } from '../Utils/TerminalTypes';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport { GameWindowLayout } from '../Views/GameWindowLayout';\nimport { Terminal, TerminalHandle } from '../Views/Terminal';\n\nconst enum TerminalPromptStep {\n  NONE,\n  COMPATIBILITY_CHECKS_PASSED,\n  DISPLAY_ACCOUNTS,\n  GENERATE_ACCOUNT,\n  IMPORT_ACCOUNT,\n  ACCOUNT_SET,\n  ASKING_HAS_WHITELIST_KEY,\n  ASKING_WAITLIST_EMAIL,\n  ASKING_WHITELIST_KEY,\n  ASKING_PLAYER_EMAIL,\n  FETCHING_ETH_DATA,\n  ASK_ADD_ACCOUNT,\n  ADD_ACCOUNT,\n  NO_HOME_PLANET,\n  SEARCHING_FOR_HOME_PLANET,\n  ALL_CHECKS_PASS,\n  COMPLETE,\n  TERMINATED,\n  ERROR,\n}\n\nexport function GameLandingPage({ match, location }: RouteComponentProps<{ contract: string }>) {\n  const history = useHistory();\n  const terminalHandle = useRef<TerminalHandle>();\n  const gameUIManagerRef = useRef<GameUIManager | undefined>();\n  const topLevelContainer = useRef<HTMLDivElement | null>(null);\n\n  const [gameManager, setGameManager] = useState<GameManager | undefined>();\n  const [terminalVisible, setTerminalVisible] = useState(true);\n  const [initRenderState, setInitRenderState] = useState(InitRenderState.NONE);\n  const [ethConnection, setEthConnection] = useState<EthConnection | undefined>();\n  const [step, setStep] = useState(TerminalPromptStep.NONE);\n\n  const params = new URLSearchParams(location.search);\n  const useZkWhitelist = params.has('zkWhitelist');\n  const selectedAddress = params.get('account');\n  const contractAddress = address(match.params.contract);\n  const isLobby = contractAddress !== address(CONTRACT_ADDRESS);\n\n  useEffect(() => {\n    getEthConnection()\n      .then((ethConnection) => setEthConnection(ethConnection))\n      .catch((e) => {\n        alert('error connecting to blockchain');\n        console.log(e);\n      });\n  }, []);\n\n  const isProd = process.env.NODE_ENV === 'production';\n\n  const advanceStateFromNone = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const issues = await unsupportedFeatures();\n\n      if (issues.includes(Incompatibility.MobileOrTablet)) {\n        terminal.current?.println(\n          'ERROR: Mobile or tablet device detected. Please use desktop.',\n          TerminalTextStyle.Red\n        );\n      }\n\n      if (issues.includes(Incompatibility.NoIDB)) {\n        terminal.current?.println(\n          'ERROR: IndexedDB not found. Try using a different browser.',\n          TerminalTextStyle.Red\n        );\n      }\n\n      if (issues.includes(Incompatibility.UnsupportedBrowser)) {\n        terminal.current?.println(\n          'ERROR: Browser unsupported. Try Brave, Firefox, or Chrome.',\n          TerminalTextStyle.Red\n        );\n      }\n\n      if (issues.length > 0) {\n        terminal.current?.print(\n          `${issues.length.toString()} errors found. `,\n          TerminalTextStyle.Red\n        );\n        terminal.current?.println('Please resolve them and refresh the page.');\n        setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL);\n      } else {\n        setStep(TerminalPromptStep.COMPATIBILITY_CHECKS_PASSED);\n      }\n    },\n    []\n  );\n\n  const advanceStateFromCompatibilityPassed = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      if (isLobby) {\n        terminal.current?.newline();\n        terminal.current?.printElement(\n          <MythicLabelText text={`You are joining a Dark Forest lobby`} />\n        );\n        terminal.current?.newline();\n        terminal.current?.newline();\n      } else {\n        terminal.current?.newline();\n        terminal.current?.newline();\n        terminal.current?.printElement(<MythicLabelText text={`                 Dark Forest`} />);\n        terminal.current?.newline();\n        terminal.current?.newline();\n\n        terminal.current?.print('    ');\n        terminal.current?.print('Version', TerminalTextStyle.Sub);\n        terminal.current?.print('    ');\n        terminal.current?.print('Date', TerminalTextStyle.Sub);\n        terminal.current?.print('              ');\n        terminal.current?.print('Champion', TerminalTextStyle.Sub);\n        terminal.current?.newline();\n\n        terminal.current?.print('    v0.1       ', TerminalTextStyle.Text);\n        terminal.current?.print('02/05/2020        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          'Dylan Field',\n          () => {\n            window.open('https://twitter.com/zoink');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n        terminal.current?.print('    v0.2       ', TerminalTextStyle.Text);\n        terminal.current?.println('06/06/2020        Nate Foss', TerminalTextStyle.Text);\n        terminal.current?.print('    v0.3       ', TerminalTextStyle.Text);\n        terminal.current?.print('08/07/2020        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          '@hideandcleanse',\n          () => {\n            window.open('https://twitter.com/hideandcleanse');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n        terminal.current?.print('    v0.4       ', TerminalTextStyle.Text);\n        terminal.current?.print('10/02/2020        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          'Jacob Rosenthal',\n          () => {\n            window.open('https://twitter.com/jacobrosenthal');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n        terminal.current?.print('    v0.5       ', TerminalTextStyle.Text);\n        terminal.current?.print('12/25/2020        ', TerminalTextStyle.Text);\n        terminal.current?.printElement(\n          <TextPreview\n            text={'0xb05d95422bf8d5024f9c340e8f7bd696d67ee3a9'}\n            focusedWidth={'100px'}\n            unFocusedWidth={'100px'}\n          />\n        );\n        terminal.current?.println('');\n\n        terminal.current?.print('    v0.6 r1    ', TerminalTextStyle.Text);\n        terminal.current?.print('05/22/2021        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          'Ansgar Dietrichs',\n          () => {\n            window.open('https://twitter.com/adietrichs');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n\n        terminal.current?.print('    v0.6 r2    ', TerminalTextStyle.Text);\n        terminal.current?.print('06/28/2021        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          '@orden_gg',\n          () => {\n            window.open('https://twitter.com/orden_gg');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n\n        terminal.current?.print('    v0.6 r3    ', TerminalTextStyle.Text);\n        terminal.current?.print('08/22/2021        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          '@dropswap_gg',\n          () => {\n            window.open('https://twitter.com/dropswap_gg');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n\n        terminal.current?.print('    v0.6 r4    ', TerminalTextStyle.Text);\n        terminal.current?.print('10/01/2021        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          '@orden_gg',\n          () => {\n            window.open('https://twitter.com/orden_gg');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n\n        terminal.current?.print('    v0.6 r5    ', TerminalTextStyle.Text);\n        terminal.current?.print('02/18/2022        ', TerminalTextStyle.Text);\n        terminal.current?.printLink(\n          '@d_fdao',\n          () => {\n            window.open('https://twitter.com/d_fdao');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.print(' + ');\n        terminal.current?.printLink(\n          '@orden_gg',\n          () => {\n            window.open('https://twitter.com/orden_gg');\n          },\n          TerminalTextStyle.Text\n        );\n        terminal.current?.newline();\n        terminal.current?.newline();\n      }\n\n      const accounts = getAccounts();\n      terminal.current?.println(`Found ${accounts.length} accounts on this device.`);\n      terminal.current?.println(``);\n\n      if (accounts.length > 0) {\n        terminal.current?.print('(a) ', TerminalTextStyle.Sub);\n        terminal.current?.println('Login with existing account.');\n      }\n\n      terminal.current?.print('(n) ', TerminalTextStyle.Sub);\n      terminal.current?.println(`Generate new burner wallet account.`);\n      terminal.current?.print('(i) ', TerminalTextStyle.Sub);\n      terminal.current?.println(`Import private key.`);\n      terminal.current?.println(``);\n      terminal.current?.println(`Select an option:`, TerminalTextStyle.Text);\n\n      if (selectedAddress !== null) {\n        terminal.current?.println(\n          `Selecting account ${selectedAddress} from url...`,\n          TerminalTextStyle.Green\n        );\n\n        // Search accounts backwards in case a player has used a private key more than once.\n        // In that case, we want to take the most recently created account.\n        const account = reverse(getAccounts()).find((a) => a.address === selectedAddress);\n        if (!account) {\n          terminal.current?.println('Unrecognized account found in url.', TerminalTextStyle.Red);\n          return;\n        }\n\n        try {\n          await ethConnection?.setAccount(account.privateKey);\n          setStep(TerminalPromptStep.ACCOUNT_SET);\n        } catch (e) {\n          terminal.current?.println(\n            'An unknown error occurred. please try again.',\n            TerminalTextStyle.Red\n          );\n        }\n      } else {\n        const userInput = await terminal.current?.getInput();\n        if (userInput === 'a' && accounts.length > 0) {\n          setStep(TerminalPromptStep.DISPLAY_ACCOUNTS);\n        } else if (userInput === 'n') {\n          setStep(TerminalPromptStep.GENERATE_ACCOUNT);\n        } else if (userInput === 'i') {\n          setStep(TerminalPromptStep.IMPORT_ACCOUNT);\n        } else {\n          terminal.current?.println('Unrecognized input. Please try again.');\n          await advanceStateFromCompatibilityPassed(terminal);\n        }\n      }\n    },\n    [isLobby, ethConnection, selectedAddress]\n  );\n\n  const advanceStateFromDisplayAccounts = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println(``);\n      const accounts = getAccounts();\n      for (let i = 0; i < accounts.length; i += 1) {\n        terminal.current?.print(`(${i + 1}): `, TerminalTextStyle.Sub);\n        terminal.current?.println(`${accounts[i].address}`);\n      }\n      terminal.current?.println(``);\n      terminal.current?.println(`Select an account:`, TerminalTextStyle.Text);\n\n      const selection = +((await terminal.current?.getInput()) || '');\n      if (isNaN(selection) || selection > accounts.length) {\n        terminal.current?.println('Unrecognized input. Please try again.');\n        await advanceStateFromDisplayAccounts(terminal);\n      } else {\n        const account = accounts[selection - 1];\n        try {\n          await ethConnection?.setAccount(account.privateKey);\n          setStep(TerminalPromptStep.ACCOUNT_SET);\n        } catch (e) {\n          terminal.current?.println(\n            'An unknown error occurred. please try again.',\n            TerminalTextStyle.Red\n          );\n        }\n      }\n    },\n    [ethConnection]\n  );\n\n  const advanceStateFromGenerateAccount = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const newWallet = Wallet.createRandom();\n      const newSKey = newWallet.privateKey;\n      const newAddr = address(newWallet.address);\n      try {\n        addAccount(newSKey);\n        ethConnection?.setAccount(newSKey);\n\n        terminal.current?.println(``);\n        terminal.current?.print(`Created new burner wallet with address `);\n        terminal.current?.printElement(<TextPreview text={newAddr} unFocusedWidth={'100px'} />);\n        terminal.current?.println(``);\n        terminal.current?.println('');\n        terminal.current?.println(\n          'Note: Burner wallets are stored in local storage.',\n          TerminalTextStyle.Text\n        );\n        terminal.current?.println('They are relatively insecure and you should avoid ');\n        terminal.current?.println('storing substantial funds in them.');\n        terminal.current?.println('');\n        terminal.current?.println('Also, clearing browser local storage/cache will render your');\n        terminal.current?.println(\n          'burner wallets inaccessible, unless you export your private keys.'\n        );\n        terminal.current?.println('');\n        terminal.current?.println('Press any key to continue:', TerminalTextStyle.Text);\n\n        await terminal.current?.getInput();\n        setStep(TerminalPromptStep.ACCOUNT_SET);\n      } catch (e) {\n        terminal.current?.println(\n          'An unknown error occurred. please try again.',\n          TerminalTextStyle.Red\n        );\n      }\n    },\n    [ethConnection]\n  );\n\n  const advanceStateFromImportAccount = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println(\n        'Enter the 0x-prefixed private key of the account you wish to import',\n        TerminalTextStyle.Text\n      );\n      terminal.current?.println(\n        \"NOTE: THIS WILL STORE THE PRIVATE KEY IN YOUR BROWSER'S LOCAL STORAGE\",\n        TerminalTextStyle.Text\n      );\n      terminal.current?.println(\n        'Local storage is relatively insecure. We recommend only importing accounts with zero-to-no funds.'\n      );\n      const newSKey = (await terminal.current?.getInput()) || '';\n      try {\n        const newAddr = address(utils.computeAddress(newSKey));\n\n        addAccount(newSKey);\n\n        ethConnection?.setAccount(newSKey);\n        terminal.current?.println(`Imported account with address ${newAddr}.`);\n        setStep(TerminalPromptStep.ACCOUNT_SET);\n      } catch (e) {\n        terminal.current?.println(\n          'An unknown error occurred. please try again.',\n          TerminalTextStyle.Red\n        );\n      }\n    },\n    [ethConnection]\n  );\n\n  const advanceStateFromAccountSet = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      try {\n        const playerAddress = ethConnection?.getAddress();\n        if (!playerAddress || !ethConnection) throw new Error('not logged in');\n\n        const whitelist = await ethConnection.loadContract<DarkForest>(\n          contractAddress,\n          loadDiamondContract\n        );\n        const isWhitelisted = await whitelist.isWhitelisted(playerAddress);\n        // TODO(#2329): isWhitelisted should just check the contractOwner\n        const adminAddress = address(await whitelist.adminAddress());\n\n        terminal.current?.println('');\n        terminal.current?.print('Checking if whitelisted... ');\n\n        // TODO(#2329): isWhitelisted should just check the contractOwner\n        if (isWhitelisted || playerAddress === adminAddress) {\n          terminal.current?.println('Player whitelisted.');\n          terminal.current?.println('');\n          terminal.current?.println(`Welcome, player ${playerAddress}.`);\n          // TODO: Provide own env variable for this feature\n          if (!isProd) {\n            // in development, automatically get some ether from faucet\n            const balance = weiToEth(await ethConnection?.loadBalance(playerAddress));\n            if (balance === 0) {\n              await requestDevFaucet(playerAddress);\n            }\n          }\n          setStep(TerminalPromptStep.FETCHING_ETH_DATA);\n        } else {\n          setStep(TerminalPromptStep.ASKING_HAS_WHITELIST_KEY);\n        }\n      } catch (e) {\n        console.error(`error connecting to whitelist: ${e}`);\n        terminal.current?.println(\n          'ERROR: Could not connect to whitelist contract. Please refresh and try again in a few minutes.',\n          TerminalTextStyle.Red\n        );\n        setStep(TerminalPromptStep.TERMINATED);\n      }\n    },\n    [ethConnection, isProd, contractAddress]\n  );\n\n  const advanceStateFromAskHasWhitelistKey = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.print('Do you have a whitelist key?', TerminalTextStyle.Text);\n      terminal.current?.println(' (y/n)');\n      const userInput = await terminal.current?.getInput();\n      if (userInput === 'y') {\n        setStep(TerminalPromptStep.ASKING_WHITELIST_KEY);\n      } else if (userInput === 'n') {\n        setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL);\n      } else {\n        terminal.current?.println('Unrecognized input. Please try again.');\n        await advanceStateFromAskHasWhitelistKey(terminal);\n      }\n    },\n    []\n  );\n\n  const advanceStateFromAskWhitelistKey = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const address = ethConnection?.getAddress();\n      if (!address) throw new Error('not logged in');\n\n      terminal.current?.println(\n        'Please enter your invite key (XXXXXX-XXXXXX-XXXXXX-XXXXXX):',\n        TerminalTextStyle.Sub\n      );\n\n      const key = (await terminal.current?.getInput()) || '';\n\n      terminal.current?.print('Processing key... (this may take up to 30s)');\n      terminal.current?.newline();\n\n      if (!useZkWhitelist) {\n        let registerConfirmationResponse = {} as RegisterConfirmationResponse;\n        try {\n          registerConfirmationResponse = await callRegisterAndWaitForConfirmation(\n            key,\n            address,\n            terminal\n          );\n        } catch (e) {\n          registerConfirmationResponse = {\n            canRetry: true,\n            errorMessage:\n              'There was an error connecting to the whitelist server. Please try again later.',\n          };\n        }\n\n        if (!registerConfirmationResponse.txHash) {\n          terminal.current?.println(\n            'ERROR: ' + registerConfirmationResponse.errorMessage,\n            TerminalTextStyle.Red\n          );\n          if (registerConfirmationResponse.canRetry) {\n            terminal.current?.println('Press any key to try again.');\n            await terminal.current?.getInput();\n            advanceStateFromAskWhitelistKey(terminal);\n          } else {\n            setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL);\n          }\n        } else {\n          terminal.current?.print('Successfully joined game. ', TerminalTextStyle.Green);\n          terminal.current?.print(`Welcome, player `);\n          terminal.current?.println(address, TerminalTextStyle.Text);\n          terminal.current?.print('Sent player $0.15 :) ', TerminalTextStyle.Blue);\n          terminal.current?.printLink(\n            '(View Transaction)',\n            () => {\n              window.open(`${BLOCK_EXPLORER_URL}/${registerConfirmationResponse.txHash}`);\n            },\n            TerminalTextStyle.Blue\n          );\n          terminal.current?.newline();\n          setStep(TerminalPromptStep.ASKING_PLAYER_EMAIL);\n        }\n      } else {\n        if (!ethConnection) throw new Error('no eth connection');\n        const contractsAPI = await makeContractsAPI({ connection: ethConnection, contractAddress });\n\n        const keyBigInt = bigIntFromKey(key);\n        const snarkArgs = await getWhitelistArgs(keyBigInt, address, terminal);\n        try {\n          const ukReceipt = await contractsAPI.contract.useKey(\n            snarkArgs[ZKArgIdx.PROOF_A],\n            snarkArgs[ZKArgIdx.PROOF_B],\n            snarkArgs[ZKArgIdx.PROOF_C],\n            [...snarkArgs[ZKArgIdx.DATA]]\n          );\n          await ukReceipt.wait();\n          terminal.current?.print('Successfully joined game. ', TerminalTextStyle.Green);\n          terminal.current?.print(`Welcome, player `);\n          terminal.current?.println(address, TerminalTextStyle.Text);\n          terminal.current?.print('Sent player $0.15 :) ', TerminalTextStyle.Blue);\n          terminal.current?.printLink(\n            '(View Transaction)',\n            () => {\n              window.open(`${BLOCK_EXPLORER_URL}/${ukReceipt.hash}`);\n            },\n            TerminalTextStyle.Blue\n          );\n          terminal.current?.newline();\n          setStep(TerminalPromptStep.ASKING_PLAYER_EMAIL);\n        } catch (e) {\n          const error = e.error;\n          if (error instanceof Error) {\n            const invalidKey = error.message.includes('invalid key');\n            if (invalidKey) {\n              terminal.current?.println(`ERROR: Key ${key} is not valid.`, TerminalTextStyle.Red);\n              setStep(TerminalPromptStep.ASKING_WAITLIST_EMAIL);\n            } else {\n              terminal.current?.println(`ERROR: Something went wrong.`, TerminalTextStyle.Red);\n              terminal.current?.println('Press any key to try again.');\n              await terminal.current?.getInput();\n              advanceStateFromAskWhitelistKey(terminal);\n            }\n          }\n          console.error('Error whitelisting.');\n        }\n      }\n    },\n    [ethConnection, contractAddress, useZkWhitelist]\n  );\n\n  const advanceStateFromAskWaitlistEmail = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println(\n        'Enter your email address to sign up for the whitelist.',\n        TerminalTextStyle.Text\n      );\n      const email = (await terminal.current?.getInput()) || '';\n      terminal.current?.print('Response pending... ');\n      const response = await submitInterestedEmail(email);\n      if (response === EmailResponse.Success) {\n        terminal.current?.println('Email successfully recorded. ', TerminalTextStyle.Green);\n        terminal.current?.println(\n          'Keep an eye out for updates and invite keys in the next few weeks. Press ENTER to return to the homepage.',\n          TerminalTextStyle.Sub\n        );\n        setStep(TerminalPromptStep.TERMINATED);\n        (await await terminal.current?.getInput()) || '';\n        history.push('/');\n      } else if (response === EmailResponse.Invalid) {\n        terminal.current?.println('Email invalid. Please try again.', TerminalTextStyle.Red);\n      } else {\n        terminal.current?.print('ERROR: Server error. ', TerminalTextStyle.Red);\n        terminal.current?.print('Press ENTER to return to homepage.', TerminalTextStyle.Sub);\n        (await await terminal.current?.getInput()) || '';\n        setStep(TerminalPromptStep.TERMINATED);\n        history.push('/');\n      }\n    },\n    [history]\n  );\n\n  const advanceStateFromAskPlayerEmail = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const address = ethConnection?.getAddress();\n      if (!address) throw new Error('not logged in');\n\n      terminal.current?.print('Enter your email address. ', TerminalTextStyle.Text);\n      terminal.current?.println(\"We'll use this email address to notify you if you win a prize.\");\n\n      const email = (await terminal.current?.getInput()) || '';\n      const response = await submitPlayerEmail(await ethConnection?.signMessageObject({ email }));\n\n      if (response === EmailResponse.Success) {\n        terminal.current?.println('Email successfully recorded.');\n        setStep(TerminalPromptStep.FETCHING_ETH_DATA);\n      } else if (response === EmailResponse.Invalid) {\n        terminal.current?.println('Email invalid.', TerminalTextStyle.Red);\n        advanceStateFromAskPlayerEmail(terminal);\n      } else {\n        terminal.current?.println('Error recording email.', TerminalTextStyle.Red);\n        setStep(TerminalPromptStep.FETCHING_ETH_DATA);\n      }\n    },\n    [ethConnection]\n  );\n\n  const advanceStateFromFetchingEthData = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      let newGameManager: GameManager;\n\n      try {\n        if (!ethConnection) throw new Error('no eth connection');\n\n        newGameManager = await GameManager.create({\n          connection: ethConnection,\n          terminal,\n          contractAddress,\n        });\n      } catch (e) {\n        console.error(e);\n\n        setStep(TerminalPromptStep.ERROR);\n\n        terminal.current?.print(\n          'Network under heavy load. Please refresh the page, and check ',\n          TerminalTextStyle.Red\n        );\n\n        terminal.current?.printLink(\n          'https://blockscout.com/poa/xdai/',\n          () => {\n            window.open('https://blockscout.com/poa/xdai/');\n          },\n          TerminalTextStyle.Red\n        );\n\n        terminal.current?.println('');\n\n        return;\n      }\n\n      setGameManager(newGameManager);\n\n      window.df = newGameManager;\n\n      const newGameUIManager = await GameUIManager.create(newGameManager, terminal);\n\n      window.ui = newGameUIManager;\n\n      terminal.current?.newline();\n      terminal.current?.println('Connected to Dark Forest Contract');\n      gameUIManagerRef.current = newGameUIManager;\n\n      if (!newGameManager.hasJoinedGame()) {\n        setStep(TerminalPromptStep.NO_HOME_PLANET);\n      } else {\n        const browserHasData = !!newGameManager.getHomeCoords();\n        if (!browserHasData) {\n          terminal.current?.println(\n            'ERROR: Home coords not found on this browser.',\n            TerminalTextStyle.Red\n          );\n          setStep(TerminalPromptStep.ASK_ADD_ACCOUNT);\n          return;\n        }\n        terminal.current?.println('Validated Local Data...');\n        setStep(TerminalPromptStep.ALL_CHECKS_PASS);\n      }\n    },\n    [ethConnection, contractAddress]\n  );\n\n  const advanceStateFromAskAddAccount = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println('Import account home coordinates? (y/n)', TerminalTextStyle.Text);\n      terminal.current?.println(\n        \"If you're importing an account, make sure you know what you're doing.\"\n      );\n      const userInput = await terminal.current?.getInput();\n      if (userInput === 'y') {\n        setStep(TerminalPromptStep.ADD_ACCOUNT);\n      } else if (userInput === 'n') {\n        terminal.current?.println('Try using a different account and reload.');\n        setStep(TerminalPromptStep.TERMINATED);\n      } else {\n        terminal.current?.println('Unrecognized input. Please try again.');\n        await advanceStateFromAskAddAccount(terminal);\n      }\n    },\n    []\n  );\n\n  const advanceStateFromAddAccount = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const gameUIManager = gameUIManagerRef.current;\n\n      if (gameUIManager) {\n        try {\n          terminal.current?.println('x: ', TerminalTextStyle.Blue);\n          const x = parseInt((await terminal.current?.getInput()) || '');\n          terminal.current?.println('y: ', TerminalTextStyle.Blue);\n          const y = parseInt((await terminal.current?.getInput()) || '');\n          if (\n            Number.isNaN(x) ||\n            Number.isNaN(y) ||\n            Math.abs(x) > 2 ** 32 ||\n            Math.abs(y) > 2 ** 32\n          ) {\n            throw 'Invalid home coordinates.';\n          }\n          if (await gameUIManager.addAccount({ x, y })) {\n            terminal.current?.println('Successfully added account.');\n            terminal.current?.println('Initializing game...');\n            setStep(TerminalPromptStep.ALL_CHECKS_PASS);\n          } else {\n            throw 'Invalid home coordinates.';\n          }\n        } catch (e) {\n          terminal.current?.println(`ERROR: ${e}`, TerminalTextStyle.Red);\n          terminal.current?.println('Please try again.');\n        }\n      } else {\n        terminal.current?.println('ERROR: Game UI Manager not found. Terminating session.');\n        setStep(TerminalPromptStep.TERMINATED);\n      }\n    },\n    []\n  );\n\n  const advanceStateFromNoHomePlanet = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println('Welcome to DARK FOREST.');\n\n      const gameUIManager = gameUIManagerRef.current;\n      if (!gameUIManager) {\n        terminal.current?.println('ERROR: Game UI Manager not found. Terminating session.');\n        setStep(TerminalPromptStep.TERMINATED);\n        return;\n      }\n\n      if (Date.now() / 1000 > gameUIManager.getEndTimeSeconds()) {\n        terminal.current?.println('ERROR: This game has ended. Terminating session.');\n        setStep(TerminalPromptStep.TERMINATED);\n        return;\n      }\n\n      terminal.current?.newline();\n\n      terminal.current?.println('We collect a minimal set of statistics such as SNARK proving');\n      terminal.current?.println('times and average transaction times across browsers, to help ');\n      terminal.current?.println('us optimize performance and fix bugs. You can opt out of this');\n      terminal.current?.println('in the Settings pane.');\n      terminal.current?.println('');\n\n      terminal.current?.newline();\n\n      terminal.current?.println('Press ENTER to find a home planet. This may take up to 120s.');\n      terminal.current?.println('This will consume a lot of CPU.');\n\n      await terminal.current?.getInput();\n\n      gameUIManager.getGameManager().on(GameManagerEvent.InitializedPlayer, () => {\n        setTimeout(() => {\n          terminal.current?.println('Initializing game...');\n          setStep(TerminalPromptStep.ALL_CHECKS_PASS);\n        });\n      });\n\n      gameUIManager\n        .joinGame(async (e) => {\n          console.error(e);\n\n          terminal.current?.println('Error Joining Game:');\n          terminal.current?.println('');\n          terminal.current?.println(e.message, TerminalTextStyle.Red);\n          terminal.current?.println('');\n          terminal.current?.println('Press Enter to Try Again:');\n\n          await terminal.current?.getInput();\n          return true;\n        })\n        .catch((error: Error) => {\n          terminal.current?.println(\n            `[ERROR] An error occurred: ${error.toString().slice(0, 10000)}`,\n            TerminalTextStyle.Red\n          );\n        });\n    },\n    []\n  );\n\n  const advanceStateFromAllChecksPass = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      terminal.current?.println('');\n      terminal.current?.println('Press ENTER to begin');\n      terminal.current?.println(\"Press 's' then ENTER to begin in SAFE MODE - plugins disabled\");\n\n      const input = await terminal.current?.getInput();\n\n      if (input === 's') {\n        const gameUIManager = gameUIManagerRef.current;\n        gameUIManager?.getGameManager()?.setSafeMode(true);\n      }\n\n      setStep(TerminalPromptStep.COMPLETE);\n      setInitRenderState(InitRenderState.COMPLETE);\n      terminal.current?.clear();\n\n      terminal.current?.println('Welcome to the Dark Forest.', TerminalTextStyle.Green);\n      terminal.current?.println('');\n      terminal.current?.println(\n        \"This is the Dark Forest interactive JavaScript terminal. Only use this if you know exactly what you're doing.\"\n      );\n      terminal.current?.println('');\n      terminal.current?.println('Try running: df.getAccount()');\n      terminal.current?.println('');\n    },\n    []\n  );\n\n  const advanceStateFromComplete = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      const input = (await terminal.current?.getInput()) || '';\n      let res = '';\n      try {\n        // indrect eval call: http://perfectionkills.com/global-eval-what-are-the-options/\n        res = (1, eval)(input);\n        if (res !== undefined) {\n          terminal.current?.println(res.toString(), TerminalTextStyle.Text);\n        }\n      } catch (e) {\n        res = e.message;\n        terminal.current?.println(`ERROR: ${res}`, TerminalTextStyle.Red);\n      }\n      advanceStateFromComplete(terminal);\n    },\n    []\n  );\n\n  const advanceStateFromError = useCallback(async () => {\n    await neverResolves();\n  }, []);\n\n  const advanceState = useCallback(\n    async (terminal: React.MutableRefObject<TerminalHandle | undefined>) => {\n      if (step === TerminalPromptStep.NONE && ethConnection) {\n        await advanceStateFromNone(terminal);\n      } else if (step === TerminalPromptStep.COMPATIBILITY_CHECKS_PASSED) {\n        await advanceStateFromCompatibilityPassed(terminal);\n      } else if (step === TerminalPromptStep.DISPLAY_ACCOUNTS) {\n        await advanceStateFromDisplayAccounts(terminal);\n      } else if (step === TerminalPromptStep.GENERATE_ACCOUNT) {\n        await advanceStateFromGenerateAccount(terminal);\n      } else if (step === TerminalPromptStep.IMPORT_ACCOUNT) {\n        await advanceStateFromImportAccount(terminal);\n      } else if (step === TerminalPromptStep.ACCOUNT_SET) {\n        await advanceStateFromAccountSet(terminal);\n      } else if (step === TerminalPromptStep.ASKING_HAS_WHITELIST_KEY) {\n        await advanceStateFromAskHasWhitelistKey(terminal);\n      } else if (step === TerminalPromptStep.ASKING_WHITELIST_KEY) {\n        await advanceStateFromAskWhitelistKey(terminal);\n      } else if (step === TerminalPromptStep.ASKING_WAITLIST_EMAIL) {\n        await advanceStateFromAskWaitlistEmail(terminal);\n      } else if (step === TerminalPromptStep.ASKING_PLAYER_EMAIL) {\n        await advanceStateFromAskPlayerEmail(terminal);\n      } else if (step === TerminalPromptStep.FETCHING_ETH_DATA) {\n        await advanceStateFromFetchingEthData(terminal);\n      } else if (step === TerminalPromptStep.ASK_ADD_ACCOUNT) {\n        await advanceStateFromAskAddAccount(terminal);\n      } else if (step === TerminalPromptStep.ADD_ACCOUNT) {\n        await advanceStateFromAddAccount(terminal);\n      } else if (step === TerminalPromptStep.NO_HOME_PLANET) {\n        await advanceStateFromNoHomePlanet(terminal);\n      } else if (step === TerminalPromptStep.ALL_CHECKS_PASS) {\n        await advanceStateFromAllChecksPass(terminal);\n      } else if (step === TerminalPromptStep.COMPLETE) {\n        await advanceStateFromComplete(terminal);\n      } else if (step === TerminalPromptStep.ERROR) {\n        await advanceStateFromError();\n      }\n    },\n    [\n      step,\n      advanceStateFromAccountSet,\n      advanceStateFromAddAccount,\n      advanceStateFromAllChecksPass,\n      advanceStateFromAskAddAccount,\n      advanceStateFromAskHasWhitelistKey,\n      advanceStateFromAskPlayerEmail,\n      advanceStateFromAskWaitlistEmail,\n      advanceStateFromAskWhitelistKey,\n      advanceStateFromCompatibilityPassed,\n      advanceStateFromComplete,\n      advanceStateFromDisplayAccounts,\n      advanceStateFromError,\n      advanceStateFromFetchingEthData,\n      advanceStateFromGenerateAccount,\n      advanceStateFromImportAccount,\n      advanceStateFromNoHomePlanet,\n      advanceStateFromNone,\n      ethConnection,\n    ]\n  );\n\n  useEffect(() => {\n    const uiEmitter = UIEmitter.getInstance();\n    uiEmitter.emit(UIEmitterEvent.UIChange);\n  }, [initRenderState]);\n\n  useEffect(() => {\n    const gameUiManager = gameUIManagerRef.current;\n    if (!terminalVisible && gameUiManager) {\n      const tutorialManager = TutorialManager.getInstance(gameUiManager);\n      tutorialManager.acceptInput(TutorialState.Terminal);\n    }\n  }, [terminalVisible]);\n\n  useEffect(() => {\n    if (terminalHandle.current && topLevelContainer.current) {\n      advanceState(terminalHandle);\n    }\n  }, [terminalHandle, topLevelContainer, advanceState]);\n\n  return (\n    <Wrapper initRender={initRenderState} terminalEnabled={terminalVisible}>\n      <GameWindowWrapper initRender={initRenderState} terminalEnabled={terminalVisible}>\n        {gameUIManagerRef.current && topLevelContainer.current && gameManager && (\n          <TopLevelDivProvider value={topLevelContainer.current}>\n            <UIManagerProvider value={gameUIManagerRef.current}>\n              <GameWindowLayout\n                terminalVisible={terminalVisible}\n                setTerminalVisible={setTerminalVisible}\n              />\n            </UIManagerProvider>\n          </TopLevelDivProvider>\n        )}\n        <TerminalToggler\n          terminalEnabled={terminalVisible}\n          setTerminalEnabled={setTerminalVisible}\n        />\n      </GameWindowWrapper>\n      <TerminalWrapper initRender={initRenderState} terminalEnabled={terminalVisible}>\n        <Terminal ref={terminalHandle} promptCharacter={'$'} />\n      </TerminalWrapper>\n      <div ref={topLevelContainer}></div>\n    </Wrapper>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/GifMaker.tsx",
    "content": "import { ArtifactFileColor } from '@darkforest_eth/gamelogic';\nimport React, { useEffect, useRef, useState } from 'react';\nimport styled from 'styled-components';\nimport { GifRenderer } from '../Renderers/GifRenderer';\n\nconst IS_THUMB = true;\nconst GIF_DIM = IS_THUMB ? 90 : 350;\nexport const GIF_ARTIFACT_COLOR = ArtifactFileColor.APP_BACKGROUND;\n\nconst StyledGifMaker = styled.div`\n  overflow-x: scroll;\n  width: max-content;\n\n  input {\n    color: black;\n  }\n\n  img {\n    margin-right: 4px;\n  }\n`;\n\n/**\n * Entrypoint for gif and sprite generation, accessed via `yarn run gifs`.\n * Wait a second or so for the textures to get loaded, then click the buttons to download files as a zip.\n * gifs are saved as 60fps webm, and can take a while - open the console to see progress (logged verbosely)\n */\nexport function GifMaker() {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n  const [renderer, setRenderer] = useState<GifRenderer | null>(null);\n  useEffect(() => {\n    if (!canvasRef.current) return;\n\n    setRenderer(new GifRenderer(canvasRef.current, GIF_DIM, IS_THUMB));\n  }, [canvasRef]);\n\n  useEffect(() => {\n    const script = document.createElement('script');\n\n    script.src = '/public/CCapture.all.min.js';\n    script.async = true;\n\n    document.body.appendChild(script);\n  }, []);\n\n  return (\n    <StyledGifMaker>\n      <canvas width={GIF_DIM} height={GIF_DIM} ref={canvasRef}></canvas>\n      <button onClick={() => renderer?.getAllSprites()}>Get All Sprites</button>\n      <button onClick={() => renderer?.getAllVideos()}>Get All Videos</button>\n    </StyledGifMaker>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/LandingPage.tsx",
    "content": "import { CONTRACT_ADDRESS } from '@darkforest_eth/contracts';\nimport { address } from '@darkforest_eth/serde';\nimport React from 'react';\nimport { useHistory } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { EmSpacer, Link, Spacer, Title } from '../Components/CoreUI';\nimport { EmailCTA, EmailCTAMode } from '../Components/Email';\nimport { Modal } from '../Components/Modal';\nimport { HideSmall, Text, White } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { LandingPageRoundArt } from '../Views/LandingPageRoundArt';\nimport { LeadboardDisplay } from '../Views/Leaderboard';\n\nexport const enum LandingPageZIndex {\n  Background = 0,\n  Canvas = 1,\n  BasePage = 2,\n}\n\nconst links = {\n  twitter: 'http://twitter.com/darkforest_eth',\n  email: 'mailto:ivan@0xparc.org',\n  blog: 'https://blog.zkga.me/',\n  discord: 'https://discord.gg/2u2TN6v8r6',\n  github: 'https://github.com/darkforest-eth',\n  wiki: 'https://dfwiki.net/wiki/Main_Page',\n  plugins: 'https://plugins.zkga.me/',\n};\n\nconst defaultAddress = address(CONTRACT_ADDRESS);\n\nconst ButtonWrapper = styled.div`\n  display: flex;\n  justify-content: center;\n  gap: 8px;\n  flex-direction: row;\n\n  @media only screen and (max-device-width: 1000px) {\n    grid-template-columns: auto;\n    flex-direction: column;\n  }\n\n  --df-button-color: ${dfstyles.colors.dfgreen};\n  --df-button-border: 1px solid ${dfstyles.colors.dfgreen};\n  --df-button-hover-background: ${dfstyles.colors.dfgreen};\n  --df-button-hover-border: 1px solid ${dfstyles.colors.dfgreen};\n`;\n\nexport default function LandingPage() {\n  const history = useHistory();\n\n  return (\n    <>\n      <PrettyOverlayGradient />\n      <Hiring />\n\n      <Page>\n        <OnlyMobile>\n          <Spacer height={8} />\n        </OnlyMobile>\n        <HideOnMobile>\n          <Spacer height={150} />\n        </HideOnMobile>\n\n        <MainContentContainer>\n          <Header>\n            <LinkContainer>\n              <Link to={links.email}>email</Link>\n              <Spacer width={4} />\n              <Link to={links.blog}>blog</Link>\n              <Spacer width={4} />\n\n              <a className={'link-twitter'} href={links.twitter}>\n                <span className={'icon-twitter'}></span>\n              </a>\n              <Spacer width={4} />\n              <a className={'link-discord'} href={links.discord}>\n                <span className={'icon-discord'}></span>\n              </a>\n              <Spacer width={4} />\n              <a className={'link-github'} href={links.github}>\n                <span className={'icon-github'}></span>\n              </a>\n\n              <Spacer width={4} />\n              <Link to={links.plugins}>plugins</Link>\n              <Spacer width={4} />\n              <Link to={links.wiki}>wiki</Link>\n            </LinkContainer>\n\n            <OnlyMobile>\n              <Spacer height={4} />\n            </OnlyMobile>\n            <HideOnMobile>\n              <Spacer height={16} />\n            </HideOnMobile>\n\n            <LandingPageRoundArt />\n\n            <p>\n              <White>Dark Forest</White> <Text>zkSNARK space warfare</Text>\n              <br />\n              <Text>Round 5: </Text>\n              <White>The Junk Wars</White>\n            </p>\n\n            <Spacer height={16} />\n\n            <ButtonWrapper>\n              <Btn size='large' onClick={() => history.push(`/lobby/${defaultAddress}`)}>\n                Create Lobby\n              </Btn>\n              <Btn size='large' onClick={() => history.push(`/play/${defaultAddress}`)}>\n                Enter Round 5\n              </Btn>\n              <Btn size='large' onClick={() => history.push(`/events`)}>\n                Events\n              </Btn>\n            </ButtonWrapper>\n          </Header>\n          <EmSpacer height={3} />\n          Ways to get Involved\n          <EmSpacer height={1} />\n          <Involved>\n            <InvolvedItem\n              href='https://blog.zkga.me/hosting-a-dark-forest-community-round'\n              style={{\n                backgroundImage: \"url('/public/get_involved/community_round.png')\",\n              }}\n            ></InvolvedItem>\n            <InvolvedItem\n              href='https://github.com/darkforest-eth/plugins#adding-your-plugin'\n              style={{\n                backgroundImage: \"url('/public/get_involved/write_plugin.png')\",\n              }}\n            ></InvolvedItem>\n            <InvolvedItem\n              href='https://github.com/darkforest-eth/plugins#reviewer-guidelines'\n              style={{\n                backgroundImage: \"url('/public/get_involved/reveiw_plugin.png')\",\n              }}\n            ></InvolvedItem>\n            <InvolvedItem\n              href='https://blog.zkga.me/renderer-plugin-contest'\n              style={{\n                backgroundImage: \"url('/public/get_involved/plugin_render.png')\",\n              }}\n            ></InvolvedItem>\n            <InvolvedItem\n              href='https://blog.zkga.me/introducing-dark-forest-lobbies'\n              style={{\n                backgroundImage: \"url('/public/get_involved/lobby.png')\",\n              }}\n            ></InvolvedItem>\n          </Involved>\n          <EmSpacer height={3} />\n          <HallOfFame style={{ color: dfstyles.colors.text }}>\n            <HallOfFameTitle>Space Masters</HallOfFameTitle>\n            <Spacer height={8} />\n            <table>\n              <tbody>\n                <TRow>\n                  <td>\n                    <HideSmall>v</HideSmall>0.1\n                  </td>\n                  <td>\n                    02/22/<HideSmall>20</HideSmall>20\n                  </td>\n                  <td>\n                    <a href='https://twitter.com/zoink'>Dylan Field</a>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <HideSmall>v</HideSmall>0.2\n                  </td>\n                  <td>\n                    06/24/<HideSmall>20</HideSmall>20\n                  </td>\n                  <td>Nate Foss</td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v3-rules'>\n                      <HideSmall>v</HideSmall>0.3\n                    </Link>\n                  </td>\n                  <td>\n                    08/07/<HideSmall>20</HideSmall>20\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/hideandcleanse'>@hideandcleanse</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v4-recap'>\n                      <HideSmall>v</HideSmall>0.4\n                    </Link>\n                  </td>\n                  <td>\n                    10/02/<HideSmall>20</HideSmall>20\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/jacobrosenthal'>Jacob Rosenthal</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v5-winners'>\n                      <HideSmall>v</HideSmall>0.5\n                    </Link>\n                  </td>\n                  <td>\n                    12/25/<HideSmall>20</HideSmall>20\n                  </td>\n                  <td>0xb05d9542...</td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v6-r1-wrapup'>\n                      <HideSmall>v</HideSmall>0.6 round 1\n                    </Link>\n                  </td>\n                  <td>\n                    05/22/<HideSmall>20</HideSmall>21\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/adietrichs'>Ansgar Dietrichs</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v6-r2-wrapup'>\n                      <HideSmall>v</HideSmall>0.6 round 2\n                    </Link>\n                  </td>\n                  <td>\n                    07/07/<HideSmall>20</HideSmall>21\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/orden_gg'>@orden_gg</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v6-r3-wrapup'>\n                      <HideSmall>v</HideSmall>0.6 round 3\n                    </Link>\n                  </td>\n                  <td>\n                    08/22/<HideSmall>20</HideSmall>21\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/dropswap_gg'>@dropswap_gg</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v6-r4-wrapup'>\n                      <HideSmall>v</HideSmall>0.6 round 4\n                    </Link>\n                  </td>\n                  <td>\n                    10/01/<HideSmall>20</HideSmall>21\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/orden_gg'>@orden_gg</Link>\n                  </td>\n                </TRow>\n                <TRow>\n                  <td>\n                    <Link to='https://blog.zkga.me/v6-r5-wrapup'>\n                      <HideSmall>v</HideSmall>0.6 round 5\n                    </Link>\n                  </td>\n                  <td>\n                    02/18/<HideSmall>20</HideSmall>22\n                  </td>\n                  <td>\n                    <Link to='https://twitter.com/d_fdao'>@d_fdao</Link>\n                    {' + '}\n                    <Link to='https://twitter.com/orden_gg'>@orden_gg</Link>\n                  </td>\n                </TRow>\n              </tbody>\n            </table>\n          </HallOfFame>\n          <Spacer height={32} />\n          <EmailWrapper>\n            <EmailCTA mode={EmailCTAMode.SUBSCRIBE} />\n          </EmailWrapper>\n        </MainContentContainer>\n\n        <Spacer height={128} />\n\n        <LeadboardDisplay />\n\n        <Spacer height={256} />\n      </Page>\n    </>\n  );\n}\n\nconst PrettyOverlayGradient = styled.div`\n  width: 100vw;\n  height: 100vh;\n  background: linear-gradient(to left top, rgba(74, 74, 74, 0.628), rgba(60, 1, 255, 0.2)) fixed;\n  background-position: 50%, 50%;\n  display: inline-block;\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: -1;\n`;\n\nconst Header = styled.div`\n  text-align: center;\n`;\n\nconst EmailWrapper = styled.div`\n  display: flex;\n  flex-direction: row;\n`;\n\nconst TRow = styled.tr`\n  & td:first-child {\n    color: ${dfstyles.colors.subtext};\n  }\n  & td:nth-child(2) {\n    padding-left: 12pt;\n  }\n  & td:nth-child(3) {\n    text-align: right;\n    padding-left: 16pt;\n  }\n`;\n\nconst MainContentContainer = styled.div`\n  max-width: 100%;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: space-between;\n`;\n\nconst Page = styled.div`\n  position: absolute;\n  width: 100vw;\n  max-width: 100vw;\n  height: 100%;\n  color: white;\n  font-size: ${dfstyles.fontSize};\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  z-index: ${LandingPageZIndex.BasePage};\n`;\n\nconst HallOfFameTitle = styled.div`\n  color: ${dfstyles.colors.subtext};\n  display: inline-block;\n  border-bottom: 1px solid ${dfstyles.colors.subtext};\n  line-height: 1em;\n`;\n\nexport const LinkContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n\n  a {\n    margin: 0 6pt;\n    transition: color 0.2s;\n    display: flex;\n    justify-content: center;\n    align-items: center;\n\n    &:hover {\n      cursor: pointer;\n      &.link-twitter {\n        color: ${dfstyles.colors.icons.twitter};\n      }\n      &.link-github {\n        color: ${dfstyles.colors.icons.github};\n      }\n      &.link-discord {\n        color: ${dfstyles.colors.icons.discord};\n      }\n      &.link-blog {\n        color: ${dfstyles.colors.icons.blog};\n      }\n      &.link-email {\n        color: ${dfstyles.colors.icons.email};\n      }\n    }\n  }\n`;\n\nfunction Hiring() {\n  return (\n    <HideOnMobile>\n      <Modal contain={['top', 'left', 'right']} initialX={50} initialY={50}>\n        <Title slot='title'>Dark Forest is Hiring!</Title>\n        <div style={{ maxWidth: '300px', textAlign: 'justify' }}>\n          We are looking for experienced full stack and solidity developers to join our team! If you\n          like what you see,{' '}\n          <Link to='https://docs.google.com/forms/d/e/1FAIpQLSdaWvjxX4TrDDLidPXtgk6UW3rC082rpvi3AIPkCPxAahg_rg/viewform?usp=sf_link'>\n            consider applying\n          </Link>\n          . If you know someone who you think would be a great fit for our team,{' '}\n          <Link to='https://docs.google.com/forms/d/e/1FAIpQLScku_bQDbkPqpHrwBzOBfQ4SV6Nw6Tgxi6zWQL8Bb0olyBE3w/viewform?usp=sf_link'>\n            please refer them here\n          </Link>\n          .\n          <br />\n          <br />\n          Learn more about the role{' '}\n          <Link to='https://ivanchub.notion.site/Dark-Forest-is-Hiring-ad1f0cbe816640fb9b4c663dacaaca04'>\n            here\n          </Link>\n          .\n        </div>\n      </Modal>\n    </HideOnMobile>\n  );\n}\n\nconst HideOnMobile = styled.div`\n  @media only screen and (max-device-width: 1000px) {\n    display: none;\n  }\n`;\n\nconst OnlyMobile = styled.div`\n  @media only screen and (min-device-width: 1000px) {\n    display: none;\n  }\n`;\n\nconst Involved = styled.div`\n  width: 100%;\n  padding-left: 16px;\n  padding-right: 16px;\n  display: grid;\n  grid-template-columns: auto auto;\n  gap: 10px;\n  grid-auto-rows: minmax(100px, auto);\n\n  @media only screen and (max-device-width: 1000px) {\n    grid-template-columns: auto;\n  }\n`;\n\nconst InvolvedItem = styled.a`\n  height: 150px;\n  display: inline-block;\n  margin: 4px;\n  padding: 4px 8px;\n\n  background-color: ${dfstyles.colors.backgroundlighter};\n  background-size: cover;\n  background-position: 50% 50%;\n  background-repeat: no-repeat;\n\n  cursor: pointer;\n  transition: transform 200ms;\n  &:hover {\n    transform: scale(1.03);\n  }\n  &:hover:active {\n    transform: scale(1.05);\n  }\n`;\n\nconst HallOfFame = styled.div`\n  @media only screen and (max-device-width: 1000px) {\n    font-size: 70%;\n  }\n`;\n"
  },
  {
    "path": "src/Frontend/Pages/LoadingPage.tsx",
    "content": "import * as React from 'react';\n\nconst styles = {\n  container: {\n    height: '100%',\n    width: '100%',\n    display: 'flex',\n    alignItems: 'center',\n    justifyContent: 'center',\n  },\n};\n\nexport default function LoadingPage() {\n  return <div style={styles.container}>Loading... (this may take a few seconds)</div>;\n}\n"
  },
  {
    "path": "src/Frontend/Pages/LobbyLandingPage.tsx",
    "content": "import { EthConnection, ThrottledConcurrentQueue, weiToEth } from '@darkforest_eth/network';\nimport { address } from '@darkforest_eth/serde';\nimport { EthAddress } from '@darkforest_eth/types';\nimport { utils, Wallet } from 'ethers';\nimport React, { useEffect, useRef, useState } from 'react';\nimport { addAccount, getAccounts } from '../../Backend/Network/AccountManager';\nimport { getEthConnection } from '../../Backend/Network/Blockchain';\nimport { InitRenderState, TerminalWrapper } from '../Components/GameLandingPageComponents';\nimport { MythicLabelText } from '../Components/Labels/MythicLabel';\nimport { TextPreview } from '../Components/TextPreview';\nimport { TerminalTextStyle } from '../Utils/TerminalTypes';\nimport { DarkForestTips } from '../Views/DarkForestTips';\nimport { Terminal, TerminalHandle } from '../Views/Terminal';\n\nclass LobbyPageTerminal {\n  private ethConnection: EthConnection;\n  private terminal: TerminalHandle;\n  private accountSet: (account: EthAddress) => void;\n  private balancesEth: number[];\n\n  public constructor(\n    ethConnection: EthConnection,\n    terminal: TerminalHandle,\n    accountSet: (account: EthAddress) => void\n  ) {\n    this.ethConnection = ethConnection;\n    this.terminal = terminal;\n    this.accountSet = accountSet;\n  }\n\n  private async loadBalances(addresses: EthAddress[]) {\n    const queue = new ThrottledConcurrentQueue({\n      invocationIntervalMs: 1000,\n      maxInvocationsPerIntervalMs: 25,\n    });\n\n    const balances = await Promise.all(\n      addresses.map((address) => queue.add(() => this.ethConnection.loadBalance(address)))\n    );\n\n    this.balancesEth = balances.map(weiToEth);\n  }\n\n  public async chooseAccount() {\n    this.terminal.printElement(<MythicLabelText text='                  Create a Lobby' />);\n    this.terminal.newline();\n    this.terminal.newline();\n    this.terminal.printElement(<DarkForestTips tips={lobbyTips} title='Lobby Tips' />);\n    this.terminal.newline();\n\n    const accounts = getAccounts();\n    this.terminal.println(`Found ${accounts.length} accounts on this device. Loading balances...`);\n    this.terminal.newline();\n\n    try {\n      await this.loadBalances(accounts.map((a) => a.address));\n    } catch (e) {\n      console.log(e);\n      this.terminal.println(\n        `Error loading balances. Reload the page to try again.`,\n        TerminalTextStyle.Red\n      );\n      return;\n    }\n\n    this.terminal.println(`Log in to create a lobby. We recommend using an account`);\n    this.terminal.println(`which owns at least 0.25 xDAI.`);\n    this.terminal.newline();\n\n    if (accounts.length > 0) {\n      this.terminal.print('(a) ', TerminalTextStyle.Sub);\n      this.terminal.println('Login with existing account.');\n    }\n\n    this.terminal.print('(n) ', TerminalTextStyle.Sub);\n    this.terminal.println(`Generate new burner wallet account.`);\n    this.terminal.print('(i) ', TerminalTextStyle.Sub);\n    this.terminal.println(`Import private key.`);\n    this.terminal.println(``);\n    this.terminal.println(`Select an option:`, TerminalTextStyle.Text);\n\n    const userInput = await this.terminal.getInput();\n    if (userInput === 'a' && accounts.length > 0) {\n      this.displayAccounts();\n    } else if (userInput === 'n') {\n      this.generateAccount();\n    } else if (userInput === 'i') {\n      this.importAccount();\n    } else {\n      this.terminal.println('Unrecognized input. Please try again.', TerminalTextStyle.Red);\n      this.terminal.println('');\n      await this.chooseAccount();\n    }\n  }\n\n  private async displayAccounts() {\n    this.terminal.println(``);\n    const accounts = getAccounts();\n    for (let i = 0; i < accounts.length; i += 1) {\n      this.terminal.print(`(${i + 1}): `, TerminalTextStyle.Sub);\n      this.terminal.print(`${accounts[i].address} `);\n\n      if (this.balancesEth[i] < 0.25) {\n        this.terminal.println(this.balancesEth[i].toFixed(2) + ' xDAI', TerminalTextStyle.Red);\n      } else {\n        this.terminal.println(this.balancesEth[i].toFixed(2) + ' xDAI', TerminalTextStyle.Green);\n      }\n    }\n    this.terminal.println(``);\n    this.terminal.println(`Select an account:`, TerminalTextStyle.Text);\n\n    const selection = +((await this.terminal.getInput()) || '');\n    if (isNaN(selection) || selection > accounts.length) {\n      this.terminal.println('Unrecognized input. Please try again.', TerminalTextStyle.Red);\n      await this.displayAccounts();\n    } else if (this.balancesEth[selection - 1] < 0.25) {\n      this.terminal.println('Not enough xDAI. Select another account.', TerminalTextStyle.Red);\n      await this.displayAccounts();\n    } else {\n      const account = accounts[selection - 1];\n      try {\n        await this.ethConnection.setAccount(account.privateKey);\n        this.accountSet(account.address);\n      } catch (e) {\n        this.terminal.println(\n          'An unknown error occurred. please try again.',\n          TerminalTextStyle.Red\n        );\n        this.terminal.println('');\n        this.displayAccounts();\n      }\n    }\n  }\n\n  private async generateAccount() {\n    const newWallet = Wallet.createRandom();\n    const newSKey = newWallet.privateKey;\n    const newAddr = address(newWallet.address);\n    try {\n      addAccount(newSKey);\n      this.ethConnection.setAccount(newSKey);\n\n      this.terminal.println(``);\n      this.terminal.print(`Created new burner wallet with address `);\n      this.terminal.printElement(<TextPreview text={newAddr} unFocusedWidth={'100px'} />);\n      this.terminal.println(``);\n      this.terminal.println('');\n      this.terminal.println(\n        'Note: Burner wallets are stored in local storage.',\n        TerminalTextStyle.Text\n      );\n      this.terminal.println('They are relatively insecure and you should avoid ');\n      this.terminal.println('storing substantial funds in them.');\n      this.terminal.println('');\n      this.terminal.println('Also, clearing browser local storage/cache will render your');\n      this.terminal.println('burner wallets inaccessible, unless you export your private keys.');\n      this.terminal.println('');\n      this.terminal.println('Press any key to continue:', TerminalTextStyle.Text);\n\n      await this.terminal.getInput();\n      this.accountSet(newAddr);\n    } catch (e) {\n      console.log(e);\n      this.terminal.println('An unknown error occurred. please try again.', TerminalTextStyle.Red);\n    }\n  }\n\n  private async importAccount() {\n    this.terminal.println(\n      'Enter the 0x-prefixed private key of the account you wish to import',\n      TerminalTextStyle.Text\n    );\n    this.terminal.println(\n      \"NOTE: THIS WILL STORE THE PRIVATE KEY IN YOUR BROWSER'S LOCAL STORAGE\",\n      TerminalTextStyle.Text\n    );\n    this.terminal.println(\n      'Local storage is relatively insecure. We recommend only importing accounts with zero-to-no funds.'\n    );\n    const newSKey = (await this.terminal.getInput()) || '';\n    try {\n      const newAddr = address(utils.computeAddress(newSKey));\n\n      addAccount(newSKey);\n\n      this.ethConnection.setAccount(newSKey);\n      this.terminal.println(`Imported account with address ${newAddr}.`);\n      this.accountSet(newAddr);\n    } catch (e) {\n      this.terminal.println('An unknown error occurred. please try again.', TerminalTextStyle.Red);\n      this.terminal.println('');\n      this.importAccount();\n    }\n  }\n}\n\nexport function LobbyLandingPage({ onReady }: { onReady: (connection: EthConnection) => void }) {\n  const terminal = useRef<TerminalHandle>();\n  const [connection, setConnection] = useState<EthConnection | undefined>();\n  const [controller, setController] = useState<LobbyPageTerminal | undefined>();\n\n  useEffect(() => {\n    getEthConnection()\n      .then((connection) => setConnection(connection))\n      .catch((e) => {\n        alert('error connecting to blockchain');\n        console.log(e);\n      });\n  }, []);\n\n  useEffect(() => {\n    if (!controller && connection && terminal.current) {\n      const newController = new LobbyPageTerminal(\n        connection,\n        terminal.current,\n        (account: EthAddress) => {\n          if (connection) {\n            terminal.current?.println(`Creating lobby with account: ${account}`);\n            onReady(connection);\n          } else {\n            alert('Unable to make a connection to blockchain');\n          }\n        }\n      );\n      newController.chooseAccount();\n      setController(newController);\n    }\n  }, [terminal, connection, controller, onReady]);\n\n  return (\n    <TerminalWrapper initRender={InitRenderState.NONE} terminalEnabled={false}>\n      <Terminal ref={terminal} promptCharacter={'$'} />\n    </TerminalWrapper>\n  );\n}\n\nconst lobbyTips = [\n  'A lobby is a Dark Forest universe which is under the control of the account that created it.',\n  'You can customize most aspects of Dark Forest when you create a lobby.',\n  'Mirror the X or Y space type for credibly neutral maps.',\n  'Fixed world radius can be used for a 1v1 battle.',\n  'Try increasing game speed for a quick round.',\n  'Use the Admin Controls plugin for god-mode control over your lobby.',\n  'You can spawn in any space type by adjusting the player spawn perlin range',\n  'Disable ZK to make mining the unverse super fast. WARNING: insecure.',\n  \"Don't like Space Junk? Disable it!\",\n  \"Don't like Capture Zones? Disable them!\",\n  'Change the Planet Hash Key to change where planets are. Think of it as the seed for planet generation.',\n  'Change the Space Type Key to vary the space type zones in your lobby.',\n  // TODO: link to the blog post\n  // TODO: link to Jordan's video\n];\n"
  },
  {
    "path": "src/Frontend/Pages/NotFoundPage.tsx",
    "content": "import React from 'react';\nimport { CadetWormhole } from '../Views/CadetWormhole';\n\nexport function NotFoundPage() {\n  return <CadetWormhole imgUrl='/public/img/404-text.png' />;\n}\n"
  },
  {
    "path": "src/Frontend/Pages/ShareArtifact.tsx",
    "content": "import { Artifact, ArtifactId } from '@darkforest_eth/types';\nimport React from 'react';\nimport { RouteComponentProps } from 'react-router-dom';\nimport ReaderDataStore from '../../Backend/Storage/ReaderDataStore';\nimport { Share } from '../Views/Share';\n\nexport function ShareArtifact({ match }: RouteComponentProps<{ artifactId: ArtifactId }>) {\n  function load(dataStore: ReaderDataStore) {\n    return dataStore.loadArtifactFromContract(match.params.artifactId);\n  }\n\n  return (\n    <Share load={load}>\n      {(artifact: Artifact | undefined, loading: boolean, error: Error | undefined) => {\n        if (error) {\n          return 'error';\n        }\n\n        if (loading) {\n          return 'loading';\n        }\n\n        return JSON.stringify(artifact);\n      }}\n    </Share>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/SharePlanet.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { getPlanetBlurb, getPlanetName, getPlanetTagline } from '@darkforest_eth/procedural';\nimport { LocationId, Planet } from '@darkforest_eth/types';\nimport React from 'react';\nimport { RouteComponentProps } from 'react-router-dom';\nimport styled from 'styled-components';\nimport ReaderDataStore from '../../Backend/Storage/ReaderDataStore';\nimport { getPlanetShortHash } from '../../Backend/Utils/Utils';\nimport { Sub } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { Share } from '../Views/Share';\n\nconst PlanetCard = styled.div`\n  width: 36em;\n  margin: 2em auto;\n  font-size: 14pt;\n  z-index: 1000;\n  background: ${dfstyles.colors.background};\n  border-radius: 3px;\n  border: 1px solid ${dfstyles.colors.text};\n\n  & > div {\n    width: 100%;\n    padding: 0.5em;\n    &:nth-child(1) {\n      // header\n      border-bottom: 1px solid ${dfstyles.colors.subtext};\n    }\n    &:nth-child(2) {\n      // scape\n      height: 350px;\n    }\n  }\n`;\n\ninterface SharePlanetData {\n  planet: Planet;\n  biome: number | null;\n  ownerTwitter: string | null;\n}\n\nexport function SharePlanet({ match }: RouteComponentProps<{ locationId: LocationId }>) {\n  async function load(dataStore: ReaderDataStore): Promise<SharePlanetData> {\n    const loadedPlanet = await dataStore.loadPlanetFromContract(match.params.locationId);\n\n    return {\n      planet: loadedPlanet,\n      biome: isLocatable(loadedPlanet) ? loadedPlanet.biome : null,\n      ownerTwitter: dataStore.getTwitter(loadedPlanet?.owner) || null,\n    };\n  }\n\n  return (\n    <Share load={load}>\n      {(data: SharePlanetData | undefined, loading: boolean, error: Error | undefined) => {\n        if (loading) {\n          return 'loading';\n        }\n\n        if (error || !data) {\n          return 'error';\n        }\n\n        return (\n          <PlanetCard>\n            <div>\n              {getPlanetShortHash(data.planet)} {getPlanetName(data.planet)}\n            </div>\n            <div>\n              <p>\n                <em>{getPlanetTagline(data.planet)}...</em>\n                <span>\n                  <Sub>{getPlanetBlurb(data.planet)}</Sub>\n                </span>\n              </p>\n              <p>{`Owner: ${data.ownerTwitter || data.planet.owner}`}</p>\n              <p>{`Energy: ${data.planet.energy}`}</p>\n              <p>{`Biome: ${data.biome || 'unknown'}`}</p>\n              <p>\n                Find this planet in-game at <a href='/'>http://zkga.me</a> to read more!\n              </p>\n            </div>\n          </PlanetCard>\n        );\n      }}\n    </Share>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/TestArtifactImages.tsx",
    "content": "import { EMPTY_ARTIFACT_ID } from '@darkforest_eth/constants';\nimport { ArtifactFileColor, artifactFileName, setForceAncient } from '@darkforest_eth/gamelogic';\nimport { ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types';\nimport React, { useEffect } from 'react';\nimport styled from 'styled-components';\nimport { ARTIFACT_URL } from '../Components/ArtifactImage';\n\nconst Container = styled.div`\n  width: fit-content;\n  overflow-x: scroll;\n`;\n\nconst Row = styled.div`\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  width: fit-content;\n`;\n\nconst basicArtifacts = Object.values(ArtifactType).filter(\n  (type) => type >= ArtifactType.Monolith && type <= ArtifactType.Pyramid\n);\nconst relicArtifacts = Object.values(ArtifactType).filter(\n  (type) => type >= ArtifactType.Wormhole && type <= ArtifactType.BlackDomain\n);\n\nconst knownTypes = Object.values(ArtifactType).filter((type) => type !== ArtifactType.Unknown);\nconst knownBiomes = Object.values(Biome).filter((biome) => biome !== Biome.UNKNOWN);\nconst knownRarities = Object.values(ArtifactRarity).filter(\n  (rarity) => rarity !== ArtifactRarity.Unknown\n);\n\nfunction ArtifactPreviewer({\n  type,\n  biome,\n  rarity,\n  ancient,\n  thumb,\n}: {\n  type: ArtifactType;\n  biome: Biome;\n  rarity: ArtifactRarity;\n  ancient?: boolean;\n  thumb: boolean;\n}) {\n  return (\n    <video width={250} loop autoPlay key={type + '-' + biome + '-' + rarity + 'vid'}>\n      <source\n        src={\n          ARTIFACT_URL +\n          artifactFileName(\n            true,\n            thumb,\n            {\n              artifactType: type,\n              planetBiome: biome,\n              rarity,\n              id: EMPTY_ARTIFACT_ID,\n            },\n            ArtifactFileColor.BLUE,\n            { forceAncient: ancient === true, skipCaching: true }\n          )\n        }\n        type={'video/webm'}\n      />\n    </video>\n  );\n}\n\nconst THUMB = false;\nexport function TestArtifactImages() {\n  useEffect(() => {\n    setForceAncient(false);\n  }, []);\n\n  return (\n    <Container>\n      <h1>Artifacts</h1>\n      {basicArtifacts.map((type) => (\n        <div key={type}>\n          {knownRarities.map((rarity) => (\n            <Row key={rarity}>\n              {knownBiomes.map((biome, i) => (\n                <ArtifactPreviewer\n                  key={i}\n                  type={type}\n                  biome={biome}\n                  rarity={rarity}\n                  thumb={THUMB}\n                />\n              ))}\n            </Row>\n          ))}\n        </div>\n      ))}\n      <h1>Relics</h1>\n      {relicArtifacts.map((type) => (\n        <Row key={type}>\n          {knownRarities.map((rarity, i) => (\n            <ArtifactPreviewer\n              key={i}\n              type={type}\n              biome={Biome.OCEAN}\n              rarity={rarity}\n              thumb={THUMB}\n            />\n          ))}\n        </Row>\n      ))}\n      <h1>Ancient</h1>\n      {knownTypes.map((type) => (\n        <Row key={type}>\n          {knownRarities.map((rarity, i) => (\n            <ArtifactPreviewer\n              key={i}\n              type={type}\n              biome={Biome.OCEAN}\n              rarity={rarity}\n              ancient\n              thumb={THUMB}\n            />\n          ))}\n        </Row>\n      ))}\n    </Container>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/TxConfirmPopup.tsx",
    "content": "import { weiToGwei } from '@darkforest_eth/network';\nimport { address } from '@darkforest_eth/serde';\nimport { Setting } from '@darkforest_eth/types';\nimport { BigNumber as EthersBN } from 'ethers';\nimport React, { useState } from 'react';\nimport { RouteComponentProps } from 'react-router-dom';\nimport styled, { css, keyframes } from 'styled-components';\nimport { ONE_DAY } from '../../Backend/Utils/Utils';\nimport Button from '../Components/Button';\nimport { Checkbox, DarkForestCheckbox } from '../Components/Input';\nimport { Row } from '../Components/Row';\nimport dfstyles from '../Styles/dfstyles';\nimport { setBooleanSetting } from '../Utils/SettingsHooks';\n\nconst StyledTxConfirmPopup = styled.div`\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  z-index: 2;\n\n  display: flex;\n  flex-direction: column;\n  justify-content: space-between;\n\n  background: white;\n  color: black;\n\n  font-family: 'Helvetica', 'Arial', sans-serif;\n  font-size: 12pt;\n\n  font-weight: 400;\n\n  .mono {\n    font-family: 'Inconsolata', 'Monaco', monospace;\n    font-size: 11pt;\n  }\n\n  b {\n    font-weight: 700;\n  }\n\n  .mtop {\n    margin-top: 1em;\n  }\n\n  button {\n    flex-grow: 1;\n    padding: 1em;\n    border-radius: 8px;\n\n    transition: filter 0.1s;\n    &:hover {\n      filter: brightness(1.1);\n    }\n    &:active {\n      filter: brightness(0.9);\n    }\n\n    &:first-child {\n      margin-right: 0.5em;\n      background: #e3e3e3;\n      border: 2px solid #444;\n    }\n    &:last-child {\n      color: white;\n      background: #00aed9;\n      border: 2px solid #00708b;\n    }\n  }\n\n  .network {\n    color: ${dfstyles.colors.subtext};\n  }\n\n  .section {\n    padding: 0.5em;\n\n    &:not(:last-of-type) {\n      border-bottom: 1px solid gray;\n    }\n\n    & > h2 {\n      font-size: 1.5em;\n      font-weight: 300;\n    }\n  }\n`;\n\nconst keys = keyframes`\n  from {\n    filter: brightness(1.3);\n  }\n  to {\n    filter: brightness(0.6);\n  }\n`;\n\nconst anim = css`\n  animation: ${keys} 1s ${dfstyles.game.styles.animProps};\n`;\n\nconst ConfirmIcon = styled.span`\n  display: inline-block;\n  width: 12px;\n  height: 12px;\n  border-radius: 6px;\n  background: ${dfstyles.colors.dfgreen};\n\n  ${anim};\n`;\n\nexport function TxConfirmPopup({\n  match,\n}: RouteComponentProps<{\n  contract: string;\n  addr: string;\n  actionId: string;\n  balance: string;\n  method: string;\n}>) {\n  const { contract, addr, actionId, balance, method } = match.params;\n\n  const contractAddress = address(contract);\n  const account = address(addr);\n\n  const doReject = () => {\n    localStorage.setItem(`tx-approved-${account}-${actionId}`, 'false');\n    window.close();\n  };\n\n  const [autoApproveChecked, setAutoApprovedChecked] = useState<boolean>(false);\n\n  const approve = () => {\n    localStorage.setItem(`tx-approved-${account}-${actionId}`, 'true');\n    window.close();\n  };\n\n  const setAutoApproveSetting = () => {\n    localStorage.setItem(`tx-approved-${account}-${actionId}`, 'true');\n    localStorage.setItem(`wallet-enabled-${account}`, (Date.now() + ONE_DAY).toString());\n    const config = {\n      contractAddress,\n      account,\n    };\n    setBooleanSetting(config, Setting.AutoApproveNonPurchaseTransactions, true);\n    window.close();\n  };\n\n  const doApprove = () => {\n    if (autoApproveChecked) setAutoApproveSetting();\n    else approve();\n  };\n\n  const gasFee = EthersBN.from(localStorage.getItem(`${account}-gasFeeGwei`) || '');\n\n  const fromPlanet = localStorage.getItem(`${account}-fromPlanet`);\n  const toPlanet = localStorage.getItem(`${account}-toPlanet`);\n\n  const hatPlanet = localStorage.getItem(`${account}-hatPlanet`);\n  const hatLevel = localStorage.getItem(`${account}-hatLevel`);\n  const hatCost: number = method === 'buyHat' && hatLevel ? 2 ** parseInt(hatLevel) : 0;\n\n  const txCost: number = hatCost + 0.002 * weiToGwei(gasFee);\n\n  const upPlanet = localStorage.getItem(`${account}-upPlanet`);\n  const branch = localStorage.getItem(`${account}-branch`);\n\n  const planetToTransfer = localStorage.getItem(`${account}-transferPlanet`);\n  const transferTo = localStorage.getItem(`${account}-transferOwner`);\n\n  const findArtifactPlanet = localStorage.getItem(`${account}-findArtifactOnPlanet`);\n\n  const depositPlanet = localStorage.getItem(`${account}-depositPlanet`);\n  const depositArtifact = localStorage.getItem(`${account}-depositArtifact`);\n\n  const withdrawPlanet = localStorage.getItem(`${account}-withdrawPlanet`);\n  const withdrawArtifact = localStorage.getItem(`${account}-withdrawArtifact`);\n\n  const activatePlanet = localStorage.getItem(`${account}-activatePlanet`);\n  const activateArtifact = localStorage.getItem(`${account}-activateArtifact`);\n\n  const deactivatePlanet = localStorage.getItem(`${account}-deactivatePlanet`);\n  const deactivateArtifact = localStorage.getItem(`${account}-deactivateArtifact`);\n\n  const withdrawSilverPlanet = localStorage.getItem(`${account}-withdrawSilverPlanet`);\n\n  const revealPlanet = localStorage.getItem(`${account}-revealLocationId`);\n\n  return (\n    <StyledTxConfirmPopup>\n      <div className='section'>\n        <h2>Confirm Transaction</h2>\n      </div>\n\n      <div className='section'>\n        <Row>\n          <b>Contract Action</b>\n          <span>{method.toUpperCase()}</span>\n        </Row>\n        {method === 'revealLocation' && (\n          <Row>\n            <b>Planet ID</b>\n            <span className='mono'>{revealPlanet}</span>\n          </Row>\n        )}\n        {method === 'buyHat' && (\n          <>\n            <Row>\n              <b>On</b>\n              <span className='mono'>{hatPlanet}</span>\n            </Row>\n            <Row>\n              <b>HAT Level</b>\n              <span>\n                {hatLevel} ({hatCost} xDAI)\n              </span>\n            </Row>\n          </>\n        )}\n        {method === 'move' && (\n          <>\n            <Row>\n              <b>From</b>\n              <span className='mono'>{fromPlanet}</span>\n            </Row>\n            <Row>\n              <b>To</b>\n              <span className='mono'>{toPlanet}</span>\n            </Row>\n          </>\n        )}\n        {method === 'upgradePlanet' && (\n          <>\n            <Row>\n              <b>On</b>\n              <span className='mono'>{upPlanet}</span>\n            </Row>\n            <Row>\n              <b>Branch</b>\n              <span>{branch}</span>\n            </Row>\n          </>\n        )}\n        {method === 'transferPlanet' && (\n          <>\n            <Row>\n              <b>Planet ID</b>\n              <span className='mono'>{planetToTransfer}</span>\n            </Row>\n            <Row>\n              <b>Transfer to</b>\n              <span>{transferTo}</span>\n            </Row>\n          </>\n        )}\n        {method === 'findArtifact' && (\n          <Row>\n            <b>Planet ID</b>\n            <span className='mono'>{findArtifactPlanet}</span>\n          </Row>\n        )}\n        {method === 'depositArtifact' && (\n          <>\n            <Row>\n              <b>Planet ID</b>\n              <span className='mono'>{depositPlanet}</span>\n            </Row>\n            <Row>\n              <b>Artifact ID</b>\n              <span className='mono'>{depositArtifact}</span>\n            </Row>\n          </>\n        )}\n        {method === 'withdrawArtifact' && (\n          <>\n            <Row>\n              <b>Planet ID</b>\n              <span className='mono'>{withdrawPlanet}</span>\n            </Row>\n            <Row>\n              <b>Artifact ID</b>\n              <span className='mono'>{withdrawArtifact}</span>\n            </Row>\n          </>\n        )}\n        {method === 'activateArtifact' && (\n          <>\n            <Row>\n              <b>Planet ID</b>\n              <span className='mono'>{activatePlanet}</span>\n            </Row>\n            <Row>\n              <b>Artifact ID</b>\n              <span className='mono'>{activateArtifact}</span>\n            </Row>\n          </>\n        )}\n        {method === 'deactivateArtifact' && (\n          <>\n            <Row>\n              <b>Planet ID</b>\n              <span className='mono'>{deactivatePlanet}</span>\n            </Row>\n            <Row>\n              <b>Artifact ID</b>\n              <span className='mono'>{deactivateArtifact}</span>\n            </Row>\n          </>\n        )}\n        {method === 'withdrawSilver' && (\n          <Row>\n            <b>Planet ID</b>\n            <span className='mono'>{withdrawSilverPlanet}</span>\n          </Row>\n        )}\n      </div>\n\n      <div className='section'>\n        <Row>\n          <b>Gas Fee</b>\n          <span>{weiToGwei(gasFee)} gwei</span>\n        </Row>\n        <Row>\n          <b>Gas Limit</b>\n          <span>2000000</span>\n        </Row>\n        <Row>\n          <b>Total Transaction Cost</b>\n          <span>{txCost.toFixed(8)} xDAI</span>\n        </Row>\n        {method === 'buyHat' && hatLevel && +hatLevel > 6 && (\n          <Row>\n            <b\n              style={{\n                color: 'red',\n              }}\n            >\n              WARNING: You are buying a very expensive HAT! Check the price and make sure you intend\n              to do this!\n            </b>\n          </Row>\n        )}\n        <Row className='mtop'>\n          <b>Account Balance</b>\n          <span>{parseFloat(balance).toFixed(8)} xDAI</span>\n        </Row>\n        <Row className='mtop'>\n          <Button onClick={doReject}>\n            <span>{'Reject'}</span>\n          </Button>\n\n          <Button onClick={doApprove}>\n            <span>{'Approve'}</span>\n          </Button>\n        </Row>\n      </div>\n\n      <div className='section'>\n        <Row className='network'>\n          <div>\n            <ConfirmIcon /> DF connected to xDAI\n          </div>\n        </Row>\n        <Row className='mtop'>\n          <Checkbox\n            label='Auto-confirm all transactions except purchases. Currently, you can only purchase Hats, or anything 3rd party plugins offer.'\n            checked={autoApproveChecked}\n            onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n              setAutoApprovedChecked(e.target.checked)\n            }\n          />\n        </Row>\n      </div>\n    </StyledTxConfirmPopup>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/UnsubscribePage.tsx",
    "content": "import React from 'react';\nimport { useHistory } from 'react-router-dom';\nimport styled from 'styled-components';\nimport Button from '../Components/Button';\nimport { EmailCTA, EmailCTAMode } from '../Components/Email';\nimport { BlinkCursor, HideSmall, Invisible, Sub, Text } from '../Components/Text';\nimport LandingPageCanvas from '../Renderers/LandingPageCanvas';\nimport dfstyles from '../Styles/dfstyles';\nimport { LinkContainer } from './LandingPage';\n\nexport const enum LandingPageZIndex {\n  Background = 0,\n  Canvas = 1,\n  BasePage = 2,\n}\n\nconst styles: {\n  [name: string]: React.CSSProperties;\n} = {\n  // container stuff\n  wrapper: {\n    width: '100%',\n    height: '100%',\n  },\n  background: {\n    position: 'absolute',\n    width: '100%',\n    height: '100%',\n    background: dfstyles.colors.background,\n    zIndex: LandingPageZIndex.Background,\n  },\n  basePage: {\n    position: 'absolute',\n    width: '100%',\n    height: '100%',\n    color: dfstyles.colors.text,\n    fontSize: dfstyles.fontSize,\n    display: 'flex',\n    flexDirection: 'column',\n    justifyContent: 'space-around',\n    alignItems: 'center',\n    zIndex: LandingPageZIndex.BasePage,\n  },\n\n  // hall of fame\n  hofTitle: {\n    color: dfstyles.colors.subtext,\n    display: 'inline-block',\n    borderBottom: `1px solid ${dfstyles.colors.subtext}`,\n    lineHeight: '1em',\n  },\n};\n\nconst links = {\n  twitter: 'http://twitter.com/darkforest_eth',\n  email: 'mailto:ivan@0xparc.org',\n  blog: 'https://blog.zkga.me/',\n  telegram: 'https://t.me/zk_forest',\n  github: 'https://github.com/darkforest-eth',\n};\n\n// note: prefer styled-components when possible because semantically easier to debug\nconst Header = styled.div`\n  text-align: center;\n`;\n\nconst EmailWrapper = styled.div`\n  display: flex;\n  flex-direction: row;\n`;\n\nconst Footer = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  & > div {\n    margin-top: 16pt;\n  }\n`;\n\nconst TextLinks = styled.div`\n  & a {\n    &:first-child {\n      margin-left: 0;\n    }\n    margin-left: 7pt;\n    &:after {\n      margin-left: 7pt;\n      content: '-';\n      color: ${dfstyles.colors.subtext};\n    }\n    &:last-child:after {\n      display: none;\n    }\n\n    transition: color 0.2s;\n    &:hover {\n      color: ${dfstyles.colors.dfblue};\n    }\n  }\n`;\n\nconst Title = styled.div`\n  font-size: ${dfstyles.fontH1};\n  font-family: ${dfstyles.titleFont};\n  @media (max-width: ${dfstyles.screenSizeS}) {\n    font-size: ${dfstyles.fontH1S};\n  }\n  position: relative;\n\n  & h1 {\n    white-space: nowrap;\n  }\n\n  & h1:first-child {\n    position: absolute;\n  }\n  & h1:last-child {\n    &:before {\n      content: '>';\n      position: absolute;\n      top: 0;\n      left: -1em;\n      color: #00ff00;\n    }\n  }\n`;\n\nconst Fat = styled.span`\n  display: inline-block;\n  transform: scale(1, 1.2);\n`;\n\nexport default function UnsubscribePage() {\n  return (\n    <div style={styles.wrapper}>\n      <div style={styles.background} />\n      <LandingPageCanvas />\n\n      <div style={styles.basePage}>\n        {/* Spacer */}\n        <div></div>\n\n        {/* Title + CTA */}\n        <Header>\n          <Title>\n            <h1>\n              dark forest\n              <Fat>\n                <Sub>\n                  <BlinkCursor />\n                </Sub>\n              </Fat>\n            </h1>\n            <h1>\n              <Invisible>dark forest</Invisible>\n            </h1>\n          </Title>\n\n          <p>\n            <Sub>\n              zkSNARK space warfare <HideSmall>(v0.6)</HideSmall>\n            </Sub>\n          </p>\n        </Header>\n\n        {/* Email CTA */}\n        <EmailWrapper>\n          <EmailCTA mode={EmailCTAMode.UNSUBSCRIBE} />\n        </EmailWrapper>\n\n        {/* Footer */}\n        <Footer>\n          <TextLinks>\n            <a href={links.email}>email</a>\n            <a href={links.blog}>blog</a>\n          </TextLinks>\n          <LinkContainer>\n            <a className={'link-twitter'} href={links.twitter}>\n              <span className={'icon-twitter'}></span>\n            </a>\n            <a className={'link-discord'} href={links.telegram}>\n              <span className={'icon-discord'}></span>\n            </a>\n            <a className={'link-github'} href={links.github}>\n              <span className={'icon-github'}></span>\n            </a>\n          </LinkContainer>\n        </Footer>\n      </div>\n\n      {/*\n      <div style={styles.linksRow}>\n        <Link to='/tutorial' size='lg' style={{ marginRight: '2rem' }}>\n          Tutorial\n        </Link>\n        <Link to='/' size='lg'>\n          About\n        </Link>\n          <BasicEmailInput isPlayer={false} />\n      </div>\n      */}\n    </div>\n  );\n}\n\nfunction _GamesTable() {\n  const history = useHistory();\n  const games = ['Dark Forest 0.0', 'EF Workshop 2/21', 'ETHGlobal 4/29'];\n  return (\n    <div style={styles.gamesTable}>\n      {games.map((name, ind) => (\n        <div\n          key={name}\n          style={{\n            ...styles.gamesTableRow,\n            borderBottom: ind !== games.length - 1 ? '2px solid #9f9f9f' : undefined,\n          }}\n        >\n          <div style={styles.gamesTableRowContents}>\n            <Text>{name}</Text>\n            <div>\n              <Button\n                style={{ margin: '0 1rem' }}\n                onClick={() => {\n                  history.push('/play');\n                }}\n              >\n                Enter\n              </Button>\n              <Button\n                onClick={() => {\n                  history.push('/replay1');\n                }}\n              >\n                Replay\n              </Button>\n            </div>\n          </div>\n        </div>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Pages/ValhallaPage.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\n\nconst Container = styled.div`\n  width: 100vw;\n  height: 100vh;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: column;\n  text-align: center;\n`;\n\nexport function ValhallaPage() {\n  window.location.href = 'https://valhalla.zkga.me';\n\n  return <Container></Container>;\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ArtifactCard.tsx",
    "content": "import { ArtifactId } from '@darkforest_eth/types';\nimport React from 'react';\nimport { Padded } from '../Components/CoreUI';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ArtifactDetailsBody } from './ArtifactDetailsPane';\n\nexport function ArtifactCard({ artifactId }: { artifactId?: ArtifactId }) {\n  const uiManager = useUIManager();\n\n  if (!artifactId) return null;\n\n  return (\n    <Padded>\n      <ArtifactDetailsBody\n        noActions={true}\n        artifactId={artifactId}\n        contractConstants={uiManager.contractConstants}\n      />\n    </Padded>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ArtifactDetailsPane.tsx",
    "content": "import { EMPTY_ADDRESS } from '@darkforest_eth/constants';\nimport { dateMintedAt, hasStatBoost, isActivated, isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { artifactName, getPlanetName, getPlanetNameHash } from '@darkforest_eth/procedural';\nimport {\n  Artifact,\n  ArtifactId,\n  ArtifactRarityNames,\n  ArtifactType,\n  EthAddress,\n  LocationId,\n  TooltipName,\n  Upgrade,\n} from '@darkforest_eth/types';\nimport _ from 'lodash';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { getUpgradeStat } from '../../Backend/Utils/Utils';\nimport { ContractConstants } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { StatIdx } from '../../_types/global/GlobalTypes';\nimport { ArtifactImage } from '../Components/ArtifactImage';\nimport { Spacer } from '../Components/CoreUI';\nimport { StatIcon } from '../Components/Icons';\nimport { ArtifactRarityLabelAnim, ArtifactTypeText } from '../Components/Labels/ArtifactLabels';\nimport { ArtifactBiomeLabelAnim } from '../Components/Labels/BiomeLabels';\nimport { AccountLabel } from '../Components/Labels/Labels';\nimport { ReadMore } from '../Components/ReadMore';\nimport { Green, Red, Sub, Text, White } from '../Components/Text';\nimport { TextPreview } from '../Components/TextPreview';\nimport { TimeUntil } from '../Components/TimeUntil';\nimport dfstyles from '../Styles/dfstyles';\nimport { useArtifact, useUIManager } from '../Utils/AppHooks';\nimport { ModalHandle } from '../Views/ModalPane';\nimport { ArtifactActions } from './ManagePlanetArtifacts/ArtifactActions';\nimport { TooltipTrigger } from './Tooltip';\n\nconst StatsContainer = styled.div`\n  flex-grow: 1;\n`;\n\nconst ArtifactDetailsHeader = styled.div`\n  min-height: 128px;\n  display: flex;\n  flex-direction: row;\n\n  & > div::last-child {\n    flex-grow: 1;\n  }\n\n  .statrow {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n    align-items: center;\n\n    & > span:first-child {\n      margin-right: 1.5em;\n    }\n\n    & > span:last-child {\n      text-align: right;\n      width: 6em;\n      flex-grow: 1;\n    }\n  }\n`;\n\nexport function UpgradeStatInfo({\n  upgrades,\n  stat,\n}: {\n  upgrades: (Upgrade | undefined)[];\n  stat: StatIdx;\n}) {\n  let mult = 100;\n\n  for (const upgrade of upgrades) {\n    if (upgrade) {\n      mult *= getUpgradeStat(upgrade, stat) / 100;\n    }\n  }\n\n  const statName = [\n    TooltipName.Energy,\n    TooltipName.EnergyGrowth,\n    TooltipName.Range,\n    TooltipName.Speed,\n    TooltipName.Defense,\n  ][stat];\n\n  return (\n    <div className='statrow'>\n      <TooltipTrigger name={statName}>\n        <StatIcon stat={stat} />\n      </TooltipTrigger>\n      <span>\n        {mult > 100 && <Green>+{Math.round(mult - 100)}%</Green>}\n        {mult === 100 && <Sub>no effect</Sub>}\n        {mult < 100 && <Red>-{Math.round(100 - mult)}%</Red>}\n      </span>\n    </div>\n  );\n}\n\nconst StyledArtifactDetailsBody = styled.div`\n  & > div:first-child p {\n    text-decoration: underline;\n  }\n\n  & .row {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n\n    & > span:first-child {\n      color: ${dfstyles.colors.subtext};\n    }\n\n    & > span:last-child {\n      text-align: right;\n    }\n  }\n\n  & .link {\n    &:hover {\n      cursor: pointer;\n      text-decoration: underline;\n    }\n  }\n`;\n\nconst ArtifactName = styled.div`\n  color: ${dfstyles.colors.text};\n  font-weight: bold;\n`;\n\nconst ArtifactNameSubtitle = styled.div`\n  color: ${dfstyles.colors.subtext};\n  margin-bottom: 8px;\n`;\n\nexport function ArtifactDetailsHelpContent() {\n  return (\n    <div>\n      <p>\n        In this pane, you can see specific information about a particular artifact. You can also\n        initiate a conversation with the artifact! Try talking to your artifacts. Make some new\n        friends (^:\n      </p>\n    </div>\n  );\n}\n\nexport function ArtifactDetailsBody({\n  artifactId,\n  contractConstants,\n  depositOn,\n  noActions,\n}: {\n  artifactId: ArtifactId;\n  contractConstants: ContractConstants;\n  modal?: ModalHandle;\n  depositOn?: LocationId;\n  noActions?: boolean;\n}) {\n  const uiManager = useUIManager();\n  const artifactWrapper = useArtifact(uiManager, artifactId);\n  const artifact = artifactWrapper.value;\n\n  if (!artifact) {\n    return null;\n  }\n\n  const account = (addr: EthAddress) => {\n    const twitter = uiManager?.getTwitter(addr);\n    if (twitter) {\n      return '@' + twitter;\n    }\n    return <TextPreview text={addr} />;\n  };\n\n  const owner = () => {\n    if (!artifact) return '';\n    return account(artifact.currentOwner);\n  };\n\n  const discoverer = () => {\n    if (!artifact) return '';\n    return account(artifact.discoverer);\n  };\n\n  // TODO make this common with playerartifactspane\n  const planetArtifactName = (a: Artifact): string | undefined => {\n    const onPlanet = uiManager?.getArtifactPlanet(a);\n    if (!onPlanet) return undefined;\n    return getPlanetName(onPlanet);\n  };\n\n  const planetClicked = (): void => {\n    if (artifact.onPlanetId) uiManager?.setSelectedId(artifact.onPlanetId);\n  };\n\n  let readyInStr = undefined;\n\n  if (artifact.artifactType === ArtifactType.PhotoidCannon && isActivated(artifact)) {\n    readyInStr = (\n      <TimeUntil\n        timestamp={\n          artifact.lastActivated * 1000 + contractConstants.PHOTOID_ACTIVATION_DELAY * 1000\n        }\n        ifPassed={'now!'}\n      />\n    );\n  }\n\n  return (\n    <>\n      <div style={{ display: 'inline-block' }}>\n        <ArtifactImage artifact={artifact} size={32} />\n      </div>\n      <Spacer width={8} />\n      <div style={{ display: 'inline-block' }}>\n        {isSpaceShip(artifact.artifactType) ? (\n          <>\n            <ArtifactName>\n              <ArtifactTypeText artifact={artifact} />\n            </ArtifactName>\n            <ArtifactNameSubtitle>{artifactName(artifact)}</ArtifactNameSubtitle>\n          </>\n        ) : (\n          <>\n            <ArtifactName>{artifactName(artifact)}</ArtifactName>\n            <ArtifactNameSubtitle>\n              <ArtifactRarityLabelAnim rarity={artifact.rarity} />{' '}\n              <ArtifactBiomeLabelAnim artifact={artifact} />{' '}\n              <ArtifactTypeText artifact={artifact} />\n            </ArtifactNameSubtitle>\n          </>\n        )}\n      </div>\n\n      {hasStatBoost(artifact.artifactType) && (\n        <ArtifactDetailsHeader>\n          <StatsContainer>\n            {_.range(0, 5).map((val) => (\n              <UpgradeStatInfo\n                upgrades={[artifact.upgrade, artifact.timeDelayedUpgrade]}\n                stat={val}\n                key={val}\n              />\n            ))}\n          </StatsContainer>\n        </ArtifactDetailsHeader>\n      )}\n\n      {isSpaceShip(artifact.artifactType) && (\n        <ArtifactDescription collapsable={false} artifact={artifact} />\n      )}\n\n      <StyledArtifactDetailsBody>\n        {!isSpaceShip(artifact.artifactType) && <ArtifactDescription artifact={artifact} />}\n        <Spacer height={8} />\n\n        <div className='row'>\n          <span>Located On</span>\n          {planetArtifactName(artifact) ? (\n            <span className='link' onClick={planetClicked}>\n              {planetArtifactName(artifact)}\n            </span>\n          ) : (\n            <span>n / a</span>\n          )}\n        </div>\n\n        {!isSpaceShip(artifact.artifactType) && (\n          <>\n            <div className='row'>\n              <span>Minted At</span>\n              <span>{dateMintedAt(artifact)}</span>\n            </div>\n            <div className='row'>\n              <span>Discovered On</span>\n              <span>{getPlanetNameHash(artifact.planetDiscoveredOn)}</span>\n            </div>\n            <div className='row'>\n              <span>Discovered By</span>\n              <span>{discoverer()}</span>\n            </div>\n          </>\n        )}\n\n        {artifact.controller === EMPTY_ADDRESS && (\n          <div className='row'>\n            <span>Owner</span>\n            <span>{owner()}</span>\n          </div>\n        )}\n        <div className='row'>\n          <span>ID</span>\n          <TextPreview text={artifact.id} />\n        </div>\n\n        {artifact.controller !== EMPTY_ADDRESS && (\n          <div className='row'>\n            <span>Controller</span>\n            <span>\n              <AccountLabel ethAddress={artifact.controller} />\n            </span>\n          </div>\n        )}\n        {readyInStr && (\n          <div className='row'>\n            <span>Ready In</span>\n            <span>{readyInStr}</span>\n          </div>\n        )}\n\n        {!noActions && (\n          <ArtifactActions artifactId={artifactWrapper.value?.id} depositOn={depositOn} />\n        )}\n      </StyledArtifactDetailsBody>\n    </>\n  );\n}\n\nexport function ArtifactDetailsPane({\n  modal,\n  artifactId,\n  depositOn,\n}: {\n  modal: ModalHandle;\n  artifactId: ArtifactId;\n  depositOn?: LocationId;\n}) {\n  const uiManager = useUIManager();\n  const contractConstants = uiManager.contractConstants;\n\n  return (\n    <ArtifactDetailsBody\n      modal={modal}\n      artifactId={artifactId}\n      contractConstants={contractConstants}\n      depositOn={depositOn}\n    />\n  );\n}\n\nfunction ArtifactDescription({\n  artifact,\n  collapsable,\n}: {\n  artifact: Artifact;\n  collapsable?: boolean;\n}) {\n  let content;\n\n  const maxLevelsBlackDomain = [0, 2, 4, 6, 8, 9];\n  const maxLevelBlackDomain = maxLevelsBlackDomain[artifact.rarity];\n\n  const maxLevelsBloomFilter = [0, 2, 4, 6, 8, 9];\n  const maxLevelBloomFilter = maxLevelsBloomFilter[artifact.rarity];\n\n  const wormholeShrinkLevels = [0, 2, 4, 8, 16, 32];\n  const rarityName = ArtifactRarityNames[artifact.rarity];\n  const photoidRanges = [0, 2, 2, 2, 2, 2];\n  const photoidSpeeds = [0, 5, 10, 15, 20, 25];\n\n  const genericSpaceshipDescription = <>Can move between planets without sending energy.</>;\n\n  switch (artifact.artifactType) {\n    case ArtifactType.BlackDomain:\n      content = (\n        <Text>\n          When activated, permanently disables your planet. It'll still be yours, but you won't be\n          able to do anything with it. It turns completely black too. Just ... gone. Because this\n          one is <White>{rarityName}</White>, it works on planets up to level{' '}\n          <White>{maxLevelBlackDomain}</White>. This artifact is consumed on activation.\n        </Text>\n      );\n      break;\n    case ArtifactType.BloomFilter:\n      content = (\n        <Text>\n          When activated refills your planet's energy and silver to their respective maximum values.\n          How it does this, we do not know. Because this one is <White>{rarityName}</White>, it\n          works on planets up to level <White>{maxLevelBloomFilter}</White>. This artifact is\n          consumed on activation.\n        </Text>\n      );\n      break;\n    case ArtifactType.Wormhole:\n      content = (\n        <Text>\n          When activated, shortens the distance between this planet and another one. All moves\n          between those two planets decay less energy, and complete faster.{' '}\n          <Red>\n            Energy sent through your wormhole to a planet you do not control does not arrive.\n          </Red>{' '}\n          Because this one is <White>{rarityName}</White>, it shrinks the distance by a factor of{' '}\n          <White>{wormholeShrinkLevels[artifact.rarity]}</White>x.\n        </Text>\n      );\n      break;\n    case ArtifactType.PhotoidCannon:\n      content = (\n        <Text>\n          Ahh, the Photoid Canon. Activate it, wait four hours. Because this one is{' '}\n          <White>{rarityName}</White>, the next move you send will be able to go{' '}\n          <White>{photoidRanges[artifact.rarity]}</White>x further and{' '}\n          <White>{photoidSpeeds[artifact.rarity]}</White>x faster. During the 4 hour waiting period,\n          your planet's defense is temporarily decreased. This artifact is consumed once the canon\n          is fired.\n        </Text>\n      );\n      break;\n    case ArtifactType.PlanetaryShield:\n      content = (\n        <Text>\n          Activate the planetary shield to gain a defense bonus on your planet, at the expense of\n          range and speed. When this artifact is deactivated, it is destroyed and your planet's\n          stats are reverted--so use it wisely!\n        </Text>\n      );\n      break;\n    case ArtifactType.ShipMothership:\n      content = (\n        <Text>\n          Doubles energy regeneration of the planet that it is currently on.{' '}\n          {genericSpaceshipDescription}\n        </Text>\n      );\n      break;\n    case ArtifactType.ShipCrescent:\n      content = (\n        <Text>\n          Activate to convert an un-owned planet whose level is more than 0 into an Asteroid Field.{' '}\n          <Red>Can only be used once.</Red> {genericSpaceshipDescription}\n        </Text>\n      );\n      break;\n    case ArtifactType.ShipGear:\n      content = (\n        <Text>\n          Allows you to prospect planets, and subsequently find artifacts on them.{' '}\n          {genericSpaceshipDescription}\n        </Text>\n      );\n      break;\n    case ArtifactType.ShipTitan:\n      content = (\n        <Text>\n          Pauses energy and silver regeneration on the planet it's on. {genericSpaceshipDescription}\n        </Text>\n      );\n      break;\n    case ArtifactType.ShipWhale:\n      content = (\n        <Text>\n          Doubles the silver regeneration of the planet that it is currently on.{' '}\n          {genericSpaceshipDescription}\n        </Text>\n      );\n      break;\n  }\n\n  if (content) {\n    return (\n      <div>\n        {collapsable ? (\n          <ReadMore height={'1.2em'} toggleButtonMargin={'0em'}>\n            {content}\n          </ReadMore>\n        ) : (\n          content\n        )}\n      </div>\n    );\n  }\n\n  return null;\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ArtifactHoverPane.tsx",
    "content": "import React from 'react';\nimport { useHoverArtifactId, useUIManager } from '../Utils/AppHooks';\nimport { ArtifactCard } from './ArtifactCard';\nimport { HoverPane } from './HoverPane';\n\nexport function ArtifactHoverPane() {\n  const uiManager = useUIManager();\n  const hoverArtifactId = useHoverArtifactId(uiManager);\n\n  return (\n    <HoverPane\n      visible={hoverArtifactId.value !== undefined}\n      element={<ArtifactCard artifactId={hoverArtifactId.value} />}\n    />\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ArtifactsList.tsx",
    "content": "import { isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { artifactName, getPlanetName } from '@darkforest_eth/procedural';\nimport { Artifact, ArtifactTypeNames, LocationId } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { CenterBackgroundSubtext, Truncate } from '../Components/CoreUI';\nimport { ArtifactRarityLabelAnim } from '../Components/Labels/ArtifactLabels';\nimport { Sub } from '../Components/Text';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ArtifactLink } from '../Views/ArtifactLink';\nimport { ModalHandle } from '../Views/ModalPane';\nimport { PlanetLink } from '../Views/PlanetLink';\nimport { SortableTable } from '../Views/SortableTable';\nimport { TabbedView } from '../Views/TabbedView';\n\nconst ArtifactsBody = styled.div`\n  min-height: 200px;\n  max-height: 400px;\n  overflow-y: scroll;\n`;\n\nconst PlanetName = styled.span`\n  display: block;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  max-width: 100px;\n`;\n\nconst planetArtifactName = (a: Artifact, uiManager: GameUIManager): string | undefined => {\n  const onPlanet = uiManager?.getArtifactPlanet(a);\n  if (!onPlanet) return undefined;\n  return getPlanetName(onPlanet);\n};\n\nexport function ArtifactsList({\n  modal,\n  artifacts,\n  depositOn,\n  maxRarity,\n  noArtifactsMessage,\n}: {\n  modal: ModalHandle;\n  artifacts: Artifact[];\n  depositOn?: LocationId;\n  maxRarity?: number;\n  noArtifactsMessage?: React.ReactElement;\n}) {\n  const uiManager = useUIManager();\n  let nonShipArtifacts = artifacts.filter((a) => !isSpaceShip(a.artifactType));\n  if (maxRarity !== undefined) {\n    nonShipArtifacts = nonShipArtifacts.filter((a) => a.rarity <= maxRarity);\n  }\n  const headers = ['Name', 'Location', 'Type', 'Rarity'];\n  const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r'];\n\n  const columns = [\n    (artifact: Artifact) => (\n      <ArtifactLink depositOn={depositOn} modal={modal} artifact={artifact}>\n        {artifactName(artifact)}\n      </ArtifactLink>\n    ),\n    (artifact: Artifact) => {\n      const planetOn = uiManager.getArtifactPlanet(artifact);\n      const planetOnName = planetArtifactName(artifact, uiManager);\n\n      return (\n        <span>\n          {planetOnName && planetOn ? (\n            <PlanetLink planet={planetOn}>\n              <PlanetName>{planetOnName}</PlanetName>\n            </PlanetLink>\n          ) : (\n            <Sub>wallet</Sub>\n          )}\n        </span>\n      );\n    },\n    (artifact: Artifact) => (\n      <Sub>\n        <Truncate maxWidth='75px'>{ArtifactTypeNames[artifact.artifactType]}</Truncate>\n      </Sub>\n    ),\n    (artifact: Artifact) => <ArtifactRarityLabelAnim rarity={artifact.rarity} />,\n  ];\n\n  const sortFunctions = [\n    (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)),\n    (left: Artifact, right: Artifact) =>\n      planetArtifactName(left, uiManager)?.localeCompare(\n        planetArtifactName(right, uiManager) || ''\n      ) || 0,\n    (left: Artifact, right: Artifact) =>\n      ArtifactTypeNames[left.artifactType]?.localeCompare(\n        ArtifactTypeNames[right.artifactType] || ''\n      ) || 0,\n    (left: Artifact, right: Artifact) => left.rarity - right.rarity,\n  ];\n\n  if (nonShipArtifacts.length === 0) {\n    return (\n      <CenterBackgroundSubtext width={'100%'} height='100px'>\n        {noArtifactsMessage ?? (\n          <>\n            You Don't Have <br /> Any Artifacts\n          </>\n        )}\n      </CenterBackgroundSubtext>\n    );\n  }\n\n  return (\n    <SortableTable\n      paginated={false}\n      rows={nonShipArtifacts}\n      headers={headers}\n      columns={columns}\n      sortFunctions={sortFunctions}\n      alignments={alignments}\n    />\n  );\n}\n\nexport function ShipList({\n  modal,\n  artifacts,\n  depositOn,\n  noShipsMessage,\n}: {\n  modal: ModalHandle;\n  artifacts: Artifact[];\n  depositOn?: LocationId;\n  noShipsMessage?: React.ReactElement;\n}) {\n  const uiManager = useUIManager();\n  const headers = ['Name', 'Location', 'Type'];\n  const alignments: Array<'r' | 'c' | 'l'> = ['l', 'r', 'r', 'r'];\n  const shipArtifacts = artifacts.filter((a) => isSpaceShip(a.artifactType));\n\n  const columns = [\n    (artifact: Artifact) => (\n      <ArtifactLink depositOn={depositOn} modal={modal} artifact={artifact}>\n        {artifactName(artifact)}\n      </ArtifactLink>\n    ),\n    (artifact: Artifact) => {\n      const planetOn = uiManager.getArtifactPlanet(artifact);\n      const planetOnName = planetArtifactName(artifact, uiManager);\n\n      return (\n        <span>\n          {planetOnName && planetOn ? (\n            <PlanetLink planet={planetOn}>\n              <PlanetName>{planetOnName}</PlanetName>\n            </PlanetLink>\n          ) : (\n            <Sub>moving</Sub>\n          )}\n        </span>\n      );\n    },\n    (artifact: Artifact) => (\n      <Sub>\n        <Truncate maxWidth='75px'>{ArtifactTypeNames[artifact.artifactType]}</Truncate>\n      </Sub>\n    ),\n  ];\n\n  const sortFunctions = [\n    (left: Artifact, right: Artifact) => artifactName(left).localeCompare(artifactName(right)),\n    (left: Artifact, right: Artifact) =>\n      planetArtifactName(left, uiManager)?.localeCompare(\n        planetArtifactName(right, uiManager) || ''\n      ) || 0,\n    (left: Artifact, right: Artifact) =>\n      ArtifactTypeNames[left.artifactType]?.localeCompare(\n        ArtifactTypeNames[right.artifactType] || ''\n      ) || 0,\n  ];\n\n  if (shipArtifacts.length === 0) {\n    return (\n      <CenterBackgroundSubtext width={'100%'} height='100px'>\n        {noShipsMessage ?? (\n          <>\n            You Don't Have <br /> Any Ships\n          </>\n        )}\n      </CenterBackgroundSubtext>\n    );\n  }\n\n  return (\n    <SortableTable\n      paginated={false}\n      rows={shipArtifacts}\n      headers={headers}\n      columns={columns}\n      sortFunctions={sortFunctions}\n      alignments={alignments}\n    />\n  );\n}\n\nexport function AllArtifacts({\n  modal,\n  artifacts,\n  depositOn,\n  maxRarity,\n  noArtifactsMessage,\n  noShipsMessage,\n}: {\n  modal: ModalHandle;\n  artifacts: Artifact[];\n  depositOn?: LocationId;\n  maxRarity?: number;\n  noArtifactsMessage?: React.ReactElement;\n  noShipsMessage?: React.ReactElement;\n}) {\n  return (\n    <ArtifactsBody>\n      <TabbedView\n        style={{ height: '100%' }}\n        tabTitles={['artifacts', 'ships']}\n        tabContents={(i) => {\n          if (i === 0) {\n            return (\n              <ArtifactsList\n                maxRarity={maxRarity}\n                depositOn={depositOn}\n                modal={modal}\n                artifacts={artifacts}\n                noArtifactsMessage={noArtifactsMessage}\n              />\n            );\n          }\n\n          return (\n            <ShipList\n              depositOn={depositOn}\n              modal={modal}\n              artifacts={artifacts}\n              noShipsMessage={noShipsMessage}\n            />\n          );\n        }}\n      />\n    </ArtifactsBody>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/BroadcastPane.tsx",
    "content": "import { isUnconfirmedRevealTx } from '@darkforest_eth/serde';\nimport { EthAddress, LocationId } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Blue, White } from '../Components/Text';\nimport { formatDuration, TimeUntil } from '../Components/TimeUntil';\nimport dfstyles from '../Styles/dfstyles';\nimport { usePlanet, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { ModalHandle } from '../Views/ModalPane';\n\nconst BroadcastWrapper = styled.div`\n  & .row {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n    & > span {\n      &:first-child {\n        color: ${dfstyles.colors.subtext};\n        padding-right: 1em;\n      }\n    }\n  }\n  & .message {\n    margin: 1em 0;\n\n    & p {\n      margin: 0.5em 0;\n\n      &:last-child {\n        margin-bottom: 1em;\n      }\n    }\n  }\n`;\n\nexport function BroadcastPaneHelpContent() {\n  return (\n    <div>\n      Reveal this planet's location to all other players on-chain!\n      <Spacer height={8} />\n      Broadcasting can be a potent offensive tactic! Reveal a powerful enemy's location, and maybe\n      someone else will take care of them for you?\n    </div>\n  );\n}\n\nexport function BroadcastPane({\n  initialPlanetId,\n  modal: _modal,\n}: {\n  modal: ModalHandle;\n  initialPlanetId: LocationId | undefined;\n}) {\n  const uiManager = useUIManager();\n  const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId);\n  const planet = usePlanet(uiManager, planetId).value;\n\n  const getLoc = () => {\n    if (!planet || !uiManager) return { x: 0, y: 0 };\n    const loc = uiManager.getLocationOfPlanet(planet.locationId);\n    if (!loc) return { x: 0, y: 0 };\n    return loc.coords;\n  };\n\n  const broadcast = () => {\n    if (!planet || !uiManager) return;\n    const loc = uiManager.getLocationOfPlanet(planet.locationId);\n    if (!loc) return;\n\n    uiManager.revealLocation(loc.hash);\n  };\n\n  const [account, setAccount] = useState<EthAddress | undefined>(undefined); // consider moving this one to parent\n  const isRevealed = planet?.coordsRevealed;\n  const broadcastCooldownPassed = uiManager.getNextBroadcastAvailableTimestamp() <= Date.now();\n  const currentlyBroadcastingAnyPlanet = uiManager.isCurrentlyRevealing();\n\n  useEffect(() => {\n    if (!uiManager) return;\n    setAccount(uiManager.getAccount());\n  }, [uiManager]);\n\n  let revealBtn = undefined;\n\n  if (isRevealed) {\n    revealBtn = <Btn disabled={true}>Broadcast Coordinates</Btn>;\n  } else if (planet?.transactions?.hasTransaction(isUnconfirmedRevealTx)) {\n    revealBtn = (\n      <Btn disabled={true}>\n        <LoadingSpinner initialText={'Broadcasting...'} />\n      </Btn>\n    );\n  } else if (!broadcastCooldownPassed) {\n    revealBtn = <Btn disabled={true}>Broadcast Coordinates</Btn>;\n  } else {\n    revealBtn = (\n      <Btn disabled={currentlyBroadcastingAnyPlanet} onClick={broadcast}>\n        Broadcast Coordinates\n      </Btn>\n    );\n  }\n\n  const warningsSection = (\n    <div>\n      {currentlyBroadcastingAnyPlanet && (\n        <p>\n          <Blue>INFO:</Blue> Revealing...\n        </p>\n      )}\n      {planet?.owner === account && (\n        <p>\n          <Blue>INFO:</Blue> You own this planet! Revealing its location is a dangerous flex.\n        </p>\n      )}\n      {isRevealed && (\n        <p>\n          <Blue>INFO:</Blue> This planet's location is already revealed, and can't be revealed\n          again!\n        </p>\n      )}\n      {!broadcastCooldownPassed && (\n        <p>\n          <Blue>INFO:</Blue> You must wait{' '}\n          <TimeUntil timestamp={uiManager.getNextBroadcastAvailableTimestamp()} ifPassed={'now!'} />{' '}\n          to reveal another planet.\n        </p>\n      )}\n    </div>\n  );\n\n  if (planet) {\n    return (\n      <BroadcastWrapper>\n        <div>\n          You can broadcast a planet to publically reveal its location on the map. You can only\n          broadcast a planet's location once every{' '}\n          <White>\n            {formatDuration(uiManager.contractConstants.LOCATION_REVEAL_COOLDOWN * 1000)}\n          </White>\n          .\n        </div>\n        <div className='message'>{warningsSection}</div>\n        <div className='row'>\n          <span>Coordinates</span>\n          <span>{`(${getLoc().x}, ${getLoc().y})`}</span>\n        </div>\n        <Spacer height={8} />\n        <p style={{ textAlign: 'right' }}>{revealBtn}</p>\n      </BroadcastWrapper>\n    );\n  } else {\n    return (\n      <CenterBackgroundSubtext width='100%' height='75px'>\n        Select a Planet\n      </CenterBackgroundSubtext>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Panes/CoordsPane.tsx",
    "content": "import { SpaceType, WorldCoords } from '@darkforest_eth/types';\nimport React, { useState } from 'react';\nimport styled from 'styled-components';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { useUIManager } from '../Utils/AppHooks';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\n\nclass CoordsText extends React.Component<\n  {\n    uiManager: GameUIManager | undefined;\n  },\n  never\n> {\n  private coordsRef = React.createRef<HTMLSpanElement>();\n  private spacetypeRef = React.createRef<HTMLSpanElement>();\n  private uiEmitter = UIEmitter.getInstance();\n\n  componentDidMount() {\n    this.uiEmitter.on(UIEmitterEvent.WorldMouseMove, this.update);\n  }\n\n  componentWillUnmount() {\n    this.uiEmitter.removeListener(UIEmitterEvent.WorldMouseMove, this.update);\n  }\n\n  update = (coords: WorldCoords) => {\n    this.setCoords(coords);\n    this.setSpacetype(coords);\n  };\n\n  setCoords(coords: WorldCoords) {\n    if (this.coordsRef.current) {\n      this.coordsRef.current.innerText = coords\n        ? `(${Math.round(coords.x)}, ${Math.round(coords.y)})`\n        : '(x, y)';\n    }\n  }\n\n  setSpacetype(coords: WorldCoords) {\n    let spacetypeText = '???';\n\n    if (this.props.uiManager) {\n      const per = this.props.uiManager.getSpaceTypePerlin(coords, false);\n      const spaceType = this.props.uiManager.spaceTypeFromPerlin(per);\n\n      let suff = '';\n      if (spaceType === SpaceType.NEBULA) suff = '\\u00b0 (NEBULA)';\n      else if (spaceType === SpaceType.SPACE) suff = '\\u00b0 (SPACE)';\n      else if (spaceType === SpaceType.DEEP_SPACE) suff = '\\u00b0 (DEEP SPACE)';\n      else if (spaceType === SpaceType.DEAD_SPACE) suff = '\\u00b0 (DEAD SPACE)';\n\n      spacetypeText = `${Math.floor((16 - per) * 16)}${suff}`;\n    }\n\n    if (this.spacetypeRef.current) {\n      this.spacetypeRef.current.innerText = 'TEMP: ' + spacetypeText;\n    }\n  }\n\n  render() {\n    return (\n      <>\n        <span ref={this.coordsRef}></span>\n        <span ref={this.spacetypeRef}></span>\n      </>\n    );\n  }\n}\n\nconst StyledCoordsPane = styled.div`\n  position: absolute;\n  bottom: 0;\n  right: 0;\n  padding: 0.5em;\n\n  text-align: right;\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-end;\n\n  width: 16em;\n  height: 4em;\n`;\n\nexport function CoordsPane() {\n  const [hovering, setHovering] = useState<boolean>(false);\n  const [hidden, setHidden] = useState<boolean>(false);\n  const uiManager = useUIManager();\n\n  return (\n    <StyledCoordsPane\n      onClick={() => setHidden((b) => !b)}\n      onMouseEnter={() => setHovering(true)}\n      onMouseLeave={() => setHovering(false)}\n    >\n      {hidden ? (\n        <span>{hovering ? 'Click to show' : ''}</span>\n      ) : hovering ? (\n        <span>Click to hide</span>\n      ) : (\n        <CoordsText uiManager={uiManager} />\n      )}\n    </StyledCoordsPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/DiagnosticsPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { Diagnostics, ModalName, Setting } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { EmSpacer, Separator, SpreadApart } from '../Components/CoreUI';\nimport { DisplayGasPrices } from '../Components/DisplayGasPrices';\nimport { TextPreview } from '../Components/TextPreview';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { BooleanSetting } from '../Utils/SettingsHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { TabbedView } from '../Views/TabbedView';\n\nexport function DiagnosticsPane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n  const [currentDiagnostics, setCurrentDiagnostics] = useState(\n    new Wrapper(uiManager.getDiagnostics())\n  );\n\n  useEffect(() => {\n    const interval = setInterval(() => {\n      setCurrentDiagnostics(new Wrapper(uiManager.getDiagnostics()));\n    }, 250);\n\n    return () => clearInterval(interval);\n  }, [uiManager]);\n\n  return (\n    <ModalPane\n      id={ModalName.Diagnostics}\n      title={'Diagnostics'}\n      visible={visible}\n      onClose={onClose}\n      width={RECOMMENDED_MODAL_WIDTH}\n    >\n      <DiagnosticsTabs diagnostics={currentDiagnostics} />\n    </ModalPane>\n  );\n}\n\nfunction DiagnosticsTabs({ diagnostics }: { diagnostics: Wrapper<Diagnostics> }) {\n  return (\n    <TabbedView\n      tabTitles={['rendering', 'networking']}\n      tabContents={(i) => {\n        switch (i) {\n          case 0:\n            return <RenderingTab diagnostics={diagnostics} />;\n          case 1:\n            return <NetworkingTab diagnostics={diagnostics} />;\n        }\n      }}\n    />\n  );\n}\n\nfunction RenderingTab({ diagnostics }: { diagnostics: Wrapper<Diagnostics> }) {\n  const uiManager = useUIManager();\n\n  return (\n    <>\n      <Separator />\n\n      <SpreadApart>\n        <span>fps</span>\n        <span>{Math.floor(diagnostics.value.fps)}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>visible chunks</span>\n        <span>{diagnostics.value.visibleChunks.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>total chunks</span>\n        <span>{diagnostics.value.totalChunks.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>visible planets</span>\n        <span>{diagnostics.value.visiblePlanets.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>total planets</span>\n        <span>{diagnostics.value.totalPlanets.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>queued chunk writes</span>\n        <span>{diagnostics.value.chunkUpdates.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>viewport</span>\n        <span>\n          <span>{diagnostics.value.width?.toLocaleString()}</span>\n          <span> x </span>\n          <span>{diagnostics.value.height?.toLocaleString()}</span>\n        </span>\n      </SpreadApart>\n      <Separator />\n      <EmSpacer height={0.5} />\n      <BooleanSetting\n        uiManager={uiManager}\n        setting={Setting.DrawChunkBorders}\n        settingDescription='draw chunk borders'\n      />\n    </>\n  );\n}\n\nfunction NetworkingTab({ diagnostics }: { diagnostics: Wrapper<Diagnostics> }) {\n  return (\n    <>\n      <Separator />\n\n      <SpreadApart>\n        <span>rpc url</span>\n        <span>\n          <TextPreview\n            text={diagnostics.value.rpcUrl}\n            unFocusedWidth={'100px'}\n            focusedWidth={'100px'}\n          />\n        </span>\n      </SpreadApart>\n      <Separator />\n      <SpreadApart>\n        <span>completed calls</span>\n        <span>{diagnostics.value.totalCalls.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>queued calls</span>\n        <span>{diagnostics.value.callsInQueue.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>completed transactions</span>\n        <span>{diagnostics.value.totalTransactions.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>queued transactions</span>\n        <span>{diagnostics.value.transactionsInQueue.toLocaleString()}</span>\n      </SpreadApart>\n\n      <Separator />\n\n      <SpreadApart>\n        <span>oracle gas prices</span>\n        <span>\n          <DisplayGasPrices gasPrices={diagnostics.value.gasPrices} />\n        </span>\n      </SpreadApart>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ExplorePane.tsx",
    "content": "import {\n  CursorState,\n  ModalManagerEvent,\n  Setting,\n  TooltipName,\n  WorldCoords,\n} from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport TutorialManager, { TutorialState } from '../../Backend/GameLogic/TutorialManager';\nimport {\n  MiningPatternType,\n  SpiralPattern,\n  SwissCheesePattern,\n  TowardsCenterPattern,\n  TowardsCenterPatternV2,\n} from '../../Backend/Miner/MiningPatterns';\nimport { EmSpacer, SelectFrom } from '../Components/CoreUI';\nimport { Icon, IconType } from '../Components/Icons';\nimport { MaybeShortcutButton } from '../Components/MaybeShortcutButton';\nimport { Coords, Sub } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { MIN_CHUNK_SIZE } from '../Utils/constants';\nimport { MultiSelectSetting, useBooleanSetting } from '../Utils/SettingsHooks';\nimport { TOGGLE_EXPLORE, TOGGLE_TARGETTING } from '../Utils/ShortcutConstants';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport { TooltipTrigger } from './Tooltip';\n\nconst StyledExplorePane = styled.div`\n  background: ${dfstyles.colors.background};\n  position: absolute;\n  bottom: 0;\n  left: 0;\n\n  padding: 0.5em;\n  margin: 0.5em;\n\n  /* border: 1px solid ${dfstyles.colors.subtext}; */\n  border-radius: ${dfstyles.borderRadius};\n`;\n\nfunction Cores() {\n  const uiManager = useUIManager();\n\n  const values = ['1', '2', '4', '8', '16', '32'];\n  const labels = values.map((value) => value + ' core' + (value === '1' ? '' : 's'));\n\n  return (\n    <MultiSelectSetting\n      style={{ width: '7em' }}\n      uiManager={uiManager}\n      setting={Setting.MiningCores}\n      values={values}\n      labels={labels}\n    />\n  );\n}\n\nconst Pattern = {\n  [MiningPatternType.Spiral.toString()]: SpiralPattern,\n  [MiningPatternType.SwissCheese.toString()]: SwissCheesePattern,\n  [MiningPatternType.TowardsCenter.toString()]: TowardsCenterPattern,\n  [MiningPatternType.TowardsCenterV2.toString()]: TowardsCenterPatternV2,\n};\n\nconst miningSelectValues = [\n  MiningPatternType.Spiral.toString(),\n  MiningPatternType.SwissCheese.toString(),\n  MiningPatternType.TowardsCenter.toString(),\n  MiningPatternType.TowardsCenterV2.toString(),\n];\nconst miningSelectLabels = ['Spiral', 'SwissCheese', 'TowardsCenter', 'TowardsCenterV2'];\n\nfunction HashesPerSec() {\n  const uiManager = useUIManager();\n\n  const [hashRate, setHashRate] = useState<number>(0);\n\n  useEffect(() => {\n    if (!uiManager) return;\n    const updateHashes = () => {\n      setHashRate(uiManager.getHashesPerSec());\n    };\n\n    const intervalId = setInterval(updateHashes, 1000);\n    updateHashes();\n\n    return () => {\n      clearInterval(intervalId);\n    };\n  }, [uiManager]);\n\n  const getHashes = () => {\n    return Math.floor(hashRate).toLocaleString();\n  };\n\n  return (\n    <>\n      <TooltipTrigger name={TooltipName.HashesPerSec}>\n        {getHashes()}\n        <EmSpacer width={0.5} />\n        <Sub>#/s</Sub>\n      </TooltipTrigger>\n    </>\n  );\n}\n\nexport function ExplorePane() {\n  const uiManager = useUIManager();\n  const modalManager = uiManager.getModalManager();\n  const uiEmitter = UIEmitter.getInstance();\n  const [pattern, setPattern] = useState<string>(MiningPatternType.Spiral.toString());\n  const [mining] = useBooleanSetting(uiManager, Setting.IsMining);\n  const [targetting, setTargetting] = useState(false);\n  const [coords, setCoords] = useState<WorldCoords>(uiManager.getHomeCoords());\n\n  useEffect(() => {\n    const doMouseDown = (worldCoords: WorldCoords) => {\n      if (modalManager.getCursorState() === CursorState.TargetingExplorer) {\n        modalManager.acceptInputForTarget({\n          x: Math.floor(worldCoords.x),\n          y: Math.floor(worldCoords.y),\n        });\n      }\n    };\n\n    const updateCoords = (worldCoords: WorldCoords) => {\n      const PatternCtor = Pattern[pattern];\n      const newpattern = new PatternCtor(worldCoords, MIN_CHUNK_SIZE);\n      uiManager?.setMiningPattern(newpattern);\n      setCoords(worldCoords);\n\n      const tutorialManager = TutorialManager.getInstance(uiManager);\n      tutorialManager.acceptInput(TutorialState.MinerMove);\n    };\n    const cursorStateChanged = (state: CursorState) => {\n      setTargetting(state === CursorState.TargetingExplorer);\n    };\n\n    uiEmitter.on(UIEmitterEvent.WorldMouseDown, doMouseDown);\n    modalManager.on(ModalManagerEvent.MiningCoordsUpdate, updateCoords);\n    uiEmitter.on(ModalManagerEvent.StateChanged, cursorStateChanged);\n    return () => {\n      uiEmitter.removeListener(UIEmitterEvent.WorldMouseDown, doMouseDown);\n      modalManager.removeListener(ModalManagerEvent.MiningCoordsUpdate, updateCoords);\n      uiEmitter.removeListener(ModalManagerEvent.StateChanged, cursorStateChanged);\n    };\n  }, [uiEmitter, modalManager, uiManager, pattern]);\n\n  const updatePattern = (pattern: string) => {\n    setPattern(pattern);\n    const PatternCtor = Pattern[pattern];\n    const newpattern = new PatternCtor(coords, MIN_CHUNK_SIZE);\n    uiManager.setMiningPattern(newpattern);\n  };\n\n  const doTarget = () => {\n    uiManager.toggleTargettingExplorer();\n  };\n\n  const doExplore = () => {\n    uiManager.toggleExplore();\n  };\n\n  return (\n    <StyledExplorePane>\n      {/* button which allows player to preposition the center of their miner */}\n      <TooltipTrigger style={{ display: 'inline-block' }} name={TooltipName.MiningTarget}>\n        <MaybeShortcutButton\n          onClick={doTarget}\n          onShortcutPressed={doTarget}\n          shortcutKey={TOGGLE_TARGETTING}\n          shortcutText={TOGGLE_TARGETTING}\n        >\n          {targetting ? 'Moving...' : 'Move'}\n          <EmSpacer width={1} />\n          <Icon type={IconType.Target} />\n        </MaybeShortcutButton>\n      </TooltipTrigger>\n      <EmSpacer width={0.5} />\n      {/* button which toggles whether or not the game is mining. this persists between refreshes */}\n      <TooltipTrigger style={{ display: 'inline-block' }} name={TooltipName.MiningPause}>\n        <MaybeShortcutButton\n          onClick={doExplore}\n          onShortcutPressed={doExplore}\n          shortcutKey={TOGGLE_EXPLORE}\n          shortcutText={'space'}\n        >\n          {mining ? 'Pause' : 'Explore!'} <EmSpacer width={1} />{' '}\n          {mining ? <Icon type={IconType.Pause} /> : <Icon type={IconType.Play} />}\n        </MaybeShortcutButton>\n      </TooltipTrigger>\n\n      {mining && (\n        <>\n          <EmSpacer width={0.5} />\n          <Cores />\n          <EmSpacer width={0.5} />\n          {/* TODO: Make this a Settings thing */}\n          <SelectFrom\n            style={{ width: '10em' }}\n            values={miningSelectValues}\n            labels={miningSelectLabels}\n            value={pattern}\n            setValue={updatePattern}\n          />\n          <EmSpacer width={1} />\n          <Sub>\n            <Coords coords={coords} />\n          </Sub>\n          <EmSpacer width={0.5} />\n          <HashesPerSec />\n        </>\n      )}\n    </StyledExplorePane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/HatPane.tsx",
    "content": "import { weiToEth } from '@darkforest_eth/network';\nimport { getHatSizeName, getPlanetCosmetic } from '@darkforest_eth/procedural';\nimport { isUnconfirmedBuyHatTx } from '@darkforest_eth/serde';\nimport { LocationId, Planet } from '@darkforest_eth/types';\nimport { BigNumber } from 'ethers';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { CenterBackgroundSubtext, EmSpacer, Link } from '../Components/CoreUI';\nimport { Sub } from '../Components/Text';\nimport { useAccount, usePlanet, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { ModalHandle } from '../Views/ModalPane';\n\nconst StyledHatPane = styled.div`\n  & > div {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n\n    &:last-child > span {\n      margin-top: 1em;\n      text-align: center;\n      flex-grow: 1;\n    }\n\n    &.margin-top {\n      margin-top: 0.5em;\n    }\n  }\n`;\n\nconst getHatCostEth = (planet: Planet) => {\n  return 2 ** planet.hatLevel;\n};\n\nexport function HatPane({\n  initialPlanetId,\n  modal: _modal,\n}: {\n  modal: ModalHandle;\n  initialPlanetId?: LocationId;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId);\n  const planetWrapper = usePlanet(uiManager, planetId);\n  const planet = planetWrapper.value;\n  const balanceEth = weiToEth(\n    useEmitterValue(uiManager.getEthConnection().myBalance$, BigNumber.from('0'))\n  );\n  const enabled = (planet: Planet): boolean =>\n    !planet.transactions?.hasTransaction(isUnconfirmedBuyHatTx) &&\n    planet?.owner === account &&\n    balanceEth > getHatCostEth(planet);\n\n  if (planet && planet.owner === account) {\n    return (\n      <StyledHatPane>\n        <div>\n          <Sub>HAT</Sub>\n          <span>{getPlanetCosmetic(planet).hatType}</span>\n        </div>\n        <div>\n          <Sub>HAT Level</Sub>\n          <span>{getHatSizeName(planet)}</span>\n        </div>\n        <div className='margin-top'>\n          <Sub>Next Level Cost</Sub>\n          <span>\n            {getHatCostEth(planet)} USD <Sub>/</Sub> {getHatCostEth(planet)} DAI\n          </span>\n        </div>\n        <div>\n          <Sub>Current Balance</Sub>\n          <span>{balanceEth} xDAI</span>\n        </div>\n\n        <EmSpacer height={1} />\n        <Link to={'https://blog.zkga.me/df-04-faq'}>Get More xDai</Link>\n        <EmSpacer height={0.5} />\n\n        <Btn\n          onClick={() => {\n            if (!enabled(planet) || !uiManager || !planet) return;\n            uiManager.buyHat(planet);\n          }}\n          disabled={!enabled(planet)}\n        >\n          {planet && planet.hatLevel > 0 ? 'Upgrade' : 'Buy'} HAT\n        </Btn>\n      </StyledHatPane>\n    );\n  } else {\n    return (\n      <CenterBackgroundSubtext width='100%' height='75px'>\n        Select a Planet <br /> You Own\n      </CenterBackgroundSubtext>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Panes/HelpPane.tsx",
    "content": "import { ArtifactRarity, ModalName, PlanetLevel } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { EmSpacer, Link, Section, SectionHeader } from '../Components/CoreUI';\nimport { ArtifactRarityLabel } from '../Components/Labels/ArtifactLabels';\nimport { Gold, White } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\n\nconst HelpContent = styled.div`\n  width: 500px;\n  height: 500px;\n  max-height: 500px;\n  max-width: 500px;\n  overflow-y: scroll;\n  text-align: justify;\n  color: ${dfstyles.colors.text};\n`;\n\nexport function HelpPane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n\n  const silverScoreValue = uiManager.getSilverScoreValue();\n  const artifactPointValues = uiManager.getArtifactPointValues();\n  const captureZonePointValues = uiManager.getCaptureZonePointValues();\n\n  return (\n    <ModalPane id={ModalName.Help} title='Help' visible={visible} onClose={onClose}>\n      <HelpContent>\n        {uiManager.isRoundOver() && (\n          <Section>\n            <SectionHeader>Round 5 Complete</SectionHeader>\n            Dark Forest v0.6 Round 5 is now complete! Scores are being compiled and winners will be\n            announced shortly. Also, Artifacts will no longer be mintable. Thanks for playing!\n          </Section>\n        )}\n\n        <Section>\n          <SectionHeader>Firstly, Some Links:</SectionHeader>\n          <Link to='https://blog.zkga.me'>Official Info and Announcements</Link>\n          <br />\n          <Link to='https://twitter.com/darkforest_eth'>Official Twitter</Link>\n          <br />\n          <Link to='https://discord.gg/2u2TN6v8r6'>Official Discord Server</Link>\n          <br />\n          <Link to='https://dfwiki.net/'>Community-Run Wiki</Link>\n          <br />\n          <br />\n          Secondly... welcome to\n        </Section>\n\n        <Section>\n          <SectionHeader>Dark Forest v0.6 R5: The Junk Wars</SectionHeader>\n          Dark Forest is a vast universe, obfuscated by zero-knowledge cryptography. Your{' '}\n          <White>explorer</White> (bottom left) explores the universe, searching for{' '}\n          <White>Planets</White> and other players.\n          <EmSpacer height={1} />\n          All planets produce <White>Energy</White>. You can click-drag to move energy from planets\n          you own to new planets to conquer them.\n          <EmSpacer height={1} />\n          Also scattered through the universe are <White>Asteroid Fields</White>, which produce{' '}\n          <White>Silver</White>. Silver can be sent to planets and can be spent on{' '}\n          <White>Upgrades</White>.\n          <EmSpacer height={1} /> Some planets contain <White>Artifacts</White> - ERC721 tokens that\n          can be traded with other players. Artifacts can be harvested and deposited onto planets,\n          buffing their stats.\n        </Section>\n\n        <Section>\n          <SectionHeader>Prizes and Scoring</SectionHeader>A snapshot of scores will be taken on{' '}\n          <White>February 28, 2022</White> at 9PM Pacific Time. At that time, the top 63\n          highest-scoring players will be awarded prizes from a pool 63 prize planets. You can see\n          the current rankings by scrolling down on the landing page of the game.\n          <EmSpacer height={1} />\n          Scoring this round is made up of three parts: finding artifacts using you Gear ship,\n          withdrawing silver from Spacetime Rips, and invading and capturing planets inside of\n          Capture Zones. For more information about capture zones, hover over the 'Capture Zones'\n          sections at the top of the screen.\n          <EmSpacer height={1} />\n          The values for each scoring type are provided below:\n        </Section>\n\n        <Section>\n          <SectionHeader>Scoring values</SectionHeader>\n          Each single <Gold>silver</Gold> you withdraw increases your score by{' '}\n          {silverScoreValue / 100}.\n          <EmSpacer height={1} />\n          Discovering an artifact increases your score based on its rarity:\n          <br />\n          <ArtifactRarityLabel rarity={ArtifactRarity.Common} />:{' '}\n          {artifactPointValues[ArtifactRarity.Common]}\n          <br />\n          <ArtifactRarityLabel rarity={ArtifactRarity.Rare} />:{' '}\n          {artifactPointValues[ArtifactRarity.Rare]}\n          <br />\n          <ArtifactRarityLabel rarity={ArtifactRarity.Epic} />:{' '}\n          {artifactPointValues[ArtifactRarity.Epic]}\n          <br />\n          <ArtifactRarityLabel rarity={ArtifactRarity.Legendary} />:{' '}\n          {artifactPointValues[ArtifactRarity.Legendary]}\n          <br />\n          <ArtifactRarityLabel rarity={ArtifactRarity.Mythic} />:{' '}\n          {artifactPointValues[ArtifactRarity.Mythic]}\n          <EmSpacer height={1} />\n          Capturing an invaded planet increases your score based on its level:\n          <br />\n          Level {PlanetLevel.ZERO}: {captureZonePointValues[PlanetLevel.ZERO]}\n          <br />\n          Level {PlanetLevel.ONE}: {captureZonePointValues[PlanetLevel.ONE]}\n          <br />\n          Level {PlanetLevel.TWO}: {captureZonePointValues[PlanetLevel.TWO]}\n          <br />\n          Level {PlanetLevel.THREE}: {captureZonePointValues[PlanetLevel.THREE]}\n          <br />\n          Level {PlanetLevel.FOUR}: {captureZonePointValues[PlanetLevel.FOUR]}\n          <br />\n          Level {PlanetLevel.FIVE}: {captureZonePointValues[PlanetLevel.FIVE]}\n          <br />\n          Level {PlanetLevel.SIX}: {captureZonePointValues[PlanetLevel.SIX]}\n          <br />\n          Level {PlanetLevel.SEVEN}: {captureZonePointValues[PlanetLevel.SEVEN]}\n          <br />\n          Level {PlanetLevel.EIGHT}: {captureZonePointValues[PlanetLevel.EIGHT]}\n          <br />\n          Level {PlanetLevel.NINE}: {captureZonePointValues[PlanetLevel.NINE]}\n        </Section>\n      </HelpContent>\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/HoverPane.tsx",
    "content": "import React, { useLayoutEffect, useRef } from 'react';\nimport styled from 'styled-components';\nimport { snips } from '../Styles/dfstyles';\nimport { DFZIndex } from '../Utils/constants';\n\nconst StyledHoverPane = styled.div`\n  ${snips.absoluteTopLeft}\n  ${snips.defaultBackground}\n  ${snips.roundedBordersWithEdge}\n  width: 350px;\n`;\n\n/**\n * This is the pane that is rendered when you hover over a planet.\n */\nexport function HoverPane({\n  style,\n  visible,\n  element,\n}: {\n  style?: React.CSSProperties;\n  visible: boolean;\n  element: React.ReactChild;\n}) {\n  const paneRef = useRef<HTMLDivElement>(null);\n\n  useLayoutEffect(() => {\n    if (!paneRef.current) return;\n\n    let leftOffset;\n    let topOffset;\n\n    const doMouseMove = (e: MouseEvent) => {\n      if (!paneRef.current) return;\n\n      const width = paneRef.current.offsetWidth;\n      const height = paneRef.current.offsetHeight;\n\n      if (e.clientX < window.innerWidth / 2) leftOffset = 10;\n      else leftOffset = -10 - width;\n\n      if (e.clientY < window.innerHeight / 2) topOffset = 10;\n      else topOffset = -10 - height;\n\n      paneRef.current.style.top = e.clientY + topOffset + 'px';\n      paneRef.current.style.left = e.clientX + leftOffset + 'px';\n    };\n\n    window.addEventListener('mousemove', doMouseMove);\n\n    return () => window.removeEventListener('mousemove', doMouseMove);\n  }, [paneRef]);\n\n  return (\n    <StyledHoverPane\n      ref={paneRef}\n      style={{ display: visible ? undefined : 'none', zIndex: DFZIndex.Tooltip, ...style }}\n    >\n      {element}\n    </StyledHoverPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/HoverPlanetPane.tsx",
    "content": "import React, { useEffect, useMemo, useState } from 'react';\nimport { snips } from '../Styles/dfstyles';\nimport { useHoverPlanet, useSelectedPlanet, useUIManager } from '../Utils/AppHooks';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport { PlanetCard } from '../Views/PlanetCard';\nimport { HoverPane } from './HoverPane';\n\n/**\n * This is the pane that is rendered when you hover over a planet.\n */\nexport function HoverPlanetPane() {\n  const uiManager = useUIManager();\n  const hoverWrapper = useHoverPlanet(uiManager);\n  const hovering = hoverWrapper.value;\n  const selected = useSelectedPlanet(uiManager).value;\n\n  /* really bad way to do this but it works for now */\n  const [sending, setSending] = useState<boolean>(false);\n\n  useEffect(() => {\n    const uiEmitter = UIEmitter.getInstance();\n    const setSendTrue = () => setSending(true);\n    const setSendFalse = () => setSending(false);\n\n    uiEmitter.addListener(UIEmitterEvent.SendCancelled, setSendFalse);\n    uiEmitter.addListener(UIEmitterEvent.SendInitiated, setSendTrue);\n    uiEmitter.addListener(UIEmitterEvent.SendCompleted, setSendFalse);\n\n    return () => {\n      uiEmitter.removeListener(UIEmitterEvent.SendCancelled, setSendFalse);\n      uiEmitter.removeListener(UIEmitterEvent.SendInitiated, setSendTrue);\n      uiEmitter.removeListener(UIEmitterEvent.SendCompleted, setSendFalse);\n    };\n  }, []);\n\n  const visible = useMemo(\n    () =>\n      !!hovering &&\n      (hovering?.locationId !== selected?.locationId || !uiManager.getPlanetHoveringInRenderer()) &&\n      !sending &&\n      !uiManager.getMouseDownCoords(),\n    [hovering, selected, sending, uiManager]\n  );\n\n  return (\n    <HoverPane\n      style={hoverWrapper.value?.destroyed ? snips.destroyedBackground : undefined}\n      visible={visible}\n      element={<PlanetCard standalone planetWrapper={hoverWrapper} />}\n    />\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/AdminPermissionsPane.tsx",
    "content": "import React from 'react';\nimport { Checkbox, DarkForestCheckbox } from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function AdminPermissionsPane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='Admin can add planets?'\n          checked={config.ADMIN_CAN_ADD_PLANETS.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n            onUpdate({ type: 'ADMIN_CAN_ADD_PLANETS', value: e.target.checked })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.ADMIN_CAN_ADD_PLANETS.warning}</Warning>\n      </Row>\n      <Row>\n        <Checkbox\n          label='Is whitelist enabled?'\n          checked={config.WHITELIST_ENABLED.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n            onUpdate({ type: 'WHITELIST_ENABLED', value: e.target.checked })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.WHITELIST_ENABLED.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/ArtifactSettingsPane.tsx",
    "content": "import { ArtifactRarity } from '@darkforest_eth/types';\nimport React from 'react';\nimport { DarkForestNumberInput, NumberInput } from '../../Components/Input';\nimport { ArtifactRarityLabel } from '../../Components/Labels/ArtifactLabels';\nimport { Row } from '../../Components/Row';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nfunction ArtifactPointsPerRarity({\n  value,\n  index,\n  onUpdate,\n}: LobbiesPaneProps & { value: number | undefined; index: number }) {\n  // We can skip Unknown\n  if (index === 0) {\n    return null;\n  }\n\n  return (\n    <div>\n      {/* TODO: We should have a utility that converts an integer into an ArtifactRarity safely  */}\n      <ArtifactRarityLabel rarity={index as ArtifactRarity} />\n      <NumberInput\n        format='integer'\n        value={value}\n        onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n          onUpdate({ type: 'ARTIFACT_POINT_VALUES', value: e.target.value, index });\n        }}\n      />\n    </div>\n  );\n}\n\nconst pointsRowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties;\n\nexport function ArtifactSettingsPane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <span>Photoid Cannon activation delay (in seconds)</span>\n        <NumberInput\n          value={config.PHOTOID_ACTIVATION_DELAY.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'PHOTOID_ACTIVATION_DELAY', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PHOTOID_ACTIVATION_DELAY.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Artifact point values by rarity</span>\n      </Row>\n      <Row style={pointsRowStyle}>\n        {(config.ARTIFACT_POINT_VALUES.displayValue ?? []).map((displayValue, idx) => (\n          <ArtifactPointsPerRarity\n            key={`artifact-points-row-${idx}`}\n            config={config}\n            value={displayValue}\n            index={idx}\n            onUpdate={onUpdate}\n          />\n        ))}\n      </Row>\n      <Row>\n        <Warning>{config.ARTIFACT_POINT_VALUES.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/CaptureZonesPane.tsx",
    "content": "import _ from 'lodash';\nimport React from 'react';\nimport {\n  Checkbox,\n  DarkForestCheckbox,\n  DarkForestNumberInput,\n  NumberInput,\n} from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { DarkForestSlider, Slider } from '../../Components/Slider';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nfunction CaptureZoneScorePerLevel({\n  value,\n  index,\n  onUpdate,\n}: LobbiesPaneProps & { value: number | undefined; index: number }) {\n  return (\n    <div>\n      <span>Level {index}</span>\n      <NumberInput\n        format='integer'\n        value={value}\n        onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n          onUpdate({ type: 'CAPTURE_ZONE_PLANET_LEVEL_SCORE', value: e.target.value, index });\n        }}\n      />\n    </div>\n  );\n}\n\nconst rowChunkSize = 5;\nconst rowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties;\n\nexport function CaptureZonesPane({ config, onUpdate }: LobbiesPaneProps) {\n  let captureZoneOptions = null;\n  if (config.CAPTURE_ZONES_ENABLED.currentValue === true) {\n    const scores = _.chunk(config.CAPTURE_ZONE_PLANET_LEVEL_SCORE.displayValue, rowChunkSize).map(\n      (items, rowIdx) => {\n        return (\n          <Row key={`score-row-${rowIdx}`} style={rowStyle}>\n            {items.map((displayValue, idx) => (\n              <CaptureZoneScorePerLevel\n                key={`score-lvl-${idx}`}\n                config={config}\n                value={displayValue}\n                index={rowIdx * rowChunkSize + idx}\n                onUpdate={onUpdate}\n              />\n            ))}\n          </Row>\n        );\n      }\n    );\n\n    captureZoneOptions = (\n      <>\n        <Row>\n          <span>Radius of each Capture Zone</span>\n          <NumberInput\n            format='integer'\n            value={config.CAPTURE_ZONE_RADIUS.displayValue}\n            onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n              onUpdate({ type: 'CAPTURE_ZONE_RADIUS', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Slider\n            label='Amount of Capture Zones within each 5000 World Radius'\n            variant='filled'\n            min={1}\n            max={10}\n            value={config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS.displayValue}\n            step={1}\n            onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) => {\n              onUpdate({ type: 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.CAPTURE_ZONES_PER_5000_WORLD_RADIUS.warning}</Warning>\n        </Row>\n        <Row>\n          <Slider\n            label='Change Capture Zones every X blocks?'\n            variant='filled'\n            min={1}\n            max={255}\n            value={config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL.displayValue}\n            step={1}\n            formatOptions={{ style: 'unit', unit: ' blocks' }}\n            onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) => {\n              onUpdate({ type: 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL.warning}</Warning>\n        </Row>\n        <Row>\n          <span>Number of blocks between Invade & Capture</span>\n          <NumberInput\n            format='integer'\n            value={config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED.displayValue}\n            onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n              onUpdate({ type: 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED.warning}</Warning>\n        </Row>\n        {scores}\n        <Row>\n          <Warning>{config.CAPTURE_ZONE_PLANET_LEVEL_SCORE.warning}</Warning>\n        </Row>\n      </>\n    );\n  }\n\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='Capture zones enabled?'\n          checked={config.CAPTURE_ZONES_ENABLED.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) => {\n            onUpdate({ type: 'CAPTURE_ZONES_ENABLED', value: e.target.checked });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.CAPTURE_ZONES_ENABLED.warning}</Warning>\n      </Row>\n      {captureZoneOptions}\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/ConfigurationPane.tsx",
    "content": "import { EthAddress } from '@darkforest_eth/types';\nimport _ from 'lodash';\nimport React, { useEffect, useReducer, useState } from 'react';\nimport { Route, Switch, useRouteMatch } from 'react-router-dom';\nimport { Btn } from '../../Components/Btn';\nimport { Spacer, Title } from '../../Components/CoreUI';\nimport { MythicLabelText } from '../../Components/Labels/MythicLabel';\nimport { LoadingSpinner } from '../../Components/LoadingSpinner';\nimport { Modal } from '../../Components/Modal';\nimport { Row } from '../../Components/Row';\nimport { TextPreview } from '../../Components/TextPreview';\nimport { AdminPermissionsPane } from './AdminPermissionsPane';\nimport { ArtifactSettingsPane } from './ArtifactSettingsPane';\nimport { CaptureZonesPane } from './CaptureZonesPane';\nimport { GameSettingsPane } from './GameSettingsPane';\nimport {\n  ButtonRow,\n  ConfigDownload,\n  ConfigUpload,\n  LinkButton,\n  LobbiesPaneProps,\n  NavigationTitle,\n  Warning,\n} from './LobbiesUtils';\nimport { MinimapConfig } from './MinimapUtils';\nimport { PlanetPane } from './PlanetPane';\nimport { PlayerSpawnPane } from './PlayerSpawnPane';\nimport {\n  InvalidConfigError,\n  LobbyConfigAction,\n  lobbyConfigInit,\n  lobbyConfigReducer,\n  LobbyInitializers,\n  toInitializers,\n} from './Reducer';\nimport { SnarkPane } from './SnarkPane';\nimport { SpaceJunkPane } from './SpaceJunkPane';\nimport { SpaceTypeBiomePane } from './SpaceTypeBiomePane';\nimport { WorldSizePane } from './WorldSizePane';\n\ninterface PaneConfig {\n  title: string;\n  shortcut: string;\n  path: string;\n  Pane: (props: LobbiesPaneProps) => JSX.Element;\n}\n\nconst panes: ReadonlyArray<PaneConfig> = [\n  {\n    title: 'Game settings',\n    shortcut: `1`,\n    path: '/settings/game',\n    Pane: (props: LobbiesPaneProps) => <GameSettingsPane {...props} />,\n  },\n  {\n    title: 'World size',\n    shortcut: `2`,\n    path: '/settings/world',\n    Pane: (props: LobbiesPaneProps) => <WorldSizePane {...props} />,\n  },\n  {\n    title: 'Space type & Biome',\n    shortcut: `3`,\n    path: '/settings/space',\n    Pane: (props: LobbiesPaneProps) => <SpaceTypeBiomePane {...props} />,\n  },\n  {\n    title: 'Planets',\n    shortcut: `4`,\n    path: '/settings/planet',\n    Pane: (props: LobbiesPaneProps) => <PlanetPane {...props} />,\n  },\n  {\n    title: 'Player spawn',\n    shortcut: `5`,\n    path: '/settings/spawn',\n    Pane: (props: LobbiesPaneProps) => <PlayerSpawnPane {...props} />,\n  },\n  {\n    title: 'Space junk',\n    shortcut: `6`,\n    path: '/settings/junk',\n    Pane: (props: LobbiesPaneProps) => <SpaceJunkPane {...props} />,\n  },\n  {\n    title: 'Capture zones',\n    shortcut: `7`,\n    path: '/settings/zones',\n    Pane: (props: LobbiesPaneProps) => <CaptureZonesPane {...props} />,\n  },\n  {\n    title: 'Artifacts',\n    shortcut: `8`,\n    path: '/settings/artifact',\n    Pane: (props: LobbiesPaneProps) => <ArtifactSettingsPane {...props} />,\n  },\n  {\n    title: 'Admin permissions',\n    shortcut: `9`,\n    path: '/settings/admin',\n    Pane: (props: LobbiesPaneProps) => <AdminPermissionsPane {...props} />,\n  },\n  {\n    title: 'Advanced: Snarks',\n    shortcut: `0`,\n    path: '/settings/snark',\n    Pane: (props: LobbiesPaneProps) => <SnarkPane {...props} />,\n  },\n] as const;\n\ntype Status = 'creating' | 'created' | 'errored' | undefined;\n\nfunction ConfigurationNavigation({\n  error,\n  lobbyAddress,\n  status,\n  onCreate,\n}: {\n  error: string | undefined;\n  lobbyAddress: EthAddress | undefined;\n  status: Status;\n  onCreate: () => Promise<void>;\n}) {\n  const buttons = _.chunk(panes, 2).map(([fst, snd], idx) => {\n    return (\n      // Index key is fine here because the array is stable\n      <ButtonRow key={idx}>\n        {fst && (\n          <LinkButton to={fst.path} shortcut={fst.shortcut}>\n            {fst.title}\n          </LinkButton>\n        )}\n        {snd && (\n          <LinkButton to={snd.path} shortcut={snd.shortcut}>\n            {snd.title}\n          </LinkButton>\n        )}\n      </ButtonRow>\n    );\n  });\n\n  const url =\n    process.env.NODE_ENV === 'production'\n      ? `${window.DEPLOY_URL}/play/${lobbyAddress}`\n      : `${window.location.origin}/play/${lobbyAddress}`;\n\n  let lobbyContent;\n  if (status === 'created' && lobbyAddress) {\n    lobbyContent = (\n      <>\n        <Btn size='stretch' onClick={() => window.open(url)}>\n          Launch Lobby\n        </Btn>\n        <Row>\n          {/* Stealing MythicLabelText because it accepts variable text input */}\n          <MythicLabelText style={{ margin: 'auto' }} text='Your lobby is ready!' />\n        </Row>\n        <Row>\n          <span style={{ margin: 'auto' }}>\n            You can also share the direct url with your friends:\n          </span>\n        </Row>\n        {/* Didn't like the TextPreview jumping, so I'm setting the height */}\n        <Row style={{ height: '30px' } as CSSStyleDeclaration & React.CSSProperties}>\n          <TextPreview\n            style={{ margin: 'auto' }}\n            text={url}\n            unFocusedWidth='50%'\n            focusedWidth='100%'\n          />\n        </Row>\n      </>\n    );\n  }\n\n  const createDisabled = status === 'creating' || status === 'created';\n  const creating = status === 'creating' || (status === 'created' && !lobbyAddress);\n\n  return (\n    <>\n      <Title slot='title'>Customize Lobby</Title>\n      <div>\n        Welcome Cadet! You can launch a copy of Dark Forest from this UI. We call this a Lobby.\n        <Spacer height={12} />\n        All settings will be defaulted to the same configuration of the main contract you are\n        copying. However, you can change any of those settings through the buttons below!\n        <Spacer height={12} />\n      </div>\n      {buttons}\n      <Spacer height={20} />\n      <Btn size='stretch' onClick={onCreate} disabled={createDisabled}>\n        {creating ? <LoadingSpinner initialText='Creating...' /> : 'Create Lobby'}\n      </Btn>\n      <Row>\n        <Warning>{error}</Warning>\n      </Row>\n      {lobbyContent}\n    </>\n  );\n}\n\nexport function ConfigurationPane({\n  modalIndex,\n  lobbyAddress,\n  startingConfig,\n  onMapChange,\n  onCreate,\n}: {\n  modalIndex: number;\n  lobbyAddress: EthAddress | undefined;\n  startingConfig: LobbyInitializers;\n  onMapChange: (props: MinimapConfig) => void;\n  onCreate: (config: LobbyInitializers) => Promise<void>;\n}) {\n  const { path: root } = useRouteMatch();\n  const [error, setError] = useState<string | undefined>();\n  const [status, setStatus] = useState<Status>(undefined);\n  // Separated IO Errors from Download/Upload so they show on any pane of the modal\n  const [ioErr, setIoErr] = useState<string | undefined>();\n\n  const [config, updateConfig] = useReducer(lobbyConfigReducer, startingConfig, lobbyConfigInit);\n\n  function onUpdate(action: LobbyConfigAction) {\n    setError(undefined);\n    setIoErr(undefined);\n    updateConfig(action);\n  }\n\n  // Minimap only changes on a subset of properties, so we only trigger when one of them changes value (and still debounce it)\n  useEffect(() => {\n    onMapChange({\n      worldRadius: config.WORLD_RADIUS_MIN.currentValue,\n      key: config.SPACETYPE_KEY.currentValue,\n      scale: config.PERLIN_LENGTH_SCALE.currentValue,\n      mirrorX: config.PERLIN_MIRROR_X.currentValue,\n      mirrorY: config.PERLIN_MIRROR_Y.currentValue,\n      perlinThreshold1: config.PERLIN_THRESHOLD_1.currentValue,\n      perlinThreshold2: config.PERLIN_THRESHOLD_2.currentValue,\n      perlinThreshold3: config.PERLIN_THRESHOLD_3.currentValue,\n    });\n  }, [\n    onMapChange,\n    config.WORLD_RADIUS_MIN.currentValue,\n    config.SPACETYPE_KEY.currentValue,\n    config.PERLIN_LENGTH_SCALE.currentValue,\n    config.PERLIN_MIRROR_X.currentValue,\n    config.PERLIN_MIRROR_Y.currentValue,\n    config.PERLIN_THRESHOLD_1.currentValue,\n    config.PERLIN_THRESHOLD_2.currentValue,\n    config.PERLIN_THRESHOLD_3.currentValue,\n  ]);\n\n  const routes = panes.map(({ title, path, Pane }, idx) => {\n    return (\n      // Index key is fine here because the array is stable\n      <Route key={idx} path={`${root}${path}`}>\n        <NavigationTitle>{title}</NavigationTitle>\n        <Pane config={config} onUpdate={onUpdate} />\n      </Route>\n    );\n  });\n\n  async function validateAndCreateLobby() {\n    try {\n      setStatus('creating');\n      const initializers = toInitializers(config);\n      await onCreate(initializers);\n      setStatus('created');\n    } catch (err) {\n      setStatus('errored');\n      console.error(err);\n      if (err instanceof InvalidConfigError) {\n        setError(`Invalid ${err.key} value ${err.value ?? ''} - ${err.message}`);\n      } else {\n        setError(err?.message || 'Something went wrong. Check your dev console.');\n      }\n    }\n  }\n\n  function configUploadSuccess(initializers: LobbyInitializers) {\n    updateConfig({ type: 'RESET', value: lobbyConfigInit(initializers) });\n  }\n\n  return (\n    <Modal width='500px' initialX={100} initialY={100} index={modalIndex}>\n      <Switch>\n        <Route path={root} exact={true}>\n          <ConfigurationNavigation\n            error={error}\n            lobbyAddress={lobbyAddress}\n            status={status}\n            onCreate={validateAndCreateLobby}\n          />\n        </Route>\n        {routes}\n      </Switch>\n      {/* Button this in the title slot but at the end moves it to the end of the title bar */}\n      <ConfigDownload onError={setIoErr} address={lobbyAddress} config={config} />\n      <ConfigUpload onError={setIoErr} onUpload={configUploadSuccess} />\n      <Warning>{ioErr}</Warning>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/GameSettingsPane.tsx",
    "content": "import React from 'react';\nimport {\n  Checkbox,\n  DarkForestCheckbox,\n  DarkForestNumberInput,\n  NumberInput,\n} from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { DarkForestSlider, Slider } from '../../Components/Slider';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function GameSettingsPane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Slider\n          variant='filled'\n          label='Game speed'\n          formatOptions={{ style: 'unit', unit: 'x' }}\n          min={1}\n          max={60}\n          step={1}\n          value={config.TIME_FACTOR_HUNDREDTHS.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) => {\n            onUpdate({ type: 'TIME_FACTOR_HUNDREDTHS', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.TIME_FACTOR_HUNDREDTHS.warning}</Warning>\n      </Row>\n      <Row>\n        <Checkbox\n          label='Planet transfer enabled?'\n          checked={config.PLANET_TRANSFER_ENABLED.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) => {\n            onUpdate({ type: 'PLANET_TRANSFER_ENABLED', value: e.target.checked });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PLANET_TRANSFER_ENABLED.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Location reveal cooldown (in seconds)</span>\n        <NumberInput\n          value={config.LOCATION_REVEAL_COOLDOWN.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'LOCATION_REVEAL_COOLDOWN', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.LOCATION_REVEAL_COOLDOWN.warning}</Warning>\n      </Row>\n      <Row>\n        {/* It is a little weird that this is in Game Settings, but I'd rather keep other scoring grouped */}\n        <span>Amount of points for each silver withdrawn</span>\n        <NumberInput\n          format='float'\n          value={config.SILVER_SCORE_VALUE.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'SILVER_SCORE_VALUE', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.SILVER_SCORE_VALUE.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/LobbiesUtils.tsx",
    "content": "/** This file contains some common utilities used by the Lobbies UI */\nimport { Initializers } from '@darkforest_eth/settings';\nimport { EthAddress } from '@darkforest_eth/types';\nimport React from 'react';\nimport { useHistory, useRouteMatch } from 'react-router-dom';\nimport styled from 'styled-components';\nimport { Btn, ShortcutBtn } from '../../Components/Btn';\nimport { Title } from '../../Components/CoreUI';\nimport { Row } from '../../Components/Row';\nimport { Red } from '../../Components/Text';\nimport { LobbyConfigAction, LobbyConfigState, toInitializers } from './Reducer';\n\nexport interface LobbiesPaneProps {\n  config: LobbyConfigState;\n  onUpdate: (change: LobbyConfigAction) => void;\n}\n\nexport const ButtonRow = styled(Row)`\n  gap: 8px;\n\n  .button {\n    flex: 1 1 50%;\n  }\n`;\n\nexport function LinkButton({\n  to,\n  shortcut,\n  children,\n}: React.PropsWithChildren<{ to: string; shortcut?: string }>) {\n  const { url } = useRouteMatch();\n  const history = useHistory();\n\n  function navigate() {\n    history.push(`${url}${to}`);\n  }\n\n  // Adding className=\"button\" so ButtonRow will add the flex stuff\n  return (\n    <ShortcutBtn\n      className='button'\n      size='stretch'\n      onClick={navigate}\n      onShortcutPressed={navigate}\n      shortcutKey={shortcut}\n      shortcutText={shortcut}\n    >\n      {children}\n    </ShortcutBtn>\n  );\n}\n\nexport function NavigationTitle({ children }: React.PropsWithChildren<unknown>) {\n  const history = useHistory();\n\n  const shortcut = 't';\n\n  function goBack() {\n    history.goBack();\n  }\n\n  return (\n    <>\n      <ShortcutBtn\n        slot='title'\n        size='small'\n        onClick={goBack}\n        onShortcutPressed={goBack}\n        shortcutKey={shortcut}\n        shortcutText={shortcut}\n      >\n        back\n      </ShortcutBtn>\n      <Title slot='title'>{children}</Title>\n    </>\n  );\n}\n\nexport function Warning({ children }: React.PropsWithChildren<unknown>) {\n  if (!children) {\n    return null;\n  } else {\n    return (\n      <div style={{ margin: 'auto', maxWidth: '80%', textAlign: 'center' }}>\n        <Red>Error:</Red> {children}\n      </div>\n    );\n  }\n}\n\nexport function ConfigDownload({\n  onError,\n  address,\n  config,\n}: {\n  onError: (msg: string) => void;\n  address: EthAddress | undefined;\n  config: LobbyConfigState;\n}) {\n  function doDownload() {\n    try {\n      const initializers = toInitializers(config);\n      const blob = new Blob([JSON.stringify(initializers, null, 2)], { type: 'application/json' });\n      const name = address\n        ? `${address.substring(0, 6)}-lobbies-config.json`\n        : 'lobbies-config.json';\n      const blobAsUrl = (window.webkitURL || window.URL).createObjectURL(blob);\n      const anchor = document.createElement('a');\n      anchor.href = blobAsUrl;\n      anchor.download = name;\n      anchor.click();\n    } catch (err) {\n      console.error(err);\n      onError('Unable to download config file');\n    }\n  }\n\n  return (\n    <Btn slot='title' size='small' onClick={doDownload}>\n      Download\n    </Btn>\n  );\n}\n\nexport function ConfigUpload({\n  onError,\n  onUpload,\n}: {\n  onError: (msg: string) => void;\n  onUpload: (initializers: Initializers) => void;\n}) {\n  function doUpload() {\n    const reader = new FileReader();\n    reader.onload = () => {\n      if (typeof reader.result === 'string') {\n        try {\n          onUpload(JSON.parse(reader.result));\n        } catch (err) {\n          onError('Cannot process uploaded JSON');\n        }\n      } else {\n        onError('Could not read uploaded file');\n      }\n    };\n    const inputFile = document.createElement('input');\n    inputFile.type = 'file';\n    inputFile.onchange = () => {\n      try {\n        const file = inputFile.files?.item(0);\n\n        if (file) {\n          reader.readAsText(file);\n        } else {\n          onError('Could not find a file to upload');\n        }\n      } catch (err) {\n        console.error(err);\n        onError('Upload failed');\n      }\n    };\n    inputFile.click();\n  }\n\n  return (\n    <Btn slot='title' size='small' onClick={doUpload}>\n      Upload\n    </Btn>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/MinimapPane.tsx",
    "content": "import React, { useEffect, useMemo, useRef, useState } from 'react';\nimport { LoadingSpinner } from '../../Components/LoadingSpinner';\nimport { Modal } from '../../Components/Modal';\nimport { DrawMessage, MinimapConfig } from './MinimapUtils';\n\nfunction getWorker() {\n  return new Worker(new URL('./minimap.worker.ts', import.meta.url));\n}\n\nfunction drawOnCanvas(canvas: HTMLCanvasElement | null, msg: DrawMessage) {\n  if (!canvas) {\n    console.error(`No canvas to draw to`);\n    return;\n  }\n\n  const ctx = canvas.getContext('2d');\n\n  if (!ctx) {\n    console.error(`Couldn't get the planet context`);\n    return;\n  }\n\n  ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n  const dot = 4;\n  const sizeFactor = 380;\n  const { radius, data } = msg;\n\n  const normalize = (val: number) => {\n    return Math.floor(((val + radius) * sizeFactor) / (radius * 2));\n  };\n\n  // draw mini-map\n\n  for (let i = 0; i < data.length; i++) {\n    if (data[i].type === 0) {\n      ctx.fillStyle = '#186469'; // inner nebula\n    } else if (data[i].type === 1) {\n      ctx.fillStyle = '#24247d'; // outer nebula\n    } else if (data[i].type === 2) {\n      ctx.fillStyle = '#000000'; // deep space\n    } else if (data[i].type === 3) {\n      ctx.fillStyle = '#038700'; // dead space\n    }\n    ctx.fillRect(normalize(data[i].x) + 10, normalize(data[i].y * -1) + 10, dot, dot);\n  }\n\n  // draw extents of map\n\n  const radiusNormalized = normalize(radius) / 2;\n\n  ctx.beginPath();\n  ctx.arc(radiusNormalized + 12, radiusNormalized + 12, radiusNormalized, 0, 2 * Math.PI);\n  ctx.strokeStyle = '#DDDDDD';\n  ctx.lineWidth = 2;\n  ctx.stroke();\n}\n\nexport function Minimap({\n  modalIndex,\n  config,\n}: {\n  modalIndex: number;\n  config: MinimapConfig | undefined;\n}) {\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n  const [refreshing, setRefreshing] = useState(false);\n\n  const worker = useMemo(getWorker, []);\n\n  useEffect(() => {\n    if (config) {\n      setRefreshing(true);\n      worker.postMessage(JSON.stringify(config));\n    }\n  }, [worker, config, setRefreshing]);\n\n  useEffect(() => {\n    function onMessage(e: MessageEvent) {\n      if (e.data) {\n        drawOnCanvas(canvasRef.current, JSON.parse(e.data));\n        setRefreshing(false);\n      }\n    }\n\n    worker.addEventListener('message', onMessage);\n\n    return () => worker.removeEventListener('message', onMessage);\n  }, [worker, setRefreshing]);\n\n  return (\n    <Modal width='416px' initialX={650} initialY={200} index={modalIndex}>\n      <div slot='title'>World Minimap</div>\n      <canvas\n        ref={canvasRef}\n        style={{ width: '400px', height: '400px' }}\n        width='400'\n        height='400'\n      />\n      <div style={{ textAlign: 'center', height: '24px' }}>\n        {refreshing ? <LoadingSpinner initialText='Refreshing...' /> : null}\n      </div>\n    </Modal>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/MinimapUtils.ts",
    "content": "import { SpaceType } from '@darkforest_eth/types';\n\nexport type MinimapConfig = {\n  worldRadius: number;\n  // perlin\n  key: number;\n  scale: number;\n  mirrorX: boolean;\n  mirrorY: boolean;\n  perlinThreshold1: number;\n  perlinThreshold2: number;\n  perlinThreshold3: number;\n};\n\nexport type DrawMessage = {\n  radius: number;\n  data: { x: number; y: number; type: SpaceType }[];\n};\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/PlanetPane.tsx",
    "content": "import _ from 'lodash';\nimport React from 'react';\nimport { DarkForestNumberInput, NumberInput } from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { DarkForestSlider, Slider } from '../../Components/Slider';\nimport { Sub } from '../../Components/Text';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nconst rowChunkSize = 5;\nconst rowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties;\n// Handling the non-input lvl 0 by calculating the items in the row\nconst itemStyle = { flex: `1 1 ${Math.floor(100 / rowChunkSize)}%` };\n\nfunction ThresholdByPlanetLevel({\n  index,\n  value,\n  onUpdate,\n}: LobbiesPaneProps & { value: number | undefined; index: number }) {\n  // The level 0 value can never change\n  if (index === 0) {\n    return (\n      <div style={itemStyle}>\n        <span>Level 0</span>\n        <div>\n          <Sub>{value}</Sub>\n        </div>\n      </div>\n    );\n  } else {\n    return (\n      <div style={itemStyle}>\n        <span>Level {index}</span>\n        <NumberInput\n          format='integer'\n          value={value}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'PLANET_LEVEL_THRESHOLDS', index, value: e.target.value });\n          }}\n        />\n      </div>\n    );\n  }\n}\n\nexport function PlanetPane({ config, onUpdate }: LobbiesPaneProps) {\n  let planetLevelThresholds = null;\n  if (config.PLANET_LEVEL_THRESHOLDS.displayValue) {\n    planetLevelThresholds = _.chunk(config.PLANET_LEVEL_THRESHOLDS.displayValue, rowChunkSize).map(\n      (items, rowIdx) => {\n        return (\n          <Row key={`threshold-row-${rowIdx}`} style={rowStyle}>\n            {items.map((displayValue, idx) => (\n              <ThresholdByPlanetLevel\n                key={`threshold-lvl-${idx}`}\n                config={config}\n                value={displayValue}\n                index={rowIdx * rowChunkSize + idx}\n                onUpdate={onUpdate}\n              />\n            ))}\n          </Row>\n        );\n      }\n    );\n  }\n\n  return (\n    <>\n      <Row>\n        <span>Planet rarity:</span>\n        <NumberInput\n          value={config.PLANET_RARITY.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'PLANET_RARITY', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PLANET_RARITY.warning}</Warning>\n      </Row>\n      <Row>\n        <Slider\n          label='Maximum natural planet level'\n          variant='filled'\n          min={0}\n          max={9}\n          value={config.MAX_NATURAL_PLANET_LEVEL.displayValue}\n          step={1}\n          onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) =>\n            onUpdate({ type: 'MAX_NATURAL_PLANET_LEVEL', value: e.target.value })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.MAX_NATURAL_PLANET_LEVEL.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Advanced: Frequency of planet levels</span>\n      </Row>\n      {planetLevelThresholds}\n      <Row>\n        <Warning>{config.PLANET_LEVEL_THRESHOLDS.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/PlayerSpawnPane.tsx",
    "content": "import React from 'react';\nimport { DarkForestNumberInput, NumberInput } from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { DarkForestSliderHandle, Slider, SliderHandle } from '../../Components/Slider';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function PlayerSpawnPane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Slider min={0} max={32} step={1} variant='range' label='Spawn perlin range'>\n          <SliderHandle\n            slot='handle'\n            name='perlin-min'\n            label='Perlin minimum'\n            value={config.INIT_PERLIN_MIN.displayValue}\n            step={1}\n            max='next'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'INIT_PERLIN_MIN', value: e.target.value });\n            }}\n          />\n          <SliderHandle\n            slot='handle'\n            name='perlin-max'\n            label='Perlin maximum'\n            value={config.INIT_PERLIN_MAX.displayValue}\n            step={1}\n            min='previous'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'INIT_PERLIN_MAX', value: e.target.value });\n            }}\n          />\n        </Slider>\n      </Row>\n      <Row>\n        <Warning>{config.INIT_PERLIN_MIN.warning || config.INIT_PERLIN_MAX.warning}</Warning>\n      </Row>\n      <Row>\n        {/* TODO: Explain this better in Help content */}\n        <span>Equivalent spawnable radius</span>\n        <NumberInput\n          value={config.SPAWN_RIM_AREA.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'SPAWN_RIM_AREA', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.SPAWN_RIM_AREA.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/Reducer.ts",
    "content": "import { Initializers } from '@darkforest_eth/settings';\n\nexport const SAFE_UPPER_BOUNDS = Number.MAX_SAFE_INTEGER - 1;\n\nexport class InvalidConfigError extends Error {\n  key: string;\n  value: unknown | undefined;\n\n  constructor(msg: string, key: string, value: unknown) {\n    super(msg);\n\n    this.key = key;\n    // Handling number and strings - if we add an object value, this will assign it\n    if (!Array.isArray(value)) {\n      this.value = value;\n    }\n  }\n}\n\n// Throws an error if any warnings exist\nexport function toInitializers(obj: LobbyConfigState) {\n  const filtered: Record<string, unknown> = {};\n  for (const [key, { currentValue, displayValue, warning }] of Object.entries(obj)) {\n    if (warning) {\n      // displayValue is the invalid value, so we use that as the \"value\"\n      throw new InvalidConfigError(warning, key, displayValue);\n    }\n    filtered[key] = currentValue;\n  }\n  return filtered as LobbyInitializers;\n}\n\n// Actions aren't 1-to-1 with Initializers because we sometimes need to update into arrays\nexport type LobbyConfigAction =\n  | { type: 'START_PAUSED'; value: Initializers['START_PAUSED'] | undefined }\n  | { type: 'ADMIN_CAN_ADD_PLANETS'; value: Initializers['ADMIN_CAN_ADD_PLANETS'] | undefined }\n  | {\n      type: 'TOKEN_MINT_END_TIMESTAMP';\n      value: Initializers['TOKEN_MINT_END_TIMESTAMP'] | undefined;\n    }\n  | { type: 'WORLD_RADIUS_LOCKED'; value: Initializers['WORLD_RADIUS_LOCKED'] | undefined }\n  | { type: 'WORLD_RADIUS_MIN'; value: Initializers['WORLD_RADIUS_MIN'] | undefined }\n  | { type: 'DISABLE_ZK_CHECKS'; value: Initializers['DISABLE_ZK_CHECKS'] | undefined }\n  | { type: 'PLANETHASH_KEY'; value: Initializers['PLANETHASH_KEY'] | undefined }\n  | { type: 'SPACETYPE_KEY'; value: Initializers['SPACETYPE_KEY'] | undefined }\n  | { type: 'BIOMEBASE_KEY'; value: Initializers['BIOMEBASE_KEY'] | undefined }\n  | { type: 'PERLIN_MIRROR_X'; value: Initializers['PERLIN_MIRROR_X'] | undefined }\n  | { type: 'PERLIN_MIRROR_Y'; value: Initializers['PERLIN_MIRROR_Y'] | undefined }\n  | { type: 'PERLIN_LENGTH_SCALE'; value: Initializers['PERLIN_LENGTH_SCALE'] | undefined }\n  | {\n      type: 'MAX_NATURAL_PLANET_LEVEL';\n      value: Initializers['MAX_NATURAL_PLANET_LEVEL'] | undefined;\n    }\n  | { type: 'TIME_FACTOR_HUNDREDTHS'; value: Initializers['TIME_FACTOR_HUNDREDTHS'] | undefined }\n  | { type: 'PERLIN_THRESHOLD_1'; value: Initializers['PERLIN_THRESHOLD_1'] | undefined }\n  | { type: 'PERLIN_THRESHOLD_2'; value: Initializers['PERLIN_THRESHOLD_2'] | undefined }\n  | { type: 'PERLIN_THRESHOLD_3'; value: Initializers['PERLIN_THRESHOLD_3'] | undefined }\n  | { type: 'INIT_PERLIN_MIN'; value: Initializers['INIT_PERLIN_MIN'] | undefined }\n  | { type: 'INIT_PERLIN_MAX'; value: Initializers['INIT_PERLIN_MAX'] | undefined }\n  | { type: 'BIOME_THRESHOLD_1'; value: Initializers['BIOME_THRESHOLD_1'] | undefined }\n  | { type: 'BIOME_THRESHOLD_2'; value: Initializers['BIOME_THRESHOLD_2'] | undefined }\n  | { type: 'PLANET_LEVEL_THRESHOLDS'; value: number | undefined; index: number }\n  | { type: 'PLANET_RARITY'; value: Initializers['PLANET_RARITY'] | undefined }\n  | { type: 'PLANET_TRANSFER_ENABLED'; value: Initializers['PLANET_TRANSFER_ENABLED'] | undefined }\n  | {\n      type: 'PHOTOID_ACTIVATION_DELAY';\n      value: Initializers['PHOTOID_ACTIVATION_DELAY'] | undefined;\n    }\n  | { type: 'SPAWN_RIM_AREA'; value: Initializers['SPAWN_RIM_AREA'] | undefined }\n  | {\n      type: 'LOCATION_REVEAL_COOLDOWN';\n      value: Initializers['LOCATION_REVEAL_COOLDOWN'] | undefined;\n    }\n  // TODO(#2134): Add to UI when this scoring functionality is re-enabled\n  // | { type: 'CLAIM_PLANET_COOLDOWN'; value: Initializers['CLAIM_PLANET_COOLDOWN'] | undefined }\n  | { type: 'PLANET_TYPE_WEIGHTS'; value: Initializers['PLANET_TYPE_WEIGHTS'] | undefined }\n  | { type: 'SILVER_SCORE_VALUE'; value: Initializers['SILVER_SCORE_VALUE'] | undefined }\n  | {\n      type: 'ARTIFACT_POINT_VALUES';\n      value: number | undefined;\n      index: number;\n    }\n  | { type: 'SPACE_JUNK_ENABLED'; value: Initializers['SPACE_JUNK_ENABLED'] | undefined }\n  | { type: 'SPACE_JUNK_LIMIT'; value: Initializers['SPACE_JUNK_LIMIT'] | undefined }\n  | { type: 'PLANET_LEVEL_JUNK'; index: number; value: number | undefined }\n  | {\n      type: 'ABANDON_SPEED_CHANGE_PERCENT';\n      value: Initializers['ABANDON_SPEED_CHANGE_PERCENT'] | undefined;\n    }\n  | {\n      type: 'ABANDON_RANGE_CHANGE_PERCENT';\n      value: Initializers['ABANDON_RANGE_CHANGE_PERCENT'] | undefined;\n    }\n  | { type: 'CAPTURE_ZONES_ENABLED'; value: Initializers['CAPTURE_ZONES_ENABLED'] | undefined }\n  // TODO(#2299): Add to UI when this functionality is implemented\n  // | { type: 'CAPTURE_ZONE_COUNT'; value: Initializers['CAPTURE_ZONE_COUNT'] | undefined }\n  | {\n      type: 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL';\n      value: Initializers['CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL'] | undefined;\n    }\n  | { type: 'CAPTURE_ZONE_RADIUS'; value: Initializers['CAPTURE_ZONE_RADIUS'] | undefined }\n  | {\n      type: 'CAPTURE_ZONE_PLANET_LEVEL_SCORE';\n      value: number | undefined;\n      index: number;\n    }\n  | {\n      type: 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED';\n      value: Initializers['CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED'] | undefined;\n    }\n  | {\n      type: 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS';\n      value: Initializers['CAPTURE_ZONES_PER_5000_WORLD_RADIUS'] | undefined;\n    }\n  | { type: 'WHITELIST_ENABLED'; value: boolean | undefined };\n\n// TODO(#2328): WHITELIST_ENABLED should just be on Initializers\nexport type LobbyInitializers = Initializers & { WHITELIST_ENABLED: boolean | undefined };\n\nexport type LobbyConfigState = {\n  [key in keyof LobbyInitializers]: {\n    currentValue: LobbyInitializers[key];\n    displayValue: Partial<LobbyInitializers[key]> | undefined;\n    defaultValue: LobbyInitializers[key];\n    warning: string | undefined;\n  };\n};\n\nexport type LobbyAction = { type: 'RESET'; value: LobbyConfigState } | LobbyConfigAction;\n\nexport function lobbyConfigReducer(state: LobbyConfigState, action: LobbyAction) {\n  let update;\n  switch (action.type) {\n    case 'START_PAUSED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'ADMIN_CAN_ADD_PLANETS': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'TOKEN_MINT_END_TIMESTAMP': {\n      // TODO: Date\n      update = ofAny(action, state);\n      break;\n    }\n    case 'WORLD_RADIUS_LOCKED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'WORLD_RADIUS_MIN': {\n      update = ofWorldRadiusMin(action, state);\n      break;\n    }\n    case 'DISABLE_ZK_CHECKS': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'PLANETHASH_KEY': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'SPACETYPE_KEY': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'BIOMEBASE_KEY': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'PERLIN_MIRROR_X': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'PERLIN_MIRROR_Y': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'PERLIN_LENGTH_SCALE': {\n      update = ofPerlinLengthScale(action, state);\n      break;\n    }\n    case 'MAX_NATURAL_PLANET_LEVEL': {\n      update = ofMaxNaturalPlanetLevel(action, state);\n      break;\n    }\n    case 'TIME_FACTOR_HUNDREDTHS': {\n      update = ofTimeFactorHundredths(action, state);\n      break;\n    }\n    case 'PERLIN_THRESHOLD_1': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'PERLIN_THRESHOLD_2': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'PERLIN_THRESHOLD_3': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'INIT_PERLIN_MIN': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'INIT_PERLIN_MAX': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'BIOME_THRESHOLD_1': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'BIOME_THRESHOLD_2': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'PLANET_LEVEL_THRESHOLDS': {\n      update = ofPlanetLevelThresholds(action, state);\n      break;\n    }\n    case 'PLANET_RARITY': {\n      update = ofPlanetRarity(action, state);\n      break;\n    }\n    case 'PLANET_TRANSFER_ENABLED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'PHOTOID_ACTIVATION_DELAY': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'SPAWN_RIM_AREA': {\n      update = ofSpawnRimArea(action, state);\n      break;\n    }\n    case 'LOCATION_REVEAL_COOLDOWN': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    // TODO(#2134): Add to UI when this scoring functionality is re-enabled\n    // case 'CLAIM_PLANET_COOLDOWN': {\n    //   update = ofPositiveInteger(action, state);\n    //   break;\n    // }\n    case 'PLANET_TYPE_WEIGHTS': {\n      // TODO: Add this\n      update = ofNoop(action, state);\n      break;\n    }\n    case 'SILVER_SCORE_VALUE': {\n      update = ofPositivePercent(action, state);\n      break;\n    }\n    case 'ARTIFACT_POINT_VALUES': {\n      update = ofArtifactPointValues(action, state);\n      break;\n    }\n    case 'SPACE_JUNK_ENABLED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'SPACE_JUNK_LIMIT': {\n      update = ofSpaceJunkLimit(action, state);\n      break;\n    }\n    case 'PLANET_LEVEL_JUNK': {\n      update = ofPlanetLevelJunk(action, state);\n      break;\n    }\n    case 'ABANDON_SPEED_CHANGE_PERCENT': {\n      update = ofPositivePercent(action, state);\n      break;\n    }\n    case 'ABANDON_RANGE_CHANGE_PERCENT': {\n      update = ofPositivePercent(action, state);\n      break;\n    }\n    case 'CAPTURE_ZONES_ENABLED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    // TODO(#2299): Add to UI when this functionality is implemented\n    // case 'CAPTURE_ZONE_COUNT': {\n    //   update = ofPositiveInteger(action, state);\n    //   break;\n    // }\n    case 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL': {\n      update = ofCaptureZoneChangeBlockInterval(action, state);\n      break;\n    }\n    case 'CAPTURE_ZONE_RADIUS': {\n      update = ofCaptureZoneRadius(action, state);\n      break;\n    }\n    case 'CAPTURE_ZONE_PLANET_LEVEL_SCORE': {\n      update = ofCaptureZonePlanetLevelScore(action, state);\n      break;\n    }\n    case 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED': {\n      update = ofPositiveInteger(action, state);\n      break;\n    }\n    case 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS': {\n      update = ofCaptureZonesPer5000WorldRadius(action, state);\n      break;\n    }\n    case 'WHITELIST_ENABLED': {\n      update = ofBoolean(action, state);\n      break;\n    }\n    case 'RESET': {\n      // Hard reset all values that were available in the JSON\n      return {\n        ...state,\n        ...action.value,\n      };\n    }\n    default: {\n      // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking\n      const _exhaustive: never = action;\n      // Return the state if somehow we hit this\n      return state;\n    }\n  }\n  return {\n    ...state,\n    [action.type]: update,\n  };\n}\n\nexport function lobbyConfigInit(startingConfig: LobbyInitializers) {\n  const state: Partial<LobbyConfigState> = {};\n  for (const key of Object.keys(startingConfig) as [keyof LobbyInitializers]) {\n    switch (key) {\n      case 'START_PAUSED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'ADMIN_CAN_ADD_PLANETS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'TOKEN_MINT_END_TIMESTAMP': {\n        // TODO: Handle dates\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'WORLD_RADIUS_LOCKED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'WORLD_RADIUS_MIN': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'DISABLE_ZK_CHECKS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANETHASH_KEY': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'SPACETYPE_KEY': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'BIOMEBASE_KEY': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_MIRROR_X': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_MIRROR_Y': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_LENGTH_SCALE': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: Math.log2(defaultValue),\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'MAX_NATURAL_PLANET_LEVEL': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'TIME_FACTOR_HUNDREDTHS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: Math.floor(defaultValue / 100),\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_THRESHOLD_1': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_THRESHOLD_2': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PERLIN_THRESHOLD_3': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'INIT_PERLIN_MIN': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'INIT_PERLIN_MAX': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'BIOME_THRESHOLD_1': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'BIOME_THRESHOLD_2': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANET_LEVEL_THRESHOLDS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANET_RARITY': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANET_TRANSFER_ENABLED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PHOTOID_ACTIVATION_DELAY': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'SPAWN_RIM_AREA': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: Math.sqrt(defaultValue / Math.PI),\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'LOCATION_REVEAL_COOLDOWN': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CLAIM_PLANET_COOLDOWN': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANET_TYPE_WEIGHTS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'SILVER_SCORE_VALUE': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue / 100,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'ARTIFACT_POINT_VALUES': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'SPACE_JUNK_ENABLED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'SPACE_JUNK_LIMIT': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'PLANET_LEVEL_JUNK': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'ABANDON_SPEED_CHANGE_PERCENT': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue / 100,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'ABANDON_RANGE_CHANGE_PERCENT': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue / 100,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONES_ENABLED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONE_COUNT': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONE_RADIUS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONE_PLANET_LEVEL_SCORE': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS': {\n        const defaultValue = startingConfig[key];\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      case 'WHITELIST_ENABLED': {\n        // Default this to false if we don't have it\n        const defaultValue = startingConfig[key] || false;\n        state[key] = {\n          currentValue: defaultValue,\n          displayValue: defaultValue,\n          defaultValue,\n          warning: undefined,\n        };\n        break;\n      }\n      default: {\n        // https://www.typescriptlang.org/docs/handbook/2/narrowing.html#exhaustiveness-checking\n        const _exhaustive: never = key;\n        // Just ignore any values that we don't know about\n        break;\n      }\n    }\n  }\n\n  return state as LobbyConfigState;\n}\n\nexport function ofNoop({ type }: LobbyConfigAction, state: LobbyConfigState) {\n  return {\n    ...state[type],\n  };\n}\n\nexport function ofAny({ type, value }: LobbyConfigAction, state: LobbyConfigState) {\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofBoolean(\n  { type, value }: Extract<LobbyConfigAction, { value: boolean | undefined }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  } else {\n    return {\n      ...state[type],\n      currentValue: value,\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n}\n\nexport function ofPositiveInteger(\n  { type, value }: Extract<LobbyConfigAction, { value: number | undefined }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a positive integer`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofPositiveFloat(\n  { type, value }: Extract<LobbyConfigAction, { value: number | undefined }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a positive number`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofPositivePercent(\n  { type, value }: Extract<LobbyConfigAction, { value: number | undefined }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be a number',\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a positive number`,\n    };\n  }\n\n  if (value !== 0 && value < 0.01) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too small`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  return {\n    currentValue: Math.floor(100 * value),\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofWorldRadiusMin(\n  { type, value }: Extract<LobbyConfigAction, { type: 'WORLD_RADIUS_MIN' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be positive`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be an integer',\n    };\n  }\n\n  if (value < 1000) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Worlds smaller than 1000 are unlikely to have spawnable area`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Worlds can't be initialized with that large of a minimum radius`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofPerlinLengthScale(\n  { type, value }: Extract<LobbyConfigAction, { type: 'PERLIN_LENGTH_SCALE' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be a number',\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: 2 ** value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofMaxNaturalPlanetLevel(\n  { type, value }: Extract<LobbyConfigAction, { type: 'MAX_NATURAL_PLANET_LEVEL' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a positive integer`,\n    };\n  }\n\n  if (value > 9) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Planets can't naturally be larger than Level 9`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be an integer',\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofTimeFactorHundredths(\n  { type, value }: Extract<LobbyConfigAction, { type: 'TIME_FACTOR_HUNDREDTHS' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be a number',\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be greater than 0`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be an integer',\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: Math.floor(100 * value),\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofPlanetRarity(\n  { type, value }: Extract<LobbyConfigAction, { type: 'PLANET_RARITY' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be greater than 0`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofSpawnRimArea(\n  { type, value }: Extract<LobbyConfigAction, { type: 'SPAWN_RIM_AREA' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be a number',\n    };\n  }\n\n  // The `0` value disables this feature\n  if (value === 0) {\n    return {\n      ...state[type],\n      currentValue: value,\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  const currentValue = Math.floor(Math.PI * value ** 2);\n\n  if (currentValue < 1000) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Spawnable area must be larger',\n    };\n  }\n\n  // Using 1 billion instead of SAFE_UPPER_BOUNDS because math is done against this and don't want to lose precision\n  if (currentValue > 1_000_000_000) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Spawnable area is too large, instead use 0 to disable`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofSpaceJunkLimit(\n  { type, value }: Extract<LobbyConfigAction, { type: 'SPACE_JUNK_LIMIT' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: 'Value must be a number',\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be greated than 0`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (state.PLANET_LEVEL_JUNK.currentValue) {\n    let warning;\n\n    // Validate that every planet can be captured within SPACE_JUNK_LIMIT\n    state.PLANET_LEVEL_JUNK.currentValue.forEach((junkAmount, planetLevel) => {\n      if (value < junkAmount) {\n        warning = `You'd be unable to capture Level ${planetLevel} planets with that Space Junk Limit`;\n      }\n    });\n\n    if (warning) {\n      return {\n        ...state[type],\n        displayValue: value,\n        warning,\n      };\n    }\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofPlanetLevelJunk(\n  { type, index, value }: Extract<LobbyConfigAction, { type: 'PLANET_LEVEL_JUNK' }>,\n  state: LobbyConfigState\n) {\n  const prevCurrentValue = state[type].currentValue;\n  const prevDisplayValue = state[type].displayValue;\n  const spaceJunk = state.SPACE_JUNK_LIMIT.currentValue;\n\n  if (!prevDisplayValue) {\n    return {\n      ...state[type],\n      warning: `Failed to update ${type}`,\n    };\n  }\n\n  if (value === undefined) {\n    return {\n      ...state[type],\n      warning: undefined,\n    };\n  }\n\n  const currentValue = [...prevCurrentValue];\n  const displayValue = [...prevDisplayValue];\n\n  displayValue[index] = value;\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be positive`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (spaceJunk < value) {\n    return {\n      currentValue,\n      displayValue,\n      warning: `Space junk on Level ${index} planets would exceed the Space Junk Limit`,\n    };\n  }\n\n  currentValue[index] = value;\n\n  return {\n    ...state[type],\n    currentValue,\n    displayValue,\n    warning: undefined,\n  };\n}\n\nexport function ofArtifactPointValues(\n  { type, index, value }: Extract<LobbyConfigAction, { type: 'ARTIFACT_POINT_VALUES' }>,\n  state: LobbyConfigState\n) {\n  const prevCurrentValue = state[type].currentValue;\n  const prevDisplayValue = state[type].displayValue;\n\n  if (!prevDisplayValue) {\n    return {\n      ...state[type],\n      warning: `Failed to update ${type}`,\n    };\n  }\n\n  if (value === undefined) {\n    return {\n      ...state[type],\n      warning: undefined,\n    };\n  }\n\n  const currentValue = [...prevCurrentValue];\n  const displayValue = [...prevDisplayValue];\n\n  displayValue[index] = value;\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be positive`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value is too large`,\n    };\n  }\n\n  currentValue[index] = value;\n\n  return {\n    ...state[type],\n    currentValue,\n    displayValue,\n    warning: undefined,\n  };\n}\n\nexport function ofCaptureZoneRadius(\n  { type, value }: Extract<LobbyConfigAction, { type: 'CAPTURE_ZONE_RADIUS' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a greater than 0`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofCaptureZonesPer5000WorldRadius(\n  { type, value }: Extract<LobbyConfigAction, { type: 'CAPTURE_ZONES_PER_5000_WORLD_RADIUS' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a greater than 0`,\n    };\n  }\n\n  if (value > 10) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofCaptureZoneChangeBlockInterval(\n  { type, value }: Extract<LobbyConfigAction, { type: 'CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL' }>,\n  state: LobbyConfigState\n) {\n  if (value === undefined) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: undefined,\n    };\n  }\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be a greater than 0`,\n    };\n  }\n\n  if (value > 255) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (Math.floor(value) !== value) {\n    return {\n      ...state[type],\n      displayValue: value,\n      warning: `Value must be an integer`,\n    };\n  }\n\n  return {\n    ...state[type],\n    currentValue: value,\n    displayValue: value,\n    warning: undefined,\n  };\n}\n\nexport function ofCaptureZonePlanetLevelScore(\n  { type, index, value }: Extract<LobbyConfigAction, { type: 'CAPTURE_ZONE_PLANET_LEVEL_SCORE' }>,\n  state: LobbyConfigState\n) {\n  const prevCurrentValue = state[type].currentValue;\n  const prevDisplayValue = state[type].displayValue;\n\n  if (!prevDisplayValue) {\n    return {\n      ...state[type],\n      warning: `Failed to update ${type}`,\n    };\n  }\n\n  if (value === undefined) {\n    return {\n      ...state[type],\n      warning: undefined,\n    };\n  }\n\n  const currentValue = [...prevCurrentValue];\n  const displayValue = [...prevDisplayValue];\n\n  displayValue[index] = value;\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 0) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be positive`,\n    };\n  }\n\n  if (value > SAFE_UPPER_BOUNDS) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value is too large`,\n    };\n  }\n\n  currentValue[index] = value;\n\n  return {\n    ...state[type],\n    currentValue,\n    displayValue,\n    warning: undefined,\n  };\n}\n\nexport function ofPlanetLevelThresholds(\n  { type, index, value }: Extract<LobbyConfigAction, { type: 'PLANET_LEVEL_THRESHOLDS' }>,\n  state: LobbyConfigState\n) {\n  const prevCurrentValue = state[type].currentValue;\n  const prevDisplayValue = state[type].displayValue;\n  const thresholds = state.PLANET_LEVEL_THRESHOLDS.currentValue;\n  const prevIndex = index - 1;\n  const prevThreshold = thresholds[prevIndex];\n\n  if (!prevDisplayValue) {\n    return {\n      ...state[type],\n      warning: `Failed to update ${type}`,\n    };\n  }\n\n  if (value === undefined) {\n    return {\n      ...state[type],\n      warning: undefined,\n    };\n  }\n\n  const currentValue = [...prevCurrentValue];\n  const displayValue = [...prevDisplayValue];\n\n  if (index === 0) {\n    // Level 0 boundary always has to be this number\n    displayValue[index] = 16777216;\n    currentValue[index] = 16777216;\n\n    return {\n      ...state[type],\n      displayValue,\n      currentValue,\n      warning: undefined,\n    };\n  }\n\n  displayValue[index] = value;\n\n  if (typeof value !== 'number') {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be a number`,\n    };\n  }\n\n  if (value < 1) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value must be greater than 0`,\n    };\n  }\n\n  if (value > 16777215) {\n    return {\n      ...state[type],\n      displayValue,\n      warning: `Value is too large`,\n    };\n  }\n\n  if (prevThreshold <= value) {\n    return {\n      currentValue,\n      displayValue,\n      warning: `Level ${index} planet threshold matches or exceeds previous threshold`,\n    };\n  }\n\n  currentValue[index] = value;\n\n  return {\n    ...state[type],\n    currentValue,\n    displayValue,\n    warning: undefined,\n  };\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/SnarkPane.tsx",
    "content": "import React from 'react';\nimport {\n  Checkbox,\n  DarkForestCheckbox,\n  DarkForestNumberInput,\n  NumberInput,\n} from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function SnarkPane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='Disable ZK?'\n          checked={config.DISABLE_ZK_CHECKS.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) => {\n            onUpdate({ type: 'DISABLE_ZK_CHECKS', value: e.target.checked });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.DISABLE_ZK_CHECKS.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Planet hash key:</span>\n        <NumberInput\n          value={config.PLANETHASH_KEY.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'PLANETHASH_KEY', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PLANETHASH_KEY.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Space type key:</span>\n        <NumberInput\n          value={config.SPACETYPE_KEY.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'SPACETYPE_KEY', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.SPACETYPE_KEY.warning}</Warning>\n      </Row>\n      <Row>\n        <span>Biome base key:</span>\n        <NumberInput\n          value={config.BIOMEBASE_KEY.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'BIOMEBASE_KEY', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.BIOMEBASE_KEY.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/SpaceJunkPane.tsx",
    "content": "import _ from 'lodash';\nimport React from 'react';\nimport {\n  Checkbox,\n  DarkForestCheckbox,\n  DarkForestNumberInput,\n  NumberInput,\n} from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { DarkForestSlider, Slider } from '../../Components/Slider';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nfunction JunkPerLevel({\n  value,\n  index,\n  onUpdate,\n}: LobbiesPaneProps & { index: number; value: number | undefined }) {\n  return (\n    <div>\n      <span>Level {index}</span>\n      <NumberInput\n        format='integer'\n        value={value}\n        onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n          onUpdate({ type: 'PLANET_LEVEL_JUNK', index, value: e.target.value });\n        }}\n      />\n    </div>\n  );\n}\n\nconst rowChunkSize = 5;\nconst junkRowStyle = { gap: '8px' } as CSSStyleDeclaration & React.CSSProperties;\n\nexport function SpaceJunkPane({ config, onUpdate }: LobbiesPaneProps) {\n  let spaceJunkOptions = null;\n  if (config.SPACE_JUNK_ENABLED.currentValue === true) {\n    const junk = _.chunk(config.PLANET_LEVEL_JUNK.displayValue, rowChunkSize).map(\n      (items, rowIdx) => {\n        return (\n          <Row key={`junk-row-${rowIdx}`} style={junkRowStyle}>\n            {items.map((displayValue, idx) => (\n              <JunkPerLevel\n                key={`junk-lvl-${idx}`}\n                config={config}\n                value={displayValue}\n                index={rowIdx * rowChunkSize + idx}\n                onUpdate={onUpdate}\n              />\n            ))}\n          </Row>\n        );\n      }\n    );\n    spaceJunkOptions = (\n      <>\n        <Row>\n          <span>Player space junk maximum</span>\n          <NumberInput\n            format='integer'\n            value={config.SPACE_JUNK_LIMIT.displayValue}\n            onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n              onUpdate({ type: 'SPACE_JUNK_LIMIT', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.SPACE_JUNK_LIMIT.warning}</Warning>\n        </Row>\n        <Row>\n          <Slider\n            label='Abandon speed boost bonus'\n            variant='filled'\n            min={1}\n            max={11}\n            value={config.ABANDON_SPEED_CHANGE_PERCENT.displayValue}\n            step={0.1}\n            formatOptions={{ style: 'unit', unit: 'x' }}\n            onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) => {\n              onUpdate({ type: 'ABANDON_SPEED_CHANGE_PERCENT', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.ABANDON_SPEED_CHANGE_PERCENT.warning}</Warning>\n        </Row>\n        <Row>\n          <Slider\n            label='Abandon range boost bonus'\n            variant='filled'\n            min={1}\n            max={11}\n            value={config.ABANDON_RANGE_CHANGE_PERCENT.displayValue}\n            step={0.1}\n            formatOptions={{ style: 'unit', unit: 'x' }}\n            onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) => {\n              onUpdate({ type: 'ABANDON_RANGE_CHANGE_PERCENT', value: e.target.value });\n            }}\n          />\n        </Row>\n        <Row>\n          <Warning>{config.ABANDON_RANGE_CHANGE_PERCENT.warning}</Warning>\n        </Row>\n        <Row>\n          <span>Default junk for each planet level</span>\n        </Row>\n        {junk}\n        <Row>\n          <Warning>{config.PLANET_LEVEL_JUNK.warning}</Warning>\n        </Row>\n      </>\n    );\n  }\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='Space junk enabled?'\n          checked={config.SPACE_JUNK_ENABLED.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) => {\n            onUpdate({ type: 'SPACE_JUNK_ENABLED', value: e.target.checked });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.SPACE_JUNK_ENABLED.warning}</Warning>\n      </Row>\n      {spaceJunkOptions}\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/SpaceTypeBiomePane.tsx",
    "content": "import React from 'react';\nimport { Checkbox, DarkForestCheckbox } from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport {\n  DarkForestSlider,\n  DarkForestSliderHandle,\n  Slider,\n  SliderHandle,\n} from '../../Components/Slider';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function SpaceTypeBiomePane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='Mirror space type and biome on x-axis?'\n          checked={config.PERLIN_MIRROR_X.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n            onUpdate({ type: 'PERLIN_MIRROR_X', value: e.target.checked })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PERLIN_MIRROR_X.warning}</Warning>\n      </Row>\n      <Row>\n        <Checkbox\n          label='Mirror space type and biome on y-axis?'\n          checked={config.PERLIN_MIRROR_Y.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n            onUpdate({ type: 'PERLIN_MIRROR_Y', value: e.target.checked })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PERLIN_MIRROR_Y.warning}</Warning>\n      </Row>\n      <Row>\n        <Slider\n          label='Size of space types'\n          labelVisibility='text'\n          variant='filled'\n          min={5}\n          max={14}\n          value={config.PERLIN_LENGTH_SCALE.displayValue}\n          step={1}\n          onChange={(e: Event & React.ChangeEvent<DarkForestSlider>) =>\n            onUpdate({ type: 'PERLIN_LENGTH_SCALE', value: e.target.value })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.PERLIN_LENGTH_SCALE.warning}</Warning>\n      </Row>\n      <Row>\n        <Slider min={0} max={32} step={1} variant='range' label='Space type thresholds'>\n          <SliderHandle\n            slot='handle'\n            name='space'\n            label='Space'\n            value={config.PERLIN_THRESHOLD_1.displayValue}\n            step={1}\n            max='next'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'PERLIN_THRESHOLD_1', value: e.target.value });\n            }}\n          />\n          <SliderHandle\n            slot='handle'\n            name='deep-space'\n            label='Deep Space'\n            value={config.PERLIN_THRESHOLD_2.displayValue}\n            step={1}\n            min='previous'\n            max='next'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'PERLIN_THRESHOLD_2', value: e.target.value });\n            }}\n          />\n          <SliderHandle\n            slot='handle'\n            name='dead-space'\n            label='Dead Space'\n            value={config.PERLIN_THRESHOLD_3.displayValue}\n            step={1}\n            min='previous'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'PERLIN_THRESHOLD_3', value: e.target.value });\n            }}\n          />\n        </Slider>\n      </Row>\n      <Row>\n        <Warning>\n          {config.PERLIN_THRESHOLD_1.warning ||\n            config.PERLIN_THRESHOLD_2.warning ||\n            config.PERLIN_THRESHOLD_3.warning}\n        </Warning>\n      </Row>\n      <Row>\n        <Slider min={0} max={32} step={1} variant='range' label='Biome thresholds'>\n          <SliderHandle\n            slot='handle'\n            name='biome-threshold-1'\n            label='Biome threshold 1'\n            value={config.BIOME_THRESHOLD_1.displayValue}\n            step={1}\n            max='next'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'BIOME_THRESHOLD_1', value: e.target.value });\n            }}\n          />\n          <SliderHandle\n            slot='handle'\n            name='biome-threshold-2'\n            label='Biome threshold 2'\n            value={config.BIOME_THRESHOLD_2.displayValue}\n            step={1}\n            min='previous'\n            onChange={(e: Event & React.ChangeEvent<DarkForestSliderHandle>) => {\n              onUpdate({ type: 'BIOME_THRESHOLD_2', value: e.target.value });\n            }}\n          />\n        </Slider>\n      </Row>\n      <Row>\n        <Warning>{config.BIOME_THRESHOLD_1.warning || config.BIOME_THRESHOLD_2.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/WorldSizePane.tsx",
    "content": "import React from 'react';\nimport {\n  Checkbox,\n  DarkForestCheckbox,\n  DarkForestNumberInput,\n  NumberInput,\n} from '../../Components/Input';\nimport { Row } from '../../Components/Row';\nimport { LobbiesPaneProps, Warning } from './LobbiesUtils';\n\nexport function WorldSizePane({ config, onUpdate }: LobbiesPaneProps) {\n  return (\n    <>\n      <Row>\n        <Checkbox\n          label='World radius locked?'\n          checked={config.WORLD_RADIUS_LOCKED.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n            onUpdate({ type: 'WORLD_RADIUS_LOCKED', value: e.target.checked })\n          }\n        />\n      </Row>\n      <Row>\n        <Warning>{config.WORLD_RADIUS_LOCKED.warning}</Warning>\n      </Row>\n      <Row>\n        <span>{config.WORLD_RADIUS_LOCKED ? 'World radius:' : 'Minimum world radius'}</span>\n        <NumberInput\n          value={config.WORLD_RADIUS_MIN.displayValue}\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n            onUpdate({ type: 'WORLD_RADIUS_MIN', value: e.target.value });\n          }}\n        />\n      </Row>\n      <Row>\n        <Warning>{config.WORLD_RADIUS_MIN.warning}</Warning>\n      </Row>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Lobbies/minimap.worker.ts",
    "content": "import { perlin } from '@darkforest_eth/hashing';\nimport { SpaceType, WorldCoords } from '@darkforest_eth/types';\nimport { DrawMessage, MinimapConfig } from './MinimapUtils';\n\nconst ctx = self as unknown as Worker;\n\nfunction spaceTypePerlin(coords: WorldCoords, config: MinimapConfig): number {\n  return perlin(coords, { ...config, floor: true });\n}\n\nfunction spaceTypeFromPerlin(perlin: number, config: MinimapConfig): SpaceType {\n  if (perlin < config.perlinThreshold1) {\n    return SpaceType.NEBULA;\n  } else if (perlin < config.perlinThreshold2) {\n    return SpaceType.SPACE;\n  } else if (perlin < config.perlinThreshold3) {\n    return SpaceType.DEEP_SPACE;\n  } else {\n    return SpaceType.DEAD_SPACE;\n  }\n}\n\n// Initial implementation by @nicholashc (https://github.com/nicholashc)\n// https://github.com/darkforest-eth/plugins/blob/358a386356b9145005f17045d9f4ce22661d99a1/content/utilities/mini-map/plugin.js\nfunction generate(config: MinimapConfig): DrawMessage {\n  const data = [];\n  const step = config.worldRadius / 25;\n\n  const radius = config.worldRadius;\n\n  // utility functions\n  const checkBounds = (a: number, b: number, x: number, y: number, r: number) => {\n    const dist = (a - x) * (a - x) + (b - y) * (b - y);\n    r *= r;\n    if (dist < r) {\n      return true;\n    }\n    return false;\n  };\n\n  // generate x coordinates\n  for (let i = radius * -1; i < radius; i += step) {\n    // generate y coordinates\n    for (let j = radius * -1; j < radius; j += step) {\n      // filter points within map circle\n      if (checkBounds(0, 0, i, j, radius)) {\n        // store coordinate and space type\n        data.push({\n          x: i,\n          y: j,\n          type: spaceTypeFromPerlin(spaceTypePerlin({ x: i, y: j }, config), config),\n        });\n      }\n    }\n  }\n\n  return { radius, data };\n}\n\nctx.addEventListener('message', (e: MessageEvent) => {\n  if (e.data) {\n    const msg = generate(JSON.parse(e.data));\n    ctx.postMessage(JSON.stringify(msg));\n  }\n});\n"
  },
  {
    "path": "src/Frontend/Panes/ManagePlanetArtifacts/ArtifactActions.tsx",
    "content": "import {\n  canActivateArtifact,\n  canDepositArtifact,\n  canWithdrawArtifact,\n  durationUntilArtifactAvailable,\n  isActivated,\n  isLocatable,\n} from '@darkforest_eth/gamelogic';\nimport {\n  isUnconfirmedActivateArtifactTx,\n  isUnconfirmedDeactivateArtifactTx,\n  isUnconfirmedDepositArtifactTx,\n  isUnconfirmedWithdrawArtifactTx,\n} from '@darkforest_eth/serde';\nimport { Artifact, ArtifactId, ArtifactType, LocationId, TooltipName } from '@darkforest_eth/types';\nimport React, { useCallback } from 'react';\nimport { Btn } from '../../Components/Btn';\nimport { Spacer } from '../../Components/CoreUI';\nimport { ArtifactRarityLabelAnim } from '../../Components/Labels/ArtifactLabels';\nimport { LoadingSpinner } from '../../Components/LoadingSpinner';\nimport { Sub } from '../../Components/Text';\nimport { formatDuration } from '../../Components/TimeUntil';\nimport {\n  useAccount,\n  useArtifact,\n  usePlanet,\n  usePlanetArtifacts,\n  useUIManager,\n} from '../../Utils/AppHooks';\nimport { TooltipTrigger, TooltipTriggerProps } from '../Tooltip';\n\nexport function ArtifactActions({\n  artifactId,\n  depositOn,\n}: {\n  artifactId: ArtifactId;\n  depositOn?: LocationId;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const artifactWrapper = useArtifact(uiManager, artifactId);\n  const artifact = artifactWrapper.value;\n\n  const depositPlanetWrapper = usePlanet(uiManager, depositOn);\n  const onPlanetWrapper = usePlanet(uiManager, artifact?.onPlanetId);\n  const depositPlanet = depositPlanetWrapper.value;\n  const onPlanet = onPlanetWrapper.value;\n\n  const otherArtifactsOnPlanet = usePlanetArtifacts(onPlanetWrapper, uiManager);\n\n  const withdraw = useCallback(\n    (artifact: Artifact) => {\n      onPlanet && uiManager.withdrawArtifact(onPlanet.locationId, artifact?.id);\n    },\n    [onPlanet, uiManager]\n  );\n\n  const deposit = useCallback(\n    (artifact: Artifact) => {\n      artifact &&\n        depositPlanetWrapper.value &&\n        uiManager.depositArtifact(depositPlanetWrapper.value.locationId, artifact?.id);\n    },\n    [uiManager, depositPlanetWrapper.value]\n  );\n\n  const activate = useCallback(\n    async (artifact: Artifact) => {\n      if (onPlanet && isLocatable(onPlanet)) {\n        let targetPlanetId = undefined;\n\n        if (artifact.artifactType === ArtifactType.Wormhole) {\n          const targetPlanet = await uiManager.startWormholeFrom(onPlanet);\n          targetPlanetId = targetPlanet?.locationId;\n        }\n\n        uiManager.activateArtifact(onPlanet.locationId, artifact.id, targetPlanetId);\n      }\n    },\n    [onPlanet, uiManager]\n  );\n\n  const deactivate = useCallback(\n    (artifact: Artifact) => {\n      onPlanet && uiManager.deactivateArtifact(onPlanet.locationId, artifact.id);\n    },\n    [onPlanet, uiManager]\n  );\n\n  if (!artifact || (!onPlanet && !depositPlanet) || !account) return null;\n\n  const actions: TooltipTriggerProps[] = [];\n\n  const withdrawing = artifact.transactions?.hasTransaction(isUnconfirmedWithdrawArtifactTx);\n  const depositing = artifact.transactions?.hasTransaction(isUnconfirmedDepositArtifactTx);\n  const activating = artifact.transactions?.hasTransaction(isUnconfirmedActivateArtifactTx);\n  const deactivating = artifact.transactions?.hasTransaction(isUnconfirmedDeactivateArtifactTx);\n\n  const canHandleDeposit =\n    depositPlanetWrapper.value && depositPlanetWrapper.value.planetLevel > artifact.rarity;\n  const canHandleWithdraw =\n    onPlanetWrapper.value && onPlanetWrapper.value.planetLevel > artifact.rarity;\n\n  const wait = durationUntilArtifactAvailable(artifact);\n\n  if (canDepositArtifact(account, artifact, depositPlanetWrapper.value)) {\n    actions.unshift({\n      name: TooltipName.DepositArtifact,\n      extraContent: !canHandleDeposit && (\n        <>\n          . <ArtifactRarityLabelAnim rarity={artifact.rarity} />\n          {` artifacts can only be deposited on level ${artifact.rarity + 1}+ spacetime rips`}\n        </>\n      ),\n      children: (\n        <Btn\n          disabled={depositing}\n          onClick={(e) => {\n            e.stopPropagation();\n            canHandleDeposit && deposit(artifact);\n          }}\n        >\n          {depositing ? <LoadingSpinner initialText={'Depositing...'} /> : 'Deposit'}\n        </Btn>\n      ),\n    });\n  }\n  if (isActivated(artifact) && artifact.artifactType !== ArtifactType.BlackDomain) {\n    actions.unshift({\n      name: TooltipName.DeactivateArtifact,\n      children: (\n        <Btn\n          disabled={deactivating}\n          onClick={(e) => {\n            e.stopPropagation();\n            deactivate(artifact);\n          }}\n        >\n          {deactivating ? <LoadingSpinner initialText={'Deactivating...'} /> : 'Deactivate'}\n        </Btn>\n      ),\n    });\n  }\n  if (canWithdrawArtifact(account, artifact, onPlanet)) {\n    actions.unshift({\n      name: TooltipName.WithdrawArtifact,\n      extraContent: !canHandleWithdraw && (\n        <>\n          . <ArtifactRarityLabelAnim rarity={artifact.rarity} />\n          {` artifacts can only be withdrawn from level ${artifact.rarity + 1}+ spacetime rips`}\n        </>\n      ),\n      children: (\n        <Btn\n          disabled={withdrawing}\n          onClick={(e) => {\n            e.stopPropagation();\n            canHandleWithdraw && withdraw(artifact);\n          }}\n        >\n          {withdrawing ? <LoadingSpinner initialText={'Withdrawing...'} /> : 'Withdraw'}\n        </Btn>\n      ),\n    });\n  }\n\n  if (canActivateArtifact(artifact, onPlanet, otherArtifactsOnPlanet)) {\n    actions.unshift({\n      name: TooltipName.ActivateArtifact,\n      children: (\n        <Btn\n          disabled={activating}\n          onClick={(e) => {\n            e.stopPropagation();\n            activate(artifact);\n          }}\n        >\n          {activating ? <LoadingSpinner initialText={'Activating...'} /> : 'Activate'}\n        </Btn>\n      ),\n    });\n  }\n\n  if (wait > 0) {\n    actions.unshift({\n      name: TooltipName.Empty,\n      extraContent: <>You have to wait before activating an artifact again</>,\n      children: <Sub>{formatDuration(wait)}</Sub>,\n    });\n  }\n\n  return (\n    <div>\n      {actions.length > 0 && <Spacer height={4} />}\n      {actions.map((a, i) => (\n        <span key={i}>\n          <TooltipTrigger {...a} />\n          <Spacer width={4} />\n        </span>\n      ))}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ManagePlanetArtifacts/ManageArtifacts.tsx",
    "content": "import { Artifact, LocatablePlanet, PlanetType } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport { Spacer } from '../../Components/CoreUI';\nimport { ModalHandle } from '../../Views/ModalPane';\nimport { AllArtifacts } from '../ArtifactsList';\n\nexport function ManageArtifactsPane({\n  planet,\n  artifactsInWallet,\n  artifactsOnPlanet,\n  playerAddress,\n  modal,\n}: {\n  planet: LocatablePlanet;\n  artifactsInWallet: Artifact[];\n  artifactsOnPlanet: Array<Artifact | undefined>;\n  playerAddress: string;\n  modal: ModalHandle;\n}) {\n  const isMyTradingPost =\n    planet.owner === playerAddress &&\n    planet.planetType === PlanetType.TRADING_POST &&\n    !planet.destroyed;\n  const [viewingDepositList, setViewingDepositList] = useState(false);\n\n  let action;\n\n  useEffect(() => {\n    setViewingDepositList(false);\n  }, [planet.locationId, playerAddress]);\n\n  return (\n    <>\n      <AllArtifacts\n        maxRarity={viewingDepositList ? planet.planetLevel - 1 : undefined}\n        depositOn={viewingDepositList ? planet.locationId : undefined}\n        artifacts={\n          (viewingDepositList ? artifactsInWallet : artifactsOnPlanet).filter(\n            (a) => !!a\n          ) as Artifact[]\n        }\n        modal={modal}\n        noArtifactsMessage={\n          <>\n            No Artifacts <br /> On This Planet\n          </>\n        }\n        noShipsMessage={\n          <>\n            No Ships <br /> On This Planet\n          </>\n        }\n      />\n      {action && (\n        <>\n          <Spacer height={8} />\n          {action}\n          <Spacer height={8} />\n        </>\n      )}\n\n      <Spacer height={4} />\n\n      {isMyTradingPost && (\n        <SelectArtifactsContainer>\n          <SelectArtifactList\n            selected={!viewingDepositList}\n            onClick={() => {\n              setViewingDepositList(false);\n            }}\n          >\n            On This Planet\n          </SelectArtifactList>\n          <SelectArtifactList\n            selected={viewingDepositList}\n            onClick={() => {\n              setViewingDepositList(true);\n            }}\n          >\n            Deposit Artifact\n          </SelectArtifactList>\n        </SelectArtifactsContainer>\n      )}\n    </>\n  );\n}\n\nconst SelectArtifactsContainer = styled.div`\n  padding: 4px;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  flex-direction: row;\n`;\n\nconst SelectArtifactList = styled.span`\n  ${({ selected }: { selected?: boolean }) => css`\n    ${selected && 'text-decoration: underline;'}\n    cursor: pointer;\n  `}\n`;\n"
  },
  {
    "path": "src/Frontend/Panes/ManagePlanetArtifacts/ManagePlanetArtifactsPane.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { LocationId } from '@darkforest_eth/types';\nimport React from 'react';\nimport { CenterBackgroundSubtext, Underline } from '../../Components/CoreUI';\nimport { useAccount, useMyArtifactsList, usePlanet, useUIManager } from '../../Utils/AppHooks';\nimport { useEmitterValue } from '../../Utils/EmitterHooks';\nimport { ModalHandle } from '../../Views/ModalPane';\nimport { ManageArtifactsPane } from './ManageArtifacts';\n\nexport function PlanetInfoHelpContent() {\n  return (\n    <div>\n      <p>Metadata related to this planet.</p>\n    </div>\n  );\n}\n\nexport function ManagePlanetArtifactsHelpContent() {\n  return (\n    <div>\n      <p>\n        Using this pane, you can manage the artifacts that are on this planet specifically. You can\n        activate a single artifact at a time. Some artifacts have a cooldown period after\n        deactivating during which they can not be activated.\n      </p>\n      <br />\n      <p>\n        If your planet is a <Underline>Spacetime Rip</Underline>, you can also withdraw and deposit\n        artifacts. When you withdraw an artifact, it is transferred to your address as an ERC 721\n        token.\n      </p>\n    </div>\n  );\n}\n\n/**\n * This is the place where a user can manage all of their artifacts on a\n * particular planet. This includes prospecting, withdrawing, depositing,\n * activating, and deactivating artifacts.\n */\nexport function ManagePlanetArtifactsPane({\n  initialPlanetId,\n  modal,\n}: {\n  initialPlanetId: LocationId | undefined;\n  modal: ModalHandle;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId);\n  const planet = usePlanet(uiManager, planetId).value;\n  const myArtifacts = useMyArtifactsList(uiManager);\n  const onPlanet = uiManager.getArtifactsWithIds(planet?.heldArtifactIds || []);\n\n  const artifactsInWallet = [];\n  for (const a of myArtifacts) {\n    if (!a.onPlanetId) {\n      artifactsInWallet.push(a);\n    }\n  }\n\n  if (planet && myArtifacts && isLocatable(planet) && account) {\n    return (\n      <ManageArtifactsPane\n        artifactsInWallet={artifactsInWallet}\n        artifactsOnPlanet={onPlanet}\n        planet={planet}\n        playerAddress={account}\n        modal={modal}\n      />\n    );\n  } else {\n    return (\n      <CenterBackgroundSubtext width='100%' height='75px'>\n        Select a Planet\n      </CenterBackgroundSubtext>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Panes/ManagePlanetArtifacts/SortBy.tsx",
    "content": "import { TooltipName, Upgrade } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { CenterRow, Spacer } from '../../Components/CoreUI';\nimport { Icon, IconType } from '../../Components/Icons';\nimport dfstyles from '../../Styles/dfstyles';\nimport { TooltipTrigger } from '../Tooltip';\n\ntype IconConfig = {\n  iconType: IconType;\n  key: keyof Upgrade;\n  tooltip: TooltipName;\n};\n\nconst icons: readonly IconConfig[] = [\n  {\n    iconType: IconType.Defense,\n    key: 'defMultiplier',\n    tooltip: TooltipName.DefenseMultiplier,\n  },\n  {\n    iconType: IconType.Energy,\n    key: 'energyCapMultiplier',\n    tooltip: TooltipName.EnergyCapMultiplier,\n  },\n  {\n    iconType: IconType.EnergyGrowth,\n    key: 'energyGroMultiplier',\n    tooltip: TooltipName.EnergyGrowthMultiplier,\n  },\n  {\n    iconType: IconType.Range,\n    key: 'rangeMultiplier',\n    tooltip: TooltipName.RangeMultiplier,\n  },\n  {\n    iconType: IconType.Speed,\n    key: 'speedMultiplier',\n    tooltip: TooltipName.SpeedMultiplier,\n  },\n] as const;\n\nexport function SortBy({\n  sortBy,\n  setSortBy,\n}: {\n  sortBy: keyof Upgrade | undefined;\n  setSortBy: (k: keyof Upgrade | undefined) => void;\n}) {\n  return (\n    <CenterRow>\n      Sort By:\n      <Spacer width={8} />\n      {icons.map(({ key, tooltip, iconType }) => (\n        <TooltipTrigger key={key} name={tooltip}>\n          <SortByIconContainer\n            onClick={() => {\n              if (key === sortBy) {\n                setSortBy(undefined);\n              } else {\n                setSortBy(key);\n              }\n            }}\n            iconColor={key === sortBy ? dfstyles.colors.dfgreen : dfstyles.colors.subtext}\n          >\n            <Icon type={iconType} />\n          </SortByIconContainer>\n        </TooltipTrigger>\n      ))}\n    </CenterRow>\n  );\n}\n\nconst SortByIconContainer = styled.div<{ iconColor: string }>`\n  line-height: 0;\n  padding-right: 8px;\n\n  /* Set the Icon color if specified on the outer component */\n  --df-icon-color: ${({ iconColor }) => iconColor};\n`;\n"
  },
  {
    "path": "src/Frontend/Panes/ManagePlanetArtifacts/UpgradeStatsView.tsx",
    "content": "import { ArtifactType, Upgrade } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { EmSpacer, Spacer } from '../../Components/CoreUI';\nimport { Icon, IconType } from '../../Components/Icons';\nimport dfstyles from '../../Styles/dfstyles';\n\nconst { dfgreen, dfred, subtext } = dfstyles.colors;\n\nfunction upgradeValue(value: number) {\n  const normalizedValue = Math.floor(value - 100);\n\n  if (normalizedValue >= -0) {\n    return '+' + normalizedValue + '%';\n  } else {\n    return normalizedValue + '%';\n  }\n}\n\nfunction SingleUpgrade({\n  upgrade,\n  active,\n  icon,\n  getMultiplier,\n}: {\n  upgrade: Upgrade;\n  active: boolean;\n  icon: IconType;\n  getMultiplier: (u: Upgrade) => number;\n}) {\n  const mult = getMultiplier(upgrade);\n  const activeColor = mult > 100 ? dfgreen : mult < 100 ? dfred : subtext;\n  const color = active ? activeColor : subtext;\n\n  return (\n    <StyledSingleUpgrade color={color} iconColor={color}>\n      <Icon type={icon} />\n      <Spacer width={2} />\n      {upgradeValue(getMultiplier(upgrade))}\n    </StyledSingleUpgrade>\n  );\n}\n\nconst getDefenseMult = (u: Upgrade) => u.defMultiplier;\nconst getEnergyCapMult = (u: Upgrade) => u.energyCapMultiplier;\nconst getEnergyGroMult = (u: Upgrade) => u.energyGroMultiplier;\nconst getRangeMult = (u: Upgrade) => u.rangeMultiplier;\nconst getSpeedMult = (u: Upgrade) => u.speedMultiplier;\n\nexport function UpgradeStatsView({\n  upgrade,\n  isActive,\n  artifactType,\n}: {\n  upgrade: Upgrade;\n  isActive: boolean;\n  artifactType: ArtifactType;\n}) {\n  let specialDescription;\n\n  switch (artifactType) {\n    case ArtifactType.BlackDomain:\n      specialDescription = 'locks planet';\n      break;\n    case ArtifactType.Wormhole:\n      specialDescription = 'decreases travel time';\n      break;\n    case ArtifactType.BloomFilter:\n      specialDescription = \"refills planet's energy and silver\";\n      break;\n    case ArtifactType.PhotoidCannon:\n      specialDescription = 'single use sniper';\n      break;\n  }\n\n  if (specialDescription) {\n    return (\n      <UpgradeSummaryContainer>\n        {specialDescription}\n        <EmSpacer height={1} />\n      </UpgradeSummaryContainer>\n    );\n  }\n\n  const iconProps = { active: isActive, upgrade };\n\n  return (\n    <UpgradeSummaryContainer>\n      <SingleUpgrade {...iconProps} icon={IconType.Defense} getMultiplier={getDefenseMult} />\n      <SingleUpgrade {...iconProps} icon={IconType.Energy} getMultiplier={getEnergyCapMult} />\n      <Spacer height={2} />\n      <SingleUpgrade {...iconProps} icon={IconType.EnergyGrowth} getMultiplier={getEnergyGroMult} />\n      <SingleUpgrade {...iconProps} icon={IconType.Range} getMultiplier={getRangeMult} />\n      <SingleUpgrade {...iconProps} icon={IconType.Speed} getMultiplier={getSpeedMult} />\n    </UpgradeSummaryContainer>\n  );\n}\n\nconst StyledSingleUpgrade = styled.div<{ color?: string; iconColor?: string }>`\n  display: inline-flex;\n  justify-content: flex-start;\n  align-items: center;\n  width: 45px;\n  color: ${({ color }) => color};\n\n  /* Set the Icon color if specified on the outer component */\n  --df-icon-color: ${({ iconColor }) => iconColor};\n`;\n\nconst UpgradeSummaryContainer = styled.div`\n  color: ${dfstyles.colors.subtext};\n  display: inline-block;\n  font-size: 12px;\n  justify-content: space-between;\n  align-items: center;\n`;\n"
  },
  {
    "path": "src/Frontend/Panes/OnboardingPane.tsx",
    "content": "import { ModalName } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { Icon, IconType } from '../Components/Icons';\nimport { Green, Red, White } from '../Components/Text';\nimport { TextPreview } from '../Components/TextPreview';\nimport dfstyles from '../Styles/dfstyles';\nimport { useAccount, useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\n\nconst StyledOnboardingContent = styled.div`\n  width: 36em;\n  height: 32em;\n  position: relative;\n  color: ${dfstyles.colors.text};\n\n  .btn {\n    position: absolute;\n    right: 0.5em;\n    bottom: 0.5em;\n  }\n\n  .indent {\n    margin-left: 1em;\n  }\n\n  & > p,\n  & > div {\n    margin: 1em 0;\n  }\n\n  & > div {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n  }\n`;\n\nconst enum OnboardState {\n  Money,\n  Storage,\n  Keys,\n  Help,\n  Finished,\n}\n\nfunction OnboardMoney({ advance }: { advance: () => void }) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n\n  const explorerAddressLink = `https://blockscout.com/poa/xdai/address/${account}`;\n\n  return (\n    <StyledOnboardingContent>\n      <p>\n        Welcome to <Green>Dark Forest</Green>!\n      </p>\n      <p>\n        <Red>There is real money being transacted in-game!</Red> We have initialized a{' '}\n        <a onClick={() => window.open('https://github.com/austintgriffith/burner-wallet')}>\n          burner wallet\n        </a>{' '}\n        for you and dripped 15c to it, courtesy of Dark Forest Team and xDAI.\n      </p>\n      <p className='indent'>\n        Your burner wallet address is: <br />\n        <White>\n          <a onClick={() => window.open(explorerAddressLink)}>{account}</a>\n        </White>\n      </p>\n      <p>\n        This means that when you make moves on Dark Forest,{' '}\n        <White> you are authorizing the client to pay gas fees on your behalf</White>.\n      </p>\n      <p>\n        To ensure the safety of your balance, <White>we require you to enable popups</White> so that\n        all transactions may be confirmed by you. Note that you can disable popups for small\n        transactions in settings. <Icon type={IconType.Settings} />\n      </p>\n      <p>\n        <White>Make sure you understand all of the above before proceeding.</White>\n      </p>\n\n      <div>\n        <span></span>\n        <Btn className='btn' onClick={advance}>\n          I understand, please proceed.\n        </Btn>\n      </div>\n    </StyledOnboardingContent>\n  );\n}\n\nfunction OnboardStorage({ advance }: { advance: () => void }) {\n  return (\n    <StyledOnboardingContent>\n      <p>\n        The game stores important information like your <White>private key</White>,{' '}\n        <White>home coordinates</White>, and <White>map data</White> in your browser's local storage\n        / cache. <Red>If you clear your browser history, you risk losing your data!</Red>\n      </p>\n      <p>\n        Your <White>private key and home coordinates</White> act as your password. You can use them\n        to access your Dark Forest account on other browsers, or to continue playing if you\n        accidentally clear local storage. But this also means{' '}\n        <Red>they should never be viewed by anyone else!</Red>\n      </p>\n      <p>\n        <White>Make sure you back them up</White> and keep them somewhere safe.\n      </p>\n      <p>\n        On the next page, you will be able to view and copy your private key and home coordinates.{' '}\n        <White>When you are ready to back them up, please proceed.</White>\n      </p>\n      <div>\n        <span></span>\n        <Btn className='btn' onClick={advance}>\n          Proceed\n        </Btn>\n      </div>\n    </StyledOnboardingContent>\n  );\n}\nfunction OnboardKeys({ advance }: { advance: () => void }) {\n  const uiManager = useUIManager();\n  const [sKey, setSKey] = useState<string | undefined>(undefined);\n  useEffect(() => {\n    if (!uiManager) return;\n    setSKey(uiManager.getPrivateKey());\n  }, [uiManager]);\n\n  const [home, setHome] = useState<string | undefined>(undefined);\n  useEffect(() => {\n    if (!uiManager) return;\n    const coords = uiManager.getHomeCoords();\n    setHome(coords ? `(${coords.x}, ${coords.y})` : '');\n  }, [uiManager]);\n\n  return (\n    <StyledOnboardingContent>\n      <p>\n        Your private key is: <br />\n        <TextPreview text={sKey} focusedWidth={'150px'} unFocusedWidth={'150px'} />\n      </p>\n      <p>\n        Your home coordinates are: <br />\n        <White>{home}</White>\n      </p>\n\n      <p>When you have backed up your key and coordinates, please proceed.</p>\n\n      <div>\n        <span></span>\n        <Btn onClick={advance} className='btn'>\n          Proceed\n        </Btn>\n      </div>\n    </StyledOnboardingContent>\n  );\n}\n\nfunction OnboardHelp({ advance }: { advance: () => void }) {\n  return (\n    <StyledOnboardingContent>\n      <p>\n        For an overview of how to play, rules, and scoring, click the question mark icon on the left\n        to open the <White>Help Pane</White>.\n      </p>\n      <div>\n        <span></span>\n        <Btn onClick={advance} className='btn'>\n          Proceed\n        </Btn>\n      </div>\n    </StyledOnboardingContent>\n  );\n}\n\nfunction OnboardFinished({ advance }: { advance: () => void }) {\n  return (\n    <StyledOnboardingContent>\n      <p>That's all! You're now ready to play the game!</p>\n      <p>\n        We invite you to log into the universe. Click <White>Proceed</White> to join the world of{' '}\n        <White>DARK FOREST...</White>\n      </p>\n      <div>\n        <span></span>\n        <Btn onClick={advance} className='btn'>\n          Proceed\n        </Btn>\n      </div>\n    </StyledOnboardingContent>\n  );\n}\n\nexport default function OnboardingPane({\n  visible,\n  onClose,\n}: {\n  visible: boolean;\n  onClose: () => void;\n}) {\n  const [onboardState, setOnboardState] = useState<OnboardState>(OnboardState.Money);\n\n  const advance = () => setOnboardState((x) => x + 1);\n\n  useEffect(() => {\n    if (onboardState === OnboardState.Finished + 1) {\n      onClose();\n    }\n  }, [onboardState, onClose]);\n\n  return (\n    <ModalPane\n      id={ModalName.Onboarding}\n      title={'Welcome to Dark Forest'}\n      hideClose\n      visible={visible}\n      onClose={onClose}\n    >\n      {onboardState === OnboardState.Money && <OnboardMoney advance={advance} />}\n      {onboardState === OnboardState.Storage && <OnboardStorage advance={advance} />}\n      {onboardState === OnboardState.Keys && <OnboardKeys advance={advance} />}\n      {onboardState === OnboardState.Help && <OnboardHelp advance={advance} />}\n      {onboardState === OnboardState.Finished && <OnboardFinished advance={advance} />}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PlanetContextPane.tsx",
    "content": "import { ModalName, Planet, PlanetType } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { CapturePlanetButton } from '../Components/CapturePlanetButton';\nimport { VerticalSplit } from '../Components/CoreUI';\nimport { MineArtifactButton } from '../Components/MineArtifactButton';\nimport {\n  OpenBroadcastPaneButton,\n  OpenHatPaneButton,\n  OpenManagePlanetArtifactsButton,\n  OpenPlanetInfoButton,\n  OpenUpgradeDetailsPaneButton,\n} from '../Components/OpenPaneButtons';\nimport { snips } from '../Styles/dfstyles';\nimport { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterSubscribe } from '../Utils/EmitterHooks';\nimport { useOnUp } from '../Utils/KeyEmitters';\nimport { EXIT_PANE, TOGGLE_ABANDON, TOGGLE_SEND } from '../Utils/ShortcutConstants';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\nimport { ModalHandle, ModalPane } from '../Views/ModalPane';\nimport { PlanetCard, PlanetCardTitle } from '../Views/PlanetCard';\nimport { getNotifsForPlanet, PlanetNotifications } from '../Views/PlanetNotifications';\nimport { SendResources } from '../Views/SendResources';\nimport { WithdrawSilver } from '../Views/WithdrawSilver';\n\nfunction PlanetContextPaneContent({\n  modal,\n  planet,\n  uiManager,\n  onToggleSendForces,\n  onToggleAbandon,\n}: {\n  modal: ModalHandle;\n  planet: Wrapper<Planet | undefined>;\n  uiManager: GameUIManager;\n  onToggleSendForces: () => void;\n  onToggleAbandon: () => void;\n}) {\n  const account = useAccount(uiManager);\n  const notifs = useMemo(() => getNotifsForPlanet(planet.value, account), [planet, account]);\n  const owned = planet.value?.owner === account;\n\n  useEffect(() => {\n    if (!planet.value) modal.popAll();\n  }, [planet.value, modal]);\n\n  const p = planet.value;\n\n  let captureRow = null;\n  if (!p?.destroyed && uiManager.captureZonesEnabled) {\n    captureRow = <CapturePlanetButton planetWrapper={planet} />;\n  }\n\n  let upgradeRow = null;\n  if (!p?.destroyed && owned) {\n    upgradeRow = <OpenUpgradeDetailsPaneButton modal={modal} planetId={p?.locationId} />;\n  }\n\n  let hatRow = null;\n  if (!p?.destroyed && owned) {\n    hatRow = <OpenHatPaneButton modal={modal} planetId={p?.locationId} />;\n  }\n\n  let withdrawRow = null;\n  if (!p?.destroyed && owned && p?.planetType === PlanetType.TRADING_POST) {\n    withdrawRow = <WithdrawSilver wrapper={planet} />;\n  }\n\n  let notifRow = null;\n  if (!p?.destroyed && notifs.length > 0) {\n    notifRow = <PlanetNotifications planet={planet} notifs={notifs} />;\n  }\n\n  return (\n    <>\n      <PlanetCard planetWrapper={planet} />\n      <SendResources\n        planetWrapper={planet}\n        onToggleSendForces={onToggleSendForces}\n        onToggleAbandon={onToggleAbandon}\n      />\n      <MineArtifactButton planetWrapper={planet} />\n      {captureRow}\n\n      <VerticalSplit>\n        <>\n          {upgradeRow}\n          <OpenBroadcastPaneButton modal={modal} planetId={p?.locationId} />\n          <OpenPlanetInfoButton modal={modal} planetId={p?.locationId} />\n        </>\n        <>\n          <OpenManagePlanetArtifactsButton modal={modal} planetId={p?.locationId} />\n          {hatRow}\n        </>\n      </VerticalSplit>\n      {withdrawRow}\n      {notifRow}\n    </>\n  );\n}\n\nexport function SelectedPlanetHelpContent() {\n  return (\n    <div>\n      <p>\n        This pane allows you to interact with the currently selected planet. Pressing the ESCAPE key\n        allows you to deselect the current planet.\n      </p>\n    </div>\n  );\n}\n\nexport function PlanetContextPane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n  const planet = useSelectedPlanet(uiManager);\n\n  /* All of this is done to support using the key commands on subpanes of the PlanetContextPane */\n  const doSend = useCallback(() => {\n    if (!uiManager) return;\n    const uiEmitter = UIEmitter.getInstance();\n    if (uiManager.isSendingForces() || uiManager.isSendingShip() || uiManager.isAbandoning()) {\n      uiEmitter.emit(UIEmitterEvent.SendInitiated, planet.value);\n    } else {\n      uiEmitter.emit(UIEmitterEvent.SendCancelled);\n    }\n  }, [planet, uiManager]);\n\n  const toggleSendingForces = useCallback(() => {\n    if (planet.value?.destroyed && !uiManager.isSendingShip(planet.value?.locationId)) return;\n    const isAbandoning = uiManager.isAbandoning();\n    if (isAbandoning) return;\n    const isSending = uiManager.isSendingForces();\n    uiManager.setSending(!isSending);\n    doSend();\n  }, [uiManager, doSend, planet]);\n\n  const toggleAbandoning = useCallback(() => {\n    if (planet.value?.destroyed) return;\n    const isAbandoning = uiManager.isAbandoning();\n    uiManager.setAbandoning(!isAbandoning);\n    doSend();\n  }, [uiManager, doSend, planet]);\n\n  useOnUp(\n    TOGGLE_SEND,\n    () => {\n      toggleSendingForces();\n    },\n    [toggleSendingForces]\n  );\n\n  useOnUp(\n    TOGGLE_ABANDON,\n    () => {\n      toggleAbandoning();\n    },\n    [toggleAbandoning]\n  );\n\n  useOnUp(\n    EXIT_PANE,\n    () => {\n      // If we clear the selectedPlanetId, the below hook will cancel and cleanup the sending\n      uiManager.setSelectedPlanet(undefined);\n    },\n    [uiManager]\n  );\n\n  // If the locationId changes, cancel any sending\n  useEmitterSubscribe(\n    uiManager.selectedPlanetId$,\n    () => {\n      const uiEmitter = UIEmitter.getInstance();\n      uiEmitter.emit(UIEmitterEvent.SendCancelled);\n\n      // Get the previous planet and clear it's artifact\n      // so it doesn't have a ship selected the next time the planet is selected\n      const previousPlanet = uiManager.getPreviousSelectedPlanet();\n      if (previousPlanet) {\n        uiManager.setArtifactSending(previousPlanet.locationId, undefined);\n      }\n    },\n    [uiManager]\n  );\n\n  const render = useCallback(\n    (modal: ModalHandle) => (\n      <PlanetContextPaneContent\n        modal={modal}\n        planet={planet}\n        uiManager={uiManager}\n        onToggleSendForces={toggleSendingForces}\n        onToggleAbandon={toggleAbandoning}\n      />\n    ),\n    [uiManager, planet, toggleSendingForces, toggleAbandoning]\n  );\n\n  return (\n    <ModalPane\n      style={planet?.value?.destroyed ? snips.destroyedBackground : undefined}\n      visible={visible}\n      onClose={onClose}\n      id={ModalName.PlanetContextPane}\n      title={(small: boolean) => <PlanetCardTitle small={small} planet={planet} />}\n      hideClose\n      helpContent={SelectedPlanetHelpContent}\n      width='350px'\n    >\n      {render}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PlanetDexPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { formatNumber } from '@darkforest_eth/gamelogic';\nimport {\n  getPlanetClass,\n  getPlanetCosmetic,\n  getPlanetName,\n  rgbStr,\n} from '@darkforest_eth/procedural';\nimport { engineConsts } from '@darkforest_eth/renderer';\nimport { ModalName, Planet, PlanetType, RGBVec } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { getPlanetRank } from '../../Backend/Utils/Utils';\nimport { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI';\nimport { Icon, IconType } from '../Components/Icons';\nimport { Sub } from '../Components/Text';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { PlanetLink } from '../Views/PlanetLink';\nimport { SortableTable } from '../Views/SortableTable';\n\nconst StyledPlanetThumb = styled.div<{ iconColor?: string }>`\n  width: 20px;\n  height: 20px;\n  position: relative;\n  line-height: 0;\n  z-index: 1;\n\n  /* Set the Icon color if specified on the outer component */\n  --df-icon-color: ${({ iconColor }) => iconColor};\n`;\n\nconst PlanetElement = styled.div`\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  top: 0;\n  left: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n`;\n\nconst PlanetName = styled.span`\n  display: block;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  max-width: 100px;\n`;\n\nconst TableContainer = styled.div`\n  overflow-y: scroll;\n`;\n\nexport function PlanetThumb({ planet }: { planet: Planet }) {\n  const radius = 5 + 2 * planet.planetLevel;\n  // const radius = 5 + 3 * PlanetLevel.MAX;\n  const { speed, range, defense } = engineConsts.colors.belt;\n  const { baseStr } = getPlanetCosmetic(planet);\n\n  const ringColor = (): string => {\n    const myClass = getPlanetClass(planet);\n    const myColor: RGBVec = [defense, range, speed][myClass];\n    return rgbStr(myColor);\n  };\n\n  const ringW = radius * 1.5;\n  const ringH = Math.max(2, ringW / 7);\n\n  if (planet.planetType === PlanetType.SILVER_MINE) {\n    return (\n      <StyledPlanetThumb iconColor={baseStr}>\n        <Icon type={IconType.Silver} />\n      </StyledPlanetThumb>\n    );\n  } else if (planet.planetType === PlanetType.SILVER_BANK) {\n    return (\n      <StyledPlanetThumb iconColor={baseStr}>\n        <Icon type={IconType.SilverGrowth} />\n      </StyledPlanetThumb>\n    );\n  } else if (planet.planetType === PlanetType.RUINS) {\n    return (\n      <StyledPlanetThumb iconColor={baseStr}>\n        <Icon type={IconType.Artifact} />\n      </StyledPlanetThumb>\n    );\n  } else if (planet.planetType === PlanetType.TRADING_POST) {\n    return (\n      <StyledPlanetThumb iconColor={baseStr}>\n        <Icon type={IconType.Withdraw} />\n      </StyledPlanetThumb>\n    );\n  }\n\n  return (\n    <StyledPlanetThumb>\n      <PlanetElement>\n        <div\n          style={{\n            width: radius + 'px',\n            height: radius + 'px',\n            borderRadius: radius / 2 + 'px',\n            background: baseStr,\n          }}\n        />\n      </PlanetElement>\n      <PlanetElement>\n        <div\n          style={{\n            width: ringW + 'px',\n            height: ringH + 'px',\n            borderRadius: ringW * 2 + 'px',\n            background: getPlanetRank(planet) > 0 ? ringColor() : 'none',\n          }}\n        />\n      </PlanetElement>\n    </StyledPlanetThumb>\n  );\n}\n\nfunction HelpContent() {\n  return (\n    <div>\n      <p>These are all the planets you currently own.</p>\n      <Spacer height={8} />\n      <p>\n        The table is interactive, and allows you to sort the planets by clicking each column's\n        header. You can also navigate to a planet that you own by clicking on its name. The planet\n        you click will be centered at the spot on the screen where the current planet you have\n        selected is located.\n      </p>\n    </div>\n  );\n}\n\nexport function PlanetDexPane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n  const [planets, setPlanets] = useState<Planet[]>([]);\n\n  // update planet list on open / close\n  useEffect(() => {\n    if (!uiManager) return;\n    const myAddr = uiManager.getAccount();\n    if (!myAddr) return;\n    const ownedPlanets = uiManager.getAllOwnedPlanets().filter((planet) => planet.owner === myAddr);\n    setPlanets(ownedPlanets);\n  }, [visible, uiManager]);\n\n  // refresh planets every 10 seconds\n  useEffect(() => {\n    if (!uiManager) return;\n    if (!visible) return;\n\n    const refreshPlanets = () => {\n      if (!uiManager) return;\n      const myAddr = uiManager.getAccount();\n      if (!myAddr) return;\n      const ownedPlanets = uiManager\n        .getAllOwnedPlanets()\n        .filter((planet) => planet.owner === myAddr);\n      setPlanets(ownedPlanets);\n    };\n\n    const intervalId = setInterval(refreshPlanets, 10000);\n\n    return () => {\n      clearInterval(intervalId);\n    };\n  }, [visible, uiManager]);\n\n  const headers = ['', 'Planet Name', 'Level', 'Energy', 'Silver', 'Inventory'];\n  const alignments: Array<'r' | 'c' | 'l'> = ['r', 'l', 'r', 'r', 'r', 'r'];\n\n  const columns = [\n    (planet: Planet) => <PlanetThumb planet={planet} />,\n    (planet: Planet) => (\n      <PlanetLink planet={planet}>\n        <PlanetName>{getPlanetName(planet)}</PlanetName>\n      </PlanetLink>\n    ),\n    (planet: Planet) => <Sub>{planet.planetLevel}</Sub>,\n    (planet: Planet) => <Sub>{formatNumber(planet.energy)}</Sub>,\n    (planet: Planet) => <Sub>{formatNumber(planet.silver)}</Sub>,\n    (planet: Planet) => <Sub>{formatNumber(planet.heldArtifactIds.length)}</Sub>,\n  ];\n\n  const sortingFunctions = [\n    // thumb\n    (_a: Planet, _b: Planet): number => 0,\n    // name\n    (a: Planet, b: Planet): number => {\n      const [nameA, nameB] = [getPlanetName(a), getPlanetName(b)];\n      return nameA.localeCompare(nameB);\n    },\n    // level\n    (a: Planet, b: Planet): number => b.planetLevel - a.planetLevel,\n    // energy\n    (a: Planet, b: Planet): number => b.energy - a.energy,\n    // silver\n    (a: Planet, b: Planet): number => b.silver - a.silver,\n    // artifacts\n    (a: Planet, b: Planet): number => {\n      const [numArtifacts, scoreB] = [a.heldArtifactIds.length, b.heldArtifactIds.length];\n      return scoreB - numArtifacts;\n    },\n  ];\n\n  let content;\n\n  if (planets.length === 0) {\n    content = (\n      <CenterBackgroundSubtext width={RECOMMENDED_MODAL_WIDTH} height='100px'>\n        Loading Your Home Planet...\n      </CenterBackgroundSubtext>\n    );\n  } else {\n    content = (\n      <TableContainer>\n        <SortableTable\n          paginated={true}\n          rows={planets}\n          headers={headers}\n          columns={columns}\n          sortFunctions={sortingFunctions}\n          alignments={alignments}\n        />\n      </TableContainer>\n    );\n  }\n\n  return (\n    <ModalPane\n      visible={visible}\n      onClose={onClose}\n      id={ModalName.PlanetDex}\n      title='Planet Dex'\n      helpContent={HelpContent}\n    >\n      {content}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PlanetInfoPane.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { LocationId, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport { CenterBackgroundSubtext } from '../Components/CoreUI';\nimport { AccountLabel } from '../Components/Labels/Labels';\nimport { TextPreview } from '../Components/TextPreview';\nimport dfstyles from '../Styles/dfstyles';\nimport { usePlanet, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { TooltipTrigger } from './Tooltip';\n\n/**\n * This pane contains misc info about the planet, which does not have a place in the main Planet Context Pane.\n */\nexport function PlanetInfoPane({ initialPlanetId }: { initialPlanetId: LocationId | undefined }) {\n  const uiManager = useUIManager();\n  const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId);\n  const planet = usePlanet(uiManager, planetId).value;\n\n  if (!isLocatable(planet)) {\n    return (\n      <CenterBackgroundSubtext width='100%' height='200px'>\n        planet with <br /> unknown location\n      </CenterBackgroundSubtext>\n    );\n  } else {\n    return (\n      <>\n        <TooltipTrigger name={TooltipName.Empty} extraContent={<>id</>}>\n          <TextPreview\n            style={{ color: dfstyles.colors.subtext }}\n            text={planet?.locationId}\n            focusedWidth={'150px'}\n            unFocusedWidth={'150px'}\n          />\n        </TooltipTrigger>\n        <br />\n        <TooltipTrigger name={TooltipName.Empty} extraContent={<>coords</>}>\n          <TextPreview\n            style={{ color: dfstyles.colors.subtext }}\n            text={`(${planet.location.coords.x}, ${planet.location.coords.y})`}\n            focusedWidth={'150px'}\n            unFocusedWidth={'150px'}\n          />\n        </TooltipTrigger>\n        <br />\n        <TooltipTrigger name={TooltipName.Empty} extraContent={<>owner</>}>\n          <AccountLabel\n            style={{ color: dfstyles.colors.subtext }}\n            ethAddress={planet.owner}\n            includeAddressIfHasTwitter\n          />\n        </TooltipTrigger>\n      </>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PlayerArtifactsPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { ModalName } from '@darkforest_eth/types';\nimport React from 'react';\nimport { Spacer } from '../Components/CoreUI';\nimport { useMyArtifactsList, useUIManager } from '../Utils/AppHooks';\nimport { ModalHandle, ModalPane } from '../Views/ModalPane';\nimport { AllArtifacts } from './ArtifactsList';\n\nfunction HelpContent() {\n  return (\n    <div>\n      <p>These are all the artifacts you currently own.</p>\n      <Spacer height={8} />\n      <p>\n        The table is interactive, and allows you to sort the artifacts by clicking each column's\n        header. You can also view more information about a particular artifact by clicking on its\n        name.\n      </p>\n    </div>\n  );\n}\n\nexport function PlayerArtifactsPane({\n  visible,\n  onClose,\n}: {\n  visible: boolean;\n  onClose: () => void;\n}) {\n  const uiManager = useUIManager();\n  const artifacts = useMyArtifactsList(uiManager);\n\n  const render = (handle: ModalHandle) => <AllArtifacts modal={handle} artifacts={artifacts} />;\n\n  return (\n    <ModalPane\n      id={ModalName.YourArtifacts}\n      title={'Your Inventory'}\n      visible={visible}\n      onClose={onClose}\n      helpContent={HelpContent}\n      width={RECOMMENDED_MODAL_WIDTH}\n    >\n      {render}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PluginEditorPane.tsx",
    "content": "import { PluginId } from '@darkforest_eth/types';\nimport * as Prism from 'prismjs';\nimport * as React from 'react';\nimport { useState } from 'react';\nimport Editor from 'react-simple-code-editor';\nimport styled from 'styled-components';\nimport { PluginManager } from '../../Backend/GameLogic/PluginManager';\nimport { PLUGIN_TEMPLATE } from '../../Backend/Plugins/PluginTemplate';\nimport { Btn } from '../Components/Btn';\nimport { Spacer } from '../Components/CoreUI';\nimport { DarkForestTextInput, TextInput } from '../Components/Input';\nimport dfstyles from '../Styles/dfstyles';\nrequire('prismjs/themes/prism-dark.css');\n\n/**\n * Make sure the editor scrolls, and is always the same size.\n */\nconst EditorContainer = styled.div`\n  overflow-y: scroll;\n  border: 1px solid ${dfstyles.colors.borderDark};\n  border-radius: ${dfstyles.borderRadius};\n  width: 500px;\n  height: 500px;\n\n  .df-editor {\n    width: 100%;\n    min-height: 100%;\n  }\n`;\n\n/**\n * Component for editing plugins. Saving causes its containing modal\n * to be closed, and the `overwrite` to be called, indicating that the\n * given plugin's source should be overwritten and reloaded. If no\n * plugin id is provided, assumes we're editing a new plugin.\n */\nexport function PluginEditorPane({\n  pluginHost,\n  pluginId,\n  setIsOpen,\n  overwrite,\n}: {\n  pluginHost?: PluginManager | null;\n  pluginId?: PluginId;\n  setIsOpen: (open: boolean) => void;\n  overwrite: (newPluginName: string, newPluginCode: string, pluginId?: PluginId) => void;\n}) {\n  const plugin = pluginId ? pluginHost?.getPluginFromLibrary(pluginId) : undefined;\n\n  const [name, setName] = useState(plugin?.name);\n  const [code, setCode] = useState(plugin?.code || PLUGIN_TEMPLATE);\n\n  function onSaveClick() {\n    overwrite(name || 'Unnamed', code || '', pluginId);\n    setIsOpen(false);\n  }\n\n  function onNameInputChange(e: Event & React.ChangeEvent<DarkForestTextInput>) {\n    setName(e.target.value);\n  }\n\n  return (\n    <>\n      <TextInput placeholder='Unnamed' value={name ?? ''} onChange={onNameInputChange} />\n      <Spacer height={8} />\n      <EditorContainer>\n        <Editor\n          className={'df-editor'}\n          value={code || ''}\n          onValueChange={setCode}\n          highlight={(code) => Prism.highlight(code, Prism.languages.javascript, 'javascript')}\n          padding={10}\n          style={{\n            fontFamily: '\"Fira code\", \"Fira Mono\", monospace',\n            fontSize: 12,\n          }}\n          onKeyDown={(e) => e.stopPropagation()}\n          onKeyUp={(e) => e.stopPropagation()}\n        />\n      </EditorContainer>\n      <Spacer height={8} />\n      <Btn size='stretch' onClick={onSaveClick}>\n        Save\n      </Btn>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PluginLibraryPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { ModalName, PluginId, Setting } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport { ReactSortable } from 'react-sortablejs';\nimport styled from 'styled-components';\nimport { v4 as uuidv4 } from 'uuid';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { SerializedPlugin } from '../../Backend/Plugins/SerializedPlugin';\nimport { Btn } from '../Components/Btn';\nimport { Link, Spacer, Truncate } from '../Components/CoreUI';\nimport { PluginModal } from '../Components/PluginModal';\nimport { RemoteModal } from '../Components/RemoteModal';\nimport { Sub } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { getBooleanSetting, setSetting, useBooleanSetting } from '../Utils/SettingsHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { PluginEditorPane } from './PluginEditorPane';\n\nfunction HelpContent() {\n  return (\n    <div>\n      <p>\n        Plugins are bits of code that can be written by anyone, and allows the writer to program the\n        game. Plugins range from cosmetic (try the rage cage plugin) to functional (imagine a plugin\n        that fights your wars for you).\n      </p>\n      <Spacer height={8} />\n      <p>\n        Dark Forest maintains a repository to which community members can submit their own plugins.\n        You can find it <Link to='https://plugins.zkga.me/'>here</Link>.\n      </p>\n      <Spacer height={8} />\n      <p>Try editing one of the default plugins to see how it works!</p>\n    </div>\n  );\n}\n\nconst Actions = styled.div`\n  float: right;\n\n  .blue {\n    --df-button-hover-background: ${dfstyles.colors.dfblue};\n    --df-button-hover-border: 1px solid ${dfstyles.colors.dfblue};\n  }\n\n  .red {\n    --df-button-hover-background: ${dfstyles.colors.dfred};\n    --df-button-hover-border: 1px solid ${dfstyles.colors.dfred};\n  }\n\n  .green {\n    --df-button-hover-background: ${dfstyles.colors.dfgreen};\n    --df-button-hover-border: 1px solid ${dfstyles.colors.dfgreen};\n  }\n`;\n\n/**\n * This modal presents an overview of all of the player's plugins. Has a button to add a new plugin,\n * and lists out all the existing plugins, allowing the user to view their titles, as well as either\n * edit, delete, or open their modal.\n *\n * You can think of this as the plugin process list, the Activity Monitor of Dark forest.\n */\nexport function PluginLibraryPane({\n  gameUIManager,\n  visible,\n  onClose,\n  modalsContainer,\n}: {\n  gameUIManager: GameUIManager;\n  visible: boolean;\n  onClose: () => void;\n  modalsContainer: Element;\n}) {\n  const pluginManager = gameUIManager.getPluginManager();\n  const modalManager = gameUIManager.getModalManager();\n  const plugins = useEmitterValue(pluginManager.plugins$, pluginManager.getLibrary());\n  const contractAddress = gameUIManager.getContractAddress();\n  const account = gameUIManager.getAccount();\n  const config = { contractAddress, account };\n  const isAdmin = gameUIManager.isAdmin();\n  const [editorIsOpen, setEditorIsOpen] = useState(false);\n  const [warningIsOpen, setWarningIsOpen] = useState(false);\n  const [clicksUntilHasPlugins, setClicksUntilHasPlugins] = useState(8);\n  const [forceReloadEmbeddedPlugins, _s] = useBooleanSetting(\n    gameUIManager,\n    Setting.ForceReloadEmbeddedPlugins\n  );\n\n  /**\n   * the id of the plugin that the user is currently editing.\n   */\n  const [currentlyEditingPluginId, setEditingPluginId] = useState<PluginId | undefined>();\n\n  /**\n   * to get a unique editor for every time we open the editor. this means that every\n   * time you open the editor, you get a fresh copy of your plugin, or a blank state.\n   * if we did not do this, then the previous unsaved edits would persist in the editor\n   * ui.\n   */\n  const [editorNonce, setEditorNonce] = useState(0);\n\n  /**\n   * Opens an editor that would overwrite an existing plugin if one\n   * exists for the given plugin id. If one doesn't exist, opens\n   * an editor that will save a new plugin. Returns a function that\n   * closes the editor.\n   */\n  function openEditorForPlugin(pluginId?: PluginId) {\n    if (!account || !getBooleanSetting(config, Setting.HasAcceptedPluginRisk)) {\n      setWarningIsOpen(true);\n      return;\n    }\n\n    setWarningIsOpen(false);\n    setEditorIsOpen(true);\n    setEditorNonce(editorNonce + 1);\n\n    if (currentlyEditingPluginId !== pluginId) {\n      setEditingPluginId(pluginId);\n    }\n  }\n\n  function runPluginClicked(pluginId: PluginId) {\n    modalManager.setModalState(pluginId, 'open');\n  }\n\n  /**\n   * Overwrites the plugin with the given plugin id, killing its process\n   * if it has a process. If `pluginId` is undefined, saves a new plugin.\n   */\n  const saveAndReloadPlugin = (newName: string, newCode: string, pluginId?: PluginId): void => {\n    if (pluginId && newCode) {\n      pluginManager?.overwritePlugin(newName || 'no name', newCode, pluginId);\n    } else {\n      // Auto generate a PluginId\n      const pluginId = uuidv4() as PluginId;\n      pluginManager?.addPluginToLibrary(pluginId, newName || 'no name', newCode || '');\n    }\n  };\n\n  const onAcceptWarningClick = () => {\n    if (clicksUntilHasPlugins === 1) {\n      account && setSetting(config, Setting.HasAcceptedPluginRisk, true + '');\n      setWarningIsOpen(false);\n    }\n\n    setClicksUntilHasPlugins(clicksUntilHasPlugins - 1);\n  };\n\n  /**\n   * When we first load this component, make sure that we've loaded all\n   * the plugins from disk.\n   */\n  useEffect(() => {\n    pluginManager.load(isAdmin, forceReloadEmbeddedPlugins);\n  }, [pluginManager, isAdmin, forceReloadEmbeddedPlugins]);\n\n  function addPluginClicked(): void {\n    openEditorForPlugin(undefined);\n  }\n\n  function deletePluginClicked(pluginId: PluginId) {\n    if (confirm('are you sure you want to delete this plugin?')) {\n      pluginManager.deletePlugin(pluginId);\n      modalManager.clearModalPosition(pluginId);\n      setEditorIsOpen(false);\n    }\n  }\n\n  function onPluginReorder(newOrder: SerializedPlugin[]) {\n    pluginManager?.reorderPlugins(newOrder.map((p) => p.id));\n  }\n\n  /**\n   * The Dark Forest process list.\n   */\n  function renderPluginsList() {\n    if (plugins.length === 0) {\n      return 'you have no plugins!';\n    }\n\n    return (\n      <ReactSortable list={plugins} setList={onPluginReorder}>\n        {plugins.map((plugin) => (\n          <div key={plugin.id}>\n            <Truncate maxWidth={'150px'} style={{ verticalAlign: 'unset' }}>\n              <Sub>{plugin.name}</Sub>\n            </Truncate>\n\n            <Spacer width={8} />\n            <Actions>\n              <Btn className='blue' onClick={() => openEditorForPlugin(plugin.id)}>\n                edit\n              </Btn>\n              <Spacer width={4} />\n              <Btn className='red' onClick={() => deletePluginClicked(plugin.id)}>\n                del\n              </Btn>\n              <Spacer width={4} />\n              <Btn className='green' onClick={() => runPluginClicked(plugin.id)}>\n                run\n              </Btn>\n            </Actions>\n          </div>\n        ))}\n      </ReactSortable>\n    );\n  }\n\n  function onPluginClosed(pluginId: PluginId) {\n    pluginManager.destroy(pluginId);\n    modalManager.setModalState(pluginId, 'closed');\n  }\n  function onPluginRendered(pluginId: PluginId, el: HTMLDivElement) {\n    // This is `async` but we don't care about the result\n    pluginManager.render(pluginId, el);\n  }\n\n  const pluginModals = plugins.map((plugin) => {\n    return (\n      <PluginModal\n        key={plugin.id}\n        id={plugin.id}\n        title={plugin.name}\n        container={modalsContainer}\n        onClose={() => onPluginClosed(plugin.id)}\n        onRender={(el) => onPluginRendered(plugin.id, el)}\n      />\n    );\n  });\n\n  return (\n    <>\n      <RemoteModal\n        id={ModalName.PluginWarning}\n        container={modalsContainer}\n        title='WARNING'\n        visible={warningIsOpen}\n        onClose={() => setWarningIsOpen(false)}\n        width={RECOMMENDED_MODAL_WIDTH}\n      >\n        <p>\n          Dark Forest supports plugins, which allow you to write JavaScript code that can interact\n          with the game. Plugins are powerful and can enhance your gameplay experience, but they can\n          also be dangerous!\n        </p>\n        <br />\n        <p>\n          Be careful using plugins that were authored by somebody other than yourself! Plugins can\n          impersonate your account, and steal all your money. A malicious plugin could transfer all\n          your planets and artifacts to somebody else!\n        </p>\n        <br />\n        <Btn variant='danger' onClick={onAcceptWarningClick}>\n          Click {clicksUntilHasPlugins} times for Plugins\n        </Btn>\n      </RemoteModal>\n      <RemoteModal\n        id={ModalName.PluginEditor}\n        container={modalsContainer}\n        title='Plugin Editor'\n        visible={editorIsOpen}\n        onClose={() => setEditorIsOpen(false)}\n      >\n        <PluginEditorPane\n          key={currentlyEditingPluginId + '' + editorNonce}\n          pluginId={currentlyEditingPluginId}\n          setIsOpen={setEditorIsOpen}\n          pluginHost={pluginManager}\n          overwrite={saveAndReloadPlugin}\n        />\n      </RemoteModal>\n\n      {pluginModals}\n\n      <ModalPane\n        visible={visible}\n        onClose={onClose}\n        id={ModalName.Plugins}\n        title={'Plugin Library'}\n        helpContent={HelpContent}\n        width={RECOMMENDED_MODAL_WIDTH}\n      >\n        {renderPluginsList()}\n        <Spacer height={8} />\n        <Btn onClick={addPluginClicked}>Add Plugin</Btn>\n      </ModalPane>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/PrivatePane.tsx",
    "content": "import { ModalName } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Sub } from '../Components/Text';\nimport { TextPreview } from '../Components/TextPreview';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\n\nconst StyledPrivatePane = styled.div`\n  width: 36em;\n  height: 10em;\n  & > div {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-between;\n  }\n`;\nexport function PrivatePane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n\n  const [sKey, setSKey] = useState<string | undefined>(undefined);\n  const [home, setHome] = useState<string | undefined>(undefined);\n  useEffect(() => {\n    if (!uiManager) return;\n    setSKey(uiManager.getPrivateKey());\n    const coords = uiManager.getHomeCoords();\n    setHome(coords ? `(${coords.x}, ${coords.y})` : '');\n  }, [uiManager]);\n  return (\n    <ModalPane\n      id={ModalName.Private}\n      title='View Secret Key and Home Coords'\n      visible={visible}\n      onClose={onClose}\n    >\n      <StyledPrivatePane>\n        <p>\n          <Sub>\n            <u>secret key</u>\n          </Sub>\n        </p>\n        <p>\n          <TextPreview text={sKey} focusedWidth={'150px'} unFocusedWidth={'150px'} />\n        </p>\n        <br />\n        <p>\n          <Sub>\n            <u>Home Coords</u>\n          </Sub>\n        </p>\n        <p>{home}</p>\n      </StyledPrivatePane>\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/SettingsPane.tsx",
    "content": "import { EthConnection } from '@darkforest_eth/network';\nimport { AutoGasSetting, Chunk, ModalName, Setting } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport TutorialManager from '../../Backend/GameLogic/TutorialManager';\nimport { Btn } from '../Components/Btn';\nimport { Section, SectionHeader, Spacer } from '../Components/CoreUI';\nimport { DarkForestTextInput, TextInput } from '../Components/Input';\nimport { Slider } from '../Components/Slider';\nimport { Green, Red } from '../Components/Text';\nimport Viewport, { getDefaultScroll } from '../Game/Viewport';\nimport { useAccount, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport {\n  BooleanSetting,\n  ColorSetting,\n  MultiSelectSetting,\n  NumberSetting,\n} from '../Utils/SettingsHooks';\nimport { ModalPane } from '../Views/ModalPane';\n\nconst SCROLL_MIN = 0.0001 * 10000;\nconst SCROLL_MAX = 0.01 * 10000;\nconst DEFAULT_SCROLL = Math.round(10000 * (getDefaultScroll() - 1));\n\nconst SettingsContent = styled.div`\n  width: 500px;\n  height: 500px;\n  overflow-y: scroll;\n  display: flex;\n  flex-direction: column;\n  text-align: justify;\n`;\n\nconst Row = styled.div`\n  display: flex;\n  flex-direction: row;\n\n  justify-content: space-between;\n  align-items: center;\n\n  & > span:first-child {\n    flex-grow: 1;\n  }\n`;\n\nexport function SettingsPane({\n  ethConnection,\n  visible,\n  onClose,\n  onOpenPrivate,\n}: {\n  ethConnection: EthConnection;\n  visible: boolean;\n  onClose: () => void;\n  onOpenPrivate: () => void;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const isDevelopment = process.env.NODE_ENV !== 'production';\n  const gasPrices = useEmitterValue(ethConnection.gasPrices$, ethConnection.getAutoGasPrices());\n\n  const [rpcUrl, setRpcURL] = useState<string>(ethConnection.getRpcEndpoint());\n  const onChangeRpc = () => {\n    ethConnection\n      .setRpcUrl(rpcUrl)\n      .then(() => {\n        localStorage.setItem('XDAI_RPC_ENDPOINT_v5', rpcUrl);\n      })\n      .catch(() => {\n        setRpcURL(ethConnection.getRpcEndpoint());\n      });\n  };\n\n  const [balance, setBalance] = useState<number>(0);\n\n  useEffect(() => {\n    if (!uiManager) return;\n    const updateBalance = () => {\n      setBalance(uiManager.getMyBalance());\n    };\n\n    updateBalance();\n    const intervalId = setInterval(updateBalance, 1000);\n\n    return () => {\n      clearInterval(intervalId);\n    };\n  }, [uiManager]);\n\n  const [failure, setFailure] = useState<string>('');\n  const [success, setSuccess] = useState<string>('');\n  const [importMapByTextBoxValue, setImportMapByTextBoxValue] = useState('');\n  useEffect(() => {\n    if (failure) {\n      setSuccess('');\n    }\n  }, [failure]);\n  useEffect(() => {\n    if (success) {\n      setFailure('');\n    }\n  }, [success]);\n  const onExportMap = async () => {\n    if (uiManager) {\n      const chunks = uiManager.getExploredChunks();\n      const chunksAsArray = Array.from(chunks);\n      try {\n        const map = JSON.stringify(chunksAsArray);\n        await window.navigator.clipboard.writeText(map);\n        setSuccess('Copied map!');\n      } catch (err) {\n        console.error(err);\n        setFailure('Failed to export');\n      }\n    } else {\n      setFailure('Unable to export map right now.');\n    }\n  };\n  const onImportMapFromTextBox = async () => {\n    try {\n      const chunks = JSON.parse(importMapByTextBoxValue);\n      await uiManager.bulkAddNewChunks(chunks as Chunk[]);\n      setImportMapByTextBoxValue('');\n    } catch (e) {\n      setFailure('Invalid map data. Check the data in your clipboard.');\n    }\n  };\n  const onImportMap = async () => {\n    if (uiManager) {\n      let input;\n      try {\n        input = await window.navigator.clipboard.readText();\n      } catch (err) {\n        console.error(err);\n        setFailure('Unable to import map. Did you allow clipboard access?');\n        return;\n      }\n\n      let chunks;\n      try {\n        chunks = JSON.parse(input);\n      } catch (err) {\n        console.error(err);\n        setFailure('Invalid map data. Check the data in your clipboard.');\n        return;\n      }\n      await uiManager.bulkAddNewChunks(chunks as Chunk[]);\n      setSuccess('Successfully imported a map!');\n    } else {\n      setFailure('Unable to import map right now.');\n    }\n  };\n\n  const [clicks, setClicks] = useState<number>(8);\n  const doPrivateClick = () => {\n    setClicks((x) => x - 1);\n    if (clicks === 1) {\n      onOpenPrivate();\n      setClicks(5);\n    }\n  };\n\n  const [scrollSpeed, setScrollSpeed] = useState<number>(DEFAULT_SCROLL);\n  const onScrollChange = (e: Event & React.ChangeEvent<HTMLInputElement>) => {\n    const value = parseFloat(e.target.value);\n    if (!isNaN(value)) setScrollSpeed(value);\n  };\n\n  useEffect(() => {\n    const scroll = localStorage.getItem('scrollSpeed');\n    if (scroll) {\n      setScrollSpeed(10000 * (parseFloat(scroll) - 1));\n    }\n  }, [setScrollSpeed]);\n\n  useEffect(() => {\n    if (!Viewport.instance) return;\n    Viewport.instance.setMouseSensitivty(scrollSpeed / 10000);\n  }, [scrollSpeed]);\n\n  return (\n    <ModalPane id={ModalName.Settings} title='Settings' visible={visible} onClose={onClose}>\n      <SettingsContent>\n        {isDevelopment && (\n          <Section>\n            <SectionHeader>Development</SectionHeader>\n            <BooleanSetting\n              uiManager={uiManager}\n              setting={Setting.ForceReloadEmbeddedPlugins}\n              settingDescription={'force reload embedded plugins'}\n            />\n          </Section>\n        )}\n\n        <Section>\n          <SectionHeader>Burner Wallet Info</SectionHeader>\n          <Row>\n            <span>Public Key</span>\n            <span>{account}</span>\n          </Row>\n          <Row>\n            <span>Balance</span>\n            <span>{balance}</span>\n          </Row>\n        </Section>\n\n        <Section>\n          <SectionHeader>Gas Price</SectionHeader>\n          Your gas price setting determines the price you pay for each transaction. A higher gas\n          price means your transactions will be prioritized by the blockchain, making them confirm\n          faster. We recommend using the auto average setting. All auto settings prices are pulled\n          from an oracle and are capped at 15 gwei.\n          <Spacer height={16} />\n          <MultiSelectSetting\n            wide\n            uiManager={uiManager}\n            setting={Setting.GasFeeGwei}\n            values={[\n              '1',\n              '2',\n              '5',\n              '10',\n              '20',\n              '40',\n              AutoGasSetting.Slow,\n              AutoGasSetting.Average,\n              AutoGasSetting.Fast,\n            ]}\n            labels={[\n              '1 gwei (default)',\n              '2 gwei (faster)',\n              '5 gwei (turbo)',\n              '10 gwei (mega turbo)',\n              '20 gwei (need4speed)',\n              '40 gwei (gigafast)',\n              `slow auto (~${gasPrices.slow} gwei)`,\n              `average auto (~${gasPrices.average} gwei)`,\n              `fast auto (~${gasPrices.fast} gwei)`,\n            ]}\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Burner Wallet Info (Private)</SectionHeader>\n          Your secret key, together with your home planet's coordinates, grant you access to your\n          Dark Forest account on different browsers. You should save this info somewhere on your\n          computer.\n          <Spacer height={16} />\n          <Red>WARNING:</Red> Never ever send this to anyone!\n          <Spacer height={8} />\n          <Btn size='stretch' variant='danger' onClick={doPrivateClick}>\n            Click {clicks} times to view info\n          </Btn>\n        </Section>\n\n        <Section>\n          <SectionHeader>Auto Confirm Transactions</SectionHeader>\n          Whether or not to auto-confirm all transactions, except purchases. This will allow you to\n          make moves, spend silver on upgrades, etc. without requiring you to confirm each\n          transaction. However, the client WILL ask for confirmation before sending transactions\n          that spend wallet funds.\n          <Spacer height={16} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.AutoApproveNonPurchaseTransactions}\n            settingDescription={'auto confirm non-purchase transactions'}\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Import and Export Map Data</SectionHeader>\n          <Red>WARNING:</Red> Maps from others could be altered and are not guaranteed to be\n          correct!\n          <Spacer height={16} />\n          <TextInput\n            value={importMapByTextBoxValue}\n            placeholder={'Paste map contents here'}\n            onChange={(e: Event & React.ChangeEvent<DarkForestTextInput>) =>\n              setImportMapByTextBoxValue(e.target.value)\n            }\n          />\n          <Spacer height={8} />\n          <Btn\n            size='stretch'\n            onClick={onImportMapFromTextBox}\n            disabled={importMapByTextBoxValue.length === 0}\n          >\n            Import Map From Above\n          </Btn>\n          <Spacer height={8} />\n          <Btn size='stretch' onClick={onExportMap}>\n            Copy Map to Clipboard\n          </Btn>\n          <Spacer height={8} />\n          <Btn size='stretch' onClick={onImportMap}>\n            Import Map from Clipboard\n          </Btn>\n          <Spacer height={8} />\n          <Green>{success}</Green>\n          <Red>{failure}</Red>\n        </Section>\n\n        <Section>\n          <SectionHeader>Change RPC Endpoint</SectionHeader>\n          <Spacer height={8} />\n          Current RPC Endpoint: {rpcUrl}\n          <Spacer height={8} />\n          <TextInput\n            value={rpcUrl}\n            onChange={(e: Event & React.ChangeEvent<DarkForestTextInput>) =>\n              setRpcURL(e.target.value)\n            }\n          />\n          <Spacer height={8} />\n          <Btn size='stretch' onClick={onChangeRpc}>\n            Change RPC URL\n          </Btn>\n        </Section>\n\n        <Section>\n          <SectionHeader>Metrics Opt Out</SectionHeader>\n          We collect a minimal set of data and statistics such as SNARK proving times, average\n          transaction times across browsers, and xDAI transaction errors, to help us optimize\n          performance and fix bugs. This does not include personal data like email or IP address.\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.OptOutMetrics}\n            settingDescription='metrics opt out'\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Performance</SectionHeader>\n          High performance mode turns off background rendering, and reduces the detail at which\n          smaller planets are rendered.\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.HighPerformanceRendering}\n            settingDescription='high performance mode'\n          />\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.DisableEmojiRendering}\n            settingDescription='disable emoji rendering'\n          />\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.DisableHatRendering}\n            settingDescription='disable hat rendering'\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Notifications</SectionHeader>\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.MoveNotifications}\n            settingDescription='show notifications for move transactions'\n          />\n          <Spacer height={8} />\n          Auto clear transaction confirmation notifications after this many seconds. Set to a\n          negative number to not auto-clear.\n          <Spacer height={8} />\n          <NumberSetting\n            uiManager={uiManager}\n            setting={Setting.AutoClearConfirmedTransactionsAfterSeconds}\n          />\n          <Spacer height={8} />\n          Auto clear transaction rejection notifications after this many seconds. Set to a negative\n          number to not auto-clear.\n          <NumberSetting\n            uiManager={uiManager}\n            setting={Setting.AutoClearRejectedTransactionsAfterSeconds}\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Scroll speed</SectionHeader>\n          <Spacer height={8} />\n          <Slider\n            variant='filled'\n            editable={true}\n            labelVisibility='none'\n            value={scrollSpeed}\n            min={SCROLL_MIN}\n            max={SCROLL_MAX}\n            step={SCROLL_MIN / 10}\n            onChange={onScrollChange}\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Reset Tutorial</SectionHeader>\n          <Spacer height={8} />\n          <Btn size='stretch' onClick={() => TutorialManager.getInstance(uiManager).reset()}>\n            Reset Tutorial\n          </Btn>\n        </Section>\n\n        <Section>\n          <SectionHeader>Disable Default Shortcuts</SectionHeader>\n          If you'd like to use custom shortcuts via a plugin, you can disable the default shortcuts\n          here.\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.DisableDefaultShortcuts}\n            settingDescription='toggle disable default shortcuts'\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Enable Experimental Features</SectionHeader>\n          Features that aren't quite ready for production but we think are cool.\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.ExperimentalFeatures}\n            settingDescription='toggle expeirmental features'\n          />\n        </Section>\n\n        <Section>\n          <SectionHeader>Renderer Settings</SectionHeader>\n          Some options for the default renderer which is included with the game.\n          <Spacer height={8} />\n          <BooleanSetting\n            uiManager={uiManager}\n            setting={Setting.DisableFancySpaceEffect}\n            settingDescription='disable fancy space shaders'\n          />\n          <Spacer height={8} />\n          <ColorSetting\n            uiManager={uiManager}\n            setting={Setting.RendererColorInnerNebula}\n            settingDescription='inner nebula color'\n          />\n          <ColorSetting\n            uiManager={uiManager}\n            setting={Setting.RendererColorNebula}\n            settingDescription='nebula color'\n          />\n          <ColorSetting\n            uiManager={uiManager}\n            setting={Setting.RendererColorSpace}\n            settingDescription='space color'\n          />\n          <ColorSetting\n            uiManager={uiManager}\n            setting={Setting.RendererColorDeepSpace}\n            settingDescription='deep space color'\n          />\n          <ColorSetting\n            uiManager={uiManager}\n            setting={Setting.RendererColorDeadSpace}\n            settingDescription='dead space color'\n          />\n        </Section>\n      </SettingsContent>\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/Tooltip.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { TooltipName } from '@darkforest_eth/types';\nimport React, { useEffect, useLayoutEffect, useRef, useState } from 'react';\nimport ReactDOM from 'react-dom';\nimport styled from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\nimport { useOverlayContainer } from '../Utils/AppHooks';\nimport { DFZIndex } from '../Utils/constants';\nimport { TooltipContent } from './TooltipPanes';\n\n/**\n * Each {@link TooltipName} has a corresponding tooltip element.\n */\nexport interface TooltipTriggerProps {\n  /**\n   * The name of the tooltip element to display. You can see all the concrete tooltip contents in\n   * the file called {@link TooltipPanes}. Set to `undefined` to not render the tooltip.\n   */\n  name: TooltipName | undefined;\n\n  /**\n   * A {@link TooltipTrigger} wraps this child, and causes a tooltip to appear when the user hovers\n   * over it.\n   */\n  children: React.ReactNode;\n\n  /**\n   * You can append some dynamic content to the given tooltip by setting this field to a React node.\n   */\n  extraContent?: React.ReactNode;\n\n  /**\n   * You can optionally style the tooltip trigger element, not the tooltip itself.\n   */\n  style?: React.CSSProperties;\n}\n\nexport interface TooltipProps extends TooltipTriggerProps {\n  top: number;\n  left: number;\n}\n\n/**\n * When the player hovers over this element, triggers the tooltip with the given name to be\n * displayed on top of everything.\n */\nexport function TooltipTrigger(props: TooltipTriggerProps) {\n  const overlayContainer = useOverlayContainer();\n  const [hovering, setHovering] = useState(false);\n  const [mouseCoords, setMouseCoords] = useState({ x: 0, y: 0 });\n\n  useEffect(() => {\n    const doMouseMove = (e: MouseEvent) => {\n      setMouseCoords({ x: e.clientX, y: e.clientY });\n    };\n\n    window.addEventListener('mousemove', doMouseMove);\n\n    return () => {\n      window.removeEventListener('mousemove', doMouseMove);\n    };\n  });\n\n  return (\n    <>\n      <StyledTooltipTrigger\n        style={{ ...props.style }}\n        onMouseEnter={() => setHovering(true)}\n        onMouseLeave={() => setHovering(false)}\n      >\n        {props.children}\n      </StyledTooltipTrigger>\n\n      {overlayContainer &&\n        hovering &&\n        ReactDOM.createPortal(\n          <Tooltip {...props} top={mouseCoords.y} left={mouseCoords.x} />,\n          overlayContainer\n        )}\n    </>\n  );\n}\n\n/**\n * At any given moment, there can only be one tooltip visible in the game. This is true because a\n * player only has one mouse cursor on the screen, and therefore can only be hovering over a single\n * {@link TooltipTrigger} element at any given time. This component is responsible for keeping track\n * of which tooltip has been hovered over, and rendering the corresponding content.\n */\nexport function Tooltip(props: TooltipProps) {\n  const elRef = useRef<HTMLDivElement>(document.createElement('div'));\n\n  const [height, setHeight] = useState<number>(20);\n  const [width, setWidth] = useState<number>(20);\n\n  const [leftOffset, setLeftOffset] = useState<number>(10);\n  const [topOffset, setTopOffset] = useState<number>(10);\n\n  // sync size to content size\n  useLayoutEffect(() => {\n    setHeight(elRef.current.offsetHeight);\n    setWidth(elRef.current.offsetWidth);\n  }, [elRef.current.offsetHeight, elRef]);\n\n  // point it in the right direction based on quadrant\n  useLayoutEffect(() => {\n    if (props.left < window.innerWidth / 2) {\n      setLeftOffset(10);\n    } else {\n      setLeftOffset(-10 - width);\n    }\n\n    if (props.top < window.innerHeight / 2) {\n      setTopOffset(10);\n    } else {\n      setTopOffset(-10 - height);\n    }\n  }, [width, height, props.left, props.top]);\n\n  if (props.name !== undefined) {\n    return (\n      <StyledTooltip\n        ref={elRef}\n        onMouseEnter={(e) => e.preventDefault()}\n        onMouseLeave={(e) => e.preventDefault()}\n        style={{\n          top: `${props.top + topOffset}px`,\n          left: `${props.left + leftOffset}px`,\n        }}\n      >\n        <TooltipContent name={props.name} />\n        {props.extraContent}\n      </StyledTooltip>\n    );\n  }\n\n  return null;\n}\n\nconst StyledTooltipTrigger = styled.span`\n  border-radius: 2px;\n  display: ${(props) => props?.style?.display || 'inline'};\n`;\n\nconst StyledTooltip = styled.div`\n  max-width: ${RECOMMENDED_MODAL_WIDTH};\n  position: absolute;\n  border: 1px solid ${dfstyles.colors.border};\n  background: ${dfstyles.colors.background};\n  padding: 0.5em 1em;\n  border-radius: 3px;\n  line-height: 1.5em;\n  z-index: ${DFZIndex.Tooltip};\n`;\n"
  },
  {
    "path": "src/Frontend/Panes/TooltipPanes.tsx",
    "content": "import { PlanetType, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport { getPlanetRank, isFullRank } from '../../Backend/Utils/Utils';\nimport { ScoreLabel, SilverLabel } from '../Components/Labels/KeywordLabels';\nimport { Green, Red, Text, White } from '../Components/Text';\nimport { useAccount, useSelectedPlanet, useUIManager } from '../Utils/AppHooks';\n\nexport function NetworkHealthPane() {\n  return (\n    <>\n      <White>xDAI Tx Speed: </White>For each auto gas setting (which you can choose in the{' '}\n      <White>Settings</White> Pane), the average amount of time it takes a transaction with that\n      setting to confirm. The Dark Forest client uploads diagnostic info (you can turn this off via\n      settings), which is aggregated into this network health indicator. I hope you find it helpful\n      in cases the network is being slow.\n    </>\n  );\n}\n\nexport function WithdrawSilverButton() {\n  return (\n    <>\n      This is a <Text>Spacetime Rip</Text> where you can withdraw <SilverLabel /> for <ScoreLabel />\n      !\n    </>\n  );\n}\nexport function DefenseMultiplierPane() {\n  return <>Defense multiplier</>;\n}\n\nexport function EnergyCapMultiplierPane() {\n  return <>EnergyCap multiplier</>;\n}\n\nexport function EnergyGrowthMultiplierPane() {\n  return <>EnergyGrowth multiplier</>;\n}\n\nexport function RangeMultiplierPane() {\n  return <>Range multiplier</>;\n}\n\nexport function SpeedMultiplierPane() {\n  return <>Speed multiplier</>;\n}\n\nexport function DepositArtifactPane() {\n  return <>Deposit this artifact</>;\n}\n\nexport function DeactivateArtifactPane() {\n  return <>Deactivate this artifact</>;\n}\n\nexport function WithdrawArtifactPane() {\n  return <>Withdraw this artifact</>;\n}\n\nexport function ActivateArtifactPane() {\n  return <>Activate this artifact</>;\n}\n\nexport function TimeUntilActivationPossiblePane() {\n  return <>You must wait this amount of time before you can activate this artifact</>;\n}\n\nexport function TwitterHandleTooltipPane() {\n  return (\n    <>\n      You may connect your account to <White>Twitter</White>\n      <br />\n      to identify yourself on the <White>Leaderboard</White>.\n    </>\n  );\n}\n\nexport function RankTooltipPane() {\n  return (\n    <>\n      Your current rank, based on <ScoreLabel />.\n    </>\n  );\n}\n\nexport function ScoreTooltipPane() {\n  return (\n    <>\n      You earn <ScoreLabel /> by finding artifacts and withdrawing silver. Check out the{' '}\n      <White>Help Pane</White> for more info on scoring.\n    </>\n  );\n}\n\nexport function MiningPauseTooltipPane() {\n  return (\n    <>\n      Start / Stop your <White>explorer</White>. Your explorer looks for planets in chunks of{' '}\n      <White>16</White> x <White>16</White>.\n    </>\n  );\n}\n\nexport function MiningTargetTooltipPane() {\n  return (\n    <>\n      Change the location of your <White>explorer</White>. Click anywhere on the{' '}\n      <White>Game Screen</White>, and your <White>miner</White> will start hashing around that\n      chunk.\n    </>\n  );\n}\n\nexport function CurrentMiningTooltipPane() {\n  return (\n    <>\n      The current coordinates of your <White>explorer</White>.\n    </>\n  );\n}\n\nexport function BonusTooltipPane() {\n  return (\n    <>\n      <Green>This stat has been randomly doubled!</Green>\n    </>\n  );\n}\n\nexport function SilverTooltipPane() {\n  return (\n    <>\n      <White>Silver:</White> the universe's monetary resource. It allows you to buy upgrades. Only\n      <White> Asteroid Fields</White> produce silver or so we've been told...\n    </>\n  );\n}\n\nexport function EnergyTooltipPane() {\n  return (\n    <>\n      <White>Energy:</White> allows you to make moves. Energy grows following an{' '}\n      <White>s-curve</White>, and grows fastest at <White>50% capacity</White>.\n    </>\n  );\n}\n\nexport function PlanetRankTooltipPane() {\n  const uiManager = useUIManager();\n  const selected = useSelectedPlanet(uiManager);\n  const rank = getPlanetRank(selected.value);\n  return (\n    <>\n      This planet is <White>{isFullRank(selected.value) ? 'fully upgraded' : 'rank ' + rank}</White>\n      !\n    </>\n  );\n}\n\nexport function MaxLevelTooltipPane() {\n  return (\n    <>\n      This planet is <White>Level 9</White>, making it one of the <br />\n      most powerful planets in the galaxy!\n    </>\n  );\n}\n\nexport function SilverProdTooltipPane() {\n  return (\n    <>\n      This planet produces <White>Silver</White>! Use it to buy upgrades!\n    </>\n  );\n}\n\nexport function SelectedSilverTooltipPane() {\n  const uiManager = useUIManager();\n  const selected = useSelectedPlanet(uiManager);\n\n  return (\n    <>\n      {selected.value ? (\n        <>\n          <>\n            Silver:\n            <span>{selected.value.silver}</span>\n          </>\n          <>\n            Cap:\n            <span>{selected.value.silverCap}</span>\n          </>\n          {selected.value.planetType === PlanetType.SILVER_MINE ? (\n            <>\n              Growth:\n              <span>{selected.value.silverGrowth * 60}</span>\n            </>\n          ) : (\n            <>\n              <Red>This planet does not produce silver.</Red>\n            </>\n          )}\n        </>\n      ) : (\n        <>Select a planet to view more about its stats. </>\n      )}\n    </>\n  );\n}\n\nexport function RangeTooltipPane() {\n  return (\n    <>\n      <White>Range:</White> how far you can send your forces. <White>Forces decay</White> the\n      farther out you send them. <br />\n      Higher range means that you can send forces the same distance with less decay.\n    </>\n  );\n}\n\nexport function MinEnergyTooltipPane() {\n  return (\n    <>\n      The minimum energy you need to send a move from this planet. <br />\n      Moves incur a base cost of 5% of the planet's <White>Energy Cap</White>.\n    </>\n  );\n}\n\nexport function Time50TooltipPane() {\n  return (\n    <>\n      Time to <White>50%</White> of full energy.\n    </>\n  );\n}\n\nexport function Time90TooltipPane() {\n  return (\n    <>\n      Time to <White>90%</White> of full energy. Since energy grows on an s-curve, energy growth\n      slows drastically by this point.\n    </>\n  );\n}\n\nexport function EnergyGrowthTooltipPane() {\n  return (\n    <>\n      <White>Energy Growth:</White> the maximum growth rate of this planet's energy representing the\n      rate at the middle of the <White>s-curve</White>.\n    </>\n  );\n}\n\nexport function SilverGrowthTooltipPane() {\n  return (\n    <>\n      <White>Silver Growth</White>: the per-minute linear growth rate of this planet's silver.\n    </>\n  );\n}\n\nexport function SilverCapTooltipPane() {\n  return (\n    <>\n      <White>Silver Cap</White>: the maximum silver that this planet can hold.\n    </>\n  );\n}\n\nexport function PiratesTooltipPane() {\n  return (\n    <>\n      <Red>This planet has space pirates!</Red> Move energy to unoccupied planets to conquer them!\n    </>\n  );\n}\n\nexport function UpgradesTooltipPane() {\n  return (\n    <>\n      <White>Planet Rank</White>: the number of times you've upgraded your planet.\n    </>\n  );\n}\n\nexport function ModalHelpTooltipPane() {\n  return <>View patch notes and instructions</>;\n}\n\nexport function ModalPlanetDetailsTooltipPane() {\n  return <>View detailed information about the selected planet</>;\n}\n\nexport function ModalLeaderboardTooltipPane() {\n  return <>View the top players, and their top planets</>;\n}\n\nexport function ModalPlanetDexTooltipPane() {\n  return <>View a list of your planets</>;\n}\n\nexport function ModalUpgradeDetailsTooltipPane() {\n  return <>Upgrade the selected planet</>;\n}\n\nexport function ModalTwitterVerificationTooltipPane() {\n  return <>Connect your address to Twitter</>;\n}\n\nexport function ModalBroadcastTooltipPane() {\n  return <>Broadcast the selected planet's coordinates to the world</>;\n}\n\nexport function BonusEnergyCapTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Energy Cap</White> has been randomly doubled!\n      </Green>\n    </>\n  );\n}\n\nexport function BonusEnergyGroTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Energy Growth</White> has been randomly doubled!\n      </Green>\n    </>\n  );\n}\n\nexport function BonusRangeTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Range</White> has been randomly doubled!\n      </Green>\n    </>\n  );\n}\n\nexport function BonusSpeedTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Speed</White> has been randomly doubled!\n      </Green>\n    </>\n  );\n}\n\nexport function BonusDefenseTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Defense</White> has been randomly doubled!\n      </Green>\n    </>\n  );\n}\n\nexport function BonusSpaceJunkTooltipPane() {\n  return (\n    <>\n      <Green>\n        This planet's <White>Space Junk</White> has been randomly halved!\n      </Green>\n    </>\n  );\n}\n\nexport function ClowntownTooltipPane() {\n  const uiManager = useUIManager();\n  const selected = useSelectedPlanet(uiManager);\n  const account = useAccount(uiManager);\n\n  return (\n    <>\n      <span>\n        {selected.value?.owner === account\n          ? `You are the proud mayor of Clown Town!`\n          : `It's a town of clowns...`}\n      </span>\n    </>\n  );\n}\n\nfunction DefenseTooltipPane() {\n  return (\n    <>\n      <White>Defense:</White> Planets with higher defense will negate incoming damage. Planets with\n      lower than 100 defense are vulnerable and will take more damage!\n    </>\n  );\n}\n\nfunction SpaceJunkTooltipPane() {\n  return (\n    <>\n      <White>Space Junk:</White> Planets are all filled with junk! Sending energy to a planet with\n      junk will remove the junk from that planet and add it to your total junk. Once you reach your\n      junk limit, you will not be able to capture planets that have junk. Abandoning planets will\n      reduce your space junk and place it back on the planet.\n    </>\n  );\n}\n\nfunction AbandonTooltipPane() {\n  const uiManager = useUIManager();\n  const abandonSpeedBoost = uiManager.getAbandonSpeedChangePercent() / 100;\n  const abandonRangeBoost = uiManager.getAbandonRangeChangePercent() / 100;\n\n  return (\n    <>\n      <Red>Abandon your planet:</Red> Give up ownership of this planet to dump some of your space\n      junk here. This triggers a special movement that sends full <White>Energy/Silver</White> and\n      gives a <Green>Range boost of {abandonRangeBoost}x</Green> and a{' '}\n      <Green>Speed boost of {abandonSpeedBoost}x</Green>.\n      <br />\n      <Red>You cannot abandon your home planet, or a planet that has incoming voyages.</Red>\n    </>\n  );\n}\n\nfunction SpeedTooltipPane() {\n  return (\n    <>\n      <White>Speed:</White> The rate at which energy travels across the universe, the faster the\n      better!\n    </>\n  );\n}\n\nfunction RetryTransactionPane() {\n  return <>Retry transaction.</>;\n}\n\nfunction CancelTransactionPane() {\n  return <>Cancel transaction.</>;\n}\n\nfunction PrioritizeTransactionPane() {\n  return <>Prioritize transaction.</>;\n}\n\nfunction ArtifactBuffPane() {\n  return <>A powerful artifact on this planet is influencing this stat!</>;\n}\n\nfunction PluginsTooltipPane() {\n  return <>Manage plugins, which allow you to add functionality to the client.</>;\n}\n\nfunction SettingsPane() {\n  return <>Manage settings - export SKEY, manage maps, and more.</>;\n}\n\nfunction YourArtifacts() {\n  return <>View your artifacts.</>;\n}\n\nfunction InvadablePane() {\n  return <>This planet is in a scoring zone and can be invaded</>;\n}\n\nfunction CapturablePane() {\n  return <>This planet has been invaded, which means you can capture it for score.</>;\n}\n\nconst ModalWithdrawSilverTooltipPane = () => <>Withdraw silver to earn score.</>;\n\nconst Hats = () => <>Buy hats for the selected planet.</>;\n\nconst FindArtifact = () => (\n  <>\n    <Green>This planet has a powerful artifact hidden somewhere!</Green> Maybe you could find it...\n  </>\n);\n\nconst ArtifactStored = () => <>This planet has a powerful artifact on it!</>;\n\nconst HashesPerSec = () => <>hashes / sec</>;\n\nexport function TooltipContent({ name }: { name: TooltipName | undefined }) {\n  if (name === TooltipName.SilverGrowth) return <SilverGrowthTooltipPane />;\n  if (name === TooltipName.SilverCap) return <SilverCapTooltipPane />;\n  if (name === TooltipName.Silver) return <SilverTooltipPane />;\n  if (name === TooltipName.Energy) return <EnergyTooltipPane />;\n  if (name === TooltipName.EnergyGrowth) return <EnergyGrowthTooltipPane />;\n  if (name === TooltipName.Range) return <RangeTooltipPane />;\n  if (name === TooltipName.TwitterHandle) return <TwitterHandleTooltipPane />;\n  if (name === TooltipName.Bonus) return <BonusTooltipPane />;\n  if (name === TooltipName.MinEnergy) return <MinEnergyTooltipPane />;\n  if (name === TooltipName.Time50) return <Time50TooltipPane />;\n  if (name === TooltipName.Time90) return <Time90TooltipPane />;\n  if (name === TooltipName.Pirates) return <PiratesTooltipPane />;\n  if (name === TooltipName.Upgrades) return <UpgradesTooltipPane />;\n  if (name === TooltipName.PlanetRank) return <PlanetRankTooltipPane />;\n  if (name === TooltipName.MaxLevel) return <MaxLevelTooltipPane />;\n  if (name === TooltipName.SelectedSilver) return <SelectedSilverTooltipPane />;\n  if (name === TooltipName.Rank) return <RankTooltipPane />;\n  if (name === TooltipName.Score) return <ScoreTooltipPane />;\n  if (name === TooltipName.MiningPause) return <MiningPauseTooltipPane />;\n  if (name === TooltipName.MiningTarget) return <MiningTargetTooltipPane />;\n  if (name === TooltipName.CurrentMining) return <CurrentMiningTooltipPane />;\n  if (name === TooltipName.SilverProd) return <SilverProdTooltipPane />;\n  if (name === TooltipName.BonusEnergyCap) return <BonusEnergyCapTooltipPane />;\n  if (name === TooltipName.BonusEnergyGro) return <BonusEnergyGroTooltipPane />;\n  if (name === TooltipName.BonusRange) return <BonusRangeTooltipPane />;\n  if (name === TooltipName.BonusSpeed) return <BonusSpeedTooltipPane />;\n  if (name === TooltipName.BonusDefense) return <BonusDefenseTooltipPane />;\n  if (name === TooltipName.BonusSpaceJunk) return <BonusSpaceJunkTooltipPane />;\n  if (name === TooltipName.Clowntown) return <ClowntownTooltipPane />;\n  if (name === TooltipName.ModalHelp) return <ModalHelpTooltipPane />;\n  if (name === TooltipName.ModalPlanetDetails) return <ModalPlanetDetailsTooltipPane />;\n  if (name === TooltipName.ModalLeaderboard) return <ModalLeaderboardTooltipPane />;\n  if (name === TooltipName.ModalPlanetDex) return <ModalPlanetDexTooltipPane />;\n  if (name === TooltipName.ModalUpgradeDetails) return <ModalUpgradeDetailsTooltipPane />;\n  if (name === TooltipName.ModalTwitterVerification) return <ModalTwitterVerificationTooltipPane />;\n  if (name === TooltipName.ModalTwitterBroadcast) return <ModalBroadcastTooltipPane />;\n  if (name === TooltipName.Defense) return <DefenseTooltipPane />;\n  if (name === TooltipName.SpaceJunk) return <SpaceJunkTooltipPane />;\n  if (name === TooltipName.Abandon) return <AbandonTooltipPane />;\n  if (name === TooltipName.Speed) return <SpeedTooltipPane />;\n  if (name === TooltipName.ArtifactBuff) return <ArtifactBuffPane />;\n  if (name === TooltipName.ModalPlugins) return <PluginsTooltipPane />;\n  if (name === TooltipName.ModalSettings) return <SettingsPane />;\n  if (name === TooltipName.ModalYourArtifacts) return <YourArtifacts />;\n  if (name === TooltipName.ModalHats) return <Hats />;\n  if (name === TooltipName.FindArtifact) return <FindArtifact />;\n  if (name === TooltipName.ArtifactStored) return <ArtifactStored />;\n  if (name === TooltipName.HashesPerSec) return <HashesPerSec />;\n  if (name === TooltipName.ModalWithdrawSilver) return <ModalWithdrawSilverTooltipPane />;\n  if (name === TooltipName.TimeUntilActivationPossible) return <TimeUntilActivationPossiblePane />;\n  if (name === TooltipName.DepositArtifact) return <DepositArtifactPane />;\n  if (name === TooltipName.DeactivateArtifact) return <DeactivateArtifactPane />;\n  if (name === TooltipName.WithdrawArtifact) return <WithdrawArtifactPane />;\n  if (name === TooltipName.ActivateArtifact) return <ActivateArtifactPane />;\n  if (name === TooltipName.DefenseMultiplier) return <DefenseMultiplierPane />;\n  if (name === TooltipName.EnergyCapMultiplier) return <EnergyCapMultiplierPane />;\n  if (name === TooltipName.EnergyGrowthMultiplier) return <EnergyGrowthMultiplierPane />;\n  if (name === TooltipName.RangeMultiplier) return <RangeMultiplierPane />;\n  if (name === TooltipName.SpeedMultiplier) return <SpeedMultiplierPane />;\n  if (name === TooltipName.NetworkHealth) return <NetworkHealthPane />;\n  if (name === TooltipName.WithdrawSilverButton) return <WithdrawSilverButton />;\n  if (name === TooltipName.RetryTransaction) return <RetryTransactionPane />;\n  if (name === TooltipName.CancelTransaction) return <CancelTransactionPane />;\n  if (name === TooltipName.PrioritizeTransaction) return <PrioritizeTransactionPane />;\n  if (name === TooltipName.Invadable) return <InvadablePane />;\n  if (name === TooltipName.Capturable) return <CapturablePane />;\n  return <></>;\n}\n"
  },
  {
    "path": "src/Frontend/Panes/TransactionLogPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { getPlanetName } from '@darkforest_eth/procedural';\nimport {\n  isUnconfirmedActivateArtifactTx,\n  isUnconfirmedBuyHatTx,\n  isUnconfirmedCapturePlanetTx,\n  isUnconfirmedDeactivateArtifactTx,\n  isUnconfirmedDepositArtifactTx,\n  isUnconfirmedFindArtifactTx,\n  isUnconfirmedInitTx,\n  isUnconfirmedInvadePlanetTx,\n  isUnconfirmedMoveTx,\n  isUnconfirmedProspectPlanetTx,\n  isUnconfirmedRevealTx,\n  isUnconfirmedTransferTx,\n  isUnconfirmedUpgradeTx,\n  isUnconfirmedWithdrawArtifactTx,\n  isUnconfirmedWithdrawSilverTx,\n} from '@darkforest_eth/serde';\nimport { ModalName, Planet, TooltipName, Transaction } from '@darkforest_eth/types';\nimport { IconType } from '@darkforest_eth/ui';\nimport { isEmpty, reverse, startCase, values } from 'lodash';\nimport React, { useCallback } from 'react';\nimport Loader from 'react-loader-spinner';\nimport 'react-loader-spinner/dist/loader/css/react-spinner-loader.css';\nimport TimeAgo from 'react-timeago';\nimport styled from 'styled-components';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { Btn } from '../Components/Btn';\nimport { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI';\nimport { Icon } from '../Components/Icons';\nimport { Sub, TxLink } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { TransactionRecord, useTransactionLog, useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { PlanetLink } from '../Views/PlanetLink';\nimport { SortableTable } from '../Views/SortableTable';\nimport { PlanetThumb } from './PlanetDexPane';\nimport { TooltipTrigger } from './Tooltip';\n\nconst PlanetName = styled.span`\n  display: block;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  max-width: 120px;\n`;\n\nconst TableContainer = styled.div`\n  min-width: 500px;\n  overflow-y: scroll;\n`;\n\nfunction TransactionState({ tx }: { tx: Transaction }) {\n  let element;\n  if (tx.state === 'Init') {\n    element = (\n      <Sub style={{ overflowX: 'hidden', display: 'block' }}>\n        <TooltipTrigger name={TooltipName.Empty} extraContent='Queued'>\n          <Loader type='Circles' color={dfstyles.colors.subtext} height={23} width={23} />\n        </TooltipTrigger>\n      </Sub>\n    );\n  } else if (tx.state === 'Prioritized') {\n    element = (\n      <TooltipTrigger name={TooltipName.Empty} extraContent='Prioritized'>\n        <Loader type='Circles' color={dfstyles.colors.dfyellow} height={23} width={23} />\n      </TooltipTrigger>\n    );\n  } else if (['Submit', 'Processing'].includes(tx.state)) {\n    element = (\n      <TooltipTrigger name={TooltipName.Empty} extraContent='Submitting'>\n        <Loader type='Circles' color={dfstyles.colors.dfblue} height={23} width={23} />\n      </TooltipTrigger>\n    );\n  } else if (tx.state === 'Confirm') {\n    element = (\n      <TooltipTrigger name={TooltipName.Empty} extraContent='Confirmed!'>\n        {' '}\n        <Icon type={IconType.Check} />\n      </TooltipTrigger>\n    );\n  } else if (tx.state === 'Cancel') {\n    element = (\n      <TooltipTrigger name={TooltipName.Empty} extraContent='Cancelled'>\n        <Icon type={IconType.X} />\n      </TooltipTrigger>\n    );\n  } else {\n    element = (\n      <TooltipTrigger\n        name={TooltipName.Empty}\n        extraContent={\n          tx.hash\n            ? 'Failed. Use the transaction has link to look up the failure on Tenderly.'\n            : 'Failed.'\n        }\n      >\n        <Icon type={IconType.X} />\n      </TooltipTrigger>\n    );\n  }\n\n  return (\n    <div\n      style={{\n        display: 'flex',\n        flexDirection: 'row',\n        justifyContent: 'space-around',\n        padding: '0 8px',\n        lineHeight: '0',\n      }}\n    >\n      {element}\n    </div>\n  );\n}\n\nconst ActionButton = styled(Btn)`\n  height: 24px;\n  width: 36px;\n`;\n\nconst ActionContainer = styled.div`\n  min-width: 96px;\n`;\n\nconst TransactionActions = ({\n  tx,\n  cancelTransaction,\n  retryTransaction,\n  prioritizeTransaction,\n}: {\n  tx: Transaction;\n  cancelTransaction: (tx: Transaction) => void;\n  retryTransaction: (tx: Transaction) => void;\n  prioritizeTransaction: (tx: Transaction) => void;\n}) => {\n  let actions;\n\n  if (tx.state === 'Fail') {\n    actions = (\n      <TooltipTrigger name={TooltipName.RetryTransaction}>\n        <ActionButton onClick={() => retryTransaction(tx)}>\n          <Icon type={IconType.Refresh} />\n        </ActionButton>\n      </TooltipTrigger>\n    );\n  } else if (tx.state === 'Init') {\n    actions = (\n      <div\n        style={{\n          display: 'flex',\n          flexDirection: 'row',\n          justifyContent: 'center',\n        }}\n      >\n        <TooltipTrigger\n          name={TooltipName.Empty}\n          extraContent='Bump this transaction to the top of the queue.'\n        >\n          <ActionButton onClick={() => prioritizeTransaction(tx)}>\n            <Icon type={IconType.FastForward} />\n          </ActionButton>\n        </TooltipTrigger>\n        <Spacer width={8} />\n        <TooltipTrigger name={TooltipName.CancelTransaction}>\n          <ActionButton onClick={() => cancelTransaction(tx)}>\n            <Icon type={IconType.X} />\n          </ActionButton>\n        </TooltipTrigger>\n      </div>\n    );\n  } else if (tx.state === 'Prioritized') {\n    actions = (\n      <TooltipTrigger name={TooltipName.CancelTransaction}>\n        <ActionButton onClick={() => cancelTransaction(tx)}>\n          <Icon type={IconType.X} />\n        </ActionButton>\n      </TooltipTrigger>\n    );\n  }\n\n  return <ActionContainer>{actions || '-'}</ActionContainer>;\n};\n\nconst humanizeTransactionType = (tx: Transaction) => startCase(tx.intent.methodName);\n\n/**\n * Grab the planet associated with the specified transction. Unfortunately, transaction intent\n * is not standardized right now, so we need to know all of the different ways the planet location\n * id is stored.\n */\nconst getPlanetFromTransaction = (\n  uiManager: GameUIManager,\n  tx: Transaction\n): Planet | undefined => {\n  const gameManager = uiManager.getGameManager();\n\n  if (isUnconfirmedMoveTx(tx)) return gameManager.getPlanetWithId(tx.intent.from);\n  if (isUnconfirmedUpgradeTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedActivateArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedRevealTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedInitTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedBuyHatTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedTransferTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId);\n  if (isUnconfirmedFindArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId);\n  if (isUnconfirmedDepositArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedWithdrawArtifactTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedProspectPlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.planetId);\n  if (isUnconfirmedDeactivateArtifactTx(tx))\n    return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedWithdrawSilverTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedInvadePlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n  if (isUnconfirmedCapturePlanetTx(tx)) return gameManager.getPlanetWithId(tx.intent.locationId);\n};\n\nfunction QueuedTransactionsTable({ transactions }: { transactions: Wrapper<TransactionRecord> }) {\n  const uiManager = useUIManager();\n  const visibleTransactions = reverse(values(transactions.value));\n\n  const headers = ['Type', 'Hash', 'State', 'Planet', 'Updated', 'Actions'];\n  const alignments: Array<'r' | 'c' | 'l'> = ['c', 'c', 'c', 'c', 'c', 'c'];\n\n  const cancelTransaction = useCallback(\n    (tx: Transaction) => {\n      uiManager.getGameManager().getContractAPI().cancelTransaction(tx);\n    },\n    [uiManager]\n  );\n\n  const retryTransaction = useCallback(\n    (tx: Transaction) => {\n      uiManager.getGameManager().getContractAPI().submitTransaction(tx.intent);\n    },\n    [uiManager]\n  );\n\n  const prioritizeTransaction = useCallback(\n    (tx: Transaction) => {\n      uiManager.getGameManager().getContractAPI().prioritizeTransaction(tx);\n    },\n    [uiManager]\n  );\n\n  const queuedTransctions = useCallback(() => {\n    return values(transactions.value).filter((tx) => ['Init', 'Prioritized'].includes(tx.state));\n  }, [transactions]);\n\n  const cancelAllQueuedTransactions = useCallback(() => {\n    queuedTransctions().forEach((queuedTx) => {\n      try {\n        cancelTransaction(queuedTx);\n      } catch {}\n    });\n  }, [cancelTransaction, queuedTransctions]);\n\n  const columns = [\n    (tx: Transaction) => (\n      <Sub\n        style={{\n          display: 'block',\n          whiteSpace: 'nowrap',\n          textOverflow: 'ellipsis',\n          width: '54px',\n          overflow: 'hidden',\n        }}\n      >\n        <TooltipTrigger name={TooltipName.Empty} extraContent={humanizeTransactionType(tx)}>\n          {humanizeTransactionType(tx)}\n        </TooltipTrigger>\n      </Sub>\n    ),\n    (tx: Transaction) => (\n      <div style={{ minWidth: '80px' }}>\n        <TxLink tx={tx} />\n      </div>\n    ),\n    (tx: Transaction) => <TransactionState tx={tx} />,\n    (tx: Transaction) => {\n      const planet = getPlanetFromTransaction(uiManager, tx);\n      if (!planet) return <></>;\n\n      return (\n        <div\n          style={{\n            display: 'flex',\n            flexDirection: 'row',\n            alignItems: 'center',\n            minWidth: '144px',\n          }}\n        >\n          <PlanetThumb planet={planet} />\n          <PlanetLink planet={planet}>\n            <PlanetName>{getPlanetName(planet)}</PlanetName>\n          </PlanetLink>\n        </div>\n      );\n    },\n    (tx: Transaction) => (\n      <Sub style={{ display: 'block', minWidth: '80px' }}>\n        <TimeAgo\n          date={tx.lastUpdatedAt}\n          formatter={(value: number, unit: TimeAgo.Unit, suffix: TimeAgo.Suffix) => {\n            let newUnit = unit as string;\n\n            if (unit === 'second' && value === 0) return 'just now';\n            if (unit === 'second') newUnit = 's';\n            if (unit === 'minute') newUnit = 'm';\n            if (unit === 'hour') newUnit = 'h';\n            if (unit === 'day') newUnit = 'd';\n\n            return `${value}${newUnit} ${suffix}`;\n          }}\n        />\n      </Sub>\n    ),\n    (tx: Transaction) => (\n      <TransactionActions\n        tx={tx}\n        cancelTransaction={cancelTransaction}\n        retryTransaction={retryTransaction}\n        prioritizeTransaction={prioritizeTransaction}\n      />\n    ),\n  ];\n\n  const sortingFunctions = [\n    (a: Transaction, b: Transaction): number =>\n      a.intent.methodName.localeCompare(b.intent.methodName),\n    (_a: Transaction, _b: Transaction): number => 0,\n    (a: Transaction, b: Transaction): number => a.state.localeCompare(b.state),\n    (a: Transaction, b: Transaction): number => {\n      const planetA = getPlanetFromTransaction(uiManager, a);\n      if (!planetA) return -1;\n\n      const planetB = getPlanetFromTransaction(uiManager, b);\n      if (!planetB) return 1;\n\n      return getPlanetName(planetB).localeCompare(getPlanetName(planetA));\n    },\n    (a: Transaction, b: Transaction): number => b.lastUpdatedAt - a.lastUpdatedAt,\n  ];\n\n  return (\n    <TableContainer>\n      <div\n        style={{\n          width: '100%',\n          display: 'flex',\n          flexDirection: 'row',\n          justifyContent: 'space-around',\n        }}\n      >\n        <div\n          style={{\n            display: 'flex',\n            flexDirection: 'row',\n            alignItems: 'center',\n          }}\n        >\n          {queuedTransctions().length !== 0 && (\n            <Loader type='Circles' color={dfstyles.colors.subtext} height={23} width={23} />\n          )}\n          <Spacer width={8} />\n          {queuedTransctions().length} queued transactions\n          <Spacer width={8} />\n          {queuedTransctions().length !== 0 && (\n            <Btn onClick={cancelAllQueuedTransactions}>cancel all</Btn>\n          )}\n        </div>\n      </div>\n      <Spacer height={8} />\n      <SortableTable\n        paginated={true}\n        rows={visibleTransactions}\n        headers={headers}\n        columns={columns}\n        sortFunctions={sortingFunctions}\n        alignments={alignments}\n      />\n    </TableContainer>\n  );\n}\n\nexport function TransactionLogPane({\n  visible,\n  onClose,\n}: {\n  visible: boolean;\n  onClose: () => void;\n}) {\n  const transactions = useTransactionLog();\n\n  return (\n    <ModalPane\n      visible={visible}\n      onClose={onClose}\n      id={ModalName.TransactionLog}\n      title='Transaction Log'\n    >\n      {isEmpty(transactions.value) ? (\n        <CenterBackgroundSubtext width={RECOMMENDED_MODAL_WIDTH} height='100px'>\n          No transactions to be shown\n        </CenterBackgroundSubtext>\n      ) : (\n        <QueuedTransactionsTable transactions={transactions} />\n      )}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/TutorialPane.tsx",
    "content": "import { Setting } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport TutorialManager, {\n  TutorialManagerEvent,\n  TutorialState,\n} from '../../Backend/GameLogic/TutorialManager';\nimport { Hook } from '../../_types/global/GlobalTypes';\nimport { Btn } from '../Components/Btn';\nimport { Underline } from '../Components/CoreUI';\nimport { Icon, IconType } from '../Components/Icons';\nimport { White } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { useBooleanSetting } from '../Utils/SettingsHooks';\n\nfunction TutorialPaneContent({ tutorialState }: { tutorialState: TutorialState }) {\n  const uiManager = useUIManager();\n  const tutorialManager = TutorialManager.getInstance(uiManager);\n\n  if (tutorialState === TutorialState.None) {\n    return (\n      <div className='tutintro'>\n        Welcome to the universe of <White>DARK FOREST</White>. Would you like to play the tutorial?\n        <div>\n          <Btn className='btn' onClick={() => tutorialManager.acceptInput(TutorialState.None)}>\n            Yes\n          </Btn>\n          <Btn className='btn' onClick={() => tutorialManager.complete()}>\n            No\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.HomePlanet) {\n    return (\n      <div>\n        Welcome to the universe. You've initialized with 50 energy on your home planet, in a NEBULA.\n        <br />\n        <br />\n        <White>Click your home planet to learn more.</White>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.SendFleet) {\n    return (\n      <div>\n        Well done! In the Selected Planet pane, you'll see more information about your planet. This\n        pane displays quick information about your planet and the ability to send resources.\n        <br />\n        <br />\n        <White>Try sending energy to another planet.</White> You can click and drag to look for\n        other planets.\n      </div>\n    );\n  } else if (tutorialState === TutorialState.SpaceJunk) {\n    return (\n      <div>\n        When you sent energy to a planet you accumulated some <White>Space Junk</White>. Sending\n        energy to planets that no one has moved to yet will give you junk. You are not allowed to\n        take on more junk than your maximum limit and will be unable to make moves.\n        <br />\n        <br />\n        Take a look at the top of the screen to see you current and maximum{' '}\n        <White>Space Junk</White>.\n        <div>\n          <Btn className='btn' onClick={() => tutorialManager.acceptInput(TutorialState.SpaceJunk)}>\n            Next\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.Spaceship) {\n    return (\n      <div>\n        You also control several space ships - check your home planet! You can move spaceships\n        between any two planets, even if you don't own the source or destination planets. Space\n        ships can move any distance!{' '}\n        <White>Try moving a spaceship you own to another planet now!</White>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.Deselect) {\n    return (\n      <div>\n        Congrats, you've submitted a move to xDAI! Moves that are in the mempool are shown as dotted\n        lines. Accepted moves are shown as solid lines.\n        <br />\n        <br />\n        <White>Try deselecting a planet now. Click in empty space to deselect.</White>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.ZoomOut) {\n    return (\n      <div className='tutzoom'>\n        Great! You'll notice that most of the universe appears grayed out. You need to explore those\n        areas before you can view them.\n        <br />\n        <br />\n        You'll notice a target <Icon type={IconType.Target} /> indicating where you are currently\n        exploring. <White>Press next when you can see it.</White> You can also zoom using the mouse\n        wheel.\n        <div>\n          <Btn className='btn' onClick={() => tutorialManager.acceptInput(TutorialState.ZoomOut)}>\n            Next\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.MinerMove) {\n    return (\n      <div>\n        You can move your explorer by using the bottom-left context menu when nothing is selected.\n        <br />\n        <br />\n        <White>\n          Try moving your explorer by clicking on the Move <Icon type={IconType.Target} /> button\n        </White>\n        , then clicking somewhere in space.\n      </div>\n    );\n  } else if (tutorialState === TutorialState.MinerPause) {\n    return (\n      <div>\n        Great! You can also pause your explorer by clicking the pause <Icon type={IconType.Pause} />{' '}\n        button.\n        <br />\n        <br />\n        <White>Try pausing your explorer now.</White>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.Terminal) {\n    return (\n      <div>\n        You can hide the terminal on the right by clicking on its left edge.\n        <br />\n        <br />\n        <White>Try hiding the terminal now.</White>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.HowToGetScore) {\n    return (\n      <div className='tutzoom'>\n        <White>It's a Junk War!</White> <br />\n        <br />\n        Have the highest score at the end of the round to win!\n        <br />\n        <div>\n          <Btn\n            className='btn'\n            onClick={() => tutorialManager.acceptInput(TutorialState.HowToGetScore)}\n          >\n            Next\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.ScoringDetails) {\n    return (\n      <div className='tutzoom'>\n        You can increase your score by withdrawing silver via space time rips, and by finding\n        artifacts. The rarer the artifact, the more points it gives you! You can also increase your\n        score via Capture Zones. Hover over the 'Capture Zone' section in the top bar for more info\n        about capture zones.\n        <div>\n          <Btn\n            className='btn'\n            onClick={() => tutorialManager.acceptInput(TutorialState.ScoringDetails)}\n          >\n            Next\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.Valhalla) {\n    return (\n      <div className='tutalmost'>\n        Winners of each round of Dark Forest v0.6.x will receive a prize, and be added to the{' '}\n        <Underline>Valhalla</Underline> universe.\n        <br />\n        <br />\n        To win, have the highest score (^:\n        <div>\n          <Btn className='btn' onClick={() => tutorialManager.acceptInput(TutorialState.Valhalla)}>\n            Next\n          </Btn>\n        </div>\n      </div>\n    );\n  } else if (tutorialState === TutorialState.AlmostCompleted) {\n    return (\n      <div className='tutalmost'>\n        This is the end of the tutorial. Go out and explore the universe! More information will pop\n        up in the <White>upper-right</White> as you discover more about the game.\n        <br />\n        We hope you enjoy Dark Forest!\n        <div>\n          <Btn className='btn' onClick={() => tutorialManager.complete()}>\n            Finish\n          </Btn>\n        </div>\n      </div>\n    );\n  } else {\n    return <> </>;\n  }\n}\n\nconst StyledTutorialPane = styled.div<{ visible: boolean }>`\n  display: ${({ visible }) => (visible ? 'block' : 'none')};\n  position: absolute;\n  top: 0;\n  left: 0;\n\n  background: ${dfstyles.colors.backgroundlighter};\n  color: ${dfstyles.colors.text};\n  padding: 8px;\n  border-bottom: 1px solid ${dfstyles.colors.border};\n  border-right: 1px solid ${dfstyles.colors.border};\n\n  width: 24em;\n  height: fit-content;\n\n  z-index: 10;\n\n  & .tutintro {\n    & > div:last-child {\n      display: flex;\n      flex-direction: row;\n      justify-content: space-around;\n      margin-top: 1em;\n    }\n  }\n\n  & .tutzoom,\n  & .tutalmost {\n    & > div:last-child {\n      display: flex;\n      flex-direction: row;\n      justify-content: flex-end;\n      margin-top: 1em;\n    }\n  }\n`;\n\nexport function TutorialPane({ tutorialHook }: { tutorialHook: Hook<boolean> }) {\n  const uiManager = useUIManager();\n  const tutorialManager = TutorialManager.getInstance(uiManager);\n\n  const [tutorialState, setTutorialState] = useState<TutorialState>(TutorialState.None);\n  const [tutorialOpen] = tutorialHook;\n  const [completed, setCompleted] = useBooleanSetting(uiManager, Setting.TutorialCompleted);\n\n  // sync tutorial state\n  useEffect(() => {\n    const update = (newState: TutorialState) => {\n      setTutorialState(newState);\n      setCompleted(newState === TutorialState.Completed);\n    };\n    tutorialManager.on(TutorialManagerEvent.StateChanged, update);\n\n    return () => {\n      tutorialManager.removeListener(TutorialManagerEvent.StateChanged, update);\n    };\n  }, [tutorialManager, setCompleted]);\n\n  return (\n    <StyledTutorialPane visible={!completed && tutorialOpen}>\n      <TutorialPaneContent tutorialState={tutorialState} />\n    </StyledTutorialPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/TwitterVerifyPane.tsx",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { ModalName } from '@darkforest_eth/types';\nimport React, { useState } from 'react';\nimport { Btn } from '../Components/Btn';\nimport { Expand, Spacer } from '../Components/CoreUI';\nimport { DarkForestTextInput, TextInput } from '../Components/Input';\nimport { TwitterLink } from '../Components/Labels/Labels';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Red } from '../Components/Text';\nimport { usePlayer, useUIManager } from '../Utils/AppHooks';\nimport { ModalPane } from '../Views/ModalPane';\nimport { TabbedView } from '../Views/TabbedView';\n\nexport function TwitterVerifyPane({ visible, onClose }: { visible: boolean; onClose: () => void }) {\n  const uiManager = useUIManager();\n  const user = usePlayer(uiManager);\n  const [twitterHandleInputValue, setTwitterHandleInputValue] = useState<string>('');\n  const [verifying, setVerifying] = useState(false);\n  const [disconnecting, setDisconnecting] = useState(false);\n  const [error, setError] = useState(false);\n\n  const onTwitterInputChange = (newHandle: string) => {\n    setTwitterHandleInputValue(newHandle.replace('@', ''));\n  };\n\n  /**\n   * Called when the user clicks on the 'tweet' button. Opens up a popup that prompts them to tweet\n   * the required verification tweet from the account that they entered into the pane.\n   */\n  const onTweetClick = async () => {\n    if (uiManager) {\n      const tweetText = await uiManager.generateVerificationTweet(twitterHandleInputValue);\n      const str = `Verifying my @darkforest_eth v0.6 account (https://zkga.me): ${tweetText}`;\n      window.open(`https://twitter.com/intent/tweet?hashtags=darkforest&text=${encodeURI(str)}`);\n    }\n  };\n\n  /**\n   * Called when the user clicks on the 'verify' button. Asks the webserver whether or not the given\n   * twitter account tweeted the correct verification tweet. If that happened, sets the twitter\n   * account internally.\n   */\n  const onVerifyClick = async () => {\n    try {\n      setVerifying(true);\n      await uiManager?.verifyTwitter(twitterHandleInputValue);\n    } catch (e) {\n      setError(true);\n    } finally {\n      setVerifying(false);\n    }\n  };\n\n  /**\n   * Called when the user clicks the 'disconnect' button. Allows them to disconnect their account from twitter.\n   */\n  const onDisconnectClick = async () => {\n    if (confirm('are you sure you want to disconnect your twitter?')) {\n      try {\n        setDisconnecting(true);\n        await uiManager?.disconnectTwitter(twitterHandleInputValue);\n      } catch (e) {\n        setError(true);\n      } finally {\n        setDisconnecting(false);\n      }\n    }\n  };\n\n  return (\n    <ModalPane\n      id={ModalName.TwitterVerify}\n      title='Connect/Disconnect Twitter'\n      visible={visible}\n      onClose={onClose}\n      initialPosition={{ y: 100, x: window.innerWidth / 2 - 300 }}\n      width={RECOMMENDED_MODAL_WIDTH}\n    >\n      {user.value !== undefined && user.value.twitter === undefined && (\n        <TabbedView\n          tabTitles={['Tweet Proof', 'Verify Tweet']}\n          tabContents={(i: number) => {\n            if (i === 0)\n              return (\n                <>\n                  Tweet a signed message, proving account ownership!\n                  <Spacer height={8} />\n                  <TextInput\n                    placeholder='Your Twitter handle'\n                    value={twitterHandleInputValue}\n                    onChange={(e: Event & React.ChangeEvent<DarkForestTextInput>) =>\n                      onTwitterInputChange(e.target.value)\n                    }\n                  />\n                  <Spacer height={8} />\n                  <Expand />\n                  <Btn size='stretch' onClick={onTweetClick}>\n                    Tweet\n                  </Btn>\n                </>\n              );\n\n            if (i === 1) {\n              return (\n                <>\n                  After tweeting, click the button below to verify ownership!\n                  <Spacer height={8} />\n                  <TextInput\n                    placeholder='Your Twitter handle'\n                    value={twitterHandleInputValue}\n                    onChange={(e: Event & React.ChangeEvent<DarkForestTextInput>) =>\n                      onTwitterInputChange(e.target.value)\n                    }\n                  />\n                  <Spacer height={8} />\n                  <Expand />\n                  {error && (\n                    <>\n                      <Spacer height={8} />\n                      <Red>error verifying ownership</Red>\n                    </>\n                  )}\n                  <Btn size='stretch' disabled={verifying} onClick={onVerifyClick}>\n                    {verifying ? <LoadingSpinner initialText={'Verifying...'} /> : 'Verify'}\n                  </Btn>\n                </>\n              );\n            }\n          }}\n        />\n      )}\n\n      {user.value !== undefined && user.value.twitter && (\n        <>\n          You are connected, <TwitterLink twitter={user.value.twitter} />. You can disconnect from\n          twitter anytime by clicking the button below.\n          <Spacer height={16} />\n          <Btn size='stretch' disabled={disconnecting} onClick={onDisconnectClick}>\n            {verifying ? (\n              <LoadingSpinner initialText={'Disconnecting Twitter...'} />\n            ) : (\n              'Disconnect Twitter'\n            )}\n          </Btn>\n        </>\n      )}\n    </ModalPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/UpgradeDetailsPane.tsx",
    "content": "import { isUnconfirmedUpgradeTx } from '@darkforest_eth/serde';\nimport { LocationId, Planet, PlanetType, UpgradeBranchName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport {\n  getPlanetMaxRank,\n  getPlanetRank,\n  isFullRank,\n  upgradeName,\n} from '../../Backend/Utils/Utils';\nimport { Btn } from '../Components/Btn';\nimport { CenterBackgroundSubtext, Spacer } from '../Components/CoreUI';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Gold, Red, Sub, Subber } from '../Components/Text';\nimport { useAccount, usePlanet, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { ModalHandle } from '../Views/ModalPane';\nimport { TabbedView } from '../Views/TabbedView';\nimport { UpgradePreview } from '../Views/UpgradePreview';\n\nconst SECTION_MARGIN = '0.75em';\n\nconst SectionPreview = styled.div`\n  margin-top: ${SECTION_MARGIN};\n`;\n\nconst SectionBuy = styled.div`\n  margin-top: ${SECTION_MARGIN};\n`;\n\nexport function UpgradeDetailsPaneHelpContent() {\n  return (\n    <div>\n      <p>\n        Upgrades cost Silver, and allow you to boost the stats of your planet. You need to move the\n        required silver to this planet to be able to spend it on upgrades.\n      </p>\n      <Spacer height={8} />\n      <p>\n        All planets have a certain max rank, and each branch can only be upgraded so many times.\n        Choose wisely!\n      </p>\n    </div>\n  );\n}\n\nfunction SilverRequired({ planet }: { planet: Planet }) {\n  const maxRank = getPlanetMaxRank(planet);\n  const silverPerRank = [];\n\n  for (let i = 0; i < maxRank; i++) {\n    silverPerRank[i] = Math.floor((i + 1) * 0.2 * planet.silverCap);\n  }\n\n  return (\n    <>\n      {silverPerRank.map((silver: number, i: number) => (\n        <span key={i}>\n          {i === getPlanetRank(planet) ? <Gold>{silver}</Gold> : <Subber>{silver}</Subber>}\n          <Spacer width={8} />\n        </span>\n      ))}\n    </>\n  );\n}\n\nexport function UpgradeDetailsPane({\n  initialPlanetId,\n  modal: _modal,\n}: {\n  modal: ModalHandle;\n  initialPlanetId: LocationId | undefined;\n}) {\n  const uiManager = useUIManager();\n  const planetId = useEmitterValue(uiManager.selectedPlanetId$, initialPlanetId);\n  const planet = usePlanet(uiManager, planetId).value;\n  const account = useAccount(uiManager);\n  const planetAtMaxRank = isFullRank(planet);\n\n  if (planet && account) {\n    if (planet.owner !== account) {\n    } else if (planet.planetType !== PlanetType.PLANET || planet.silverCap === 0) {\n      return (\n        <CenterBackgroundSubtext width='100%' height='75px'>\n          This Planet <br /> is not Upgradeable\n        </CenterBackgroundSubtext>\n      );\n    } else {\n      return (\n        <TabbedView\n          tabTitles={['Defense', 'Range', 'Speed']}\n          tabContents={(branch: UpgradeBranchName) => {\n            const currentLevel = planet.upgradeState[branch];\n            const branchAtMaxRank = !planet || planet.upgradeState[branch] >= 4;\n            const upgrade = branchAtMaxRank\n              ? undefined\n              : uiManager.getUpgrade(branch, currentLevel);\n\n            const totalLevel = planet.upgradeState.reduce((a, b) => a + b);\n            const silverNeeded = Math.floor((totalLevel + 1) * 0.2 * planet.silverCap);\n            const enoughSilver = planet.silver >= silverNeeded;\n            const isPendingUpgrade = planet.transactions?.hasTransaction(isUnconfirmedUpgradeTx);\n            const canUpgrade =\n              enoughSilver && !planetAtMaxRank && !branchAtMaxRank && !isPendingUpgrade;\n\n            const doUpgrade = (branch: UpgradeBranchName) => {\n              if (canUpgrade) {\n                uiManager.upgrade(planet, branch);\n              }\n            };\n\n            return (\n              <>\n                <SectionPreview>\n                  <UpgradePreview\n                    upgrade={upgrade}\n                    planet={planet}\n                    branchName={branch}\n                    cantUpgrade={planetAtMaxRank || branchAtMaxRank}\n                  />\n                </SectionPreview>\n                <SectionBuy>\n                  <div>\n                    <Sub>Silver Available</Sub>: <span>{planet.silver}</span>\n                  </div>\n                  <div>\n                    <Sub>Silver Cost:</Sub> <SilverRequired planet={planet} />\n                  </div>\n                  <div>\n                    <Spacer height={8} />\n                    {isPendingUpgrade ? (\n                      <Btn disabled={true}>\n                        <LoadingSpinner initialText='Upgrading...' />\n                      </Btn>\n                    ) : (\n                      <>\n                        <Btn onClick={() => doUpgrade(branch)} disabled={!canUpgrade}>\n                          {'Upgrade'}\n                        </Btn>{' '}\n                        {planetAtMaxRank ? (\n                          <Red>Planet at Max Rank</Red>\n                        ) : branchAtMaxRank ? (\n                          <Red>{upgradeName(branch)} at Max Rank</Red>\n                        ) : !enoughSilver ? (\n                          <Red>Not Enough Silver</Red>\n                        ) : undefined}\n                      </>\n                    )}\n                  </div>\n                </SectionBuy>\n              </>\n            );\n          }}\n        />\n      );\n    }\n  }\n\n  return (\n    <CenterBackgroundSubtext width='100%' height='75px'>\n      Select a Planet <br /> You Own\n    </CenterBackgroundSubtext>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Panes/WikiPane.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\n\nexport function WikiPane({ children }: { children: React.ReactNode }) {\n  return <WikiPaneContainer>{children}</WikiPaneContainer>;\n}\n\nconst WikiPaneContainer = styled.div`\n  width: 400px;\n  max-height: 300px;\n  overflow-y: scroll;\n  border-radius: 2px;\n  border: 1px solid ${dfstyles.colors.text};\n  background-color: ${dfstyles.colors.blueBackground};\n  color: #ccc;\n  padding: 8px 16px;\n`;\n"
  },
  {
    "path": "src/Frontend/Panes/ZoomPane.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { LongDash } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { DFZIndex } from '../Utils/constants';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\n\nconst StyledZoomPane = styled.div`\n  z-index: ${DFZIndex.MenuBar};\n  padding-left: 0.75em;\n  padding-top: 0.1em;\n  margin-top: 0;\n  display: flex;\n  font-size: 1.5em;\n  flex-direction: row;\n  justify-content: flex-end;\n  height: fit-content;\n  & > a:first-child {\n    margin-right: 0.75em;\n  }\n  color: ${dfstyles.colors.subtext};\n  & > a {\n    &:hover {\n      color: ${dfstyles.colors.text};\n      cursor: pointer;\n    }\n    &:active {\n      color: ${dfstyles.colors.subbertext};\n    }\n  }\n`;\n\nexport function ZoomPane() {\n  const uiEmitter = UIEmitter.getInstance();\n  return (\n    <StyledZoomPane>\n      <a onClick={() => uiEmitter.emit(UIEmitterEvent.ZoomOut)}>\n        <LongDash />\n      </a>\n      <a onClick={() => uiEmitter.emit(UIEmitterEvent.ZoomIn)}>+</a>\n    </StyledZoomPane>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Renderers/Artifacts/ArtifactRenderer.ts",
    "content": "import { MAX_BIOME, MIN_BIOME } from '@darkforest_eth/constants';\nimport { SpriteRenderer, WebGLManager } from '@darkforest_eth/renderer';\nimport { Artifact, ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types';\nimport autoBind from 'auto-bind';\nimport { ARTIFACT_ROW_H } from '../../Styles/dfstyles';\n\nconst NUM_BIOMES = MAX_BIOME;\n\nconst thumbDim = 32;\nconst cellDim = ARTIFACT_ROW_H;\nconst mTop = 0.5 * (cellDim - thumbDim);\n\nexport const artifactColM = 32;\nexport const artifactColW = cellDim * 4;\n\nexport const aDexCanvasW = 4 * artifactColW + 3 * artifactColM;\nexport const aDexCanvasH = NUM_BIOMES * cellDim;\n\nexport const aListCanvasW = cellDim;\nexport const aListCanvasH = 400;\n\nexport class ArtifactRenderer extends WebGLManager {\n  private frameRequestId: number;\n\n  private spriteRenderer: SpriteRenderer;\n\n  private visible = false;\n  private artifacts: Artifact[];\n  private isDex: boolean;\n  private scroll = 0;\n\n  constructor(canvas: HTMLCanvasElement, isDex = true) {\n    super(canvas);\n\n    autoBind(this);\n\n    this.setIsDex(isDex);\n\n    this.spriteRenderer = new SpriteRenderer(this, true);\n\n    this.loop();\n  }\n\n  public setIsDex(isDex: boolean) {\n    this.isDex = isDex;\n  }\n\n  public setScroll(scroll: number) {\n    this.scroll = scroll;\n  }\n\n  public setVisible(visible: boolean): void {\n    this.visible = visible;\n  }\n\n  public setArtifacts(artifacts: Artifact[]): void {\n    this.artifacts = artifacts;\n  }\n\n  private queueRarityColumn(rarity: ArtifactRarity, startX: number) {\n    let col = 0;\n    for (let type = 1; type <= 4; type++) {\n      this.queueArtifactColumn(type as ArtifactType, rarity, startX + col * cellDim);\n      col++;\n    }\n  }\n\n  private containsArtifact(biome: Biome, rarity: ArtifactRarity, type: ArtifactType) {\n    for (const artifact of this.artifacts) {\n      if (\n        artifact.planetBiome === biome &&\n        artifact.artifactType === type &&\n        artifact.rarity === rarity\n      ) {\n        return true;\n      }\n    }\n\n    return false;\n  }\n\n  private queueArtifactColumn(type: ArtifactType, rarity: ArtifactRarity, startX: number) {\n    let row = 0;\n    for (let biome = MIN_BIOME; biome <= MAX_BIOME; biome++) {\n      const pos = { x: startX, y: mTop + row * ARTIFACT_ROW_H };\n      const artifact = {\n        planetBiome: biome,\n        rarity,\n        artifactType: type,\n      } as Artifact;\n      const contains = this.containsArtifact(biome, rarity, type);\n      const alpha = contains ? 255 : 70;\n      this.spriteRenderer.queueArtifact(artifact, pos, thumbDim, alpha);\n\n      row++;\n    }\n  }\n\n  private drawDex() {\n    for (let r = ArtifactRarity.Common; r <= ArtifactRarity.Legendary; r++) {\n      this.queueRarityColumn(r, r * (artifactColW + artifactColM));\n    }\n  }\n\n  private drawList() {\n    let row = 0;\n    for (const artifact of this.artifacts) {\n      const pos = { x: 0, y: row * ARTIFACT_ROW_H + mTop - this.scroll };\n      this.spriteRenderer.queueArtifact(artifact, pos, thumbDim);\n      row++;\n    }\n  }\n\n  private draw() {\n    if (this.isDex) this.drawDex();\n    else this.drawList();\n\n    this.spriteRenderer.flush();\n  }\n\n  private loop() {\n    if (this.visible) {\n      this.setProjectionMatrix();\n      this.clear();\n      this.draw();\n    }\n\n    this.frameRequestId = window.requestAnimationFrame(this.loop);\n  }\n\n  public destroy() {\n    window.cancelAnimationFrame(this.frameRequestId);\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Renderers/GifRenderer.ts",
    "content": "import {\n  EMPTY_ARTIFACT_ID,\n  MAX_ARTIFACT_RARITY,\n  MAX_ARTIFACT_TYPE,\n  MAX_BIOME,\n  MIN_ARTIFACT_RARITY,\n  MIN_ARTIFACT_TYPE,\n  MIN_BIOME,\n} from '@darkforest_eth/constants';\nimport { ArtifactFileColor, artifactFileName, setForceAncient } from '@darkforest_eth/gamelogic';\nimport { mockArtifactWithRarity } from '@darkforest_eth/procedural';\nimport { SpriteRenderer, WebGLManager } from '@darkforest_eth/renderer';\nimport { Artifact, ArtifactRarity, ArtifactType, Biome } from '@darkforest_eth/types';\nimport { mat4 } from 'gl-matrix';\nimport JSZip from 'jszip';\nimport { GIF_ARTIFACT_COLOR } from '../Pages/GifMaker';\n\nconst FileSaver = require('file-saver');\n\ndeclare global {\n  interface Window {\n    /* eslint-disable @typescript-eslint/no-explicit-any */\n    CCapture: any;\n  }\n}\n\nconst COLORS: Record<ArtifactFileColor, readonly [number, number, number, number]> = {\n  [ArtifactFileColor.BLUE]: [0.0724, 0.051, 0.3111, 1] as const,\n  [ArtifactFileColor.APP_BACKGROUND]: [0.031372549, 0.031372549, 0.031372549, 1] as const,\n};\n\nexport class GifRenderer extends WebGLManager {\n  public projectionMatrix: mat4;\n  private spriteRenderer: SpriteRenderer;\n  private margin: number;\n  private canvasDim: number;\n  private artifactDim: number;\n  private resolution: number;\n\n  private thumb: boolean;\n\n  constructor(canvas: HTMLCanvasElement, dim: number, isThumb: boolean) {\n    super(canvas, { preserveDrawingBuffer: true });\n\n    this.thumb = isThumb;\n\n    this.spriteRenderer = new SpriteRenderer(this, isThumb);\n    this.setDim(dim);\n  }\n\n  private setDim(dim: number) {\n    const SPRITE_DIM = this.thumb ? 16 : 64;\n\n    this.canvasDim = dim;\n    this.resolution = Math.floor(this.canvasDim / SPRITE_DIM) - 1;\n    this.artifactDim = this.resolution * SPRITE_DIM;\n\n    this.margin = Math.floor(0.5 * (this.canvasDim - this.artifactDim));\n\n    this.setProjectionMatrix();\n  }\n\n  // https://gist.github.com/ahgood/bfc57a7f44d6ab7803f3ee2ec0abb980\n\n  private drawSprite(artifact: Artifact, atFrame: number | undefined = undefined) {\n    this.clear();\n    this.spriteRenderer.queueArtifact(\n      artifact,\n      { x: this.margin, y: this.margin },\n      this.artifactDim,\n      255,\n      atFrame\n    );\n    this.spriteRenderer.flush();\n  }\n\n  private getBase64(): string {\n    const b64 = this.canvas.toDataURL('image/png').replace(/^data:image\\/(png|jpg);base64,/, '');\n\n    return b64;\n  }\n\n  private getFileName(\n    video: boolean,\n    type: ArtifactType,\n    biome: Biome,\n    rarity: ArtifactRarity,\n    ancient: boolean\n  ) {\n    return artifactFileName(\n      video,\n      this.thumb,\n      { artifactType: type, planetBiome: biome, rarity, id: EMPTY_ARTIFACT_ID },\n      GIF_ARTIFACT_COLOR,\n      { skipCaching: true, forceAncient: ancient }\n    );\n  }\n\n  private addSprite(\n    dir: JSZip,\n    type: ArtifactType,\n    biome: Biome,\n    rarity: ArtifactRarity,\n    ancient = false\n  ) {\n    const fileName = this.getFileName(false, type, biome, rarity, ancient);\n    this.drawSprite(mockArtifactWithRarity(rarity, type, biome));\n\n    dir.file(fileName, this.getBase64(), { base64: true });\n  }\n\n  private async addVideo(\n    dir: JSZip,\n    type: ArtifactType,\n    biome: Biome,\n    rarity: ArtifactRarity,\n    ancient = false\n  ) {\n    const fileName = this.getFileName(true, type, biome, rarity, ancient);\n    const artifact = mockArtifactWithRarity(rarity, type, biome);\n\n    const capturer = new window.CCapture({\n      format: 'webm',\n      framerate: 60,\n      quality: 0.999,\n    });\n    capturer.start();\n\n    for (let i = 0; i < 180; i++) {\n      this.drawSprite(artifact, i);\n      capturer.capture(this.canvas);\n    }\n    capturer.stop();\n    return new Promise<void>((resolve) => {\n      capturer.save((blob: Blob) => {\n        dir.file(fileName, blob);\n        console.log('saved ' + fileName + '!');\n        resolve();\n      });\n    });\n  }\n\n  private async addBiomes(videoMode: boolean, dir: JSZip) {\n    setForceAncient(false);\n    for (let type = MIN_ARTIFACT_TYPE; type <= MAX_ARTIFACT_TYPE; type++) {\n      for (let rarity = MIN_ARTIFACT_RARITY; rarity <= MAX_ARTIFACT_RARITY; rarity++) {\n        for (let biome = MIN_BIOME; biome <= MAX_BIOME; biome++) {\n          if (videoMode) await this.addVideo(dir, type, biome, rarity, false);\n          else this.addSprite(dir, type, biome, rarity, false);\n        }\n      }\n    }\n  }\n\n  private async addAncient(videoMode: boolean, dir: JSZip) {\n    setForceAncient(true);\n    for (let type = MIN_ARTIFACT_TYPE; type <= MAX_ARTIFACT_TYPE; type++) {\n      for (let rarity = MIN_ARTIFACT_RARITY; rarity <= MAX_ARTIFACT_RARITY; rarity++) {\n        if (videoMode) await this.addVideo(dir, type, Biome.OCEAN, rarity, true);\n        else this.addSprite(dir, type, Biome.OCEAN, rarity, true);\n      }\n    }\n  }\n\n  private async getAll(videoMode = false) {\n    const zip = new JSZip();\n\n    zip.folder('img');\n    const dir = zip.folder('img');\n    if (!dir) {\n      console.error('jszip error');\n      return;\n    }\n\n    await this.addBiomes(videoMode, dir);\n    await this.addAncient(videoMode, dir);\n\n    zip.generateAsync({ type: 'blob' }).then((content) => {\n      FileSaver.saveAs(content, 'files.zip');\n    });\n  }\n\n  getAllSprites() {\n    this.getAll(false);\n  }\n\n  getAllVideos() {\n    this.getAll(true);\n  }\n\n  clear() {\n    super.clear(0, [...COLORS[GIF_ARTIFACT_COLOR]]);\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Renderers/LandingPageCanvas.tsx",
    "content": "import autoBind from 'auto-bind';\nimport React, { RefObject, useCallback, useEffect, useRef } from 'react';\nimport dfstyles from '../Styles/dfstyles';\nimport { hasTouchscreen, isMobileOrTablet } from '../Utils/BrowserChecks';\n\nconst canvasStyle = {\n  position: 'absolute',\n  width: '100vw',\n  height: '150vh',\n} as React.CSSProperties;\n\ntype Position = { x: number; y: number };\ntype Point = {\n  pos: Position;\n  r: number;\n  z: number;\n  color: string;\n};\n\ntype Edge = [number, number]; // as index, index\n\nclass LandingPageCanvasRenderer {\n  static instance: LandingPageCanvasRenderer | null;\n  canvasRef: RefObject<HTMLCanvasElement>;\n  canvas: HTMLCanvasElement;\n  ctx: CanvasRenderingContext2D;\n  frameRequestId: number;\n\n  mouse: Position;\n  delMouse: Position;\n\n  points: Point[];\n  realPos: Position[];\n  edges: Edge[];\n\n  private constructor(canvas: HTMLCanvasElement) {\n    this.canvas = canvas;\n\n    const ctx = this.canvas.getContext('2d');\n    if (!ctx) {\n      throw new Error('Not a 2D canvas.');\n    }\n    this.ctx = ctx;\n    this.mouse = { x: -0xdeadbeef, y: -0xdeadbeef };\n    this.delMouse = { x: 0, y: 0 };\n    this.points = [];\n    this.edges = [];\n\n    autoBind(this);\n\n    this.setupDraw();\n    this.frame();\n  }\n\n  private makeConstellation(center: Position): void {\n    const randomColor: () => string = () => {\n      const idx = Math.floor(Math.random() * 3);\n      return [dfstyles.colors.dfgreen, dfstyles.colors.dfblue, dfstyles.colors.dfred][idx];\n    };\n\n    const newPoints: Point[] = [];\n    let newEdges: Edge[] = [];\n\n    const numPoints = Math.floor(2 + Math.random() * 7);\n    for (let i = 0; i < numPoints; i++) {\n      const size = 20 + Math.random() * 160;\n      const t = Math.random() * 2 * Math.PI;\n      newPoints.push({\n        pos: {\n          x: center.x + size * Math.sin(t),\n          y: center.y + size * Math.cos(t),\n        },\n        z: 0.2 + Math.random() * 0.8,\n        r: 1 + Math.random() * 4,\n        color: randomColor(),\n      });\n    }\n\n    const numEdges = Math.floor((1 + Math.random()) * (numPoints - 1));\n    for (let i = 0; i < numEdges; i++) {\n      const p1 = Math.floor(Math.random() * numPoints);\n      let p2 = p1;\n      while (p2 === p1) p2 = Math.floor(Math.random() * numPoints);\n      newEdges.push([p1, p2]);\n    }\n\n    const length = this.points.length;\n    newEdges = newEdges.map((e) => [e[0] + length, e[1] + length]);\n\n    for (const point of newPoints) {\n      this.points.push(point);\n      this.realPos.push({\n        x: -0xdeadbeef,\n        y: -0xdeadbeef,\n      });\n    }\n    for (const edge of newEdges) {\n      this.edges.push(edge);\n    }\n  }\n\n  private setupDraw() {\n    const canvas = this.canvas;\n\n    this.points = [];\n    this.edges = [];\n    this.realPos = [];\n\n    this.canvas.width = this.canvas.getBoundingClientRect().width;\n    this.canvas.height = this.canvas.getBoundingClientRect().height;\n\n    const numH = window.innerWidth > 400 ? Math.floor(canvas.width / 200) : 2;\n    const numV = window.innerHeight > 600 ? Math.floor(canvas.height / 360) : 3;\n\n    const perturb = () => Math.random() * 100 - 50;\n    const midX = (x: number) => 0.2 * canvas.width < x && x < 0.8 * canvas.width;\n    const midY = (y: number) => 0.2 * canvas.height < y && y < 0.8 * canvas.height;\n    const mid = (x: number, y: number) => midX(x) && midY(y);\n\n    for (let i = 0; i < numH; i++) {\n      for (let j = 0; j < numV; j++) {\n        const baseX = (i * canvas.width) / (numH - 1);\n        const baseY = (j * canvas.height) / 2 / (numV - 1);\n        const rand = Math.random();\n        const m = mid(baseX, baseY);\n        if ((m && rand < 0.4) || (!m && rand < 0.9)) {\n          this.makeConstellation({\n            x: baseX + perturb(),\n            y: baseY + perturb(),\n          });\n        }\n      }\n    }\n  }\n\n  private allowMouse(): boolean {\n    return !(hasTouchscreen() || isMobileOrTablet() || window.innerWidth < 600);\n  }\n\n  private frame(): void {\n    // references for ease\n    const ctx = this.ctx;\n    const canvas = this.canvas;\n    const mouse = this.mouse;\n    const delMouse = this.delMouse;\n    const edges = this.edges;\n    const points = this.points;\n    const realPos = this.realPos;\n\n    // fake mouse\n    if (!this.allowMouse()) {\n      const now = () => Date.now() / 3000;\n\n      const xOfT = () => 1.5 * (0.5 + 0.5 * Math.cos(now()));\n      const yOfT = () => 1.5 * (0.5 + 0.5 * Math.sin(now()));\n      const fakeE = {\n        clientX: xOfT() * window.innerWidth,\n        clientY: yOfT() * window.innerHeight,\n      };\n      this._mouseMove(fakeE as MouseEvent);\n    }\n\n    // draw stuff\n\n    ctx.clearRect(0, 0, canvas.width, canvas.height);\n\n    for (const i in points) {\n      const point = points[i];\n      // const { x, y } = point.pos;\n      const {\n        z,\n        pos: { x, y },\n      } = point;\n\n      const realX = x + z * delMouse.x * -4;\n      const realY = y + z * delMouse.y * -4;\n\n      this.realPos[i] = {\n        x: realX,\n        y: realY,\n      };\n    }\n\n    for (const edge of edges) {\n      const [i1, i2] = edge;\n      const p1 = realPos[i1].x === -0xdeadbeef ? points[i1].pos : realPos[i1];\n      const p2 = realPos[i2].x === -0xdeadbeef ? points[i2].pos : realPos[i2];\n\n      ctx.strokeStyle = dfstyles.colors.subtext;\n\n      ctx.lineWidth = 0.5;\n      ctx.beginPath();\n      ctx.moveTo(p1.x, p1.y);\n      ctx.lineTo(p2.x, p2.y);\n      ctx.stroke();\n    }\n\n    for (const i in points) {\n      const { x: realX, y: realY } = this.realPos[i];\n\n      const point = points[i];\n      // const { x, y } = point.pos;\n      const { r, color } = point;\n\n      const dist = (realX - mouse.x) ** 2 + (realY - mouse.y) ** 2;\n      const maxDist = 900 ** 2;\n      const factor1 = Math.max(0, 1 - dist / maxDist);\n      const factor2 = 0.5 * (Math.sin(8 ** Math.sin(dist / 300000)) + 1);\n\n      // const factor = factor1 * factor2;\n      const factor = factor1 * factor2;\n\n      // const factor = factor1;\n\n      ctx.fillStyle = dfstyles.colors.subtext;\n      ctx.beginPath();\n      ctx.arc(realX, realY, r * (1 + factor * 1.5), 0, 2 * Math.PI);\n      ctx.fill();\n\n      ctx.globalAlpha = factor;\n\n      ctx.fillStyle = color;\n      ctx.beginPath();\n      ctx.arc(realX, realY, r * (1 + factor * 1.5), 0, 2 * Math.PI);\n      ctx.fill();\n\n      ctx.globalAlpha = 1.0;\n    }\n\n    this.frameRequestId = window.requestAnimationFrame(this.frame.bind(this));\n  }\n\n  private mouseMove(e: MouseEvent) {\n    if (!this.allowMouse()) return;\n    this._mouseMove(e);\n  }\n\n  private _mouseMove(e: MouseEvent) {\n    const newMouse = {\n      x: e.clientX,\n      y: e.clientY,\n    };\n\n    let vMouse = { x: 0, y: 0 };\n\n    const _tCircle: (base: Position, max: number) => Position = (base, max) => {\n      if (base.x ** 2 + base.y ** 2 > max ** 2) {\n        const mult = max / Math.sqrt(base.x ** 2 + base.y ** 2);\n        return { x: base.x * mult, y: base.y * mult };\n      } else return base;\n    };\n\n    const _tIdentity = (x: Position, _m: number) => x;\n\n    if (this.mouse.x !== -0xdeadbeef) {\n      const baseVmouse = {\n        x: newMouse.x - this.mouse.x,\n        y: newMouse.y - this.mouse.y,\n      };\n      vMouse = _tCircle(baseVmouse, 20);\n    }\n\n    const baseDelMouse = {\n      x: this.delMouse.x + vMouse.x * 0.1,\n      y: this.delMouse.y + vMouse.y * 0.1,\n    };\n    this.delMouse = _tIdentity(baseDelMouse, 100);\n\n    this.mouse = {\n      x: e.clientX,\n      y: e.clientY,\n    };\n  }\n\n  static initialize(canvas: HTMLCanvasElement) {\n    const canvasRenderer = new LandingPageCanvasRenderer(canvas);\n    LandingPageCanvasRenderer.instance = canvasRenderer;\n    const _this = canvasRenderer;\n    window.addEventListener('mousemove', _this.mouseMove);\n\n    return canvasRenderer;\n  }\n\n  static destroyInstance(): void {\n    const _this = LandingPageCanvasRenderer.instance;\n    if (_this) {\n      window.cancelAnimationFrame(_this.frameRequestId);\n      window.removeEventListener('mousemove', _this.mouseMove);\n    }\n    LandingPageCanvasRenderer.instance = null;\n  }\n}\n\nexport default function LandingPageCanvas() {\n  const [width, setWidth] = React.useState(window.innerWidth);\n  const [height, setHeight] = React.useState(window.innerHeight * 1.5);\n  const canvasRef = useRef<HTMLCanvasElement | null>(null);\n\n  const onResize = useCallback(function onResize() {\n    setWidth(window.innerWidth);\n    setHeight(window.innerHeight);\n  }, []);\n\n  useEffect(() => {\n    if (canvasRef.current) LandingPageCanvasRenderer.initialize(canvasRef.current);\n    else console.error('could not init draw');\n\n    window.addEventListener('resize', onResize);\n\n    return () => {\n      window.removeEventListener('resize', onResize);\n      LandingPageCanvasRenderer.destroyInstance();\n    };\n  }, [onResize]);\n\n  return <canvas width={width} height={height} ref={canvasRef} style={canvasStyle}></canvas>;\n}\n\nexport function LandingPageBackground() {\n  return (\n    <div style={{ width: '100%', height: '100%' }}>\n      <LandingPageCanvas />\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Renderers/PlanetscapeRenderer/PlanetIcons.tsx",
    "content": "import { EMPTY_ADDRESS, MAX_PLANET_LEVEL } from '@darkforest_eth/constants';\nimport { isLocatable } from '@darkforest_eth/gamelogic';\nimport { bonusFromHex } from '@darkforest_eth/hexgen';\nimport { getPlanetName } from '@darkforest_eth/procedural';\nimport { Planet, PlanetType, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { getPlanetRank } from '../../../Backend/Utils/Utils';\nimport { StatIdx } from '../../../_types/global/GlobalTypes';\nimport { Icon, IconType, RankIcon } from '../../Components/Icons';\nimport { TooltipTrigger } from '../../Panes/Tooltip';\nimport dfstyles from '../../Styles/dfstyles';\nimport { useUIManager } from '../../Utils/AppHooks';\n\nconst StyledPlanetIcons = styled.div`\n  display: inline-flex;\n  flex-direction: row;\n  flex-wrap: wrap-reverse;\n  align-items: center;\n  justify-content: center;\n\n  & > span {\n    font-size: 0.9em;\n    width: 1.5em;\n    height: 1.5em;\n    background: ${dfstyles.colors.backgroundlighter};\n    border-radius: 2px;\n    margin: 0.1em;\n    margin-right: 0.25em;\n    cursor: help;\n\n    &,\n    & > span {\n      display: inline-flex !important;\n      flex-direction: row;\n      justify-content: space-around;\n      align-items: center;\n    }\n  }\n`;\n\nconst ClownIcon = styled.span`\n  background: red;\n  width: 8px;\n  height: 8px;\n  border-radius: 4px;\n`;\n\nexport function PlanetIcons({ planet }: { planet: Planet | undefined }) {\n  const uiManager = useUIManager();\n\n  if (!planet) return <StyledPlanetIcons />;\n  const bonuses = bonusFromHex(planet.locationId);\n  const rank = getPlanetRank(planet);\n\n  let captureZoneIcons = null;\n  if (uiManager.captureZonesEnabled) {\n    const captureZoneGenerator = uiManager.getCaptureZoneGenerator();\n    if (captureZoneGenerator) {\n      captureZoneIcons = (\n        <>\n          {captureZoneGenerator.isInZone(planet.locationId) &&\n            uiManager.potentialCaptureScore(planet.planetLevel) > 0 &&\n            planet.invader === EMPTY_ADDRESS &&\n            planet.capturer === EMPTY_ADDRESS && (\n              <TooltipTrigger name={TooltipName.Invadable}>\n                <Icon type={IconType.Invadable} />\n              </TooltipTrigger>\n            )}\n          {planet.invader !== EMPTY_ADDRESS && planet.capturer === EMPTY_ADDRESS && (\n            <TooltipTrigger name={TooltipName.Capturable}>\n              <Icon type={IconType.Capturable} />\n            </TooltipTrigger>\n          )}\n          {planet.capturer !== EMPTY_ADDRESS && (\n            <TooltipTrigger\n              name={TooltipName.Empty}\n              extraContent={<>This planet has been captured by {planet.capturer}</>}\n            >\n              <Icon type={IconType.Capturable} />\n            </TooltipTrigger>\n          )}\n        </>\n      );\n    }\n  }\n\n  return (\n    <StyledPlanetIcons>\n      {planet.owner === EMPTY_ADDRESS && planet.energy > 0 && (\n        <TooltipTrigger name={TooltipName.Pirates}>\n          <Icon type={IconType.Pirates} />\n        </TooltipTrigger>\n      )}\n      {planet.planetLevel === MAX_PLANET_LEVEL && (\n        <TooltipTrigger name={TooltipName.MaxLevel}>\n          <Icon type={IconType.MaxLevel} />\n        </TooltipTrigger>\n      )}\n      {planet.planetType === PlanetType.SILVER_MINE && (\n        <TooltipTrigger name={TooltipName.SilverProd}>\n          <Icon type={IconType.SilverProd} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.EnergyCap] && (\n        <TooltipTrigger name={TooltipName.BonusEnergyCap}>\n          <Icon type={IconType.Energy} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.EnergyGro] && (\n        <TooltipTrigger name={TooltipName.BonusEnergyGro}>\n          <Icon type={IconType.EnergyGrowth} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.Range] && (\n        <TooltipTrigger name={TooltipName.BonusRange}>\n          <Icon type={IconType.Range} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.Speed] && (\n        <TooltipTrigger name={TooltipName.BonusSpeed}>\n          <Icon type={IconType.Speed} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.Defense] && (\n        <TooltipTrigger name={TooltipName.BonusDefense}>\n          <Icon type={IconType.Defense} />\n        </TooltipTrigger>\n      )}\n      {bonuses[StatIdx.SpaceJunk] && (\n        <TooltipTrigger name={TooltipName.BonusSpaceJunk}>\n          <Icon type={IconType.Sparkles} />\n        </TooltipTrigger>\n      )}\n      {rank > 0 && (\n        <TooltipTrigger name={TooltipName.PlanetRank}>\n          <RankIcon planet={planet} />\n        </TooltipTrigger>\n      )}\n      {getPlanetName(planet) === 'Clown Town' && (\n        <TooltipTrigger name={TooltipName.Clowntown}>\n          <ClownIcon />\n        </TooltipTrigger>\n      )}\n      {isLocatable(planet) &&\n        planet.planetType === PlanetType.RUINS &&\n        !planet.hasTriedFindingArtifact && (\n          <TooltipTrigger name={TooltipName.FindArtifact}>\n            <Icon type={IconType.Artifact} />\n          </TooltipTrigger>\n        )}\n      {captureZoneIcons}\n      {planet.destroyed && (\n        <TooltipTrigger\n          name={TooltipName.Empty}\n          extraContent={\n            <>\n              This planet is destroyed. It does not generate energy or silver, all incoming voyages\n              are void, and you cannot send or receive energy from it.\n            </>\n          }\n        >\n          <Icon type={IconType.Destroyed} />\n        </TooltipTrigger>\n      )}\n    </StyledPlanetIcons>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Styles/Colors.tsx",
    "content": "import { ArtifactRarity, Biome } from '@darkforest_eth/types';\nimport dfstyles from './dfstyles';\n\n/* tsx file so that we get color previews in VScode! */\n\nexport const BiomeTextColors = {\n  [Biome.UNKNOWN]: '#000000',\n  [Biome.OCEAN]: '#0088ff',\n  [Biome.FOREST]: '#46FB73',\n  [Biome.GRASSLAND]: '#CFF391',\n  [Biome.TUNDRA]: '#FB6A9D',\n  [Biome.SWAMP]: '#b48812',\n  [Biome.DESERT]: '#ffe554',\n  [Biome.ICE]: 'hsl(198, 78%, 77%)',\n  [Biome.WASTELAND]: '#000000',\n  [Biome.LAVA]: '#FF5100',\n  [Biome.CORRUPTED]: '#8DF15B',\n} as const;\n\nexport const BiomeBackgroundColors = {\n  [Biome.UNKNOWN]: '#000000',\n  [Biome.OCEAN]: '#000e2d',\n  [Biome.FOREST]: '#06251d',\n  [Biome.GRASSLAND]: '#212617',\n  [Biome.TUNDRA]: '#260f17',\n  [Biome.SWAMP]: '#211b0e',\n  [Biome.DESERT]: '#302e0e',\n  [Biome.ICE]: '#0d212f',\n  [Biome.WASTELAND]: '#321b1b',\n  [Biome.LAVA]: '#321000',\n  [Biome.CORRUPTED]: '#15260D',\n} as const;\n\nexport const ANCIENT_PURPLE = '#d23191';\nexport const ANCIENT_BLUE = '#b2fffc';\n\nexport const RarityColors = {\n  [ArtifactRarity.Unknown]: '#000000',\n  [ArtifactRarity.Common]: dfstyles.colors.subtext,\n  [ArtifactRarity.Rare]: '#6b68ff',\n  [ArtifactRarity.Epic]: '#c13cff',\n  [ArtifactRarity.Legendary]: '#f8b73e',\n  [ArtifactRarity.Mythic]: '#ff44b7',\n} as const;\n"
  },
  {
    "path": "src/Frontend/Styles/Mixins.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { Planet, PlanetType } from '@darkforest_eth/types';\nimport { css, keyframes } from 'styled-components';\nimport { BiomeBackgroundColors } from './Colors';\n\nconst scrolling = keyframes`\n  from {\n    background-position: 0 0;\n  }\n  to {\n    background-position: 200px 200px;\n  }\n`;\n\nexport function planetBackground({ planet }: { planet: Planet | undefined }) {\n  if (!planet || planet.planetType === PlanetType.TRADING_POST)\n    return css`\n      background: url('/public/img/spacebg.jpg');\n      background-size: 200px 200px;\n      background-repeat: repeat;\n      animation: ${scrolling} 10s linear infinite;\n    `;\n  else\n    return isLocatable(planet)\n      ? css`\n          background: ${BiomeBackgroundColors[planet.biome]};\n        `\n      : ``;\n}\n"
  },
  {
    "path": "src/Frontend/Styles/dfstyles.ts",
    "content": "import { RECOMMENDED_MODAL_WIDTH } from '@darkforest_eth/constants';\nimport { SpaceType } from '@darkforest_eth/types';\nimport color from 'color';\nimport { css } from 'styled-components';\n\nexport const ARTIFACT_ROW_H = 48;\n\nexport const SPACE_TYPE_COLORS = {\n  [SpaceType.NEBULA]: 'rgb(0, 20.4, 81.6)',\n  [SpaceType.SPACE]: 'rgb(0, 5.4, 43.35)',\n  [SpaceType.DEEP_SPACE]: 'rgb(2.04, 0, 6.12)',\n  [SpaceType.DEAD_SPACE]: 'rgb(0, 37, 1)',\n} as const;\n\nconst text = color('#bbb').hex();\nconst textLight = color(text).lighten(0.3).hex();\nconst subtext = color(text).darken(0.3).hex();\nconst subbertext = color(text).darken(0.5).hex();\nconst subbesttext = color(text).darken(0.8).hex();\n\nconst background = '#151515';\nconst backgrounddark = '#252525';\nconst backgroundlight = color(background).lighten(0.5).hex();\nconst backgroundlighter = color(backgroundlight).lighten(0.3).hex();\n\nconst border = '#777';\nconst borderDark = color(border).darken(0.2).hex();\nconst borderDarker = color(borderDark).darken(0.2).hex();\nconst borderDarkest = color(borderDarker).darken(0.5).hex();\n\nconst blueBackground = '#0a0a23';\n\nconst dfblue = '#00ADE1';\nconst dfgreen = '#00DC82';\nconst dfgreendark = color(dfgreen).darken(0.7).hex();\nconst dfgreenlight = color(dfgreen).lighten(0.1).hex();\nconst dfred = '#FF6492';\nconst dfyellow = '#e8e228';\nconst dfpurple = '#9189d9';\nconst dfwhite = '#ffffff';\nconst dforange = 'rgb(196, 101, 0)';\n\nconst dfstyles = {\n  colors: {\n    text,\n    textLight,\n    subtext,\n    subbertext,\n    subbesttext,\n    blueBackground,\n    background,\n    backgrounddark,\n    backgroundlight,\n    backgroundlighter,\n    dfblue,\n\n    border,\n    borderDark,\n    borderDarker,\n    borderDarkest,\n\n    dfgreen,\n    dfgreendark,\n    dfgreenlight,\n    dfred,\n    dfyellow,\n    dfpurple,\n    dfwhite,\n    dforange,\n\n    artifactBackground: 'rgb(21, 17, 71)',\n\n    icons: {\n      twitter: '#1DA1F2',\n      github: '#8e65db',\n      discord: '#7289da',\n      email: '#D44638',\n      blog: '#ffcb1f',\n    },\n  },\n\n  borderRadius: '3px',\n\n  fontSize: '16pt',\n  fontSizeS: '12pt',\n  fontSizeXS: '10pt',\n  fontH1: '42pt',\n  fontH1S: '36pt',\n  fontH2: '24pt',\n\n  titleFont: 'perfect_dos_vga_437regular',\n\n  screenSizeS: '660px',\n\n  game: {\n    terminalWidth: '240pt',\n    fontSize: '12pt',\n    canvasbg: '#100544',\n    rangecolors: {\n      dash: '#9691bf',\n      dashenergy: '#f5c082',\n      colorenergy: '#080330',\n      color100: '#050228',\n      color50: '#050233',\n      color25: '#050238',\n    },\n    bonuscolors: {\n      energyCap: 'hsl(360, 73%, 70%)',\n      speed: 'hsl(290, 73%, 70%)',\n      def: 'hsl(231, 73%, 70%)',\n      spaceJunk: 'hsl(43, 33%, 29%)',\n      energyGro: 'hsl(136, 73%, 70%)',\n      range: 'hsl(50, 73%, 70%)',\n    },\n    toolbarHeight: '12em',\n    terminalFontSize: '10pt',\n\n    styles: {\n      active: 'filter: brightness(80%)',\n      animProps: 'ease-in-out infinite alternate-reverse',\n    },\n  },\n\n  prefabs: {\n    // https://stackoverflow.com/questions/826782/how-to-disable-text-selection-highlighting\n    noselect: css`\n      -webkit-touch-callout: none; /* iOS Safari */\n      -webkit-user-select: none; /* Safari */\n      -khtml-user-select: none; /* Konqueror HTML */\n      -moz-user-select: none; /* Old versions of Firefox */\n      -ms-user-select: none; /* Internet Explorer/Edge */\n      user-select: none;\n    `,\n  },\n};\n\nexport const snips = {\n  bigPadding: css`\n    padding: 2px 12px;\n  `,\n  defaultModalWidth: css`\n    width: ${RECOMMENDED_MODAL_WIDTH};\n    max-width: ${RECOMMENDED_MODAL_WIDTH};\n  `,\n  defaultBackground: `background: ${dfstyles.colors.background};`,\n  roundedBorders: `border-radius:${dfstyles.borderRadius};`,\n  roundedBordersWithEdge: css`\n    border-radius: 3px;\n    border: 1px solid ${dfstyles.colors.borderDark};\n  `,\n  absoluteTopLeft: css`\n    position: absolute;\n    top: 0;\n    left: 0;\n  `,\n  pane: ``,\n  // It is unclear where this should go in this file\n  destroyedBackground: {\n    backgroundImage: 'url(\"/public/img/destroyedbg.png\")',\n    backgroundSize: '150px',\n    backgroundPosition: 'right bottom',\n    backgroundRepeat: 'no-repeat',\n  } as CSSStyleDeclaration & React.CSSProperties,\n};\n\nexport default dfstyles;\n"
  },
  {
    "path": "src/Frontend/Styles/font/generator_config.txt",
    "content": "# Font Squirrel Font-face Generator Configuration File\n# Upload this file to the generator to recreate the settings\n# you used to create these fonts.\n\n{\"mode\":\"optimal\",\"formats\":[\"woff\",\"woff2\"],\"tt_instructor\":\"default\",\"fix_gasp\":\"xy\",\"fix_vertical_metrics\":\"Y\",\"metrics_ascent\":\"\",\"metrics_descent\":\"\",\"metrics_linegap\":\"\",\"add_spaces\":\"Y\",\"add_hyphens\":\"Y\",\"fallback\":\"none\",\"fallback_custom\":\"100\",\"options_subset\":\"basic\",\"subset_custom\":\"\",\"subset_custom_range\":\"\",\"subset_ot_features_list\":\"\",\"css_stylesheet\":\"stylesheet.css\",\"filename_suffix\":\"-webfont\",\"emsquare\":\"2048\",\"spacing_adjustment\":\"0\"}"
  },
  {
    "path": "src/Frontend/Styles/font/perfect_dos_vga_437-demo.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\" lang=\"en\">\n  <head>\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n    <script\n      src=\"http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js\"\n      type=\"text/javascript\"\n      charset=\"utf-8\"\n    ></script>\n    <script type=\"text/javascript\">\n      (function ($) {\n        $.fn.easyTabs = function (option) {\n          var param = jQuery.extend(\n            { fadeSpeed: 'fast', defaultContent: 1, activeClass: 'active' },\n            option\n          );\n          $(this).each(function () {\n            var thisId = '#' + this.id;\n            if (param.defaultContent == '') {\n              param.defaultContent = 1;\n            }\n            if (typeof param.defaultContent == 'number') {\n              var defaultTab = $(thisId + ' .tabs li:eq(' + (param.defaultContent - 1) + ') a')\n                .attr('href')\n                .substr(1);\n            } else {\n              var defaultTab = param.defaultContent;\n            }\n            $(thisId + ' .tabs li a').each(function () {\n              var tabToHide = $(this).attr('href').substr(1);\n              $('#' + tabToHide).addClass('easytabs-tab-content');\n            });\n            hideAll();\n            changeContent(defaultTab);\n            function hideAll() {\n              $(thisId + ' .easytabs-tab-content').hide();\n            }\n            function changeContent(tabId) {\n              hideAll();\n              $(thisId + ' .tabs li').removeClass(param.activeClass);\n              $(thisId + ' .tabs li a[href=#' + tabId + ']')\n                .closest('li')\n                .addClass(param.activeClass);\n              if (param.fadeSpeed != 'none') {\n                $(thisId + ' #' + tabId).fadeIn(param.fadeSpeed);\n              } else {\n                $(thisId + ' #' + tabId).show();\n              }\n            }\n            $(thisId + ' .tabs li').click(function () {\n              var tabId = $(this).find('a').attr('href').substr(1);\n              changeContent(tabId);\n              return false;\n            });\n          });\n        };\n      })(jQuery);\n    </script>\n    <link\n      rel=\"stylesheet\"\n      href=\"specimen_files/specimen_stylesheet.css\"\n      type=\"text/css\"\n      charset=\"utf-8\"\n    />\n    <link rel=\"stylesheet\" href=\"stylesheet.css\" type=\"text/css\" charset=\"utf-8\" />\n\n    <style type=\"text/css\">\n      body {\n        font-family: 'perfect_dos_vga_437regular';\n      }\n    </style>\n\n    <title>Perfect DOS VGA 437 Regular Specimen</title>\n\n    <script type=\"text/javascript\" charset=\"utf-8\">\n      $(document).ready(function () {\n        $('#container').easyTabs({ defaultContent: 1 });\n      });\n    </script>\n  </head>\n\n  <body>\n    <div id=\"container\">\n      <div id=\"header\">Perfect DOS VGA 437 Regular</div>\n      <ul class=\"tabs\">\n        <li><a href=\"#specimen\">Specimen</a></li>\n        <li><a href=\"#layout\">Sample Layout</a></li>\n        <li><a href=\"#glyphs\">Glyphs &amp; Languages</a></li>\n        <li><a href=\"#installing\">Installing Webfonts</a></li>\n      </ul>\n\n      <div id=\"main_content\">\n        <div id=\"specimen\">\n          <div class=\"section\">\n            <div class=\"grid12 firstcol\">\n              <div class=\"huge\">AaBb</div>\n            </div>\n          </div>\n\n          <div class=\"section\">\n            <div class=\"glyph_range\">\n              A&#x200B;B&#x200b;C&#x200b;D&#x200b;E&#x200b;F&#x200b;G&#x200b;H&#x200b;I&#x200b;J&#x200b;K&#x200b;L&#x200b;M&#x200b;N&#x200b;O&#x200b;P&#x200b;Q&#x200b;R&#x200b;S&#x200b;T&#x200b;U&#x200b;V&#x200b;W&#x200b;X&#x200b;Y&#x200b;Z&#x200b;a&#x200b;b&#x200b;c&#x200b;d&#x200b;e&#x200b;f&#x200b;g&#x200b;h&#x200b;i&#x200b;j&#x200b;k&#x200b;l&#x200b;m&#x200b;n&#x200b;o&#x200b;p&#x200b;q&#x200b;r&#x200b;s&#x200b;t&#x200b;u&#x200b;v&#x200b;w&#x200b;x&#x200b;y&#x200b;z&#x200b;1&#x200b;2&#x200b;3&#x200b;4&#x200b;5&#x200b;6&#x200b;7&#x200b;8&#x200b;9&#x200b;0&#x200b;&amp;&#x200b;.&#x200b;,&#x200b;?&#x200b;!&#x200b;&#64;&#x200b;(&#x200b;)&#x200b;#&#x200b;$&#x200b;%&#x200b;*&#x200b;+&#x200b;-&#x200b;=&#x200b;:&#x200b;;\n            </div>\n          </div>\n          <div class=\"section\">\n            <div class=\"grid12 firstcol\">\n              <table class=\"sample_table\">\n                <tr>\n                  <td>10</td>\n                  <td class=\"size10\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>11</td>\n                  <td class=\"size11\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>12</td>\n                  <td class=\"size12\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>13</td>\n                  <td class=\"size13\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>14</td>\n                  <td class=\"size14\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>16</td>\n                  <td class=\"size16\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>18</td>\n                  <td class=\"size18\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>20</td>\n                  <td class=\"size20\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>24</td>\n                  <td class=\"size24\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>30</td>\n                  <td class=\"size30\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>36</td>\n                  <td class=\"size36\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>48</td>\n                  <td class=\"size48\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>60</td>\n                  <td class=\"size60\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>72</td>\n                  <td class=\"size72\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n                <tr>\n                  <td>90</td>\n                  <td class=\"size90\">\n                    abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n                  </td>\n                </tr>\n              </table>\n            </div>\n          </div>\n\n          <div class=\"section\" id=\"bodycomparison\">\n            <div id=\"xheight\">\n              <div class=\"fontbody\">\n                &#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;&#x25FC;body\n              </div>\n              <div class=\"arialbody\">body</div>\n              <div class=\"verdanabody\">body</div>\n              <div class=\"georgiabody\">body</div>\n            </div>\n            <div class=\"fontbody\" style=\"z-index: 1\">\n              body<span>Perfect DOS VGA 437 Regular</span>\n            </div>\n            <div class=\"arialbody\" style=\"z-index: 1\">body<span>Arial</span></div>\n            <div class=\"verdanabody\" style=\"z-index: 1\">body<span>Verdana</span></div>\n            <div class=\"georgiabody\" style=\"z-index: 1\">body<span>Georgia</span></div>\n          </div>\n\n          <div class=\"section psample psample_row1\" id=\"\">\n            <div class=\"grid2 firstcol\">\n              <p class=\"size10\">\n                <span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid3\">\n              <p class=\"size11\">\n                <span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid3\">\n              <p class=\"size12\">\n                <span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid4\">\n              <p class=\"size13\">\n                <span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"white_blend\"></div>\n          </div>\n          <div class=\"section psample psample_row2\" id=\"\">\n            <div class=\"grid3 firstcol\">\n              <p class=\"size14\">\n                <span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid4\">\n              <p class=\"size16\">\n                <span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid5\">\n              <p class=\"size18\">\n                <span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n\n            <div class=\"white_blend\"></div>\n          </div>\n\n          <div class=\"section psample psample_row3\" id=\"\">\n            <div class=\"grid5 firstcol\">\n              <p class=\"size20\">\n                <span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid7\">\n              <p class=\"size24\">\n                <span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n\n            <div class=\"white_blend\"></div>\n          </div>\n\n          <div class=\"section psample psample_row4\" id=\"\">\n            <div class=\"grid12 firstcol\">\n              <p class=\"size30\">\n                <span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"white_blend\"></div>\n          </div>\n\n          <div class=\"section psample psample_row1 fullreverse\">\n            <div class=\"grid2 firstcol\">\n              <p class=\"size10\">\n                <span>10.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid3\">\n              <p class=\"size11\">\n                <span>11.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid3\">\n              <p class=\"size12\">\n                <span>12.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid4\">\n              <p class=\"size13\">\n                <span>13.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"black_blend\"></div>\n          </div>\n\n          <div class=\"section psample psample_row2 fullreverse\">\n            <div class=\"grid3 firstcol\">\n              <p class=\"size14\">\n                <span>14.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid4\">\n              <p class=\"size16\">\n                <span>16.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid5\">\n              <p class=\"size18\">\n                <span>18.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"black_blend\"></div>\n          </div>\n\n          <div class=\"section psample fullreverse psample_row3\" id=\"\">\n            <div class=\"grid5 firstcol\">\n              <p class=\"size20\">\n                <span>20.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"grid7\">\n              <p class=\"size24\">\n                <span>24.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n\n            <div class=\"black_blend\"></div>\n          </div>\n\n          <div\n            class=\"section psample fullreverse psample_row4\"\n            id=\"\"\n            style=\"border-bottom: 20px #000 solid\"\n          >\n            <div class=\"grid12 firstcol\">\n              <p class=\"size30\">\n                <span>30.</span>Aenean lacinia bibendum nulla sed consectetur. Fusce dapibus, tellus\n                ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet\n                risus. Nullam id dolor id nibh ultricies vehicula ut id elit. Cum sociis natoque\n                penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nulla vitae elit\n                libero, a pharetra augue.\n              </p>\n            </div>\n            <div class=\"black_blend\"></div>\n          </div>\n        </div>\n\n        <div id=\"layout\">\n          <div class=\"section\">\n            <div class=\"grid12 firstcol\">\n              <h1>Lorem Ipsum Dolor</h1>\n              <h2>Etiam porta sem malesuada magna mollis euismod</h2>\n\n              <p class=\"byline\">By <a href=\"#link\">Aenean Lacinia</a></p>\n            </div>\n          </div>\n          <div class=\"section\">\n            <div class=\"grid8 firstcol\">\n              <p class=\"large\">\n                Donec sed odio dui. Morbi leo risus, porta ac consectetur ac, vestibulum at eros.\n                Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut\n                fermentum massa justo sit amet risus.\n              </p>\n\n              <h3>Pellentesque ornare sem</h3>\n\n              <p>\n                Maecenas sed diam eget risus varius blandit sit amet non magna. Maecenas faucibus\n                mollis interdum. Donec ullamcorper nulla non metus auctor fringilla. Nullam id dolor\n                id nibh ultricies vehicula ut id elit. Nullam id dolor id nibh ultricies vehicula ut\n                id elit.\n              </p>\n\n              <p>\n                Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum. Lorem\n                ipsum dolor sit amet, consectetur adipiscing elit. Cum sociis natoque penatibus et\n                magnis dis parturient montes, nascetur ridiculus mus.\n              </p>\n\n              <p>\n                Nulla vitae elit libero, a pharetra augue. Praesent commodo cursus magna, vel\n                scelerisque nisl consectetur et. Aenean lacinia bibendum nulla sed consectetur.\n              </p>\n\n              <p>\n                Nullam quis risus eget urna mollis ornare vel eu leo. Nullam quis risus eget urna\n                mollis ornare vel eu leo. Maecenas sed diam eget risus varius blandit sit amet non\n                magna. Donec ullamcorper nulla non metus auctor fringilla.\n              </p>\n\n              <h3>Cras mattis consectetur</h3>\n\n              <p>\n                Aenean eu leo quam. Pellentesque ornare sem lacinia quam venenatis vestibulum.\n                Aenean lacinia bibendum nulla sed consectetur. Integer posuere erat a ante venenatis\n                dapibus posuere velit aliquet. Cras mattis consectetur purus sit amet fermentum.\n              </p>\n\n              <p>\n                Nullam id dolor id nibh ultricies vehicula ut id elit. Nullam quis risus eget urna\n                mollis ornare vel eu leo. Cras mattis consectetur purus sit amet fermentum.\n              </p>\n            </div>\n\n            <div class=\"grid4 sidebar\">\n              <div class=\"box reverse\">\n                <p class=\"last\">\n                  Nullam quis risus eget urna mollis ornare vel eu leo. Donec ullamcorper nulla non\n                  metus auctor fringilla. Cras mattis consectetur purus sit amet fermentum. Sed\n                  posuere consectetur est at lobortis. Lorem ipsum dolor sit amet, consectetur\n                  adipiscing elit.\n                </p>\n              </div>\n\n              <p class=\"caption\">Maecenas sed diam eget risus varius.</p>\n\n              <p>\n                Vestibulum id ligula porta felis euismod semper. Integer posuere erat a ante\n                venenatis dapibus posuere velit aliquet. Vestibulum id ligula porta felis euismod\n                semper. Sed posuere consectetur est at lobortis. Maecenas sed diam eget risus varius\n                blandit sit amet non magna. Fusce dapibus, tellus ac cursus commodo, tortor mauris\n                condimentum nibh, ut fermentum massa justo sit amet risus.\n              </p>\n\n              <p>\n                Duis mollis, est non commodo luctus, nisi erat porttitor ligula, eget lacinia odio\n                sem nec elit. Aenean lacinia bibendum nulla sed consectetur. Vivamus sagittis lacus\n                vel augue laoreet rutrum faucibus dolor auctor. Aenean lacinia bibendum nulla sed\n                consectetur. Nullam quis risus eget urna mollis ornare vel eu leo.\n              </p>\n\n              <p>\n                Praesent commodo cursus magna, vel scelerisque nisl consectetur et. Donec\n                ullamcorper nulla non metus auctor fringilla. Maecenas faucibus mollis interdum.\n                Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut\n                fermentum massa justo sit amet risus.\n              </p>\n            </div>\n          </div>\n        </div>\n\n        <div id=\"glyphs\">\n          <div class=\"section\">\n            <div class=\"grid12 firstcol\">\n              <h1>Language Support</h1>\n              <p>\n                The subset of Perfect DOS VGA 437 Regular in this kit supports the following\n                languages:<br />\n\n                Albanian, Basque, Breton, Chamorro, Danish, Dutch, English, Faroese, Finnish,\n                French, Frisian, Galician, German, Icelandic, Italian, Malagasy, Norwegian,\n                Portuguese, Spanish, Alsatian, Aragonese, Arapaho, Arrernte, Asturian, Aymara,\n                Bislama, Cebuano, Corsican, Fijian, French_creole, Genoese, Gilbertese, Greenlandic,\n                Haitian_creole, Hiligaynon, Hmong, Hopi, Ibanag, Iloko_ilokano, Indonesian,\n                Interglossa_glosa, Interlingua, Irish_gaelic, Jerriais, Lojban, Lombard,\n                Luxembourgeois, Manx, Mohawk, Norfolk_pitcairnese, Occitan, Oromo, Pangasinan,\n                Papiamento, Piedmontese, Potawatomi, Rhaeto-romance, Romansh, Rotokas, Sami_lule,\n                Samoan, Sardinian, Scots_gaelic, Seychelles_creole, Shona, Sicilian, Somali,\n                Southern_ndebele, Swahili, Swati_swazi, Tagalog_filipino_pilipino, Tetum, Tok_pisin,\n                Uyghur_latinized, Volapuk, Walloon, Warlpiri, Xhosa, Yapese, Zulu, Latinbasic,\n                Ubasic, Demo\n              </p>\n              <h1>Glyph Chart</h1>\n              <p>\n                The subset of Perfect DOS VGA 437 Regular in this kit includes all the glyphs listed\n                below. Unicode entities are included above each glyph to help you insert individual\n                characters into your layout.\n              </p>\n              <div id=\"glyph_chart\">\n                <div>\n                  <p>&amp;#1;</p>\n                  &#1;\n                </div>\n                <div>\n                  <p>&amp;#2;</p>\n                  &#2;\n                </div>\n                <div>\n                  <p>&amp;#3;</p>\n                  &#3;\n                </div>\n                <div>\n                  <p>&amp;#4;</p>\n                  &#4;\n                </div>\n                <div>\n                  <p>&amp;#5;</p>\n                  &#5;\n                </div>\n                <div>\n                  <p>&amp;#6;</p>\n                  &#6;\n                </div>\n                <div>\n                  <p>&amp;#7;</p>\n                  &#7;\n                </div>\n                <div>\n                  <p>&amp;#8;</p>\n                  &#8;\n                </div>\n                <div>\n                  <p>&amp;#9;</p>\n                  &#9;\n                </div>\n                <div>\n                  <p>&amp;#10;</p>\n                  &#10;\n                </div>\n                <div>\n                  <p>&amp;#11;</p>\n                  &#11;\n                </div>\n                <div>\n                  <p>&amp;#12;</p>\n                  &#12;\n                </div>\n                <div>\n                  <p>&amp;#13;</p>\n                  &#13;\n                </div>\n                <div>\n                  <p>&amp;#14;</p>\n                  &#14;\n                </div>\n                <div>\n                  <p>&amp;#15;</p>\n                  &#15;\n                </div>\n                <div>\n                  <p>&amp;#16;</p>\n                  &#16;\n                </div>\n                <div>\n                  <p>&amp;#17;</p>\n                  &#17;\n                </div>\n                <div>\n                  <p>&amp;#18;</p>\n                  &#18;\n                </div>\n                <div>\n                  <p>&amp;#19;</p>\n                  &#19;\n                </div>\n                <div>\n                  <p>&amp;#20;</p>\n                  &#20;\n                </div>\n                <div>\n                  <p>&amp;#21;</p>\n                  &#21;\n                </div>\n                <div>\n                  <p>&amp;#22;</p>\n                  &#22;\n                </div>\n                <div>\n                  <p>&amp;#23;</p>\n                  &#23;\n                </div>\n                <div>\n                  <p>&amp;#24;</p>\n                  &#24;\n                </div>\n                <div>\n                  <p>&amp;#25;</p>\n                  &#25;\n                </div>\n                <div>\n                  <p>&amp;#26;</p>\n                  &#26;\n                </div>\n                <div>\n                  <p>&amp;#27;</p>\n                  &#27;\n                </div>\n                <div>\n                  <p>&amp;#28;</p>\n                  &#28;\n                </div>\n                <div>\n                  <p>&amp;#29;</p>\n                  &#29;\n                </div>\n                <div>\n                  <p>&amp;#30;</p>\n                  &#30;\n                </div>\n                <div>\n                  <p>&amp;#31;</p>\n                  &#31;\n                </div>\n                <div>\n                  <p>&amp;#32;</p>\n                  &#32;\n                </div>\n                <div>\n                  <p>&amp;#33;</p>\n                  &#33;\n                </div>\n                <div>\n                  <p>&amp;#34;</p>\n                  &#34;\n                </div>\n                <div>\n                  <p>&amp;#35;</p>\n                  &#35;\n                </div>\n                <div>\n                  <p>&amp;#36;</p>\n                  &#36;\n                </div>\n                <div>\n                  <p>&amp;#37;</p>\n                  &#37;\n                </div>\n                <div>\n                  <p>&amp;#38;</p>\n                  &#38;\n                </div>\n                <div>\n                  <p>&amp;#39;</p>\n                  &#39;\n                </div>\n                <div>\n                  <p>&amp;#40;</p>\n                  &#40;\n                </div>\n                <div>\n                  <p>&amp;#41;</p>\n                  &#41;\n                </div>\n                <div>\n                  <p>&amp;#42;</p>\n                  &#42;\n                </div>\n                <div>\n                  <p>&amp;#43;</p>\n                  &#43;\n                </div>\n                <div>\n                  <p>&amp;#44;</p>\n                  &#44;\n                </div>\n                <div>\n                  <p>&amp;#45;</p>\n                  &#45;\n                </div>\n                <div>\n                  <p>&amp;#46;</p>\n                  &#46;\n                </div>\n                <div>\n                  <p>&amp;#47;</p>\n                  &#47;\n                </div>\n                <div>\n                  <p>&amp;#48;</p>\n                  &#48;\n                </div>\n                <div>\n                  <p>&amp;#49;</p>\n                  &#49;\n                </div>\n                <div>\n                  <p>&amp;#50;</p>\n                  &#50;\n                </div>\n                <div>\n                  <p>&amp;#51;</p>\n                  &#51;\n                </div>\n                <div>\n                  <p>&amp;#52;</p>\n                  &#52;\n                </div>\n                <div>\n                  <p>&amp;#53;</p>\n                  &#53;\n                </div>\n                <div>\n                  <p>&amp;#54;</p>\n                  &#54;\n                </div>\n                <div>\n                  <p>&amp;#55;</p>\n                  &#55;\n                </div>\n                <div>\n                  <p>&amp;#56;</p>\n                  &#56;\n                </div>\n                <div>\n                  <p>&amp;#57;</p>\n                  &#57;\n                </div>\n                <div>\n                  <p>&amp;#58;</p>\n                  &#58;\n                </div>\n                <div>\n                  <p>&amp;#59;</p>\n                  &#59;\n                </div>\n                <div>\n                  <p>&amp;#60;</p>\n                  &#60;\n                </div>\n                <div>\n                  <p>&amp;#61;</p>\n                  &#61;\n                </div>\n                <div>\n                  <p>&amp;#62;</p>\n                  &#62;\n                </div>\n                <div>\n                  <p>&amp;#63;</p>\n                  &#63;\n                </div>\n                <div>\n                  <p>&amp;#64;</p>\n                  &#64;\n                </div>\n                <div>\n                  <p>&amp;#65;</p>\n                  &#65;\n                </div>\n                <div>\n                  <p>&amp;#66;</p>\n                  &#66;\n                </div>\n                <div>\n                  <p>&amp;#67;</p>\n                  &#67;\n                </div>\n                <div>\n                  <p>&amp;#68;</p>\n                  &#68;\n                </div>\n                <div>\n                  <p>&amp;#69;</p>\n                  &#69;\n                </div>\n                <div>\n                  <p>&amp;#70;</p>\n                  &#70;\n                </div>\n                <div>\n                  <p>&amp;#71;</p>\n                  &#71;\n                </div>\n                <div>\n                  <p>&amp;#72;</p>\n                  &#72;\n                </div>\n                <div>\n                  <p>&amp;#73;</p>\n                  &#73;\n                </div>\n                <div>\n                  <p>&amp;#74;</p>\n                  &#74;\n                </div>\n                <div>\n                  <p>&amp;#75;</p>\n                  &#75;\n                </div>\n                <div>\n                  <p>&amp;#76;</p>\n                  &#76;\n                </div>\n                <div>\n                  <p>&amp;#77;</p>\n                  &#77;\n                </div>\n                <div>\n                  <p>&amp;#78;</p>\n                  &#78;\n                </div>\n                <div>\n                  <p>&amp;#79;</p>\n                  &#79;\n                </div>\n                <div>\n                  <p>&amp;#80;</p>\n                  &#80;\n                </div>\n                <div>\n                  <p>&amp;#81;</p>\n                  &#81;\n                </div>\n                <div>\n                  <p>&amp;#82;</p>\n                  &#82;\n                </div>\n                <div>\n                  <p>&amp;#83;</p>\n                  &#83;\n                </div>\n                <div>\n                  <p>&amp;#84;</p>\n                  &#84;\n                </div>\n                <div>\n                  <p>&amp;#85;</p>\n                  &#85;\n                </div>\n                <div>\n                  <p>&amp;#86;</p>\n                  &#86;\n                </div>\n                <div>\n                  <p>&amp;#87;</p>\n                  &#87;\n                </div>\n                <div>\n                  <p>&amp;#88;</p>\n                  &#88;\n                </div>\n                <div>\n                  <p>&amp;#89;</p>\n                  &#89;\n                </div>\n                <div>\n                  <p>&amp;#90;</p>\n                  &#90;\n                </div>\n                <div>\n                  <p>&amp;#91;</p>\n                  &#91;\n                </div>\n                <div>\n                  <p>&amp;#92;</p>\n                  &#92;\n                </div>\n                <div>\n                  <p>&amp;#93;</p>\n                  &#93;\n                </div>\n                <div>\n                  <p>&amp;#94;</p>\n                  &#94;\n                </div>\n                <div>\n                  <p>&amp;#95;</p>\n                  &#95;\n                </div>\n                <div>\n                  <p>&amp;#96;</p>\n                  &#96;\n                </div>\n                <div>\n                  <p>&amp;#97;</p>\n                  &#97;\n                </div>\n                <div>\n                  <p>&amp;#98;</p>\n                  &#98;\n                </div>\n                <div>\n                  <p>&amp;#99;</p>\n                  &#99;\n                </div>\n                <div>\n                  <p>&amp;#100;</p>\n                  &#100;\n                </div>\n                <div>\n                  <p>&amp;#101;</p>\n                  &#101;\n                </div>\n                <div>\n                  <p>&amp;#102;</p>\n                  &#102;\n                </div>\n                <div>\n                  <p>&amp;#103;</p>\n                  &#103;\n                </div>\n                <div>\n                  <p>&amp;#104;</p>\n                  &#104;\n                </div>\n                <div>\n                  <p>&amp;#105;</p>\n                  &#105;\n                </div>\n                <div>\n                  <p>&amp;#106;</p>\n                  &#106;\n                </div>\n                <div>\n                  <p>&amp;#107;</p>\n                  &#107;\n                </div>\n                <div>\n                  <p>&amp;#108;</p>\n                  &#108;\n                </div>\n                <div>\n                  <p>&amp;#109;</p>\n                  &#109;\n                </div>\n                <div>\n                  <p>&amp;#110;</p>\n                  &#110;\n                </div>\n                <div>\n                  <p>&amp;#111;</p>\n                  &#111;\n                </div>\n                <div>\n                  <p>&amp;#112;</p>\n                  &#112;\n                </div>\n                <div>\n                  <p>&amp;#113;</p>\n                  &#113;\n                </div>\n                <div>\n                  <p>&amp;#114;</p>\n                  &#114;\n                </div>\n                <div>\n                  <p>&amp;#115;</p>\n                  &#115;\n                </div>\n                <div>\n                  <p>&amp;#116;</p>\n                  &#116;\n                </div>\n                <div>\n                  <p>&amp;#117;</p>\n                  &#117;\n                </div>\n                <div>\n                  <p>&amp;#118;</p>\n                  &#118;\n                </div>\n                <div>\n                  <p>&amp;#119;</p>\n                  &#119;\n                </div>\n                <div>\n                  <p>&amp;#120;</p>\n                  &#120;\n                </div>\n                <div>\n                  <p>&amp;#121;</p>\n                  &#121;\n                </div>\n                <div>\n                  <p>&amp;#122;</p>\n                  &#122;\n                </div>\n                <div>\n                  <p>&amp;#123;</p>\n                  &#123;\n                </div>\n                <div>\n                  <p>&amp;#124;</p>\n                  &#124;\n                </div>\n                <div>\n                  <p>&amp;#125;</p>\n                  &#125;\n                </div>\n                <div>\n                  <p>&amp;#126;</p>\n                  &#126;\n                </div>\n                <div>\n                  <p>&amp;#127;</p>\n                  &#127;\n                </div>\n                <div>\n                  <p>&amp;#129;</p>\n                  &#129;\n                </div>\n                <div>\n                  <p>&amp;#141;</p>\n                  &#141;\n                </div>\n                <div>\n                  <p>&amp;#142;</p>\n                  &#142;\n                </div>\n                <div>\n                  <p>&amp;#143;</p>\n                  &#143;\n                </div>\n                <div>\n                  <p>&amp;#144;</p>\n                  &#144;\n                </div>\n                <div>\n                  <p>&amp;#157;</p>\n                  &#157;\n                </div>\n                <div>\n                  <p>&amp;#158;</p>\n                  &#158;\n                </div>\n                <div>\n                  <p>&amp;#160;</p>\n                  &#160;\n                </div>\n                <div>\n                  <p>&amp;#161;</p>\n                  &#161;\n                </div>\n                <div>\n                  <p>&amp;#162;</p>\n                  &#162;\n                </div>\n                <div>\n                  <p>&amp;#163;</p>\n                  &#163;\n                </div>\n                <div>\n                  <p>&amp;#164;</p>\n                  &#164;\n                </div>\n                <div>\n                  <p>&amp;#165;</p>\n                  &#165;\n                </div>\n                <div>\n                  <p>&amp;#166;</p>\n                  &#166;\n                </div>\n                <div>\n                  <p>&amp;#167;</p>\n                  &#167;\n                </div>\n                <div>\n                  <p>&amp;#168;</p>\n                  &#168;\n                </div>\n                <div>\n                  <p>&amp;#169;</p>\n                  &#169;\n                </div>\n                <div>\n                  <p>&amp;#170;</p>\n                  &#170;\n                </div>\n                <div>\n                  <p>&amp;#171;</p>\n                  &#171;\n                </div>\n                <div>\n                  <p>&amp;#172;</p>\n                  &#172;\n                </div>\n                <div>\n                  <p>&amp;#173;</p>\n                  &#173;\n                </div>\n                <div>\n                  <p>&amp;#174;</p>\n                  &#174;\n                </div>\n                <div>\n                  <p>&amp;#175;</p>\n                  &#175;\n                </div>\n                <div>\n                  <p>&amp;#176;</p>\n                  &#176;\n                </div>\n                <div>\n                  <p>&amp;#177;</p>\n                  &#177;\n                </div>\n                <div>\n                  <p>&amp;#178;</p>\n                  &#178;\n                </div>\n                <div>\n                  <p>&amp;#179;</p>\n                  &#179;\n                </div>\n                <div>\n                  <p>&amp;#180;</p>\n                  &#180;\n                </div>\n                <div>\n                  <p>&amp;#181;</p>\n                  &#181;\n                </div>\n                <div>\n                  <p>&amp;#182;</p>\n                  &#182;\n                </div>\n                <div>\n                  <p>&amp;#183;</p>\n                  &#183;\n                </div>\n                <div>\n                  <p>&amp;#184;</p>\n                  &#184;\n                </div>\n                <div>\n                  <p>&amp;#185;</p>\n                  &#185;\n                </div>\n                <div>\n                  <p>&amp;#186;</p>\n                  &#186;\n                </div>\n                <div>\n                  <p>&amp;#187;</p>\n                  &#187;\n                </div>\n                <div>\n                  <p>&amp;#188;</p>\n                  &#188;\n                </div>\n                <div>\n                  <p>&amp;#189;</p>\n                  &#189;\n                </div>\n                <div>\n                  <p>&amp;#190;</p>\n                  &#190;\n                </div>\n                <div>\n                  <p>&amp;#191;</p>\n                  &#191;\n                </div>\n                <div>\n                  <p>&amp;#192;</p>\n                  &#192;\n                </div>\n                <div>\n                  <p>&amp;#193;</p>\n                  &#193;\n                </div>\n                <div>\n                  <p>&amp;#194;</p>\n                  &#194;\n                </div>\n                <div>\n                  <p>&amp;#195;</p>\n                  &#195;\n                </div>\n                <div>\n                  <p>&amp;#196;</p>\n                  &#196;\n                </div>\n                <div>\n                  <p>&amp;#197;</p>\n                  &#197;\n                </div>\n                <div>\n                  <p>&amp;#198;</p>\n                  &#198;\n                </div>\n                <div>\n                  <p>&amp;#199;</p>\n                  &#199;\n                </div>\n                <div>\n                  <p>&amp;#200;</p>\n                  &#200;\n                </div>\n                <div>\n                  <p>&amp;#201;</p>\n                  &#201;\n                </div>\n                <div>\n                  <p>&amp;#202;</p>\n                  &#202;\n                </div>\n                <div>\n                  <p>&amp;#203;</p>\n                  &#203;\n                </div>\n                <div>\n                  <p>&amp;#204;</p>\n                  &#204;\n                </div>\n                <div>\n                  <p>&amp;#205;</p>\n                  &#205;\n                </div>\n                <div>\n                  <p>&amp;#206;</p>\n                  &#206;\n                </div>\n                <div>\n                  <p>&amp;#207;</p>\n                  &#207;\n                </div>\n                <div>\n                  <p>&amp;#208;</p>\n                  &#208;\n                </div>\n                <div>\n                  <p>&amp;#209;</p>\n                  &#209;\n                </div>\n                <div>\n                  <p>&amp;#210;</p>\n                  &#210;\n                </div>\n                <div>\n                  <p>&amp;#211;</p>\n                  &#211;\n                </div>\n                <div>\n                  <p>&amp;#212;</p>\n                  &#212;\n                </div>\n                <div>\n                  <p>&amp;#213;</p>\n                  &#213;\n                </div>\n                <div>\n                  <p>&amp;#214;</p>\n                  &#214;\n                </div>\n                <div>\n                  <p>&amp;#215;</p>\n                  &#215;\n                </div>\n                <div>\n                  <p>&amp;#216;</p>\n                  &#216;\n                </div>\n                <div>\n                  <p>&amp;#217;</p>\n                  &#217;\n                </div>\n                <div>\n                  <p>&amp;#218;</p>\n                  &#218;\n                </div>\n                <div>\n                  <p>&amp;#219;</p>\n                  &#219;\n                </div>\n                <div>\n                  <p>&amp;#220;</p>\n                  &#220;\n                </div>\n                <div>\n                  <p>&amp;#221;</p>\n                  &#221;\n                </div>\n                <div>\n                  <p>&amp;#222;</p>\n                  &#222;\n                </div>\n                <div>\n                  <p>&amp;#223;</p>\n                  &#223;\n                </div>\n                <div>\n                  <p>&amp;#224;</p>\n                  &#224;\n                </div>\n                <div>\n                  <p>&amp;#225;</p>\n                  &#225;\n                </div>\n                <div>\n                  <p>&amp;#226;</p>\n                  &#226;\n                </div>\n                <div>\n                  <p>&amp;#227;</p>\n                  &#227;\n                </div>\n                <div>\n                  <p>&amp;#228;</p>\n                  &#228;\n                </div>\n                <div>\n                  <p>&amp;#229;</p>\n                  &#229;\n                </div>\n                <div>\n                  <p>&amp;#230;</p>\n                  &#230;\n                </div>\n                <div>\n                  <p>&amp;#231;</p>\n                  &#231;\n                </div>\n                <div>\n                  <p>&amp;#232;</p>\n                  &#232;\n                </div>\n                <div>\n                  <p>&amp;#233;</p>\n                  &#233;\n                </div>\n                <div>\n                  <p>&amp;#234;</p>\n                  &#234;\n                </div>\n                <div>\n                  <p>&amp;#235;</p>\n                  &#235;\n                </div>\n                <div>\n                  <p>&amp;#236;</p>\n                  &#236;\n                </div>\n                <div>\n                  <p>&amp;#237;</p>\n                  &#237;\n                </div>\n                <div>\n                  <p>&amp;#238;</p>\n                  &#238;\n                </div>\n                <div>\n                  <p>&amp;#239;</p>\n                  &#239;\n                </div>\n                <div>\n                  <p>&amp;#240;</p>\n                  &#240;\n                </div>\n                <div>\n                  <p>&amp;#241;</p>\n                  &#241;\n                </div>\n                <div>\n                  <p>&amp;#242;</p>\n                  &#242;\n                </div>\n                <div>\n                  <p>&amp;#243;</p>\n                  &#243;\n                </div>\n                <div>\n                  <p>&amp;#244;</p>\n                  &#244;\n                </div>\n                <div>\n                  <p>&amp;#245;</p>\n                  &#245;\n                </div>\n                <div>\n                  <p>&amp;#246;</p>\n                  &#246;\n                </div>\n                <div>\n                  <p>&amp;#247;</p>\n                  &#247;\n                </div>\n                <div>\n                  <p>&amp;#248;</p>\n                  &#248;\n                </div>\n                <div>\n                  <p>&amp;#249;</p>\n                  &#249;\n                </div>\n                <div>\n                  <p>&amp;#250;</p>\n                  &#250;\n                </div>\n                <div>\n                  <p>&amp;#251;</p>\n                  &#251;\n                </div>\n                <div>\n                  <p>&amp;#252;</p>\n                  &#252;\n                </div>\n                <div>\n                  <p>&amp;#253;</p>\n                  &#253;\n                </div>\n                <div>\n                  <p>&amp;#254;</p>\n                  &#254;\n                </div>\n                <div>\n                  <p>&amp;#255;</p>\n                  &#255;\n                </div>\n                <div>\n                  <p>&amp;#338;</p>\n                  &#338;\n                </div>\n                <div>\n                  <p>&amp;#339;</p>\n                  &#339;\n                </div>\n                <div>\n                  <p>&amp;#376;</p>\n                  &#376;\n                </div>\n                <div>\n                  <p>&amp;#710;</p>\n                  &#710;\n                </div>\n                <div>\n                  <p>&amp;#732;</p>\n                  &#732;\n                </div>\n                <div>\n                  <p>&amp;#8192;</p>\n                  &#8192;\n                </div>\n                <div>\n                  <p>&amp;#8193;</p>\n                  &#8193;\n                </div>\n                <div>\n                  <p>&amp;#8194;</p>\n                  &#8194;\n                </div>\n                <div>\n                  <p>&amp;#8195;</p>\n                  &#8195;\n                </div>\n                <div>\n                  <p>&amp;#8196;</p>\n                  &#8196;\n                </div>\n                <div>\n                  <p>&amp;#8197;</p>\n                  &#8197;\n                </div>\n                <div>\n                  <p>&amp;#8198;</p>\n                  &#8198;\n                </div>\n                <div>\n                  <p>&amp;#8199;</p>\n                  &#8199;\n                </div>\n                <div>\n                  <p>&amp;#8200;</p>\n                  &#8200;\n                </div>\n                <div>\n                  <p>&amp;#8201;</p>\n                  &#8201;\n                </div>\n                <div>\n                  <p>&amp;#8202;</p>\n                  &#8202;\n                </div>\n                <div>\n                  <p>&amp;#8208;</p>\n                  &#8208;\n                </div>\n                <div>\n                  <p>&amp;#8209;</p>\n                  &#8209;\n                </div>\n                <div>\n                  <p>&amp;#8210;</p>\n                  &#8210;\n                </div>\n                <div>\n                  <p>&amp;#8211;</p>\n                  &#8211;\n                </div>\n                <div>\n                  <p>&amp;#8212;</p>\n                  &#8212;\n                </div>\n                <div>\n                  <p>&amp;#8216;</p>\n                  &#8216;\n                </div>\n                <div>\n                  <p>&amp;#8217;</p>\n                  &#8217;\n                </div>\n                <div>\n                  <p>&amp;#8218;</p>\n                  &#8218;\n                </div>\n                <div>\n                  <p>&amp;#8220;</p>\n                  &#8220;\n                </div>\n                <div>\n                  <p>&amp;#8221;</p>\n                  &#8221;\n                </div>\n                <div>\n                  <p>&amp;#8222;</p>\n                  &#8222;\n                </div>\n                <div>\n                  <p>&amp;#8226;</p>\n                  &#8226;\n                </div>\n                <div>\n                  <p>&amp;#8230;</p>\n                  &#8230;\n                </div>\n                <div>\n                  <p>&amp;#8239;</p>\n                  &#8239;\n                </div>\n                <div>\n                  <p>&amp;#8249;</p>\n                  &#8249;\n                </div>\n                <div>\n                  <p>&amp;#8250;</p>\n                  &#8250;\n                </div>\n                <div>\n                  <p>&amp;#8287;</p>\n                  &#8287;\n                </div>\n                <div>\n                  <p>&amp;#8364;</p>\n                  &#8364;\n                </div>\n                <div>\n                  <p>&amp;#8482;</p>\n                  &#8482;\n                </div>\n                <div>\n                  <p>&amp;#9724;</p>\n                  &#9724;\n                </div>\n              </div>\n            </div>\n          </div>\n        </div>\n\n        <div id=\"specs\"></div>\n\n        <div id=\"installing\">\n          <div class=\"section\">\n            <div class=\"grid7 firstcol\">\n              <h1>Installing Webfonts</h1>\n\n              <p>\n                Webfonts are supported by all major browser platforms but not all in the same way.\n                There are currently four different font formats that must be included in order to\n                target all browsers. This includes TTF, WOFF, EOT and SVG.\n              </p>\n\n              <h2>1. Upload your webfonts</h2>\n              <p>\n                You must upload your webfont kit to your website. They should be in or near the same\n                directory as your CSS files.\n              </p>\n\n              <h2>2. Include the webfont stylesheet</h2>\n              <p>\n                A special CSS @font-face declaration helps the various browsers select the\n                appropriate font it needs without causing you a bunch of headaches. Learn more about\n                this syntax by reading the\n                <a\n                  href=\"https://www.fontspring.com/blog/further-hardening-of-the-bulletproof-syntax\"\n                  >Fontspring blog post</a\n                >\n                about it. The code for it is as follows:\n              </p>\n\n              <code>\n                @font-face{ font-family: 'MyWebFont'; src: url('WebFont.eot'); src:\n                url('WebFont.eot?#iefix') format('embedded-opentype'), url('WebFont.woff')\n                format('woff'), url('WebFont.ttf') format('truetype'), url('WebFont.svg#webfont')\n                format('svg'); }\n              </code>\n\n              <p>\n                We've already gone ahead and generated the code for you. All you have to do is link\n                to the stylesheet in your HTML, like this:\n              </p>\n              <code\n                >&lt;link rel=&quot;stylesheet&quot; href=&quot;stylesheet.css&quot;\n                type=&quot;text/css&quot; charset=&quot;utf-8&quot; /&gt;</code\n              >\n\n              <h2>3. Modify your own stylesheet</h2>\n              <p>\n                To take advantage of your new fonts, you must tell your stylesheet to use them. Look\n                at the original @font-face declaration above and find the property called\n                \"font-family.\" The name linked there will be what you use to reference the font.\n                Prepend that webfont name to the font stack in the \"font-family\" property, inside\n                the selector you want to change. For example:\n              </p>\n              <code>p { font-family: 'WebFont', Arial, sans-serif; }</code>\n\n              <h2>4. Test</h2>\n              <p>\n                Getting webfonts to work cross-browser <em>can</em> be tricky. Use the information\n                in the sidebar to help you if you find that fonts aren't loading in a particular\n                browser.\n              </p>\n            </div>\n\n            <div class=\"grid5 sidebar\">\n              <div class=\"box\">\n                <h2>Troubleshooting<br />Font-Face Problems</h2>\n                <p>\n                  Having trouble getting your webfonts to load in your new website? Here are some\n                  tips to sort out what might be the problem.\n                </p>\n\n                <h3>Fonts not showing in any browser</h3>\n\n                <p>\n                  This sounds like you need to work on the plumbing. You either did not upload the\n                  fonts to the correct directory, or you did not link the fonts properly in the CSS.\n                  If you've confirmed that all this is correct and you still have a problem, take a\n                  look at your .htaccess file and see if requests are getting intercepted.\n                </p>\n\n                <h3>Fonts not loading in iPhone or iPad</h3>\n\n                <p>\n                  The most common problem here is that you are serving the fonts from an IIS server.\n                  IIS refuses to serve files that have unknown MIME types. If that is the case, you\n                  must set the MIME type for SVG to \"image/svg+xml\" in the server settings. Follow\n                  these instructions from Microsoft if you need help.\n                </p>\n\n                <h3>Fonts not loading in Firefox</h3>\n\n                <p>\n                  The primary reason for this failure? You are still using a version Firefox older\n                  than 3.5. So upgrade already! If that isn't it, then you are very likely serving\n                  fonts from a different domain. Firefox requires that all font assets be served\n                  from the same domain. Lastly it is possible that you need to add WOFF to your list\n                  of MIME types (if you are serving via IIS.)\n                </p>\n\n                <h3>Fonts not loading in IE</h3>\n\n                <p>\n                  Are you looking at Internet Explorer on an actual Windows machine or are you\n                  cheating by using a service like Adobe BrowserLab? Many of these screenshot\n                  services do not render @font-face for IE. Best to test it on a real machine.\n                </p>\n\n                <h3>Fonts not loading in IE9</h3>\n\n                <p>\n                  IE9, like Firefox, requires that fonts be served from the same domain as the\n                  website. Make sure that is the case.\n                </p>\n              </div>\n            </div>\n          </div>\n        </div>\n      </div>\n      <div id=\"footer\">\n        <p>&copy;2010-2017 Font Squirrel. All rights reserved.</p>\n      </div>\n    </div>\n  </body>\n</html>\n"
  },
  {
    "path": "src/Frontend/Styles/font/specimen_files/grid_12-825-55-15.css",
    "content": "/*Notes about grid:\nColumns:      12\nGrid Width:   825px\nColumn Width: 55px\nGutter Width: 15px\n-------------------------------*/\n\n.section {\n  margin-bottom: 18px;\n}\n.section:after {\n  content: '.';\n  display: block;\n  height: 0;\n  clear: both;\n  visibility: hidden;\n}\n.section {\n  *zoom: 1;\n}\n\n.section .firstcolumn,\n.section .firstcol {\n  margin-left: 0;\n}\n\n/* Border on left hand side of a column. */\n.border {\n  padding-left: 7px;\n  margin-left: 7px;\n  border-left: 1px solid #eee;\n}\n\n/* Border with more whitespace, spans one column. */\n.colborder {\n  padding-left: 42px;\n  margin-left: 42px;\n  border-left: 1px solid #eee;\n}\n\n/* The Grid Classes */\n.grid1,\n.grid1_2cols,\n.grid1_3cols,\n.grid1_4cols,\n.grid2,\n.grid2_3cols,\n.grid2_4cols,\n.grid3,\n.grid3_2cols,\n.grid3_4cols,\n.grid4,\n.grid4_3cols,\n.grid5,\n.grid5_2cols,\n.grid5_3cols,\n.grid5_4cols,\n.grid6,\n.grid6_4cols,\n.grid7,\n.grid7_2cols,\n.grid7_3cols,\n.grid7_4cols,\n.grid8,\n.grid8_3cols,\n.grid9,\n.grid9_2cols,\n.grid9_4cols,\n.grid10,\n.grid10_3cols,\n.grid10_4cols,\n.grid11,\n.grid11_2cols,\n.grid11_3cols,\n.grid11_4cols,\n.grid12 {\n  margin-left: 15px;\n  float: left;\n  display: inline;\n  overflow: hidden;\n}\n\n.width1,\n.grid1,\n.span-1 {\n  width: 55px;\n}\n.width1_2cols,\n.grid1_2cols {\n  width: 20px;\n}\n.width1_3cols,\n.grid1_3cols {\n  width: 8px;\n}\n.width1_4cols,\n.grid1_4cols {\n  width: 2px;\n}\n.input_width1 {\n  width: 49px;\n}\n\n.width2,\n.grid2,\n.span-2 {\n  width: 125px;\n}\n.width2_3cols,\n.grid2_3cols {\n  width: 31px;\n}\n.width2_4cols,\n.grid2_4cols {\n  width: 20px;\n}\n.input_width2 {\n  width: 119px;\n}\n\n.width3,\n.grid3,\n.span-3 {\n  width: 195px;\n}\n.width3_2cols,\n.grid3_2cols {\n  width: 90px;\n}\n.width3_4cols,\n.grid3_4cols {\n  width: 37px;\n}\n.input_width3 {\n  width: 189px;\n}\n\n.width4,\n.grid4,\n.span-4 {\n  width: 265px;\n}\n.width4_3cols,\n.grid4_3cols {\n  width: 78px;\n}\n.input_width4 {\n  width: 259px;\n}\n\n.width5,\n.grid5,\n.span-5 {\n  width: 335px;\n}\n.width5_2cols,\n.grid5_2cols {\n  width: 160px;\n}\n.width5_3cols,\n.grid5_3cols {\n  width: 101px;\n}\n.width5_4cols,\n.grid5_4cols {\n  width: 72px;\n}\n.input_width5 {\n  width: 329px;\n}\n\n.width6,\n.grid6,\n.span-6 {\n  width: 405px;\n}\n.width6_4cols,\n.grid6_4cols {\n  width: 90px;\n}\n.input_width6 {\n  width: 399px;\n}\n\n.width7,\n.grid7,\n.span-7 {\n  width: 475px;\n}\n.width7_2cols,\n.grid7_2cols {\n  width: 230px;\n}\n.width7_3cols,\n.grid7_3cols {\n  width: 148px;\n}\n.width7_4cols,\n.grid7_4cols {\n  width: 107px;\n}\n.input_width7 {\n  width: 469px;\n}\n\n.width8,\n.grid8,\n.span-8 {\n  width: 545px;\n}\n.width8_3cols,\n.grid8_3cols {\n  width: 171px;\n}\n.input_width8 {\n  width: 539px;\n}\n\n.width9,\n.grid9,\n.span-9 {\n  width: 615px;\n}\n.width9_2cols,\n.grid9_2cols {\n  width: 300px;\n}\n.width9_4cols,\n.grid9_4cols {\n  width: 142px;\n}\n.input_width9 {\n  width: 609px;\n}\n\n.width10,\n.grid10,\n.span-10 {\n  width: 685px;\n}\n.width10_3cols,\n.grid10_3cols {\n  width: 218px;\n}\n.width10_4cols,\n.grid10_4cols {\n  width: 160px;\n}\n.input_width10 {\n  width: 679px;\n}\n\n.width11,\n.grid11,\n.span-11 {\n  width: 755px;\n}\n.width11_2cols,\n.grid11_2cols {\n  width: 370px;\n}\n.width11_3cols,\n.grid11_3cols {\n  width: 241px;\n}\n.width11_4cols,\n.grid11_4cols {\n  width: 177px;\n}\n.input_width11 {\n  width: 749px;\n}\n\n.width12,\n.grid12,\n.span-12 {\n  width: 825px;\n}\n.input_width12 {\n  width: 819px;\n}\n\n/* Subdivided grid spaces */\n.emptycols_left1,\n.prepend-1 {\n  padding-left: 70px;\n}\n.emptycols_right1,\n.append-1 {\n  padding-right: 70px;\n}\n.emptycols_left2,\n.prepend-2 {\n  padding-left: 140px;\n}\n.emptycols_right2,\n.append-2 {\n  padding-right: 140px;\n}\n.emptycols_left3,\n.prepend-3 {\n  padding-left: 210px;\n}\n.emptycols_right3,\n.append-3 {\n  padding-right: 210px;\n}\n.emptycols_left4,\n.prepend-4 {\n  padding-left: 280px;\n}\n.emptycols_right4,\n.append-4 {\n  padding-right: 280px;\n}\n.emptycols_left5,\n.prepend-5 {\n  padding-left: 350px;\n}\n.emptycols_right5,\n.append-5 {\n  padding-right: 350px;\n}\n.emptycols_left6,\n.prepend-6 {\n  padding-left: 420px;\n}\n.emptycols_right6,\n.append-6 {\n  padding-right: 420px;\n}\n.emptycols_left7,\n.prepend-7 {\n  padding-left: 490px;\n}\n.emptycols_right7,\n.append-7 {\n  padding-right: 490px;\n}\n.emptycols_left8,\n.prepend-8 {\n  padding-left: 560px;\n}\n.emptycols_right8,\n.append-8 {\n  padding-right: 560px;\n}\n.emptycols_left9,\n.prepend-9 {\n  padding-left: 630px;\n}\n.emptycols_right9,\n.append-9 {\n  padding-right: 630px;\n}\n.emptycols_left10,\n.prepend-10 {\n  padding-left: 700px;\n}\n.emptycols_right10,\n.append-10 {\n  padding-right: 700px;\n}\n.emptycols_left11,\n.prepend-11 {\n  padding-left: 770px;\n}\n.emptycols_right11,\n.append-11 {\n  padding-right: 770px;\n}\n.pull-1 {\n  margin-left: -70px;\n}\n.push-1 {\n  margin-right: -70px;\n  margin-left: 18px;\n  float: right;\n}\n.pull-2 {\n  margin-left: -140px;\n}\n.push-2 {\n  margin-right: -140px;\n  margin-left: 18px;\n  float: right;\n}\n.pull-3 {\n  margin-left: -210px;\n}\n.push-3 {\n  margin-right: -210px;\n  margin-left: 18px;\n  float: right;\n}\n.pull-4 {\n  margin-left: -280px;\n}\n.push-4 {\n  margin-right: -280px;\n  margin-left: 18px;\n  float: right;\n}\n"
  },
  {
    "path": "src/Frontend/Styles/font/specimen_files/specimen_stylesheet.css",
    "content": "@import url('grid_12-825-55-15.css');\n\n/*  \n\tCSS Reset by Eric Meyer - Released under Public Domain\n    http://meyerweb.com/eric/tools/css/reset/\n*/\nhtml,\nbody,\ndiv,\nspan,\napplet,\nobject,\niframe,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\np,\nblockquote,\npre,\na,\nabbr,\nacronym,\naddress,\nbig,\ncite,\ncode,\ndel,\ndfn,\nem,\nfont,\nimg,\nins,\nkbd,\nq,\ns,\nsamp,\nsmall,\nstrike,\nstrong,\nsub,\nsup,\ntt,\nvar,\nb,\nu,\ni,\ncenter,\ndl,\ndt,\ndd,\nol,\nul,\nli,\nfieldset,\nform,\nlabel,\nlegend,\ntable,\ncaption,\ntbody,\ntfoot,\nthead,\ntr,\nth,\ntd {\n  margin: 0;\n  padding: 0;\n  border: 0;\n  outline: 0;\n  font-size: 100%;\n  vertical-align: baseline;\n  background: transparent;\n}\nbody {\n  line-height: 1;\n}\nol,\nul {\n  list-style: none;\n}\nblockquote,\nq {\n  quotes: none;\n}\nblockquote:before,\nblockquote:after,\nq:before,\nq:after {\n  content: '';\n  content: none;\n}\n:focus {\n  outline: 0;\n}\nins {\n  text-decoration: none;\n}\ndel {\n  text-decoration: line-through;\n}\ntable {\n  border-collapse: collapse;\n  border-spacing: 0;\n}\n\nbody {\n  color: #000;\n  background-color: #dcdcdc;\n}\n\na {\n  text-decoration: none;\n  color: #1883ba;\n}\n\nh1 {\n  font-size: 32px;\n  font-weight: normal;\n  font-style: normal;\n  margin-bottom: 18px;\n}\n\nh2 {\n  font-size: 18px;\n}\n\n#container {\n  width: 865px;\n  margin: 0px auto;\n}\n\n#header {\n  padding: 20px;\n  font-size: 36px;\n  background-color: #000;\n  color: #fff;\n}\n\n#header span {\n  color: #666;\n}\n#main_content {\n  background-color: #fff;\n  padding: 60px 20px 20px;\n}\n\n#footer p {\n  margin: 0;\n  padding-top: 10px;\n  padding-bottom: 50px;\n  color: #333;\n  font: 10px Arial, sans-serif;\n}\n\n.tabs {\n  width: 100%;\n  height: 31px;\n  background-color: #444;\n}\n.tabs li {\n  float: left;\n  margin: 0;\n  overflow: hidden;\n  background-color: #444;\n}\n.tabs li a {\n  display: block;\n  color: #fff;\n  text-decoration: none;\n  font: bold 11px/11px 'Arial';\n  text-transform: uppercase;\n  padding: 10px 15px;\n  border-right: 1px solid #fff;\n}\n\n.tabs li a:hover {\n  background-color: #00b3ff;\n}\n\n.tabs li.active a {\n  color: #000;\n  background-color: #fff;\n}\n\ndiv.huge {\n  font-size: 300px;\n  line-height: 1em;\n  padding: 0;\n  letter-spacing: -0.02em;\n  overflow: hidden;\n}\ndiv.glyph_range {\n  font-size: 72px;\n  line-height: 1.1em;\n}\n\n.size10 {\n  font-size: 10px;\n}\n.size11 {\n  font-size: 11px;\n}\n.size12 {\n  font-size: 12px;\n}\n.size13 {\n  font-size: 13px;\n}\n.size14 {\n  font-size: 14px;\n}\n.size16 {\n  font-size: 16px;\n}\n.size18 {\n  font-size: 18px;\n}\n.size20 {\n  font-size: 20px;\n}\n.size24 {\n  font-size: 24px;\n}\n.size30 {\n  font-size: 30px;\n}\n.size36 {\n  font-size: 36px;\n}\n.size48 {\n  font-size: 48px;\n}\n.size60 {\n  font-size: 60px;\n}\n.size72 {\n  font-size: 72px;\n}\n.size90 {\n  font-size: 90px;\n}\n\n.psample_row1 {\n  height: 120px;\n}\n.psample_row1 {\n  height: 120px;\n}\n.psample_row2 {\n  height: 160px;\n}\n.psample_row3 {\n  height: 160px;\n}\n.psample_row4 {\n  height: 160px;\n}\n\n.psample {\n  overflow: hidden;\n  position: relative;\n}\n.psample p {\n  line-height: 1.3em;\n  display: block;\n  overflow: hidden;\n  margin: 0;\n}\n\n.psample span {\n  margin-right: 0.5em;\n}\n\n.white_blend {\n  width: 100%;\n  height: 61px;\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAO1JREFUeNrs3TsKgFAMRUE/eer+NxztxMYuEWQG3ECKwwUF58ycAKixOAGAyAKILAAiCyCyACILgMgCiCyAyAIgsgAiCyCyAIgsgMgCiCwAIgsgsgAiC4DIAogsACIL0CWuZ3UGgLrIhjMA1EV2OAOAJQtgyQLwjOzmDAAiCyCyAIgsQFtkd2cAEFkAkQVAZAHaIns4A4AlC2DJAiCyACILILIAiCzAV5H1dQGAJQsgsgCILIDIAvwisl58AViyAJYsACILILIAIgvAe2T9EhxAZAFEFgCRBeiL7HAGgLrIhjMAWLIAliwAt1OAAQDwygTBulLIlQAAAABJRU5ErkJggg==);\n  position: absolute;\n  bottom: 0;\n}\n.black_blend {\n  width: 100%;\n  height: 61px;\n  background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAVkAAAA9CAYAAAAH4BojAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAPJJREFUeNrs3TEKhTAQRVGjibr/9QoxhY2N3Ywo50A28IrLwP9g6b1PAMSYTQAgsgAiC4DIAogsgMgCILIAIgsgsgCILIDIAogsACILILIAIguAyAKILIDIAiCyACILgMgCZCnjLWYAiFGvB0BQZJsZAFyyAC5ZAO6RXc0AILIAIguAyAKkRXYzA4DIAogsACILkBbZ3QwALlkAlywAIgsgsgAiC4DIArwVWf8uAHDJAogsACILILIAv4isH74AXLIALlkARBZAZAFEFoDnyPokOIDIAogsACILkBfZZgaAuMhWMwC4ZAE+p4x3mAEgxinAAJ+XBbPWGkwAAAAAAElFTkSuQmCC);\n  position: absolute;\n  bottom: 0;\n}\n.fullreverse {\n  background: #000 !important;\n  color: #fff !important;\n  margin-left: -20px;\n  padding-left: 20px;\n  margin-right: -20px;\n  padding-right: 20px;\n  padding: 20px;\n  margin-bottom: 0;\n}\n\n.sample_table td {\n  padding-top: 3px;\n  padding-bottom: 5px;\n  padding-left: 5px;\n  vertical-align: middle;\n  line-height: 1.2em;\n}\n\n.sample_table td:first-child {\n  background-color: #eee;\n  text-align: right;\n  padding-right: 5px;\n  padding-left: 0;\n  padding: 5px;\n  font: 11px/12px 'Courier New', Courier, mono;\n}\n\ncode {\n  white-space: pre;\n  background-color: #eee;\n  display: block;\n  padding: 10px;\n  margin-bottom: 18px;\n  overflow: auto;\n}\n\n.bottom,\n.last {\n  margin-bottom: 0 !important;\n  padding-bottom: 0 !important;\n}\n\n.box {\n  padding: 18px;\n  margin-bottom: 18px;\n  background: #eee;\n}\n\n.reverse,\n.reversed {\n  background: #000 !important;\n  color: #fff !important;\n  border: none !important;\n}\n\n#bodycomparison {\n  position: relative;\n  overflow: hidden;\n  font-size: 72px;\n  height: 90px;\n  white-space: nowrap;\n}\n\n#bodycomparison div {\n  font-size: 72px;\n  line-height: 90px;\n  display: inline;\n  margin: 0 15px 0 0;\n  padding: 0;\n}\n\n#bodycomparison div span {\n  font: 10px Arial;\n  position: absolute;\n  left: 0;\n}\n#xheight {\n  float: none;\n  position: absolute;\n  color: #d9f3ff;\n  font-size: 72px;\n  line-height: 90px;\n}\n\n.fontbody {\n  position: relative;\n}\n.arialbody {\n  font-family: Arial;\n  position: relative;\n}\n.verdanabody {\n  font-family: Verdana;\n  position: relative;\n}\n.georgiabody {\n  font-family: Georgia;\n  position: relative;\n}\n\n/* @group Layout page\n */\n\n#layout h1 {\n  font-size: 36px;\n  line-height: 42px;\n  font-weight: normal;\n  font-style: normal;\n}\n\n#layout h2 {\n  font-size: 24px;\n  line-height: 23px;\n  font-weight: normal;\n  font-style: normal;\n}\n\n#layout h3 {\n  font-size: 22px;\n  line-height: 1.4em;\n  margin-top: 1em;\n  font-weight: normal;\n  font-style: normal;\n}\n\n#layout p.byline {\n  font-size: 12px;\n  margin-top: 18px;\n  line-height: 12px;\n  margin-bottom: 0;\n}\n#layout p {\n  font-size: 14px;\n  line-height: 21px;\n  margin-bottom: 0.5em;\n}\n\n#layout p.large {\n  font-size: 18px;\n  line-height: 26px;\n}\n\n#layout .sidebar p {\n  font-size: 12px;\n  line-height: 1.4em;\n}\n\n#layout p.caption {\n  font-size: 10px;\n  margin-top: -16px;\n  margin-bottom: 18px;\n}\n\n/* @end */\n\n/* @group Glyphs */\n\n#glyph_chart div {\n  background-color: #d9f3ff;\n  color: black;\n  float: left;\n  font-size: 36px;\n  height: 1.2em;\n  line-height: 1.2em;\n  margin-bottom: 1px;\n  margin-right: 1px;\n  text-align: center;\n  width: 1.2em;\n  position: relative;\n  padding: 0.6em 0.2em 0.2em;\n}\n\n#glyph_chart div p {\n  position: absolute;\n  left: 0;\n  top: 0;\n  display: block;\n  text-align: center;\n  font: bold 9px Arial, sans-serif;\n  background-color: #3a768f;\n  width: 100%;\n  color: #fff;\n  padding: 2px 0;\n}\n\n#glyphs h1 {\n  font-family: Arial, sans-serif;\n}\n/* @end */\n\n/* @group Installing */\n\n#installing {\n  font: 13px Arial, sans-serif;\n}\n\n#installing p,\n#glyphs p {\n  line-height: 1.2em;\n  margin-bottom: 18px;\n  font: 13px Arial, sans-serif;\n}\n\n#installing h3 {\n  font-size: 15px;\n  margin-top: 18px;\n}\n\n/* @end */\n\n#rendering h1 {\n  font-family: Arial, sans-serif;\n}\n.render_table td {\n  font: 11px 'Courier New', Courier, mono;\n  vertical-align: middle;\n}\n"
  },
  {
    "path": "src/Frontend/Styles/font/stylesheet.css",
    "content": "/*! Generated by Font Squirrel (https://www.fontsquirrel.com) on July 30, 2020 */\n\n@font-face {\n  font-family: 'perfect_dos_vga_437regular';\n  src: url('perfect_dos_vga_437-webfont.woff2') format('woff2'),\n    url('perfect_dos_vga_437-webfont.woff') format('woff');\n  font-weight: normal;\n  font-style: normal;\n}\n"
  },
  {
    "path": "src/Frontend/Styles/icomoon/style.css",
    "content": "@font-face {\n  font-family: 'icomoon';\n  src: url('fonts/icomoon.eot');\n  src: url('fonts/icomoon.eot#iefix') format('embedded-opentype'),\n    url('fonts/icomoon.ttf') format('truetype'), url('fonts/icomoon.woff') format('woff'),\n    url('fonts/icomoon.svg#icomoon') format('svg');\n  font-weight: normal;\n  font-style: normal;\n  font-display: block;\n}\n\n[class^='icon-'],\n[class*=' icon-'] {\n  /* use !important to prevent issues with browser extensions that change fonts */\n  font-family: 'icomoon' !important;\n  speak: never;\n  font-style: normal;\n  font-weight: normal;\n  font-variant: normal;\n  text-transform: none;\n  line-height: 1;\n\n  /* Better Font Rendering =========== */\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-github:before {\n  content: '\\e903';\n}\n.icon-discord:before {\n  content: '\\e900';\n}\n.icon-twitter:before {\n  content: '\\e901';\n}\n.icon-brand:before {\n  content: '\\e901';\n}\n.icon-tweet:before {\n  content: '\\e901';\n}\n.icon-social:before {\n  content: '\\e901';\n}\n.icon-mail:before {\n  content: '\\e902';\n}\n.icon-contact:before {\n  content: '\\e902';\n}\n.icon-support:before {\n  content: '\\e902';\n}\n.icon-newsletter:before {\n  content: '\\e902';\n}\n.icon-letter:before {\n  content: '\\e902';\n}\n.icon-email:before {\n  content: '\\e902';\n}\n.icon-envelop:before {\n  content: '\\e902';\n}\n.icon-social1:before {\n  content: '\\e902';\n}\n"
  },
  {
    "path": "src/Frontend/Styles/preflight.css",
    "content": "/* Manually copied from https://unpkg.com/tailwindcss@1.2.0/dist/base.css. This\nhelps us normalize CSS properties in an expected way that's friendly with web\napp development. */\n\n/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */\n\n/* Document\n   ========================================================================== */\n\n/**\n * 1. Correct the line height in all browsers.\n * 2. Prevent adjustments of font size after orientation changes in iOS.\n */\n\nhtml {\n  line-height: 1.15; /* 1 */\n  -webkit-text-size-adjust: 100%; /* 2 */\n}\n\n/* Sections\n     ========================================================================== */\n\n/**\n   * Remove the margin in all browsers.\n   */\n\nbody {\n  margin: 0;\n}\n\n/**\n   * Render the `main` element consistently in IE.\n   */\n\nmain {\n  display: block;\n}\n\n/**\n   * Correct the font size and margin on `h1` elements within `section` and\n   * `article` contexts in Chrome, Firefox, and Safari.\n   */\n\nh1 {\n  font-size: 2em;\n  margin: 0.67em 0;\n}\n\n/* Grouping content\n     ========================================================================== */\n\n/**\n   * 1. Add the correct box sizing in Firefox.\n   * 2. Show the overflow in Edge and IE.\n   */\n\nhr {\n  box-sizing: content-box; /* 1 */\n  height: 0; /* 1 */\n  overflow: visible; /* 2 */\n}\n\n/**\n   * 1. Correct the inheritance and scaling of font size in all browsers.\n   * 2. Correct the odd `em` font sizing in all browsers.\n   */\n\npre {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/* Text-level semantics\n     ========================================================================== */\n\n/**\n   * Remove the gray background on active links in IE 10.\n   */\n\na {\n  background-color: transparent;\n}\n\n/**\n   * 1. Remove the bottom border in Chrome 57-\n   * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.\n   */\n\nabbr[title] {\n  border-bottom: none; /* 1 */\n  text-decoration: underline; /* 2 */\n  -webkit-text-decoration: underline dotted;\n  text-decoration: underline dotted; /* 2 */\n}\n\n/**\n   * Add the correct font weight in Chrome, Edge, and Safari.\n   */\n\nb,\nstrong {\n  font-weight: bolder;\n}\n\n/**\n   * 1. Correct the inheritance and scaling of font size in all browsers.\n   * 2. Correct the odd `em` font sizing in all browsers.\n   */\n\ncode,\nkbd,\nsamp {\n  font-family: monospace, monospace; /* 1 */\n  font-size: 1em; /* 2 */\n}\n\n/**\n   * Add the correct font size in all browsers.\n   */\n\nsmall {\n  font-size: 80%;\n}\n\n/**\n   * Prevent `sub` and `sup` elements from affecting the line height in\n   * all browsers.\n   */\n\nsub,\nsup {\n  font-size: 75%;\n  line-height: 0;\n  position: relative;\n  vertical-align: baseline;\n}\n\nsub {\n  bottom: -0.25em;\n}\n\nsup {\n  top: -0.5em;\n}\n\n/* Embedded content\n     ========================================================================== */\n\n/**\n   * Remove the border on images inside links in IE 10.\n   */\n\nimg {\n  border-style: none;\n}\n\n/* Forms\n     ========================================================================== */\n\n/**\n   * 1. Change the font styles in all browsers.\n   * 2. Remove the margin in Firefox and Safari.\n   */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  font-family: inherit; /* 1 */\n  font-size: 100%; /* 1 */\n  line-height: 1.15; /* 1 */\n  margin: 0; /* 2 */\n}\n\n/**\n   * Show the overflow in IE.\n   * 1. Show the overflow in Edge.\n   */\n\nbutton,\ninput {\n  /* 1 */\n  overflow: visible;\n}\n\n/**\n   * Remove the inheritance of text transform in Edge, Firefox, and IE.\n   * 1. Remove the inheritance of text transform in Firefox.\n   */\n\nbutton,\nselect {\n  /* 1 */\n  text-transform: none;\n}\n\n/**\n   * Correct the inability to style clickable types in iOS and Safari.\n   */\n\nbutton,\n[type='button'],\n[type='reset'],\n[type='submit'] {\n  -webkit-appearance: button;\n}\n\n/**\n   * Remove the inner border and padding in Firefox.\n   */\n\nbutton::-moz-focus-inner,\n[type='button']::-moz-focus-inner,\n[type='reset']::-moz-focus-inner,\n[type='submit']::-moz-focus-inner {\n  border-style: none;\n  padding: 0;\n}\n\n/**\n   * Restore the focus styles unset by the previous rule.\n   */\n\nbutton:-moz-focusring,\n[type='button']:-moz-focusring,\n[type='reset']:-moz-focusring,\n[type='submit']:-moz-focusring {\n  outline: 1px dotted ButtonText;\n}\n\n/**\n   * Correct the padding in Firefox.\n   */\n\nfieldset {\n  padding: 0.35em 0.75em 0.625em;\n}\n\n/**\n   * 1. Correct the text wrapping in Edge and IE.\n   * 2. Correct the color inheritance from `fieldset` elements in IE.\n   * 3. Remove the padding so developers are not caught out when they zero out\n   *    `fieldset` elements in all browsers.\n   */\n\nlegend {\n  box-sizing: border-box; /* 1 */\n  color: inherit; /* 2 */\n  display: table; /* 1 */\n  max-width: 100%; /* 1 */\n  padding: 0; /* 3 */\n  white-space: normal; /* 1 */\n}\n\n/**\n   * Add the correct vertical alignment in Chrome, Firefox, and Opera.\n   */\n\nprogress {\n  vertical-align: baseline;\n}\n\n/**\n   * Remove the default vertical scrollbar in IE 10+.\n   */\n\ntextarea {\n  overflow: auto;\n}\n\n/**\n   * 1. Add the correct box sizing in IE 10.\n   * 2. Remove the padding in IE 10.\n   */\n\n[type='checkbox'],\n[type='radio'] {\n  box-sizing: border-box; /* 1 */\n  padding: 0; /* 2 */\n}\n\n/**\n   * Correct the cursor style of increment and decrement buttons in Chrome.\n   */\n\n[type='number']::-webkit-inner-spin-button,\n[type='number']::-webkit-outer-spin-button {\n  height: auto;\n}\n\n/**\n   * 1. Correct the odd appearance in Chrome and Safari.\n   * 2. Correct the outline style in Safari.\n   */\n\n[type='search'] {\n  -webkit-appearance: textfield; /* 1 */\n  outline-offset: -2px; /* 2 */\n}\n\n/**\n   * Remove the inner padding in Chrome and Safari on macOS.\n   */\n\n[type='search']::-webkit-search-decoration {\n  -webkit-appearance: none;\n}\n\n/**\n   * 1. Correct the inability to style clickable types in iOS and Safari.\n   * 2. Change font properties to `inherit` in Safari.\n   */\n\n::-webkit-file-upload-button {\n  -webkit-appearance: button; /* 1 */\n  font: inherit; /* 2 */\n}\n\n/* Interactive\n     ========================================================================== */\n\n/*\n   * Add the correct display in Edge, IE 10+, and Firefox.\n   */\n\ndetails {\n  display: block;\n}\n\n/*\n   * Add the correct display in all browsers.\n   */\n\nsummary {\n  display: list-item;\n}\n\n/* Misc\n     ========================================================================== */\n\n/**\n   * Add the correct display in IE 10+.\n   */\n\ntemplate {\n  display: none;\n}\n\n/**\n   * Add the correct display in IE 10.\n   */\n\n[hidden] {\n  display: none;\n}\n\n/**\n   * Manually forked from SUIT CSS Base: https://github.com/suitcss/base\n   * A thin layer on top of normalize.css that provides a starting point more\n   * suitable for web applications.\n   */\n\n/**\n   * Removes the default spacing and border for appropriate elements.\n   */\n\nblockquote,\ndl,\ndd,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nhr,\nfigure,\np,\npre {\n  margin: 0;\n}\n\nbutton {\n  background-color: transparent;\n  background-image: none;\n  padding: 0;\n}\n\n/**\n   * Work around a Firefox/IE bug where the transparent `button` background\n   * results in a loss of the default `button` focus styles.\n   */\n\nbutton:focus {\n  outline: 1px dotted;\n  outline: 5px auto -webkit-focus-ring-color;\n}\n\nfieldset {\n  margin: 0;\n  padding: 0;\n}\n\nol,\nul {\n  list-style: none;\n  margin: 0;\n  padding: 0;\n}\n\n/**\n   * Tailwind custom reset styles\n   */\n\n/**\n   * 1. Use the user's configured `sans` font-family (with Tailwind's default\n   *    sans-serif font stack as a fallback) as a sane default.\n   * 2. Use Tailwind's default \"normal\" line-height so the user isn't forced\n   *    to override it to ensure consistency even when using the default theme.\n   */\n\nhtml {\n  font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue',\n    Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',\n    'Noto Color Emoji'; /* 1 */\n  line-height: 1.5; /* 2 */\n}\n\n/**\n   * 1. Prevent padding and border from affecting element width.\n   *\n   *    We used to set this in the html element and inherit from\n   *    the parent element for everything else. This caused issues\n   *    in shadow-dom-enhanced elements like <details> where the content\n   *    is wrapped by a div with box-sizing set to `content-box`.\n   *\n   *    https://github.com/mozdevs/cssremedy/issues/4\n   *\n   *\n   * 2. Allow adding a border to an element by just adding a border-width.\n   *\n   *    By default, the way the browser specifies that an element should have no\n   *    border is by setting it's border-style to `none` in the user-agent\n   *    stylesheet.\n   *\n   *    In order to easily add borders to elements by just setting the `border-width`\n   *    property, we change the default border-style for all elements to `solid`, and\n   *    use border-width to hide them instead. This way our `border` utilities only\n   *    need to set the `border-width` property instead of the entire `border`\n   *    shorthand, making our border utilities much more straightforward to compose.\n   *\n   *    https://github.com/tailwindcss/tailwindcss/pull/116\n   */\n\n*,\n::before,\n::after {\n  box-sizing: border-box; /* 1 */\n  border-width: 0; /* 2 */\n  border-style: solid; /* 2 */\n  border-color: #e2e8f0; /* 2 */\n}\n\n/*\n   * Ensure horizontal rules are visible by default\n   */\n\nhr {\n  border-top-width: 1px;\n}\n\n/**\n   * Undo the `border-style: none` reset that Normalize applies to images so that\n   * our `border-{width}` utilities have the expected effect.\n   *\n   * The Normalize reset is unnecessary for us since we default the border-width\n   * to 0 on all elements.\n   *\n   * https://github.com/tailwindcss/tailwindcss/issues/362\n   */\n\nimg {\n  border-style: solid;\n}\n\ntextarea {\n  resize: vertical;\n}\n\ninput:-ms-input-placeholder,\ntextarea:-ms-input-placeholder {\n  color: #a0aec0;\n}\n\ninput::-ms-input-placeholder,\ntextarea::-ms-input-placeholder {\n  color: #a0aec0;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n  color: #a0aec0;\n}\n\nbutton,\n[role='button'] {\n  cursor: pointer;\n}\n\ntable {\n  border-collapse: collapse;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n  font-size: inherit;\n  font-weight: inherit;\n}\n\n/**\n   * Reset links to optimize for opt-in styling instead of\n   * opt-out.\n   */\n\na {\n  color: inherit;\n  text-decoration: inherit;\n}\n\n/**\n   * Reset form element properties that are easy to forget to\n   * style explicitly so you don't inadvertently introduce\n   * styles that deviate from your design system. These styles\n   * supplement a partial reset that is already applied by\n   * normalize.css.\n   */\n\nbutton,\ninput,\noptgroup,\nselect,\ntextarea {\n  padding: 0;\n  line-height: inherit;\n  color: inherit;\n}\n\n/**\n   * Use the configured 'mono' font family for elements that\n   * are expected to be rendered with a monospace font, falling\n   * back to the system monospace stack if there is no configured\n   * 'mono' font family.\n   */\n\npre,\ncode,\nkbd,\nsamp {\n  font-family: Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;\n}\n\n/**\n   * Make replaced elements `display: block` by default as that's\n   * the behavior you want almost all of the time. Inspired by\n   * CSS Remedy, with `svg` added as well.\n   *\n   * https://github.com/mozdevs/cssremedy/issues/14\n   */\n\nimg,\nsvg,\nvideo,\ncanvas,\naudio,\niframe,\nembed,\nobject {\n  display: block;\n  vertical-align: middle;\n}\n\n/**\n   * Constrain images and videos to the parent width and preserve\n   * their instrinsic aspect ratio.\n   *\n   * https://github.com/mozdevs/cssremedy/issues/14\n   */\n\nimg,\nvideo {\n  max-width: 100%;\n  height: auto;\n}\n\n/*# sourceMappingURL=base.css.map */\n"
  },
  {
    "path": "src/Frontend/Styles/style.css",
    "content": "@import url('https://fonts.googleapis.com/css2?family=Inconsolata:wght@300&display=swap');\n\nhtml,\nbody,\n#root {\n  height: 100%;\n}\n::-webkit-scrollbar {\n  width: 0px;\n  background: transparent;\n}\nbody {\n  font-family: 'Inconsolata', monospace;\n  font-weight: 300;\n}\n\n/* Hide scrollbar for Chrome, Safari and Opera */\n*::-webkit-scrollbar {\n  display: none;\n}\n\n/* Hide scrollbar for IE, Edge and Firefox */\n* {\n  -ms-overflow-style: none; /* IE and Edge */\n  scrollbar-width: none; /* Firefox */\n}\n"
  },
  {
    "path": "src/Frontend/Utils/AppHooks.ts",
    "content": "import { getActivatedArtifact, isActivated } from '@darkforest_eth/gamelogic';\nimport {\n  Artifact,\n  ArtifactId,\n  EthAddress,\n  Leaderboard,\n  LocationId,\n  Planet,\n  Player,\n  Transaction,\n  TransactionId,\n} from '@darkforest_eth/types';\nimport { useCallback, useEffect, useMemo, useState } from 'react';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { loadLeaderboard } from '../../Backend/Network/LeaderboardApi';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { ContractsAPIEvent } from '../../_types/darkforest/api/ContractsAPITypes';\nimport { ModalHandle } from '../Views/ModalPane';\nimport { createDefinedContext } from './createDefinedContext';\nimport { useEmitterSubscribe, useEmitterValue, useWrappedEmitter } from './EmitterHooks';\nimport { usePoll } from './Hooks';\n\nexport const { useDefinedContext: useUIManager, provider: UIManagerProvider } =\n  createDefinedContext<GameUIManager>();\n\nexport const { useDefinedContext: useTopLevelDiv, provider: TopLevelDivProvider } =\n  createDefinedContext<HTMLDivElement>();\n\nexport function useOverlayContainer(): HTMLDivElement | null {\n  return useUIManager()?.getOverlayContainer() ?? null;\n}\n\n/**\n * Get the currently used account on the client.\n * @param uiManager instance of GameUIManager\n */\nexport function useAccount(uiManager: GameUIManager): EthAddress | undefined {\n  const account = useMemo(() => uiManager.getAccount(), [uiManager]);\n\n  return account;\n}\n\n/**\n * Hook which gets you the player, and updates whenever that player's twitter or score changes.\n */\nexport function usePlayer(\n  uiManager: GameUIManager,\n  ethAddress?: EthAddress\n): Wrapper<Player | undefined> {\n  const [player, setPlayer] = useState<Wrapper<Player | undefined>>(\n    () => new Wrapper(uiManager.getPlayer(ethAddress))\n  );\n\n  useEmitterSubscribe(\n    uiManager.getGameManager().playersUpdated$,\n    () => {\n      setPlayer(new Wrapper(uiManager.getPlayer(ethAddress)));\n    },\n    [uiManager, setPlayer, ethAddress]\n  );\n\n  return player;\n}\n\n/**\n * Create a subscription to the currently selected planet.\n * @param uiManager instance of GameUIManager\n */\nexport function useSelectedPlanet(uiManager: GameUIManager): Wrapper<Planet | undefined> {\n  const selectedPlanetId = useWrappedEmitter<LocationId>(uiManager.selectedPlanetId$, undefined);\n  return usePlanet(uiManager, selectedPlanetId.value);\n}\n\nexport function useSelectedPlanetId(uiManager: GameUIManager, defaultId?: LocationId) {\n  return useWrappedEmitter<LocationId>(uiManager.selectedPlanetId$, defaultId);\n}\n\nexport function usePlanet(\n  uiManager: GameUIManager,\n  locationId: LocationId | undefined\n): Wrapper<Planet | undefined> {\n  const [planet, setPlanet] = useState<Wrapper<Planet | undefined>>(\n    () => new Wrapper(uiManager.getPlanetWithId(locationId))\n  );\n\n  useEffect(() => {\n    setPlanet(new Wrapper(uiManager.getPlanetWithId(locationId)));\n  }, [uiManager, locationId]);\n\n  useEmitterSubscribe(\n    uiManager.getGameManager().getGameObjects().planetUpdated$,\n    (id: LocationId) => {\n      if (id === locationId) {\n        setPlanet(new Wrapper(uiManager.getPlanetWithId(locationId)));\n      }\n    },\n    [uiManager, setPlanet, locationId]\n  );\n\n  return planet;\n}\n\n/**\n * Create a subscription to the currently hovering planet.\n * @param uiManager instance of GameUIManager\n */\nexport function useHoverPlanet(uiManager: GameUIManager): Wrapper<Planet | undefined> {\n  return useWrappedEmitter<Planet>(uiManager.hoverPlanet$, undefined);\n}\n\nexport function useHoverArtifact(uiManager: GameUIManager): Wrapper<Artifact | undefined> {\n  return useWrappedEmitter<Artifact>(uiManager.hoverArtifact$, undefined);\n}\n\nexport function useHoverArtifactId(uiManager: GameUIManager): Wrapper<ArtifactId | undefined> {\n  return useWrappedEmitter<ArtifactId>(uiManager.hoverArtifactId$, undefined);\n}\n\nexport function useMyArtifactsList(uiManager: GameUIManager) {\n  const [myArtifacts, setMyArtifacts] = useState(uiManager.getMyArtifacts());\n  useEmitterSubscribe(\n    uiManager.getArtifactUpdated$(),\n    () => {\n      setMyArtifacts(uiManager.getMyArtifacts());\n    },\n    [uiManager, setMyArtifacts]\n  );\n  return myArtifacts;\n}\n\n// note that this is going to throw an error if the pointer to `artifacts` changes but not to `planet`\nexport function usePlanetArtifacts(\n  planet: Wrapper<Planet | undefined>,\n  uiManager: GameUIManager\n): Artifact[] {\n  const artifacts = useMemo(\n    () => (planet.value ? uiManager.getArtifactsWithIds(planet.value.heldArtifactIds) : []),\n    [planet, uiManager]\n  );\n\n  return artifacts.filter((a) => !!a) as Artifact[];\n}\n\nexport function usePlanetInactiveArtifacts(\n  planet: Wrapper<Planet | undefined>,\n  uiManager: GameUIManager\n): Artifact[] {\n  const artifacts = usePlanetArtifacts(planet, uiManager);\n  const filtered = useMemo(() => artifacts.filter((a) => !isActivated(a)), [artifacts]);\n\n  return filtered;\n}\n\nexport function useActiveArtifact(\n  planet: Wrapper<Planet | undefined>,\n  uiManager: GameUIManager\n): Artifact | undefined {\n  const artifacts = usePlanetArtifacts(planet, uiManager);\n  return getActivatedArtifact(artifacts);\n}\n\n/**\n * Create a subscription to the currently selected artifact.\n * @param uiManager instance of GameUIManager\n */\nexport function useSelectedArtifact(uiManager: GameUIManager): Wrapper<Artifact | undefined> {\n  return useWrappedEmitter<Artifact>(uiManager.hoverArtifact$, undefined);\n}\n\nexport function useArtifact(uiManager: GameUIManager, artifactId: ArtifactId) {\n  const [artifact, setArtifact] = useState<Wrapper<Artifact | undefined>>(\n    new Wrapper(uiManager.getArtifactWithId(artifactId))\n  );\n\n  useEmitterSubscribe(\n    uiManager.getGameManager().getGameObjects().artifactUpdated$,\n    (id: ArtifactId) => {\n      if (id === artifactId) {\n        setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId)));\n      }\n    },\n    [uiManager, setArtifact, artifactId]\n  );\n\n  useEffect(() => {\n    setArtifact(new Wrapper(uiManager.getArtifactWithId(artifactId)));\n  }, [uiManager, artifactId]);\n\n  return artifact;\n}\n\n// TODO cache this globally\n\n/** Loads the leaderboard */\nexport function useLeaderboard(poll: number | undefined = undefined): {\n  leaderboard: Leaderboard | undefined;\n  error: Error | undefined;\n} {\n  const [leaderboard, setLeaderboard] = useState<Leaderboard | undefined>();\n  const [error, setError] = useState<Error | undefined>();\n\n  const load = useCallback(async function load() {\n    try {\n      setLeaderboard(await loadLeaderboard());\n    } catch (e) {\n      console.log('error loading leaderboard', e);\n      setError(e);\n    }\n  }, []);\n\n  usePoll(load, poll, true);\n\n  return { leaderboard, error };\n}\n\nexport function usePopAllOnSelectedPlanetChanged(\n  modal: ModalHandle,\n  startingId: LocationId | undefined\n) {\n  const selected = useSelectedPlanetId(useUIManager(), startingId).value;\n\n  useEffect(() => {\n    if (selected !== startingId) {\n      modal.popAll();\n    }\n  }, [selected, modal, startingId]);\n}\n\nexport type TransactionRecord = Record<TransactionId, Transaction>;\n\n/**\n * Creates subscriptions to all contract transaction events to keep an up to date\n * list of all transactions and their states.\n */\nexport function useTransactionLog() {\n  const uiManager = useUIManager();\n  const [transactions, setTransactions] = useState<Wrapper<TransactionRecord>>(new Wrapper({}));\n\n  /**\n   * Update the matching transaction in the {@link TransactionRecord}\n   * with data from the contract lifecycle events. A {@link Wrapper}\n   * around the {@link TransactionRecord} needs to be used to avoid\n   * force a React state change and re-render.\n   */\n  const updateTransaction = useCallback((tx: Transaction) => {\n    setTransactions((txs) => {\n      const txWrapper = new Wrapper(txs.value);\n      txWrapper.value[tx.id] = {\n        ...tx,\n      };\n      return txWrapper;\n    });\n  }, []);\n\n  useEffect(() => {\n    const gameManager = uiManager.getGameManager();\n    const contractEventEmitter = gameManager.getContractAPI();\n\n    contractEventEmitter.on(ContractsAPIEvent.TxQueued, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxPrioritized, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxProcessing, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxSubmitted, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxConfirmed, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxErrored, updateTransaction);\n    contractEventEmitter.on(ContractsAPIEvent.TxCancelled, updateTransaction);\n\n    return () => {\n      contractEventEmitter.off(ContractsAPIEvent.TxQueued, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxPrioritized, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxProcessing, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxSubmitted, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxConfirmed, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxErrored, updateTransaction);\n      contractEventEmitter.off(ContractsAPIEvent.TxCancelled, updateTransaction);\n    };\n  }, [uiManager, updateTransaction]);\n\n  return transactions;\n}\n\nexport function usePaused() {\n  const ui = useUIManager();\n  return useEmitterValue(ui.getPaused$(), ui.getPaused());\n}\n"
  },
  {
    "path": "src/Frontend/Utils/BrowserChecks.ts",
    "content": "import _ from 'lodash';\n\nexport const enum Incompatibility {\n  NoIDB = 'no_idb',\n  NotRopsten = 'not_ropsten',\n  MobileOrTablet = 'mobile_or_tablet',\n  UnsupportedBrowser = 'unsupported_browser',\n  NotLoggedInOrEnabled = 'not_logged_in_or_enabled',\n  UnexpectedError = 'unexpected_error',\n}\n\nexport const hasTouchscreen = () => {\n  // @ts-ignore       TS2551: Property 'msMaxTouchPoints' does not exist on type 'Navigator'\n  return 'ontouchstart' in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0;\n};\n\nconst supportsIDB = () => {\n  return 'indexedDB' in window;\n};\n\n// modified, original from https://stackoverflow.com/questions/11381673/detecting-a-mobile-browser\nexport const isMobileOrTablet = () => {\n  let check = false;\n  (function (a) {\n    if (\n      /(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i.test(\n        a as string\n      ) ||\n      /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\\-|your|zeto|zte\\-/i.test(\n        (a as string).substr(0, 4)\n      )\n    )\n      check = true;\n  })(navigator.userAgent || navigator.vendor || 'opera' in window);\n  return check;\n};\n\nconst isSupportedBrowser = async () => isChrome() || isFirefox() || (await isBrave());\n\ntype FeatureList = Partial<Record<Incompatibility, boolean>>;\n\nconst checkFeatures = async (): Promise<FeatureList> => {\n  const incompats: FeatureList = {};\n\n  try {\n    incompats[Incompatibility.UnsupportedBrowser] = !(await isSupportedBrowser());\n    incompats[Incompatibility.NoIDB] = !supportsIDB();\n    incompats[Incompatibility.MobileOrTablet] = isMobileOrTablet();\n  } catch (e) {\n    console.error(e);\n    incompats[Incompatibility.UnexpectedError] = true;\n  }\n\n  return incompats;\n};\n\nexport const unsupportedFeatures = async (): Promise<Incompatibility[]> => {\n  const features = await checkFeatures();\n  return _.keys(features).filter((f: Incompatibility) => features[f]) as Incompatibility[];\n};\n\nexport const isFirefox = () => navigator.userAgent.indexOf('Firefox') > 0;\n\nexport const isChrome = () => /Google Inc/.test(navigator.vendor);\n\nexport const isBrave = async () =>\n  !!((navigator as any).brave && (await (navigator as any).brave.isBrave())); // eslint-disable-line @typescript-eslint/no-explicit-any\n"
  },
  {
    "path": "src/Frontend/Utils/EmitterHooks.ts",
    "content": "import { Callback, Monomitter } from '@darkforest_eth/events';\nimport { useEffect, useState } from 'react';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\n\n/**\n * Execute something on emitter callback\n * @param emitter `Monomitter` to subscribe to\n * @param callback callback to subscribe\n */\nexport function useEmitterSubscribe<T>(\n  emitter: Monomitter<T>,\n  callback: Callback<T>,\n  deps: React.DependencyList\n) {\n  useEffect(\n    () => {\n      return emitter.subscribe(callback).unsubscribe;\n    },\n    // Disable exhaustive because we don't change if the callback changes, only deps\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [\n      emitter,\n      // eslint-disable-next-line react-hooks/exhaustive-deps\n      ...deps,\n    ]\n  );\n}\n\n/**\n * Use returned value from an emitter\n * @param emitter `Monomitter` to subscribe to\n * @param initialVal initial state value\n */\nexport function useEmitterValue<T>(emitter: Monomitter<T>, initialVal: T) {\n  const [val, setVal] = useState<T>(initialVal);\n\n  useEffect(() => {\n    const sub = emitter.subscribe((v) => setVal(v));\n\n    return sub.unsubscribe;\n  }, [emitter]);\n\n  return val;\n}\n\n/**\n * Use returned value from an emitter, and clone the reference - used to force an update to the UI\n * @param emitter `Monomitter` to subscribe to\n * @param initialVal initial state value\n */\nexport function useWrappedEmitter<T>(\n  emitter: Monomitter<T | undefined>,\n  initialVal: T | undefined\n): Wrapper<T | undefined> {\n  const [val, setVal] = useState<Wrapper<T | undefined>>(new Wrapper(initialVal));\n\n  useEffect(() => {\n    const sub = emitter.subscribe((v) => {\n      setVal(new Wrapper(v));\n    });\n\n    return sub.unsubscribe;\n  }, [emitter]);\n\n  return val;\n}\n"
  },
  {
    "path": "src/Frontend/Utils/EmitterUtils.ts",
    "content": "import { monomitter, Monomitter } from '@darkforest_eth/events';\nimport { isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { Artifact, EthAddress, Planet } from '@darkforest_eth/types';\nimport _ from 'lodash';\n\n/**\n * Create a monomitter to emit objects with a given id from a cached map of ids to objects.\n * @param objMap the cached map of `<Id, Obj>`\n * @param objId$ the object id to select\n * @param objUpdated$ emitter which indicates when an object has been updated\n */\nexport function getObjectWithIdFromMap<Obj, Id>(\n  objMap: Map<Id, Obj>,\n  objId$: Monomitter<Id | undefined>,\n  objUpdated$: Monomitter<Id>\n): Monomitter<Obj | undefined> {\n  let lastId: Id | undefined;\n\n  const selectedObj$ = monomitter<Obj | undefined>();\n\n  const publishIdEvent = (id: Id | undefined) => {\n    // emit object associated with id\n    selectedObj$.publish(id ? objMap.get(id) : undefined);\n  };\n\n  objId$.subscribe((id: Id | undefined) => {\n    // Publish event for new object\n    publishIdEvent(id);\n    //Update tracked object\n    lastId = id;\n  });\n\n  objUpdated$.subscribe((id: Id) => {\n    if (lastId && lastId === id) publishIdEvent(id);\n  });\n\n  return selectedObj$;\n}\n\n/**\n * Create a monomitter to emit objects with a given id from a cached map of ids to objects. Not intended for re-use\n * @param objMap the cached map of `<Id, Obj>`\n * @param objId the object id to select\n * @param objUpdated$ emitter which indicates when an object has been updated\n */\nexport function getDisposableEmitter<Obj, Id>(\n  objMap: Map<Id, Obj>,\n  objId: Id,\n  objUpdated$: Monomitter<Id>\n): Monomitter<Obj | undefined> {\n  const selectedObj$ = monomitter<Obj | undefined>();\n\n  const publishIdEvent = (id: Id | undefined) => {\n    // emit value of new id\n    selectedObj$.publish(id ? objMap.get(id) : undefined);\n  };\n\n  objUpdated$.subscribe((id: Id) => {\n    if (objId === id) publishIdEvent(id);\n  });\n\n  return selectedObj$;\n}\n\n/**\n * Utility function for setting a game entity into our internal data stores in a way\n * that is friendly to our application. Caches the object into a map, syncs it to a map\n * of our owned objects, and also emits a message that the object was updated.\n * @param objectMap map that caches known objects\n * @param myObjectMap map that caches known objects owned by the user\n * @param address the user's account address\n * @param obj the object we want to cache\n * @param objUpdated$ emitter for announcing object updates\n */\nexport function setObjectSyncState<Obj, Id>(\n  objectMap: Map<Id, Obj>,\n  myObjectMap: Map<Id, Obj>,\n  address: EthAddress | undefined,\n  objUpdated$: Monomitter<Id>,\n  myObjListUpdated$: Monomitter<Map<Id, Obj>>,\n  getId: (o: Obj) => Id,\n  getOwner: (o: Obj) => EthAddress,\n  obj: Obj\n): void {\n  objectMap.set(getId(obj), obj);\n\n  let myObjListChanged = false;\n\n  if (address === getOwner(obj)) {\n    myObjectMap.set(getId(obj), obj);\n    myObjListChanged = true;\n  } else if (myObjectMap.has(getId(obj))) {\n    myObjectMap.delete(getId(obj));\n    myObjListChanged = true;\n  }\n\n  objUpdated$.publish(getId(obj));\n\n  if (myObjListChanged) {\n    myObjListUpdated$.publish(myObjectMap);\n  }\n}\n\n/**\n * @param previous The previously emitted state of an object\n * @param current The current emitted state of an object\n */\nexport interface Diff<Type> {\n  previous: Type;\n  current: Type;\n}\n\n/**\n * Wraps an existing emitter and emits an event with the current and previous values\n * @param emitter an emitter announcing game objects\n */\nexport function generateDiffEmitter<Obj>(\n  emitter: Monomitter<Obj | undefined>\n): Monomitter<Diff<Obj> | undefined> {\n  let prevInstance: Obj | undefined;\n  const currPrevEmitter$ = monomitter<Diff<Obj> | undefined>();\n  emitter.subscribe((instance) => {\n    currPrevEmitter$.publish({\n      current: _.cloneDeep(instance) as Obj,\n      previous: prevInstance ? _.cloneDeep(prevInstance) : (_.cloneDeep(instance) as Obj),\n    });\n    prevInstance = _.cloneDeep(instance);\n  });\n\n  return currPrevEmitter$;\n}\n/* i'm slightly worried that function literals would have to get allocated and de-allocated\n   all the time - the below functions exist in order to prevent that */\n\nexport const getPlanetId = (p: Planet) => p.locationId;\nexport const getPlanetOwner = (p: Planet) => p.owner;\n\nexport const getArtifactId = (a: Artifact) => a.id;\nexport const getArtifactOwner = (a: Artifact) => {\n  if (isSpaceShip(a.artifactType)) {\n    return a.controller;\n  }\n\n  return a.currentOwner;\n};\n"
  },
  {
    "path": "src/Frontend/Utils/Hooks.tsx",
    "content": "import { useEffect } from 'react';\n\n/**\n * Executes the callback `cb` every `poll` ms\n * @param cb callback to execute\n * @param poll ms to poll\n * @param execFirst if we want to execute the callback on first render\n */\nexport function usePoll(\n  cb: () => void,\n  poll: number | undefined = undefined,\n  execFirst: boolean | undefined = undefined\n) {\n  useEffect(() => {\n    if (execFirst) cb();\n\n    if (!poll) return;\n    const interval = setInterval(cb, poll);\n\n    return () => clearInterval(interval);\n  }, [poll, cb, execFirst]);\n}\n"
  },
  {
    "path": "src/Frontend/Utils/KeyEmitters.ts",
    "content": "import { SpecialKey } from '@darkforest_eth/constants';\nimport { monomitter } from '@darkforest_eth/events';\nimport { Setting } from '@darkforest_eth/types';\nimport { useEffect, useState } from 'react';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { useUIManager } from './AppHooks';\nimport { useEmitterSubscribe } from './EmitterHooks';\nimport { useBooleanSetting } from './SettingsHooks';\n\nexport const keyUp$ = monomitter<Wrapper<string>>();\nexport const keyDown$ = monomitter<Wrapper<string>>();\n\nconst onKeyUp = (e: KeyboardEvent) => {\n  if (!shouldIgnoreShortcutKeypress(e))\n    keyUp$.publish(new Wrapper(getSpecialKeyFromEvent(e) || e.key.toLowerCase()));\n};\n\nconst onKeyDown = (e: KeyboardEvent) => {\n  if (!shouldIgnoreShortcutKeypress(e))\n    keyDown$.publish(new Wrapper(getSpecialKeyFromEvent(e) || e.key.toLowerCase()));\n};\n\nexport function listenForKeyboardEvents() {\n  document.addEventListener('keydown', onKeyDown);\n  document.addEventListener('keyup', onKeyUp);\n}\n\nexport function unlinkKeyboardEvents() {\n  document.removeEventListener('keydown', onKeyDown);\n  document.removeEventListener('keyup', onKeyUp);\n}\n\n/**\n * If the user is using their keyboard to input some text somewhere, we should NOT trigger the\n * shortcuts.\n */\nfunction shouldIgnoreShortcutKeypress(e: KeyboardEvent): boolean {\n  const targetElement = e.target as HTMLElement;\n  if (targetElement.tagName === 'INPUT')\n    return targetElement.attributes.getNamedItem('type')?.value !== 'range';\n  return targetElement.tagName === 'TEXTAREA';\n}\n\nfunction getSpecialKeyFromEvent(e: KeyboardEvent): string | undefined {\n  if ((Object.values(SpecialKey) as string[]).includes(e.key)) {\n    return e.key;\n  }\n}\n\nexport function useIsDown(key?: string) {\n  const [isDown, setIsDown] = useState(false);\n  useEmitterSubscribe(keyDown$, (k) => key !== undefined && k.value === key && setIsDown(true), [\n    key,\n    setIsDown,\n  ]);\n  useEmitterSubscribe(keyUp$, (k) => key !== undefined && k.value === key && setIsDown(false), [\n    key,\n    setIsDown,\n  ]);\n  return isDown;\n}\n\nexport function useOnUp(key: string, onUp: () => void, deps: React.DependencyList = []) {\n  const [disableDefaultShortcuts] = useBooleanSetting(\n    useUIManager(),\n    Setting.DisableDefaultShortcuts\n  );\n\n  useEffect(() => {\n    const onKeyUp = (e: KeyboardEvent) => {\n      if (\n        (getSpecialKeyFromEvent(e) === key || e.key.toLowerCase() === key) &&\n        !shouldIgnoreShortcutKeypress(e)\n      ) {\n        !disableDefaultShortcuts && onUp && onUp();\n      }\n    };\n\n    document.addEventListener('keyup', onKeyUp);\n    return () => document.removeEventListener('keyup', onKeyUp);\n  }, [\n    key,\n    onUp,\n    disableDefaultShortcuts,\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    ...deps,\n  ]);\n}\n"
  },
  {
    "path": "src/Frontend/Utils/SettingsHooks.tsx",
    "content": "import { monomitter, Monomitter } from '@darkforest_eth/events';\nimport { AutoGasSetting, EthAddress, Setting } from '@darkforest_eth/types';\nimport React, { useCallback, useState } from 'react';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\nimport { SelectFrom } from '../Components/CoreUI';\nimport {\n  Checkbox,\n  ColorInput,\n  DarkForestCheckbox,\n  DarkForestColorInput,\n  DarkForestNumberInput,\n  DarkForestTextInput,\n  NumberInput,\n  TextInput,\n} from '../Components/Input';\nimport { useEmitterSubscribe } from './EmitterHooks';\n\n/**\n * Whenever a setting changes, we publish the setting's name to this event emitter.\n */\nexport const settingChanged$: Monomitter<Setting> = monomitter();\n\nexport const ALL_AUTO_GAS_SETTINGS = [\n  AutoGasSetting.Slow,\n  AutoGasSetting.Average,\n  AutoGasSetting.Fast,\n];\n\nfunction onlyInProduction(): string {\n  return process.env.NODE_ENV === 'production' ? 'true' : 'false';\n}\n\nfunction onlyInDevelopment(): string {\n  return process.env.NODE_ENV !== 'production' ? 'true' : 'false';\n}\n\nconst defaultSettings: Record<Setting, string> = {\n  [Setting.OptOutMetrics]: onlyInDevelopment(),\n  [Setting.AutoApproveNonPurchaseTransactions]: onlyInDevelopment(),\n  [Setting.DrawChunkBorders]: 'false',\n  [Setting.HighPerformanceRendering]: 'false',\n  [Setting.MoveNotifications]: 'true',\n  [Setting.HasAcceptedPluginRisk]: onlyInDevelopment(),\n  [Setting.GasFeeGwei]: AutoGasSetting.Average,\n  [Setting.TerminalVisible]: 'true',\n  [Setting.TutorialOpen]: onlyInProduction(),\n\n  [Setting.FoundPirates]: 'false',\n  [Setting.TutorialCompleted]: 'false',\n  [Setting.FoundSilver]: 'false',\n  [Setting.FoundSilverBank]: 'false',\n  [Setting.FoundTradingPost]: 'false',\n  [Setting.FoundComet]: 'false',\n  [Setting.FoundArtifact]: 'false',\n  [Setting.FoundDeepSpace]: 'false',\n  [Setting.FoundSpace]: 'false',\n  // prevent the tutorial and help pane popping up in development mode.\n  [Setting.NewPlayer]: onlyInProduction(),\n  [Setting.MiningCores]: '1',\n  [Setting.IsMining]: 'true',\n  [Setting.DisableDefaultShortcuts]: 'false',\n  [Setting.ExperimentalFeatures]: 'false',\n  [Setting.DisableEmojiRendering]: 'false',\n  [Setting.DisableHatRendering]: 'false',\n  [Setting.AutoClearConfirmedTransactionsAfterSeconds]: '-1',\n  [Setting.AutoClearRejectedTransactionsAfterSeconds]: '-1',\n  [Setting.DisableFancySpaceEffect]: 'false',\n  [Setting.RendererColorInnerNebula]: '#186469',\n  [Setting.RendererColorNebula]: '#0B2B5B',\n  [Setting.RendererColorSpace]: '#0B0F34',\n  [Setting.RendererColorDeepSpace]: '#0B061F',\n  [Setting.RendererColorDeadSpace]: '#11291b',\n  [Setting.ForceReloadEmbeddedPlugins]: 'false',\n};\n\ninterface SettingStorageConfig {\n  contractAddress: EthAddress;\n  account: EthAddress | undefined;\n}\n\n/**\n * Each setting is stored in local storage. Each account has their own setting.\n */\nexport function getLocalStorageSettingKey(\n  { contractAddress, account }: SettingStorageConfig,\n  setting: Setting\n): string {\n  if (account === undefined) {\n    return contractAddress + ':anonymous:' + setting;\n  }\n\n  return contractAddress + ':' + account + ':' + setting;\n}\n\n/**\n * Read the local storage setting from local storage.\n */\nexport function getSetting(config: SettingStorageConfig, setting: Setting): string {\n  const key = getLocalStorageSettingKey(config, setting);\n\n  let valueInStorage = localStorage.getItem(key);\n\n  if (valueInStorage === null) {\n    valueInStorage = defaultSettings[setting];\n  }\n\n  return valueInStorage;\n}\n\n/**\n * Save the given setting to local storage. Publish an event to {@link settingChanged$}.\n */\nexport function setSetting(\n  { contractAddress, account }: SettingStorageConfig,\n  setting: Setting,\n  value: string\n): void {\n  const keyInLocalStorage =\n    account && getLocalStorageSettingKey({ contractAddress, account }, setting);\n  if (keyInLocalStorage === undefined || account === undefined) {\n    return;\n  }\n\n  localStorage.setItem(keyInLocalStorage, value);\n  settingChanged$.publish(setting);\n}\n\n/**\n * Loads from local storage, and interprets as a boolean the setting with the given name.\n */\nexport function getBooleanSetting(config: SettingStorageConfig, setting: Setting): boolean {\n  const value = getSetting(config, setting);\n  return value === 'true';\n}\n\n/**\n * Save the given setting to local storage. Publish an event to {@link settingChanged$}.\n */\nexport function setBooleanSetting(config: SettingStorageConfig, setting: Setting, value: boolean) {\n  setSetting(config, setting, value + '');\n}\n\n/**\n * Loads from local storage, and interprets as a boolean the setting with the given name.\n */\nexport function getNumberSetting(config: SettingStorageConfig, setting: Setting): number {\n  const value = getSetting(config, setting);\n  const parsedValue = parseFloat(value);\n\n  if (isNaN(parsedValue)) {\n    return parseFloat(defaultSettings[setting]);\n  }\n\n  return parsedValue;\n}\n\n/**\n * Save the given setting to local storage. Publish an event to {@link settingChanged$}.\n */\nexport function setNumberSetting(config: SettingStorageConfig, setting: Setting, value: number) {\n  setSetting(config, setting, value + '');\n}\n\n/**\n * Allows a react component to subscribe to changes and set the given setting.\n */\nexport function useSetting(\n  uiManager: GameUIManager,\n  setting: Setting\n): [string, (newValue: string | undefined) => void] {\n  const contractAddress = uiManager.getContractAddress();\n  const account = uiManager.getAccount();\n  const config = { contractAddress, account };\n  const [settingValue, setSettingValue] = useState(() => getSetting(config, setting));\n\n  useEmitterSubscribe(\n    settingChanged$,\n    (changedSetting: Setting) => {\n      if (changedSetting === setting) {\n        setSettingValue(getSetting(config, changedSetting));\n      }\n    },\n    [setting, setSettingValue, getSetting]\n  );\n\n  return [\n    settingValue,\n    (newValue: string) => {\n      setSetting(config, setting, newValue);\n    },\n  ];\n}\n\nexport function StringSetting({\n  uiManager,\n  setting,\n  settingDescription,\n}: {\n  uiManager: GameUIManager;\n  setting: Setting;\n  settingDescription?: string;\n}) {\n  const [settingValue, setSettingValue] = useSetting(uiManager, setting);\n  const onChange = useCallback(\n    (e: Event & React.ChangeEvent<DarkForestTextInput>) => {\n      setSettingValue(e.target.value);\n    },\n    [setSettingValue]\n  );\n  return (\n    <>\n      {settingDescription}\n      <br />\n      <TextInput value={settingValue} onChange={onChange} />\n    </>\n  );\n}\n\nexport function ColorSetting({\n  uiManager,\n  setting,\n  settingDescription,\n}: {\n  uiManager: GameUIManager;\n  setting: Setting;\n  settingDescription?: string;\n}) {\n  const [settingValue, setSettingValue] = useSetting(uiManager, setting);\n  const onChange = useCallback(\n    (e: Event & React.ChangeEvent<DarkForestColorInput>) => {\n      setSettingValue(e.target.value);\n    },\n    [setSettingValue]\n  );\n  return (\n    <>\n      {settingDescription}\n      <br />\n      <ColorInput value={settingValue} onChange={onChange} />\n    </>\n  );\n}\n\n/**\n * Allows a react component to subscribe to changes and set the given setting as a number. Doesn't\n * allow you to set the value of this setting to anything but a valid number.\n */\nexport function useNumberSetting(\n  uiManager: GameUIManager,\n  setting: Setting\n): [number, (newValue: number) => void] {\n  const [stringSetting, setStringSetting] = useSetting(uiManager, setting);\n  let parsedNumber = parseInt(stringSetting, 10);\n\n  if (isNaN(parsedNumber)) {\n    parsedNumber = 0;\n  }\n\n  return [\n    parsedNumber,\n    (newValue: number) => {\n      setStringSetting(newValue + '');\n    },\n  ];\n}\n\n/**\n * Allows a react component to subscribe to changes to the given setting, interpreting its value as\n * a boolean.\n */\nexport function useBooleanSetting(\n  uiManager: GameUIManager,\n  setting: Setting\n): [boolean, (newValue: boolean) => void] {\n  const [stringSetting, setStringSetting] = useSetting(uiManager, setting);\n  const booleanValue = stringSetting === 'true';\n\n  return [\n    booleanValue,\n    (newValue: boolean) => {\n      setStringSetting(newValue + '');\n    },\n  ];\n}\n\n/**\n * React component that renders a checkbox representing the current value of this particular\n * setting, interpreting its value as a boolean. Allows the player to click on the checkbox to\n * toggle the setting. Toggling the setting both notifies the rest of the game that the given\n * setting was changed, and also saves it to local storage.\n */\nexport function BooleanSetting({\n  uiManager,\n  setting,\n  settingDescription,\n}: {\n  uiManager: GameUIManager;\n  setting: Setting;\n  settingDescription?: string;\n}) {\n  const [settingValue, setSettingValue] = useBooleanSetting(uiManager, setting);\n\n  return (\n    <Checkbox\n      label={settingDescription}\n      checked={settingValue}\n      onChange={(e: Event & React.ChangeEvent<DarkForestCheckbox>) =>\n        setSettingValue(e.target.checked)\n      }\n    />\n  );\n}\n\nexport function NumberSetting({\n  uiManager,\n  setting,\n}: {\n  uiManager: GameUIManager;\n  setting: Setting;\n}) {\n  const [settingValue, setSettingValue] = useNumberSetting(uiManager, setting);\n\n  return (\n    <NumberInput\n      format='float'\n      value={settingValue}\n      onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => {\n        if (e.target.value) {\n          setSettingValue(e.target.value);\n        }\n      }}\n    />\n  );\n}\n\n/**\n * UI that is kept in-sync with a particular setting which allows you to set that setting to one of\n * several options.\n */\nexport function MultiSelectSetting({\n  uiManager,\n  setting,\n  values,\n  labels,\n  style,\n  wide,\n}: {\n  uiManager: GameUIManager;\n  setting: Setting;\n  values: string[];\n  labels: string[];\n  style?: React.CSSProperties;\n  wide?: boolean;\n}) {\n  const [settingValue, setSettingValue] = useSetting(uiManager, setting);\n\n  return (\n    <SelectFrom\n      wide={wide}\n      style={style}\n      values={values}\n      labels={labels}\n      value={settingValue}\n      setValue={setSettingValue}\n    />\n  );\n}\n\n/**\n * Some settings can be set from another browser window. In particular, the 'auto accept\n * transaction' setting is set from multiple browser windows. As a result, the local storage setting\n * can get out of sync with the in memory setting. To fix this, we can poll the given setting from\n * local storage, and notify the rest of the game that it changed if it changed.\n */\nexport function pollSetting(\n  config: SettingStorageConfig,\n  setting: Setting\n): ReturnType<typeof setInterval> {\n  const SETTING_POLL_INTERVAL = 1000;\n  const value = getSetting(config, setting);\n\n  return setInterval(() => {\n    const newValue = getSetting(config, setting);\n\n    if (value !== newValue) {\n      settingChanged$.publish(setting);\n    }\n  }, SETTING_POLL_INTERVAL);\n}\n"
  },
  {
    "path": "src/Frontend/Utils/ShortcutConstants.ts",
    "content": "import { SpecialKey } from '@darkforest_eth/constants';\n\n// modal shortcuts\nexport const MODAL_BACK_SHORTCUT = 't';\nexport const CLOSE_MODAL = 't';\nexport const TOGGLE_SETTINGS_PANE = 'h';\nexport const TOGGLE_HELP_PANE = 'j';\nexport const TOGGLE_PLUGINS_PANE = 'k';\nexport const TOGGLE_YOUR_ARTIFACTS_PANE = 'l';\nexport const TOGGLE_YOUR_PLANETS_DEX_PANE = ';';\nexport const TOGGLE_TRANSACTIONS_PANE = \"'\";\n\n// planet context pane shortcuts\nexport const TOGGLE_PLANET_ARTIFACTS_PANE = 's';\nexport const TOGGLE_HAT_PANE = 'x';\nexport const TOGGLE_ABANDON = 'r';\nexport const INVADE = 'y';\nexport const MINE_ARTIFACT = 'f';\nexport const TOGGLE_BROADCAST_PANE = 'z';\nexport const TOGGLE_UPGRADES_PANE = 'a';\nexport const TOGGLE_SEND = 'q';\nexport const TOGGLE_PLANET_INFO_PANE = 'c';\nexport const EXIT_PANE = SpecialKey.Escape;\n\n// global shortcuts\nexport const TOGGLE_DIAGNOSTICS_PANE = 'i';\nexport const TOGGLE_EXPLORE = SpecialKey.Space;\nexport const TOGGLE_TARGETTING = '`';\n"
  },
  {
    "path": "src/Frontend/Utils/TerminalTypes.ts",
    "content": "export const enum TerminalTextStyle {\n  Green,\n  Sub,\n  Subber,\n  Text,\n  White,\n  Red,\n  Blue,\n  Invisible,\n  Underline,\n  Mythic,\n}\n"
  },
  {
    "path": "src/Frontend/Utils/TimeUtils.ts",
    "content": "export function formatDuration(durationMs: number) {\n  if (durationMs < 0) {\n    return '';\n  }\n\n  const hours = Math.floor(durationMs / 1000 / 60 / 60);\n  const minutes = Math.floor((durationMs - hours * 60 * 60 * 1000) / 1000 / 60);\n  const seconds = Math.floor((durationMs - hours * 60 * 60 * 1000 - minutes * 60 * 1000) / 1000);\n\n  return (\n    timestampSection(hours) + ':' + timestampSection(minutes) + ':' + timestampSection(seconds)\n  );\n}\n\nfunction timestampSection(value: number) {\n  return value.toString().padStart(2, '0');\n}\n"
  },
  {
    "path": "src/Frontend/Utils/UIEmitter.ts",
    "content": "import { EventEmitter } from 'events';\n\nexport const enum UIEmitterEvent {\n  GamePlanetSelected = 'GamePlanetSelected',\n  CenterPlanet = 'CenterPlanet',\n  WindowResize = 'WindowResize',\n\n  UIChange = 'UIChange', // whenever you collapse, etc.\n\n  CanvasMouseDown = 'CanvasMouseDown',\n  CanvasMouseMove = 'CanvasMouseMove',\n  CanvasMouseUp = 'CanvasMouseUp',\n  CanvasMouseOut = 'CanvasMouseOut',\n  CanvasScroll = 'CanvasScroll',\n\n  WorldMouseDown = 'WorldMouseDown',\n  WorldMouseClick = 'WorldMouseClick',\n  WorldMouseMove = 'WorldMouseMove',\n  WorldMouseUp = 'WorldMouseUp',\n  WorldMouseOut = 'WorldMouseOut',\n\n  ZoomIn = 'ZoomIn',\n  ZoomOut = 'ZoomOut',\n\n  SendInitiated = 'SendInitiated',\n  SendCancelled = 'SendCancelled',\n  SendCompleted = 'SendCompleted',\n\n  DepositArtifact = 'DepositArtifact',\n  DepositToPlanet = 'DepositToPlanet',\n\n  SelectArtifact = 'SelectArtifact',\n  ShowArtifact = 'ShowArtifact',\n}\n\nclass UIEmitter extends EventEmitter {\n  static instance: UIEmitter;\n\n  private constructor() {\n    super();\n  }\n\n  static getInstance(): UIEmitter {\n    if (!UIEmitter.instance) {\n      UIEmitter.instance = new UIEmitter();\n    }\n\n    return UIEmitter.instance;\n  }\n\n  static initialize(): UIEmitter {\n    const uiEmitter = new UIEmitter();\n\n    return uiEmitter;\n  }\n}\n\nexport default UIEmitter;\n"
  },
  {
    "path": "src/Frontend/Utils/constants.ts",
    "content": "import * as bigInt from 'big-integer';\n\n// To developer, increase this number to 256. This, in combination with setting `DISABLE_ZK_CHECKS`\n// in darkforest.toml, will make you mine the map at ULTRA SPEED!\n// To code reviewer, make sure this does not change in a PR to develop!\nconst MIN_CHUNK_SIZE = 16;\n\n/**\n * @tutorial to speed up the game's background rendering code, it is possible to set this value to\n * be a higher power of two. This means that smaller chunks will be merged into larger chunks via\n * the algorithms implemented in {@link ChunkUtils}.\n *\n * {@code Math.floor(Math.pow(2, 16))} should be large enough for most.\n */\nconst MAX_CHUNK_SIZE = 2 ** 14;\n\nconst LOCATION_ID_UB = bigInt(\n  '21888242871839275222246405745257275088548364400416034343698204186575808495617'\n);\n\nexport { MIN_CHUNK_SIZE, MAX_CHUNK_SIZE, LOCATION_ID_UB };\n\nexport const enum DFZIndex {\n  MenuBar = 4,\n  HoverPlanet = 1001,\n  Modal = 1001,\n  Tooltip = 16000000,\n  Notification = 1000,\n}\n"
  },
  {
    "path": "src/Frontend/Utils/createDefinedContext.ts",
    "content": "import React from 'react';\n\ntype ContextHookWithProvider<T> = {\n  useDefinedContext: () => T;\n  provider: React.Provider<T>;\n};\n\n/**\n * Return a hook and a provider which return a value that must be defined. Normally is difficult\n * because `React.createContext()` defaults to `undefined`.\n *\n * `useDefinedContext()` must be called inside of `provider`, otherwise an error will be thrown.\n */\nexport function createDefinedContext<T>(): ContextHookWithProvider<T> {\n  /* This non-null assertion will indeed cause problems if the provider is passed\n     a nullable value, but the below `throw` should catch that case. */\n\n  /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */\n  const context = React.createContext<T>(undefined!);\n\n  const useDefinedContext = (): T => {\n    const used = React.useContext(context);\n    // not doing !used because `used` could be a number\n    if (used === undefined) {\n      throw new Error(\n        'useDefinedContext is undefined! Make sure it is used in a provider and passed a defined value.'\n      );\n    }\n    return used;\n  };\n\n  return {\n    useDefinedContext,\n    provider: context.Provider,\n  };\n}\n"
  },
  {
    "path": "src/Frontend/Views/ArtifactLink.tsx",
    "content": "import { artifactName } from '@darkforest_eth/procedural';\nimport { Artifact, LocationId } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect } from 'react';\nimport { Link } from '../Components/CoreUI';\nimport { ArtifactDetailsPane } from '../Panes/ArtifactDetailsPane';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { ModalHandle } from './ModalPane';\n\nexport function ArtifactLink({\n  modal,\n  children,\n  artifact,\n  depositOn,\n}: {\n  modal?: ModalHandle;\n  artifact: Artifact;\n  children: React.ReactNode | React.ReactNode[];\n  depositOn?: LocationId;\n}) {\n  const uiManager = useUIManager();\n\n  useEffect(() => {\n    // this is called when the component is unrendered\n    return () => uiManager?.setHoveringOverArtifact(undefined);\n  }, [uiManager]);\n\n  const onClick = useCallback(() => {\n    uiManager?.setHoveringOverArtifact(undefined);\n    modal &&\n      modal.push({\n        element() {\n          return (\n            <ArtifactDetailsPane depositOn={depositOn} artifactId={artifact?.id} modal={modal} />\n          );\n        },\n        title: artifactName(artifact),\n      });\n  }, [artifact, modal, depositOn, uiManager]);\n\n  return (\n    <Link\n      color={dfstyles.colors.text}\n      onClick={onClick}\n      onMouseDown={() => {\n        uiManager?.setHoveringOverArtifact(undefined);\n      }}\n      onMouseEnter={() => {\n        uiManager?.setHoveringOverArtifact(artifact.id);\n      }}\n      onMouseLeave={() => {\n        uiManager?.setHoveringOverArtifact(undefined);\n      }}\n    >\n      {children}\n    </Link>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/ArtifactRow.tsx",
    "content": "import { isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { Artifact } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect, useMemo } from 'react';\nimport styled, { css } from 'styled-components';\nimport { ArtifactImage } from '../Components/ArtifactImage';\nimport { Spacer } from '../Components/CoreUI';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\n\nconst RowWrapper = styled.div`\n  width: 100%;\n  display: flex;\n  flex-direction: row;\n  justify-content: 'space-around';\n  align-items: center;\n  overflow-x: scroll;\n`;\n\nconst thumbActive = css`\n  border: 1px solid ${dfstyles.colors.border};\n  background-color: ${dfstyles.colors.border};\n`;\n\nconst StyledArtifactThumb = styled.div<{ active: boolean; enemy: boolean }>`\n  min-width: 2.5em;\n  min-height: 2.5em;\n  width: 2.5em;\n  height: 2.5em;\n\n  border: 1px solid ${({ enemy }) => (enemy ? dfstyles.colors.dfred : dfstyles.colors.borderDark)};\n  border-radius: 4px;\n\n  &:last-child {\n    margin-right: none;\n  }\n\n  display: inline-flex;\n  flex-direction: row;\n  justify-content: space-around;\n  align-items: center;\n\n  background: ${dfstyles.colors.artifactBackground};\n\n  &:hover {\n    ${thumbActive}\n    cursor: pointer;\n\n    & > div {\n      filter: brightness(1.2);\n    }\n  }\n\n  ${({ active }) => active && thumbActive}\n`;\n\nexport function ArtifactThumb({\n  artifact,\n  selectedArtifact,\n  onArtifactChange,\n}: {\n  selectedArtifact?: Artifact | undefined;\n  onArtifactChange?: (artifact: Artifact | undefined) => void;\n  artifact: Artifact;\n}) {\n  const uiManager = useUIManager();\n  const enemy = useMemo(() => {\n    const account = uiManager.getAccount();\n    if (isSpaceShip(artifact.artifactType)) {\n      return artifact?.controller !== account;\n    }\n\n    return false;\n  }, [artifact, uiManager]);\n  const click = useCallback(() => {\n    if (!onArtifactChange || enemy) return;\n\n    if (artifact.id === selectedArtifact?.id) onArtifactChange(undefined);\n    else onArtifactChange(artifact);\n  }, [onArtifactChange, artifact, selectedArtifact, enemy]);\n\n  useEffect(() => {\n    // this is called when the component is unrendered\n    return () => uiManager?.setHoveringOverArtifact(undefined);\n  }, [uiManager]);\n\n  return (\n    <StyledArtifactThumb\n      active={selectedArtifact?.id === artifact.id}\n      enemy={enemy}\n      onClick={click}\n      onMouseEnter={() => {\n        uiManager?.setHoveringOverArtifact(artifact.id);\n      }}\n      onMouseLeave={() => {\n        uiManager?.setHoveringOverArtifact(undefined);\n      }}\n    >\n      <ArtifactImage artifact={artifact} thumb size={32} />\n    </StyledArtifactThumb>\n  );\n}\n\nexport function SelectArtifactRow({\n  selectedArtifact,\n  onArtifactChange,\n  artifacts,\n}: {\n  selectedArtifact?: Artifact | undefined;\n  onArtifactChange?: (artifact: Artifact | undefined) => void;\n  artifacts: Artifact[];\n}) {\n  return (\n    <RowWrapper>\n      {artifacts.length > 0 &&\n        artifacts.map((a) => (\n          <span key={a.id}>\n            <ArtifactThumb\n              artifact={a}\n              selectedArtifact={selectedArtifact}\n              onArtifactChange={onArtifactChange}\n            />\n            <Spacer width={4} />\n          </span>\n        ))}\n    </RowWrapper>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/CadetWormhole.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\n\nconst CadetWormholeContainer = styled.div`\n  height: 100vh;\n  width: 100vw;\n  padding: 3%;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  background-image: url(/public/img/cadet-wormhole.png);\n  background-attachment: fixed;\n  background-position: top;\n  background-repeat: no-repeat;\n\n  /* This is the height of the background-image */\n  @media (max-height: 1058px) {\n    background-size: contain;\n  }\n`;\n\nexport function CadetWormhole({ imgUrl }: { imgUrl: string }) {\n  return (\n    <CadetWormholeContainer>\n      <img src={imgUrl} />\n    </CadetWormholeContainer>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/DFErrorBoundary.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { Spacer, Underline } from '../Components/CoreUI';\nimport { Red } from '../Components/Text';\n\nexport class DFErrorBoundary extends React.Component<unknown, { hasError: boolean }> {\n  constructor(props: unknown) {\n    super(props);\n    this.state = { hasError: false };\n  }\n\n  static getDerivedStateFromError(_error: Error) {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, _errorInfo: React.ErrorInfo) {\n    console.error(`ui rendering error`);\n    console.error(error);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        <ErrorBoundaryContent>\n          <Red>Error!</Red>\n          <Spacer height={8} />\n          There was an error rendering this UI! Sorry!\n          <Spacer height={8} />\n          If you would like to report this error, please send a screenshot of the developer console\n          to the <Underline>df-feedback</Underline> channel in our discord.\n        </ErrorBoundaryContent>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst ErrorBoundaryContent = styled.div`\n  padding: 8px;\n  max-width: 350px;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/DarkForestTips.tsx",
    "content": "import _ from 'lodash';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { HeaderText, Spacer, TextButton } from '../Components/CoreUI';\nimport dfstyles from '../Styles/dfstyles';\n\nconst TipText = styled.div`\n  max-width: 500px;\n  word-break: keep-all;\n  text-align: justify;\n`;\n\nconst CYCLE_TIPS_INTERVAL = 10 * 1000;\n\nexport function DarkForestTips({\n  tips,\n  title,\n}: {\n  tips: (JSX.Element | string)[];\n  title?: string;\n}) {\n  const [tipIndex, setTipIndex] = useState(0);\n  const [_interval, setIntervalHandle] = useState<ReturnType<typeof setInterval> | undefined>();\n\n  const incrementTipIndex = useCallback(\n    (increment: number, shouldClearInterval = false) => {\n      if (shouldClearInterval) {\n        setIntervalHandle((interval) => {\n          if (interval) {\n            clearInterval(interval);\n          }\n          return undefined;\n        });\n      }\n\n      setTipIndex((tipIndex) => (tipIndex + increment + tips.length) % tips.length);\n    },\n    [tips.length]\n  );\n\n  useEffect(() => {\n    const intervalHandle = setInterval(() => incrementTipIndex(1), CYCLE_TIPS_INTERVAL);\n    setIntervalHandle(intervalHandle);\n    return () => clearInterval(intervalHandle);\n  }, [incrementTipIndex]);\n\n  return (\n    <TipsContainer>\n      <HeaderText style={{ textDecoration: 'none' }}>{title ?? 'Dark Forest Tips'}</HeaderText>{' '}\n      <PrevNextContainer>\n        <TextButton onClick={() => incrementTipIndex(-1, true)}>previous</TextButton>\n        <Spacer width={16} />\n        <TextButton onClick={() => incrementTipIndex(1, true)}>next</TextButton>\n      </PrevNextContainer>\n      <br />\n      <br />\n      <TipText>{tips[tipIndex]}</TipText>\n    </TipsContainer>\n  );\n}\n\nexport function MakeDarkForestTips(tips: string[]) {\n  const shuffledTips = _.shuffle(tips);\n  return <DarkForestTips tips={shuffledTips} />;\n}\n\nconst PrevNextContainer = styled.div`\n  float: right;\n`;\n\nconst TipsContainer = styled.div`\n  margin-bottom: 8px;\n  background-color: ${dfstyles.colors.backgrounddark};\n  width: 400px;\n  height: 250px;\n  padding: 16px;\n  border-radius: 3px;\n  overflow: hidden;\n  border: 1px solid ${dfstyles.colors.border};\n`;\n"
  },
  {
    "path": "src/Frontend/Views/EmojiPicker.tsx",
    "content": "import Picker from 'emoji-picker-react';\nimport React, { useState } from 'react';\nimport styled from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\n\nexport function EmojiPicker({\n  emoji,\n  setEmoji,\n}: {\n  emoji: string | undefined;\n  setEmoji: (emoji: string) => void;\n}) {\n  const [pickerOpen, setPickerOpen] = useState(false);\n\n  return (\n    <EmojiPickerContainer>\n      <SelectedEmoji onClick={() => setPickerOpen((open) => !open)}>\n        {emoji || '\\u00a0'}\n      </SelectedEmoji>\n      {pickerOpen && (\n        <EmojiPickerElementContainer>\n          <Picker\n            disableSearchBar={true}\n            onEmojiClick={(event, emojiObject) => {\n              setEmoji(emojiObject.emoji);\n              setPickerOpen(false);\n            }}\n          />\n        </EmojiPickerElementContainer>\n      )}\n    </EmojiPickerContainer>\n  );\n}\n\nconst SelectedEmoji = styled.div`\n  display: inline-flex;\n  justify-content: center;\n  align-items: center;\n  border: 1px solid ${dfstyles.colors.borderDark};\n  border-radius: 3px;\n  cursor: pointer;\n  font-size: 1.5em;\n  width: 30px;\n  height: 30px;\n  margin-right: 8px;\n\n  &:hover,\n  &:active,\n  &:focus {\n    border: 1px solid ${dfstyles.colors.border};\n  }\n`;\n\nconst EmojiPickerContainer = styled.div`\n  display: inline-block;\n  position: relative;\n`;\n\nconst EmojiPickerElementContainer = styled.div`\n  position: absolute;\n  bottom: 100%;\n  right: 100%;\n\n  .emoji-picker-react {\n    background-color: ${dfstyles.colors.backgroundlight};\n    box-shadow: none;\n  }\n\n  .emoji-group::before {\n    background-color: ${dfstyles.colors.backgroundlight};\n  }\n\n  .emoji-categories {\n    display: none;\n  }\n\n  .active-category-indicator-wrapper {\n    display: none;\n  }\n\n  .emoji-search {\n    display: none;\n  }\n\n  .content-wrapper::before {\n    display: none;\n  }\n`;\n"
  },
  {
    "path": "src/Frontend/Views/EmojiPlanetNotification.tsx",
    "content": "import { Planet, TooltipName } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { getEmojiMessage } from '../../Backend/GameLogic/ArrivalUtils';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { Btn } from '../Components/Btn';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Row } from '../Components/Row';\nimport { Sub } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { EmojiPicker } from './EmojiPicker';\n\nconst TextWrapper = styled.span`\n  width: 120px;\n  font-size: ${dfstyles.fontSizeXS};\n  text-align: center;\n`;\n\nexport function EmojiPlanetNotification({ wrapper }: { wrapper: Wrapper<Planet | undefined> }) {\n  const gameManager = useUIManager().getGameManager();\n  const emojiMessage = getEmojiMessage(wrapper.value);\n  const currentEmoji = emojiMessage?.body?.emoji;\n  const [chosenEmoji, setChosenEmoji] = useState(currentEmoji);\n\n  useEffect(() => {\n    setChosenEmoji(undefined);\n  }, [wrapper?.value?.locationId]);\n\n  if (wrapper.value?.needsServerRefresh) {\n    return (\n      <Row>\n        <Sub style={{ width: '100%' }}>\n          <LoadingSpinner initialText='Loading...' />\n        </Sub>\n      </Row>\n    );\n  } else if (emojiMessage !== undefined && currentEmoji !== undefined) {\n    return (\n      <Row>\n        <Sub>Current emoji: {emojiMessage?.body?.emoji}</Sub>\n        <Btn\n          disabled={wrapper.value?.unconfirmedClearEmoji || wrapper.value?.needsServerRefresh}\n          onClick={() => {\n            if (wrapper.value?.locationId) {\n              gameManager.clearEmoji(wrapper.value.locationId);\n            }\n          }}\n        >\n          <TextWrapper>Clear Emoji</TextWrapper>\n        </Btn>\n      </Row>\n    );\n  } else {\n    const disabled =\n      wrapper.value?.unconfirmedAddEmoji || wrapper.value?.needsServerRefresh || !chosenEmoji;\n\n    return (\n      <Row>\n        <EmojiPicker emoji={chosenEmoji} setEmoji={setChosenEmoji} />\n\n        <TooltipTrigger\n          name={disabled ? TooltipName.Empty : undefined}\n          extraContent='Choose an emoji!'\n        >\n          <Btn\n            disabled={disabled}\n            onClick={() => {\n              if (wrapper.value?.locationId && chosenEmoji) {\n                gameManager.setPlanetEmoji(wrapper.value?.locationId, chosenEmoji);\n              }\n            }}\n          >\n            <TextWrapper>Set Emoji</TextWrapper>\n          </Btn>\n        </TooltipTrigger>\n      </Row>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Views/GameWindowLayout.tsx",
    "content": "import { ModalId, ModalName, Setting } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { BorderlessPane } from '../Components/CoreUI';\nimport {\n  CanvasContainer,\n  CanvasWrapper,\n  MainWindow,\n  UpperLeft,\n  WindowWrapper,\n} from '../Components/GameWindowComponents';\nimport ControllableCanvas from '../Game/ControllableCanvas';\nimport { ArtifactHoverPane } from '../Panes/ArtifactHoverPane';\nimport { CoordsPane } from '../Panes/CoordsPane';\nimport { DiagnosticsPane } from '../Panes/DiagnosticsPane';\nimport { ExplorePane } from '../Panes/ExplorePane';\nimport { HelpPane } from '../Panes/HelpPane';\nimport { HoverPlanetPane } from '../Panes/HoverPlanetPane';\nimport OnboardingPane from '../Panes/OnboardingPane';\nimport { PlanetContextPane } from '../Panes/PlanetContextPane';\nimport { PlanetDexPane } from '../Panes/PlanetDexPane';\nimport { PlayerArtifactsPane } from '../Panes/PlayerArtifactsPane';\nimport { PluginLibraryPane } from '../Panes/PluginLibraryPane';\nimport { PrivatePane } from '../Panes/PrivatePane';\nimport { SettingsPane } from '../Panes/SettingsPane';\nimport { TransactionLogPane } from '../Panes/TransactionLogPane';\nimport { TutorialPane } from '../Panes/TutorialPane';\nimport { TwitterVerifyPane } from '../Panes/TwitterVerifyPane';\nimport { ZoomPane } from '../Panes/ZoomPane';\nimport { useSelectedPlanet, useUIManager } from '../Utils/AppHooks';\nimport { useOnUp } from '../Utils/KeyEmitters';\nimport { useBooleanSetting } from '../Utils/SettingsHooks';\nimport { TOGGLE_DIAGNOSTICS_PANE } from '../Utils/ShortcutConstants';\nimport { NotificationsPane } from './Notifications';\nimport { SidebarPane } from './SidebarPane';\nimport { TopBar } from './TopBar';\n\nexport function GameWindowLayout({\n  terminalVisible,\n  setTerminalVisible,\n}: {\n  terminalVisible: boolean;\n  setTerminalVisible: (visible: boolean) => void;\n}) {\n  const uiManager = useUIManager();\n  const modalManager = uiManager.getModalManager();\n  const modalPositions = modalManager.getModalPositions();\n\n  /**\n   * We use the existence of a window position for a given modal as an indicator\n   * that it should be opened on page load. This is to satisfy the feature of\n   * peristent modal positions across browser sessions for a given account.\n   */\n  const isModalOpen = useCallback(\n    (modalId: ModalId) => {\n      const pos = modalPositions.get(modalId);\n      if (pos) {\n        return pos.state !== 'closed';\n      } else {\n        return false;\n      }\n    },\n    [modalPositions]\n  );\n\n  const [helpVisible, setHelpVisible] = useState<boolean>(isModalOpen(ModalName.Help));\n  const [transactionLogVisible, setTransactionLogVisible] = useState<boolean>(\n    isModalOpen(ModalName.TransactionLog)\n  );\n  const [planetdexVisible, setPlanetdexVisible] = useState<boolean>(\n    isModalOpen(ModalName.PlanetDex)\n  );\n  const [playerArtifactsVisible, setPlayerArtifactsVisible] = useState<boolean>(\n    isModalOpen(ModalName.YourArtifacts)\n  );\n  const [twitterVerifyVisible, setTwitterVerifyVisible] = useState<boolean>(\n    isModalOpen(ModalName.TwitterVerify)\n  );\n  const [settingsVisible, setSettingsVisible] = useState<boolean>(isModalOpen(ModalName.Settings));\n  const [privateVisible, setPrivateVisible] = useState<boolean>(isModalOpen(ModalName.Private));\n  const [pluginsVisible, setPluginsVisible] = useState<boolean>(isModalOpen(ModalName.Plugins));\n  const [diagnosticsVisible, setDiagnosticsVisible] = useState<boolean>(\n    isModalOpen(ModalName.Diagnostics)\n  );\n\n  const [modalsContainer, setModalsContainer] = useState<HTMLDivElement | undefined>();\n  const modalsContainerCB = useCallback((node) => {\n    setModalsContainer(node);\n  }, []);\n  const [onboardingVisible, setOnboardingVisible] = useBooleanSetting(uiManager, Setting.NewPlayer);\n  const tutorialHook = useBooleanSetting(uiManager, Setting.TutorialOpen);\n  const selected = useSelectedPlanet(uiManager).value;\n  const [selectedPlanetVisible, setSelectedPlanetVisible] = useState<boolean>(!!selected);\n\n  const [userTerminalVisibleSetting, setTerminalVisibleSetting] = useBooleanSetting(\n    uiManager,\n    Setting.TerminalVisible\n  );\n\n  useEffect(() => {\n    uiManager.setOverlayContainer(modalsContainer);\n  }, [uiManager, modalsContainer]);\n\n  const account = uiManager.getAccount();\n  useEffect(() => {\n    if (uiManager.getAccount()) {\n      setTerminalVisible(uiManager.getBooleanSetting(Setting.TerminalVisible));\n    }\n  }, [account, uiManager, setTerminalVisible]);\n\n  useEffect(() => {\n    if (userTerminalVisibleSetting !== terminalVisible) {\n      setTerminalVisibleSetting(terminalVisible);\n    }\n  }, [userTerminalVisibleSetting, setTerminalVisibleSetting, terminalVisible]);\n\n  useEffect(() => setSelectedPlanetVisible(!!selected), [selected, setSelectedPlanetVisible]);\n\n  useOnUp(\n    TOGGLE_DIAGNOSTICS_PANE,\n    useCallback(() => {\n      setDiagnosticsVisible((value) => !value);\n    }, [setDiagnosticsVisible])\n  );\n\n  return (\n    <WindowWrapper>\n      <TopBarPaneContainer>\n        <BorderlessPane>\n          <TopBar twitterVerifyHook={[twitterVerifyVisible, setTwitterVerifyVisible]} />\n        </BorderlessPane>\n      </TopBarPaneContainer>\n\n      {/* all modals rendered into here */}\n      <div ref={modalsContainerCB}>\n        <HelpPane visible={helpVisible} onClose={() => setHelpVisible(false)} />\n        <TransactionLogPane\n          visible={transactionLogVisible}\n          onClose={() => setTransactionLogVisible(false)}\n        />\n        <PlanetDexPane visible={planetdexVisible} onClose={() => setPlanetdexVisible(false)} />\n        <TwitterVerifyPane\n          visible={twitterVerifyVisible}\n          onClose={() => setTwitterVerifyVisible(false)}\n        />\n        <SettingsPane\n          ethConnection={uiManager.getEthConnection()}\n          visible={settingsVisible}\n          onClose={() => setSettingsVisible(false)}\n          onOpenPrivate={() => setPrivateVisible(true)}\n        />\n        <PrivatePane visible={privateVisible} onClose={() => setPrivateVisible(false)} />\n        <PlayerArtifactsPane\n          visible={playerArtifactsVisible}\n          onClose={() => setPlayerArtifactsVisible(false)}\n        />\n        <PlanetContextPane\n          visible={selectedPlanetVisible}\n          onClose={() => setSelectedPlanetVisible(false)}\n        />\n        <DiagnosticsPane\n          visible={diagnosticsVisible}\n          onClose={() => setDiagnosticsVisible(false)}\n        />\n        {modalsContainer && (\n          <PluginLibraryPane\n            modalsContainer={modalsContainer}\n            gameUIManager={uiManager}\n            visible={pluginsVisible}\n            onClose={() => setPluginsVisible(false)}\n          />\n        )}\n      </div>\n\n      <OnboardingPane visible={onboardingVisible} onClose={() => setOnboardingVisible(false)} />\n\n      <MainWindow>\n        <CanvasContainer>\n          <UpperLeft>\n            <ZoomPane />\n          </UpperLeft>\n          <SidebarPane\n            transactionLogHook={[transactionLogVisible, setTransactionLogVisible]}\n            settingsHook={[settingsVisible, setSettingsVisible]}\n            helpHook={[helpVisible, setHelpVisible]}\n            pluginsHook={[pluginsVisible, setPluginsVisible]}\n            yourArtifactsHook={[playerArtifactsVisible, setPlayerArtifactsVisible]}\n            planetdexHook={[planetdexVisible, setPlanetdexVisible]}\n          />\n          <CanvasWrapper>\n            <ControllableCanvas />\n          </CanvasWrapper>\n\n          <NotificationsPane />\n          <CoordsPane />\n          <ExplorePane />\n\n          <HoverPlanetPane />\n          <ArtifactHoverPane />\n\n          <TutorialPane tutorialHook={tutorialHook} />\n        </CanvasContainer>\n      </MainWindow>\n    </WindowWrapper>\n  );\n}\n\nconst TopBarPaneContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 100vw;\n  position: absolute;\n  top: 0;\n  left: 0;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/GenericErrorBoundary.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { Red } from '../Components/Text';\n\ninterface GenericErrorBoundaryProps {\n  errorMessage: string;\n}\n\nexport class GenericErrorBoundary extends React.Component<\n  GenericErrorBoundaryProps,\n  { hasError: boolean }\n> {\n  constructor(props: GenericErrorBoundaryProps) {\n    super(props);\n    this.state = { hasError: false };\n  }\n\n  static getDerivedStateFromError(_error: Error) {\n    return { hasError: true };\n  }\n\n  componentDidCatch(error: Error, _errorInfo: React.ErrorInfo) {\n    console.error(`ui rendering error`);\n    console.error(error);\n  }\n\n  render() {\n    if (this.state.hasError) {\n      return (\n        <ErrorBoundaryContent>\n          <Red>{this.props.errorMessage}</Red>\n        </ErrorBoundaryContent>\n      );\n    }\n\n    return this.props.children;\n  }\n}\n\nconst ErrorBoundaryContent = styled.div`\n  padding: 8px;\n  max-width: 350px;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/LandingPageRoundArt.tsx",
    "content": "import React from 'react';\nimport styled from 'styled-components';\nimport { TwitterLink } from '../Components/Labels/Labels';\nimport { Smaller, Text } from '../Components/Text';\n\nexport function LandingPageRoundArt() {\n  return (\n    <Container>\n      <ImgContainer>\n        <LandingPageRoundArtImg src={'/public/round_art/round5.jpg'} />\n        <Smaller>\n          <Text>Art by</Text> <TwitterLink twitter='JannehMoe' />{' '}\n        </Smaller>\n      </ImgContainer>\n    </Container>\n  );\n}\n\nconst Container = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n`;\n\nconst ImgContainer = styled.div`\n  display: inline-block;\n  text-align: right;\n  width: 750px;\n  max-width: 80vw;\n\n  @media only screen and (max-device-width: 1000px) {\n    width: 100%;\n    max-width: 100%;\n    padding: 8px;\n    font-size: 80%;\n  }\n`;\n\nconst LandingPageRoundArtImg = styled.img``;\n"
  },
  {
    "path": "src/Frontend/Views/Leaderboard.tsx",
    "content": "import { ArtifactRarity, Leaderboard } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { Spacer } from '../Components/CoreUI';\nimport { TwitterLink } from '../Components/Labels/Labels';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Red } from '../Components/Text';\nimport { TextPreview } from '../Components/TextPreview';\nimport { RarityColors } from '../Styles/Colors';\nimport dfstyles from '../Styles/dfstyles';\nimport { useLeaderboard } from '../Utils/AppHooks';\nimport { formatDuration } from '../Utils/TimeUtils';\nimport { GenericErrorBoundary } from './GenericErrorBoundary';\nimport { Table } from './Table';\n\nexport function LeadboardDisplay() {\n  const { leaderboard, error } = useLeaderboard();\n\n  const errorMessage = 'Error Loading Leaderboard';\n\n  return (\n    <GenericErrorBoundary errorMessage={errorMessage}>\n      {!leaderboard && !error && <LoadingSpinner initialText={'Loading Leaderboard...'} />}\n      {leaderboard && <LeaderboardBody leaderboard={leaderboard} />}\n      {error && <Red>{errorMessage}</Red>}\n    </GenericErrorBoundary>\n  );\n}\n\nfunction scoreToString(score?: number | null) {\n  if (score === null || score === undefined) {\n    return 'n/a';\n  }\n  score = Math.floor(score);\n  if (score < 10000) {\n    return score + '';\n  }\n\n  return score.toLocaleString();\n}\n\n// pass in either an address, or a twitter handle. this function will render the appropriate\n// component\nfunction playerToEntry(playerStr: string, color: string) {\n  // if this is an address\n  if (playerStr.startsWith('0x') && playerStr.length === 42) {\n    return <TextPreview text={playerStr} focusedWidth={'150px'} unFocusedWidth={'150px'} />;\n  }\n\n  return <TwitterLink twitter={playerStr} color={color} />;\n}\n\nfunction getRankColor([rank, score]: [number, number | undefined]) {\n  if (score === undefined || score === null) {\n    return dfstyles.colors.subtext;\n  }\n\n  if (rank === 0) {\n    return RarityColors[ArtifactRarity.Mythic];\n  }\n\n  if (rank === 1 || rank === 2) {\n    return RarityColors[ArtifactRarity.Legendary];\n  }\n\n  if (rank >= 3 && rank <= 6) {\n    return RarityColors[ArtifactRarity.Epic];\n  }\n\n  if (rank >= 7 && rank <= 14) {\n    return RarityColors[ArtifactRarity.Rare];\n  }\n\n  if (rank >= 15 && rank <= 30) {\n    return dfstyles.colors.dfgreen;\n  }\n\n  if (rank >= 31 && rank <= 62) {\n    return 'white';\n  }\n\n  return dfstyles.colors.subtext;\n}\n\nfunction LeaderboardTable({ rows }: { rows: Array<[string, number | undefined]> }) {\n  return (\n    <TableContainer>\n      <Table\n        alignments={['r', 'l', 'r']}\n        headers={[\n          <Cell key='place'>place</Cell>,\n          <Cell key='player'>player</Cell>,\n          <Cell key='score'>score</Cell>,\n        ]}\n        rows={rows}\n        columns={[\n          (row: [string, number], i) => (\n            <Cell style={{ color: getRankColor([i, row[1]]) }}>\n              {row[1] === undefined || row[1] === null ? 'unranked' : i + 1 + '.'}\n            </Cell>\n          ),\n          (row: [string, number | undefined], i) => {\n            const color = getRankColor([i, row[1]]);\n            return <Cell style={{ color }}>{playerToEntry(row[0], color)}</Cell>;\n          },\n          (row: [string, number], i) => {\n            return (\n              <Cell style={{ color: getRankColor([i, row[1]]) }}>{scoreToString(row[1])}</Cell>\n            );\n          },\n        ]}\n      />\n    </TableContainer>\n  );\n}\n\n// TODO: update this each round, or pull from contract constants\nconst roundEndTimestamp = '2022-03-01T05:00:00.000Z';\nconst roundEndTime = new Date(roundEndTimestamp).getTime();\n\nfunction CountDown() {\n  const [str, setStr] = useState('');\n\n  const update = () => {\n    const timeUntilEndms = roundEndTime - new Date().getTime();\n    if (timeUntilEndms <= 0) {\n      setStr('yes');\n    } else {\n      setStr(formatDuration(timeUntilEndms));\n    }\n  };\n\n  useEffect(() => {\n    const interval = setInterval(() => {\n      update();\n    }, 499);\n\n    update();\n\n    return () => clearInterval(interval);\n  }, []);\n\n  return <>{str}</>;\n}\n\nfunction LeaderboardBody({ leaderboard }: { leaderboard: Leaderboard }) {\n  const rankedPlayers = leaderboard.entries.filter(\n    (entry) => entry.score !== undefined && entry.score > 0\n  );\n\n  leaderboard.entries.sort((a, b) => {\n    if (typeof a.score !== 'number' && typeof b.score !== 'number') {\n      return 0;\n    } else if (typeof a.score !== 'number') {\n      return 1;\n    } else if (typeof b.score !== 'number') {\n      return -1;\n    }\n\n    return b.score - a.score;\n  });\n\n  const rows: [string, number | undefined][] = leaderboard.entries.map((entry) => {\n    if (typeof entry.twitter === 'string') {\n      return [entry.twitter, entry.score];\n    }\n\n    return [entry.ethAddress, entry.score];\n  });\n\n  return (\n    <div>\n      <StatsTableContainer>\n        <StatsTable>\n          <tbody>\n            <tr>\n              <td>round complete</td>\n              <td>\n                <CountDown />\n              </td>\n            </tr>\n            <tr>\n              <td>players</td>\n              <td>{leaderboard.entries.length}</td>\n            </tr>\n            <tr>\n              <td>ranked players</td>\n              <td>{rankedPlayers.length}</td>\n            </tr>\n          </tbody>\n        </StatsTable>\n      </StatsTableContainer>\n      <Spacer height={8} />\n      <LeaderboardTable rows={rows} />\n    </div>\n  );\n}\n\nconst Cell = styled.div`\n  padding: 4px 8px;\n  color: ${dfstyles.colors.text};\n`;\n\nconst TableContainer = styled.div`\n  display: inline-block;\n  border-radius: 2px 2px 0 0px;\n  border-bottom: none;\n  padding: 16px;\n`;\n\nconst StatsTableContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  color: ${dfstyles.colors.text};\n`;\n\nconst StatsTable = styled.table`\n  td {\n    padding: 4px 8px;\n\n    &:first-child {\n      text-align: right;\n      color: ${dfstyles.colors.subtext};\n    }\n\n    &:last-child {\n      text-align: left;\n    }\n  }\n`;\n"
  },
  {
    "path": "src/Frontend/Views/ModalIcon.tsx",
    "content": "import { ModalName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { Hook } from '../../_types/global/GlobalTypes';\nimport { Spacer } from '../Components/CoreUI';\nimport { Icon, IconType } from '../Components/Icons';\nimport { MaybeShortcutButton } from '../Components/MaybeShortcutButton';\n\nconst ModalIconText = styled.span`\n  flex-grow: 1;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: row;\n  height: 26px;\n`;\n\nconst icon = (modal: ModalName): React.ReactNode => {\n  if (modal === ModalName.Help) return <Icon type={IconType.Help} />;\n  else if (modal === ModalName.PlanetDetails) return <Icon type={IconType.Planet} />;\n  else if (modal === ModalName.Leaderboard) return <Icon type={IconType.Leaderboard} />;\n  else if (modal === ModalName.PlanetDex) return <Icon type={IconType.PlanetDex} />;\n  else if (modal === ModalName.UpgradeDetails) return <Icon type={IconType.Upgrade} />;\n  else if (modal === ModalName.TwitterVerify) return <Icon type={IconType.Twitter} />;\n  else if (modal === ModalName.Broadcast) return <Icon type={IconType.Broadcast} />;\n  else if (modal === ModalName.MapShare) return <Icon type={IconType.Share} />;\n  else if (modal === ModalName.ManageAccount) return <Icon type={IconType.Lock} />;\n  else if (modal === ModalName.Hats) return <Icon type={IconType.Hat} />;\n  else if (modal === ModalName.Settings) return <Icon type={IconType.Settings} />;\n  else if (modal === ModalName.Plugins) return <Icon type={IconType.Plugin} />;\n  else if (modal === ModalName.YourArtifacts) return <Icon type={IconType.Artifact} />;\n  else if (modal === ModalName.WithdrawSilver) return <Icon type={IconType.Withdraw} />;\n  else if (modal === ModalName.TransactionLog) return <Icon type={IconType.DoubleArrows} />;\n  return <span>T</span>;\n};\n\n/**\n * A button which allows you to open a modal.\n */\nexport function ModalToggleButton({\n  modal,\n  hook: [_active, setActive],\n  text,\n  style,\n  ...props\n}: {\n  modal: ModalName;\n  hook: Hook<boolean>;\n  text?: string;\n  style?: React.CSSProperties;\n} & React.ComponentProps<typeof MaybeShortcutButton>) {\n  const toggle = () => {\n    setActive((b: boolean) => !b);\n  };\n\n  return (\n    <MaybeShortcutButton {...props} onClick={toggle} onShortcutPressed={toggle}>\n      <ModalIconText style={style}>\n        {icon(modal)}\n        {text !== undefined && (\n          <>\n            <Spacer width={8} />\n            {text}\n          </>\n        )}\n      </ModalIconText>\n    </MaybeShortcutButton>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/ModalPane.tsx",
    "content": "import { ModalId } from '@darkforest_eth/types';\nimport React, { useCallback, useEffect, useLayoutEffect, useMemo, useState } from 'react';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { EmSpacer, Spacer, Title, Truncate } from '../Components/CoreUI';\nimport { PaneProps } from '../Components/GameWindowComponents';\nimport { MaybeShortcutButton } from '../Components/MaybeShortcutButton';\nimport { DarkForestModal, Modal, PositionChangedEvent } from '../Components/Modal';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { MODAL_BACK_SHORTCUT } from '../Utils/ShortcutConstants';\nimport { DFErrorBoundary } from './DFErrorBoundary';\n\nfunction InformationSection({ children, hide }: { children: React.ReactNode; hide: () => void }) {\n  return (\n    <InfoSectionContent>\n      {children}\n      <BtnContainer>\n        <Btn onClick={hide}>close help</Btn>\n      </BtnContainer>\n    </InfoSectionContent>\n  );\n}\n\nconst BtnContainer = styled.div`\n  display: flex;\n  justify-content: flex-end;\n  align-items: center;\n  margin-top: 8px;\n`;\n\nconst InfoSectionContent = styled.div`\n  text-align: justify;\n  color: ${dfstyles.colors.subtext};\n`;\n\nexport type ModalProps = PaneProps & {\n  title: string | React.ReactNode;\n  style?: CSSStyleDeclaration & React.CSSProperties;\n  visible: boolean;\n  onClose: () => void;\n  id: ModalId;\n  hideClose?: boolean;\n  helpContent?: () => React.ReactNode;\n  width?: string;\n  initialPosition?: {\n    x: number;\n    y: number;\n  };\n};\n\n/**\n * A modal has a {@code content}, and also optionally many {@link ModalFrames} pushed on top of it.\n */\nexport interface ModalFrame {\n  title: string;\n  element: () => React.ReactElement;\n  helpContent?: React.ReactElement;\n}\n\n/**\n * @todo Add things like open, close, set position, etc.\n */\nexport interface ModalHandle {\n  push(frame: ModalFrame): void;\n  popAll(): void;\n  pop(): void;\n  id: string;\n  isActive: boolean;\n}\n\nexport function ModalPane({\n  style,\n  children,\n  title,\n  visible,\n  onClose,\n  hideClose,\n  helpContent,\n  width,\n  initialPosition,\n  id,\n}: ModalProps) {\n  const uiManager = useUIManager();\n  const modalManager = uiManager.getModalManager();\n  const windowPositions = modalManager.getModalPositions();\n  const modalPosition = id ? windowPositions.get(id) : undefined;\n  const activeModalId = useEmitterValue(modalManager.activeModalId$, undefined);\n  const isActive = id === activeModalId;\n  const [frames, setFrames] = useState<ModalFrame[]>([]);\n  const [renderedFrame, setRenderedFrame] = useState<undefined | React.ReactElement>();\n  const [renderedFrameHelp, setRenderedFrameHelp] = useState<undefined | React.ReactElement>();\n  const [minimized, setMinimized] = useState(modalPosition?.state === 'minimized');\n  const [modalIndex, setModalIndex] = useState<number>(() => modalManager.getIndex());\n  const push = useCallback(() => {\n    modalManager.activeModalId$.publish(id);\n    setModalIndex(modalManager.getIndex());\n  }, [modalManager, id]);\n  const [showingInformationSection, setShowingInformationSection] = useState(false);\n  const onMouseDown = useCallback(\n    (e: Event & React.MouseEvent<DarkForestModal>) => {\n      push();\n      e.stopPropagation();\n    },\n    [push]\n  );\n\n  const initialPos = modalPosition || initialPosition;\n  const showingHelp = helpContent !== undefined && showingInformationSection;\n\n  const api: ModalHandle = useMemo<ModalHandle>(\n    () => ({\n      pop: () => {\n        setFrames((frames) => {\n          if (frames.length === 0) return frames;\n          frames = [...frames];\n          frames.pop();\n          return frames;\n        });\n      },\n      push: (args: ModalFrame) => {\n        setFrames((frames) => {\n          frames = [...frames];\n          frames.push(args);\n\n          return frames;\n        });\n      },\n      popAll: () => {\n        setFrames([]);\n      },\n      id,\n      isActive,\n    }),\n    [id, isActive]\n  );\n\n  // push to top\n  useLayoutEffect(() => {\n    push();\n  }, [visible, modalManager, push]);\n\n  useEffect(() => {\n    const timeout = setTimeout(() => {\n      const topFrame = frames[frames.length - 1];\n      setRenderedFrame((topFrame && topFrame.element()) || undefined);\n      setRenderedFrameHelp((topFrame && topFrame.helpContent) || undefined);\n    }, 0);\n\n    return () => clearTimeout(timeout);\n  }, [frames, api]);\n\n  const onPositionChanged = useCallback(\n    (evt: PositionChangedEvent) => {\n      if (!id) return;\n      if (visible) {\n        modalManager.setModalPosition(id, {\n          x: evt.coords.x,\n          y: evt.coords.y,\n          state: minimized ? 'minimized' : 'open',\n          modalId: id,\n        });\n      }\n    },\n    [visible, modalManager, minimized, id]\n  );\n\n  useEffect(() => {\n    if (!id) return;\n    if (!visible) {\n      modalManager.setModalState(id, 'closed');\n    }\n  }, [visible, modalManager, id]);\n\n  let content;\n  if (showingHelp) {\n    content = (\n      <InformationSection hide={() => setShowingInformationSection(false)}>\n        {renderedFrameHelp || (helpContent && helpContent())}\n      </InformationSection>\n    );\n  } else if (renderedFrame) {\n    content = <DFErrorBoundary>{renderedFrame}</DFErrorBoundary>;\n  } else {\n    content = (\n      <DFErrorBoundary>{typeof children === 'function' ? children(api) : children}</DFErrorBoundary>\n    );\n  }\n\n  function getFrameTitle(args?: ModalFrame) {\n    if (!args) return undefined;\n    return `${args.title}`;\n  }\n\n  const modalTitleElement = typeof title === 'string' ? title : title(frames.length > 0);\n  const allSubModalTitleElements = [];\n\n  if (frames.length > 0) {\n    allSubModalTitleElements.push(getFrameTitle(frames[frames.length - 1]));\n  }\n\n  if (!visible) {\n    return null;\n  } else {\n    return (\n      <Modal\n        style={style}\n        width={width}\n        minimized={minimized}\n        index={modalIndex}\n        initialX={initialPos?.x}\n        initialY={initialPos?.y}\n        onMouseDown={onMouseDown}\n        onPositionChanged={onPositionChanged}\n      >\n        {frames.length > 0 && (\n          <MaybeShortcutButton\n            slot='title'\n            size='small'\n            onClick={() => api.pop()}\n            onShortcutPressed={() => api.pop()}\n            shortcutKey={MODAL_BACK_SHORTCUT}\n            shortcutText={MODAL_BACK_SHORTCUT}\n          >\n            back\n          </MaybeShortcutButton>\n        )}\n        <Title slot='title'>\n          <Truncate maxWidth={allSubModalTitleElements.length !== 0 ? '50px' : undefined}>\n            {modalTitleElement}\n          </Truncate>\n          {allSubModalTitleElements.length !== 0 && <EmSpacer width={0.5} />}\n          {allSubModalTitleElements}\n        </Title>\n\n        {/* render the 'close' and 'help me' buttons, depending on whether or not they're relevant */}\n        <div slot='title' style={{ marginLeft: '8px', flexShrink: 0 }}>\n          {helpContent !== undefined && !minimized && (\n            <>\n              <Btn size='small' onClick={() => setShowingInformationSection((showing) => !showing)}>\n                help\n              </Btn>\n              <Spacer width={4} />\n            </>\n          )}\n          <Btn size='small' onClick={() => setMinimized((minimized: boolean) => !minimized)}>\n            {minimized ? 'maximize' : 'minimize'}\n          </Btn>\n          {!hideClose && (\n            <>\n              <Spacer width={4} />\n              <Btn size='small' onClick={() => onClose()}>\n                close\n              </Btn>\n            </>\n          )}\n        </div>\n\n        {content}\n      </Modal>\n    );\n  }\n}\n"
  },
  {
    "path": "src/Frontend/Views/NetworkHealth.tsx",
    "content": "import { AutoGasSetting, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport { Spread } from '../Components/CoreUI';\nimport { Sub, Text } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\n\nexport function NetworkHealth() {\n  const uiManager = useUIManager();\n  const networkHealth = useEmitterValue(uiManager.getGameManager().networkHealth$, undefined);\n\n  if (!networkHealth || networkHealth.length === 0) {\n    return <></>;\n  }\n\n  return (\n    <>\n      <br />\n      <TooltipTrigger name={TooltipName.NetworkHealth}>\n        <Spread style={{ width: '100%' }}>\n          {networkHealth &&\n            Object.values(AutoGasSetting)\n              .map((setting) => networkHealth.find((entry) => entry[0] === setting))\n              .map(\n                (entry) =>\n                  entry && (\n                    <NetworkHealthForGasSetting\n                      key={entry[0]}\n                      setting={entry[0]}\n                      confirmationWaitTime={entry[1]}\n                    />\n                  )\n              )}\n        </Spread>\n      </TooltipTrigger>\n    </>\n  );\n}\n\nconst SettingNames = {\n  [AutoGasSetting.Average]: 'avg',\n  [AutoGasSetting.Slow]: 'slo',\n  [AutoGasSetting.Fast]: 'fst',\n};\n\nfunction NetworkHealthForGasSetting({\n  setting,\n  confirmationWaitTime,\n}: {\n  setting: AutoGasSetting;\n  confirmationWaitTime: number;\n}) {\n  return (\n    <Sub>\n      {SettingNames[setting]}: <Text>{(confirmationWaitTime / 1000).toFixed(2)}s</Text>\n    </Sub>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/Notifications.tsx",
    "content": "import { Setting } from '@darkforest_eth/types';\nimport _ from 'lodash';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport NotificationManager, {\n  NotificationInfo,\n  NotificationManagerEvent,\n  NotificationType,\n} from '../Game/NotificationManager';\nimport dfstyles, { snips } from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { DFZIndex } from '../Utils/constants';\n\n/**\n * React component which represents a single notification. Can be hovered over for more info, or\n * clicked on to dismiss.\n */\nfunction Notification({\n  notif,\n  onClick,\n  style,\n}: {\n  notif: NotificationInfo;\n  onClick: () => void;\n  style?: React.CSSProperties;\n}) {\n  const { message, icon } = notif;\n  const [showing, setShowing] = useState<boolean>(false);\n\n  return (\n    <StyledNotification onMouseLeave={() => setShowing(false)}>\n      <NotificationIconContainer\n        onMouseEnter={() => setShowing(true)}\n        onClick={onClick}\n        style={{ ...style, background: notif.color }}\n      >\n        {icon}\n      </NotificationIconContainer>\n      {showing && <NotificationContent>{message}</NotificationContent>}\n    </StyledNotification>\n  );\n}\n\n/**\n * React component in charge of listening for new notifications and displaying them interactively to\n * the user.\n */\nexport function NotificationsPane() {\n  const [notifs, setNotifs] = useState<Array<NotificationInfo>>([]);\n\n  const uiManager = useUIManager();\n\n  // listen for new notifs\n  useEffect(() => {\n    const notifManager = NotificationManager.getInstance();\n\n    const addNotif = (notif: NotificationInfo) => {\n      const notifMove = uiManager.getBooleanSetting(Setting.MoveNotifications);\n\n      if (!notifMove && notif.type === NotificationType.Tx && notif.txData?.methodName === 'move')\n        return;\n\n      setNotifs((arr) => {\n        const newArr = _.clone(arr);\n        for (let i = 0; i < arr.length; i++) {\n          if (arr[i].id === notif.id) {\n            newArr[i] = notif;\n            return newArr;\n          }\n        }\n\n        return newArr.concat([notif]);\n      });\n    };\n\n    const clearNotif = (id: string) => {\n      setNotifs((arr) => arr.filter((n) => n.id !== id));\n    };\n\n    notifManager.on(NotificationManagerEvent.ClearNotification, clearNotif);\n    notifManager.on(NotificationManagerEvent.Notify, addNotif);\n\n    return () => {\n      notifManager.removeListener(NotificationManagerEvent.ClearNotification, clearNotif);\n      notifManager.removeListener(NotificationManagerEvent.Notify, addNotif);\n    };\n  }, [uiManager]);\n\n  // creates a callback for a notif which removes itself\n  const getRemove = (notif: NotificationInfo): (() => void) => {\n    return (): void => {\n      const copy = _.clone(notifs);\n      for (let i = 0; i < copy.length; i++) {\n        if (copy[i].id === notif.id) copy.splice(i, 1);\n      }\n      setNotifs(copy);\n    };\n  };\n\n  return (\n    <NotificationsContainer>\n      {notifs.slice(0, Math.min(10, notifs.length)).map((el, i) => (\n        <Notification\n          notif={el}\n          key={el.id}\n          onClick={getRemove(el)}\n          style={{ opacity: 1 - (i - 2) * 0.13 }}\n        />\n      ))}\n    </NotificationsContainer>\n  );\n}\n\nconst NOTIF_SIZE = '4em';\nconst MARGIN = '8px';\n\nconst StyledNotification = styled.div`\n  margin: ${MARGIN};\n  display: flex;\n  flex-direction: row-reverse;\n  justify-content: flex-start;\n  &:hover {\n    z-index: ${DFZIndex.Tooltip};\n  }\n`;\n\n/**\n * Element which contains the notification-dependent icon. User can hover over this to display more\n * info about the notification.\n */\nconst NotificationIconContainer = styled.div`\n  width: ${NOTIF_SIZE};\n  height: ${NOTIF_SIZE};\n  border-radius: 8px;\n  border: 1px solid ${dfstyles.colors.text};\n  background: ${({ color }) => color || dfstyles.colors.backgroundlighter};\n  overflow: hidden;\n  display: flex;\n  flex-grow: 0;\n  flex-shrink: 0;\n  flex-direction: row;\n  justify-content: space-around;\n  align-items: center;\n`;\n\n/**\n * Element which contains the information which is attached to the notification. Only shown for a\n * notification if the user is hovering over the notification's {@link NotificationIconContainer}.\n */\nconst NotificationContent = styled.div`\n  border-radius: ${snips.roundedBordersWithEdge};\n  min-height: ${NOTIF_SIZE};\n  height: ${NOTIF_SIZE};\n  min-width: 3em;\n  max-width: 50em;\n  overflow: scroll;\n  margin-right: ${MARGIN};\n  background: ${dfstyles.colors.background};\n  padding: 0.5em 1em;\n  border-radius: 2px;\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  align-items: center;\n  z-index: ${DFZIndex.Tooltip};\n`;\n\n/**\n * The element which contains all the notifications\n */\nconst NotificationsContainer = styled.div`\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: ${DFZIndex.Notification};\n`;\n"
  },
  {
    "path": "src/Frontend/Views/Paused.tsx",
    "content": "import { TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport { usePaused } from '../Utils/AppHooks';\n\nexport function Paused() {\n  const paused = usePaused();\n\n  if (!paused) {\n    return <></>;\n  }\n\n  return (\n    <PausedContainer>\n      <TooltipTrigger\n        extraContent={\n          <>\n            The game is currently paused so that everyone can spawn and then start playing at the\n            same time. You can still mine the map, but you can't make any moves.\n          </>\n        }\n        name={TooltipName.Empty}\n      >\n        PAUSED\n      </TooltipTrigger>\n    </PausedContainer>\n  );\n}\n\nconst PausedContainer = styled.div`\n  font-size: 4em;\n  text-align: center;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/PlanetCard.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { getPlanetName } from '@darkforest_eth/procedural';\nimport { Planet, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { StatIdx } from '../../_types/global/GlobalTypes';\nimport { AlignCenterHorizontally, EmSpacer, InlineBlock, SpreadApart } from '../Components/CoreUI';\nimport { Icon, IconType } from '../Components/Icons';\nimport { AccountLabel } from '../Components/Labels/Labels';\nimport {\n  DefenseText,\n  EnergyGrowthText,\n  JunkText,\n  PlanetBiomeTypeLabelAnim,\n  PlanetEnergyLabel,\n  PlanetLevel,\n  PlanetRank,\n  PlanetSilverLabel,\n  RangeText,\n  SilverGrowthText,\n  SpeedText,\n} from '../Components/Labels/PlanetLabels';\nimport { Sub } from '../Components/Text';\nimport { PlanetIcons } from '../Renderers/PlanetscapeRenderer/PlanetIcons';\nimport dfstyles, { snips } from '../Styles/dfstyles';\nimport { useActiveArtifact, usePlanetArtifacts, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { SelectArtifactRow } from './ArtifactRow';\nimport { Halved, PlanetActiveArtifact, RowTip, TimesTwo, TitleBar } from './PlanetCardComponents';\n\nexport function PlanetCardTitle({\n  planet,\n  small,\n}: {\n  planet: Wrapper<Planet | undefined>;\n  small?: boolean;\n}) {\n  if (!planet.value) return <></>;\n  if (small) return <>{getPlanetName(planet.value)}</>;\n\n  return (\n    <AlignCenterHorizontally style={{ width: 'initial', display: 'inline-flex' }}>\n      {getPlanetName(planet.value)}\n      <EmSpacer width={0.5} />\n      <PlanetIcons planet={planet.value} />\n    </AlignCenterHorizontally>\n  );\n}\n\nconst ElevatedContainer = styled.div`\n  ${snips.roundedBordersWithEdge}\n  border-color: ${dfstyles.colors.borderDarker};\n  background-color: ${dfstyles.colors.backgroundlight};\n  margin-top: 8px;\n  margin-bottom: 8px;\n  font-size: 85%;\n`;\n\n/** Preview basic planet information - used in `PlanetContextPane` and `HoverPlanetPane` */\nexport function PlanetCard({\n  planetWrapper: p,\n  standalone,\n}: {\n  planetWrapper: Wrapper<Planet | undefined>;\n  standalone?: boolean;\n}) {\n  const uiManager = useUIManager();\n  const active = useActiveArtifact(p, uiManager);\n  const planet = p.value;\n  const artifacts = usePlanetArtifacts(p, uiManager);\n  const spaceJunkEnabled = uiManager.getSpaceJunkEnabled();\n  const isAbandoning = useEmitterValue(uiManager.isAbandoning$, uiManager.isAbandoning());\n\n  if (!planet || !isLocatable(planet)) return <></>;\n\n  return (\n    <>\n      {standalone && (\n        <TitleBar>\n          <PlanetCardTitle planet={p} />\n        </TitleBar>\n      )}\n      <div style={{ padding: standalone ? '8px' : undefined }}>\n        <AlignCenterHorizontally style={{ justifyContent: 'space-between' }}>\n          <InlineBlock>\n            <PlanetLevel planet={planet} />\n            <EmSpacer width={0.5} />\n            <PlanetRank planet={planet} />\n            <EmSpacer width={0.5} />\n            <PlanetBiomeTypeLabelAnim planet={planet} />\n            <EmSpacer width={0.5} />\n          </InlineBlock>\n        </AlignCenterHorizontally>\n        {active && (\n          <>\n            <EmSpacer height={0.5} />\n            <PlanetActiveArtifact artifact={active} planet={planet} />\n          </>\n        )}\n\n        <ElevatedContainer>\n          <StatRow>\n            <SpreadApart>\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderTop: 'none',\n                  borderLeft: 'none',\n                  width: '50%',\n                }}\n              >\n                <RowTip name={TooltipName.Energy}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.Energy} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <PlanetEnergyLabel planet={planet} />\n                      {planet?.bonus && planet.bonus[StatIdx.EnergyCap] && <TimesTwo />}\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderTop: 'none',\n                  borderRight: 'none',\n                  borderLeft: 'none',\n                  width: '50%',\n                }}\n              >\n                <RowTip name={TooltipName.Silver}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.Silver} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <PlanetSilverLabel planet={planet} />\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n            </SpreadApart>\n          </StatRow>\n          <StatRow>\n            <SpreadApart>\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderTop: 'none',\n                  borderLeft: 'none',\n                  borderBottom: 'none',\n                  width: '50%',\n                }}\n              >\n                <RowTip name={TooltipName.EnergyGrowth}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.EnergyGrowth} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <EnergyGrowthText planet={planet} />\n                      {planet?.bonus && planet.bonus[StatIdx.EnergyGro] && <TimesTwo />}\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n              <div\n                style={{\n                  borderBottom: 'none',\n                  borderTop: 'none',\n                  borderRight: 'none',\n                  width: '50%',\n                }}\n              >\n                <RowTip name={TooltipName.SilverGrowth}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.SilverGrowth} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <SilverGrowthText planet={p.value} />\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n            </SpreadApart>\n          </StatRow>\n\n          <StatRow>\n            <SpreadApart>\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderBottom: 'none',\n                  borderLeft: 'none',\n                  width: spaceJunkEnabled ? '25%' : '34%',\n                }}\n              >\n                <RowTip name={TooltipName.Defense}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.Defense} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <DefenseText planet={planet} />\n                      {planet?.bonus && planet.bonus[StatIdx.Defense] && <TimesTwo />}\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderLeft: 'none',\n                  borderBottom: 'none',\n                  width: spaceJunkEnabled ? '25%' : '33%',\n                }}\n              >\n                <RowTip name={TooltipName.Speed}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.Speed} />\n                    </AlignCenterHorizontally>\n                    <AlignCenterHorizontally>\n                      <SpeedText\n                        planet={planet}\n                        buff={isAbandoning ? uiManager.getSpeedBuff() : undefined}\n                      />\n                      {planet?.bonus && planet.bonus[StatIdx.Speed] && <TimesTwo />}\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n\n              <div\n                style={{\n                  border: `1px solid ${dfstyles.colors.borderDarker}`,\n                  borderLeft: 'none',\n                  borderRight: 'none',\n                  borderBottom: 'none',\n                  width: spaceJunkEnabled ? '25%' : '33%',\n                }}\n              >\n                <RowTip name={TooltipName.Range}>\n                  <SpreadApart>\n                    <AlignCenterHorizontally>\n                      <EmSpacer width={0.5} />\n                      <Icon type={IconType.Range} />\n                    </AlignCenterHorizontally>\n\n                    <AlignCenterHorizontally>\n                      <RangeText\n                        planet={planet}\n                        buff={isAbandoning ? uiManager.getRangeBuff() : undefined}\n                      />\n                      {planet?.bonus && planet.bonus[StatIdx.Range] && <TimesTwo />}\n                      <EmSpacer width={0.5} />\n                    </AlignCenterHorizontally>\n                  </SpreadApart>\n                </RowTip>\n              </div>\n\n              {spaceJunkEnabled && (\n                <div\n                  style={{\n                    border: `1px solid ${dfstyles.colors.borderDarker}`,\n                    borderRight: 'none',\n                    borderBottom: 'none',\n                    width: '25%',\n                  }}\n                >\n                  <RowTip name={TooltipName.SpaceJunk}>\n                    <SpreadApart>\n                      <AlignCenterHorizontally>\n                        <EmSpacer width={0.5} />\n                        <Icon type={IconType.TrashCan} />\n                      </AlignCenterHorizontally>\n\n                      <AlignCenterHorizontally>\n                        <JunkText planet={planet} />\n                        {planet?.bonus && planet.bonus[StatIdx.SpaceJunk] && <Halved />}\n                        <EmSpacer width={0.5} />\n                      </AlignCenterHorizontally>\n                    </SpreadApart>\n                  </RowTip>\n                </div>\n              )}\n            </SpreadApart>\n          </StatRow>\n        </ElevatedContainer>\n\n        {standalone && (\n          <>\n            <SpreadApart>\n              <Sub>owner</Sub>\n              <Sub>\n                <AccountLabel ethAddress={planet.owner} includeAddressIfHasTwitter={true} />\n              </Sub>\n            </SpreadApart>\n            <SelectArtifactRow artifacts={artifacts} />\n          </>\n        )}\n      </div>\n    </>\n  );\n}\n\nconst StatRow = styled(AlignCenterHorizontally)`\n  ${snips.roundedBorders}\n  display: inline-block;\n  box-sizing: border-box;\n  width: 100%;\n\n  /* Set the Icon color to something a little dimmer */\n  --df-icon-color: ${dfstyles.colors.subtext};\n`;\n"
  },
  {
    "path": "src/Frontend/Views/PlanetCardComponents.tsx",
    "content": "import { Artifact, Planet, TooltipName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport {\n  ArtifactBiomeText,\n  ArtifactRarityLabelAnim,\n  ArtifactTypeText,\n} from '../Components/Labels/ArtifactLabels';\nimport { Sub, White } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport dfstyles from '../Styles/dfstyles';\n\nconst BonusStyle = styled.span`\n  color: ${dfstyles.colors.dfgreen};\n  font-size: 0.8em;\n  vertical-align: center;\n  line-height: 1.5em;\n  margin-left: 8px;\n`;\n\nexport const TimesTwo = () => <BonusStyle>x2</BonusStyle>;\nexport const Halved = () => <BonusStyle>%2</BonusStyle>;\n\nexport const RowTip = ({ name, children }: { name: TooltipName; children: React.ReactNode }) => (\n  <TooltipTrigger\n    name={name}\n    style={{ lineHeight: '100%', position: 'relative', top: '0.2em', cursor: 'help' }}\n  >\n    {children}\n  </TooltipTrigger>\n);\n\nexport const TitleBar = styled.div`\n  height: 2em;\n  padding: 0.25em 0.5em;\n  display: flex;\n  flex-direction: row;\n  justify-content: space-between;\n  color: ${dfstyles.colors.subtext};\n  border-bottom: 1px solid ${dfstyles.colors.border};\n`;\n\nconst StyledPlanetActiveArtifact = styled.div<{ planet: Planet | undefined }>`\n  display: flex;\n  flex-direction: row;\n  justify-content: flex-start;\n  align-items: center;\n  color: ${dfstyles.colors.text};\n`;\n\nexport function PlanetActiveArtifact({\n  artifact,\n  planet,\n}: {\n  artifact: Artifact;\n  planet: Planet | undefined;\n}) {\n  return (\n    <StyledPlanetActiveArtifact planet={planet}>\n      <Sub>\n        Active Artifact:{' '}\n        <White>\n          {' '}\n          <ArtifactRarityLabelAnim rarity={artifact.rarity} />{' '}\n          <ArtifactBiomeText artifact={artifact} /> <ArtifactTypeText artifact={artifact} />\n        </White>\n      </Sub>\n    </StyledPlanetActiveArtifact>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/PlanetLink.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { Planet } from '@darkforest_eth/types';\nimport React from 'react';\nimport { Link } from '../Components/CoreUI';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport UIEmitter, { UIEmitterEvent } from '../Utils/UIEmitter';\n\nexport function PlanetLink({ planet, children }: { planet: Planet; children: React.ReactNode }) {\n  const uiManager = useUIManager();\n  const uiEmitter = UIEmitter.getInstance();\n\n  return (\n    <Link\n      color={dfstyles.colors.text}\n      onClick={() => {\n        if (isLocatable(planet)) {\n          uiManager?.setSelectedPlanet(planet);\n          uiEmitter.emit(UIEmitterEvent.CenterPlanet, planet);\n        }\n      }}\n      onMouseEnter={() => {\n        if (isLocatable(planet)) uiManager?.setHoveringOverPlanet(planet, false);\n      }}\n      onMouseLeave={() => {\n        uiManager?.setHoveringOverPlanet(undefined, false);\n      }}\n    >\n      {children}\n    </Link>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/PlanetNotifications.tsx",
    "content": "import { isLocatable } from '@darkforest_eth/gamelogic';\nimport { EthAddress, Planet } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { GameObjects } from '../../Backend/GameLogic/GameObjects';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { AccountLabel } from '../Components/Labels/Labels';\nimport { Row } from '../Components/Row';\nimport { Sub } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\nimport { EmojiPlanetNotification } from './EmojiPlanetNotification';\n\nexport const enum PlanetNotifType {\n  PlanetCanUpgrade,\n  Claimed,\n  DistanceFromCenter,\n  CanAddEmoji,\n}\n\nconst StyledPlanetNotifications = styled.div`\n  font-size: ${dfstyles.fontSizeXS};\n`;\n\nexport function getNotifsForPlanet(\n  planet: Planet | undefined,\n  account: EthAddress | undefined\n): PlanetNotifType[] {\n  const notifs: PlanetNotifType[] = [];\n  if (!planet) return notifs;\n\n  if (planet?.owner === account && account !== undefined) {\n    if (GameObjects.planetCanUpgrade(planet)) notifs.push(PlanetNotifType.PlanetCanUpgrade);\n    if (process.env.DF_WEBSERVER_URL) notifs.push(PlanetNotifType.CanAddEmoji);\n  }\n\n  return notifs;\n}\n\nfunction EmojiRow({ wrapper }: { wrapper: Wrapper<Planet | undefined> }) {\n  return <EmojiPlanetNotification wrapper={wrapper} />;\n}\n\nconst PlanetCanUpgradeRow = () => (\n  <Row>\n    <Sub>This planet can upgrade!</Sub>\n  </Row>\n);\n\nexport const DistanceFromCenterRow = ({ planet }: { planet: Wrapper<Planet | undefined> }) =>\n  planet.value && isLocatable(planet.value) ? (\n    <Row>\n      <Sub>\n        Distance From Center:{' '}\n        {Math.floor(\n          Math.sqrt(\n            planet.value.location.coords.x ** 2 + planet.value.location.coords.y ** 2 + 0.001\n          )\n        ).toLocaleString()}\n      </Sub>\n    </Row>\n  ) : (\n    <Sub>Unclaimed</Sub>\n  );\n\nexport const PlanetClaimedRow = ({ planet }: { planet: Wrapper<Planet | undefined> }) =>\n  planet.value?.claimer ? (\n    <Row>\n      <Sub>\n        Claimed by{' '}\n        <AccountLabel ethAddress={planet.value?.claimer} includeAddressIfHasTwitter={true} />\n      </Sub>\n    </Row>\n  ) : (\n    <Sub>Unclaimed</Sub>\n  );\n\nfunction renderNotification(notif: PlanetNotifType, planet: Wrapper<Planet | undefined>) {\n  switch (notif) {\n    case PlanetNotifType.PlanetCanUpgrade:\n      return <PlanetCanUpgradeRow />;\n    case PlanetNotifType.CanAddEmoji:\n      return <EmojiRow wrapper={planet} key={notif + (planet.value?.locationId + '')} />;\n    case PlanetNotifType.Claimed:\n      return <PlanetClaimedRow key={notif + (planet.value?.locationId + '')} planet={planet} />;\n    case PlanetNotifType.DistanceFromCenter:\n      return <DistanceFromCenterRow planet={planet} />;\n    default:\n      return null;\n  }\n}\n\nexport function PlanetNotifications({\n  notifs,\n  planet,\n}: {\n  notifs: PlanetNotifType[];\n  planet: Wrapper<Planet | undefined>;\n}) {\n  return (\n    <StyledPlanetNotifications>\n      {notifs.map((notif, i) => (\n        <div key={i}>{renderNotification(notif, planet)}</div>\n      ))}\n    </StyledPlanetNotifications>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/SendResources.tsx",
    "content": "import { formatNumber, isSpaceShip } from '@darkforest_eth/gamelogic';\nimport { isUnconfirmedMoveTx, isUnconfirmedReleaseTx } from '@darkforest_eth/serde';\nimport { Artifact, artifactNameFromArtifact, Planet, TooltipName } from '@darkforest_eth/types';\nimport React, { useCallback } from 'react';\nimport styled from 'styled-components';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { StatIdx } from '../../_types/global/GlobalTypes';\nimport { Btn } from '../Components/Btn';\nimport { Icon, IconType } from '../Components/Icons';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { MaybeShortcutButton } from '../Components/MaybeShortcutButton';\nimport { Row } from '../Components/Row';\nimport { Slider } from '../Components/Slider';\nimport { LongDash, Subber } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport dfstyles from '../Styles/dfstyles';\nimport { useAccount, usePlanetInactiveArtifacts, useUIManager } from '../Utils/AppHooks';\nimport { useEmitterValue } from '../Utils/EmitterHooks';\nimport { useOnUp } from '../Utils/KeyEmitters';\nimport { TOGGLE_ABANDON, TOGGLE_SEND } from '../Utils/ShortcutConstants';\nimport { SelectArtifactRow } from './ArtifactRow';\n\nconst StyledSendResources = styled.div`\n  display: flex;\n  flex-direction: column;\n  gap: 8px;\n`;\n\nconst StyledShowPercent = styled.div`\n  display: inline-block;\n\n  & > span:first-child {\n    width: 3em;\n    text-align: right;\n    margin-right: 1em;\n  }\n\n  & > span:last-child {\n    color: ${dfstyles.colors.subtext};\n    & > span {\n      ${dfstyles.prefabs.noselect};\n      &:hover {\n        color: ${dfstyles.colors.text};\n        cursor: pointer;\n      }\n      &:first-child {\n        margin-right: 0.5em;\n      }\n    }\n  }\n`;\nfunction ShowPercent({ value, setValue }: { value: number; setValue: (x: number) => void }) {\n  return (\n    <StyledShowPercent>\n      <span>{value}%</span>\n      <span>\n        <span onClick={() => setValue(value - 1)}>\n          <LongDash />\n        </span>\n        <span onClick={() => setValue(value + 1)}>+</span>\n      </span>\n    </StyledShowPercent>\n  );\n}\n\nconst ResourceRowDetails = styled.div`\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n`;\n\nfunction ResourceBar({\n  isSilver,\n  selected,\n  value,\n  setValue,\n  disabled,\n}: {\n  isSilver?: boolean;\n  selected: Planet | undefined;\n  value: number;\n  setValue: (x: number) => void;\n  disabled?: boolean;\n}) {\n  const getResource = useCallback(\n    (val: number) => {\n      if (!selected) return '';\n      const resource = isSilver ? selected.silver : selected.energy;\n      return formatNumber((val / 100) * resource);\n    },\n    [selected, isSilver]\n  );\n\n  return (\n    <>\n      <Row>\n        <ResourceRowDetails>\n          <Icon type={isSilver ? IconType.Silver : IconType.Energy} />\n          {getResource(value)}\n          <Subber>{isSilver ? 'silver' : 'energy'}</Subber>\n        </ResourceRowDetails>\n        <ShowPercent value={value} setValue={setValue} />\n      </Row>\n      <Slider\n        variant='filled'\n        labelVisibility='none'\n        min={0}\n        max={100}\n        value={value}\n        step={1}\n        disabled={disabled}\n        onChange={(e: Event & React.ChangeEvent<HTMLInputElement>) => {\n          setValue(parseInt(e.target.value, 10));\n        }}\n      />\n    </>\n  );\n}\n\nfunction AbandonButton({\n  planet,\n  abandoning,\n  toggleAbandoning,\n  disabled,\n}: {\n  planet?: Planet;\n  abandoning: boolean;\n  toggleAbandoning: () => void;\n  disabled?: boolean;\n}) {\n  const uiManager = useUIManager();\n\n  if (!planet) return null;\n\n  let junk = uiManager.getDefaultSpaceJunkForPlanetLevel(planet?.planetLevel);\n  if (planet.bonus[StatIdx.SpaceJunk]) junk /= 2;\n  /* Explicitly avoid binding to `onShortcutPressed` so we can support sending on subpanes */\n  return (\n    <MaybeShortcutButton\n      size='stretch'\n      active={abandoning}\n      onClick={toggleAbandoning}\n      shortcutKey={TOGGLE_ABANDON}\n      shortcutText={TOGGLE_ABANDON}\n      disabled={planet.isHomePlanet || disabled}\n    >\n      <TooltipTrigger name={TooltipName.Abandon}>\n        {abandoning ? 'Abandoning' : `Abandon Planet (-${junk}) space junk`}\n      </TooltipTrigger>\n    </MaybeShortcutButton>\n  );\n}\n\nfunction SendRow({\n  toggleSending,\n  artifact,\n  sending,\n  abandoning,\n  disabled = false,\n}: {\n  toggleSending: () => void;\n  artifact: Artifact | undefined;\n  sending: boolean;\n  abandoning?: boolean;\n  disabled?: boolean;\n}) {\n  let content = 'Send';\n  if (artifact) {\n    const artifactName = artifactNameFromArtifact(artifact);\n    if (isSpaceShip(artifact.artifactType)) {\n      // Call it \"Move\" with a spaceship, instead of \"Send\"\n      content = `Move ${artifactName}`;\n    } else {\n      // Only add the \"+\" if we are sending Energy & Artifact\n      content += ` + ${artifactName}`;\n    }\n  }\n  if (abandoning) {\n    content += ' and Abandon';\n  }\n  /* Explicitly avoid binding to `onShortcutPressed` so we can support sending on subpanes */\n  return (\n    <MaybeShortcutButton\n      size='stretch'\n      onClick={toggleSending}\n      active={sending}\n      shortcutKey={TOGGLE_SEND}\n      shortcutText={TOGGLE_SEND}\n      disabled={disabled}\n    >\n      {content}\n    </MaybeShortcutButton>\n  );\n}\n\nexport function SendResources({\n  planetWrapper: p,\n  onToggleSendForces,\n  onToggleAbandon,\n}: {\n  planetWrapper: Wrapper<Planet | undefined>;\n  onToggleSendForces: () => void;\n  onToggleAbandon: () => void;\n}) {\n  const uiManager = useUIManager();\n  const account = useAccount(uiManager);\n  const owned = p.value?.owner === account;\n  const locationId = p?.value?.locationId;\n\n  const isSendingShip = uiManager.isSendingShip(locationId);\n\n  const isAbandoning = useEmitterValue(uiManager.isAbandoning$, false);\n  const isSendingForces = useEmitterValue(uiManager.isSending$, false);\n  const energySending = uiManager.getForcesSending(locationId);\n  const silverSending = uiManager.getSilverSending(locationId);\n  const artifactSending = uiManager.getArtifactSending(locationId);\n\n  const disableSliders = isSendingShip || isAbandoning;\n\n  const updateEnergySending = useCallback(\n    (energyPercent) => {\n      if (!locationId) return;\n      uiManager.setForcesSending(locationId, energyPercent);\n    },\n    [uiManager, locationId]\n  );\n\n  const updateSilverSending = useCallback(\n    (silverPercent) => {\n      if (!locationId) return;\n      uiManager.setSilverSending(locationId, silverPercent);\n    },\n    [uiManager, locationId]\n  );\n\n  const updateArtifactSending = useCallback(\n    (sendArtifact) => {\n      if (!locationId) return;\n      uiManager.setArtifactSending(locationId, sendArtifact);\n    },\n    [uiManager, locationId]\n  );\n\n  // this variable is an array of 10 elements. each element is a key. whenever the user presses a\n  // key, we set the amount of energy that we're sending to be proportional to how late in the array\n  // that key is\n  const energyShortcuts = '1234567890'.split('');\n\n  // same as above, except for silver\n  const silverShortcuts = '!@#$%^&*()'.split('');\n\n  // for each of the above keys, we set up a listener that is triggered whenever that key is\n  // pressed, and sets the corresponding resource sending amount\n  for (let i = 0; i < energyShortcuts.length; i++) {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useOnUp(energyShortcuts[i], () => updateEnergySending((i + 1) * 10), [updateEnergySending]);\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useOnUp(silverShortcuts[i], () => updateSilverSending((i + 1) * 10), [updateSilverSending]);\n  }\n\n  useOnUp(\n    '-',\n    () => {\n      updateEnergySending(uiManager.getForcesSending(locationId) - 10);\n    },\n    [uiManager, locationId, updateEnergySending]\n  );\n  useOnUp(\n    '=',\n    () => {\n      updateEnergySending(uiManager.getForcesSending(locationId) + 10);\n    },\n    [uiManager, locationId, updateEnergySending]\n  );\n  useOnUp(\n    '_',\n    () => {\n      updateSilverSending(uiManager.getSilverSending(locationId) - 10);\n    },\n    [uiManager, locationId, updateSilverSending]\n  );\n  useOnUp(\n    '+',\n    () => {\n      updateSilverSending(uiManager.getSilverSending(locationId) + 10);\n    },\n    [uiManager, locationId, updateSilverSending]\n  );\n\n  const artifacts = usePlanetInactiveArtifacts(p, uiManager);\n  const spaceshipsYouOwn = artifacts.filter(\n    (a) => isSpaceShip(a.artifactType) && a.controller === account\n  );\n\n  let abandonRow;\n  if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedReleaseTx)) {\n    abandonRow = (\n      <Btn size='stretch' disabled>\n        <LoadingSpinner initialText='Abandoning...' />\n      </Btn>\n    );\n  } else if (p.value && !p.value.destroyed) {\n    abandonRow = (\n      <AbandonButton\n        planet={p.value}\n        abandoning={isAbandoning && !isSendingShip}\n        toggleAbandoning={onToggleAbandon}\n        disabled={isSendingShip}\n      />\n    );\n  }\n\n  let sendRow;\n  if (p.value && p.value.transactions?.hasTransaction(isUnconfirmedMoveTx)) {\n    sendRow = (\n      <Btn size='stretch' disabled>\n        <LoadingSpinner initialText={isSendingShip ? 'Moving...' : 'Sending...'} />\n      </Btn>\n    );\n  } else {\n    const isDisabled = (p.value?.destroyed || !owned) && !isSendingShip;\n    sendRow = (\n      <SendRow\n        artifact={artifactSending}\n        toggleSending={onToggleSendForces}\n        sending={isSendingForces}\n        disabled={isDisabled}\n      />\n    );\n  }\n\n  return (\n    <StyledSendResources>\n      {owned && !p.value?.destroyed && (\n        <>\n          <ResourceBar\n            selected={p.value}\n            value={energySending}\n            setValue={updateEnergySending}\n            disabled={disableSliders}\n          />\n          {p.value && p.value.silver > 0 && (\n            <ResourceBar\n              selected={p.value}\n              value={silverSending}\n              setValue={updateSilverSending}\n              disabled={disableSliders}\n              isSilver\n            />\n          )}\n        </>\n      )}\n      {p.value && artifacts.length > 0 && (\n        <SelectArtifactRow\n          artifacts={artifacts}\n          onArtifactChange={updateArtifactSending}\n          selectedArtifact={artifactSending}\n        />\n      )}\n      {spaceshipsYouOwn.length > 0 || owned ? sendRow : null}\n\n      {uiManager.getSpaceJunkEnabled() && owned ? abandonRow : null}\n    </StyledSendResources>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/Share.tsx",
    "content": "import { EthConnection } from '@darkforest_eth/network';\nimport React, { ReactNode, useEffect, useRef, useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport { Account, getAccounts } from '../../Backend/Network/AccountManager';\nimport { getEthConnection } from '../../Backend/Network/Blockchain';\nimport ReaderDataStore from '../../Backend/Storage/ReaderDataStore';\nimport LandingPageCanvas from '../Renderers/LandingPageCanvas';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\nimport { TerminalHandle } from './Terminal';\n\nconst ShareWrapper = styled.div`\n  width: 100%;\n  height: 100%;\n\n  & p {\n    margin: 0.5em 0;\n    & a {\n      color: ${dfstyles.colors.dfblue};\n      &:hover {\n        text-decoration: underline;\n      }\n    }\n  }\n`;\n\nconst OnTop = styled.div`\n  width: 100%;\n  height: 100%;\n  position: absolute;\n  z-index: 2;\n`;\n\nconst AddressChooserContainer = styled.div`\n  width: 500px;\n  background-color: ${dfstyles.colors.text};\n  color: black;\n  padding: 8px;\n  margin: 4px;\n`;\n\nconst AddressOption = styled.div`\n  ${({ selected }: { selected: boolean }) => css`\n    background-color: ${dfstyles.colors.text};\n    cursor: pointer;\n    display: block;\n    margin: 3px;\n    font-weight: ${selected ? 'bold' : 'unset'};\n\n    &:hover {\n      text-decoration: underline;\n    }\n  `}\n`;\n\nexport interface ShareProps<T> {\n  load: (store: ReaderDataStore) => Promise<T>;\n  children: (state: T | undefined, loading: boolean, error: Error | undefined) => ReactNode;\n}\n\n/**\n * Helper component that allows you to load data from the contract, as if it was\n * viewed from a particular account. Allows you to switch accounts. Just pass in:\n *\n * 1) a function that loads the data you want, given a [[ReaderDataStore]]\n * 2) a function that renders the given data with React\n *\n * ... and this component will take care of loading what you want.\n */\nexport function Share<T>(props: ShareProps<T>) {\n  const terminalHandle = useRef<TerminalHandle | undefined>();\n  const [ethConnection, setEthConnection] = useState<EthConnection | undefined>();\n  const knownAccounts = [undefined, ...getAccounts()];\n  const [currentAccount, setCurrentAccount] = useState<Account | undefined>(knownAccounts[0]);\n  const [store, setStore] = useState<ReaderDataStore | undefined>();\n  const [state, setState] = useState<T>();\n  const [error, setError] = useState<Error>();\n  const [loading, setLoading] = useState<boolean>(false);\n  const uiManager = useUIManager();\n  const contractAddress = uiManager.getContractAddress();\n\n  const selectAccount = (idx: number) => () => {\n    setCurrentAccount(knownAccounts[idx]);\n  };\n\n  useEffect(() => {\n    getEthConnection()\n      .then((ethConnection) => {\n        setEthConnection(ethConnection);\n      })\n      .catch(console.error);\n  }, []);\n\n  useEffect(() => {\n    if (\n      terminalHandle.current &&\n      !loading &&\n      (!store || store?.getViewer() !== currentAccount) &&\n      ethConnection\n    ) {\n      store?.destroy();\n      setStore(undefined);\n      setLoading(true);\n\n      const config = {\n        connection: ethConnection,\n        viewer: currentAccount?.address,\n        contractAddress,\n      };\n\n      ReaderDataStore.create(config).then(async (store) => {\n        setStore(store);\n\n        try {\n          setState(await props.load(store));\n        } catch (e) {\n          setError(e);\n        }\n\n        setLoading(false);\n      });\n    }\n  }, [store, currentAccount, loading, ethConnection, props, contractAddress]);\n\n  return (\n    <ShareWrapper>\n      <LandingPageCanvas />\n      <OnTop>\n        <AddressChooserContainer>\n          <p>view as...</p>\n          {knownAccounts.map((addr, i) => (\n            <AddressOption onClick={selectAccount(i)} key={i} selected={addr === currentAccount}>\n              {addr || 'anonymous'}\n            </AddressOption>\n          ))}\n        </AddressChooserContainer>\n        {props.children(state, loading, error)}\n      </OnTop>\n    </ShareWrapper>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/SidebarPane.tsx",
    "content": "import { ModalName } from '@darkforest_eth/types';\nimport React, { useState } from 'react';\nimport styled from 'styled-components';\nimport { Hook } from '../../_types/global/GlobalTypes';\nimport { BorderlessPane, EmSpacer } from '../Components/CoreUI';\nimport { DFZIndex } from '../Utils/constants';\nimport {\n  TOGGLE_HELP_PANE,\n  TOGGLE_PLUGINS_PANE,\n  TOGGLE_SETTINGS_PANE,\n  TOGGLE_TRANSACTIONS_PANE,\n  TOGGLE_YOUR_ARTIFACTS_PANE,\n  TOGGLE_YOUR_PLANETS_DEX_PANE,\n} from '../Utils/ShortcutConstants';\nimport { ModalToggleButton } from './ModalIcon';\n\nexport function SidebarPane({\n  settingsHook,\n  helpHook,\n  pluginsHook,\n  yourArtifactsHook,\n  planetdexHook,\n  transactionLogHook,\n}: {\n  settingsHook: Hook<boolean>;\n  helpHook: Hook<boolean>;\n  pluginsHook: Hook<boolean>;\n  yourArtifactsHook: Hook<boolean>;\n  planetdexHook: Hook<boolean>;\n  transactionLogHook: Hook<boolean>;\n}) {\n  const [sidebarHovered, setSidebarHovered] = useState<boolean>(false);\n\n  return (\n    <WindowTogglesPaneContainer\n      onMouseEnter={() => setSidebarHovered(true)}\n      onMouseLeave={() => setSidebarHovered(false)}\n    >\n      <BorderlessPane style={{ zIndex: sidebarHovered ? DFZIndex.Tooltip : undefined }}>\n        <ModalToggleButton\n          modal={ModalName.Settings}\n          hook={settingsHook}\n          text={sidebarHovered ? 'Settings' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_SETTINGS_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_SETTINGS_PANE : undefined}\n        />\n        <EmSpacer height={0.5} />\n        <ModalToggleButton\n          modal={ModalName.Help}\n          hook={helpHook}\n          text={sidebarHovered ? 'Help' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_HELP_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_HELP_PANE : undefined}\n        />\n        <EmSpacer height={0.5} />\n        <ModalToggleButton\n          modal={ModalName.Plugins}\n          hook={pluginsHook}\n          text={sidebarHovered ? 'Plugins' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_PLUGINS_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_PLUGINS_PANE : undefined}\n        />\n        <EmSpacer height={0.5} />\n        <ModalToggleButton\n          modal={ModalName.YourArtifacts}\n          hook={yourArtifactsHook}\n          text={sidebarHovered ? 'Your Inventory' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_YOUR_ARTIFACTS_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_YOUR_ARTIFACTS_PANE : undefined}\n        />\n        <EmSpacer height={0.5} />\n        <ModalToggleButton\n          modal={ModalName.PlanetDex}\n          hook={planetdexHook}\n          text={sidebarHovered ? 'Your Planets' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_YOUR_PLANETS_DEX_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_YOUR_PLANETS_DEX_PANE : undefined}\n        />\n        <EmSpacer height={0.5} />\n        <ModalToggleButton\n          modal={ModalName.TransactionLog}\n          hook={transactionLogHook}\n          text={sidebarHovered ? 'Transaction Log' : undefined}\n          size='stretch'\n          shortcutKey={TOGGLE_TRANSACTIONS_PANE}\n          shortcutText={sidebarHovered ? TOGGLE_TRANSACTIONS_PANE : undefined}\n        />\n      </BorderlessPane>\n    </WindowTogglesPaneContainer>\n  );\n}\n\nconst WindowTogglesPaneContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100vh;\n  position: absolute;\n  top: 0;\n  left: 0;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/SortableTable.tsx",
    "content": "import React, { useCallback, useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport dfstyles from '../Styles/dfstyles';\nimport { Table } from './Table';\n\nconst TableCell = styled.div`\n  padding: 2px 4px;\n`;\n\nconst Header = styled(TableCell)`\n  ${({ isActive, isReverse }: { isActive: boolean; isReverse: boolean }) => css`\n    padding: 2px 4px;\n    font-weight: normal;\n    color: ${dfstyles.colors.text};\n    user-select: none;\n    cursor: pointer;\n    ${isActive && 'text-decoration: underline;'}\n    ${isActive && 'font-weight: bold;'}\n    ${isReverse && 'transform: scaleY(-1);'}\n\n    &:hover {\n      text-decoration: underline;\n    }\n  `}\n`;\n\nexport function SortableTable<T>({\n  rows,\n  headers,\n  columns,\n  sortFunctions,\n  alignments,\n  paginated,\n}: {\n  rows: T[];\n  headers: React.ReactNode[];\n  columns: Array<(t: T, i: number) => React.ReactNode>;\n  sortFunctions: Array<(left: T, right: T) => number>;\n  alignments?: Array<'r' | 'c' | 'l'>;\n  paginated?: boolean;\n}) {\n  const [sortByColumn, setSortByColumn] = useState<number | undefined>(undefined);\n  const [reverse, setReverse] = useState(false);\n  const sortFn = sortByColumn !== undefined ? sortFunctions[sortByColumn] : undefined;\n  const sortedRows = [...rows];\n\n  if (sortFn !== undefined) {\n    sortedRows.sort((a, b) => {\n      if (reverse) {\n        return sortFn(b, a);\n      } else {\n        return sortFn(a, b);\n      }\n    });\n  }\n\n  // when you click on a column, cycle between three states:\n  // 1) sort by that column\n  // 2) sort by the reverse of that column\n  // 3) sort by nothing\n  const onColumnTitleClicked = useCallback(\n    (columnIndex: number) => {\n      if (sortByColumn === columnIndex) {\n        if (reverse) {\n          setReverse(false);\n          setSortByColumn(undefined);\n        } else {\n          setReverse(true);\n        }\n      } else {\n        setSortByColumn(columnIndex);\n        setReverse(false);\n      }\n    },\n    [sortByColumn, reverse]\n  );\n\n  return (\n    <Table\n      paginated={paginated}\n      headerStyle={{\n        backgroundColor: dfstyles.colors.background,\n        position: 'sticky',\n        top: 0,\n      }}\n      rows={sortedRows}\n      headers={headers.map((originalHeader, i) => (\n        <Header\n          key={i}\n          onClick={() => onColumnTitleClicked(i)}\n          isActive={sortByColumn === i}\n          isReverse={reverse && sortByColumn === i}\n        >\n          {originalHeader}\n        </Header>\n      ))}\n      columns={columns.map(\n        (originalColumn) =>\n          function Column(t, i) {\n            return <TableCell>{originalColumn(t, i)}</TableCell>;\n          }\n      )}\n      alignments={alignments}\n    />\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/TabbedView.tsx",
    "content": "import React, { useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport { Spacer } from '../Components/CoreUI';\nimport dfstyles from '../Styles/dfstyles';\n\n/**\n * This component allows you to render several tabs of content. Each tab can be selected for viewing\n * by clicking on its corresponding tab button. Useful for displaying lots of slightly different but\n * related information to the user.\n */\nexport function TabbedView({\n  tabTitles,\n  tabContents,\n  style,\n}: {\n  tabTitles: string[];\n  tabContents: (tabIndex: number) => React.ReactNode;\n  style?: React.CSSProperties;\n}) {\n  const [selectedTabIndex, setSelectedTabIndex] = useState(0);\n\n  return (\n    <div style={style}>\n      <TabButtonContainer>\n        {tabTitles.map((title, i) => (\n          <TabButton\n            key={i}\n            active={i === selectedTabIndex}\n            onClick={() => {\n              setSelectedTabIndex(i);\n            }}\n          >\n            {title}\n          </TabButton>\n        ))}\n      </TabButtonContainer>\n      <Spacer height={8} />\n      {tabContents(selectedTabIndex)}\n    </div>\n  );\n}\n\nconst TabButton = styled.div<{ active: boolean }>`\n  ${({ active }: { active: boolean }) => css`\n    color: ${dfstyles.colors.subtext};\n    text-decoration: underline;\n    border-radius: 3px;\n    border: 1px solid ${dfstyles.colors.borderDarkest};\n    padding: 4px 8px;\n    margin-right: 4px;\n    margin-left: 4px;\n    flex-grow: 1;\n    text-align: center;\n    cursor: pointer;\n    user-select: none;\n\n    &:first-child {\n      margin-left: 0;\n    }\n\n    &:last-child {\n      margin-right: 0;\n    }\n\n    &:hover {\n      color: ${dfstyles.colors.text};\n      background-color: ${dfstyles.colors.backgroundlighter};\n\n      ${active &&\n      css`\n        color: ${dfstyles.colors.text};\n        background-color: ${dfstyles.colors.dfgreendark};\n      `}\n    }\n\n    ${active &&\n    css`\n      cursor: default;\n      color: ${dfstyles.colors.text};\n      background-color: ${dfstyles.colors.dfgreendark};\n    `}\n  `}\n`;\n\nconst TabButtonContainer = styled.div`\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  flex-direction: row;\n`;\n"
  },
  {
    "path": "src/Frontend/Views/Table.tsx",
    "content": "import React, { CSSProperties, useState } from 'react';\nimport styled from 'styled-components';\nimport { Btn } from '../Components/Btn';\nimport { Spacer } from '../Components/CoreUI';\nimport dfstyles from '../Styles/dfstyles';\n\nconst TableElement = styled.table`\n  width: 100%;\n  overflow-y: scroll;\n  scrollbar-width: initial;\n  border-radius: ${dfstyles.borderRadius};\n`;\n\nconst ScrollableBody = styled.tbody`\n  width: 100%;\n`;\n\nconst AlignmentOptions: { [key: string]: CSSProperties['textAlign'] } = {\n  r: 'right',\n  l: 'left',\n  c: 'center',\n};\n\nconst PaginationContainer = styled.div`\n  width: 100%;\n\n  display: flex;\n  flex-direction: row;\n  justify-content: space-around;\n`;\n\n/**\n * React api for creating tables.\n * @param rows - rows of an arbitrary type\n * @param headers - required (for now) array of strings that head each column\n * @param columns - functions, one per column, that convert a row into the react representation of\n * that row's column's value.\n * @param alignments - optional, one per column, specifies that the text-alignment in that cell is\n * either right, center, or left, represented by the characters 'r', 'c', and 'l'\n */\nexport function Table<T>({\n  rows,\n  headers,\n  columns,\n  alignments,\n  headerStyle,\n  paginated,\n}: {\n  rows: T[];\n  headers: React.ReactNode[];\n  columns: Array<(t: T, i: number) => React.ReactNode>;\n  alignments?: Array<'r' | 'c' | 'l'>;\n  headerStyle?: React.CSSProperties;\n  paginated?: boolean;\n}) {\n  const itemsPerPage = 10;\n  const [page, setPage] = useState(0);\n  const visibleRows = paginated\n    ? rows.slice(page * itemsPerPage, page * itemsPerPage + itemsPerPage)\n    : rows;\n\n  return (\n    <>\n      <TableElement>\n        <thead style={headerStyle}>\n          <tr>\n            {headers.map((h: string, colIdx: number) => (\n              <th\n                key={colIdx}\n                style={(alignments && { textAlign: AlignmentOptions[alignments[colIdx]] }) || {}}\n              >\n                {h}\n              </th>\n            ))}\n          </tr>\n        </thead>\n\n        <ScrollableBody>\n          {visibleRows.map((row: T, rowIdx: number) => (\n            <tr key={rowIdx}>\n              {columns.map((column, colIdx) => (\n                <td\n                  key={colIdx}\n                  style={(alignments && { textAlign: AlignmentOptions[alignments[colIdx]] }) || {}}\n                >\n                  {column(row, rowIdx)}\n                </td>\n              ))}\n            </tr>\n          ))}\n        </ScrollableBody>\n      </TableElement>\n      {paginated && rows.length > itemsPerPage && (\n        <>\n          <Spacer height={16} />\n          <PaginationContainer>\n            <div>\n              <Btn onClick={() => setPage(Math.max(page - 1, 0))}>&lt;</Btn>\n              <Spacer width={8} />\n              Page: {page + 1} of {Math.floor(rows.length / itemsPerPage) + 1}\n              <Spacer width={8} />\n              <Btn\n                onClick={() => setPage(Math.min(page + 1, Math.floor(rows.length / itemsPerPage)))}\n              >\n                &gt;\n              </Btn>\n            </div>\n          </PaginationContainer>\n        </>\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/Terminal.tsx",
    "content": "import EventEmitter from 'events';\nimport React, { useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';\nimport styled, { css } from 'styled-components';\nimport { Link } from '../Components/CoreUI';\nimport { MythicLabelText } from '../Components/Labels/MythicLabel';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Blue, Green, Invisible, Red, Sub, Subber, Text, White } from '../Components/Text';\nimport { LoadingBarHandle, TextLoadingBar } from '../Components/TextLoadingBar';\nimport dfstyles from '../Styles/dfstyles';\nimport { isFirefox } from '../Utils/BrowserChecks';\nimport { TerminalTextStyle } from '../Utils/TerminalTypes';\n\nconst ENTER_KEY_CODE = 13;\nconst UP_ARROW_KEY_CODE = 38;\nconst ON_INPUT = 'ON_INPUT';\n\nexport interface TerminalHandle {\n  printElement: (element: React.ReactElement) => void;\n  printLoadingBar: (prettyEntityName: string, ref: React.RefObject<LoadingBarHandle>) => void;\n  printLoadingSpinner: () => void;\n  print: (str: string, style?: TerminalTextStyle) => void;\n  println: (str: string, style?: TerminalTextStyle) => void;\n  printShellLn: (str: string) => void;\n  printLink: (str: string, onClick: () => void, style: TerminalTextStyle) => void;\n  focus: () => void;\n  removeLast: (n: number) => void;\n  getInput: () => Promise<string>;\n  newline: () => void;\n  setUserInputEnabled: (enabled: boolean) => void;\n  setInput: (input: string) => void;\n  clear: () => void;\n}\n\nexport interface TerminalProps {\n  promptCharacter: string;\n}\n\nexport const Terminal = React.forwardRef<TerminalHandle | undefined, TerminalProps>(TerminalImpl);\n\nlet terminalLineKey = 0;\n\nfunction TerminalImpl({ promptCharacter }: TerminalProps, ref: React.Ref<TerminalHandle>) {\n  const containerRef = useRef(document.createElement('div'));\n  const inputRef = useRef(document.createElement('textarea'));\n  const heightMeasureRef = useRef(document.createElement('textarea'));\n\n  const [onInputEmitter] = useState(new EventEmitter());\n  const [fragments, setFragments] = useState<React.ReactNode[]>([]);\n  const [userInputEnabled, setUserInputEnabled] = useState<boolean>(false);\n  const [inputText, setInputText] = useState<string>('');\n  const [inputHeight, setInputHeight] = useState<number>(1);\n  const [previousInput, setPreviousInput] = useState<string>('');\n\n  const append = useCallback(\n    (node: React.ReactNode) => {\n      setFragments((lines) => {\n        return [...lines.slice(-199), <span key={terminalLineKey++}>{node}</span>];\n      });\n    },\n    [setFragments]\n  );\n\n  const removeLast = useCallback(\n    (n: number) => {\n      setFragments((lines) => {\n        return [...lines.slice(0, lines.length - n)];\n      });\n    },\n    [setFragments]\n  );\n\n  const newline = useCallback(() => {\n    append(<br />);\n  }, [append]);\n\n  const print = useCallback(\n    (str: string, style = TerminalTextStyle.Sub, onClick: (() => void) | undefined = undefined) => {\n      let fragment: JSX.Element;\n      let innerFragment: JSX.Element = <span>{str}</span>;\n\n      if (onClick !== undefined) {\n        innerFragment = <Link onClick={onClick}>{innerFragment}</Link>;\n      }\n\n      switch (style) {\n        case TerminalTextStyle.Mythic:\n          fragment = <MythicLabelText text={str} />;\n          break;\n        case TerminalTextStyle.Green:\n          fragment = <Green>{innerFragment}</Green>;\n          break;\n        case TerminalTextStyle.Blue:\n          fragment = <Blue>{innerFragment}</Blue>;\n          break;\n        case TerminalTextStyle.Sub:\n          fragment = <Sub>{innerFragment}</Sub>;\n          break;\n        case TerminalTextStyle.Subber:\n          fragment = <Subber>{innerFragment}</Subber>;\n          break;\n        case TerminalTextStyle.Text:\n          fragment = <Text>{innerFragment}</Text>;\n          break;\n        case TerminalTextStyle.White:\n          fragment = <White>{innerFragment}</White>;\n          break;\n        case TerminalTextStyle.Red:\n          fragment = <Red>{innerFragment}</Red>;\n          break;\n        case TerminalTextStyle.Invisible:\n          fragment = <Invisible>{innerFragment}</Invisible>;\n          break;\n        case TerminalTextStyle.Underline:\n          fragment = (\n            <Sub>\n              <u>{innerFragment}</u>\n            </Sub>\n          );\n          break;\n        default:\n          fragment = <Sub>{innerFragment}</Sub>;\n      }\n\n      append(fragment);\n    },\n    [append]\n  );\n\n  const onKeyUp = async (e: React.KeyboardEvent<HTMLTextAreaElement>) => {\n    e.stopPropagation();\n    if (e.keyCode === ENTER_KEY_CODE && !e.shiftKey) {\n      e.preventDefault();\n      print(promptCharacter + ' ', TerminalTextStyle.Green);\n      print(inputText, TerminalTextStyle.Text);\n      newline();\n      onInputEmitter.emit(ON_INPUT, inputText);\n      setPreviousInput(inputText);\n      setInputHeight(1);\n      setInputText('');\n    } else if (e.keyCode === UP_ARROW_KEY_CODE && inputText === '' && previousInput !== '') {\n      setInputHeight(1);\n      setInputText(previousInput);\n    }\n  };\n\n  const preventEnterDefault = (e: React.KeyboardEvent<HTMLTextAreaElement>): void => {\n    e.stopPropagation();\n    if (e.keyCode === ENTER_KEY_CODE && !e.shiftKey) {\n      e.preventDefault();\n    }\n  };\n\n  useEffect(() => {\n    if (userInputEnabled) {\n      inputRef.current.focus();\n    }\n  }, [userInputEnabled]);\n\n  const scrollToEnd = () => {\n    containerRef.current.scrollTo(0, containerRef.current.scrollHeight);\n  };\n\n  useEffect(() => {\n    scrollToEnd();\n  }, [fragments]);\n\n  useEffect(() => {\n    setInputHeight(heightMeasureRef.current.scrollHeight);\n  }, [inputText]);\n\n  useImperativeHandle(\n    ref,\n    () => ({\n      printElement: (element: React.ReactElement) => {\n        append(element);\n      },\n      printLoadingBar: (prettyEntityName: string, ref: React.RefObject<LoadingBarHandle>) => {\n        append(<TextLoadingBar prettyEntityName={prettyEntityName} ref={ref} />);\n      },\n      print: (str: string, style?: TerminalTextStyle) => {\n        print(str, style, undefined);\n      },\n      println: (str: string, style?: TerminalTextStyle) => {\n        print(str, style, undefined);\n        newline();\n      },\n      printLink: (str: string, onClick: () => void, style: TerminalTextStyle) => {\n        print(str, style, onClick);\n      },\n      getInput: async () => {\n        setUserInputEnabled(true);\n        const text = await new Promise<string>((resolve) => {\n          onInputEmitter.once(ON_INPUT, (text: string) => resolve(text.trim()));\n        });\n        setUserInputEnabled(false);\n        return text;\n      },\n      printShellLn: (text: string) => {\n        print(promptCharacter + ' ', TerminalTextStyle.Green);\n        print(text, TerminalTextStyle.Text);\n        newline();\n      },\n      printLoadingSpinner: () => {\n        append(<LoadingSpinner />);\n        newline();\n      },\n      setInput: (input: string) => {\n        if (inputRef.current) {\n          setInputText(input);\n        }\n      },\n      focus: () => {\n        inputRef.current?.focus();\n      },\n      newline,\n      removeLast,\n      setUserInputEnabled,\n      clear: () => {\n        setFragments([]);\n      },\n    }),\n    [onInputEmitter, promptCharacter, newline, print, append, removeLast, setFragments]\n  );\n\n  return (\n    <TerminalContainer ref={containerRef}>\n      {fragments}\n      <Prompt\n        userInputEnabled={userInputEnabled}\n        onClick={() => {\n          if (userInputEnabled) inputRef.current.focus();\n        }}\n      >\n        <Green>{promptCharacter + ' '}</Green>\n        <TextAreas>\n          <InputTextArea\n            height={inputHeight}\n            ref={inputRef}\n            onKeyUp={onKeyUp}\n            onKeyDown={preventEnterDefault}\n            onKeyPress={isFirefox() ? () => {} : preventEnterDefault}\n            value={inputText}\n            onChange={(e) => {\n              if (userInputEnabled) {\n                setInputText(e.target.value);\n              }\n            }}\n          />\n          {/* \"ghost\" textarea used to measure the scrollHeight of the input */}\n          <InputTextArea height={0} ref={heightMeasureRef} onChange={() => {}} value={inputText} />\n        </TextAreas>\n      </Prompt>\n    </TerminalContainer>\n  );\n}\n\nconst Prompt = styled.span`\n  ${({ userInputEnabled }: { userInputEnabled: boolean }) => css`\n    display: flex;\n    justify-content: flex-start;\n    flex-direction: row;\n    opacity: ${userInputEnabled ? 1 : 0};\n  `}\n`;\n\nconst TextAreas = styled.div`\n  display: flex;\n  flex-direction: column;\n  justify-content: flex-start;\n  width: 100%;\n`;\n\nconst InputTextArea = styled.textarea`\n  ${({ height }: { height: number }) => css`\n    background: none;\n    outline: none;\n    border: none;\n    color: ${dfstyles.colors.text};\n    height: ${height}px;\n    resize: none;\n    flex-grow: ${height === 0 ? 0 : 1};\n  `}\n`;\n\nconst TerminalContainer = styled.div`\n  height: 100%;\n  width: 100%;\n  margin: 0 auto;\n  overflow: scroll;\n  white-space: pre-wrap;\n  overflow-wrap: break-word;\n\n  & span {\n    word-break: break-all;\n  }\n\n  @media (max-width: ${dfstyles.screenSizeS}) {\n    font-size: ${dfstyles.fontSizeXS};\n  }\n`;\n"
  },
  {
    "path": "src/Frontend/Views/TopBar.tsx",
    "content": "import { Monomitter } from '@darkforest_eth/events';\nimport { weiToEth } from '@darkforest_eth/network';\nimport { EthAddress, ModalName, TooltipName } from '@darkforest_eth/types';\nimport React, { useEffect, useState } from 'react';\nimport styled from 'styled-components';\nimport { CaptureZonesGeneratedEvent } from '../../Backend/GameLogic/CaptureZoneGenerator';\nimport { Hook } from '../../_types/global/GlobalTypes';\nimport { AlignCenterHorizontally } from '../Components/CoreUI';\nimport { AccountLabel } from '../Components/Labels/Labels';\nimport { Gold, Red, Sub, Text, White } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport { usePlayer, useUIManager } from '../Utils/AppHooks';\nimport { DFZIndex } from '../Utils/constants';\nimport { useEmitterSubscribe, useEmitterValue } from '../Utils/EmitterHooks';\nimport { ModalToggleButton } from './ModalIcon';\nimport { NetworkHealth } from './NetworkHealth';\nimport { Paused } from './Paused';\n\nconst TopBarContainer = styled.div`\n  z-index: ${DFZIndex.MenuBar};\n  padding: 0 2px;\n  width: 530px;\n`;\n\nconst Numbers = styled.div`\n  display: inline-block;\n`;\n\nfunction BoardPlacement({ account }: { account: EthAddress | undefined }) {\n  const uiManager = useUIManager();\n  const player = usePlayer(uiManager, account);\n\n  let content;\n\n  if (!player.value) {\n    content = <Sub>n/a</Sub>;\n  } else {\n    let formattedScore = 'n/a';\n    if (player.value.score !== undefined && player.value.score !== null) {\n      formattedScore = player.value.score.toLocaleString();\n    }\n\n    content = (\n      <Sub>\n        <TooltipTrigger name={TooltipName.Score}>\n          score: <Text>{formattedScore}</Text>\n        </TooltipTrigger>\n      </Sub>\n    );\n  }\n\n  return <Numbers>{content}</Numbers>;\n}\n\nfunction SpaceJunk({ account }: { account: EthAddress | undefined }) {\n  const uiManager = useUIManager();\n\n  const [spaceJunk, setSpaceJunk] = useState<number>(0);\n  const [spaceJunkLimit, setSpaceJunkLimit] = useState<number>(0);\n\n  useEffect(() => {\n    if (!uiManager) return;\n    const gameManager = uiManager.getGameManager();\n\n    const refreshSpaceJunk = () => {\n      if (!account) return;\n\n      setSpaceJunk(gameManager.getPlayerSpaceJunk(account) || 0);\n      setSpaceJunkLimit(gameManager.getPlayerSpaceJunkLimit(account) || 0);\n    };\n\n    const sub = gameManager.playersUpdated$.subscribe(() => {\n      refreshSpaceJunk();\n    });\n    refreshSpaceJunk();\n\n    return () => sub.unsubscribe();\n  }, [uiManager, account]);\n\n  return (\n    <Numbers>\n      <Sub>\n        <TooltipTrigger name={TooltipName.SpaceJunk}>\n          space junk:{' '}\n          <Text>\n            {spaceJunk} / {spaceJunkLimit}\n          </Text>\n        </TooltipTrigger>\n      </Sub>\n    </Numbers>\n  );\n}\n\nfunction CaptureZoneExplanation() {\n  const uiManager = useUIManager();\n\n  const numberedItem = (n: number, content: string) => (\n    <li>\n      <White>{n}.)</White> {content}\n    </li>\n  );\n\n  return (\n    <>\n      <White>Capture Zones:</White> Energy fluctations are creating highly valuable zones of space.{' '}\n      <Gold>\n        Invading and holding planets in these areas give you score! The zones are marked as gold\n        rings on your map.\n      </Gold>\n      <br />\n      <br />\n      In order to capture a planet in a zone, you must:\n      <ol>\n        {numberedItem(1, 'Own a planet in the capture zone.')}\n        {numberedItem(2, 'Start the invasion by clicking the Invade button.')}\n        {numberedItem(\n          3,\n          `Hold the planet for ${uiManager.contractConstants.CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED}\n          blocks.`\n        )}\n        {numberedItem(\n          4,\n          'Capture the planet by clicking the Capture button (Capturing does not require you to be in the zone, only Invading).'\n        )}\n      </ol>\n      <br />\n      <Red>\n        Planets can only be Captured once. However, after an Invasion has started, anyone can\n        capture it.\n      </Red>{' '}\n      If you see an opponent start their Invasion, you can take the planet from them and Capture it\n      for yourself!\n    </>\n  );\n}\n\nfunction CaptureZones({\n  emitter,\n  nextChangeBlock,\n}: {\n  emitter: Monomitter<CaptureZonesGeneratedEvent>;\n  nextChangeBlock: number;\n}) {\n  const uiManager = useUIManager();\n  const currentBlockNumber = useEmitterValue(uiManager.getEthConnection().blockNumber$, undefined);\n  const [nextGenerationBlock, setNextGenerationBlock] = useState(\n    Math.max(\n      uiManager.contractConstants.GAME_START_BLOCK +\n        uiManager.contractConstants.CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL,\n      nextChangeBlock\n    )\n  );\n\n  useEmitterSubscribe(\n    emitter,\n    (zoneGeneration) => {\n      setNextGenerationBlock(zoneGeneration.nextChangeBlock);\n    },\n    [setNextGenerationBlock]\n  );\n\n  return (\n    <Numbers>\n      <TooltipTrigger name={TooltipName.Empty} extraContent={<CaptureZoneExplanation />}>\n        Capture Zones change in {nextGenerationBlock - (currentBlockNumber || 0)} blocks.\n      </TooltipTrigger>\n    </Numbers>\n  );\n}\n\nexport function TopBar({ twitterVerifyHook }: { twitterVerifyHook: Hook<boolean> }) {\n  const uiManager = useUIManager();\n  const player = usePlayer(uiManager);\n  const account = player.value?.address;\n  const twitter = player.value?.twitter;\n  const balance = useEmitterValue(uiManager.getMyBalance$(), uiManager.getMyBalanceBn());\n\n  let captureZones = null;\n  if (uiManager.captureZonesEnabled) {\n    const captureZoneGenerator = uiManager.getCaptureZoneGenerator();\n    if (captureZoneGenerator) {\n      const emitter = captureZoneGenerator.generated$;\n      const nextChangeBlock = captureZoneGenerator.getNextChangeBlock();\n      captureZones = <CaptureZones emitter={emitter} nextChangeBlock={nextChangeBlock} />;\n    }\n  }\n\n  return (\n    <TopBarContainer>\n      <AlignCenterHorizontally style={{ width: '100%', justifyContent: 'space-around' }}>\n        <TooltipTrigger\n          name={TooltipName.Empty}\n          extraContent={<Text>Your burner wallet address.</Text>}\n        >\n          <AccountLabel includeAddressIfHasTwitter={true} width={'50px'} />\n        </TooltipTrigger>\n        <TooltipTrigger\n          name={TooltipName.Empty}\n          extraContent={<Text>Your burner wallet balance.</Text>}\n        >\n          <Sub>({weiToEth(balance).toFixed(2)} xDAI)</Sub>\n        </TooltipTrigger>\n        {process.env.DF_WEBSERVER_URL && (\n          <>\n            <TooltipTrigger\n              name={TooltipName.Empty}\n              extraContent={<Text>Connect your burner wallet to your twitter account.</Text>}\n            >\n              <ModalToggleButton\n                size='small'\n                modal={ModalName.TwitterVerify}\n                hook={twitterVerifyHook}\n                style={\n                  {\n                    width: !twitter ? '100px' : undefined,\n                  } as CSSStyleDeclaration & React.CSSProperties\n                }\n                text={!twitter ? 'Connect' : undefined}\n              />\n            </TooltipTrigger>\n          </>\n        )}\n        <BoardPlacement account={account} />\n      </AlignCenterHorizontally>\n      <AlignCenterHorizontally style={{ justifyContent: 'space-around', width: '100%' }}>\n        {captureZones}\n        {uiManager.getSpaceJunkEnabled() && (\n          <>\n            <SpaceJunk account={account} />\n          </>\n        )}\n      </AlignCenterHorizontally>\n      <NetworkHealth />\n      <Paused />\n    </TopBarContainer>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/UpgradePreview.tsx",
    "content": "import { Planet, Upgrade, UpgradeBranchName } from '@darkforest_eth/types';\nimport React from 'react';\nimport styled from 'styled-components';\nimport { getPlanetMaxRank, getPlanetRank, upgradeName } from '../../Backend/Utils/Utils';\nimport { Icon, IconType } from '../Components/Icons';\nimport { Green, Red, Sub } from '../Components/Text';\nimport dfstyles from '../Styles/dfstyles';\n\nconst StyledUpgradePreview = styled.div`\n  min-width: 15em;\n  width: 100%;\n  border-radius: 3px;\n  border: 1px solid ${dfstyles.colors.borderDark};\n  font-size: ${dfstyles.fontSizeXS};\n`;\n\nconst StatRow = styled.div`\n  display: flex;\n  flex-direction: row;\n  justify-content: center;\n  align-items: center;\n  padding-left: 4px;\n  padding-right: 4px;\n\n  & > span {\n    margin-left: 0.2em;\n\n    &:nth-child(1) {\n      color: ${dfstyles.colors.subtext};\n      margin-left: 0;\n      width: 9em;\n    }\n    &:nth-child(2),\n    &:nth-child(4) {\n      text-align: center;\n      width: 6em;\n      flex-grow: 1;\n    }\n    &:nth-child(3) {\n      // arrow\n      text-align: center;\n      width: 1.5em;\n\n      /* Set the Icon color to something a little dimmer */\n      --df-icon-color: ${dfstyles.colors.subtext};\n    }\n    &:nth-child(5) {\n      width: 5em;\n      text-align: right;\n    }\n  }\n\n  &.upgrade-willupdate {\n    background: ${dfstyles.colors.backgroundlighter};\n  }\n`;\n\nconst StatRowFilled = ({\n  planet,\n  upgrade,\n  title,\n  stat,\n  className,\n}: {\n  planet: Planet | undefined;\n  upgrade: Upgrade | undefined;\n  title: string;\n  stat: string;\n  className?: string;\n}) => {\n  const getStat = (stat: string): number => {\n    if (!planet) return 0;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const mySelected = planet as any;\n\n    if (stat === 'silverGrowth') return mySelected[stat] * 60;\n    else return mySelected[stat];\n  };\n  const statNow = (stat: string): string => {\n    const num = getStat(stat);\n    if (num % 1.0 === 0) return num.toFixed(0);\n    else return num.toFixed(2);\n  };\n  const getStatFuture = (stat: string): number => {\n    if (!planet) return 0;\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    const mySelected = planet as any;\n\n    if (!upgrade) return mySelected[stat];\n\n    let mult = 1;\n    if (stat === 'energyCap') {\n      mult = upgrade.energyCapMultiplier / 100;\n    } else if (stat === 'energyGrowth') {\n      mult = upgrade.energyGroMultiplier / 100;\n    } else if (stat === 'range') {\n      mult = upgrade.rangeMultiplier / 100;\n    } else if (stat === 'speed') {\n      mult = upgrade.speedMultiplier / 100;\n    } else if (stat === 'defense') {\n      mult = upgrade.defMultiplier / 100;\n    }\n\n    return getStat(stat) * mult;\n  };\n  const statFuture = (stat: string): string => {\n    const num = getStatFuture(stat);\n    if (num % 1.0 === 0) return num.toFixed(0);\n    else return num.toFixed(2);\n  };\n  const getStatDiff = (stat: string): number => {\n    return getStatFuture(stat) - getStat(stat);\n  };\n  const statDiff = (stat: string): React.ReactNode => {\n    const diff: number = getStatDiff(stat);\n    if (diff < 0) return <Red>{diff.toFixed(2)}</Red>;\n    else if (diff > 0) return <Green>+{diff.toFixed(2)}</Green>;\n    else return <Sub>0</Sub>;\n  };\n\n  const updateClass = (stat: string): string => {\n    if (getStat(stat) !== getStatFuture(stat)) return 'upgrade-willupdate';\n    return '';\n  };\n\n  return (\n    <StatRow className={[className, updateClass(stat)].join(' ')}>\n      <span>{title}</span>\n      <span>{statNow(stat)}</span>\n      <span>\n        <Icon type={IconType.RightArrow} />\n      </span>\n      <span>{statFuture(stat)}</span>\n      <span>{statDiff(stat)}</span>\n    </StatRow>\n  );\n};\n\nexport function UpgradePreview({\n  planet,\n  upgrade,\n  branchName,\n  cantUpgrade,\n}: {\n  planet: Planet | undefined;\n  upgrade: Upgrade | undefined;\n  branchName: UpgradeBranchName | undefined;\n  cantUpgrade: boolean;\n}) {\n  const branchStrName = branchName !== undefined && upgradeName(branchName);\n  const maxRank = getPlanetMaxRank(planet);\n  const maxBranchRank = Math.min(4, maxRank);\n  const branchUpgradeState = (branchName && planet && planet.upgradeState[branchName]) || 0;\n\n  if (cantUpgrade) {\n    upgrade = {\n      defMultiplier: 100,\n      energyCapMultiplier: 100,\n      energyGroMultiplier: 100,\n      rangeMultiplier: 100,\n      speedMultiplier: 100,\n    };\n  }\n\n  const increment = cantUpgrade ? 0 : 1;\n\n  return (\n    <StyledUpgradePreview>\n      <StatRowFilled planet={planet} upgrade={upgrade} stat='energyCap' title='Energy Cap' />\n      <StatRowFilled planet={planet} upgrade={upgrade} stat='energyGrowth' title='Energy Growth' />\n      <StatRowFilled planet={planet} upgrade={upgrade} title='Range' stat='range' />\n      <StatRowFilled planet={planet} upgrade={upgrade} title='Speed' stat='speed' />\n      <StatRowFilled planet={planet} upgrade={upgrade} title='Defense' stat='defense' />\n      <StatRow className={cantUpgrade ? '' : 'upgrade-willupdate'}>\n        <span>{branchStrName} Rank</span>\n        <span>{branchUpgradeState}</span>\n        <span>\n          <Icon type={IconType.RightArrow} />\n        </span>\n        <span>\n          {branchUpgradeState + increment} of {maxBranchRank} <Sub>max</Sub>\n        </span>\n        <span>{cantUpgrade ? <Sub>0</Sub> : <Green>+1</Green>}</span>\n      </StatRow>\n      <StatRow className={cantUpgrade ? '' : 'upgrade-willupdate'}>\n        <span>Planet Rank</span>\n        <span>{getPlanetRank(planet)}</span>\n        <span>\n          <Icon type={IconType.RightArrow} />\n        </span>\n        <span>\n          {getPlanetRank(planet) + increment} of {maxRank} <Sub>max</Sub>\n        </span>\n        <span>{cantUpgrade ? <Sub>0</Sub> : <Green>+1</Green>}</span>\n      </StatRow>\n    </StyledUpgradePreview>\n  );\n}\n"
  },
  {
    "path": "src/Frontend/Views/WithdrawSilver.tsx",
    "content": "import { isUnconfirmedWithdrawSilverTx } from '@darkforest_eth/serde';\nimport { Planet, PlanetType, TooltipName } from '@darkforest_eth/types';\nimport React, { useCallback, useMemo, useState } from 'react';\nimport styled from 'styled-components';\nimport { Wrapper } from '../../Backend/Utils/Wrapper';\nimport { Hook } from '../../_types/global/GlobalTypes';\nimport { Btn } from '../Components/Btn';\nimport { CenterBackgroundSubtext } from '../Components/CoreUI';\nimport { DarkForestNumberInput, NumberInput } from '../Components/Input';\nimport { LoadingSpinner } from '../Components/LoadingSpinner';\nimport { Row } from '../Components/Row';\nimport { Red } from '../Components/Text';\nimport { TooltipTrigger } from '../Panes/Tooltip';\nimport dfstyles from '../Styles/dfstyles';\nimport { useUIManager } from '../Utils/AppHooks';\n\nconst StyledSilverInput = styled.div`\n  width: fit-content;\n  display: inline-flex;\n  flex-direction: row;\n  align-items: center;\n`;\n\nconst AllBtn = styled.div`\n  color: ${dfstyles.colors.subtext};\n  font-size: ${dfstyles.fontSizeS};\n  &:hover {\n    cursor: pointer;\n    text-decoration: underline;\n  }\n`;\n\nconst InputWrapper = styled.div`\n  width: 5em;\n  margin-right: 0.5em;\n`;\n\nfunction SilverInput({\n  amt,\n  setAmt,\n  wrapper,\n}: {\n  amt: number | undefined;\n  setAmt: Hook<number | undefined>[1];\n  wrapper: Wrapper<Planet | undefined>;\n}) {\n  const click = useCallback(() => {\n    if (wrapper.value) setAmt(wrapper.value.silver);\n  }, [wrapper, setAmt]);\n\n  return (\n    <StyledSilverInput>\n      <InputWrapper>\n        <NumberInput\n          onChange={(e: Event & React.ChangeEvent<DarkForestNumberInput>) => setAmt(e.target.value)}\n          value={amt}\n        />\n      </InputWrapper>\n      <AllBtn onClick={click}>all</AllBtn>\n    </StyledSilverInput>\n  );\n}\n\nconst TextWrapper = styled.span`\n  width: 120px;\n  font-size: ${dfstyles.fontSizeXS};\n  text-align: center;\n`;\n\nexport function WithdrawSilver({ wrapper }: { wrapper: Wrapper<Planet | undefined> }) {\n  const uiManager = useUIManager();\n\n  const [error, setError] = useState<boolean>(false);\n  const [amt, setAmt] = useState<number | undefined>(0);\n\n  const withdraw = useCallback(\n    (silver: number | undefined) => {\n      if (!wrapper.value) return;\n      if (typeof silver !== 'number') {\n        setError(true);\n      } else {\n        uiManager.withdrawSilver(wrapper.value.locationId, silver);\n      }\n      setAmt(0);\n    },\n    [wrapper, uiManager]\n  );\n\n  const withdrawing = useMemo(\n    () => !!wrapper.value?.transactions?.hasTransaction(isUnconfirmedWithdrawSilverTx),\n    [wrapper]\n  );\n  const empty = useMemo(() => !!(wrapper.value && wrapper.value.silver < 1), [wrapper]);\n\n  if (wrapper.value?.planetType === PlanetType.TRADING_POST) {\n    return (\n      <>\n        {error && (\n          <Row>\n            <Red>Error with amount entered.</Red>\n          </Row>\n        )}\n        <Row>\n          <SilverInput amt={amt} setAmt={setAmt} wrapper={wrapper} />\n          <TooltipTrigger name={TooltipName.WithdrawSilverButton}>\n            <Btn onClick={() => withdraw(amt)} disabled={withdrawing || empty}>\n              <TextWrapper>\n                {withdrawing ? <LoadingSpinner initialText='Withdrawing...' /> : 'Withdraw Silver'}\n              </TextWrapper>\n            </Btn>\n          </TooltipTrigger>\n        </Row>\n      </>\n    );\n  } else {\n    return (\n      <CenterBackgroundSubtext width='100%' height='75px'>\n        Select a Spacetime Rip\n      </CenterBackgroundSubtext>\n    );\n  }\n}\n"
  },
  {
    "path": "src/_types/darkforest/api/ChunkStoreTypes.ts",
    "content": "import type { Abstract, LocationId, Rectangle } from '@darkforest_eth/types';\n\n/**\n * one of \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\n */\nexport type BucketId = Abstract<string, 'BucketId'>;\n\n/**\n * Don't worry about the values here. Never base code off the values here. PLEASE.\n */\nexport type ChunkId = Abstract<string, 'ChunkId'>;\n\n/**\n * Chunks represent map data in some rectangle. This type represents a chunk when it is at rest in\n * IndexedDB. The reason for this type's existence is that we want to reduce the amount of data we\n * store on the user's computer. Shorter names hopefully means less data.\n */\nexport interface PersistedChunk {\n  x: number; // left\n  y: number; // bottom\n  s: number; // side length\n  l: PersistedLocation[];\n  p: number; // approximate avg perlin value. used for rendering\n}\n\n/**\n * A location is a point sample of the universe. This type represents that point sample at rest when\n * it is stored in IndexedDB.\n */\nexport interface PersistedLocation {\n  x: number;\n  y: number;\n  h: LocationId;\n  p: number; // perlin\n  b: number; // biomebase perlin\n}\n\n/**\n * Abstract interface shared between different types of chunk stores. Currently we have one that\n * writes to IndexedDB, and one that simply throws away the data.\n */\nexport interface ChunkStore {\n  hasMinedChunk: (chunkFootprint: Rectangle) => boolean;\n}\n"
  },
  {
    "path": "src/_types/darkforest/api/ContractsAPITypes.ts",
    "content": "import { ArtifactPointValues, EthAddress, UpgradeBranches } from '@darkforest_eth/types';\nimport { BigNumber as EthersBN } from 'ethers';\n\nexport const enum ZKArgIdx {\n  PROOF_A,\n  PROOF_B,\n  PROOF_C,\n  DATA,\n}\n\nexport const enum InitArgIdxs {\n  LOCATION_ID,\n  PERLIN,\n  RADIUS,\n  PLANETHASH_KEY,\n  SPACETYPE_KEY,\n  PERLIN_LENGTH_SCALE,\n  PERLIN_MIRROR_X,\n  PERLIN_MIRROR_Y,\n}\n\nexport const enum MoveArgIdxs {\n  FROM_ID,\n  TO_ID,\n  TO_PERLIN,\n  TO_RADIUS,\n  DIST_MAX,\n  PLANETHASH_KEY,\n  SPACETYPE_KEY,\n  PERLIN_LENGTH_SCALE,\n  PERLIN_MIRROR_X,\n  PERLIN_MIRROR_Y,\n  SHIPS_SENT,\n  SILVER_SENT,\n  ARTIFACT_SENT,\n}\n\nexport const enum UpgradeArgIdxs {\n  LOCATION_ID,\n  UPGRADE_BRANCH,\n}\n\nexport const enum ContractEvent {\n  PlayerInitialized = 'PlayerInitialized',\n  ArrivalQueued = 'ArrivalQueued',\n  PlanetUpgraded = 'PlanetUpgraded',\n  PlanetHatBought = 'PlanetHatBought',\n  PlanetTransferred = 'PlanetTransferred',\n  PlanetInvaded = 'PlanetInvaded',\n  PlanetCaptured = 'PlanetCaptured',\n  LocationRevealed = 'LocationRevealed',\n  ArtifactFound = 'ArtifactFound',\n  ArtifactDeposited = 'ArtifactDeposited',\n  ArtifactWithdrawn = 'ArtifactWithdrawn',\n  ArtifactActivated = 'ArtifactActivated',\n  ArtifactDeactivated = 'ArtifactDeactivated',\n  PlanetSilverWithdrawn = 'PlanetSilverWithdrawn',\n  AdminOwnershipChanged = 'AdminOwnershipChanged',\n  AdminGiveSpaceship = 'AdminGiveSpaceship',\n  PauseStateChanged = 'PauseStateChanged',\n  LobbyCreated = 'LobbyCreated',\n}\n\nexport const enum ContractsAPIEvent {\n  PlayerUpdate = 'PlayerUpdate',\n  PlanetUpdate = 'PlanetUpdate',\n  PauseStateChanged = 'PauseStateChanged',\n  ArrivalQueued = 'ArrivalQueued',\n  ArtifactUpdate = 'ArtifactUpdate',\n  RadiusUpdated = 'RadiusUpdated',\n  LocationRevealed = 'LocationRevealed',\n  /**\n   * The transaction has been queued for future execution.\n   */\n  TxQueued = 'TxQueued',\n  /**\n   * The transaction has been removed from the queue and is\n   * calculating arguments in preparation for submission.\n   */\n  TxProcessing = 'TxProcessing',\n  /**\n   * The transaction is queued, but is prioritized for execution\n   * above other queued transactions.\n   */\n  TxPrioritized = 'TxPrioritized',\n  /**\n   * The transaction has been submitted and we are awaiting\n   * confirmation.\n   */\n  TxSubmitted = 'TxSubmitted',\n  /**\n   * The transaction has been confirmed.\n   */\n  TxConfirmed = 'TxConfirmed',\n  /**\n   * The transaction has failed for some reason. This\n   * could either be a revert or a purely client side\n   * error. In the case of a revert, the transaction hash\n   * will be included in the transaction object.\n   */\n  TxErrored = 'TxErrored',\n  /**\n   * The transaction was cancelled before it left the queue.\n   */\n  TxCancelled = 'TxCancelled',\n  PlanetTransferred = 'PlanetTransferred',\n  PlanetClaimed = 'PlanetClaimed',\n  LobbyCreated = 'LobbyCreated',\n}\n\n// planet locationID(BigInt), branch number\nexport type UpgradeArgs = [string, string];\n\nexport type MoveArgs = [\n  [string, string], // proofA\n  [\n    // proofB\n    [string, string],\n    [string, string]\n  ],\n  [string, string], // proofC\n  [\n    string, // from locationID (BigInt)\n    string, // to locationID (BigInt)\n    string, // perlin at to\n    string, // radius at to\n    string, // distMax\n    string, // planetHashKey\n    string, // spaceTypeKey\n    string, // perlin lengthscale\n    string, // perlin xmirror (1 true, 0 false)\n    string, // perlin ymirror (1 true, 0 false)\n    string, // ships sent\n    string, // silver sent\n    string, // artifactId sent\n    string // is planet being released (1 true, 0 false)\n  ]\n];\n\n// Same as reveal args with Explicit coords attached\nexport type ClaimArgs = [\n  [string, string],\n  [[string, string], [string, string]],\n  [string, string],\n  [string, string, string, string, string, string, string, string, string]\n];\n\nexport type DepositArtifactArgs = [string, string]; // locationId, artifactId\nexport type WithdrawArtifactArgs = [string, string]; // locationId, artifactId\nexport type WhitelistArgs = [string, string]; // hashed whitelist key, recipient address\n\nexport type PlanetTypeWeights = [number, number, number, number, number]; // relative frequencies of the 5 planet types\nexport type PlanetTypeWeightsByLevel = [\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights,\n  PlanetTypeWeights\n];\nexport type PlanetTypeWeightsBySpaceType = [\n  PlanetTypeWeightsByLevel,\n  PlanetTypeWeightsByLevel,\n  PlanetTypeWeightsByLevel,\n  PlanetTypeWeightsByLevel\n];\n\nexport interface ContractConstants {\n  ADMIN_CAN_ADD_PLANETS: boolean;\n  WORLD_RADIUS_LOCKED: boolean;\n  WORLD_RADIUS_MIN: number;\n\n  DISABLE_ZK_CHECKS: boolean;\n\n  PLANETHASH_KEY: number;\n  SPACETYPE_KEY: number;\n  BIOMEBASE_KEY: number;\n  PERLIN_LENGTH_SCALE: number;\n  PERLIN_MIRROR_X: boolean;\n  PERLIN_MIRROR_Y: boolean;\n\n  TOKEN_MINT_END_SECONDS: number;\n\n  MAX_NATURAL_PLANET_LEVEL: number;\n  TIME_FACTOR_HUNDREDTHS: number;\n  /**\n   * The perlin value at each coordinate determines the space type. There are four space\n   * types, which means there are four ranges on the number line that correspond to\n   * each space type. This function returns the boundary values between each of these\n   * four ranges: `PERLIN_THRESHOLD_1`, `PERLIN_THRESHOLD_2`, `PERLIN_THRESHOLD_3`.\n   */\n  PERLIN_THRESHOLD_1: number;\n  PERLIN_THRESHOLD_2: number;\n  PERLIN_THRESHOLD_3: number;\n  INIT_PERLIN_MIN: number;\n  INIT_PERLIN_MAX: number;\n  SPAWN_RIM_AREA: number;\n  BIOME_THRESHOLD_1: number;\n  BIOME_THRESHOLD_2: number;\n  PLANET_RARITY: number;\n  /**\n     The chance for a planet to be a specific level.\n     Each index corresponds to a planet level (index 5 is level 5 planet).\n     The lower the number the lower the chance.\n     Note: This does not control if a planet spawns or not, just the level\n     when it spawns.\n   */\n  PLANET_LEVEL_THRESHOLDS: [\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number\n  ];\n  PLANET_TRANSFER_ENABLED: boolean;\n  PLANET_TYPE_WEIGHTS: PlanetTypeWeightsBySpaceType;\n  ARTIFACT_POINT_VALUES: ArtifactPointValues;\n  /**\n   * How much score silver gives when withdrawing.\n   * Expressed as a percentage integer.\n   * (100 is 100%)\n   */\n  SILVER_SCORE_VALUE: number;\n  // Space Junk\n  SPACE_JUNK_ENABLED: boolean;\n  /**\n     Total amount of space junk a player can take on.\n     This can be overridden at runtime by updating\n     this value for a specific player in storage.\n   */\n  SPACE_JUNK_LIMIT: number;\n  /**\n     The amount of junk that each level of planet\n     gives the player when moving to it for the\n     first time.\n   */\n  PLANET_LEVEL_JUNK: [\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number\n  ];\n  /**\n     The speed boost a movement receives when abandoning\n     a planet.\n   */\n  ABANDON_SPEED_CHANGE_PERCENT: number;\n  /**\n     The range boost a movement receives when abandoning\n     a planet.\n   */\n  ABANDON_RANGE_CHANGE_PERCENT: number;\n\n  PHOTOID_ACTIVATION_DELAY: number;\n  LOCATION_REVEAL_COOLDOWN: number;\n  CLAIM_PLANET_COOLDOWN?: number;\n\n  defaultPopulationCap: number[];\n  defaultPopulationGrowth: number[];\n\n  defaultSilverCap: number[];\n  defaultSilverGrowth: number[];\n\n  defaultRange: number[];\n  defaultSpeed: number[];\n  defaultDefense: number[];\n  defaultBarbarianPercentage: number[];\n\n  planetLevelThresholds: number[];\n  planetCumulativeRarities: number[];\n\n  upgrades: UpgradeBranches;\n\n  adminAddress: EthAddress;\n\n  // Capture Zones\n  GAME_START_BLOCK: number;\n  CAPTURE_ZONES_ENABLED: boolean;\n  CAPTURE_ZONE_COUNT: number;\n  CAPTURE_ZONE_CHANGE_BLOCK_INTERVAL: number;\n  CAPTURE_ZONE_RADIUS: number;\n  CAPTURE_ZONE_PLANET_LEVEL_SCORE: [\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number,\n    number\n  ];\n  CAPTURE_ZONE_HOLD_BLOCKS_REQUIRED: number;\n  CAPTURE_ZONES_PER_5000_WORLD_RADIUS: number;\n}\n\nexport type ClientMockchainData =\n  | null\n  | undefined\n  | number\n  | string\n  | boolean\n  | EthersBN\n  | ClientMockchainData[]\n  | {\n      [key in string | number]: ClientMockchainData;\n    };\n\nexport const enum PlanetEventType {\n  ARRIVAL,\n}\n"
  },
  {
    "path": "src/_types/darkforest/api/UtilityServerAPITypes.ts",
    "content": "export type AddressTwitterMap = {\n  [ethAddress: string]: string;\n};\n"
  },
  {
    "path": "src/_types/file-loader/FileWorkerTypes.ts",
    "content": "declare module '@darkforest_eth/snarks/*.wasm' {\n  const path: string;\n  export default path;\n}\ndeclare module '@darkforest_eth/snarks/*.zkey' {\n  const path: string;\n  export default path;\n}\ndeclare module '@darkforest_eth/contracts/abis/*.json' {\n  const path: string;\n  export default path;\n}\n"
  },
  {
    "path": "src/_types/global/GlobalTypes.ts",
    "content": "import { Rectangle } from '@darkforest_eth/types';\nimport { Dispatch, SetStateAction } from 'react';\nimport GameManager from '../../Backend/GameLogic/GameManager';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\n\nexport type Hook<T> = [T, Dispatch<SetStateAction<T>>];\n\ndeclare global {\n  interface Window {\n    /* eslint-disable-next-line @typescript-eslint/no-explicit-any */\n    snarkjs: any;\n\n    // TODO: these three should eventually live in some sort of `DFTerminal` namespace\n    // instead of global\n    df?: GameManager;\n    ui?: GameUIManager;\n\n    // injected into global scope via netlify snippets - this is a permalink\n    // to the deployment hosted on netlify.\n    DEPLOY_URL?: string;\n  }\n}\n\nexport type HashConfig = {\n  planetHashKey: number;\n  spaceTypeKey: number;\n  biomebaseKey: number;\n  perlinLengthScale: number; // power of two up to 8192\n  perlinMirrorX: boolean;\n  perlinMirrorY: boolean;\n  planetRarity: number; // only for fakeHash (DISABLE_ZK_CHECKS on)\n};\n\nexport const enum StatIdx {\n  EnergyCap = 0,\n  EnergyGro = 1,\n  Range = 2,\n  Speed = 3,\n  Defense = 4,\n  SpaceJunk = 5,\n}\n\nexport interface MinerWorkerMessage {\n  chunkFootprint: Rectangle;\n  workerIndex: number;\n  totalWorkers: number;\n  planetRarity: number;\n  jobId: number;\n  useMockHash: boolean;\n  planetHashKey: number;\n  spaceTypeKey: number;\n  biomebaseKey: number;\n  perlinLengthScale: number;\n  perlinMirrorX: boolean;\n  perlinMirrorY: boolean;\n}\n\n// info about when the player can next reveal coordinates\nexport interface RevealCountdownInfo {\n  myLastRevealTimestamp?: number; // if undefined, never revealed before\n  currentlyRevealing: boolean; // true iff player has an unconfirmedReveal currently being processed\n  revealCooldownTime: number; // in seconds\n}\n\nexport interface ClaimCountdownInfo {\n  myLastClaimTimestamp?: number; // if undefined, never revealed before\n  currentlyClaiming: boolean; // true iff player has an unconfirmedReveal currently being processed\n  claimCooldownTime: number; // in seconds\n}\n"
  },
  {
    "path": "src/_types/global/global.d.ts",
    "content": "/**\n * This file declares globals that are available to all plugins.\n */\n\nimport GameManager from '../../Backend/GameLogic/GameManager';\nimport GameUIManager from '../../Backend/GameLogic/GameUIManager';\n\ndeclare global {\n  const df: GameManager;\n  const ui: GameUIManager;\n\n  // TODO: Figure out a way to share this\n  /**\n   * All plugins must conform to this interface. Provides facilities for\n   * displaying an interactive UI, as well as references to game state,\n   * which are set externally.\n   */\n  interface DFPlugin {\n    /**\n     * If present, called once when the user clicks 'run' in the plugin\n     * manager modal.\n     */\n    render?: (div: HTMLDivElement) => Promise<void>;\n\n    /**\n     * If present, called at the same framerate the the game is running at,\n     * and allows you to draw on top of the game UI.\n     */\n    draw?: (ctx: CanvasRenderingContext2D) => void;\n\n    /**\n     * Called when the plugin is unloaded. Plugins unload whenever the\n     * plugin is edited (modified and saved, or deleted).\n     */\n    destroy?: () => void;\n  }\n}\n"
  },
  {
    "path": "tsconfig.decs.json",
    "content": "// This file exists because the base tsconfig uses `noEmit`, but we still\n// want to generate the declaration files upon mirroring the project\n{\n  \"extends\": \"./tsconfig.json\",\n  \"include\": [\"src/**/*\"],\n  \"compilerOptions\": {\n    \"rootDir\": \"src\",\n    \"noEmit\": false,\n    \"emitDeclarationOnly\": true,\n    \"declaration\": true,\n    \"declarationDir\": \"./declarations\"\n  }\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"include\": [\n    \"src/**/*.ts\",\n    \"src/**/*.tsx\",\n    \"embedded_plugins/**/*.ts\",\n    \"embedded_plugins/**/*.tsx\",\n    \"plugins/**/*.ts\",\n    \"plugins/**/*.tsx\"\n  ],\n  \"compilerOptions\": {\n    \"module\": \"esnext\",\n    \"target\": \"es2020\",\n    \"sourceMap\": true,\n    \"noImplicitAny\": true,\n    \"strictNullChecks\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"jsx\": \"react\", // use typescript to transpile jsx to js\n    \"allowJs\": true, // allow a partial TypeScript and JavaScript codebase\n    \"moduleResolution\": \"Node\",\n    \"lib\": [\"es2020\", \"dom\"],\n    \"skipLibCheck\": true,\n    \"noEmit\": true\n  },\n  \"typedocOptions\": {\n    \"entryPointStrategy\": \"expand\",\n    \"entryPoints\": [\"src\"],\n    \"out\": \"docs\",\n    \"hideBreadcrumbs\": true,\n    \"readme\": \"none\",\n    \"disableSources\": true,\n    \"cleanOutputDir\": false,\n    \"githubPages\": false,\n    \"excludeExternals\": true,\n    \"listInvalidSymbolLinks\": true\n  }\n}\n"
  },
  {
    "path": "tsconfig.ref.json",
    "content": "{\n  \"extends\": \"./tsconfig.json\",\n  \"references\": [\n    {\n      \"path\": \"../packages/constants/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/contracts/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/events/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/gamelogic/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/hashing/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/hexgen/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/network/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/procedural/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/renderer/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/serde/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/settings/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/snarks/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/types/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/ui/tsconfig.ref.json\"\n    },\n    {\n      \"path\": \"../packages/whitelist/tsconfig.ref.json\"\n    }\n  ],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"declarationMap\": true\n  }\n}\n"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\nconst dotenv = require('dotenv');\n\ndotenv.config();\n\nconst resolvePackage = require('resolve-package-path');\nconst { EnvironmentPlugin } = require('webpack');\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\nconst CopyPlugin = require('copy-webpack-plugin');\nconst ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin');\n\n// This code is used to lookup where the `@darkforest_eth` packages exist in the tree\n// whether they are in a monorepo or installed as packages\nfunction findScopeDirectory() {\n  // Just chose the most likely package to be here, it could really be anything\n  const pkg = '@darkforest_eth/contracts';\n  const contractsPackageJson = resolvePackage(pkg, __dirname);\n  if (!contractsPackageJson) {\n    throw new Error(`Unable to find the @darkforest_eth scope. Exiting...`);\n  }\n  const contractsDirectory = path.dirname(contractsPackageJson);\n  const scopeDirectory = path.dirname(contractsDirectory);\n\n  return scopeDirectory;\n}\n\nmodule.exports = {\n  mode: 'production',\n  entry: ['./src/Frontend/EntryPoints/index.tsx'],\n  output: {\n    path: path.join(__dirname, '/dist'),\n    filename: 'bundle-[contenthash].min.js',\n    publicPath: '/',\n    clean: true,\n  },\n\n  // Enable sourcemaps for debugging webpack's output.\n  devtool: 'source-map',\n  devServer: {\n    port: 8081,\n    historyApiFallback: true,\n  },\n\n  resolve: {\n    // Add '.ts' and '.tsx' as resolvable extensions.\n    extensions: ['.ts', '.tsx', '...'],\n    // Adding an alias for the `@darkforest_eth` packages, whether in a monorepo or packages\n    alias: {\n      '@darkforest_eth': findScopeDirectory(),\n    },\n  },\n\n  module: {\n    rules: [\n      // Still depends on raw-loader here, with the javascript/auto content type,\n      // because otherwise the module can't be imported in PluginManager\n      {\n        test: /\\.[jt]sx?$/,\n        include: [path.join(__dirname, './embedded_plugins/')],\n        type: 'javascript/auto',\n        use: ['raw-loader', 'babel-loader'],\n      },\n      {\n        test: /\\.ts(x?)$/,\n        include: [path.join(__dirname, './src/')],\n        use: ['babel-loader'],\n      },\n      {\n        test: /\\.css$/,\n        use: ['style-loader', 'css-loader'],\n      },\n      {\n        test: /\\.(woff(2)?|ttf|eot|svg)$/,\n        include: [path.join(__dirname, './src/')],\n        type: 'asset/resource',\n        generator: {\n          filename: 'fonts/[name][ext]',\n        },\n      },\n      // Any wasm, zkye, or json files from other packages should be loaded as a plain file\n      {\n        test: /\\.(wasm|zkey|json)$/,\n        type: 'asset/resource',\n      },\n      // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'\n      {\n        enforce: 'pre',\n        test: /\\.js$/,\n        loader: 'source-map-loader',\n        options: {\n          filterSourceMappingUrl(url, resourcePath) {\n            // The sourcemaps in react-sortable are screwed up\n            if (resourcePath.includes('react-sortablejs')) {\n              return false;\n            }\n\n            return true;\n          },\n        },\n      },\n    ],\n  },\n  plugins: [\n    // We use ForkTsChecker plugin to run typechecking on `src/`\n    // in the background and report errors into the frontent UI\n    new ForkTsCheckerWebpackPlugin({\n      typescript: {\n        diagnosticOptions: {\n          semantic: true,\n          syntactic: true,\n        },\n        mode: 'readonly',\n      },\n    }),\n    // The string values are fallbacks if the env variable is not set\n    new EnvironmentPlugin({\n      NODE_ENV: 'development',\n      DEFAULT_RPC: 'https://rpc-df.xdaichain.com/',\n      // This must be null to indicate to webpack that this environment variable is optional\n      DF_WEBSERVER_URL: null,\n    }),\n    new HtmlWebpackPlugin({\n      template: './index.html',\n    }),\n    new CopyPlugin({\n      patterns: [{ from: 'public', to: 'public' }],\n    }),\n  ],\n};\n"
  }
]